Merge pull request #153 from 18F/auth-only-endpoint
Add /auth endpoint to support Nginx's auth_request
This commit is contained in:
commit
7c241ec1fe
29
README.md
29
README.md
@ -239,7 +239,6 @@ The command line to run `oauth2_proxy` in this configuration would look like thi
|
|||||||
--client-secret=...
|
--client-secret=...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Endpoint Documentation
|
## Endpoint Documentation
|
||||||
|
|
||||||
OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable.
|
OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable.
|
||||||
@ -249,6 +248,7 @@ OAuth2 Proxy responds directly to the following endpoints. All other endpoints w
|
|||||||
* /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
|
* /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
|
||||||
* /oauth2/start - a URL that will redirect to start the OAuth cycle
|
* /oauth2/start - a URL that will redirect to start the OAuth cycle
|
||||||
* /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/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)
|
||||||
|
|
||||||
## Logging Format
|
## Logging Format
|
||||||
|
|
||||||
@ -265,3 +265,30 @@ Follow the examples in the [`providers` package](providers/) to define a new
|
|||||||
`Provider` instance. Add a new `case` to
|
`Provider` instance. Add a new `case` to
|
||||||
[`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the
|
[`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the
|
||||||
new `Provider`.
|
new `Provider`.
|
||||||
|
|
||||||
|
## <a name="nginx-auth-request"></a>Configuring for use with the Nginx `auth_request` directive
|
||||||
|
|
||||||
|
The [Nginx `auth_request` directive](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html) allows Nginx to authenticate requests via the oauth2_proxy's `/auth` endpoint, which only returns a 202 Accepted response or a 401 Unauthorized response without proxying the request through. For example:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl spdy;
|
||||||
|
server_name ...;
|
||||||
|
include ssl/ssl.conf;
|
||||||
|
|
||||||
|
location = /auth {
|
||||||
|
internal;
|
||||||
|
proxy_pass http://127.0.0.1:4180;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
auth_request /auth;
|
||||||
|
error_page 401 = ...;
|
||||||
|
|
||||||
|
root /path/to/the/site;
|
||||||
|
default_type text/html;
|
||||||
|
charset utf-8;
|
||||||
|
charset_types application/json utf-8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -33,6 +33,7 @@ type OAuthProxy struct {
|
|||||||
SignInPath string
|
SignInPath string
|
||||||
OAuthStartPath string
|
OAuthStartPath string
|
||||||
OAuthCallbackPath string
|
OAuthCallbackPath string
|
||||||
|
AuthOnlyPath string
|
||||||
|
|
||||||
redirectURL *url.URL // the url to receive requests at
|
redirectURL *url.URL // the url to receive requests at
|
||||||
provider providers.Provider
|
provider providers.Provider
|
||||||
@ -156,6 +157,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
|
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
|
||||||
OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
|
OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
|
||||||
OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix),
|
OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix),
|
||||||
|
AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),
|
||||||
|
|
||||||
ProxyPrefix: opts.ProxyPrefix,
|
ProxyPrefix: opts.ProxyPrefix,
|
||||||
provider: opts.provider,
|
provider: opts.provider,
|
||||||
@ -390,6 +392,8 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
p.OAuthStart(rw, req)
|
p.OAuthStart(rw, req)
|
||||||
case path == p.OAuthCallbackPath:
|
case path == p.OAuthCallbackPath:
|
||||||
p.OAuthCallback(rw, req)
|
p.OAuthCallback(rw, req)
|
||||||
|
case path == p.AuthOnlyPath:
|
||||||
|
p.AuthenticateOnly(rw, req)
|
||||||
default:
|
default:
|
||||||
p.Proxy(rw, req)
|
p.Proxy(rw, req)
|
||||||
}
|
}
|
||||||
@ -465,7 +469,28 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
status := p.Authenticate(rw, req)
|
||||||
|
if status == http.StatusAccepted {
|
||||||
|
rw.WriteHeader(http.StatusAccepted)
|
||||||
|
} else {
|
||||||
|
http.Error(rw, "unauthorized request", http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
status := p.Authenticate(rw, req)
|
||||||
|
if status == http.StatusInternalServerError {
|
||||||
|
p.ErrorPage(rw, http.StatusInternalServerError,
|
||||||
|
"Internal Error", "Internal Error")
|
||||||
|
} else if status == http.StatusForbidden {
|
||||||
|
p.SignInPage(rw, req, http.StatusForbidden)
|
||||||
|
} else {
|
||||||
|
p.serveMux.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
|
||||||
var saveSession, clearSession, revalidated bool
|
var saveSession, clearSession, revalidated bool
|
||||||
remoteAddr := getRemoteAddr(req)
|
remoteAddr := getRemoteAddr(req)
|
||||||
|
|
||||||
@ -514,8 +539,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||||||
err := p.SaveSession(rw, req, session)
|
err := p.SaveSession(rw, req, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("%s %s", remoteAddr, err)
|
log.Printf("%s %s", remoteAddr, err)
|
||||||
p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
|
return http.StatusInternalServerError
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,8 +555,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if session == nil {
|
if session == nil {
|
||||||
p.SignInPage(rw, req, 403)
|
return http.StatusForbidden
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, the user is authenticated. proxy normally
|
// At this point, the user is authenticated. proxy normally
|
||||||
@ -551,8 +574,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
rw.Header().Set("GAP-Auth", session.Email)
|
rw.Header().Set("GAP-Auth", session.Email)
|
||||||
}
|
}
|
||||||
|
return http.StatusAccepted
|
||||||
p.serveMux.ServeHTTP(rw, req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
|
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
|
||||||
|
@ -555,3 +555,58 @@ func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
|
|||||||
t.Errorf("expected nil session %#v", session)
|
t.Errorf("expected nil session %#v", session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAuthOnlyEndpointTest() *ProcessCookieTest {
|
||||||
|
pc_test := NewProcessCookieTestWithDefaults()
|
||||||
|
pc_test.req, _ = http.NewRequest("GET",
|
||||||
|
pc_test.opts.ProxyPrefix + "/auth", nil)
|
||||||
|
return pc_test
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthOnlyEndpointAccepted(t *testing.T) {
|
||||||
|
test := NewAuthOnlyEndpointTest()
|
||||||
|
startSession := &providers.SessionState{
|
||||||
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||||
|
test.SaveSession(startSession, time.Now())
|
||||||
|
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
assert.Equal(t, http.StatusAccepted, test.rw.Code)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
|
||||||
|
assert.Equal(t, "", string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
|
||||||
|
test := NewAuthOnlyEndpointTest()
|
||||||
|
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
|
||||||
|
assert.Equal(t, "unauthorized request\n", string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
|
||||||
|
test := NewAuthOnlyEndpointTest()
|
||||||
|
test.proxy.CookieExpire = time.Duration(24) * time.Hour
|
||||||
|
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||||
|
startSession := &providers.SessionState{
|
||||||
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||||
|
test.SaveSession(startSession, reference)
|
||||||
|
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
|
||||||
|
assert.Equal(t, "unauthorized request\n", string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
|
||||||
|
test := NewAuthOnlyEndpointTest()
|
||||||
|
startSession := &providers.SessionState{
|
||||||
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
||||||
|
test.SaveSession(startSession, time.Now())
|
||||||
|
test.validate_user = false
|
||||||
|
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
|
||||||
|
assert.Equal(t, "unauthorized request\n", string(bodyBytes))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user