Merge branch 'master' of https://github.com/bitly/oauth2_proxy into HEAD

This commit is contained in:
Pavel Sorokin 2017-06-22 03:06:56 +00:00
commit 47fe902cbc
10 changed files with 141 additions and 95 deletions

View File

@ -1,7 +1,7 @@
language: go
go:
- 1.7.5
- 1.8
- 1.8.1
script:
- curl -s https://raw.githubusercontent.com/pote/gpm/v1.4.0/bin/gpm > gpm
- chmod +x gpm

141
README.md
View File

@ -17,7 +17,7 @@ to validate accounts by email, domain or group.
## Installation
1. Download [Prebuilt Binary](https://github.com/bitly/oauth2_proxy/releases) (current release is `v2.1`) or build with `$ go get github.com/bitly/oauth2_proxy` which will put the binary in `$GOROOT/bin`
1. Download [Prebuilt Binary](https://github.com/bitly/oauth2_proxy/releases) (current release is `v2.2`) or build with `$ go get github.com/bitly/oauth2_proxy` which will put the binary in `$GOROOT/bin`
2. Select a Provider and Register an OAuth Application with a Provider
3. Configure OAuth2 Proxy using config file, command line options, or environment variables
4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx)
@ -44,7 +44,7 @@ For Google, the registration steps are:
1. Create a new project: https://console.developers.google.com/project
2. Choose the new project from the top right project dropdown (only if another project is selected)
3. In the project Dashboard center pane, choose **"Enable and manage APIs"**
3. In the project Dashboard center pane, choose **"API Manager"**
4. In the left Nav pane, choose **"Credentials"**
5. In the center pane, choose **"OAuth consent screen"** tab. Fill in **"Product name shown to users"** and hit save.
6. In the center pane, choose **"Credentials"** tab.
@ -53,7 +53,7 @@ For Google, the registration steps are:
* Choose **"Web application"**
* Application name is freeform, choose something appropriate
* Authorized JavaScript origins is your domain ex: `https://internal.yourcompany.com`
* Authorized redirect URIs is the location of oath2/callback ex: `https://internal.yourcompany.com/oauth2/callback`
* Authorized redirect URIs is the location of oauth2/callback ex: `https://internal.yourcompany.com/oauth2/callback`
* Choose **"Create"**
4. Take note of the **Client ID** and **Client Secret**
@ -72,12 +72,22 @@ https://www.googleapis.com/auth/admin.directory.user.readonly
```
6. Follow the steps on https://support.google.com/a/answer/60757 to enable Admin API access.
7. Create or choose an existing administrative email address on the Gmail domain to assign to the ```google-admin-email``` flag. This email will be impersonated by this client to make calls to the Admin SDK. See the note on the link from step 5 for the reason why.
8. Create or choose an existing email group and set that email to the ```permit-groups``` flag. You can pass multiple instances of this flag with different groups and the user will be checked against all the provided groups.
8. Create or choose an existing email group and set that email to the ```google-group``` flag. You can pass multiple instances of this flag with different groups
and the user will be checked against all the provided groups.
9. Lock down the permissions on the json file downloaded from step 1 so only oauth2_proxy is able to read the file and set the path to the file in the ```google-service-account-json``` flag.
10. Restart oauth2_proxy.
Note: The user is checked against the group members list on initial authentication and every time the token is refreshed ( about once an hour ).
### Azure Auth Provider
1. [Add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/) to your Azure Active Directory tenant.
2. On the App properties page provide the correct Sign-On URL ie `https://internal.yourcompany.com/oauth2/callback`
3. If applicable take note of your `TenantID` and provide it via the `--azure-tenant=<YOUR TENANT ID>` commandline option. Default the `common` tenant is used.
The Azure AD auth provider uses `openid` as it default scope. It uses `https://graph.windows.net` as a default protected resource. It call to `https://graph.windows.net/me` to get the email address of the user that logs in.
### Facebook Auth Provider
1. Create a new FB App from <https://developers.facebook.com/>
@ -125,7 +135,7 @@ For LinkedIn, the registration steps are:
The [MyUSA](https://alpha.my.usa.gov) authentication service ([GitHub](https://github.com/18F/myusa))
### Microsoft Azure Active Directory Provider
### Microsoft Azure AD Provider
1. [Add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/) to your Azure Active Directory tenant.
2. On the App properties page provide the correct Sign-On URL ie `https://internal.yourcompany.com/oauth2/callback`
@ -163,55 +173,59 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i
```
Usage of oauth2_proxy:
-approval-prompt="force": Oauth approval_prompt
-authenticated-emails-file="": authenticate against emails via file (one per line)
-azure-tenant="common": go to a tenant-specific or common (tenant-independent) endpoint.
-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-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
-custom-templates-dir="": path to custom html templates
-display-htpasswd-form=true: display username / password login form if an htpasswd file is provided
-email-domain=: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
-filter-groups="": only pass groups in the X-Forwarded-Groups header that contain this string
-github-org="": restrict logins to members of this organisation
-github-team="": restrict logins to members of this team
-google-admin-email="": the google admin to impersonate for api calls
-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
-pass-user-headers=true: pass X-Forwarded-User and X-Forwarded-Email information to upstream
-pass-host-header=true: pass the request Host Header to upstream
-pass-groups=false: pass a pipe-separated list of groups to which the user belongs in the X-Forwarded-Groups header
-permit-groups="": The user groups to which a user must belong in order to use the proxy (see also filter-groups)
-profile-url="": Profile access endpoint
-provider="google": OAuth provider
-proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)
-redeem-url="": Token redemption endpoint
-redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
-resource="": the resource that is being protected. ie: "https://graph.microsoft.com". Currently only used in the Azure provider.
-request-logging=true: Log requests to stdout
-scope="": Oauth scope specification
-signature-key="": GAP-Signature request signature key (algorithm:secretkey)
-skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
-skip-provider-button=false: will skip sign-in-page to directly reach the next step: oauth/start
-approval-prompt string: OAuth approval_prompt (default "force")
-authenticated-emails-file string: authenticate against emails via file (one per line)
-azure-tenant string: go to a tenant-specific or common (tenant-independent) endpoint. (default "common")
-basic-auth-password string: the password to set when passing the HTTP Basic Auth header
-client-id string: the OAuth Client ID: ie: "123456.apps.googleusercontent.com"
-client-secret string: the OAuth Client Secret
-config string: path to config file
-cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com)*
-cookie-expire duration: expire timeframe for cookie (default 168h0m0s)
-cookie-httponly: set HttpOnly cookie flag (default true)
-cookie-name string: the name of the cookie that the oauth_proxy creates (default "_oauth2_proxy")
-cookie-refresh duration: refresh the cookie after this duration; 0 to disable
-cookie-secret string: the seed string for secure cookies (optionally base64 encoded)
-cookie-secure: set secure (HTTPS) cookie flag (default true)
-custom-templates-dir string: path to custom html templates
-display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true)
-email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
-filter-groups string: only pass groups in the X-Forwarded-Groups header that contain this string
-footer string: custom footer string. Use "-" to disable default footer.
-github-org string: restrict logins to members of this organisation
-github-team string: restrict logins to members of this team
-google-admin-email string: the google admin to impersonate for api calls
-google-group value: restrict logins to members of this google group (may be given multiple times).
-google-service-account-json string: the path to the service account json credentials
-htpasswd-file string: additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
-http-address string: [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients (default "127.0.0.1:4180")
-https-address string: <addr>:<port> to listen on for HTTPS clients (default ":443")
-login-url string: Authentication endpoint
-pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header
-pass-basic-auth: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream (default true)
-pass-host-header: pass the request Host Header to upstream (default true)
-pass-user-headers: pass X-Forwarded-User and X-Forwarded-Email information to upstream (default true)
-pass-groups boolean: pass a pipe-separated list of groups to which the user belongs in the X-Forwarded-Groups header
-permit-groups string: The user groups to which a user must belong in order to use the proxy (see also filter-groups)
-profile-url string: Profile access endpoint
-provider string: OAuth provider (default "google")
-proxy-prefix string: the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) (default "/oauth2")
-redeem-url string: Token redemption endpoint
-redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
-request-logging: Log requests to stdout (default true)
-resource string: The resource that is protected (Azure AD only)
-scope string: OAuth scope specification
-set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
-signature-key string: GAP-Signature request signature key (algorithm:secretkey)
-skip-auth-preflight: will skip authentication for OPTIONS requests
-skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times)
-skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start
-ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS
-tls-cert="": path to certificate file
-tls-key="": path to private key file
-upstream=: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
-validate-url="": Access token validation endpoint
-version=false: print version string
-tls-cert string: path to certificate file
-tls-key string: path to private key file
-upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
-validate-url string: Access token validation endpoint
-version: print version string
```
See below for provider specific options
@ -355,15 +369,10 @@ The [Nginx `auth_request` directive](http://nginx.org/en/docs/http/ngx_http_auth
```nginx
server {
listen 443 ssl spdy;
listen 443 ssl;
server_name ...;
include ssl/ssl.conf;
location = /oauth2/auth {
internal;
proxy_pass http://127.0.0.1:4180;
}
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
proxy_set_header Host $host;
@ -372,7 +381,7 @@ server {
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
location /upstream/ {
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
@ -383,14 +392,12 @@ server {
proxy_set_header X-User $user;
proxy_set_header X-Email $email;
# if you enabled --cookie-refresh, this is needed for it to work with auth_request
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
proxy_pass http://backend/;
}
location / {
auth_request /oauth2/auth;
error_page 401 = https://example.com/oauth2/sign_in;
root /path/to/the/site;
# or "root /path/to/site;" or "fastcgi_pass ..." etc
}
}
```

View File

@ -25,7 +25,8 @@ for os in windows linux darwin; do
fi
BUILD=$(mktemp -d ${TMPDIR:-/tmp}/oauth2_proxy.XXXXXX)
TARGET="oauth2_proxy-$version.$os-$arch.$goversion"
GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -o $BUILD/$TARGET/oauth2_proxy$EXT || exit 1
GOOS=$os GOARCH=$arch CGO_ENABLED=0 \
go build -ldflags="-s -w" -o $BUILD/$TARGET/oauth2_proxy$EXT || exit 1
pushd $BUILD
tar czvf $TARGET.tar.gz $TARGET
mv $TARGET.tar.gz $DIR/dist

18
http.go
View File

@ -5,7 +5,6 @@ import (
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
)
@ -24,19 +23,24 @@ func (s *Server) ListenAndServe() {
}
func (s *Server) ServeHTTP() {
u, err := url.Parse(s.Opts.HttpAddress)
if err != nil {
log.Fatalf("FATAL: could not parse %#v: %v", s.Opts.HttpAddress, err)
httpAddress := s.Opts.HttpAddress
scheme := ""
i := strings.Index(httpAddress, "://")
if i > -1 {
scheme = httpAddress[0:i]
}
var networkType string
switch u.Scheme {
switch scheme {
case "", "http":
networkType = "tcp"
default:
networkType = u.Scheme
networkType = scheme
}
listenAddr := strings.TrimPrefix(u.String(), u.Scheme+"://")
slice := strings.SplitN(httpAddress, "//", 2)
listenAddr := slice[len(slice)-1]
listener, err := net.Listen(networkType, listenAddr)
if err != nil {

View File

@ -42,6 +42,7 @@ func main() {
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS")
flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")

View File

@ -71,6 +71,7 @@ type OAuthProxy struct {
PassAccessToken bool
CookieCipher *cookie.Cipher
skipAuthRegex []string
skipAuthPreflight bool
compiledRegex []*regexp.Regexp
templates *template.Template
Footer string
@ -204,6 +205,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
serveMux: serveMux,
redirectURL: redirectURL,
skipAuthRegex: opts.SkipAuthRegex,
skipAuthPreflight: opts.SkipAuthPreflight,
compiledRegex: opts.CompiledRegex,
SetXAuthRequest: opts.SetXAuthRequest,
PassBasicAuth: opts.PassBasicAuth,
@ -430,6 +432,11 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
return
}
func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
}
func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
for _, u := range p.compiledRegex {
ok = u.MatchString(path)
@ -454,7 +461,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
p.RobotsTxt(rw)
case path == p.PingPath:
p.PingPage(rw)
case p.IsWhitelistedPath(path):
case p.IsWhitelistedRequest(req):
p.serveMux.ServeHTTP(rw, req)
case path == p.SignInPath:
p.SignIn(rw, req)

View File

@ -646,6 +646,33 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
assert.Equal(t, "oauth_user@example.com", pc_test.rw.HeaderMap["X-Auth-Request-Email"][0])
}
func TestAuthSkippedForPreflightRequests(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("response"))
}))
defer upstream.Close()
opts := NewOptions()
opts.Upstreams = append(opts.Upstreams, upstream.URL)
opts.ClientID = "bazquux"
opts.ClientSecret = "foobar"
opts.CookieSecret = "xyzzyplugh"
opts.SkipAuthPreflight = true
opts.Validate()
upstream_url, _ := url.Parse(upstream.URL)
opts.provider = NewTestProvider(upstream_url, "")
proxy := NewOAuthProxy(opts, func(string) bool { return false })
rw := httptest.NewRecorder()
req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
proxy.ServeHTTP(rw, req)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "response", rw.Body.String())
}
type SignatureAuthenticator struct {
auth hmacauth.HmacAuth
}

View File

@ -60,6 +60,7 @@ type Options struct {
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
// These options allow for other providers besides Google, with
// potential overrides.
@ -101,6 +102,7 @@ func NewOptions() *Options {
CookieExpire: time.Duration(168) * time.Hour,
CookieRefresh: time.Duration(0),
SetXAuthRequest: false,
SkipAuthPreflight: false,
PassBasicAuth: true,
PassUserHeaders: true,
PassGroups: false,

View File

@ -62,8 +62,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
}
params := url.Values{
"access_token": {accessToken},
"limit": {"100"},
"limit": {"100"},
}
endpoint := &url.URL{
@ -74,6 +73,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
}
req, _ := http.NewRequest("GET", endpoint.String(), nil)
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
@ -86,7 +86,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
}
if resp.StatusCode != 200 {
return false, fmt.Errorf(
"got %d from %q %s", resp.StatusCode, stripToken(endpoint.String()), body)
"got %d from %q %s", resp.StatusCode, endpoint.String(), body)
}
if err := json.Unmarshal(body, &orgs); err != nil {
@ -118,8 +118,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
}
params := url.Values{
"access_token": {accessToken},
"limit": {"100"},
"limit": {"100"},
}
endpoint := &url.URL{
@ -130,6 +129,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
}
req, _ := http.NewRequest("GET", endpoint.String(), nil)
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
@ -142,7 +142,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
}
if resp.StatusCode != 200 {
return false, fmt.Errorf(
"got %d from %q %s", resp.StatusCode, stripToken(endpoint.String()), body)
"got %d from %q %s", resp.StatusCode, endpoint.String(), body)
}
if err := json.Unmarshal(body, &teams); err != nil {
@ -198,17 +198,14 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
}
}
params := url.Values{
"access_token": {s.AccessToken},
}
endpoint := &url.URL{
Scheme: p.ValidateURL.Scheme,
Host: p.ValidateURL.Host,
Path: path.Join(p.ValidateURL.Path, "/user/emails"),
RawQuery: params.Encode(),
Scheme: p.ValidateURL.Scheme,
Host: p.ValidateURL.Host,
Path: path.Join(p.ValidateURL.Path, "/user/emails"),
}
resp, err := http.DefaultClient.Get(endpoint.String())
req, _ := http.NewRequest("GET", endpoint.String(), nil)
req.Header.Set("Authorization", fmt.Sprintf("token %s", s.AccessToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
@ -220,9 +217,9 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
if resp.StatusCode != 200 {
return "", fmt.Errorf("got %d from %q %s",
resp.StatusCode, stripToken(endpoint.String()), body)
resp.StatusCode, endpoint.String(), body)
} else {
log.Printf("got %d from %q %s", resp.StatusCode, stripToken(endpoint.String()), body)
log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
}
if err := json.Unmarshal(body, &emails); err != nil {

View File

@ -1,3 +1,3 @@
package main
const VERSION = "2.2.0-alpha"
const VERSION = "2.2.1-alpha"