Skip to content
This repository has been archived by the owner on Jan 24, 2019. It is now read-only.

Apply HMAC signatures to upstream requests #147

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Godeps
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
github.com/18F/hmacauth 1.0.1
github.com/BurntSushi/toml 3883ac1ce943878302255f538fce319d23226223
github.com/bitly/go-simplejson 3378bdcb5cebedcbf8b5750edee28010f128fe24
github.com/mreiferson/go-options ee94b57f2fbf116075426f853e5abbcdfeca8b3d
Expand Down
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,16 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i

```
Usage of oauth2_proxy:
-approval_prompt="force": Oauth approval_prompt
-approval-prompt="force": Oauth approval_prompt
-authenticated-emails-file="": authenticate against emails via file (one per line)
-basic-auth-password="": the password to set when passing the HTTP Basic Auth header
-client-id="": the OAuth Client ID: ie: "123456.apps.googleusercontent.com"
-client-secret="": the OAuth Client Secret
-config="": path to config file
-cookie-domain="": an optional cookie domain to force cookies to (ie: .yourcompany.com)*
-cookie-expire=168h0m0s: expire timeframe for cookie
-cookie-httponly=true: set HttpOnly cookie flag
-cookie-key="_oauth2_proxy": the name of the cookie that the oauth_proxy creates
-cookie-name="_oauth2_proxy": the name of the cookie that the oauth_proxy creates
-cookie-refresh=0: refresh the cookie after this duration; 0 to disable
-cookie-secret="": the seed string for secure cookies
-cookie-secure=true: set secure (HTTPS) cookie flag
Expand All @@ -130,17 +131,15 @@ Usage of oauth2_proxy:
-email-domain=: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
-github-org="": restrict logins to members of this organisation
-github-team="": restrict logins to members of this team
-google-group="": restrict logins to members of this google group
-google-admin-email="": the google admin to impersonate for api calls
-google-group=: restrict logins to members of this google group (may be given multiple times).
-google-service-account-json="": the path to the service account json credentials

-htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
-http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients
-https-address=":443": <addr>:<port> to listen on for HTTPS clients
-login-url="": Authentication endpoint
-pass-access-token=false: pass OAuth access_token to upstream via X-Forwarded-Access-Token header
-pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
-basic-auth-password="": the password to set when passing the HTTP Basic Auth header
-pass-host-header=true: pass the request Host Header to upstream
-profile-url="": Profile access endpoint
-provider="google": OAuth provider
Expand All @@ -149,6 +148,7 @@ Usage of oauth2_proxy:
-redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
-request-logging=true: Log requests to stdout
-scope="": Oauth scope specification
-signature-key="": default request signature key
-skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
-tls-cert="": path to certificate file
-tls-key="": path to private key file
Expand Down Expand Up @@ -250,6 +250,30 @@ OAuth2 Proxy responds directly to the following endpoints. All other endpoints w
* /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url.
* /oauth2/auth - only returns a 202 Accepted response or a 401 Unauthorized response; for use with the [Nginx `auth_request` directive](#nginx-auth-request)

## Request signatures

If `signature_key` is defined, proxied requests will be signed with the
`GAP-Signature header, which is a [Hash-based Message Authentication Code
(HMAC)](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)
of selected request information and the request body [see `SIGNATURE_HEADERS`
in `oauthproxy.go`](./oauthproxy.go).

If no `signature_key` is set, no signature will be applied.

`signature_key` must be of the form `algorithm:secretkey`, e.g. `

```
signature_key = sha1:secret0
```

For more information about HMAC request signature validation, read the
following:

* [Amazon Web Services: Signing and Authenticating REST
Requests](https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html)
* [rc3.org: Using HMAC to authenticate Web service
requests](http://rc3.org/2011/12/02/using-hmac-to-authenticate-web-service-requests/)

## Logging Format

OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
Expand All @@ -258,7 +282,6 @@ OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
```


## Adding a new Provider

Follow the examples in the [`providers` package](providers/) to define a new
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func main() {
flagSet.String("scope", "", "OAuth scope specification")
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")

flagSet.String("signature-key", "", "default request signature key")

flagSet.Parse(os.Args[1:])

if *showVersion {
Expand Down
31 changes: 29 additions & 2 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,26 @@ import (
"strings"
"time"

"github.com/18F/hmacauth"
"github.com/bitly/oauth2_proxy/cookie"
"github.com/bitly/oauth2_proxy/providers"
)

const SignatureHeader = "GAP-Signature"

var SignatureHeaders []string = []string{
"Content-Length",
"Content-Md5",
"Content-Type",
"Date",
"Authorization",
"X-Forwarded-User",
"X-Forwarded-Email",
"X-Forwarded-Access-Token",
"Cookie",
"Gap-Auth",
}

type OAuthProxy struct {
CookieSeed string
CookieName string
Expand Down Expand Up @@ -54,10 +70,15 @@ type OAuthProxy struct {
type UpstreamProxy struct {
upstream string
handler http.Handler
auth hmacauth.HmacAuth
}

func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("GAP-Upstream-Address", u.upstream)
if u.auth != nil {
r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
u.auth.SignRequest(r)
}
u.handler.ServeHTTP(w, r)
}

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

func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
serveMux := http.NewServeMux()
var auth hmacauth.HmacAuth
if sigData := opts.signatureData; sigData != nil {
auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
SignatureHeader, SignatureHeaders)
}
for _, u := range opts.proxyURLs {
path := u.Path
switch u.Scheme {
Expand All @@ -101,14 +127,15 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
} else {
setProxyDirector(proxy)
}
serveMux.Handle(path, &UpstreamProxy{u.Host, proxy})
serveMux.Handle(path,
&UpstreamProxy{u.Host, proxy, auth})
case "file":
if u.Fragment != "" {
path = u.Fragment
}
log.Printf("mapping path %q => file system %q", path, u.Path)
proxy := NewFileServer(path, u.Path)
serveMux.Handle(path, &UpstreamProxy{path, proxy})
serveMux.Handle(path, &UpstreamProxy{path, proxy, nil})
default:
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
}
Expand Down
Loading