Skip to content

Commit e4626c1

Browse files
Mike Blandjehiah
Mike Bland
authored andcommitted
Sign Upstream requests with HMAC. closes #147
1 parent 7c241ec commit e4626c1

File tree

7 files changed

+298
-69
lines changed

7 files changed

+298
-69
lines changed

Godeps

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
github.com/18F/hmacauth 1.0.1
12
github.com/BurntSushi/toml 3883ac1ce943878302255f538fce319d23226223
23
github.com/bitly/go-simplejson 3378bdcb5cebedcbf8b5750edee28010f128fe24
34
github.com/mreiferson/go-options ee94b57f2fbf116075426f853e5abbcdfeca8b3d

README.md

+23-6
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,16 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i
113113

114114
```
115115
Usage of oauth2_proxy:
116-
-approval_prompt="force": Oauth approval_prompt
116+
-approval-prompt="force": Oauth approval_prompt
117117
-authenticated-emails-file="": authenticate against emails via file (one per line)
118+
-basic-auth-password="": the password to set when passing the HTTP Basic Auth header
118119
-client-id="": the OAuth Client ID: ie: "123456.apps.googleusercontent.com"
119120
-client-secret="": the OAuth Client Secret
120121
-config="": path to config file
121122
-cookie-domain="": an optional cookie domain to force cookies to (ie: .yourcompany.com)*
122123
-cookie-expire=168h0m0s: expire timeframe for cookie
123124
-cookie-httponly=true: set HttpOnly cookie flag
124-
-cookie-key="_oauth2_proxy": the name of the cookie that the oauth_proxy creates
125+
-cookie-name="_oauth2_proxy": the name of the cookie that the oauth_proxy creates
125126
-cookie-refresh=0: refresh the cookie after this duration; 0 to disable
126127
-cookie-secret="": the seed string for secure cookies
127128
-cookie-secure=true: set secure (HTTPS) cookie flag
@@ -130,17 +131,15 @@ Usage of oauth2_proxy:
130131
-email-domain=: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
131132
-github-org="": restrict logins to members of this organisation
132133
-github-team="": restrict logins to members of this team
133-
-google-group="": restrict logins to members of this google group
134134
-google-admin-email="": the google admin to impersonate for api calls
135+
-google-group=: restrict logins to members of this google group (may be given multiple times).
135136
-google-service-account-json="": the path to the service account json credentials
136-
137137
-htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
138138
-http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients
139139
-https-address=":443": <addr>:<port> to listen on for HTTPS clients
140140
-login-url="": Authentication endpoint
141141
-pass-access-token=false: pass OAuth access_token to upstream via X-Forwarded-Access-Token header
142142
-pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
143-
-basic-auth-password="": the password to set when passing the HTTP Basic Auth header
144143
-pass-host-header=true: pass the request Host Header to upstream
145144
-profile-url="": Profile access endpoint
146145
-provider="google": OAuth provider
@@ -149,6 +148,7 @@ Usage of oauth2_proxy:
149148
-redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
150149
-request-logging=true: Log requests to stdout
151150
-scope="": Oauth scope specification
151+
-signature-key="": GAP-Signature request signature key (algorithm:secretkey)
152152
-skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
153153
-tls-cert="": path to certificate file
154154
-tls-key="": path to private key file
@@ -250,6 +250,24 @@ OAuth2 Proxy responds directly to the following endpoints. All other endpoints w
250250
* /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url.
251251
* /oauth2/auth - only returns a 202 Accepted response or a 401 Unauthorized response; for use with the [Nginx `auth_request` directive](#nginx-auth-request)
252252

253+
## Request signatures
254+
255+
If `signature_key` is defined, proxied requests will be signed with the
256+
`GAP-Signature` header, which is a [Hash-based Message Authentication Code
257+
(HMAC)](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)
258+
of selected request information and the request body [see `SIGNATURE_HEADERS`
259+
in `oauthproxy.go`](./oauthproxy.go).
260+
261+
`signature_key` must be of the form `algorithm:secretkey`, (ie: `signature_key = "sha1:secret0"`)
262+
263+
For more information about HMAC request signature validation, read the
264+
following:
265+
266+
* [Amazon Web Services: Signing and Authenticating REST
267+
Requests](https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html)
268+
* [rc3.org: Using HMAC to authenticate Web service
269+
requests](http://rc3.org/2011/12/02/using-hmac-to-authenticate-web-service-requests/)
270+
253271
## Logging Format
254272

255273
OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
@@ -258,7 +276,6 @@ OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
258276
<REMOTE_ADDRESS> - <[email protected]> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
259277
```
260278

261-
262279
## Adding a new Provider
263280

264281
Follow the examples in the [`providers` package](providers/) to define a new

main.go

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func main() {
6969
flagSet.String("scope", "", "OAuth scope specification")
7070
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")
7171

72+
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
73+
7274
flagSet.Parse(os.Args[1:])
7375

7476
if *showVersion {

oauthproxy.go

+29-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,26 @@ import (
1414
"strings"
1515
"time"
1616

17+
"github.com/18F/hmacauth"
1718
"github.com/bitly/oauth2_proxy/cookie"
1819
"github.com/bitly/oauth2_proxy/providers"
1920
)
2021

22+
const SignatureHeader = "GAP-Signature"
23+
24+
var SignatureHeaders []string = []string{
25+
"Content-Length",
26+
"Content-Md5",
27+
"Content-Type",
28+
"Date",
29+
"Authorization",
30+
"X-Forwarded-User",
31+
"X-Forwarded-Email",
32+
"X-Forwarded-Access-Token",
33+
"Cookie",
34+
"Gap-Auth",
35+
}
36+
2137
type OAuthProxy struct {
2238
CookieSeed string
2339
CookieName string
@@ -54,10 +70,15 @@ type OAuthProxy struct {
5470
type UpstreamProxy struct {
5571
upstream string
5672
handler http.Handler
73+
auth hmacauth.HmacAuth
5774
}
5875

5976
func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6077
w.Header().Set("GAP-Upstream-Address", u.upstream)
78+
if u.auth != nil {
79+
r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
80+
u.auth.SignRequest(r)
81+
}
6182
u.handler.ServeHTTP(w, r)
6283
}
6384

@@ -89,6 +110,11 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
89110

90111
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
91112
serveMux := http.NewServeMux()
113+
var auth hmacauth.HmacAuth
114+
if sigData := opts.signatureData; sigData != nil {
115+
auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
116+
SignatureHeader, SignatureHeaders)
117+
}
92118
for _, u := range opts.proxyURLs {
93119
path := u.Path
94120
switch u.Scheme {
@@ -101,14 +127,15 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
101127
} else {
102128
setProxyDirector(proxy)
103129
}
104-
serveMux.Handle(path, &UpstreamProxy{u.Host, proxy})
130+
serveMux.Handle(path,
131+
&UpstreamProxy{u.Host, proxy, auth})
105132
case "file":
106133
if u.Fragment != "" {
107134
path = u.Fragment
108135
}
109136
log.Printf("mapping path %q => file system %q", path, u.Path)
110137
proxy := NewFileServer(path, u.Path)
111-
serveMux.Handle(path, &UpstreamProxy{path, proxy})
138+
serveMux.Handle(path, &UpstreamProxy{path, proxy, nil})
112139
default:
113140
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
114141
}

0 commit comments

Comments
 (0)