mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 18:36:30 +02:00
authenticate/proxy: add user impersonation, refresh, dashboard (#123)
proxy: Add user dashboard. [GH-123] proxy/authenticate: Add manual refresh of their session. [GH-73] authorize: Add administrator (super user) account support. [GH-110] internal/policy: Allow administrators to impersonate other users. [GH-110]
This commit is contained in:
parent
dc2eb9668c
commit
66b4c2d3cd
42 changed files with 1644 additions and 1006 deletions
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -1,5 +1,36 @@
|
|||
# Pomerium Changelog
|
||||
|
||||
## vUNRELEASED
|
||||
|
||||
### NEW
|
||||
|
||||
- Add user dashboard containing information about the current user's session. [GH-123]
|
||||
- Add functionality allowing users to initiate manual refresh of their session. This is helpful when a user's access control details are updated but their session hasn't updated yet. To prevent abuse, manual refresh is gated by a cooldown (`REFRESH_COOLDOWN`) which defaults to five minutes. [GH-73]
|
||||
- Add Administrator (super user) account support (`ADMINISTRATORS`). [GH-110]
|
||||
- Add feature that allows Administrators to impersonate / sign-in as another user from the user dashboard. [GH-110]
|
||||
- Add docker images and builds for ARM. [GH-95]
|
||||
- Add support for public, unauthenticated routes. [GH-129]
|
||||
|
||||
### CHANGED
|
||||
|
||||
- User state is now maintained and scoped at the domain level vs at the route level. [GH-128]
|
||||
- Error pages contain a link to sign out from the current user session. [GH-100]
|
||||
- Removed `LifetimeDeadline` from `sessions.SessionState`.
|
||||
- Removed favicon specific request handling. [GH-131]
|
||||
- Headers are now configurable via the `HEADERS` configuration variable. [GH-108]
|
||||
- Refactored proxy and authenticate services to share the same session state cookie. [GH-131]
|
||||
- Removed instances of extraneous session state saves. [GH-131]
|
||||
- Changed default behavior when no session is found. Users are now redirected to login instead of being shown an error page.[GH-131]
|
||||
- Updated routes such that all http handlers are now wrapped with a standard set of middleware. Headers, request id, loggers, and health checks middleware are now applied to all routes including 4xx and 5xx responses. [GH-116]
|
||||
- Changed docker images to be built from [distroless](https://github.com/GoogleContainerTools/distroless). This fixed an issue with `nsswitch` [GH-97], includes `ca-certificates` and limits the attack surface area of our images. [GH-101]
|
||||
- Changed HTTP to HTTPS redirect server to be user configurable via `HTTP_REDIRECT_ADDR`. [GH-103]
|
||||
- `Content-Security-Policy` hash updated to match new UI assets.
|
||||
|
||||
### FIXED
|
||||
|
||||
- Fixed an issue where policy and routes were being pre-processed incorrectly. [GH-132]
|
||||
- Fixed an issue where `golint` was not being found in our docker image. [GH-121]
|
||||
|
||||
## v0.0.4
|
||||
|
||||
### CHANGED
|
||||
|
|
|
@ -62,21 +62,18 @@ func TestAuthenticate_Refresh(t *testing.T) {
|
|||
&identity.MockProvider{
|
||||
RefreshResponse: &sessions.SessionState{
|
||||
AccessToken: "updated",
|
||||
LifetimeDeadline: fixedDate,
|
||||
RefreshDeadline: fixedDate,
|
||||
}},
|
||||
&pb.Session{
|
||||
AccessToken: "original",
|
||||
LifetimeDeadline: fixedProtoTime,
|
||||
RefreshDeadline: fixedProtoTime,
|
||||
},
|
||||
&pb.Session{
|
||||
AccessToken: "updated",
|
||||
LifetimeDeadline: fixedProtoTime,
|
||||
RefreshDeadline: fixedProtoTime,
|
||||
},
|
||||
false},
|
||||
{"test error", &identity.MockProvider{RefreshError: errors.New("hi")}, &pb.Session{RefreshToken: "refresh token", RefreshDeadline: fixedProtoTime, LifetimeDeadline: fixedProtoTime}, nil, true},
|
||||
{"test error", &identity.MockProvider{RefreshError: errors.New("hi")}, &pb.Session{RefreshToken: "refresh token", RefreshDeadline: fixedProtoTime}, nil, true},
|
||||
{"test catch nil", nil, nil, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -105,7 +102,6 @@ func TestAuthenticate_Authenticate(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("expected to be able to create cipher: %v", err)
|
||||
}
|
||||
lt := time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC()
|
||||
rt := time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC()
|
||||
vtProto, err := ptypes.TimestampProto(rt)
|
||||
if err != nil {
|
||||
|
@ -115,9 +111,7 @@ func TestAuthenticate_Authenticate(t *testing.T) {
|
|||
want := &sessions.SessionState{
|
||||
AccessToken: "token1234",
|
||||
RefreshToken: "refresh4321",
|
||||
LifetimeDeadline: lt,
|
||||
RefreshDeadline: rt,
|
||||
|
||||
Email: "user@domain.com",
|
||||
User: "user",
|
||||
}
|
||||
|
@ -125,7 +119,6 @@ func TestAuthenticate_Authenticate(t *testing.T) {
|
|||
goodReply := &pb.Session{
|
||||
AccessToken: "token1234",
|
||||
RefreshToken: "refresh4321",
|
||||
LifetimeDeadline: vtProto,
|
||||
RefreshDeadline: vtProto,
|
||||
Email: "user@domain.com",
|
||||
User: "user"}
|
||||
|
|
|
@ -12,12 +12,15 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/version"
|
||||
)
|
||||
|
||||
// CSPHeaders adds content security headers for authenticate's handlers
|
||||
var CSPHeaders = map[string]string{
|
||||
"Content-Security-Policy": "default-src 'none'; style-src 'self' 'sha256-pSTVzZsFAqd2U3QYu+BoBDtuJWaPM/+qMy/dBRrhb5Y='; img-src 'self';",
|
||||
"Content-Security-Policy": "default-src 'none'; style-src 'self'" +
|
||||
" 'sha256-z9MsgkMbQjRSLxzAfN55jB3a9pP0PQ4OHFH8b4iDP6s=' " +
|
||||
" 'sha256-qnVkQSG7pWu17hBhIw0kCpfEB3XGvt0mNRa6+uM6OUU=' " +
|
||||
" 'sha256-qOdRsNZhtR+htazbcy7guQl3Cn1cqOw1FcE4d3llae0='; " +
|
||||
"img-src 'self';",
|
||||
"Referrer-Policy": "Same-origin",
|
||||
}
|
||||
|
||||
|
@ -35,7 +38,7 @@ func (a *Authenticate) Handler() http.Handler {
|
|||
mux.Handle("/oauth2/callback", c.ThenFunc(a.OAuthCallback))
|
||||
// authenticate-server endpoints
|
||||
mux.Handle("/sign_in", validate.ThenFunc(a.SignIn))
|
||||
mux.Handle("/sign_out", validate.ThenFunc(a.SignOut)) // GET POST
|
||||
mux.Handle("/sign_out", validate.ThenFunc(a.SignOut)) // POST
|
||||
return mux
|
||||
}
|
||||
|
||||
|
@ -53,7 +56,7 @@ func (a *Authenticate) authenticate(w http.ResponseWriter, r *http.Request, sess
|
|||
}
|
||||
err = a.sessionStore.SaveSession(w, r, session)
|
||||
if err != nil {
|
||||
return fmt.Errorf("authenticate: refresh failed : %v", err)
|
||||
return fmt.Errorf("authenticate: failed saving refreshed session : %v", err)
|
||||
}
|
||||
} else {
|
||||
valid, err := a.provider.Validate(r.Context(), session.IDToken)
|
||||
|
@ -86,14 +89,7 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
a.ProxyCallback(w, r, session)
|
||||
}
|
||||
|
||||
// ProxyCallback redirects the user back to proxy service along with an encrypted payload, as
|
||||
// url params, of the user's session state as specified in RFC6749 3.1.2.
|
||||
// https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
func (a *Authenticate) ProxyCallback(w http.ResponseWriter, r *http.Request, session *sessions.SessionState) {
|
||||
err := r.ParseForm()
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -104,14 +100,8 @@ func (a *Authenticate) ProxyCallback(w http.ResponseWriter, r *http.Request, ses
|
|||
httputil.ErrorResponse(w, r, "no state parameter supplied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// redirect url of proxy-service
|
||||
redirectURI := r.Form.Get("redirect_uri")
|
||||
if redirectURI == "" {
|
||||
httputil.ErrorResponse(w, r, "no redirect_uri parameter supplied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL, err := url.Parse(redirectURI)
|
||||
redirectURL, err := url.Parse(r.Form.Get("redirect_uri"))
|
||||
if err != nil {
|
||||
httputil.ErrorResponse(w, r, "malformed redirect_uri parameter passed", http.StatusBadRequest)
|
||||
return
|
||||
|
@ -145,40 +135,11 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// pretty safe to say that no matter what heppanes here, we want to revoke the local session
|
||||
redirectURI := r.Form.Get("redirect_uri")
|
||||
session, err := a.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate: signout failed to load session")
|
||||
httputil.ErrorResponse(w, r, "No session found to log out", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodGet {
|
||||
signature := r.Form.Get("sig")
|
||||
timestamp := r.Form.Get("ts")
|
||||
destinationURL, err := url.Parse(redirectURI)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate: malformed destination url")
|
||||
httputil.ErrorResponse(w, r, "Malformed destination URL", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
t := struct {
|
||||
Redirect string
|
||||
Signature string
|
||||
Timestamp string
|
||||
Destination string
|
||||
Email string
|
||||
Version string
|
||||
}{
|
||||
Redirect: redirectURI,
|
||||
Signature: signature,
|
||||
Timestamp: timestamp,
|
||||
Destination: destinationURL.Host,
|
||||
Email: session.Email,
|
||||
Version: version.FullVersion(),
|
||||
}
|
||||
a.templates.ExecuteTemplate(w, "sign_out.html", t)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
log.Error().Err(err).Msg("authenticate: no session to signout, redirect and clear")
|
||||
http.Redirect(w, r, redirectURI, http.StatusFound)
|
||||
return
|
||||
}
|
||||
a.sessionStore.ClearSession(w, r)
|
||||
|
@ -196,6 +157,7 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
|
|||
func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||
authRedirectURL := a.RedirectURL.ResolveReference(r.URL)
|
||||
|
||||
// generate a nonce to check following authentication with the IdP
|
||||
nonce := fmt.Sprintf("%x", cryptutil.GenerateKey())
|
||||
a.csrfStore.SetCSRF(w, r, nonce)
|
||||
|
||||
|
@ -204,6 +166,7 @@ func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, "Invalid redirect parameter: redirect uri not from the root domain", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// verify proxy url is from the root domain
|
||||
proxyRedirectURL, err := url.Parse(authRedirectURL.Query().Get("redirect_uri"))
|
||||
if err != nil || !middleware.SameSubdomain(proxyRedirectURL, a.RedirectURL) {
|
||||
|
@ -221,6 +184,7 @@ func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// concat base64'd nonce and authenticate url to make state
|
||||
state := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", nonce, authRedirectURL.String())))
|
||||
|
||||
// build the provider sign in url
|
||||
signInURL := a.provider.GetSignInURL(state)
|
||||
http.Redirect(w, r, signInURL, http.StatusFound)
|
||||
|
@ -242,12 +206,14 @@ func (a *Authenticate) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// redirect back to the proxy-service
|
||||
// redirect back to the proxy-service via sign_in
|
||||
log.Info().Interface("redirect", redirect).Msg("proxy: OAuthCallback")
|
||||
http.Redirect(w, r, redirect, http.StatusFound)
|
||||
}
|
||||
|
||||
// getOAuthCallback completes the oauth cycle from an identity provider's callback
|
||||
func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
// handle the callback response from the identity provider
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return "", httputil.HTTPError{Code: http.StatusInternalServerError, Message: err.Error()}
|
||||
|
@ -263,12 +229,14 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
|||
return "", httputil.HTTPError{Code: http.StatusBadRequest, Message: "Missing Code"}
|
||||
}
|
||||
|
||||
// validate the returned code with the identity provider
|
||||
session, err := a.provider.Authenticate(code)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: error redeeming authenticate code")
|
||||
return "", httputil.HTTPError{Code: http.StatusInternalServerError, Message: err.Error()}
|
||||
}
|
||||
|
||||
// okay, time to go back to the proxy service.
|
||||
bytes, err := base64.URLEncoding.DecodeString(r.Form.Get("state"))
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: failed decoding state")
|
||||
|
@ -281,30 +249,26 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
|||
nonce := s[0]
|
||||
redirect := s[1]
|
||||
c, err := a.csrfStore.GetCSRF(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Interface("s", s).Msg("authenticate: bad csrf")
|
||||
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Missing CSRF token"}
|
||||
}
|
||||
a.csrfStore.ClearCSRF(w, r)
|
||||
if c.Value != nonce {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: csrf mismatch")
|
||||
defer a.csrfStore.ClearCSRF(w, r)
|
||||
if err != nil || c.Value != nonce {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: csrf failure")
|
||||
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "CSRF failed"}
|
||||
}
|
||||
|
||||
redirectURL, err := url.Parse(redirect)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: couldn't parse redirect url")
|
||||
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Couldn't parse redirect url"}
|
||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: malformed redirect url")
|
||||
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Malformed redirect url"}
|
||||
}
|
||||
|
||||
// sanity check, we are redirecting back to the same subdomain right?
|
||||
if !middleware.SameSubdomain(redirectURL, a.RedirectURL) {
|
||||
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Invalid Redirect URI domain"}
|
||||
}
|
||||
|
||||
err = a.sessionStore.SaveSession(w, r, session)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("internal error")
|
||||
log.Error().Err(err).Msg("authenticate: failed saving new session")
|
||||
return "", httputil.HTTPError{Code: http.StatusInternalServerError, Message: "Internal Error"}
|
||||
}
|
||||
|
||||
return redirect, nil
|
||||
}
|
||||
|
|
|
@ -183,96 +183,6 @@ func (a mockCipher) Unmarshal(s string, i interface{}) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
func TestAuthenticate_ProxyCallback(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
uri string
|
||||
state string
|
||||
authCode string
|
||||
|
||||
sessionState *sessions.SessionState
|
||||
sessionStore sessions.SessionStore
|
||||
wantCode int
|
||||
wantBody string
|
||||
}{
|
||||
{"good", "https://corp.pomerium.io/", "state", "code",
|
||||
&sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
&sessions.MockSessionStore{},
|
||||
302,
|
||||
"<a href=\"https://corp.pomerium.io/?code=ok&state=state\">Found</a>."},
|
||||
{"no state",
|
||||
"https://corp.pomerium.io/",
|
||||
"",
|
||||
"code",
|
||||
&sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
&sessions.MockSessionStore{},
|
||||
403,
|
||||
"no state parameter supplied"},
|
||||
{"no redirect_url",
|
||||
"",
|
||||
"state",
|
||||
"code",
|
||||
&sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
&sessions.MockSessionStore{},
|
||||
403,
|
||||
"no redirect_uri parameter"},
|
||||
{"malformed redirect_url",
|
||||
"https://pomerium.com%zzzzz",
|
||||
"state",
|
||||
"code",
|
||||
&sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
&sessions.MockSessionStore{},
|
||||
400,
|
||||
"malformed redirect_uri"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Authenticate{
|
||||
sessionStore: tt.sessionStore,
|
||||
cipher: mockCipher{},
|
||||
}
|
||||
u, _ := url.Parse("https://pomerium.io/redirect")
|
||||
params, _ := url.ParseQuery(u.RawQuery)
|
||||
params.Set("code", tt.authCode)
|
||||
params.Set("state", tt.state)
|
||||
params.Set("redirect_uri", tt.uri)
|
||||
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
r := httptest.NewRequest("GET", u.String(), nil)
|
||||
w := httptest.NewRecorder()
|
||||
a.ProxyCallback(w, r, tt.sessionState)
|
||||
if status := w.Code; status != tt.wantCode {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
|
||||
}
|
||||
if body := w.Body.String(); !strings.Contains(body, tt.wantBody) {
|
||||
t.Errorf("handler returned wrong body Body: got \n%s \n%s", body, tt.wantBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getAuthCodeRedirectURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
@ -350,59 +260,6 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
|||
},
|
||||
http.StatusBadRequest,
|
||||
"could not revoke"},
|
||||
|
||||
{"good get",
|
||||
http.MethodGet,
|
||||
"https://corp.pomerium.io/",
|
||||
"sig",
|
||||
"ts",
|
||||
identity.MockProvider{},
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
"This will also sign you out of other internal apps."},
|
||||
{"cannot load session",
|
||||
http.MethodGet,
|
||||
"https://corp.pomerium.io/",
|
||||
"sig",
|
||||
"ts",
|
||||
identity.MockProvider{},
|
||||
&sessions.MockSessionStore{
|
||||
LoadError: errors.New("uh oh"),
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
},
|
||||
http.StatusBadRequest,
|
||||
"No session found to log out"},
|
||||
{"bad redirect url get",
|
||||
http.MethodGet,
|
||||
"https://pomerium.com%zzzzz",
|
||||
"sig",
|
||||
"ts",
|
||||
identity.MockProvider{},
|
||||
&sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
Email: "blah@blah.com",
|
||||
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
},
|
||||
http.StatusBadRequest,
|
||||
"Error"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -49,17 +49,17 @@ func New(opts *config.Options) (*Authorize, error) {
|
|||
|
||||
return &Authorize{
|
||||
SharedKey: string(sharedKey),
|
||||
identityAccess: NewIdentityWhitelist(opts.Policies),
|
||||
identityAccess: NewIdentityWhitelist(opts.Policies, opts.Administrators),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewIdentityWhitelist returns an indentity validator.
|
||||
// todo(bdd) : a radix-tree implementation is probably more efficient
|
||||
func NewIdentityWhitelist(policies []policy.Policy, admins []string) IdentityValidator {
|
||||
return newIdentityWhitelistMap(policies, admins)
|
||||
}
|
||||
|
||||
// ValidIdentity returns if an identity is authorized to access a route resource.
|
||||
func (a *Authorize) ValidIdentity(route string, identity *Identity) bool {
|
||||
return a.identityAccess.Valid(route, identity)
|
||||
}
|
||||
|
||||
// NewIdentityWhitelist returns an indentity validator.
|
||||
// todo(bdd) : a radix-tree implementation is probably more efficient
|
||||
func NewIdentityWhitelist(policies []policy.Policy) IdentityValidator {
|
||||
return newIdentityWhitelistMap(policies)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,24 @@ import (
|
|||
|
||||
// Authorize validates the user identity, device, and context of a request for
|
||||
// a given route. Currently only checks identity.
|
||||
func (a *Authorize) Authorize(ctx context.Context, in *pb.AuthorizeRequest) (*pb.AuthorizeReply, error) {
|
||||
ok := a.ValidIdentity(in.Route, &Identity{in.User, in.Email, in.Groups})
|
||||
func (a *Authorize) Authorize(ctx context.Context, in *pb.Identity) (*pb.AuthorizeReply, error) {
|
||||
ok := a.ValidIdentity(in.Route,
|
||||
&Identity{
|
||||
User: in.User,
|
||||
Email: in.Email,
|
||||
Groups: in.Groups,
|
||||
ImpersonateEmail: in.ImpersonateEmail,
|
||||
ImpersonateGroups: in.ImpersonateGroups,
|
||||
})
|
||||
return &pb.AuthorizeReply{IsValid: ok}, nil
|
||||
}
|
||||
|
||||
// IsAdmin validates the user is an administrative user.
|
||||
func (a *Authorize) IsAdmin(ctx context.Context, in *pb.Identity) (*pb.IsAdminReply, error) {
|
||||
ok := a.identityAccess.IsAdmin(
|
||||
&Identity{
|
||||
Email: in.Email,
|
||||
Groups: in.Groups,
|
||||
})
|
||||
return &pb.IsAdminReply{IsAdmin: ok}, nil
|
||||
}
|
||||
|
|
|
@ -16,12 +16,12 @@ func TestAuthorize_Authorize(t *testing.T) {
|
|||
name string
|
||||
SharedKey string
|
||||
identityAccess IdentityValidator
|
||||
in *pb.AuthorizeRequest
|
||||
in *pb.Identity
|
||||
want *pb.AuthorizeReply
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid authorization request", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", &MockIdentityValidator{ValidResponse: true}, &pb.AuthorizeRequest{Route: "http://pomerium.io", User: "user@pomerium.io"}, &pb.AuthorizeReply{IsValid: true}, false},
|
||||
{"invalid authorization request", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", &MockIdentityValidator{ValidResponse: false}, &pb.AuthorizeRequest{Route: "http://pomerium.io", User: "user@pomerium.io"}, &pb.AuthorizeReply{IsValid: false}, false},
|
||||
{"valid authorization request", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", &MockIdentityValidator{ValidResponse: true}, &pb.Identity{Route: "http://pomerium.io", User: "user@pomerium.io"}, &pb.AuthorizeReply{IsValid: true}, false},
|
||||
{"invalid authorization request", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", &MockIdentityValidator{ValidResponse: false}, &pb.Identity{Route: "http://pomerium.io", User: "user@pomerium.io"}, &pb.AuthorizeReply{IsValid: false}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -37,3 +37,33 @@ func TestAuthorize_Authorize(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorize_IsAdmin(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
identityAccess IdentityValidator
|
||||
in *pb.Identity
|
||||
want *pb.IsAdminReply
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid authorization request", &MockIdentityValidator{IsAdminResponse: true}, &pb.Identity{Route: "http://pomerium.io", User: "user@pomerium.io"}, &pb.IsAdminReply{IsAdmin: true}, false},
|
||||
{"invalid authorization request", &MockIdentityValidator{IsAdminResponse: false}, &pb.Identity{Route: "http://pomerium.io", User: "user@pomerium.io"}, &pb.IsAdminReply{IsAdmin: false}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Authorize{
|
||||
SharedKey: "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y",
|
||||
identityAccess: tt.identityAccess,
|
||||
}
|
||||
got, err := a.IsAdmin(context.Background(), tt.in)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Authorize.IsAdmin() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Authorize.IsAdmin() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,14 +14,26 @@ type Identity struct {
|
|||
User string
|
||||
Email string
|
||||
Groups []string
|
||||
// Impersonation
|
||||
ImpersonateEmail string
|
||||
ImpersonateGroups []string
|
||||
}
|
||||
|
||||
// EmailDomain returns the domain of the identity's email.
|
||||
func (i *Identity) EmailDomain() string {
|
||||
if i.Email == "" {
|
||||
// IsImpersonating returns whether the user is trying to impersonate another
|
||||
// user email or group.
|
||||
func (i *Identity) IsImpersonating() bool {
|
||||
if i.ImpersonateEmail != "" || len(i.ImpersonateGroups) != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// EmailDomain returns the domain portion of an email.
|
||||
func EmailDomain(email string) string {
|
||||
if email == "" {
|
||||
return ""
|
||||
}
|
||||
comp := strings.Split(i.Email, "@")
|
||||
comp := strings.Split(email, "@")
|
||||
if len(comp) != 2 || comp[0] == "" {
|
||||
return ""
|
||||
}
|
||||
|
@ -32,96 +44,141 @@ func (i *Identity) EmailDomain() string {
|
|||
// to a given route.
|
||||
type IdentityValidator interface {
|
||||
Valid(string, *Identity) bool
|
||||
IsAdmin(*Identity) bool
|
||||
}
|
||||
|
||||
type identityWhitelist struct {
|
||||
type whitelist struct {
|
||||
sync.RWMutex
|
||||
m map[string]bool
|
||||
access map[string]bool
|
||||
admins map[string]bool
|
||||
}
|
||||
|
||||
// newIdentityWhitelistMap takes a slice of policies and creates a hashmap of identity
|
||||
// authorizations per-route for each allowed group, domain, and email.
|
||||
func newIdentityWhitelistMap(policies []policy.Policy) *identityWhitelist {
|
||||
var im identityWhitelist
|
||||
im.m = make(map[string]bool, len(policies)*3)
|
||||
func newIdentityWhitelistMap(policies []policy.Policy, admins []string) *whitelist {
|
||||
var wl whitelist
|
||||
wl.access = make(map[string]bool, len(policies)*3)
|
||||
for _, p := range policies {
|
||||
for _, group := range p.AllowedGroups {
|
||||
wl.PutGroup(p.From, group)
|
||||
log.Debug().Str("route", p.From).Str("group", group).Msg("add group")
|
||||
im.PutGroup(p.From, group)
|
||||
}
|
||||
for _, domain := range p.AllowedDomains {
|
||||
im.PutDomain(p.From, domain)
|
||||
log.Debug().Str("route", p.From).Str("group", domain).Msg("add domain")
|
||||
|
||||
wl.PutDomain(p.From, domain)
|
||||
log.Debug().Str("route", p.From).Str("domain", domain).Msg("add domain")
|
||||
}
|
||||
for _, email := range p.AllowedEmails {
|
||||
im.PutEmail(p.From, email)
|
||||
log.Debug().Str("route", p.From).Str("group", email).Msg("add email")
|
||||
wl.PutEmail(p.From, email)
|
||||
log.Debug().Str("route", p.From).Str("email", email).Msg("add email")
|
||||
}
|
||||
}
|
||||
return &im
|
||||
|
||||
wl.admins = make(map[string]bool, len(admins))
|
||||
for _, admin := range admins {
|
||||
wl.PutAdmin(admin)
|
||||
log.Debug().Str("admin", admin).Msg("add administrator")
|
||||
}
|
||||
return &wl
|
||||
}
|
||||
|
||||
// Valid reports whether an identity has valid access for a given route.
|
||||
func (m *identityWhitelist) Valid(route string, i *Identity) bool {
|
||||
if ok := m.Domain(route, i.EmailDomain()); ok {
|
||||
func (wl *whitelist) Valid(route string, i *Identity) bool {
|
||||
email := i.Email
|
||||
domain := EmailDomain(email)
|
||||
groups := i.Groups
|
||||
|
||||
// if user is admin, and wants to impersonate, override values
|
||||
if wl.IsAdmin(i) && i.IsImpersonating() {
|
||||
email = i.ImpersonateEmail
|
||||
domain = EmailDomain(email)
|
||||
groups = i.ImpersonateGroups
|
||||
}
|
||||
|
||||
if ok := wl.Email(route, email); ok {
|
||||
return ok
|
||||
}
|
||||
if ok := m.Email(route, i.Email); ok {
|
||||
if ok := wl.Domain(route, domain); ok {
|
||||
return ok
|
||||
}
|
||||
for _, group := range i.Groups {
|
||||
if ok := m.Group(route, group); ok {
|
||||
for _, group := range groups {
|
||||
if ok := wl.Group(route, group); ok {
|
||||
return ok
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (wl *whitelist) IsAdmin(i *Identity) bool {
|
||||
if ok := wl.Admin(i.Email); ok {
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Group retrieves per-route access given a group name.
|
||||
func (m *identityWhitelist) Group(route, group string) bool {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.m[fmt.Sprintf("%s|group:%s", route, group)]
|
||||
func (wl *whitelist) Group(route, group string) bool {
|
||||
wl.RLock()
|
||||
defer wl.RUnlock()
|
||||
return wl.access[fmt.Sprintf("%s|group:%s", route, group)]
|
||||
}
|
||||
|
||||
// PutGroup adds an access entry for a route given a group name.
|
||||
func (m *identityWhitelist) PutGroup(route, group string) {
|
||||
m.Lock()
|
||||
m.m[fmt.Sprintf("%s|group:%s", route, group)] = true
|
||||
m.Unlock()
|
||||
func (wl *whitelist) PutGroup(route, group string) {
|
||||
wl.Lock()
|
||||
wl.access[fmt.Sprintf("%s|group:%s", route, group)] = true
|
||||
wl.Unlock()
|
||||
}
|
||||
|
||||
// Domain retrieves per-route access given a domain name.
|
||||
func (m *identityWhitelist) Domain(route, domain string) bool {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.m[fmt.Sprintf("%s|domain:%s", route, domain)]
|
||||
func (wl *whitelist) Domain(route, domain string) bool {
|
||||
wl.RLock()
|
||||
defer wl.RUnlock()
|
||||
return wl.access[fmt.Sprintf("%s|domain:%s", route, domain)]
|
||||
}
|
||||
|
||||
// PutDomain adds an access entry for a route given a domain name.
|
||||
func (m *identityWhitelist) PutDomain(route, domain string) {
|
||||
m.Lock()
|
||||
m.m[fmt.Sprintf("%s|domain:%s", route, domain)] = true
|
||||
m.Unlock()
|
||||
func (wl *whitelist) PutDomain(route, domain string) {
|
||||
wl.Lock()
|
||||
wl.access[fmt.Sprintf("%s|domain:%s", route, domain)] = true
|
||||
wl.Unlock()
|
||||
}
|
||||
|
||||
// Email retrieves per-route access given a user's email.
|
||||
func (m *identityWhitelist) Email(route, email string) bool {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.m[fmt.Sprintf("%s|email:%s", route, email)]
|
||||
func (wl *whitelist) Email(route, email string) bool {
|
||||
wl.RLock()
|
||||
defer wl.RUnlock()
|
||||
return wl.access[fmt.Sprintf("%s|email:%s", route, email)]
|
||||
}
|
||||
|
||||
// PutEmail adds an access entry for a route given a user's email.
|
||||
func (m *identityWhitelist) PutEmail(route, email string) {
|
||||
m.Lock()
|
||||
m.m[fmt.Sprintf("%s|email:%s", route, email)] = true
|
||||
m.Unlock()
|
||||
func (wl *whitelist) PutEmail(route, email string) {
|
||||
wl.Lock()
|
||||
wl.access[fmt.Sprintf("%s|email:%s", route, email)] = true
|
||||
wl.Unlock()
|
||||
}
|
||||
|
||||
// PutEmail adds an admin entry
|
||||
func (wl *whitelist) PutAdmin(admin string) {
|
||||
wl.Lock()
|
||||
wl.admins[admin] = true
|
||||
wl.Unlock()
|
||||
}
|
||||
|
||||
// Admin checks if the email matches an admin
|
||||
func (wl *whitelist) Admin(admin string) bool {
|
||||
wl.RLock()
|
||||
defer wl.RUnlock()
|
||||
return wl.admins[admin]
|
||||
}
|
||||
|
||||
// MockIdentityValidator is a mock implementation of IdentityValidator
|
||||
type MockIdentityValidator struct{ ValidResponse bool }
|
||||
type MockIdentityValidator struct {
|
||||
ValidResponse bool
|
||||
IsAdminResponse bool
|
||||
}
|
||||
|
||||
// Valid is a mock implementation IdentityValidator's Valid method
|
||||
func (mv *MockIdentityValidator) Valid(u string, i *Identity) bool { return mv.ValidResponse }
|
||||
|
||||
// IsAdmin is a mock implementation IdentityValidator's IsAdmin method
|
||||
func (mv *MockIdentityValidator) IsAdmin(i *Identity) bool { return mv.IsAdminResponse }
|
||||
|
|
|
@ -21,8 +21,7 @@ func TestIdentity_EmailDomain(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
i := &Identity{Email: tt.Email}
|
||||
if got := i.EmailDomain(); got != tt.want {
|
||||
if got := EmailDomain(tt.Email); got != tt.want {
|
||||
t.Errorf("Identity.EmailDomain() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
@ -36,25 +35,39 @@ func Test_IdentityWhitelistMap(t *testing.T) {
|
|||
policies []policy.Policy
|
||||
route string
|
||||
Identity *Identity
|
||||
admins []string
|
||||
want bool
|
||||
}{
|
||||
{"valid domain", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "user@example.com"}, true},
|
||||
{"invalid domain prepend", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "a@1example.com"}, false},
|
||||
{"invalid domain postpend", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "user@example.com2"}, false},
|
||||
{"valid group", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"admin"}}, true},
|
||||
{"invalid group", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"everyone"}}, false},
|
||||
{"invalid empty", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{""}}, false},
|
||||
{"valid group multiple", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"everyone", "admin"}}, true},
|
||||
{"invalid group multiple", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"everyones", "sadmin"}}, false},
|
||||
{"valid user email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "user@example.com"}, true},
|
||||
{"invalid user email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "user2@example.com"}, false},
|
||||
{"empty everything", []policy.Policy{{From: "example.com"}}, "example.com", &Identity{Email: "user2@example.com"}, false},
|
||||
{"valid domain", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "user@example.com"}, nil, true},
|
||||
{"valid domain with admins", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "user@example.com"}, []string{"admin@example.com"}, true},
|
||||
{"invalid domain prepend", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "a@1example.com"}, nil, false},
|
||||
{"invalid domain postpend", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "user@example.com2"}, nil, false},
|
||||
{"valid group", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"admin"}}, nil, true},
|
||||
{"invalid group", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"everyone"}}, nil, false},
|
||||
{"invalid empty", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{""}}, nil, false},
|
||||
{"valid group multiple", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"everyone", "admin"}}, nil, true},
|
||||
{"invalid group multiple", []policy.Policy{{From: "example.com", AllowedGroups: []string{"admin"}}}, "example.com", &Identity{Email: "user@example.com", Groups: []string{"everyones", "sadmin"}}, nil, false},
|
||||
{"valid user email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "user@example.com"}, nil, true},
|
||||
{"invalid user email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "user2@example.com"}, nil, false},
|
||||
{"empty everything", []policy.Policy{{From: "example.com"}}, "example.com", &Identity{Email: "user2@example.com"}, nil, false},
|
||||
// impersonation related
|
||||
{"admin not impersonating allowed", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "admin@example.com"}, []string{"admin@example.com"}, true},
|
||||
{"admin not impersonating denied", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "admin@admin-domain.com"}, []string{"admin@admin-domain.com"}, false},
|
||||
{"impersonating match domain", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@example.com"}, []string{"admin@admin-domain.com"}, true},
|
||||
{"impersonating does not match domain", []policy.Policy{{From: "example.com", AllowedDomains: []string{"example.com"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@not-example.com"}, []string{"admin@admin-domain.com"}, false},
|
||||
{"impersonating match email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@example.com"}, []string{"admin@admin-domain.com"}, true},
|
||||
{"impersonating does not match email", []policy.Policy{{From: "example.com", AllowedEmails: []string{"user@example.com"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@not-example.com"}, []string{"admin@admin-domain.com"}, false},
|
||||
{"impersonating match groups", []policy.Policy{{From: "example.com", AllowedGroups: []string{"support"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"support"}}, []string{"admin@admin-domain.com"}, true},
|
||||
{"impersonating match many groups", []policy.Policy{{From: "example.com", AllowedGroups: []string{"support"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"a", "b", "c", "support"}}, []string{"admin@admin-domain.com"}, true},
|
||||
{"impersonating does not match groups", []policy.Policy{{From: "example.com", AllowedGroups: []string{"support"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"not support"}}, []string{"admin@admin-domain.com"}, false},
|
||||
{"impersonating does not match many groups", []policy.Policy{{From: "example.com", AllowedGroups: []string{"support"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"not support", "b", "c"}}, []string{"admin@admin-domain.com"}, false},
|
||||
{"impersonating does not match empty groups", []policy.Policy{{From: "example.com", AllowedGroups: []string{"support"}}}, "example.com", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{""}}, []string{"admin@admin-domain.com"}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wl := NewIdentityWhitelist(tt.policies)
|
||||
wl := NewIdentityWhitelist(tt.policies, tt.admins)
|
||||
if got := wl.Valid(tt.route, tt.Identity); got != tt.want {
|
||||
t.Errorf("IdentityACLMap.Allowed() = %v, want %v", got, tt.want)
|
||||
t.Errorf("wl.Valid() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
@ -146,16 +146,14 @@ func wrapMiddleware(o *config.Options, mux *http.ServeMux) http.Handler {
|
|||
c = c.Append(middleware.NewHandler(log.Logger))
|
||||
c = c.Append(middleware.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
|
||||
middleware.FromRequest(r).Debug().
|
||||
Str("service", o.Services).
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Int("status", status).
|
||||
Int("size", size).
|
||||
Dur("duration", duration).
|
||||
Str("user", r.Header.Get(proxy.HeaderUserID)).
|
||||
Int("size", size).
|
||||
Int("status", status).
|
||||
Str("email", r.Header.Get(proxy.HeaderEmail)).
|
||||
Str("group", r.Header.Get(proxy.HeaderGroups)).
|
||||
// Str("sig", r.Header.Get(proxy.HeaderJWT)).
|
||||
Str("method", r.Method).
|
||||
Str("service", o.Services).
|
||||
Str("url", r.URL.String()).
|
||||
Msg("http-request")
|
||||
}))
|
||||
if o != nil && len(o.Headers) != 0 {
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
# export SERVICE="all" # optional, default is all
|
||||
# export LOG_LEVEL="info" # optional, default is debug
|
||||
|
||||
export AUTHENTICATE_SERVICE_URL=https://authenticate.corp.example.com
|
||||
export AUTHORIZE_SERVICE_URL=https://authorize.corp.example.com
|
||||
export AUTHENTICATE_SERVICE_URL=https://authenticate.corp.beyondperimeter.com
|
||||
export AUTHORIZE_SERVICE_URL=https://authorize.corp.beyondperimeter.com
|
||||
|
||||
# Certificates can be loaded as files or base64 encoded bytes. If neither is set, a
|
||||
# pomerium will attempt to locate a pair in the root directory
|
|
@ -3,7 +3,7 @@
|
|||
# Proxied routes and per-route policies are defined in a policy block
|
||||
policy: # Omit the 'policy' key if you are encoding it into an environment variable
|
||||
- from: httpbin.corp.beyondperimeter.com
|
||||
to: http://httpbin
|
||||
to: http://localhost:8000
|
||||
allowed_domains:
|
||||
- pomerium.io
|
||||
cors_allow_preflight: true
|
||||
|
@ -20,6 +20,6 @@ policy: # Omit the 'policy' key if you are encoding it into an environment varia
|
|||
- admins
|
||||
- developers
|
||||
- from: hello.corp.beyondperimeter.com
|
||||
to: http://hello:8080
|
||||
to: http://localhost:8080
|
||||
allowed_groups:
|
||||
- admins
|
||||
- admins@pomerium.io
|
|
@ -2,7 +2,7 @@
|
|||
address: ":8443" # optional, default is 443
|
||||
pomerium_debug: true # optional, default is false
|
||||
service: "all" # optional, default is all
|
||||
# log_level: "info" # optional, default is debug
|
||||
log_level: "info" # optional, default is debug
|
||||
|
||||
authenticate_service_url: https://authenticate.corp.pomerium.io:8443
|
||||
authorize_service_url: https://authorize.corp.pomerium.io:8443
|
||||
|
@ -12,10 +12,12 @@ authorize_service_url: https://authorize.corp.pomerium.io:8443
|
|||
certificate_file: "./cert.pem" # optional, defaults to `./cert.pem`
|
||||
certificate_key_file: "./privkey.pem" # optional, defaults to `./certprivkey.pem`
|
||||
certificate_authority_file: "./cert.pem"
|
||||
|
||||
# base64 encoded cert, eg. `base64 -i cert.pem` / `base64 -i privkey.pem`
|
||||
# certificate: |
|
||||
# "xxxxxx" # base64 encoded cert, eg. `base64 -i cert.pem`
|
||||
# "xxxxxx"
|
||||
# certificate_key: |
|
||||
# "xxxx" # base64 encoded key, eg. `base64 -i privkey.pem`
|
||||
# "xxxx"
|
||||
|
||||
# Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64`
|
||||
shared_secret: hsJIQsx9KKx4qVlggg/T3AuLTmVu0uHhwTQgMPlVs7U=
|
||||
|
|
|
@ -40,6 +40,15 @@ Service mode sets the pomerium service(s) to run. If testing, you may want to se
|
|||
|
||||
Address specifies the host and port to serve HTTPS and gRPC requests from. If empty, `:https`/`:443` is used.
|
||||
|
||||
### Administrators
|
||||
|
||||
- Environmental Variable: `ADMINISTRATORS`
|
||||
- Config File Key: `administrators`
|
||||
- Type: slice of `string`
|
||||
- Example: `"admin@example.com,admin2@example.com"`
|
||||
|
||||
Administrative users are [super user](https://en.wikipedia.org/wiki/Superuser) that can sign in as another user or group. User impersonation allows administrators to temporarily sign in as a different user.
|
||||
|
||||
### Shared Secret
|
||||
|
||||
- Environmental Variable: `SHARED_SECRET`
|
||||
|
@ -206,8 +215,7 @@ Allow unauthenticated HTTP OPTIONS requests as [per the CORS spec](https://devel
|
|||
- Optional
|
||||
- Default: `false`
|
||||
|
||||
**Use with caution:** Allow all requests for a given route, bypassing authentication and authorization.
|
||||
Suitable for publicly exposed web services.
|
||||
**Use with caution:** Allow all requests for a given route, bypassing authentication and authorization. Suitable for publicly exposed web services.
|
||||
|
||||
If this setting is enabled, no whitelists (e.g. Allowed Users) should be provided in this route.
|
||||
|
||||
|
@ -374,6 +382,16 @@ By default, conservative [secure HTTP headers](https://www.owasp.org/index.php/O
|
|||
|
||||

|
||||
|
||||
### Refresh Cooldown
|
||||
|
||||
- Environmental Variable: `REFRESH_COOLDOWN`
|
||||
- Config File Key: `refresh_cooldown`
|
||||
- Type: [Duration](https://golang.org/pkg/time/#Duration) `string`
|
||||
- Example: `10m`, `1h45m`
|
||||
- Default: `5m`
|
||||
|
||||
Refresh cooldown is the minimum amount of time between allowed manually refreshed sessions.
|
||||
|
||||
[base64 encoded]: https://en.wikipedia.org/wiki/Base64
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
[identity provider]: ./identity-providers.md
|
||||
|
|
9
go.mod
9
go.mod
|
@ -5,15 +5,16 @@ go 1.12
|
|||
require (
|
||||
github.com/golang/mock v1.2.0
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/pomerium/envconfig v1.5.0
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/pomerium/go-oidc v2.0.0+incompatible
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/rs/zerolog v1.12.0
|
||||
github.com/rs/zerolog v1.14.3
|
||||
github.com/spf13/viper v1.3.2
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
|
||||
golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
google.golang.org/api v0.1.0
|
||||
google.golang.org/grpc v1.19.1
|
||||
|
|
94
go.sum
94
go.sum
|
@ -4,38 +4,22 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
|
@ -43,61 +27,35 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
|||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pomerium/envconfig v1.5.0 h1:OeYS/p6AUxKFqCZHM5BG7pUb0m3MkaC1ZhRLPTHbk8g=
|
||||
github.com/pomerium/envconfig v1.5.0/go.mod h1:1Kz8Ca8PhJDtLYqgvbDZGn6GsJCvrT52SxQ3sPNJkDc=
|
||||
github.com/pomerium/go-oidc v2.0.0+incompatible h1:gVvG/ExWsHQqatV+uceROnGmbVYF44mDNx5nayBhC0o=
|
||||
github.com/pomerium/go-oidc v2.0.0+incompatible/go.mod h1:DRsGVw6MOgxbfq4Y57jKOE8lbEfayxeiY0A8/4vxjBM=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rs/zerolog v1.12.0 h1:aqZ1XRadoS8IBknR5IDFvGzbHly1X9ApIqOroooQF/c=
|
||||
github.com/rs/zerolog v1.12.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
|
||||
github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
|
@ -108,41 +66,28 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
|||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7 h1:Qe/u+eY379X4He4GBMFZYu3pmh1ML5yT1aL1ndNM1zQ=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -152,24 +97,23 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4 h1:VSJ45BzqrVgR4clSx415y1rHH7QAGhGt71J0ZmhLYrc=
|
||||
golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
|
@ -184,18 +128,12 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
|
|||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -94,6 +94,10 @@ type Options struct {
|
|||
|
||||
Policies []policy.Policy
|
||||
|
||||
// Administrators contains a set of emails with users who have super user
|
||||
// (sudo) access including the ability to impersonate other users' access
|
||||
Administrators []string `mapstructure:"administrators"`
|
||||
|
||||
// AuthenticateInternalAddr is used as an override when using a load balancer
|
||||
// or ingress that does not natively support routing gRPC.
|
||||
AuthenticateInternalAddr string `mapstructure:"authenticate_internal_url"`
|
||||
|
@ -116,12 +120,15 @@ type Options struct {
|
|||
// Headers to set on all proxied requests. Add a 'disable' key map to turn off.
|
||||
Headers map[string]string `mapstructure:"headers"`
|
||||
|
||||
// RefreshCooldown limits the rate a user can refresh her session
|
||||
RefreshCooldown time.Duration `mapstructure:"refresh_cooldown"`
|
||||
|
||||
// Sub-routes
|
||||
Routes map[string]string `mapstructure:"routes"`
|
||||
DefaultUpstreamTimeout time.Duration `mapstructure:"default_upstream_timeout"`
|
||||
}
|
||||
|
||||
// NewOptions returns a new options struct with default vaules
|
||||
// NewOptions returns a new options struct with default values
|
||||
func NewOptions() *Options {
|
||||
o := &Options{
|
||||
Debug: false,
|
||||
|
@ -148,6 +155,7 @@ func NewOptions() *Options {
|
|||
IdleTimeout: 5 * time.Minute,
|
||||
AuthenticateURL: new(url.URL),
|
||||
AuthorizeURL: new(url.URL),
|
||||
RefreshCooldown: time.Duration(5 * time.Minute),
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
|
28
internal/cryptutil/mock_cipher.go
Normal file
28
internal/cryptutil/mock_cipher.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package cryptutil // import "github.com/pomerium/pomerium/internal/cryptutil"
|
||||
|
||||
// MockCipher MockCSRFStore is a mock implementation of Cipher.
|
||||
type MockCipher struct {
|
||||
EncryptResponse []byte
|
||||
EncryptError error
|
||||
DecryptResponse []byte
|
||||
DecryptError error
|
||||
MarshalResponse string
|
||||
MarshalError error
|
||||
UnmarshalError error
|
||||
}
|
||||
|
||||
// Encrypt is a mock implementation of MockCipher.
|
||||
func (mc MockCipher) Encrypt(b []byte) ([]byte, error) { return mc.EncryptResponse, mc.EncryptError }
|
||||
|
||||
// Decrypt is a mock implementation of MockCipher.
|
||||
func (mc MockCipher) Decrypt(b []byte) ([]byte, error) { return mc.DecryptResponse, mc.DecryptError }
|
||||
|
||||
// Marshal is a mock implementation of MockCipher.
|
||||
func (mc MockCipher) Marshal(i interface{}) (string, error) {
|
||||
return mc.MarshalResponse, mc.MarshalError
|
||||
}
|
||||
|
||||
// Unmarshal is a mock implementation of MockCipher.
|
||||
func (mc MockCipher) Unmarshal(s string, i interface{}) error {
|
||||
return mc.UnmarshalError
|
||||
}
|
|
@ -30,7 +30,11 @@ func CodeForError(err error) int {
|
|||
}
|
||||
|
||||
// ErrorResponse renders an error page for errors given a message and a status code.
|
||||
// If no message is passed, defaults to the text of the status code.
|
||||
func ErrorResponse(rw http.ResponseWriter, req *http.Request, message string, code int) {
|
||||
if message == "" {
|
||||
message = http.StatusText(code)
|
||||
}
|
||||
if req.Header.Get("Accept") == "application/json" {
|
||||
var response struct {
|
||||
Error string `json:"error"`
|
||||
|
|
|
@ -170,7 +170,6 @@ func (p *GoogleProvider) Authenticate(code string) (*sessions.SessionState, erro
|
|||
AccessToken: oauth2Token.AccessToken,
|
||||
RefreshToken: oauth2Token.RefreshToken,
|
||||
RefreshDeadline: oauth2Token.Expiry.Truncate(time.Second),
|
||||
LifetimeDeadline: sessions.ExtendDeadline(p.SessionLifetimeTTL),
|
||||
Email: claims.Email,
|
||||
User: idToken.Subject,
|
||||
Groups: groups,
|
||||
|
|
|
@ -113,7 +113,6 @@ func (p *AzureProvider) Authenticate(code string) (*sessions.SessionState, error
|
|||
AccessToken: oauth2Token.AccessToken,
|
||||
RefreshToken: oauth2Token.RefreshToken,
|
||||
RefreshDeadline: oauth2Token.Expiry.Truncate(time.Second),
|
||||
LifetimeDeadline: sessions.ExtendDeadline(p.SessionLifetimeTTL),
|
||||
Email: claims.Email,
|
||||
User: idToken.Subject,
|
||||
Groups: groups,
|
||||
|
|
|
@ -87,7 +87,6 @@ type Provider struct {
|
|||
ClientSecret string
|
||||
ProviderURL string
|
||||
Scopes []string
|
||||
SessionLifetimeTTL time.Duration
|
||||
|
||||
// Some providers, such as google, require additional remote api calls to retrieve
|
||||
// user details like groups. Provider is responsible for parsing.
|
||||
|
@ -160,7 +159,6 @@ func (p *Provider) Authenticate(code string) (*sessions.SessionState, error) {
|
|||
AccessToken: oauth2Token.AccessToken,
|
||||
RefreshToken: oauth2Token.RefreshToken,
|
||||
RefreshDeadline: oauth2Token.Expiry.Truncate(time.Second),
|
||||
LifetimeDeadline: sessions.ExtendDeadline(p.SessionLifetimeTTL),
|
||||
Email: claims.Email,
|
||||
User: idToken.Subject,
|
||||
Groups: claims.Groups,
|
||||
|
|
|
@ -210,7 +210,6 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
|||
&SessionState{
|
||||
AccessToken: "token1234",
|
||||
RefreshToken: "refresh4321",
|
||||
LifetimeDeadline: time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC(),
|
||||
RefreshDeadline: time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC(),
|
||||
Email: "user@domain.com",
|
||||
User: "user",
|
||||
|
@ -219,7 +218,6 @@ func TestCookieStore_SaveSession(t *testing.T) {
|
|||
&SessionState{
|
||||
AccessToken: "token1234",
|
||||
RefreshToken: "refresh4321",
|
||||
LifetimeDeadline: time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC(),
|
||||
RefreshDeadline: time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC(),
|
||||
Email: "user@domain.com",
|
||||
User: "user",
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
|
@ -17,18 +21,14 @@ type SessionState struct {
|
|||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
IDToken string `json:"id_token"`
|
||||
|
||||
RefreshDeadline time.Time `json:"refresh_deadline"`
|
||||
LifetimeDeadline time.Time `json:"lifetime_deadline"`
|
||||
|
||||
Email string `json:"email"`
|
||||
User string `json:"user"` // 'sub' in jwt
|
||||
User string `json:"user"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
// LifetimePeriodExpired returns true if the lifetime has expired
|
||||
func (s *SessionState) LifetimePeriodExpired() bool {
|
||||
return isExpired(s.LifetimeDeadline)
|
||||
ImpersonateEmail string
|
||||
ImpersonateGroups []string
|
||||
}
|
||||
|
||||
// RefreshPeriodExpired returns true if the refresh period has expired
|
||||
|
@ -36,6 +36,28 @@ func (s *SessionState) RefreshPeriodExpired() bool {
|
|||
return isExpired(s.RefreshDeadline)
|
||||
}
|
||||
|
||||
type idToken struct {
|
||||
Issuer string `json:"iss"`
|
||||
Subject string `json:"sub"`
|
||||
Expiry jsonTime `json:"exp"`
|
||||
IssuedAt jsonTime `json:"iat"`
|
||||
Nonce string `json:"nonce"`
|
||||
AtHash string `json:"at_hash"`
|
||||
}
|
||||
|
||||
// IssuedAt parses the IDToken's issue date and returns a valid go time.Time.
|
||||
func (s *SessionState) IssuedAt() (time.Time, error) {
|
||||
payload, err := parseJWT(s.IDToken)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("internal/sessions: malformed jwt: %v", err)
|
||||
}
|
||||
var token idToken
|
||||
if err := json.Unmarshal(payload, &token); err != nil {
|
||||
return time.Time{}, fmt.Errorf("internal/sessions: failed to unmarshal claims: %v", err)
|
||||
}
|
||||
return time.Time(token.IssuedAt), nil
|
||||
}
|
||||
|
||||
func isExpired(t time.Time) bool {
|
||||
return t.Before(time.Now())
|
||||
}
|
||||
|
@ -61,3 +83,37 @@ func UnmarshalSession(value string, c cryptutil.Cipher) (*SessionState, error) {
|
|||
func ExtendDeadline(ttl time.Duration) time.Time {
|
||||
return time.Now().Add(ttl).Truncate(time.Second)
|
||||
}
|
||||
|
||||
func parseJWT(p string) ([]byte, error) {
|
||||
parts := strings.Split(p, ".")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("internal/sessions: malformed jwt, expected 3 parts got %d", len(parts))
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("internal/sessions: malformed jwt payload: %v", err)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
type jsonTime time.Time
|
||||
|
||||
func (j *jsonTime) UnmarshalJSON(b []byte) error {
|
||||
var n json.Number
|
||||
if err := json.Unmarshal(b, &n); err != nil {
|
||||
return err
|
||||
}
|
||||
var unix int64
|
||||
|
||||
if t, err := n.Int64(); err == nil {
|
||||
unix = t
|
||||
} else {
|
||||
f, err := n.Float64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unix = int64(f)
|
||||
}
|
||||
*j = jsonTime(time.Unix(unix, 0))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||
want := &SessionState{
|
||||
AccessToken: "token1234",
|
||||
RefreshToken: "refresh4321",
|
||||
LifetimeDeadline: time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC(),
|
||||
RefreshDeadline: time.Now().Add(1 * time.Hour).Truncate(time.Second).UTC(),
|
||||
Email: "user@domain.com",
|
||||
User: "user",
|
||||
|
@ -45,16 +44,10 @@ func TestSessionStateExpirations(t *testing.T) {
|
|||
session := &SessionState{
|
||||
AccessToken: "token1234",
|
||||
RefreshToken: "refresh4321",
|
||||
LifetimeDeadline: time.Now().Add(-1 * time.Hour),
|
||||
RefreshDeadline: time.Now().Add(-1 * time.Hour),
|
||||
Email: "user@domain.com",
|
||||
User: "user",
|
||||
}
|
||||
|
||||
if !session.LifetimePeriodExpired() {
|
||||
t.Errorf("expected lifetime period to be expired")
|
||||
}
|
||||
|
||||
if !session.RefreshPeriodExpired() {
|
||||
t.Errorf("expected lifetime period to be expired")
|
||||
}
|
||||
|
@ -80,3 +73,30 @@ func TestExtendDeadline(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionState_IssuedAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
IDToken string
|
||||
want time.Time
|
||||
wantErr bool
|
||||
}{
|
||||
{"simple parse", "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlkzYm1qV3R4US16OW1fM1RLb0dtRWciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY3MjY4NywiZXhwIjoxNTU4Njc2Mjg3fQ.a4g8W94E7iVJhiIUmsNMwJssfx3Evi8sXeiXgXMC7kHNvftQ2CFU_LJ-dqZ5Jf61OXcrp26r7lUcTNENXuen9tyUWAiHvxk6OHTxZusdywTCY5xowpSZBO9PDWYrmmdvfhRbaKO6QVAUMkbKr1Tr8xqfoaYVXNZhERXhcVReDznI0ccbwCGrNx5oeqiL4eRdZY9eqFXi4Yfee0mkef9oyVPc2HvnpwcpM0eckYa_l_ZQChGjXVGBFIus_Ao33GbWDuc9gs-_Vp2ev4KUT2qWb7AXMCGDLx0tWI9umm7mCBi_7xnaanGKUYcVwcSrv45arllAAwzuNxO0BVw3oRWa5Q", time.Unix(1558672687, 0), false},
|
||||
{"bad jwt", "x.x.x-x-x", time.Time{}, true},
|
||||
{"malformed jwt", "x", time.Time{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &SessionState{IDToken: tt.IDToken}
|
||||
got, err := s.IssuedAt()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SessionState.IssuedAt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SessionState.IssuedAt() = %v, want %v", got.Format(time.RFC3339), tt.want.Format(time.RFC3339))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package templates // import "github.com/pomerium/pomerium/internal/templates"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/version"
|
||||
)
|
||||
|
||||
// New loads html and style resources directly. Panics on failure.
|
||||
|
@ -17,129 +14,240 @@ func New() *template.Template {
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,'Helvetica Neue', sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 1em;
|
||||
line-height: 1.42857143;
|
||||
color: #333;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background: #F8F8FF;
|
||||
}
|
||||
p {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 40em;
|
||||
display: block;
|
||||
margin: 10% auto;
|
||||
|
||||
#main {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.content, .message, button {
|
||||
border: 1px solid rgba(0,0,0,.125);
|
||||
border-bottom-width: 4px;
|
||||
|
||||
|
||||
#info-box {
|
||||
max-width: 480px;
|
||||
width: 480px;
|
||||
margin-top: 200px;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
letter-spacing: 0.3px;
|
||||
text-transform: uppercase;
|
||||
color: #32325d;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
text-align: center;
|
||||
background: #F8F8FF;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 15px 0;
|
||||
color: #32325d;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0 -30px;
|
||||
padding: 20px 30px 30px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8fb;
|
||||
background-color: #F8F8FF;
|
||||
}
|
||||
.content, .message {
|
||||
background-color: #fff;
|
||||
padding: 2rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
fieldset {
|
||||
margin-bottom: 20px;
|
||||
background: #FCFCFF;
|
||||
box-shadow: 0 1px 3px 0 rgba(50, 50, 93, 0.15), 0 4px 6px 0 rgba(112, 157, 199, 0.15);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-size: 0;
|
||||
}
|
||||
.error, .message {
|
||||
border-bottom-color: #c00;
|
||||
|
||||
fieldset label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 42px;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
.message {
|
||||
padding: 1.5rem 2rem 1.3rem;
|
||||
|
||||
fieldset label:not(:last-child) {
|
||||
border-bottom: 1px solid #f0f5fa;
|
||||
}
|
||||
header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.075);
|
||||
margin: -2rem 0 2rem;
|
||||
padding: 2rem 0 1.8rem;
|
||||
}
|
||||
header h1 {
|
||||
font-size: 1.5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
.error header {
|
||||
color: #c00;
|
||||
}
|
||||
.details {
|
||||
font-size: .85rem;
|
||||
color: #999;
|
||||
}
|
||||
button {
|
||||
color: #fff;
|
||||
background-color: #3B8686;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
padding: 1rem 2.5rem;
|
||||
text-shadow: 0 3px 1px rgba(0,0,0,.2);
|
||||
outline: none;
|
||||
}
|
||||
button:active {
|
||||
border-top-width: 4px;
|
||||
border-bottom-width: 1px;
|
||||
text-shadow: none;
|
||||
}
|
||||
footer {
|
||||
font-size: 0.75em;
|
||||
color: #999;
|
||||
|
||||
fieldset label span {
|
||||
min-width: 125px;
|
||||
padding: 0 15px;
|
||||
text-align: right;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
#group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
#group::before {
|
||||
display: inline-flex;
|
||||
content: '';
|
||||
height: 15px;
|
||||
background-position: -1000px -1000px;
|
||||
background-repeat: no-repeat;
|
||||
// margin-right: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-table;
|
||||
margin-top: -72px;
|
||||
background: #F8F8FF;
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 115px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ok{
|
||||
fill: #6E43E8;
|
||||
}
|
||||
|
||||
.error{
|
||||
fill: #EB292F;
|
||||
}
|
||||
|
||||
p.message {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.field {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
background: transparent;
|
||||
font-weight: 400;
|
||||
color: #31325f;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
fieldset .select::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 5px;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
margin-top: -2px;
|
||||
pointer-events: none;
|
||||
background: #6E43E8 url("data:image/svg+xml;utf8,<svg viewBox='0 0 140 140' width='24' height='24' xmlns='http://www.w3.org/2000/svg'><g><path d='m121.3,34.6c-1.6-1.6-4.2-1.6-5.8,0l-51,51.1-51.1-51.1c-1.6-1.6-4.2-1.6-5.8,0-1.6,1.6-1.6,4.2 0,5.8l53.9,53.9c0.8,0.8 1.8,1.2 2.9,1.2 1,0 2.1-0.4 2.9-1.2l53.9-53.9c1.7-1.6 1.7-4.2 0.1-5.8z' fill='white'/></g></svg>") no-repeat;
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
color: #313b3f;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.flex{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #FCFCFF;
|
||||
background: #6E43E8;
|
||||
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
font-weight: 700;
|
||||
width: 50%;
|
||||
height: 40px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button.half{
|
||||
flex-grow:0;
|
||||
flex-shrink:0;
|
||||
flex-basis:calc(50% - 10px);
|
||||
}
|
||||
|
||||
.button.full{
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 7px 14px 0 rgba(50, 50, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.off-color{
|
||||
background: #5735B5;
|
||||
}
|
||||
|
||||
</style>
|
||||
{{end}}`))
|
||||
t = template.Must(t.Parse(fmt.Sprintf(`{{define "footer.html"}}Secured by <b>pomerium</b> %s {{end}}`, version.FullVersion())))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "sign_in_message.html"}}
|
||||
{{if eq (len .AllowedDomains) 1}}
|
||||
{{if eq (index .AllowedDomains 0) "@*"}}
|
||||
<p>You may sign in with any {{.ProviderName}} account.</p>
|
||||
{{else}}
|
||||
<p>You may sign in with your <b>{{index .AllowedDomains 0}}</b> {{.ProviderName}} account.</p>
|
||||
{{end}}
|
||||
{{else if gt (len .AllowedDomains) 1}}
|
||||
<p>
|
||||
You may sign in with any of these {{.ProviderName}} accounts:<br>
|
||||
{{range $i, $e := .AllowedDomains}}{{if $i}}, {{end}}<b>{{$e}}</b>{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}`))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "sign_in.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Sign In</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<header>
|
||||
<h1>Sign in to <b>{{.Destination}}</b></h1>
|
||||
</header>
|
||||
|
||||
{{template "sign_in_message.html" .}}
|
||||
|
||||
<form method="GET" action="/start">
|
||||
<input type="hidden" name="redirect_uri" value="{{.Redirect}}">
|
||||
<button type="submit" class="btn">Sign in with {{.ProviderName}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<footer>{{template "footer.html"}} </footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}`))
|
||||
|
||||
template.Must(t.Parse(`
|
||||
|
@ -147,49 +255,114 @@ footer {
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Error</title>
|
||||
<title>{{.Code}} - {{.Title}}</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content error">
|
||||
<header>
|
||||
<h1>{{.Title}}</h1>
|
||||
</header>
|
||||
<p>
|
||||
{{.Message}}<br>
|
||||
<span class="details">HTTP {{.Code}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<footer>{{template "footer.html"}} </footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>{{end}}`))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "sign_out.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
<head>
|
||||
<title>Sign Out</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<header>
|
||||
<h1>Sign out of <b>{{.Destination}}</b></h1>
|
||||
</header>
|
||||
|
||||
<p>You're currently signed in as <b>{{.Email}}</b>. This will also sign you out of other internal apps.</p>
|
||||
<form method="POST" action="/sign_out">
|
||||
<input type="hidden" name="redirect_uri" value="{{.Redirect}}">
|
||||
<input type="hidden" name="sig" value="{{.Signature}}">
|
||||
<input type="hidden" name="ts" value="{{.Timestamp}}">
|
||||
<button type="submit">Sign out</button>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
<svg class="icon error" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/></svg>
|
||||
<h1 class="title">{{.Title}}</h1>
|
||||
<section>
|
||||
<p class="message">{{.Message}}.</p>
|
||||
<p class="message">Troubleshoot your <a href="/.pomerium">session</a>.</p>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<footer>{{template "footer.html"}}</footer>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io" style="display: block;">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 139 30"><defs><style>.a {fill: #6e43e8;}.a,.b {fill-rule: evenodd;}.b,.c {fill: #fff;}</style></defs><title>powered-by-pomerium</title><path class="a" d="M10.6,5.5H138.4c3.09,0,5.6,2,5.6,4.39V31.11c0,2.42-2.51,4.39-5.6,4.39H10.6c-3.09,0-5.6-2-5.6-4.39V9.89C5,7.47,7.51,5.5,10.6,5.5Z" transform="translate(-5 -5.5)" /><path class="b" d="M75.4,26.62H73.94l1.13-2.79-2.25-5.69h1.54L75.78,22l1.43-3.87h1.54Zm-5.61-2.44a2.42,2.42,0,0,1-1.5-.55V24H66.78V15.56h1.51v3a2.48,2.48,0,0,1,1.5-.55c1.58,0,2.66,1.28,2.66,3.09S71.37,24.18,69.79,24.18Zm-.32-4.88a1.68,1.68,0,0,0-1.18.53v2.52a1.65,1.65,0,0,0,1.18.54c.85,0,1.44-.73,1.44-1.8S70.32,19.3,69.47,19.3Zm-8.8,4.33a2.38,2.38,0,0,1-1.5.55c-1.57,0-2.66-1.27-2.66-3.09S57.6,18,59.17,18a2.44,2.44,0,0,1,1.5.55v-3h1.52V24H60.67Zm0-3.8a1.63,1.63,0,0,0-1.17-.53c-.86,0-1.45.73-1.45,1.79s.59,1.8,1.45,1.8a1.6,1.6,0,0,0,1.17-.54Zm-9,1.68A1.69,1.69,0,0,0,53.47,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,53.11,18,2.66,2.66,0,0,1,55.7,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34a1.38,1.38,0,0,0-1.37,1.36h2.57A1.28,1.28,0,0,0,53.05,19.17Zm-5.34.93V24H46.2v-5.9h1.51v.59A2,2,0,0,1,49.16,18a1.65,1.65,0,0,1,.49.06v1.35a1.83,1.83,0,0,0-.53-.07A1.87,1.87,0,0,0,47.71,20.1ZM41,21.51A1.69,1.69,0,0,0,42.76,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,42.4,18,2.66,2.66,0,0,1,45,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34A1.38,1.38,0,0,0,41,20.53h2.57A1.28,1.28,0,0,0,42.34,19.17ZM35.7,24l-1.2-4-1.2,4H32l-2-5.9h1.51l1.19,4,1.19-4h1.37l1.19,4,1.19-4h1.51l-2,5.9Zm-9.23.14a2.94,2.94,0,0,1-3-3.09,3,3,0,1,1,6.07,0A2.94,2.94,0,0,1,26.47,24.18Zm0-4.92c-.88,0-1.49.75-1.49,1.83s.61,1.83,1.49,1.83S28,22.18,28,21.09,27.35,19.26,26.47,19.26Zm-6.62,1.87H18.49V24H17V15.93h2.87a2.61,2.61,0,1,1,0,5.2Zm-.22-4H18.49V19.9h1.14a1.38,1.38,0,1,0,0-2.75Z" transform="translate(-5 -5.5)" /><path class="c" d="M132.71,14.9A3.93,3.93,0,0,0,128.78,11H94.59a3.93,3.93,0,0,0-3.93,3.92V31.06h2.71V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2.47ZM93.37,19a5.49,5.49,0,1,1,11,0Zm12.95,0a5.49,5.49,0,1,1,11,0Zm12.94,0a5.49,5.49,0,1,1,11,0Z" transform="translate(-5 -5.5)" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{end}}`))
|
||||
|
||||
t = template.Must(t.Parse(`
|
||||
{{define "dashboard.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" charset="utf-8">
|
||||
|
||||
<head>
|
||||
<title>Pomerium</title>
|
||||
{{template "header.html"}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="info-box">
|
||||
<div class="card">
|
||||
<svg class="icon ok" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
|
||||
</svg>
|
||||
<form method="POST" action="{{.SignoutURL}}">
|
||||
<section>
|
||||
<h2>Session</h2>
|
||||
<p class="message">Your current session details.</p>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input name="email" type="email" class="field" value="{{.Email}}" disabled>
|
||||
</label>
|
||||
<label>
|
||||
<span>User</span>
|
||||
<input name="user" type="text" class="field" value="{{.User}}" disabled>
|
||||
</label>
|
||||
<label class="select">
|
||||
<span>Groups</span>
|
||||
<div id="group" class="field">
|
||||
<select name="group">
|
||||
{{range .Groups}}
|
||||
<option value="{{.}}">{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<span>Expiry</span>
|
||||
<input name="session expiration" type="text" class="field" value="{{.RefreshDeadline}}" disabled>
|
||||
</label>
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
<button class="button half" type="submit">Sign Out</button>
|
||||
<a href="/.pomerium/refresh" class="button half">Refresh</a>
|
||||
</div>
|
||||
</form>
|
||||
{{if .IsAdmin}}
|
||||
<form method="POST" action="/.pomerium/impersonate">
|
||||
<section>
|
||||
<h2>Sign-in-as</h2>
|
||||
<p class="message">Administrators can temporarily impersonate another a user.</p>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input name="email" type="email" class="field" value="{{.ImpersonateEmail}}" placeholder="user@example.com">
|
||||
</label>
|
||||
<label>
|
||||
<span>Group</span>
|
||||
<input name="group" type="text" class="field" value="{{.ImpersonateGroup}}" placeholder="engineering">
|
||||
</label>
|
||||
</fieldset>
|
||||
</section>
|
||||
<div class="flex">
|
||||
<input name="csrf" type="hidden" value="{{.CSRF}}">
|
||||
<button class="button full" type="submit">Impersonate session</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://www.pomerium.io" style="display: block;">
|
||||
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 139 30"><defs><style>.a {fill: #6e43e8;}.a,.b {fill-rule: evenodd;}.b,.c {fill: #fff;}</style></defs><title>powered-by-pomerium</title><path class="a" d="M10.6,5.5H138.4c3.09,0,5.6,2,5.6,4.39V31.11c0,2.42-2.51,4.39-5.6,4.39H10.6c-3.09,0-5.6-2-5.6-4.39V9.89C5,7.47,7.51,5.5,10.6,5.5Z" transform="translate(-5 -5.5)" /><path class="b" d="M75.4,26.62H73.94l1.13-2.79-2.25-5.69h1.54L75.78,22l1.43-3.87h1.54Zm-5.61-2.44a2.42,2.42,0,0,1-1.5-.55V24H66.78V15.56h1.51v3a2.48,2.48,0,0,1,1.5-.55c1.58,0,2.66,1.28,2.66,3.09S71.37,24.18,69.79,24.18Zm-.32-4.88a1.68,1.68,0,0,0-1.18.53v2.52a1.65,1.65,0,0,0,1.18.54c.85,0,1.44-.73,1.44-1.8S70.32,19.3,69.47,19.3Zm-8.8,4.33a2.38,2.38,0,0,1-1.5.55c-1.57,0-2.66-1.27-2.66-3.09S57.6,18,59.17,18a2.44,2.44,0,0,1,1.5.55v-3h1.52V24H60.67Zm0-3.8a1.63,1.63,0,0,0-1.17-.53c-.86,0-1.45.73-1.45,1.79s.59,1.8,1.45,1.8a1.6,1.6,0,0,0,1.17-.54Zm-9,1.68A1.69,1.69,0,0,0,53.47,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,53.11,18,2.66,2.66,0,0,1,55.7,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34a1.38,1.38,0,0,0-1.37,1.36h2.57A1.28,1.28,0,0,0,53.05,19.17Zm-5.34.93V24H46.2v-5.9h1.51v.59A2,2,0,0,1,49.16,18a1.65,1.65,0,0,1,.49.06v1.35a1.83,1.83,0,0,0-.53-.07A1.87,1.87,0,0,0,47.71,20.1ZM41,21.51A1.69,1.69,0,0,0,42.76,23a3.55,3.55,0,0,0,1.76-.56v1.26a4.73,4.73,0,0,1-2,.46,3,3,0,0,1-3-3.13A2.87,2.87,0,0,1,42.4,18,2.66,2.66,0,0,1,45,21a5.53,5.53,0,0,1,0,.56Zm1.37-2.34A1.38,1.38,0,0,0,41,20.53h2.57A1.28,1.28,0,0,0,42.34,19.17ZM35.7,24l-1.2-4-1.2,4H32l-2-5.9h1.51l1.19,4,1.19-4h1.37l1.19,4,1.19-4h1.51l-2,5.9Zm-9.23.14a2.94,2.94,0,0,1-3-3.09,3,3,0,1,1,6.07,0A2.94,2.94,0,0,1,26.47,24.18Zm0-4.92c-.88,0-1.49.75-1.49,1.83s.61,1.83,1.49,1.83S28,22.18,28,21.09,27.35,19.26,26.47,19.26Zm-6.62,1.87H18.49V24H17V15.93h2.87a2.61,2.61,0,1,1,0,5.2Zm-.22-4H18.49V19.9h1.14a1.38,1.38,0,1,0,0-2.75Z" transform="translate(-5 -5.5)" /><path class="c" d="M132.71,14.9A3.93,3.93,0,0,0,128.78,11H94.59a3.93,3.93,0,0,0-3.93,3.92V31.06h2.71V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2V26.55h0a5.49,5.49,0,1,1,11,0h0v4.51h2.47ZM93.37,19a5.49,5.49,0,1,1,11,0Zm12.95,0a5.49,5.49,0,1,1,11,0Zm12.94,0a5.49,5.49,0,1,1,11,0Z" transform="translate(-5 -5.5)" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
- from: httpbin.corp.beyondperimeter.com
|
||||
to: http://httpbin
|
||||
allowed_domains:
|
||||
- pomerium.io
|
||||
cors_allow_preflight: true
|
||||
timeout: 30s
|
||||
- from: external-httpbin.corp.beyondperimeter.com
|
||||
to: httpbin.org
|
||||
allowed_domains:
|
||||
- gmail.com
|
||||
- from: weirdlyssl.corp.beyondperimeter.com
|
||||
to: http://neverssl.com
|
||||
allowed_users:
|
||||
- bdd@pomerium.io
|
||||
allowed_groups:
|
||||
- admins
|
||||
- developers
|
||||
- from: hello.corp.beyondperimeter.com
|
||||
to: http://hello:8080
|
||||
allowed_groups:
|
||||
- admins
|
|
@ -35,7 +35,7 @@ func (m *AuthenticateRequest) Reset() { *m = AuthenticateRequest{} }
|
|||
func (m *AuthenticateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AuthenticateRequest) ProtoMessage() {}
|
||||
func (*AuthenticateRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authenticate_2c495f1e6e8d5900, []int{0}
|
||||
return fileDescriptor_authenticate_d9796afa57ba1f78, []int{0}
|
||||
}
|
||||
func (m *AuthenticateRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AuthenticateRequest.Unmarshal(m, b)
|
||||
|
@ -73,7 +73,7 @@ func (m *ValidateRequest) Reset() { *m = ValidateRequest{} }
|
|||
func (m *ValidateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ValidateRequest) ProtoMessage() {}
|
||||
func (*ValidateRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authenticate_2c495f1e6e8d5900, []int{1}
|
||||
return fileDescriptor_authenticate_d9796afa57ba1f78, []int{1}
|
||||
}
|
||||
func (m *ValidateRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ValidateRequest.Unmarshal(m, b)
|
||||
|
@ -111,7 +111,7 @@ func (m *ValidateReply) Reset() { *m = ValidateReply{} }
|
|||
func (m *ValidateReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*ValidateReply) ProtoMessage() {}
|
||||
func (*ValidateReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authenticate_2c495f1e6e8d5900, []int{2}
|
||||
return fileDescriptor_authenticate_d9796afa57ba1f78, []int{2}
|
||||
}
|
||||
func (m *ValidateReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ValidateReply.Unmarshal(m, b)
|
||||
|
@ -146,7 +146,6 @@ type Session struct {
|
|||
Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Groups []string `protobuf:"bytes,6,rep,name=groups,proto3" json:"groups,omitempty"`
|
||||
RefreshDeadline *timestamp.Timestamp `protobuf:"bytes,7,opt,name=refresh_deadline,json=refreshDeadline,proto3" json:"refresh_deadline,omitempty"`
|
||||
LifetimeDeadline *timestamp.Timestamp `protobuf:"bytes,8,opt,name=lifetime_deadline,json=lifetimeDeadline,proto3" json:"lifetime_deadline,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
@ -156,7 +155,7 @@ func (m *Session) Reset() { *m = Session{} }
|
|||
func (m *Session) String() string { return proto.CompactTextString(m) }
|
||||
func (*Session) ProtoMessage() {}
|
||||
func (*Session) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authenticate_2c495f1e6e8d5900, []int{3}
|
||||
return fileDescriptor_authenticate_d9796afa57ba1f78, []int{3}
|
||||
}
|
||||
func (m *Session) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Session.Unmarshal(m, b)
|
||||
|
@ -225,13 +224,6 @@ func (m *Session) GetRefreshDeadline() *timestamp.Timestamp {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Session) GetLifetimeDeadline() *timestamp.Timestamp {
|
||||
if m != nil {
|
||||
return m.LifetimeDeadline
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AuthenticateRequest)(nil), "authenticate.AuthenticateRequest")
|
||||
proto.RegisterType((*ValidateRequest)(nil), "authenticate.ValidateRequest")
|
||||
|
@ -377,32 +369,31 @@ var _Authenticator_serviceDesc = grpc.ServiceDesc{
|
|||
Metadata: "authenticate.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("authenticate.proto", fileDescriptor_authenticate_2c495f1e6e8d5900) }
|
||||
func init() { proto.RegisterFile("authenticate.proto", fileDescriptor_authenticate_d9796afa57ba1f78) }
|
||||
|
||||
var fileDescriptor_authenticate_2c495f1e6e8d5900 = []byte{
|
||||
// 378 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x6e, 0x9b, 0x40,
|
||||
0x10, 0xc6, 0x8d, 0xff, 0x81, 0xc7, 0x58, 0x76, 0xa7, 0x7f, 0x44, 0xa9, 0xaa, 0xda, 0xf4, 0xe2,
|
||||
0x56, 0x15, 0x96, 0xdc, 0x53, 0x8f, 0x95, 0x5a, 0x25, 0xca, 0x91, 0x58, 0xb9, 0x5a, 0x18, 0xc6,
|
||||
0xf6, 0x2a, 0x98, 0x25, 0xec, 0x12, 0xc9, 0x2f, 0x97, 0x67, 0xc9, 0xa3, 0x44, 0x2c, 0x20, 0x43,
|
||||
0x64, 0x2b, 0x37, 0x66, 0xf6, 0x37, 0x1f, 0xb3, 0xdf, 0xb7, 0x80, 0x7e, 0x26, 0xf7, 0x14, 0x4b,
|
||||
0x16, 0xf8, 0x92, 0xdc, 0x24, 0xe5, 0x92, 0xa3, 0x59, 0xef, 0xd9, 0xdf, 0x76, 0x9c, 0xef, 0x22,
|
||||
0x5a, 0xa8, 0xb3, 0x4d, 0xb6, 0x5d, 0x48, 0x76, 0x20, 0x21, 0xfd, 0x43, 0x52, 0xe0, 0xce, 0x0f,
|
||||
0x78, 0xff, 0xb7, 0x36, 0xe0, 0xd1, 0x43, 0x46, 0x42, 0x22, 0x42, 0x37, 0xe0, 0x21, 0x59, 0xda,
|
||||
0x54, 0x9b, 0x0f, 0x3c, 0xf5, 0xed, 0xfc, 0x82, 0xf1, 0x9d, 0x1f, 0xb1, 0xb0, 0x86, 0x7d, 0x06,
|
||||
0x83, 0x85, 0x6b, 0xc9, 0xef, 0x29, 0x2e, 0x51, 0x9d, 0x85, 0xab, 0xbc, 0x74, 0x7e, 0xc2, 0xe8,
|
||||
0x44, 0x27, 0xd1, 0x51, 0xb1, 0x62, 0xfd, 0x98, 0xf7, 0x14, 0x6b, 0x78, 0x3a, 0x13, 0x0a, 0x71,
|
||||
0x9e, 0xda, 0xa0, 0xdf, 0x92, 0x10, 0x8c, 0xc7, 0x38, 0x03, 0xd3, 0x0f, 0x02, 0x12, 0xa2, 0x21,
|
||||
0x3b, 0x2c, 0x7a, 0x4a, 0x1a, 0xbf, 0xc3, 0x28, 0xa5, 0x6d, 0x4a, 0x62, 0x5f, 0x32, 0x6d, 0xc5,
|
||||
0x98, 0x65, 0xb3, 0x80, 0xea, 0xab, 0x75, 0x1a, 0xab, 0xe5, 0x97, 0xcb, 0x04, 0xa5, 0x56, 0xb7,
|
||||
0xb8, 0x5c, 0xfe, 0x8d, 0x1f, 0xa0, 0x47, 0x07, 0x9f, 0x45, 0x56, 0x4f, 0x35, 0x8b, 0x02, 0x3f,
|
||||
0x41, 0x7f, 0x97, 0xf2, 0x2c, 0x11, 0x56, 0x7f, 0xda, 0x99, 0x0f, 0xbc, 0xb2, 0xc2, 0xff, 0x30,
|
||||
0xa9, 0x36, 0x08, 0xc9, 0x0f, 0x23, 0x16, 0x93, 0xa5, 0x4f, 0xb5, 0xf9, 0x70, 0x69, 0xbb, 0x85,
|
||||
0xe3, 0x6e, 0xe5, 0xb8, 0xbb, 0xaa, 0x1c, 0xf7, 0xc6, 0xe5, 0xcc, 0xbf, 0x72, 0x04, 0xaf, 0xe0,
|
||||
0x5d, 0xc4, 0xb6, 0x94, 0x67, 0x72, 0xd2, 0x31, 0xde, 0xd4, 0x99, 0x54, 0x43, 0x95, 0xd0, 0xf2,
|
||||
0x59, 0x83, 0x51, 0x2d, 0x46, 0x9e, 0xe2, 0x0d, 0x98, 0xf5, 0x5c, 0x71, 0xe6, 0x36, 0xde, 0xca,
|
||||
0x99, 0xcc, 0xed, 0x8f, 0x4d, 0xa4, 0x0c, 0xc4, 0x69, 0xe1, 0x35, 0x18, 0x55, 0x94, 0xf8, 0xb5,
|
||||
0x09, 0xbd, 0x7a, 0x10, 0xf6, 0x97, 0x4b, 0xc7, 0x49, 0x74, 0x74, 0x5a, 0xf8, 0x07, 0x74, 0xaf,
|
||||
0xf0, 0x00, 0xcf, 0xff, 0xed, 0xe2, 0x12, 0x9b, 0xbe, 0x32, 0xe2, 0xf7, 0x4b, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xcb, 0xcf, 0xe8, 0x63, 0xf4, 0x02, 0x00, 0x00,
|
||||
var fileDescriptor_authenticate_d9796afa57ba1f78 = []byte{
|
||||
// 354 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x4d, 0x4f, 0xb3, 0x40,
|
||||
0x14, 0x85, 0xcb, 0xdb, 0x0f, 0xda, 0x5b, 0x9a, 0xbe, 0xb9, 0x7e, 0x04, 0x31, 0xc6, 0x16, 0x37,
|
||||
0xd5, 0x18, 0x9a, 0xd4, 0x95, 0x4b, 0x13, 0x4d, 0x8c, 0x4b, 0x6c, 0xdc, 0x36, 0x14, 0x6e, 0xdb,
|
||||
0x89, 0x94, 0x41, 0x66, 0x30, 0xe9, 0xbf, 0xf5, 0x4f, 0xb8, 0x37, 0x0c, 0x10, 0xc1, 0xb4, 0x3b,
|
||||
0xee, 0x99, 0xe7, 0x0e, 0x67, 0xce, 0x01, 0xf4, 0x52, 0xb9, 0xa1, 0x48, 0x32, 0xdf, 0x93, 0xe4,
|
||||
0xc4, 0x09, 0x97, 0x1c, 0x8d, 0xaa, 0x66, 0x5d, 0xae, 0x39, 0x5f, 0x87, 0x34, 0x55, 0x67, 0xcb,
|
||||
0x74, 0x35, 0x95, 0x6c, 0x4b, 0x42, 0x7a, 0xdb, 0x38, 0xc7, 0xed, 0x6b, 0x38, 0x7a, 0xa8, 0x2c,
|
||||
0xb8, 0xf4, 0x91, 0x92, 0x90, 0x88, 0xd0, 0xf2, 0x79, 0x40, 0xa6, 0x36, 0xd2, 0x26, 0x3d, 0x57,
|
||||
0x7d, 0xdb, 0xb7, 0x30, 0x7c, 0xf3, 0x42, 0x16, 0x54, 0xb0, 0x33, 0xe8, 0xb2, 0x60, 0x21, 0xf9,
|
||||
0x3b, 0x45, 0x05, 0xaa, 0xb3, 0x60, 0x9e, 0x8d, 0xf6, 0x0d, 0x0c, 0x7e, 0xe9, 0x38, 0xdc, 0x29,
|
||||
0x56, 0x2c, 0x3e, 0x33, 0x4d, 0xb1, 0x5d, 0x57, 0x67, 0x42, 0x21, 0xf6, 0xb7, 0x06, 0xfa, 0x2b,
|
||||
0x09, 0xc1, 0x78, 0x84, 0x63, 0x30, 0x3c, 0xdf, 0x27, 0x21, 0x6a, 0xd7, 0xf6, 0x73, 0x4d, 0x5d,
|
||||
0x8d, 0x57, 0x30, 0x48, 0x68, 0x95, 0x90, 0xd8, 0x14, 0xcc, 0x3f, 0xc5, 0x18, 0x85, 0x98, 0x43,
|
||||
0x55, 0x6b, 0xcd, 0x9a, 0xb5, 0xec, 0x71, 0xa9, 0xa0, 0xc4, 0x6c, 0xe5, 0x8f, 0xcb, 0xbe, 0xf1,
|
||||
0x18, 0xda, 0xb4, 0xf5, 0x58, 0x68, 0xb6, 0x95, 0x98, 0x0f, 0x78, 0x0a, 0x9d, 0x75, 0xc2, 0xd3,
|
||||
0x58, 0x98, 0x9d, 0x51, 0x73, 0xd2, 0x73, 0x8b, 0x09, 0x9f, 0xe0, 0x7f, 0xe9, 0x20, 0x20, 0x2f,
|
||||
0x08, 0x59, 0x44, 0xa6, 0x3e, 0xd2, 0x26, 0xfd, 0x99, 0xe5, 0xe4, 0x89, 0x3b, 0x65, 0xe2, 0xce,
|
||||
0xbc, 0x4c, 0xdc, 0x1d, 0x16, 0x3b, 0x8f, 0xc5, 0xca, 0xec, 0x4b, 0x83, 0x41, 0x25, 0x7d, 0x9e,
|
||||
0xe0, 0x0b, 0x18, 0xd5, 0x3a, 0x70, 0xec, 0xd4, 0x2a, 0xde, 0x53, 0x95, 0x75, 0x52, 0x47, 0x8a,
|
||||
0x1c, 0xed, 0x06, 0x3e, 0x43, 0xb7, 0x6c, 0x00, 0x2f, 0xea, 0xd0, 0x9f, 0x1e, 0xad, 0xf3, 0x43,
|
||||
0xc7, 0x71, 0xb8, 0xb3, 0x1b, 0x78, 0x0f, 0xba, 0x9b, 0x5b, 0xc7, 0xfd, 0x7f, 0x3b, 0x68, 0x62,
|
||||
0xd9, 0x51, 0x39, 0xdc, 0xfd, 0x04, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x47, 0xff, 0x7e, 0xab, 0x02,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -23,5 +23,4 @@ message Session {
|
|||
string email = 5;
|
||||
repeated string groups = 6;
|
||||
google.protobuf.Timestamp refresh_deadline = 7;
|
||||
google.protobuf.Timestamp lifetime_deadline = 8;
|
||||
}
|
||||
|
|
|
@ -12,10 +12,7 @@ func SessionFromProto(p *Session) (*sessions.SessionState, error) {
|
|||
if p == nil {
|
||||
return nil, fmt.Errorf("proto/authenticate: SessionFromProto session cannot be nil")
|
||||
}
|
||||
lifetimeDeadline, err := ptypes.Timestamp(p.LifetimeDeadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proto/authenticate: couldn't parse lifetime deadline %v", err)
|
||||
}
|
||||
|
||||
refreshDeadline, err := ptypes.Timestamp(p.RefreshDeadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proto/authenticate: couldn't parse refresh deadline %v", err)
|
||||
|
@ -28,7 +25,6 @@ func SessionFromProto(p *Session) (*sessions.SessionState, error) {
|
|||
User: p.User,
|
||||
Groups: p.Groups,
|
||||
RefreshDeadline: refreshDeadline,
|
||||
LifetimeDeadline: lifetimeDeadline,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -37,10 +33,6 @@ func ProtoFromSession(s *sessions.SessionState) (*Session, error) {
|
|||
if s == nil {
|
||||
return nil, fmt.Errorf("proto/authenticate: ProtoFromSession session cannot be nil")
|
||||
}
|
||||
lifetimeDeadline, err := ptypes.TimestampProto(s.LifetimeDeadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proto/authenticate: couldn't parse lifetime deadline %v", err)
|
||||
}
|
||||
refreshDeadline, err := ptypes.TimestampProto(s.RefreshDeadline)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proto/authenticate: couldn't parse refresh deadline %v", err)
|
||||
|
@ -53,6 +45,5 @@ func ProtoFromSession(s *sessions.SessionState) (*Session, error) {
|
|||
User: s.User,
|
||||
Groups: s.Groups,
|
||||
RefreshDeadline: refreshDeadline,
|
||||
LifetimeDeadline: lifetimeDeadline,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -23,70 +23,87 @@ var _ = math.Inf
|
|||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type AuthorizeRequest struct {
|
||||
type Identity struct {
|
||||
// request context
|
||||
Route string `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"`
|
||||
// user context
|
||||
User string `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"`
|
||||
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Groups []string `protobuf:"bytes,4,rep,name=groups,proto3" json:"groups,omitempty"`
|
||||
// user context
|
||||
ImpersonateEmail string `protobuf:"bytes,5,opt,name=impersonate_email,json=impersonateEmail,proto3" json:"impersonate_email,omitempty"`
|
||||
ImpersonateGroups []string `protobuf:"bytes,6,rep,name=impersonate_groups,json=impersonateGroups,proto3" json:"impersonate_groups,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AuthorizeRequest) Reset() { *m = AuthorizeRequest{} }
|
||||
func (m *AuthorizeRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AuthorizeRequest) ProtoMessage() {}
|
||||
func (*AuthorizeRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authorize_dad4e29706fc340b, []int{0}
|
||||
func (m *Identity) Reset() { *m = Identity{} }
|
||||
func (m *Identity) String() string { return proto.CompactTextString(m) }
|
||||
func (*Identity) ProtoMessage() {}
|
||||
func (*Identity) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authorize_86f44c7874077551, []int{0}
|
||||
}
|
||||
func (m *AuthorizeRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AuthorizeRequest.Unmarshal(m, b)
|
||||
func (m *Identity) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Identity.Unmarshal(m, b)
|
||||
}
|
||||
func (m *AuthorizeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_AuthorizeRequest.Marshal(b, m, deterministic)
|
||||
func (m *Identity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Identity.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (dst *AuthorizeRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AuthorizeRequest.Merge(dst, src)
|
||||
func (dst *Identity) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Identity.Merge(dst, src)
|
||||
}
|
||||
func (m *AuthorizeRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_AuthorizeRequest.Size(m)
|
||||
func (m *Identity) XXX_Size() int {
|
||||
return xxx_messageInfo_Identity.Size(m)
|
||||
}
|
||||
func (m *AuthorizeRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AuthorizeRequest.DiscardUnknown(m)
|
||||
func (m *Identity) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Identity.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AuthorizeRequest proto.InternalMessageInfo
|
||||
var xxx_messageInfo_Identity proto.InternalMessageInfo
|
||||
|
||||
func (m *AuthorizeRequest) GetRoute() string {
|
||||
func (m *Identity) GetRoute() string {
|
||||
if m != nil {
|
||||
return m.Route
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AuthorizeRequest) GetUser() string {
|
||||
func (m *Identity) GetUser() string {
|
||||
if m != nil {
|
||||
return m.User
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AuthorizeRequest) GetEmail() string {
|
||||
func (m *Identity) GetEmail() string {
|
||||
if m != nil {
|
||||
return m.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AuthorizeRequest) GetGroups() []string {
|
||||
func (m *Identity) GetGroups() []string {
|
||||
if m != nil {
|
||||
return m.Groups
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Identity) GetImpersonateEmail() string {
|
||||
if m != nil {
|
||||
return m.ImpersonateEmail
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Identity) GetImpersonateGroups() []string {
|
||||
if m != nil {
|
||||
return m.ImpersonateGroups
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthorizeReply struct {
|
||||
IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
|
@ -98,7 +115,7 @@ func (m *AuthorizeReply) Reset() { *m = AuthorizeReply{} }
|
|||
func (m *AuthorizeReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*AuthorizeReply) ProtoMessage() {}
|
||||
func (*AuthorizeReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authorize_dad4e29706fc340b, []int{1}
|
||||
return fileDescriptor_authorize_86f44c7874077551, []int{1}
|
||||
}
|
||||
func (m *AuthorizeReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AuthorizeReply.Unmarshal(m, b)
|
||||
|
@ -125,9 +142,48 @@ func (m *AuthorizeReply) GetIsValid() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
type IsAdminReply struct {
|
||||
IsAdmin bool `protobuf:"varint,1,opt,name=is_admin,json=isAdmin,proto3" json:"is_admin,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *IsAdminReply) Reset() { *m = IsAdminReply{} }
|
||||
func (m *IsAdminReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*IsAdminReply) ProtoMessage() {}
|
||||
func (*IsAdminReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_authorize_86f44c7874077551, []int{2}
|
||||
}
|
||||
func (m *IsAdminReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_IsAdminReply.Unmarshal(m, b)
|
||||
}
|
||||
func (m *IsAdminReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_IsAdminReply.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (dst *IsAdminReply) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_IsAdminReply.Merge(dst, src)
|
||||
}
|
||||
func (m *IsAdminReply) XXX_Size() int {
|
||||
return xxx_messageInfo_IsAdminReply.Size(m)
|
||||
}
|
||||
func (m *IsAdminReply) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_IsAdminReply.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_IsAdminReply proto.InternalMessageInfo
|
||||
|
||||
func (m *IsAdminReply) GetIsAdmin() bool {
|
||||
if m != nil {
|
||||
return m.IsAdmin
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AuthorizeRequest)(nil), "authorize.AuthorizeRequest")
|
||||
proto.RegisterType((*Identity)(nil), "authorize.Identity")
|
||||
proto.RegisterType((*AuthorizeReply)(nil), "authorize.AuthorizeReply")
|
||||
proto.RegisterType((*IsAdminReply)(nil), "authorize.IsAdminReply")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
@ -142,7 +198,8 @@ const _ = grpc.SupportPackageIsVersion4
|
|||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type AuthorizerClient interface {
|
||||
Authorize(ctx context.Context, in *AuthorizeRequest, opts ...grpc.CallOption) (*AuthorizeReply, error)
|
||||
Authorize(ctx context.Context, in *Identity, opts ...grpc.CallOption) (*AuthorizeReply, error)
|
||||
IsAdmin(ctx context.Context, in *Identity, opts ...grpc.CallOption) (*IsAdminReply, error)
|
||||
}
|
||||
|
||||
type authorizerClient struct {
|
||||
|
@ -153,7 +210,7 @@ func NewAuthorizerClient(cc *grpc.ClientConn) AuthorizerClient {
|
|||
return &authorizerClient{cc}
|
||||
}
|
||||
|
||||
func (c *authorizerClient) Authorize(ctx context.Context, in *AuthorizeRequest, opts ...grpc.CallOption) (*AuthorizeReply, error) {
|
||||
func (c *authorizerClient) Authorize(ctx context.Context, in *Identity, opts ...grpc.CallOption) (*AuthorizeReply, error) {
|
||||
out := new(AuthorizeReply)
|
||||
err := c.cc.Invoke(ctx, "/authorize.Authorizer/Authorize", in, out, opts...)
|
||||
if err != nil {
|
||||
|
@ -162,9 +219,19 @@ func (c *authorizerClient) Authorize(ctx context.Context, in *AuthorizeRequest,
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *authorizerClient) IsAdmin(ctx context.Context, in *Identity, opts ...grpc.CallOption) (*IsAdminReply, error) {
|
||||
out := new(IsAdminReply)
|
||||
err := c.cc.Invoke(ctx, "/authorize.Authorizer/IsAdmin", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AuthorizerServer is the server API for Authorizer service.
|
||||
type AuthorizerServer interface {
|
||||
Authorize(context.Context, *AuthorizeRequest) (*AuthorizeReply, error)
|
||||
Authorize(context.Context, *Identity) (*AuthorizeReply, error)
|
||||
IsAdmin(context.Context, *Identity) (*IsAdminReply, error)
|
||||
}
|
||||
|
||||
func RegisterAuthorizerServer(s *grpc.Server, srv AuthorizerServer) {
|
||||
|
@ -172,7 +239,7 @@ func RegisterAuthorizerServer(s *grpc.Server, srv AuthorizerServer) {
|
|||
}
|
||||
|
||||
func _Authorizer_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AuthorizeRequest)
|
||||
in := new(Identity)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -184,7 +251,25 @@ func _Authorizer_Authorize_Handler(srv interface{}, ctx context.Context, dec fun
|
|||
FullMethod: "/authorize.Authorizer/Authorize",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthorizerServer).Authorize(ctx, req.(*AuthorizeRequest))
|
||||
return srv.(AuthorizerServer).Authorize(ctx, req.(*Identity))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Authorizer_IsAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Identity)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthorizerServer).IsAdmin(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/authorize.Authorizer/IsAdmin",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthorizerServer).IsAdmin(ctx, req.(*Identity))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
@ -197,25 +282,34 @@ var _Authorizer_serviceDesc = grpc.ServiceDesc{
|
|||
MethodName: "Authorize",
|
||||
Handler: _Authorizer_Authorize_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "IsAdmin",
|
||||
Handler: _Authorizer_IsAdmin_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "authorize.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("authorize.proto", fileDescriptor_authorize_dad4e29706fc340b) }
|
||||
func init() { proto.RegisterFile("authorize.proto", fileDescriptor_authorize_86f44c7874077551) }
|
||||
|
||||
var fileDescriptor_authorize_dad4e29706fc340b = []byte{
|
||||
// 187 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0x2c, 0x2d, 0xc9,
|
||||
0xc8, 0x2f, 0xca, 0xac, 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x28,
|
||||
0x65, 0x71, 0x09, 0x38, 0xc2, 0x38, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x22, 0x5c,
|
||||
0xac, 0x45, 0xf9, 0xa5, 0x25, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x10, 0x8e, 0x90,
|
||||
0x10, 0x17, 0x4b, 0x69, 0x71, 0x6a, 0x91, 0x04, 0x13, 0x58, 0x10, 0xcc, 0x06, 0xa9, 0x4c, 0xcd,
|
||||
0x4d, 0xcc, 0xcc, 0x91, 0x60, 0x86, 0xa8, 0x04, 0x73, 0x84, 0xc4, 0xb8, 0xd8, 0xd2, 0x8b, 0xf2,
|
||||
0x4b, 0x0b, 0x8a, 0x25, 0x58, 0x14, 0x98, 0x35, 0x38, 0x83, 0xa0, 0x3c, 0x25, 0x6d, 0x2e, 0x3e,
|
||||
0x24, 0xbb, 0x0a, 0x72, 0x2a, 0x85, 0x24, 0xb9, 0x38, 0x32, 0x8b, 0xe3, 0xcb, 0x12, 0x73, 0x32,
|
||||
0x53, 0xc0, 0x96, 0x71, 0x04, 0xb1, 0x67, 0x16, 0x87, 0x81, 0xb8, 0x46, 0xc1, 0x5c, 0x5c, 0x70,
|
||||
0xc5, 0x45, 0x42, 0xae, 0x5c, 0x9c, 0x70, 0x9e, 0x90, 0xb4, 0x1e, 0xc2, 0x43, 0xe8, 0x8e, 0x97,
|
||||
0x92, 0xc4, 0x2e, 0x59, 0x90, 0x53, 0xa9, 0xc4, 0x90, 0xc4, 0x06, 0xf6, 0xbf, 0x31, 0x20, 0x00,
|
||||
0x00, 0xff, 0xff, 0x28, 0xac, 0x76, 0x2d, 0x12, 0x01, 0x00, 0x00,
|
||||
var fileDescriptor_authorize_86f44c7874077551 = []byte{
|
||||
// 264 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0xbd, 0x4e, 0xc3, 0x30,
|
||||
0x10, 0x6e, 0x68, 0x9b, 0x26, 0x27, 0xc4, 0xcf, 0x81, 0x20, 0x65, 0xaa, 0x3c, 0x15, 0x55, 0x74,
|
||||
0x80, 0x89, 0x81, 0xa1, 0x03, 0x42, 0x5d, 0x33, 0xb0, 0x56, 0x46, 0xb1, 0xe0, 0xa4, 0x24, 0x8e,
|
||||
0x6c, 0x07, 0xa9, 0x3c, 0x00, 0x8f, 0xc5, 0xb3, 0xa1, 0x5c, 0xd2, 0xc4, 0x48, 0x6c, 0xf9, 0x7e,
|
||||
0x73, 0x77, 0x86, 0x53, 0x59, 0xbb, 0x0f, 0x6d, 0xe8, 0x4b, 0xad, 0x2b, 0xa3, 0x9d, 0xc6, 0xb8,
|
||||
0x27, 0xc4, 0x4f, 0x00, 0xd1, 0x36, 0x53, 0xa5, 0x23, 0xb7, 0xc7, 0x4b, 0x98, 0x1a, 0x5d, 0x3b,
|
||||
0x95, 0x04, 0x8b, 0x60, 0x19, 0xa7, 0x2d, 0x40, 0x84, 0x49, 0x6d, 0x95, 0x49, 0x8e, 0x98, 0xe4,
|
||||
0xef, 0xc6, 0xa9, 0x0a, 0x49, 0x79, 0x32, 0x6e, 0x9d, 0x0c, 0xf0, 0x0a, 0xc2, 0x77, 0xa3, 0xeb,
|
||||
0xca, 0x26, 0x93, 0xc5, 0x78, 0x19, 0xa7, 0x1d, 0xc2, 0x15, 0x9c, 0x53, 0x51, 0x29, 0x63, 0x75,
|
||||
0x29, 0x9d, 0xda, 0xb5, 0xc9, 0x29, 0x27, 0xcf, 0x3c, 0xe1, 0x99, 0x4b, 0xee, 0x00, 0x7d, 0x73,
|
||||
0x57, 0x18, 0x72, 0xa1, 0x5f, 0xf3, 0xc2, 0x82, 0x58, 0xc1, 0xc9, 0xe6, 0xb0, 0x4d, 0xaa, 0xaa,
|
||||
0x7c, 0x8f, 0x73, 0x88, 0xc8, 0xee, 0x3e, 0x65, 0x4e, 0x19, 0x2f, 0x12, 0xa5, 0x33, 0xb2, 0xaf,
|
||||
0x0d, 0x14, 0xb7, 0x70, 0xbc, 0xb5, 0x9b, 0xac, 0xa0, 0xd2, 0xb7, 0xca, 0x86, 0x18, 0xac, 0xac,
|
||||
0xdf, 0x7f, 0x07, 0x00, 0x7d, 0xb1, 0xc1, 0x27, 0x88, 0x7b, 0x84, 0x17, 0xeb, 0xe1, 0xa2, 0x87,
|
||||
0xe3, 0xdd, 0xcc, 0x3d, 0xf2, 0xef, 0x44, 0x62, 0x84, 0x8f, 0x30, 0xeb, 0x7e, 0xfc, 0x7f, 0xf8,
|
||||
0xda, 0x27, 0xbd, 0x09, 0xc5, 0xe8, 0x2d, 0xe4, 0x37, 0x7b, 0xf8, 0x0d, 0x00, 0x00, 0xff, 0xff,
|
||||
0x6d, 0x2f, 0xa0, 0x1b, 0xc6, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -3,16 +3,23 @@ syntax = "proto3";
|
|||
package authorize;
|
||||
|
||||
service Authorizer {
|
||||
rpc Authorize(AuthorizeRequest) returns (AuthorizeReply) {}
|
||||
rpc Authorize(Identity) returns (AuthorizeReply) {}
|
||||
rpc IsAdmin(Identity) returns (IsAdminReply) {}
|
||||
|
||||
}
|
||||
|
||||
message AuthorizeRequest {
|
||||
message Identity {
|
||||
// request context
|
||||
string route = 1;
|
||||
// user context
|
||||
string user = 2;
|
||||
string email = 3;
|
||||
repeated string groups = 4;
|
||||
// user context
|
||||
string impersonate_email = 5;
|
||||
repeated string impersonate_groups = 6;
|
||||
}
|
||||
|
||||
message AuthorizeReply { bool is_valid = 1; }
|
||||
|
||||
message IsAdminReply { bool is_admin = 1; }
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/pomerium/pomerium/proto/authorize (interfaces: AuthorizerClient)
|
||||
// Source: proto/authorize/authorize.pb.go
|
||||
|
||||
// Package mock_authorize is a generated GoMock package.
|
||||
package mock_authorize
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
authorize "github.com/pomerium/pomerium/proto/authorize"
|
||||
"github.com/pomerium/pomerium/proto/authorize"
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
|
@ -37,10 +37,10 @@ func (m *MockAuthorizerClient) EXPECT() *MockAuthorizerClientMockRecorder {
|
|||
}
|
||||
|
||||
// Authorize mocks base method
|
||||
func (m *MockAuthorizerClient) Authorize(arg0 context.Context, arg1 *authorize.AuthorizeRequest, arg2 ...grpc.CallOption) (*authorize.AuthorizeReply, error) {
|
||||
func (m *MockAuthorizerClient) Authorize(ctx context.Context, in *authorize.Identity, opts ...grpc.CallOption) (*authorize.AuthorizeReply, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs := []interface{}{ctx, in}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Authorize", varargs...)
|
||||
|
@ -50,8 +50,81 @@ func (m *MockAuthorizerClient) Authorize(arg0 context.Context, arg1 *authorize.A
|
|||
}
|
||||
|
||||
// Authorize indicates an expected call of Authorize
|
||||
func (mr *MockAuthorizerClientMockRecorder) Authorize(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
func (mr *MockAuthorizerClientMockRecorder) Authorize(ctx, in interface{}, opts ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
varargs := append([]interface{}{ctx, in}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authorize", reflect.TypeOf((*MockAuthorizerClient)(nil).Authorize), varargs...)
|
||||
}
|
||||
|
||||
// IsAdmin mocks base method
|
||||
func (m *MockAuthorizerClient) IsAdmin(ctx context.Context, in *authorize.Identity, opts ...grpc.CallOption) (*authorize.IsAdminReply, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, in}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "IsAdmin", varargs...)
|
||||
ret0, _ := ret[0].(*authorize.IsAdminReply)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IsAdmin indicates an expected call of IsAdmin
|
||||
func (mr *MockAuthorizerClientMockRecorder) IsAdmin(ctx, in interface{}, opts ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, in}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAdmin", reflect.TypeOf((*MockAuthorizerClient)(nil).IsAdmin), varargs...)
|
||||
}
|
||||
|
||||
// MockAuthorizerServer is a mock of AuthorizerServer interface
|
||||
type MockAuthorizerServer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAuthorizerServerMockRecorder
|
||||
}
|
||||
|
||||
// MockAuthorizerServerMockRecorder is the mock recorder for MockAuthorizerServer
|
||||
type MockAuthorizerServerMockRecorder struct {
|
||||
mock *MockAuthorizerServer
|
||||
}
|
||||
|
||||
// NewMockAuthorizerServer creates a new mock instance
|
||||
func NewMockAuthorizerServer(ctrl *gomock.Controller) *MockAuthorizerServer {
|
||||
mock := &MockAuthorizerServer{ctrl: ctrl}
|
||||
mock.recorder = &MockAuthorizerServerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockAuthorizerServer) EXPECT() *MockAuthorizerServerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Authorize mocks base method
|
||||
func (m *MockAuthorizerServer) Authorize(arg0 context.Context, arg1 *authorize.Identity) (*authorize.AuthorizeReply, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Authorize", arg0, arg1)
|
||||
ret0, _ := ret[0].(*authorize.AuthorizeReply)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Authorize indicates an expected call of Authorize
|
||||
func (mr *MockAuthorizerServerMockRecorder) Authorize(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authorize", reflect.TypeOf((*MockAuthorizerServer)(nil).Authorize), arg0, arg1)
|
||||
}
|
||||
|
||||
// IsAdmin mocks base method
|
||||
func (m *MockAuthorizerServer) IsAdmin(arg0 context.Context, arg1 *authorize.Identity) (*authorize.IsAdminReply, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsAdmin", arg0, arg1)
|
||||
ret0, _ := ret[0].(*authorize.IsAdminReply)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IsAdmin indicates an expected call of IsAdmin
|
||||
func (mr *MockAuthorizerServerMockRecorder) IsAdmin(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAdmin", reflect.TypeOf((*MockAuthorizerServer)(nil).IsAdmin), arg0, arg1)
|
||||
}
|
||||
|
|
|
@ -75,7 +75,6 @@ func TestProxy_Redeem(t *testing.T) {
|
|||
IdToken: "mocked id token",
|
||||
User: "user1",
|
||||
Email: "test@email.com",
|
||||
LifetimeDeadline: mockExpire,
|
||||
RefreshDeadline: mockExpire,
|
||||
}, nil)
|
||||
tests := []struct {
|
||||
|
@ -90,7 +89,6 @@ func TestProxy_Redeem(t *testing.T) {
|
|||
IDToken: "mocked id token",
|
||||
User: "user1",
|
||||
Email: "test@email.com",
|
||||
LifetimeDeadline: (fixedDate),
|
||||
RefreshDeadline: (fixedDate),
|
||||
}, false},
|
||||
{"empty code", "", nil, true},
|
||||
|
@ -172,7 +170,6 @@ func TestProxy_AuthenticateRefresh(t *testing.T) {
|
|||
).Return(&pb.Session{
|
||||
AccessToken: "new access token",
|
||||
RefreshDeadline: mockExpire,
|
||||
LifetimeDeadline: mockExpire,
|
||||
}, nil).AnyTimes()
|
||||
|
||||
tests := []struct {
|
||||
|
@ -186,7 +183,6 @@ func TestProxy_AuthenticateRefresh(t *testing.T) {
|
|||
&sessions.SessionState{
|
||||
AccessToken: "new access token",
|
||||
RefreshDeadline: fixedDate,
|
||||
LifetimeDeadline: fixedDate,
|
||||
}, false},
|
||||
{"empty refresh token", &sessions.SessionState{RefreshToken: ""}, nil, true},
|
||||
}
|
||||
|
|
|
@ -13,8 +13,11 @@ import (
|
|||
|
||||
// Authorizer provides the authorize service interface
|
||||
type Authorizer interface {
|
||||
// Authorize takes a code and returns a validated session or an error
|
||||
// Authorize takes a route and user session and returns whether the
|
||||
// request is valid per access policy
|
||||
Authorize(context.Context, string, *sessions.SessionState) (bool, error)
|
||||
// IsAdmin takes a session and returns whether the user is an administrator
|
||||
IsAdmin(context.Context, *sessions.SessionState) (bool, error)
|
||||
// Close closes the auth connection if any.
|
||||
Close() error
|
||||
}
|
||||
|
@ -35,29 +38,42 @@ func NewGRPCAuthorizeClient(opts *Options) (p *AuthorizeGRPC, err error) {
|
|||
return &AuthorizeGRPC{Conn: conn, client: client}, nil
|
||||
}
|
||||
|
||||
// AuthorizeGRPC is a gRPC implementation of an authenticator (authenticate client)
|
||||
// AuthorizeGRPC is a gRPC implementation of an authenticator (authorize client)
|
||||
type AuthorizeGRPC struct {
|
||||
Conn *grpc.ClientConn
|
||||
client pb.AuthorizerClient
|
||||
}
|
||||
|
||||
// Authorize makes an RPC call to the authorize service to creates a session state
|
||||
// from an encrypted code provided as a result of an oauth2 callback process.
|
||||
// Authorize takes a route and user session and returns whether the
|
||||
// request is valid per access policy
|
||||
func (a *AuthorizeGRPC) Authorize(ctx context.Context, route string, s *sessions.SessionState) (bool, error) {
|
||||
if s == nil {
|
||||
return false, errors.New("session cannot be nil")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
response, err := a.client.Authorize(ctx, &pb.AuthorizeRequest{
|
||||
response, err := a.client.Authorize(ctx, &pb.Identity{
|
||||
Route: route,
|
||||
User: s.User,
|
||||
Email: s.Email,
|
||||
Groups: s.Groups,
|
||||
ImpersonateEmail: s.ImpersonateEmail,
|
||||
ImpersonateGroups: s.ImpersonateGroups,
|
||||
})
|
||||
return response.GetIsValid(), err
|
||||
}
|
||||
|
||||
// IsAdmin takes a session and returns whether the user is an administrator
|
||||
func (a *AuthorizeGRPC) IsAdmin(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
||||
if s == nil {
|
||||
return false, errors.New("session cannot be nil")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
response, err := a.client.IsAdmin(ctx, &pb.Identity{Email: s.Email, Groups: s.Groups})
|
||||
return response.GetIsAdmin(), err
|
||||
}
|
||||
|
||||
// Close tears down the ClientConn and all underlying connections.
|
||||
func (a *AuthorizeGRPC) Close() error {
|
||||
return a.Conn.Close()
|
||||
|
|
|
@ -43,3 +43,35 @@ func TestAuthorizeGRPC_Authorize(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
func TestAuthorizeGRPC_IsAdmin(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
client := mock.NewMockAuthorizerClient(ctrl)
|
||||
client.EXPECT().IsAdmin(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(&authorize.IsAdminReply{IsAdmin: true}, nil).AnyTimes()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
s *sessions.SessionState
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{"good", &sessions.SessionState{User: "admin@pomerium.io", Email: "admin@pomerium.io"}, true, false},
|
||||
{"session cannot be nil", nil, false, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &AuthorizeGRPC{client: client}
|
||||
got, err := a.IsAdmin(context.Background(), tt.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AuthorizeGRPC.IsAdmin() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("AuthorizeGRPC.IsAdmin() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ func (a MockAuthenticate) Close() error { return a.CloseError }
|
|||
type MockAuthorize struct {
|
||||
AuthorizeResponse bool
|
||||
AuthorizeError error
|
||||
IsAdminResponse bool
|
||||
IsAdminError error
|
||||
CloseError error
|
||||
}
|
||||
|
||||
|
@ -49,3 +51,8 @@ func (a MockAuthorize) Close() error { return a.CloseError }
|
|||
func (a MockAuthorize) Authorize(ctx context.Context, route string, s *sessions.SessionState) (bool, error) {
|
||||
return a.AuthorizeResponse, a.AuthorizeError
|
||||
}
|
||||
|
||||
// IsAdmin is a mocked IsAdmin function.
|
||||
func (a MockAuthorize) IsAdmin(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
||||
return a.IsAdminResponse, a.IsAdminError
|
||||
}
|
||||
|
|
|
@ -5,18 +5,15 @@ import (
|
|||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
)
|
||||
|
||||
func TestMockAuthenticate(t *testing.T) {
|
||||
// Absurd, but I caught a typo this way.
|
||||
fixedDate := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
redeemResponse := &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
LifetimeDeadline: fixedDate,
|
||||
}
|
||||
ma := &MockAuthenticate{
|
||||
RedeemError: errors.New("RedeemError"),
|
||||
|
@ -24,7 +21,6 @@ func TestMockAuthenticate(t *testing.T) {
|
|||
RefreshResponse: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
LifetimeDeadline: fixedDate,
|
||||
},
|
||||
RefreshError: errors.New("RefreshError"),
|
||||
ValidateResponse: true,
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -15,6 +14,7 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/templates"
|
||||
)
|
||||
|
||||
// StateParameter holds the redirect id along with the session id.
|
||||
|
@ -33,11 +33,14 @@ func (p *Proxy) Handler() http.Handler {
|
|||
}))
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/robots.txt", p.RobotsTxt)
|
||||
mux.HandleFunc("/.pomerium/sign_out", p.SignOut)
|
||||
mux.HandleFunc("/.pomerium/callback", p.OAuthCallback)
|
||||
// mux.HandleFunc("/.pomerium/refresh", p.Refresh) //todo(bdd): needs DoS protection before inclusion
|
||||
mux.HandleFunc("/", p.Proxy)
|
||||
return validate.Then(mux)
|
||||
mux.HandleFunc("/.pomerium", p.UserDashboard)
|
||||
mux.HandleFunc("/.pomerium/impersonate", p.Impersonate) // POST
|
||||
mux.HandleFunc("/.pomerium/sign_out", p.SignOutCallback)
|
||||
// handlers handlers with validation
|
||||
mux.Handle("/.pomerium/callback", validate.ThenFunc(p.OAuthCallback))
|
||||
mux.Handle("/.pomerium/refresh", validate.ThenFunc(p.Refresh))
|
||||
mux.Handle("/", validate.ThenFunc(p.Proxy))
|
||||
return mux
|
||||
}
|
||||
|
||||
// RobotsTxt sets the User-Agent header in the response to be "Disallow"
|
||||
|
@ -46,17 +49,12 @@ func (p *Proxy) RobotsTxt(w http.ResponseWriter, _ *http.Request) {
|
|||
fmt.Fprintf(w, "User-agent: *\nDisallow: /")
|
||||
}
|
||||
|
||||
// SignOut redirects the request to the sign out url. It's the responsibility
|
||||
// SignOutCallback redirects the request to the sign out url. It's the responsibility
|
||||
// of the authenticate service to revoke the remote session and clear
|
||||
// the local session state.
|
||||
func (p *Proxy) SignOut(w http.ResponseWriter, r *http.Request) {
|
||||
redirectURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: r.Host,
|
||||
Path: "/",
|
||||
}
|
||||
fullURL := p.GetSignOutURL(p.AuthenticateURL, redirectURL)
|
||||
http.Redirect(w, r, fullURL.String(), http.StatusFound)
|
||||
func (p *Proxy) SignOutCallback(w http.ResponseWriter, r *http.Request) {
|
||||
redirectURL := &url.URL{Scheme: "https", Host: r.Host, Path: "/"}
|
||||
http.Redirect(w, r, redirectURL.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
// OAuthStart begins the authenticate flow, encrypting the redirect url
|
||||
|
@ -65,34 +63,47 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
|||
requestURI := r.URL.String()
|
||||
callbackURL := p.GetRedirectURL(r.Host)
|
||||
|
||||
// state prevents cross site forgery and maintain state across the client and server
|
||||
// CSRF value used to mitigate replay attacks.
|
||||
state := &StateParameter{
|
||||
SessionID: fmt.Sprintf("%x", cryptutil.GenerateKey()), // nonce
|
||||
RedirectURI: requestURI, // where to redirect the user back to
|
||||
SessionID: fmt.Sprintf("%x", cryptutil.GenerateKey()),
|
||||
RedirectURI: requestURI,
|
||||
}
|
||||
|
||||
// we encrypt this value to be opaque the browser cookie
|
||||
// this value will be unique since we always use a randomized nonce as part of marshaling
|
||||
encryptedCSRF, err := p.cipher.Marshal(state)
|
||||
// Encrypt, and save CSRF state. Will be checked on callback.
|
||||
localState, err := p.cipher.Marshal(state)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed to marshal csrf")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
p.csrfStore.SetCSRF(w, r, encryptedCSRF)
|
||||
p.csrfStore.SetCSRF(w, r, localState)
|
||||
|
||||
// we encrypt this value to be opaque the uri query value
|
||||
// this value will be unique since we always use a randomized nonce as part of marshaling
|
||||
encryptedState, err := p.cipher.Marshal(state)
|
||||
// Though the plaintext payload is identical, we re-encrypt which will
|
||||
// create a different cipher text using another nonce
|
||||
remoteState, err := p.cipher.Marshal(state)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed to encrypt cookie")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
signinURL := p.GetSignInURL(p.AuthenticateURL, callbackURL, encryptedState)
|
||||
log.FromRequest(r).Info().Str("SigninURL", signinURL.String()).Msg("proxy: oauth start")
|
||||
// redirect the user to the authenticate provider along with the encrypted state which
|
||||
// contains a redirect uri pointing back to the proxy
|
||||
|
||||
// Sanity check. The encrypted payload of local and remote state should
|
||||
// never match as each encryption round uses a cryptographic nonce.
|
||||
//
|
||||
// todo(bdd): since this should nearly (1/(2^32*2^32)) never happen should
|
||||
// we panic as a failure most likely means the rands entropy source is failing?
|
||||
if remoteState == localState {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Error().Msg("proxy: encrypted state should not match")
|
||||
httputil.ErrorResponse(w, r, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
signinURL := p.GetSignInURL(p.AuthenticateURL, callbackURL, remoteState)
|
||||
log.FromRequest(r).Debug().Str("SigninURL", signinURL.String()).Msg("proxy: oauth start")
|
||||
|
||||
// Redirect the user to the authenticate service along with the encrypted
|
||||
// state which contains a redirect uri back to the proxy and a nonce
|
||||
http.Redirect(w, r, signinURL.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
|
@ -112,23 +123,18 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, errorString, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// We begin the process of redeeming the code for an access token.
|
||||
session, err := p.AuthenticateClient.Redeem(r.Context(), r.Form.Get("code"))
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: error redeeming authorization code")
|
||||
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedState := r.Form.Get("state")
|
||||
stateParameter := &StateParameter{}
|
||||
err = p.cipher.Unmarshal(encryptedState, stateParameter)
|
||||
// Encrypted CSRF passed from authenticate service
|
||||
remoteStateEncrypted := r.Form.Get("state")
|
||||
remoteStatePlain := new(StateParameter)
|
||||
err = p.cipher.Unmarshal(remoteStateEncrypted, remoteStatePlain)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: could not unmarshal state")
|
||||
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypted CSRF from session storage
|
||||
c, err := p.csrfStore.GetCSRF(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed parsing csrf cookie")
|
||||
|
@ -136,44 +142,34 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
p.csrfStore.ClearCSRF(w, r)
|
||||
|
||||
encryptedCSRF := c.Value
|
||||
csrfParameter := &StateParameter{}
|
||||
err = p.cipher.Unmarshal(encryptedCSRF, csrfParameter)
|
||||
localStateEncrypted := c.Value
|
||||
localStatePlain := new(StateParameter)
|
||||
err = p.cipher.Unmarshal(localStateEncrypted, localStatePlain)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: couldn't unmarshal CSRF")
|
||||
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if encryptedState == encryptedCSRF {
|
||||
log.FromRequest(r).Error().Msg("encrypted state and CSRF should not be equal")
|
||||
httputil.ErrorResponse(w, r, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(stateParameter, csrfParameter) {
|
||||
log.FromRequest(r).Error().Msg("state and CSRF should be equal")
|
||||
httputil.ErrorResponse(w, r, "Bad request", http.StatusBadRequest)
|
||||
|
||||
// If the encrypted value of local and remote state match, reject.
|
||||
// Likely a replay attack or nonce-reuse.
|
||||
if remoteStateEncrypted == localStateEncrypted {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Error().Msg("proxy: local and remote state should not match")
|
||||
httputil.ErrorResponse(w, r, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// We store the session in a cookie and redirect the user back to the application
|
||||
err = p.sessionStore.SaveSession(w, r, session)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Msg("error saving session")
|
||||
httputil.ErrorResponse(w, r, "Error saving session", http.StatusInternalServerError)
|
||||
// Decrypted remote and local state struct (inc. nonce) must match
|
||||
if remoteStatePlain.SessionID != localStatePlain.SessionID {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.FromRequest(r).Error().Msg("proxy: CSRF mismatch")
|
||||
httputil.ErrorResponse(w, r, "CSRF mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.FromRequest(r).Debug().
|
||||
Str("code", r.Form.Get("code")).
|
||||
Str("state", r.Form.Get("state")).
|
||||
Str("RefreshToken", session.RefreshToken).
|
||||
Str("session", session.AccessToken).
|
||||
Str("RedirectURI", stateParameter.RedirectURI).
|
||||
Msg("session")
|
||||
|
||||
// This is the redirect back to the original requested application
|
||||
http.Redirect(w, r, stateParameter.RedirectURI, http.StatusFound)
|
||||
http.Redirect(w, r, remoteStatePlain.RedirectURI, http.StatusFound)
|
||||
}
|
||||
|
||||
// shouldSkipAuthentication contains conditions for skipping authentication.
|
||||
|
@ -223,8 +219,7 @@ func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
err = p.authenticate(w, r, session)
|
||||
if err != nil {
|
||||
if err = p.authenticate(w, r, session); err != nil {
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
log.Debug().Err(err).Msg("proxy: user unauthenticated")
|
||||
httputil.ErrorResponse(w, r, "User unauthenticated", http.StatusForbidden)
|
||||
|
@ -236,10 +231,6 @@ func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, "Access unauthorized", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// append
|
||||
r.Header.Set(HeaderUserID, session.User)
|
||||
r.Header.Set(HeaderEmail, session.Email)
|
||||
r.Header.Set(HeaderGroups, strings.Join(session.Groups, ","))
|
||||
}
|
||||
|
||||
// We have validated the users request and now proxy their request to the provided upstream.
|
||||
|
@ -251,33 +242,162 @@ func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
|||
route.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Refresh refreshes a user session, validating group, extending timeout period, without requiring
|
||||
// a user to re-authenticate
|
||||
// func (p *Proxy) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
// session, err := p.sessionStore.LoadSession(r)
|
||||
// if err != nil {
|
||||
// httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// session, err = p.AuthenticateClient.Refresh(r.Context(), session)
|
||||
// if err != nil {
|
||||
// log.FromRequest(r).Warn().Err(err).Msg("proxy: refresh failed")
|
||||
// httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// err = p.sessionStore.SaveSession(w, r, session)
|
||||
// if err != nil {
|
||||
// httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// jsonSession, err := json.Marshal(session)
|
||||
// if err != nil {
|
||||
// httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// fmt.Fprint(w, string(jsonSession))
|
||||
// }
|
||||
// UserDashboard lets users investigate, and refresh their current session.
|
||||
// It also contains certain administrative actions like user impersonation.
|
||||
// Nota bene: This endpoint does authentication, not authorization.
|
||||
func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := p.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: load session failed")
|
||||
httputil.ErrorResponse(w, r, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := p.authenticate(w, r, session); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: authenticate failed")
|
||||
httputil.ErrorResponse(w, r, "", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL := &url.URL{Scheme: "https", Host: r.Host, Path: "/.pomerium/sign_out"}
|
||||
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), session)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: is admin client")
|
||||
httputil.ErrorResponse(w, r, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// CSRF value used to mitigate replay attacks.
|
||||
csrf := &StateParameter{SessionID: fmt.Sprintf("%x", cryptutil.GenerateKey())}
|
||||
csrfCookie, err := p.cipher.Marshal(csrf)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed to marshal csrf")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
p.csrfStore.SetCSRF(w, r, csrfCookie)
|
||||
|
||||
t := struct {
|
||||
Email string
|
||||
User string
|
||||
Groups []string
|
||||
RefreshDeadline string
|
||||
SignoutURL string
|
||||
|
||||
IsAdmin bool
|
||||
ImpersonateEmail string
|
||||
ImpersonateGroup string
|
||||
CSRF string
|
||||
}{
|
||||
Email: session.Email,
|
||||
User: session.User,
|
||||
Groups: session.Groups,
|
||||
RefreshDeadline: time.Until(session.RefreshDeadline).Round(time.Second).String(),
|
||||
SignoutURL: p.GetSignOutURL(p.AuthenticateURL, redirectURL).String(),
|
||||
IsAdmin: isAdmin,
|
||||
ImpersonateEmail: session.ImpersonateEmail,
|
||||
ImpersonateGroup: strings.Join(session.ImpersonateGroups[:], ","),
|
||||
CSRF: csrf.SessionID,
|
||||
}
|
||||
templates.New().ExecuteTemplate(w, "dashboard.html", t)
|
||||
return
|
||||
}
|
||||
|
||||
// Refresh redeems and extends an existing authenticated oidc session with
|
||||
// the underlying idenity provider. All session details including groups,
|
||||
// timeouts, will be renewed.
|
||||
func (p *Proxy) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := p.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
iss, err := session.IssuedAt()
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: couldn't get token's create time")
|
||||
httputil.ErrorResponse(w, r, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// reject a refresh if it's been less than 5 minutes to prevent a bad actor
|
||||
// trying to DOS the identity provider.
|
||||
if time.Since(iss) < p.refreshCooldown {
|
||||
log.FromRequest(r).Error().Dur("cooldown", p.refreshCooldown).Err(err).Msg("proxy: refresh cooldown")
|
||||
httputil.ErrorResponse(w, r,
|
||||
fmt.Sprintf("Session must be %v old before refresh", p.refreshCooldown),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
newSession, err := p.AuthenticateClient.Refresh(r.Context(), session)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Warn().Err(err).Msg("proxy: refresh failed")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err = p.sessionStore.SaveSession(w, r, newSession); err != nil {
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/.pomerium", http.StatusFound)
|
||||
}
|
||||
|
||||
// Impersonate takes the result of a form and adds user impersonation details
|
||||
// to the user's current user sessions state if the user is currently an
|
||||
// administrative user. Requests are redirected back to the user dashboard.
|
||||
func (p *Proxy) Impersonate(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: impersonate form")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
session, err := p.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: load session")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// authorization check -- is this user an admin?
|
||||
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), session)
|
||||
if err != nil || !isAdmin {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: user must be admin to impersonate")
|
||||
httputil.ErrorResponse(w, r, "user must be admin to impersonate", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// CSRF check -- did this request originate from our form?
|
||||
c, err := p.csrfStore.GetCSRF(r)
|
||||
if err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: failed parsing csrf cookie")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
p.csrfStore.ClearCSRF(w, r)
|
||||
encryptedCSRF := c.Value
|
||||
decryptedCSRF := new(StateParameter)
|
||||
if err = p.cipher.Unmarshal(encryptedCSRF, decryptedCSRF); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: couldn't unmarshal CSRF")
|
||||
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if decryptedCSRF.SessionID != r.FormValue("csrf") {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: impersonate CSRF mismatch")
|
||||
httputil.ErrorResponse(w, r, "CSRF mismatch", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// OK to impersonation
|
||||
session.ImpersonateEmail = r.FormValue("email")
|
||||
session.ImpersonateGroups = strings.Split(r.FormValue("group"), ",")
|
||||
|
||||
if err := p.sessionStore.SaveSession(w, r, session); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("proxy: save session")
|
||||
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, "/.pomerium", http.StatusFound)
|
||||
}
|
||||
|
||||
// Authenticate authenticates a request by checking for a session cookie, and validating its expiration,
|
||||
// clearing the session cookie if it's invalid and returning an error if necessary..
|
||||
|
@ -297,6 +417,9 @@ func (p *Proxy) authenticate(w http.ResponseWriter, r *http.Request, session *se
|
|||
return fmt.Errorf("proxy: session valid: %v : %v", valid, err)
|
||||
}
|
||||
}
|
||||
r.Header.Set(HeaderUserID, session.User)
|
||||
r.Header.Set(HeaderEmail, session.Email)
|
||||
r.Header.Set(HeaderGroups, strings.Join(session.Groups, ","))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
|
@ -32,7 +34,12 @@ func (a mockCipher) Decrypt(s []byte) ([]byte, error) {
|
|||
}
|
||||
return []byte("OK"), nil
|
||||
}
|
||||
func (a mockCipher) Marshal(s interface{}) (string, error) { return "ok", nil }
|
||||
func (a mockCipher) Marshal(s interface{}) (string, error) {
|
||||
if s == "error" {
|
||||
return "", errors.New("error")
|
||||
}
|
||||
return "ok", nil
|
||||
}
|
||||
func (a mockCipher) Unmarshal(s string, i interface{}) error {
|
||||
if string(s) == "unmarshal error" || string(s) == "error" {
|
||||
return errors.New("error")
|
||||
|
@ -153,9 +160,8 @@ func TestProxy_Signout(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
req := httptest.NewRequest("GET", "/.pomerium/sign_out", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
proxy.SignOut(rr, req)
|
||||
proxy.SignOutCallback(rr, req)
|
||||
if status := rr.Code; status != http.StatusFound {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusFound)
|
||||
}
|
||||
|
@ -201,78 +207,6 @@ func TestProxy_Handler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProxy_OAuthCallback(t *testing.T) {
|
||||
normalSession := sessions.MockSessionStore{
|
||||
Session: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
LifetimeDeadline: time.Now().Add(10 * time.Second),
|
||||
RefreshDeadline: time.Now().Add(-10 * time.Second),
|
||||
},
|
||||
}
|
||||
normalAuth := clients.MockAuthenticate{
|
||||
RedeemResponse: &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
LifetimeDeadline: time.Now().Add(10 * time.Second),
|
||||
},
|
||||
}
|
||||
normalCsrf := sessions.MockCSRFStore{
|
||||
ResponseCSRF: "ok",
|
||||
GetError: nil,
|
||||
Cookie: &http.Cookie{
|
||||
Name: "something_csrf",
|
||||
Value: "csrf_state",
|
||||
}}
|
||||
tests := []struct {
|
||||
name string
|
||||
csrf sessions.MockCSRFStore
|
||||
session sessions.MockSessionStore
|
||||
authenticator clients.MockAuthenticate
|
||||
params map[string]string
|
||||
wantCode int
|
||||
}{
|
||||
{"good", normalCsrf, normalSession, normalAuth, map[string]string{"code": "code", "state": "state"}, http.StatusFound},
|
||||
{"error", normalCsrf, normalSession, normalAuth, map[string]string{"error": "some error"}, http.StatusForbidden},
|
||||
{"code err", normalCsrf, normalSession, clients.MockAuthenticate{RedeemError: errors.New("error")}, map[string]string{"code": "error"}, http.StatusInternalServerError},
|
||||
{"state err", normalCsrf, normalSession, normalAuth, map[string]string{"code": "code", "state": "error"}, http.StatusInternalServerError},
|
||||
{"csrf err", sessions.MockCSRFStore{GetError: errors.New("error")}, normalSession, normalAuth, map[string]string{"code": "code", "state": "state"}, http.StatusBadRequest},
|
||||
{"unmarshal err", sessions.MockCSRFStore{
|
||||
Cookie: &http.Cookie{
|
||||
Name: "something_csrf",
|
||||
Value: "unmarshal error",
|
||||
},
|
||||
}, normalSession, normalAuth, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||
{"encrypted state != CSRF", normalCsrf, normalSession, normalAuth, map[string]string{"code": "code", "state": "csrf_state"}, http.StatusBadRequest},
|
||||
{"session save err", normalCsrf, sessions.MockSessionStore{SaveError: errors.New("error")}, normalAuth, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
proxy, err := New(testOptions())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
proxy.sessionStore = &tt.session
|
||||
proxy.csrfStore = tt.csrf
|
||||
proxy.AuthenticateClient = tt.authenticator
|
||||
proxy.cipher = mockCipher{}
|
||||
// proxy.Csrf
|
||||
req := httptest.NewRequest(http.MethodPost, "/.pomerium/callback", nil)
|
||||
q := req.URL.Query()
|
||||
for k, v := range tt.params {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
w := httptest.NewRecorder()
|
||||
proxy.OAuthCallback(w, req)
|
||||
if status := w.Code; status != tt.wantCode {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
func Test_extendDeadline(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -333,7 +267,6 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
goodSession := &sessions.SessionState{
|
||||
AccessToken: "AccessToken",
|
||||
RefreshToken: "RefreshToken",
|
||||
LifetimeDeadline: time.Now().Add(10 * time.Second),
|
||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
||||
}
|
||||
|
||||
|
@ -374,13 +307,14 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
{"unknown host", opts, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusNotFound},
|
||||
{"user forbidden", opts, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden},
|
||||
// authenticate errors
|
||||
{"no session error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: http.ErrNoCookie, Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound},
|
||||
{"weird load session error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: errors.New("weird"), Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusInternalServerError},
|
||||
{"failed refreshed session", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: &sessions.SessionState{RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RefreshError: errors.New("refresh error")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusForbidden},
|
||||
{"cannot resave refreshed session", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{SaveError: errors.New("weird"), Session: &sessions.SessionState{RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusForbidden},
|
||||
{"authenticate validation error", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: false}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusForbidden},
|
||||
{"public access", optsPublic, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusOK},
|
||||
{"public access, but unknown host", optsPublic, http.MethodGet, defaultHeaders, "https://nothttpbin.corp.example", &sessions.MockSessionStore{Session: goodSession}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden},
|
||||
// no session, redirect to login
|
||||
{"no http found (no session)", opts, http.MethodGet, defaultHeaders, "https://httpbin.corp.example", &sessions.MockSessionStore{LoadError: http.ErrNoCookie}, clients.MockAuthenticate{ValidateResponse: true}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -410,3 +344,152 @@ func TestProxy_Proxy(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy_UserDashboard(t *testing.T) {
|
||||
opts := testOptions()
|
||||
tests := []struct {
|
||||
name string
|
||||
options *config.Options
|
||||
method string
|
||||
cipher cryptutil.Cipher
|
||||
session sessions.SessionStore
|
||||
authenticator clients.Authenticator
|
||||
authorizer clients.Authorizer
|
||||
|
||||
wantAdminForm bool
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, false, http.StatusOK},
|
||||
{"cannot load session", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{LoadError: errors.New("load error")}, clients.MockAuthenticate{}, clients.MockAuthorize{}, false, http.StatusBadRequest},
|
||||
{"auth failure, validation error", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", RefreshDeadline: time.Now().Add(10 * time.Second)}}, clients.MockAuthenticate{ValidateError: errors.New("not valid anymore"), ValidateResponse: false}, clients.MockAuthorize{}, false, http.StatusUnauthorized},
|
||||
{"can't save csrf", opts, http.MethodGet, &cryptutil.MockCipher{MarshalError: errors.New("err")}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, false, http.StatusInternalServerError},
|
||||
{"want admin form good admin authorization", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK},
|
||||
{"is admin but authorization fails", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p, err := New(tt.options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.cipher = tt.cipher
|
||||
p.sessionStore = tt.session
|
||||
p.AuthenticateClient = tt.authenticator
|
||||
p.AuthorizeClient = tt.authorizer
|
||||
|
||||
r := httptest.NewRequest(tt.method, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
p.UserDashboard(w, r)
|
||||
if status := w.Code; status != tt.wantStatus {
|
||||
t.Errorf("status code: got %v want %v", status, tt.wantStatus)
|
||||
t.Errorf("\n%+v", opts)
|
||||
}
|
||||
if adminForm := strings.Contains(w.Body.String(), "impersonate"); adminForm != tt.wantAdminForm {
|
||||
t.Errorf("wanted admin form got %v want %v", adminForm, tt.wantAdminForm)
|
||||
t.Errorf("\n%+v", w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy_Refresh(t *testing.T) {
|
||||
opts := testOptions()
|
||||
opts.RefreshCooldown = 0
|
||||
timeSinceError := testOptions()
|
||||
timeSinceError.RefreshCooldown = time.Duration(int(^uint(0) >> 1))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
options *config.Options
|
||||
method string
|
||||
cipher cryptutil.Cipher
|
||||
session sessions.SessionStore
|
||||
authenticator clients.Authenticator
|
||||
authorizer clients.Authorizer
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusFound},
|
||||
{"cannot load session", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{LoadError: errors.New("load error")}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusBadRequest},
|
||||
{"bad id token", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "bad"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusInternalServerError},
|
||||
{"issue date too soon", timeSinceError, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusBadRequest},
|
||||
{"refresh failure", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{RefreshError: errors.New("err")}, clients.MockAuthorize{}, http.StatusInternalServerError},
|
||||
{"can't save refreshed session", opts, http.MethodGet, &cryptutil.MockCipher{}, &sessions.MockSessionStore{SaveError: errors.New("err"), Session: &sessions.SessionState{Email: "user@test.example", IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3YTA4MjgzOWYyZTcxYTliZjZjNTk2OTk2Yjk0NzM5Nzg1YWZkYzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI4NTE4NzcwODIwNTktYmZna3BqMDlub29nN2FzM2dwYzN0N3I2bjlzamJnczYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE0MzI2NTU5NzcyNzMxNTAzMDgiLCJoZCI6InBvbWVyaXVtLmlvIiwiZW1haWwiOiJiZGRAcG9tZXJpdW0uaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlppQ1g0WndDYl9tcUVxM2xnbmFZRHciLCJuYW1lIjoiQm9iYnkgRGVTaW1vbmUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1PX1BzRTlILTgzRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQ0hpM3JjQ0U0SFRLVDBhQk1pUFVfOEZfVXFOQ3F6RTBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJCb2JieSIsImZhbWlseV9uYW1lIjoiRGVTaW1vbmUiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTU1ODY1NDEzNywiZXhwIjoxNTU4NjU3NzM3fQ.Flah31XfqmPhWYh2rJ-6rtowmSQFgp6HqDf1rpS38Wo0DXnIYmXxEQVLanDNV62Z0sLhUk1QO9NqoSgA3NscM-Ww-JsqU80oKnWcMYweUb_KU0kfHyTiUB0iEHMqu6tXn5dA_dIaPnL5oorXZ_gG4sooRxBZrDkaNAjRINLciKDQkUTVaNfnM6IBZ_pWDPd2lWGtj8h8sEIe2PIiH73Z2VLlXz8kw60VTPsi9U2zrF0ZJ9MfRGJhceQ58vW2ZlFfXJixgvbOZjKmcRv8NaJDIUss48l0Bsya6icZ0l1ZK-sAiFr0KVLTl2ywu8d5SQpTJ1X7vDW_u_04xaqDQUdYKA"}}, clients.MockAuthenticate{}, clients.MockAuthorize{}, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p, err := New(tt.options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.cipher = tt.cipher
|
||||
p.sessionStore = tt.session
|
||||
p.AuthenticateClient = tt.authenticator
|
||||
p.AuthorizeClient = tt.authorizer
|
||||
|
||||
r := httptest.NewRequest(tt.method, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
p.Refresh(w, r)
|
||||
if status := w.Code; status != tt.wantStatus {
|
||||
t.Errorf("status code: got %v want %v", status, tt.wantStatus)
|
||||
// t.Errorf("\n%+v", w.Body.String())
|
||||
t.Errorf("\n%+v", opts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy_Impersonate(t *testing.T) {
|
||||
opts := testOptions()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
options *config.Options
|
||||
method string
|
||||
email string
|
||||
groups string
|
||||
csrf string
|
||||
cipher cryptutil.Cipher
|
||||
sessionStore sessions.SessionStore
|
||||
csrfStore sessions.CSRFStore
|
||||
authenticator clients.Authenticator
|
||||
authorizer clients.Authorizer
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"session load error", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{LoadError: errors.New("err"), Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"non admin users rejected", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden},
|
||||
{"non admin users rejected on error", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusForbidden},
|
||||
{"csrf from store retrieve failure", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}, GetError: errors.New("err")}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusBadRequest},
|
||||
{"can't decrypt csrf value", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{UnmarshalError: errors.New("err")}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"decrypted csrf mismatch", opts, http.MethodPost, "user@blah.com", "", "CSRF!", &cryptutil.MockCipher{}, &sessions.MockSessionStore{Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusForbidden},
|
||||
{"save session failure", opts, http.MethodPost, "user@blah.com", "", "", &cryptutil.MockCipher{}, &sessions.MockSessionStore{SaveError: errors.New("err"), Session: &sessions.SessionState{Email: "user@test.example", IDToken: ""}}, &sessions.MockCSRFStore{Cookie: &http.Cookie{Value: "csrf"}}, clients.MockAuthenticate{}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p, err := New(tt.options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.cipher = tt.cipher
|
||||
p.sessionStore = tt.sessionStore
|
||||
p.csrfStore = tt.csrfStore
|
||||
p.AuthenticateClient = tt.authenticator
|
||||
p.AuthorizeClient = tt.authorizer
|
||||
postForm := url.Values{}
|
||||
postForm.Add("email", tt.email)
|
||||
postForm.Add("group", tt.groups)
|
||||
postForm.Set("csrf", tt.csrf)
|
||||
|
||||
r := httptest.NewRequest(tt.method, "/", bytes.NewBufferString(postForm.Encode()))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||||
w := httptest.NewRecorder()
|
||||
p.Impersonate(w, r)
|
||||
if status := w.Code; status != tt.wantStatus {
|
||||
t.Errorf("status code: got %v want %v", status, tt.wantStatus)
|
||||
// t.Errorf("\n%+v", w.Body.String())
|
||||
t.Errorf("\n%+v", opts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
|
||||
|
@ -95,6 +96,7 @@ type Proxy struct {
|
|||
redirectURL *url.URL
|
||||
templates *template.Template
|
||||
routeConfigs map[string]*routeConfig
|
||||
refreshCooldown time.Duration
|
||||
}
|
||||
|
||||
type routeConfig struct {
|
||||
|
@ -143,6 +145,7 @@ func New(opts *config.Options) (*Proxy, error) {
|
|||
SharedKey: opts.SharedKey,
|
||||
redirectURL: &url.URL{Path: "/.pomerium/callback"},
|
||||
templates: templates.New(),
|
||||
refreshCooldown: opts.RefreshCooldown,
|
||||
}
|
||||
|
||||
for _, route := range opts.Policies {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package proxy // import "github.com/pomerium/pomerium/proxy"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -10,6 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/config"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/policy"
|
||||
)
|
||||
|
@ -237,3 +240,46 @@ func TestNew(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy_OAuthCallback(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
csrf sessions.MockCSRFStore
|
||||
session sessions.MockSessionStore
|
||||
authenticator clients.MockAuthenticate
|
||||
params map[string]string
|
||||
wantCode int
|
||||
}{
|
||||
{"good", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusFound},
|
||||
{"error", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"error": "some error"}, http.StatusForbidden},
|
||||
{"state err", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "error"}, http.StatusInternalServerError},
|
||||
{"csrf err", sessions.MockCSRFStore{GetError: errors.New("error")}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusBadRequest},
|
||||
{"unmarshal err", sessions.MockCSRFStore{Cookie: &http.Cookie{Name: "something_csrf", Value: "unmarshal error"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
proxy, err := New(testOptions())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
proxy.sessionStore = &tt.session
|
||||
proxy.csrfStore = tt.csrf
|
||||
proxy.AuthenticateClient = tt.authenticator
|
||||
proxy.cipher = mockCipher{}
|
||||
// proxy.Csrf
|
||||
req := httptest.NewRequest(http.MethodPost, "/.pomerium/callback", nil)
|
||||
q := req.URL.Query()
|
||||
for k, v := range tt.params {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
w := httptest.NewRecorder()
|
||||
proxy.OAuthCallback(w, req)
|
||||
if status := w.Code; status != tt.wantCode {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue