authorize: add authorization (#59)

* authorize: authorization module adds support for per-route access policy. In this release we support the most common forms of identity based access policy: `allowed_users`, `allowed_groups`, and `allowed_domains`. In future versions, the authorization module will also support context and device based authorization policy and decisions. See website documentation for more details.
 * docs: updated `env.example` to include a `POLICY` setting example.
 * docs:  added `IDP_SERVICE_ACCOUNT` to  `env.example` .
 * docs: removed `PROXY_ROOT_DOMAIN` settings which has been replaced by `POLICY`.
 * all: removed `ALLOWED_DOMAINS` settings which has been replaced by `POLICY`. Authorization is now handled by the authorization service and is defined in the policy configuration files.
 * proxy: `ROUTES` settings which has been replaced by `POLICY`.
* internal/log: `http.Server` and `httputil.NewSingleHostReverseProxy` now uses pomerium's logging package instead of the standard library's built in one.

Closes #54
Closes #41
Closes #61
Closes #58
This commit is contained in:
Bobby DeSimone 2019-03-07 12:47:07 -08:00 committed by GitHub
parent 1187be2bf3
commit c13459bb88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1683 additions and 879 deletions

View file

@ -17,15 +17,10 @@ import (
"github.com/pomerium/pomerium/internal/templates"
)
// mocks for validator func
func trueValidator(s string) bool { return true }
func falseValidator(s string) bool { return false }
func testAuthenticate() *Authenticate {
var auth Authenticate
auth.RedirectURL, _ = url.Parse("https://auth.example.com/oauth/callback")
auth.SharedKey = "IzY7MOZwzfOkmELXgozHDKTxoT3nOYhwkcmUVINsRww="
auth.AllowedDomains = []string{"*"}
auth.ProxyRootDomains = []string{"example.com"}
auth.templates = templates.New()
return &auth
@ -86,66 +81,25 @@ func TestAuthenticate_authenticate(t *testing.T) {
}}
tests := []struct {
name string
session sessions.SessionStore
provider identity.MockProvider
validator func(string) bool
want *sessions.SessionState
wantErr bool
name string
session sessions.SessionStore
provider identity.MockProvider
want *sessions.SessionState
wantErr bool
}{
{"good", goodSession, identity.MockProvider{ValidateResponse: true}, trueValidator, nil, false},
{"good but fails validation", goodSession, identity.MockProvider{ValidateResponse: true}, falseValidator, nil, true},
{"can't load session", &sessions.MockSessionStore{LoadError: errors.New("error")}, identity.MockProvider{ValidateResponse: true}, trueValidator, nil, true},
{"validation fails", goodSession, identity.MockProvider{ValidateResponse: false}, trueValidator, nil, true},
{"session fails after good validation", &sessions.MockSessionStore{
SaveError: errors.New("error"),
Session: &sessions.SessionState{
AccessToken: "AccessToken",
RefreshToken: "RefreshToken",
RefreshDeadline: time.Now().Add(10 * time.Second),
}}, identity.MockProvider{ValidateResponse: true},
trueValidator, nil, true},
{"refresh expired",
expiredRefresPeriod,
identity.MockProvider{
ValidateResponse: true,
RefreshResponse: &sessions.SessionState{
AccessToken: "new token",
LifetimeDeadline: time.Now(),
},
},
trueValidator, nil, false},
{"refresh expired refresh error",
expiredRefresPeriod,
identity.MockProvider{
ValidateResponse: true,
RefreshError: errors.New("error"),
},
trueValidator, nil, true},
{"refresh expired failed save",
&sessions.MockSessionStore{
SaveError: errors.New("error"),
Session: &sessions.SessionState{
AccessToken: "AccessToken",
RefreshToken: "RefreshToken",
RefreshDeadline: time.Now().Add(10 * -time.Second),
}},
identity.MockProvider{
ValidateResponse: true,
RefreshResponse: &sessions.SessionState{
AccessToken: "new token",
LifetimeDeadline: time.Now(),
},
},
trueValidator, nil, true},
{"good", goodSession, identity.MockProvider{ValidateResponse: true}, nil, false},
{"can't load session", &sessions.MockSessionStore{LoadError: errors.New("error")}, identity.MockProvider{ValidateResponse: true}, nil, true},
{"validation fails", goodSession, identity.MockProvider{ValidateResponse: false}, nil, true},
{"session fails after good validation", &sessions.MockSessionStore{SaveError: errors.New("error"), Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * time.Second)}}, identity.MockProvider{ValidateResponse: true}, nil, true},
{"refresh expired", expiredRefresPeriod, identity.MockProvider{ValidateResponse: true, RefreshResponse: &sessions.SessionState{AccessToken: "new token", LifetimeDeadline: time.Now()}}, nil, false},
{"refresh expired refresh error", expiredRefresPeriod, identity.MockProvider{ValidateResponse: true, RefreshError: errors.New("error")}, nil, true},
{"refresh expired failed save", &sessions.MockSessionStore{SaveError: errors.New("error"), Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(10 * -time.Second)}}, identity.MockProvider{ValidateResponse: true, RefreshResponse: &sessions.SessionState{AccessToken: "new token", LifetimeDeadline: time.Now()}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Authenticate{
sessionStore: tt.session,
provider: tt.provider,
Validator: tt.validator,
}
r := httptest.NewRequest("GET", "/auth", nil)
w := httptest.NewRecorder()
@ -161,11 +115,10 @@ func TestAuthenticate_authenticate(t *testing.T) {
func TestAuthenticate_SignIn(t *testing.T) {
tests := []struct {
name string
session sessions.SessionStore
provider identity.MockProvider
validator func(string) bool
wantCode int
name string
session sessions.SessionStore
provider identity.MockProvider
wantCode int
}{
{"good",
&sessions.MockSessionStore{
@ -175,7 +128,7 @@ func TestAuthenticate_SignIn(t *testing.T) {
RefreshDeadline: time.Now().Add(10 * time.Second),
}},
identity.MockProvider{ValidateResponse: true},
trueValidator,
http.StatusForbidden},
{"session fails after good validation", &sessions.MockSessionStore{
SaveError: errors.New("error"),
@ -184,14 +137,13 @@ func TestAuthenticate_SignIn(t *testing.T) {
RefreshToken: "RefreshToken",
RefreshDeadline: time.Now().Add(10 * time.Second),
}}, identity.MockProvider{ValidateResponse: true},
trueValidator, http.StatusBadRequest},
http.StatusBadRequest},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Authenticate{
sessionStore: tt.session,
provider: tt.provider,
Validator: tt.validator,
RedirectURL: uriParse("http://www.pomerium.io"),
csrfStore: &sessions.MockCSRFStore{},
SharedKey: "secret",
@ -592,11 +544,9 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
code string
state string
validDomains []string
validator func(string) bool
session sessions.SessionStore
provider identity.MockProvider
csrfStore sessions.MockCSRFStore
session sessions.SessionStore
provider identity.MockProvider
csrfStore sessions.MockCSRFStore
want string
wantErr bool
@ -607,7 +557,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -629,7 +579,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -652,7 +602,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -674,7 +624,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateError: errors.New("error"),
@ -691,30 +641,8 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{SaveError: errors.New("error")},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
AccessToken: "AccessToken",
RefreshToken: "RefreshToken",
Email: "blah@blah.com",
RefreshDeadline: time.Now().Add(10 * time.Second),
}},
sessions.MockCSRFStore{
ResponseCSRF: "csrf",
Cookie: &http.Cookie{Value: "nonce"}},
"",
true,
},
{"failed email validation",
http.MethodGet,
"",
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
falseValidator,
&sessions.MockSessionStore{},
&sessions.MockSessionStore{SaveError: errors.New("error")},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
AccessToken: "AccessToken",
@ -736,7 +664,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -758,7 +686,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -780,7 +708,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
"nonce:https://corp.pomerium.io",
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -802,7 +730,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -824,7 +752,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"code",
base64.URLEncoding.EncodeToString([]byte("nonce:corp.pomerium.io")),
[]string{"pomerium.io"},
trueValidator,
&sessions.MockSessionStore{},
identity.MockProvider{
AuthenticateResponse: sessions.SessionState{
@ -848,7 +776,6 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
csrfStore: tt.csrfStore,
provider: tt.provider,
ProxyRootDomains: tt.validDomains,
Validator: tt.validator,
}
u, _ := url.Parse("/oauthGet")
params, _ := url.ParseQuery(u.RawQuery)