authorize: use opa for policy engine (#474)

Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
Bobby DeSimone 2020-02-02 11:18:22 -08:00 committed by GitHub
parent 111aa8f4d5
commit 2f13488598
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1022 additions and 872 deletions

View file

@ -74,7 +74,7 @@ func (a *Authenticate) Handler() http.Handler {
// session state is attached to the users's request context.
func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
state, err := sessions.FromContext(r.Context())
state, _, err := sessions.FromContext(r.Context())
if errors.Is(err, sessions.ErrExpired) {
ctx, err := a.refresh(w, r, state)
if err != nil {
@ -103,7 +103,7 @@ func (a *Authenticate) refresh(w http.ResponseWriter, r *http.Request, s *sessio
return nil, fmt.Errorf("authenticate: refresh save failed: %w", err)
}
// return the new session and add it to the current request context
return sessions.NewContext(ctx, newSession, err), nil
return sessions.NewContext(ctx, newSession, "", err), nil
}
// RobotsTxt handles the /robots.txt route.
@ -142,7 +142,7 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) error {
jwtAudience = append(jwtAudience, fwdAuth)
}
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
@ -197,7 +197,7 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) error {
// SignOut signs the user out and attempts to revoke the user's identity session
// Handles both GET and POST.
func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) error {
session, err := sessions.FromContext(r.Context())
session, _, err := sessions.FromContext(r.Context())
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
@ -318,7 +318,7 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
// tokens and state with the identity provider. If successful, a new signed JWT
// and refresh token (`refresh_token`) are returned as JSON
func (a *Authenticate) RefreshAPI(w http.ResponseWriter, r *http.Request) error {
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
if err != nil && !errors.Is(err, sessions.ErrExpired) {
return httputil.NewError(http.StatusBadRequest, err)
}
@ -359,7 +359,7 @@ func (a *Authenticate) RefreshAPI(w http.ResponseWriter, r *http.Request) error
// middleware. This handler is responsible for creating a new route scoped
// session and returning it.
func (a *Authenticate) Refresh(w http.ResponseWriter, r *http.Request) error {
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}

View file

@ -151,9 +151,9 @@ func TestAuthenticate_SignIn(t *testing.T) {
uri.RawQuery = queryString.Encode()
r := httptest.NewRequest(http.MethodGet, uri.String(), nil)
r.Header.Set("Accept", "application/json")
state, err := tt.session.LoadSession(r)
state, _, err := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, err)
ctx = sessions.NewContext(ctx, state, "", err)
r = r.WithContext(ctx)
w := httptest.NewRecorder()
@ -206,9 +206,9 @@ func TestAuthenticate_SignOut(t *testing.T) {
params.Add(urlutil.QueryRedirectURI, tt.redirectURL)
u.RawQuery = params.Encode()
r := httptest.NewRequest(tt.method, u.String(), nil)
state, _ := tt.sessionStore.LoadSession(r)
state, _, _ := tt.sessionStore.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
@ -349,9 +349,9 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) {
cookieCipher: aead,
}
r := httptest.NewRequest("GET", "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
@ -408,9 +408,9 @@ func TestAuthenticate_RefreshAPI(t *testing.T) {
cookieCipher: aead,
}
r := httptest.NewRequest("GET", "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
@ -459,9 +459,9 @@ func TestAuthenticate_Refresh(t *testing.T) {
cookieCipher: aead,
}
r := httptest.NewRequest("GET", "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")

View file

@ -3,69 +3,67 @@
package authorize // import "github.com/pomerium/pomerium/authorize"
import (
"encoding/base64"
"context"
"fmt"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/authorize/evaluator/opa"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/cryptutil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
"github.com/pomerium/pomerium/internal/telemetry/trace"
)
// ValidateOptions checks to see if configuration values are valid for the
// authorize service. Returns first error, if found.
func ValidateOptions(o config.Options) error {
decoded, err := base64.StdEncoding.DecodeString(o.SharedKey)
if err != nil {
return fmt.Errorf("authorize: `SHARED_SECRET` malformed base64: %w", err)
}
if len(decoded) != 32 {
return fmt.Errorf("authorize: `SHARED_SECRET` want 32 but got %d bytes", len(decoded))
}
return nil
}
// Authorize struct holds
type Authorize struct {
SharedKey string
identityAccess IdentityValidator
// contextValidator
// deviceValidator
pe evaluator.Evaluator
}
// New validates and creates a new Authorize service from a set of Options
// New validates and creates a new Authorize service from a set of config options.
func New(opts config.Options) (*Authorize, error) {
if err := ValidateOptions(opts); err != nil {
if err := validateOptions(opts); err != nil {
return nil, fmt.Errorf("authorize: bad options: %w", err)
}
var a Authorize
var err error
if a.pe, err = newPolicyEvaluator(&opts); err != nil {
return nil, err
}
// errors handled by validate
sharedKey, _ := base64.StdEncoding.DecodeString(opts.SharedKey)
return &Authorize{
SharedKey: string(sharedKey),
identityAccess: NewIdentityWhitelist(opts.Policies, opts.Administrators),
}, nil
return &a, nil
}
// NewIdentityWhitelist returns an indentity validator.
// todo(bdd) : a radix-tree implementation is probably more efficient
func NewIdentityWhitelist(policies []config.Policy, admins []string) IdentityValidator {
func validateOptions(o config.Options) error {
if _, err := cryptutil.NewAEADCipherFromBase64(o.SharedKey); err != nil {
return fmt.Errorf("bad shared_secret: %w", err)
}
return nil
}
// newPolicyEvaluator returns an policy evaluator.
func newPolicyEvaluator(opts *config.Options) (evaluator.Evaluator, error) {
metrics.AddPolicyCountCallback("authorize", func() int64 {
return int64(len(policies))
return int64(len(opts.Policies))
})
return newIdentityWhitelistMap(policies, admins)
ctx := context.Background()
ctx, span := trace.StartSpan(ctx, "authorize.newPolicyEvaluator")
defer span.End()
data := map[string]interface{}{
"shared_key": opts.SharedKey,
"route_policies": opts.Policies,
"admins": opts.Administrators,
}
return opa.New(ctx, &opa.Options{Data: data})
}
// 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)
// UpdateOptions implements the OptionsUpdater interface and updates internal
// structures based on config.Options
func (a *Authorize) UpdateOptions(opts config.Options) error {
log.Info().Str("checksum", opts.Checksum()).Msg("authorize: updating options")
var err error
if a.pe, err = newPolicyEvaluator(&opts); err != nil {
return err
}
// UpdateOptions updates internal structures based on config.Options
func (a *Authorize) UpdateOptions(o config.Options) error {
if a == nil {
return nil
}
log.Info().Msg("authorize: updating options")
a.identityAccess = NewIdentityWhitelist(o.Policies, o.Administrators)
return nil
}

View file

@ -3,14 +3,14 @@ package authorize
import (
"testing"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/authorize/evaluator/mock"
"github.com/pomerium/pomerium/config"
)
func TestNew(t *testing.T) {
t.Parallel()
policies := testPolicies(t)
tests := []struct {
name string
SharedKey string
@ -22,6 +22,7 @@ func TestNew(t *testing.T) {
{"really bad shared secret", "sup", policies, true},
{"validation error, short secret", "AZA85podM73CjLCjViDNz1EUvvejKpWp7Hysr0knXA==", policies, true},
{"empty options", "", []config.Policy{}, true}, // special case
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -39,7 +40,7 @@ func TestNew(t *testing.T) {
}
func testPolicies(t *testing.T) []config.Policy {
testPolicy := config.Policy{From: "https://pomerium.io", To: "http://httpbin.org", AllowedEmails: []string{"test@gmail.com"}}
testPolicy := config.Policy{From: "https://pomerium.io", To: "http://httpbin.org", AllowedUsers: []string{"test@gmail.com"}}
err := testPolicy.Validate()
if err != nil {
t.Fatal(err)
@ -47,55 +48,27 @@ func testPolicies(t *testing.T) []config.Policy {
policies := []config.Policy{
testPolicy,
}
return policies
}
func Test_UpdateOptions(t *testing.T) {
func TestAuthorize_UpdateOptions(t *testing.T) {
t.Parallel()
policies := testPolicies(t)
newPolicy := config.Policy{From: "https://source.example", To: "http://destination.example", AllowedEmails: []string{"test@gmail.com"}}
if err := newPolicy.Validate(); err != nil {
t.Fatal(err)
}
newPolicies := []config.Policy{
newPolicy,
}
identity := &Identity{Email: "test@gmail.com"}
tests := []struct {
name string
SharedKey string
Policies []config.Policy
newPolices []config.Policy
route string
wantAllowed bool
pe evaluator.Evaluator
opts config.Options
wantErr bool
}{
{"good", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, policies, "pomerium.io", true},
{"changed", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, newPolicies, "source.example", true},
{"changed and missing", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, newPolicies, "pomerium.io", false},
{"good", &mock.PolicyEvaluator{}, config.Options{}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := config.Options{SharedKey: tt.SharedKey, Policies: tt.Policies}
authorize, err := New(o)
if err != nil {
t.Fatal(err)
a := &Authorize{
pe: tt.pe,
}
o.Policies = tt.newPolices
if err := authorize.UpdateOptions(o); err != nil {
t.Fatal(err)
}
allowed := authorize.ValidIdentity(tt.route, identity)
if allowed != tt.wantAllowed {
t.Errorf("New() allowed = %v, wantAllowed %v", allowed, tt.wantAllowed)
return
if err := a.UpdateOptions(tt.opts); (err != nil) != tt.wantErr {
t.Errorf("Authorize.UpdateOptions() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
// Test nil
var a *Authorize
a.UpdateOptions(config.Options{})
}

View file

@ -0,0 +1,51 @@
// Package evaluator defines a Evaluator interfaces that can be implemented by
// a policy evaluator framework.
package evaluator
import (
"context"
)
// Evaluator specifies the interface for a policy engine.
type Evaluator interface {
IsAuthorized(ctx context.Context, input interface{}) (bool, error)
IsAdmin(ctx context.Context, input interface{}) (bool, error)
PutData(ctx context.Context, data map[string]interface{}) error
}
// A Request represents an evaluable request with an associated user, device,
// and request context.
type Request struct {
// User context
//
// User contains the associated user's JWT created by the authenticate
// service
User string `json:"user,omitempty"`
// Request context
//
// Method specifies the HTTP method (GET, POST, PUT, etc.).
Method string `json:"method,omitempty"`
// URL specifies either the URI being requested.
URL string `json:"url,omitempty"`
// The protocol version for incoming server requests.
Proto string `json:"proto,omitempty"` // "HTTP/1.0"
// Header contains the request header fields either received
// by the server or to be sent by the client.
Header map[string][]string `json:"headers,omitempty"`
// Host specifies the host on which the URL is sought.
Host string `json:"host,omitempty"`
// RemoteAddr is the network address that sent the request.
RemoteAddr string `json:"remote_addr,omitempty"`
// RequestURI is the unmodified request-target of the
// Request-Line (RFC 7230, Section 3.1.1) as sent by the client
// to a server. Usually the URL field should be used instead.
// It is an error to set this field in an HTTP client request.
RequestURI string `json:"request_uri,omitempty"`
// Device context
//
// todo(bdd):
// Use the peer TLS certificate as the basis for binding device
// identity with a request context !
}

View file

@ -0,0 +1,35 @@
// Package mock implements the policy evaluator interface to make authorization
// decisions.
package mock
import (
"context"
"github.com/pomerium/pomerium/authorize/evaluator"
)
var _ evaluator.Evaluator = &PolicyEvaluator{}
// PolicyEvaluator is the mock implementation of Evaluator
type PolicyEvaluator struct {
IsAuthorizedResponse bool
IsAuthorizedErr error
IsAdminResponse bool
IsAdminErr error
PutDataErr error
}
// IsAuthorized is the mock implementation of IsAuthorized
func (pe PolicyEvaluator) IsAuthorized(ctx context.Context, input interface{}) (bool, error) {
return pe.IsAuthorizedResponse, pe.IsAuthorizedErr
}
// IsAdmin is the mock implementation of IsAdmin
func (pe PolicyEvaluator) IsAdmin(ctx context.Context, input interface{}) (bool, error) {
return pe.IsAdminResponse, pe.IsAdminErr
}
// PutData is the mock implementation of PutData
func (pe PolicyEvaluator) PutData(ctx context.Context, data map[string]interface{}) error {
return pe.PutDataErr
}

View file

@ -0,0 +1,155 @@
// Package opa implements the policy evaluator interface to make authorization
// decisions.
package opa
import (
"context"
"errors"
"fmt"
"sync"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/internal/telemetry/trace"
)
var _ evaluator.Evaluator = &PolicyEvaluator{}
// PolicyEvaluator implements the evaluator interface using the open policy
// agent framework. The Open Policy Agent (OPA, pronounced “oh-pa”) is an open
// source, general-purpose policy engine that unifies policy enforcement across
// the stack.
// https://www.openpolicyagent.org/docs/latest/
type PolicyEvaluator struct {
// The in-memory store supports multi-reader/single-writer concurrency with
// rollback so we leverage a RWMutex.
mu sync.RWMutex
store storage.Store
isAuthorized rego.PreparedEvalQuery
isAdmin rego.PreparedEvalQuery
}
// Options represent OPA's evaluator configurations.
type Options struct {
// AuthorizationPolicy accepts custom rego code which can be used to
// apply custom authorization policy.
// Defaults to authorization policy defined in config.yaml's policy.
AuthorizationPolicy string
// PAMPolicy accepts custom rego code which can be used to
// apply custom privileged access management policy.
// Defaults to users whose emails match those defined in config.yaml.
PAMPolicy string
// Data maps data that will be bound and
Data map[string]interface{}
}
// New creates a new OPA policy evaluator.
func New(ctx context.Context, opts *Options) (*PolicyEvaluator, error) {
var pe PolicyEvaluator
pe.store = inmem.New()
if opts.Data == nil {
return nil, errors.New("opa: cannot create new evaluator without data")
}
if opts.AuthorizationPolicy == "" {
opts.AuthorizationPolicy = defaultAuthorization
}
if opts.PAMPolicy == "" {
opts.PAMPolicy = defaultPAM
}
if err := pe.PutData(ctx, opts.Data); err != nil {
return nil, err
}
if err := pe.UpdatePolicy(ctx, opts.AuthorizationPolicy, opts.PAMPolicy); err != nil {
return nil, err
}
return &pe, nil
}
// UpdatePolicy takes authorization and privilege access management rego code
// as an input and updates the prepared policy evaluator.
func (pe *PolicyEvaluator) UpdatePolicy(ctx context.Context, authz, pam string) error {
ctx, span := trace.StartSpan(ctx, "authorize.evaluator.opa.UpdatePolicy")
defer span.End()
var err error
pe.mu.Lock()
defer pe.mu.Unlock()
r := rego.New(
rego.Store(pe.store),
rego.Module("pomerium.authz", authz),
// rego.Query("data.pomerium.authz"),
rego.Query("result = data.pomerium.authz.allow"),
)
pe.isAuthorized, err = r.PrepareForEval(ctx)
if err != nil {
return fmt.Errorf("opa: prepare policy: %w", err)
}
r = rego.New(
rego.Store(pe.store),
rego.Module("pomerium.pam", pam),
rego.Query("result = data.pomerium.pam.is_admin"),
)
pe.isAdmin, err = r.PrepareForEval(ctx)
if err != nil {
return fmt.Errorf("opa: prepare policy: %w", err)
}
return nil
}
// IsAuthorized determines if a given request input is authorized.
func (pe *PolicyEvaluator) IsAuthorized(ctx context.Context, input interface{}) (bool, error) {
ctx, span := trace.StartSpan(ctx, "authorize.evaluator.opa.PutData")
defer span.End()
return pe.runBoolQuery(ctx, input, pe.isAuthorized)
}
// IsAdmin determines if a given input user has super user privleges.
func (pe *PolicyEvaluator) IsAdmin(ctx context.Context, input interface{}) (bool, error) {
ctx, span := trace.StartSpan(ctx, "authorize.evaluator.opa.IsAdmin")
defer span.End()
return pe.runBoolQuery(ctx, input, pe.isAdmin)
}
// PutData adds (or replaces if the mapping key is the same) contextual data
// for making policy decisions.
func (pe *PolicyEvaluator) PutData(ctx context.Context, data map[string]interface{}) error {
ctx, span := trace.StartSpan(ctx, "authorize.evaluator.opa.PutData")
defer span.End()
pe.mu.Lock()
defer pe.mu.Unlock()
txn, err := pe.store.NewTransaction(ctx, storage.WriteParams)
if err != nil {
return fmt.Errorf("opa: bad transaction: %w", err)
}
if err := pe.store.Write(ctx, txn, storage.ReplaceOp, storage.Path{}, data); err != nil {
pe.store.Abort(ctx, txn)
return fmt.Errorf("opa: write failed %v : %w", data, err)
}
if err := pe.store.Commit(ctx, txn); err != nil {
return fmt.Errorf("opa: commit failed: %w", err)
}
return nil
}
func (pe *PolicyEvaluator) runBoolQuery(ctx context.Context, input interface{}, q rego.PreparedEvalQuery) (bool, error) {
pe.mu.RLock()
defer pe.mu.RUnlock()
rs, err := q.Eval(
ctx,
rego.EvalInput(input),
)
if err != nil {
return false, fmt.Errorf("opa: eval query: %w", err)
} else if len(rs) != 1 {
return false, fmt.Errorf("opa: eval result set: %v, expected len 1", rs)
} else if result, ok := rs[0].Bindings["result"].(bool); !ok {
return false, fmt.Errorf("opa: expected bool, got: %v", rs)
} else {
return result, nil
}
}

View file

@ -1,68 +1,60 @@
package authorize
package opa
import (
"context"
"testing"
"time"
"github.com/pomerium/pomerium/config"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
func TestIdentity_EmailDomain(t *testing.T) {
func Test_Eval(t *testing.T) {
t.Parallel()
tests := []struct {
name string
Email string
want string
}{
{"simple", "user@pomerium.io", "pomerium.io"},
{"period malformed", "user@.io", ".io"},
{"empty", "", ""},
{"empty first part", "@uhoh.com", ""},
{"empty second part", "uhoh@", ""},
type Identity struct {
User string `json:"user,omitempty"`
Email string `json:"email,omitempty"`
Groups []string `json:"groups,omitempty"`
ImpersonateEmail string `json:"impersonate_email,omitempty"`
ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := EmailDomain(tt.Email); got != tt.want {
t.Errorf("Identity.EmailDomain() = %v, want %v", got, tt.want)
}
})
}
}
func Test_IdentityWhitelistMap(t *testing.T) {
t.Parallel()
tests := []struct {
name string
policies []config.Policy
route string
Identity *Identity
admins []string
secret string
want bool
}{
{"valid domain", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, nil, true},
{"valid domain with admins", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, []string{"admin@example.com"}, true},
{"invalid domain prepend", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "a@1example.com"}, nil, false},
{"invalid domain postpend", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com2"}, nil, false},
{"valid group", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"admin"}}, nil, true},
{"invalid group", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"everyone"}}, nil, false},
{"invalid empty", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{""}}, nil, false},
{"valid group multiple", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"everyone", "admin"}}, nil, true},
{"invalid group multiple", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"everyones", "sadmin"}}, nil, false},
{"valid user email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedEmails: []string{"user@example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, nil, true},
{"invalid user email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedEmails: []string{"user@example.com"}}}, "from.example", &Identity{Email: "user2@example.com"}, nil, false},
{"empty everything", []config.Policy{{From: "https://from.example", To: "https://to.example"}}, "from.example", &Identity{Email: "user2@example.com"}, nil, false},
{"empty policy", []config.Policy{}, "from.example", &Identity{Email: "user2@example.com"}, nil, false},
{"valid domain", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, nil, "secret", true},
{"valid domain with admins", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, []string{"admin@example.com"}, "secret", true},
{"invalid domain prepend", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "a@1example.com"}, nil, "secret", false},
{"invalid domain postpend", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com2"}, nil, "secret", false},
{"valid group", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"admin"}}, nil, "secret", true},
{"invalid group", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"everyone"}}, nil, "secret", false},
{"invalid empty", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{""}}, nil, "secret", false},
{"valid group multiple", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"everyone", "admin"}}, nil, "secret", true},
{"invalid group multiple", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"admin"}}}, "from.example", &Identity{Email: "user@example.com", Groups: []string{"everyones", "sadmin"}}, nil, "secret", false},
{"valid user email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedUsers: []string{"user@example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, nil, "secret", true},
{"invalid user email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedUsers: []string{"user@example.com"}}}, "from.example", &Identity{Email: "user2@example.com"}, nil, "secret", false},
{"empty everything", []config.Policy{{From: "https://from.example", To: "https://to.example"}}, "from.example", &Identity{Email: "user2@example.com"}, nil, "secret", false},
{"empty policy", []config.Policy{}, "from.example", &Identity{Email: "user2@example.com"}, nil, "secret", false},
// impersonation related
{"admin not impersonating allowed", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@example.com"}, []string{"admin@example.com"}, true},
{"admin not impersonating denied", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com"}, []string{"admin@admin-domain.com"}, false},
{"impersonating match domain", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@example.com"}, []string{"admin@admin-domain.com"}, true},
{"impersonating does not match domain", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@not-example.com"}, []string{"admin@admin-domain.com"}, false},
{"impersonating match email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedEmails: []string{"user@example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@example.com"}, []string{"admin@admin-domain.com"}, true},
{"impersonating does not match email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedEmails: []string{"user@example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@not-example.com"}, []string{"admin@admin-domain.com"}, false},
{"impersonating match groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"support"}}, []string{"admin@admin-domain.com"}, true},
{"impersonating match many groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"a", "b", "c", "support"}}, []string{"admin@admin-domain.com"}, true},
{"impersonating does not match groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"not support"}}, []string{"admin@admin-domain.com"}, false},
{"impersonating does not match many groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"not support", "b", "c"}}, []string{"admin@admin-domain.com"}, false},
{"impersonating does not match empty groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{""}}, []string{"admin@admin-domain.com"}, false},
{"admin not impersonating allowed", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@example.com"}, []string{"admin@example.com"}, "secret", true},
{"admin not impersonating denied", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com"}, []string{"admin@admin-domain.com"}, "secret", false},
{"impersonating match domain", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@example.com"}, []string{"admin@admin-domain.com"}, "secret", true},
{"impersonating does not match domain", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@not-example.com"}, []string{"admin@admin-domain.com"}, "secret", false},
{"impersonating match email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedUsers: []string{"user@example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@example.com"}, []string{"admin@admin-domain.com"}, "secret", true},
{"impersonating does not match email", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedUsers: []string{"user@example.com"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateEmail: "user@not-example.com"}, []string{"admin@admin-domain.com"}, "secret", false},
{"impersonating match groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"support"}}, []string{"admin@admin-domain.com"}, "secret", true},
{"impersonating match many groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"a", "b", "c", "support"}}, []string{"admin@admin-domain.com"}, "secret", true},
{"impersonating does not match groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"not support"}}, []string{"admin@admin-domain.com"}, "secret", false},
{"impersonating does not match many groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{"not support", "b", "c"}}, []string{"admin@admin-domain.com"}, "secret", false},
{"impersonating does not match empty groups", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedGroups: []string{"support"}}}, "from.example", &Identity{Email: "admin@admin-domain.com", ImpersonateGroups: []string{""}}, []string{"admin@admin-domain.com"}, "secret", false},
// jwt validation
{"bad jwt shared secret", []config.Policy{{From: "https://from.example", To: "https://to.example", AllowedDomains: []string{"example.com"}}}, "from.example", &Identity{Email: "user@example.com"}, nil, "bad-secret", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -71,12 +63,46 @@ func Test_IdentityWhitelistMap(t *testing.T) {
t.Fatal(err)
}
}
wl := NewIdentityWhitelist(tt.policies, tt.admins)
if got := wl.Valid(tt.route, tt.Identity); got != tt.want {
t.Errorf("wl.Valid() = %v, want %v", got, tt.want)
key := []byte("secret")
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key},
(&jose.SignerOptions{}).WithType("JWT"))
if err != nil {
t.Fatal(err)
}
cl := jwt.Claims{
NotBefore: jwt.NewNumericDate(time.Now()),
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
Audience: jwt.Audience{tt.route},
}
rawJWT, err := jwt.Signed(sig).Claims(cl).Claims(tt.Identity).CompactSerialize()
if err != nil {
t.Fatal(err)
}
data := map[string]interface{}{
"route_policies": tt.policies,
"admins": tt.admins,
"shared_key": tt.secret,
}
pe, err := New(context.Background(), &Options{Data: data})
if err != nil {
t.Fatal(err)
}
req := struct {
Host string `json:"host,omitempty"`
User string `json:"user,omitempty"`
}{
Host: tt.route,
User: rawJWT,
}
got, err := pe.IsAuthorized(context.TODO(), req)
if err != nil {
t.Fatal(err)
}
if got != tt.want {
t.Errorf("pe.Eval() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,87 @@
package opa
//todo(bdd): embed source files directly, and setup tests.
const defaultAuthorization = `
package pomerium.authz
import data.route_policies
import data.shared_key
default allow = false
# allow by email
allow {
some route
input.host = route_policies[route].source
jwt.payload.email = route_policies[route].allowed_users[_]
jwt.valid
}
# allow group
allow {
some route
input.host = route_policies[route].source
jwt.payload.groups[_] = route_policies[route].allowed_groups[_]
jwt.valid
}
# allow by impersonate email
allow {
some route
input.host = route_policies[route].source
jwt.payload.impersonate_email = route_policies[route].allowed_users[_]
jwt.valid
}
# allow by impersonate group
allow {
some route
input.host = route_policies[route].source
jwt.payload.impersonate_groups[_] = route_policies[route].allowed_groups[_]
jwt.valid
}
# allow by domain
allow {
some route
input.host = route_policies[route].source
x := split(jwt.payload.email, "@")
count(x)=2
x[1] = route_policies[route].allowed_domains[_]
jwt.valid
}
# allow by impersonate domain
allow {
some route
input.host = route_policies[route].source
x := split(jwt.payload.impersonate_email, "@")
count(x)=2
x[1] == route_policies[route].allowed_domains[_]
jwt.valid
}
jwt = {"payload": payload, "valid": valid} {
[valid, header, payload] := io.jwt.decode_verify(
input.user, {
"secret": shared_key,
"aud": input.host,
}
)
}
`
const defaultPAM = `
package pomerium.pam
import data.admins
import data.shared_key
default is_admin = false
is_admin{
io.jwt.verify_hs256(input.user,shared_key)
jwt.payload.email = admins[_]
}
jwt = {"payload": payload} {[header, payload, signature] := io.jwt.decode(input.user)}
`

View file

@ -4,35 +4,54 @@ package authorize // import "github.com/pomerium/pomerium/authorize"
import (
"context"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/internal/grpc/authorize"
"github.com/pomerium/pomerium/internal/telemetry/trace"
)
// 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 *authorize.Identity) (*authorize.AuthorizeReply, error) {
// IsAuthorized checks to see if a given user is authorized to make a request.
func (a *Authorize) IsAuthorized(ctx context.Context, in *authorize.IsAuthorizedRequest) (*authorize.IsAuthorizedReply, error) {
_, span := trace.StartSpan(ctx, "authorize.grpc.Authorize")
defer span.End()
ok := a.ValidIdentity(in.Route,
&Identity{
User: in.User,
Email: in.Email,
Groups: in.Groups,
ImpersonateEmail: in.ImpersonateEmail,
ImpersonateGroups: in.ImpersonateGroups,
})
return &authorize.AuthorizeReply{IsValid: ok}, nil
req := &evaluator.Request{
User: in.GetUserToken(),
Header: cloneHeaders(in.GetRequestHeaders()),
Host: in.GetRequestHost(),
Method: in.GetRequestMethod(),
RequestURI: in.GetRequestRequestUri(),
RemoteAddr: in.GetRequestRemoteAddr(),
URL: in.GetRequestUrl(),
}
ok, err := a.pe.IsAuthorized(ctx, req)
if err != nil {
return nil, err
}
return &authorize.IsAuthorizedReply{IsValid: ok}, nil
}
// IsAdmin validates the user is an administrative user.
func (a *Authorize) IsAdmin(ctx context.Context, in *authorize.Identity) (*authorize.IsAdminReply, error) {
// IsAdmin checks to see if a given user has super user privleges.
func (a *Authorize) IsAdmin(ctx context.Context, in *authorize.IsAdminRequest) (*authorize.IsAdminReply, error) {
_, span := trace.StartSpan(ctx, "authorize.grpc.IsAdmin")
defer span.End()
ok := a.identityAccess.IsAdmin(
&Identity{
Email: in.Email,
Groups: in.Groups,
})
return &authorize.IsAdminReply{IsAdmin: ok}, nil
req := &evaluator.Request{
User: in.GetUserToken(),
}
ok, err := a.pe.IsAdmin(ctx, req)
if err != nil {
return nil, err
}
return &authorize.IsAdminReply{IsValid: ok}, nil
}
type protoHeader map[string]*authorize.IsAuthorizedRequest_Headers
func cloneHeaders(in protoHeader) map[string][]string {
out := make(map[string][]string, len(in))
for key, values := range in {
newValues := make([]string, len(values.Value))
copy(newValues, values.Value)
out[key] = newValues
}
return out
}

View file

@ -1,36 +1,43 @@
//go:generate protoc -I ../internal/grpc/authorize/ --go_out=plugins=grpc:../internal/grpc/authorize/ ../internal/grpc/authorize/authorize.proto
package authorize
import (
"context"
"errors"
"reflect"
"testing"
pb "github.com/pomerium/pomerium/internal/grpc/authorize"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/authorize/evaluator/mock"
"github.com/pomerium/pomerium/internal/grpc/authorize"
)
func TestAuthorize_Authorize(t *testing.T) {
func TestAuthorize_IsAuthorized(t *testing.T) {
t.Parallel()
tests := []struct {
name string
SharedKey string
identityAccess IdentityValidator
in *pb.Identity
want *pb.AuthorizeReply
pe evaluator.Evaluator
in *authorize.IsAuthorizedRequest
want *authorize.IsAuthorizedReply
wantErr bool
}{
{"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},
{"want false", &mock.PolicyEvaluator{}, &authorize.IsAuthorizedRequest{}, &authorize.IsAuthorizedReply{IsValid: false}, false},
{"want true", &mock.PolicyEvaluator{IsAuthorizedResponse: true}, &authorize.IsAuthorizedRequest{}, &authorize.IsAuthorizedReply{IsValid: true}, false},
{"want err", &mock.PolicyEvaluator{IsAuthorizedErr: errors.New("err")}, &authorize.IsAuthorizedRequest{}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Authorize{SharedKey: tt.SharedKey, identityAccess: tt.identityAccess}
got, err := a.Authorize(context.Background(), tt.in)
a := &Authorize{
pe: tt.pe,
}
got, err := a.IsAuthorized(context.TODO(), tt.in)
if (err != nil) != tt.wantErr {
t.Errorf("Authorize.Authorize() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("Authorize.IsAuthorized() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Authorize.Authorize() = %v, want %v", got, tt.want)
t.Errorf("Authorize.IsAuthorized() = %v, want %v", got, tt.want)
}
})
}
@ -40,21 +47,21 @@ func TestAuthorize_IsAdmin(t *testing.T) {
t.Parallel()
tests := []struct {
name string
identityAccess IdentityValidator
in *pb.Identity
want *pb.IsAdminReply
pe evaluator.Evaluator
in *authorize.IsAdminRequest
want *authorize.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},
{"want false", &mock.PolicyEvaluator{}, &authorize.IsAdminRequest{}, &authorize.IsAdminReply{IsValid: false}, false},
{"want true", &mock.PolicyEvaluator{IsAdminResponse: true}, &authorize.IsAdminRequest{}, &authorize.IsAdminReply{IsValid: true}, false},
{"want err", &mock.PolicyEvaluator{IsAdminErr: errors.New("err")}, &authorize.IsAdminRequest{}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Authorize{
SharedKey: "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y",
identityAccess: tt.identityAccess,
pe: tt.pe,
}
got, err := a.IsAdmin(context.Background(), tt.in)
got, err := a.IsAdmin(context.TODO(), tt.in)
if (err != nil) != tt.wantErr {
t.Errorf("Authorize.IsAdmin() error = %v, wantErr %v", err, tt.wantErr)
return

View file

@ -1,187 +0,0 @@
package authorize // import "github.com/pomerium/pomerium/authorize"
import (
"fmt"
"strings"
"sync"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
)
// Identity contains a user's identity information.
type Identity struct {
User string
Email string
Groups []string
// Impersonation
ImpersonateEmail string
ImpersonateGroups []string
}
// 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(email, "@")
if len(comp) != 2 || comp[0] == "" {
return ""
}
return comp[1]
}
// IdentityValidator provides an interface to check whether a user has access
// to a given route.
type IdentityValidator interface {
Valid(string, *Identity) bool
IsAdmin(*Identity) bool
}
type whitelist struct {
sync.RWMutex
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 []config.Policy, admins []string) *whitelist {
if len(policies) == 0 {
log.Warn().Msg("authorize: loaded configuration with no policies")
}
var wl whitelist
wl.access = make(map[string]bool, len(policies)*3)
for _, p := range policies {
for _, group := range p.AllowedGroups {
wl.PutGroup(p.Source.Host, group)
log.Debug().Str("route", p.Source.Host).Str("group", group).Msg("add group")
}
for _, domain := range p.AllowedDomains {
wl.PutDomain(p.Source.Host, domain)
log.Debug().Str("route", p.Source.Host).Str("domain", domain).Msg("add domain")
}
for _, email := range p.AllowedEmails {
wl.PutEmail(p.Source.Host, email)
log.Debug().Str("route", p.Source.Host).Str("email", email).Msg("add email")
}
}
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 (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 := wl.Domain(route, domain); ok {
return 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 (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 (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 (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 (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 (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 (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
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 }

View file

@ -151,7 +151,7 @@ func Test_parsePolicyFile(t *testing.T) {
want []Policy
wantErr bool
}{
{"simple json", []byte(fmt.Sprintf(`{"policy":[{"from": "%s","to":"%s"}]}`, source, dest)), []Policy{{From: source, To: dest, Source: sourceURL, Destination: destURL}}, false},
{"simple json", []byte(fmt.Sprintf(`{"policy":[{"from": "%s","to":"%s"}]}`, source, dest)), []Policy{{From: source, To: dest, Source: &HostnameURL{sourceURL}, Destination: destURL}}, false},
{"bad from", []byte(`{"policy":[{"from": "%","to":"httpbin.org"}]}`), nil, true},
{"bad to", []byte(`{"policy":[{"from": "pomerium.io","to":"%"}]}`), nil, true},
}

View file

@ -3,6 +3,7 @@ package config // import "github.com/pomerium/pomerium/config"
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net/url"
"time"
@ -16,12 +17,12 @@ type Policy struct {
From string `mapstructure:"from" yaml:"from"`
To string `mapstructure:"to" yaml:"to"`
// Identity related policy
AllowedEmails []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty"`
AllowedGroups []string `mapstructure:"allowed_groups" yaml:"allowed_groups,omitempty"`
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty"`
AllowedUsers []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty" json:"allowed_users,omitempty"`
AllowedGroups []string `mapstructure:"allowed_groups" yaml:"allowed_groups,omitempty" json:"allowed_groups,omitempty"`
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
Source *url.URL `yaml:",omitempty"`
Destination *url.URL `yaml:",omitempty"`
Source *HostnameURL `yaml:",omitempty" json:"source,omitempty"`
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty"`
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
@ -80,10 +81,11 @@ type Policy struct {
// Validate checks the validity of a policy.
func (p *Policy) Validate() error {
var err error
p.Source, err = urlutil.ParseAndValidateURL(p.From)
source, err := urlutil.ParseAndValidateURL(p.From)
if err != nil {
return fmt.Errorf("config: policy bad source url %w", err)
}
p.Source = &HostnameURL{source}
p.Destination, err = urlutil.ParseAndValidateURL(p.To)
if err != nil {
@ -91,7 +93,7 @@ func (p *Policy) Validate() error {
}
// Only allow public access if no other whitelists are in place
if p.AllowPublicUnauthenticatedAccess && (p.AllowedDomains != nil || p.AllowedGroups != nil || p.AllowedEmails != nil) {
if p.AllowPublicUnauthenticatedAccess && (p.AllowedDomains != nil || p.AllowedGroups != nil || p.AllowedUsers != nil) {
return fmt.Errorf("config: policy route marked as public but contains whitelists")
}
@ -123,12 +125,23 @@ func (p *Policy) Validate() error {
return fmt.Errorf("config: couldn't load custom ca file %w", err)
}
}
return nil
}
func (p *Policy) String() string {
if p.Source == nil || p.Destination == nil {
return fmt.Sprintf("%s → %s", p.From, p.To)
}
return fmt.Sprintf("%s → %s", p.Source.String(), p.Destination.String())
}
// HostnameURL wraps url but marshals only the host representation of that
// url struct.
type HostnameURL struct {
*url.URL
}
// MarshalJSON returns the URLs host as json.
func (j *HostnameURL) MarshalJSON() ([]byte, error) {
return json.Marshal(j.Host)
}

View file

@ -1,7 +1,10 @@
package config
import (
"encoding/json"
"testing"
"github.com/google/go-cmp/cmp"
)
func Test_PolicyValidate(t *testing.T) {
@ -19,8 +22,8 @@ func Test_PolicyValidate(t *testing.T) {
{"empty to scheme", Policy{From: "https://httpbin.corp.example", To: "//httpbin.corp.example"}, true},
{"cors policy", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", CORSAllowPreflight: true}, false},
{"public policy", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", AllowPublicUnauthenticatedAccess: true}, false},
{"public and whitelist", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", AllowPublicUnauthenticatedAccess: true, AllowedEmails: []string{"test@domain.example"}}, true},
{"route must have", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", AllowPublicUnauthenticatedAccess: true, AllowedEmails: []string{"test@domain.example"}}, true},
{"public and whitelist", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", AllowPublicUnauthenticatedAccess: true, AllowedUsers: []string{"test@domain.example"}}, true},
{"route must have", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", AllowPublicUnauthenticatedAccess: true, AllowedUsers: []string{"test@domain.example"}}, true},
{"good client cert", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", TLSClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", TLSClientCert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, false},
{"bad base64 client cert", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", TLSClientKey: "!=", TLSClientCert: "!="}, true},
{"bad one client cert empty", Policy{From: "https://httpbin.corp.example", To: "https://httpbin.corp.notatld", TLSClientKey: "", TLSClientCert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, true},
@ -52,9 +55,10 @@ func TestPolicy_String(t *testing.T) {
From string
To string
want string
wantFrom string
}{
{"good", "https://pomerium.io", "https://localhost", "https://pomerium.io → https://localhost"},
{"failed to validate", "https://pomerium.io", "localhost", "https://pomerium.io → localhost"},
{"good", "https://pomerium.io", "https://localhost", "https://pomerium.io → https://localhost", `"pomerium.io"`},
{"failed to validate", "https://pomerium.io", "localhost", "https://pomerium.io → localhost", `"pomerium.io"`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -66,6 +70,13 @@ func TestPolicy_String(t *testing.T) {
if got := p.String(); got != tt.want {
t.Errorf("Policy.String() = %v, want %v", got, tt.want)
}
out, err := json.Marshal(p.Source)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(string(out), tt.wantFrom); diff != "" {
t.Errorf("json diff() = %s", diff)
}
})
}
}

5
go.mod
View file

@ -6,18 +6,20 @@ require (
cloud.google.com/go v0.49.0 // indirect
contrib.go.opencensus.io/exporter/jaeger v0.2.0
contrib.go.opencensus.io/exporter/prometheus v0.1.0
github.com/OneOfOne/xxhash v1.2.7 // indirect
github.com/cespare/xxhash/v2 v2.1.1
github.com/fsnotify/fsnotify v1.4.7
github.com/go-redis/redis v6.15.6+incompatible
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/golang/mock v1.4.0
github.com/golang/protobuf v1.3.2
github.com/google/go-cmp v0.4.0
github.com/gorilla/mux v1.7.3
github.com/mitchellh/hashstructure v1.0.0
github.com/onsi/ginkgo v1.11.0 // indirect
github.com/onsi/gomega v1.8.1 // indirect
github.com/open-policy-agent/opa v0.16.2
github.com/pelletier/go-toml v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pomerium/autocache v0.0.0-20200121155820-dc85d6127c4e
github.com/pomerium/csrf v1.6.2-0.20190918035251-f3318380bad3
github.com/pomerium/go-oidc v2.0.0+incompatible
@ -25,6 +27,7 @@ require (
github.com/prometheus/client_golang v1.2.1
github.com/prometheus/procfs v0.0.8 // indirect
github.com/rakyll/statik v0.1.6
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
github.com/rs/cors v1.7.0
github.com/rs/zerolog v1.17.2
github.com/spf13/afero v1.2.2 // indirect

46
go.sum
View file

@ -23,6 +23,10 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.3 h1:wS8NNaIgtzapuArKIAjsyXtEN/IUjQkbw90xszUdS40=
github.com/OneOfOne/xxhash v1.2.3/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.7 h1:fzrmmkskv067ZQbd9wERNGuxckWw67dyzoMG62p7LMo=
github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -64,6 +68,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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 v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -73,9 +79,12 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg=
github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
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=
@ -88,8 +97,7 @@ github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
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=
@ -115,6 +123,7 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
@ -152,6 +161,7 @@ github.com/hashicorp/memberlist v0.1.5 h1:AYBsgJOW9gab/toO5tEB8lWetVgDKZycqkebJ8
github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -160,8 +170,10 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
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/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/konsorten/go-windows-terminal-sequences v1.0.2/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -172,6 +184,7 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@ -183,17 +196,21 @@ github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/open-policy-agent/opa v0.16.2 h1:Fdt1ysSA3p7z88HVHmUFiPM6hqqXbLDDZF9cQFYaIP0=
github.com/open-policy-agent/opa v0.16.2/go.mod h1:P0xUE/GQAAgnvV537GzA0Ikw4+icPELRT327QJPkaKY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -201,9 +218,13 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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/autocache v0.0.0-20200121155820-dc85d6127c4e h1:kD8uNYLnc/MGAGfFzCEDQPVKulBEBY3FlVHvMJkkmQ4=
@ -215,6 +236,7 @@ github.com/pomerium/go-oidc v2.0.0+incompatible/go.mod h1:DRsGVw6MOgxbfq4Y57jKOE
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
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.0.0-20181025174421-f30f42803563/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.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
@ -227,6 +249,7 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
@ -245,6 +268,10 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs=
github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
@ -256,6 +283,7 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@ -271,10 +299,12 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -298,6 +328,8 @@ github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMW
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
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=
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY=
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -326,6 +358,7 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -397,7 +430,6 @@ golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c h1:gUYreENmqtjZb2brVfUas1sC6
golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200121082415-34d275377bf9 h1:N19i1HjUnR7TF7rMt8O4p3dLvqvmYyzB6ifMFmrbY50=
golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -407,6 +439,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@ -422,6 +455,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2 h1:EtTFh6h4SAKemS+CURDMTDIANuduG5zKEXShyy18bGA=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -444,6 +478,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -485,8 +520,7 @@ 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=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -495,5 +529,3 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

File diff suppressed because one or more lines are too long

View file

@ -24,129 +24,224 @@ var _ = math.Inf
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
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"`
type IsAuthorizedRequest struct {
// User Context
//
UserToken string `protobuf:"bytes,1,opt,name=user_token,json=userToken,proto3" json:"user_token,omitempty"`
// Request Context
//
// Method specifies the HTTP method (GET, POST, PUT, etc.).
RequestMethod string `protobuf:"bytes,2,opt,name=request_method,json=requestMethod,proto3" json:"request_method,omitempty"`
// URL specifies either the URI being requested
RequestUrl string `protobuf:"bytes,3,opt,name=request_url,json=requestUrl,proto3" json:"request_url,omitempty"`
// host specifies the host on which the URL per RFC 7230, section 5.4
RequestHost string `protobuf:"bytes,4,opt,name=request_host,json=requestHost,proto3" json:"request_host,omitempty"`
// request_uri is the unmodified request-target of the
// Request-Line (RFC 7230, Section 3.1.1) as sent by the client
RequestRequestUri string `protobuf:"bytes,5,opt,name=request_request_uri,json=requestRequestUri,proto3" json:"request_request_uri,omitempty"`
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
RequestRemoteAddr string `protobuf:"bytes,6,opt,name=request_remote_addr,json=requestRemoteAddr,proto3" json:"request_remote_addr,omitempty"`
RequestHeaders map[string]*IsAuthorizedRequest_Headers `protobuf:"bytes,7,rep,name=request_headers,json=requestHeaders,proto3" json:"request_headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Identity) Reset() { *m = Identity{} }
func (m *Identity) String() string { return proto.CompactTextString(m) }
func (*Identity) ProtoMessage() {}
func (*Identity) Descriptor() ([]byte, []int) {
func (m *IsAuthorizedRequest) Reset() { *m = IsAuthorizedRequest{} }
func (m *IsAuthorizedRequest) String() string { return proto.CompactTextString(m) }
func (*IsAuthorizedRequest) ProtoMessage() {}
func (*IsAuthorizedRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_ffbc3c71370bee9a, []int{0}
}
func (m *Identity) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Identity.Unmarshal(m, b)
func (m *IsAuthorizedRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IsAuthorizedRequest.Unmarshal(m, b)
}
func (m *Identity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Identity.Marshal(b, m, deterministic)
func (m *IsAuthorizedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_IsAuthorizedRequest.Marshal(b, m, deterministic)
}
func (m *Identity) XXX_Merge(src proto.Message) {
xxx_messageInfo_Identity.Merge(m, src)
func (m *IsAuthorizedRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_IsAuthorizedRequest.Merge(m, src)
}
func (m *Identity) XXX_Size() int {
return xxx_messageInfo_Identity.Size(m)
func (m *IsAuthorizedRequest) XXX_Size() int {
return xxx_messageInfo_IsAuthorizedRequest.Size(m)
}
func (m *Identity) XXX_DiscardUnknown() {
xxx_messageInfo_Identity.DiscardUnknown(m)
func (m *IsAuthorizedRequest) XXX_DiscardUnknown() {
xxx_messageInfo_IsAuthorizedRequest.DiscardUnknown(m)
}
var xxx_messageInfo_Identity proto.InternalMessageInfo
var xxx_messageInfo_IsAuthorizedRequest proto.InternalMessageInfo
func (m *Identity) GetRoute() string {
func (m *IsAuthorizedRequest) GetUserToken() string {
if m != nil {
return m.Route
return m.UserToken
}
return ""
}
func (m *Identity) GetUser() string {
func (m *IsAuthorizedRequest) GetRequestMethod() string {
if m != nil {
return m.User
return m.RequestMethod
}
return ""
}
func (m *Identity) GetEmail() string {
func (m *IsAuthorizedRequest) GetRequestUrl() string {
if m != nil {
return m.Email
return m.RequestUrl
}
return ""
}
func (m *Identity) GetGroups() []string {
func (m *IsAuthorizedRequest) GetRequestHost() string {
if m != nil {
return m.Groups
return m.RequestHost
}
return ""
}
func (m *IsAuthorizedRequest) GetRequestRequestUri() string {
if m != nil {
return m.RequestRequestUri
}
return ""
}
func (m *IsAuthorizedRequest) GetRequestRemoteAddr() string {
if m != nil {
return m.RequestRemoteAddr
}
return ""
}
func (m *IsAuthorizedRequest) GetRequestHeaders() map[string]*IsAuthorizedRequest_Headers {
if m != nil {
return m.RequestHeaders
}
return nil
}
func (m *Identity) GetImpersonateEmail() string {
if m != nil {
return m.ImpersonateEmail
}
return ""
// headers represents key-value pairs in an HTTP header; map[string][]string
type IsAuthorizedRequest_Headers struct {
Value []string `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Identity) GetImpersonateGroups() []string {
func (m *IsAuthorizedRequest_Headers) Reset() { *m = IsAuthorizedRequest_Headers{} }
func (m *IsAuthorizedRequest_Headers) String() string { return proto.CompactTextString(m) }
func (*IsAuthorizedRequest_Headers) ProtoMessage() {}
func (*IsAuthorizedRequest_Headers) Descriptor() ([]byte, []int) {
return fileDescriptor_ffbc3c71370bee9a, []int{0, 0}
}
func (m *IsAuthorizedRequest_Headers) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IsAuthorizedRequest_Headers.Unmarshal(m, b)
}
func (m *IsAuthorizedRequest_Headers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_IsAuthorizedRequest_Headers.Marshal(b, m, deterministic)
}
func (m *IsAuthorizedRequest_Headers) XXX_Merge(src proto.Message) {
xxx_messageInfo_IsAuthorizedRequest_Headers.Merge(m, src)
}
func (m *IsAuthorizedRequest_Headers) XXX_Size() int {
return xxx_messageInfo_IsAuthorizedRequest_Headers.Size(m)
}
func (m *IsAuthorizedRequest_Headers) XXX_DiscardUnknown() {
xxx_messageInfo_IsAuthorizedRequest_Headers.DiscardUnknown(m)
}
var xxx_messageInfo_IsAuthorizedRequest_Headers proto.InternalMessageInfo
func (m *IsAuthorizedRequest_Headers) GetValue() []string {
if m != nil {
return m.ImpersonateGroups
return m.Value
}
return nil
}
type AuthorizeReply struct {
type IsAuthorizedReply struct {
IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AuthorizeReply) Reset() { *m = AuthorizeReply{} }
func (m *AuthorizeReply) String() string { return proto.CompactTextString(m) }
func (*AuthorizeReply) ProtoMessage() {}
func (*AuthorizeReply) Descriptor() ([]byte, []int) {
func (m *IsAuthorizedReply) Reset() { *m = IsAuthorizedReply{} }
func (m *IsAuthorizedReply) String() string { return proto.CompactTextString(m) }
func (*IsAuthorizedReply) ProtoMessage() {}
func (*IsAuthorizedReply) Descriptor() ([]byte, []int) {
return fileDescriptor_ffbc3c71370bee9a, []int{1}
}
func (m *AuthorizeReply) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AuthorizeReply.Unmarshal(m, b)
func (m *IsAuthorizedReply) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IsAuthorizedReply.Unmarshal(m, b)
}
func (m *AuthorizeReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AuthorizeReply.Marshal(b, m, deterministic)
func (m *IsAuthorizedReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_IsAuthorizedReply.Marshal(b, m, deterministic)
}
func (m *AuthorizeReply) XXX_Merge(src proto.Message) {
xxx_messageInfo_AuthorizeReply.Merge(m, src)
func (m *IsAuthorizedReply) XXX_Merge(src proto.Message) {
xxx_messageInfo_IsAuthorizedReply.Merge(m, src)
}
func (m *AuthorizeReply) XXX_Size() int {
return xxx_messageInfo_AuthorizeReply.Size(m)
func (m *IsAuthorizedReply) XXX_Size() int {
return xxx_messageInfo_IsAuthorizedReply.Size(m)
}
func (m *AuthorizeReply) XXX_DiscardUnknown() {
xxx_messageInfo_AuthorizeReply.DiscardUnknown(m)
func (m *IsAuthorizedReply) XXX_DiscardUnknown() {
xxx_messageInfo_IsAuthorizedReply.DiscardUnknown(m)
}
var xxx_messageInfo_AuthorizeReply proto.InternalMessageInfo
var xxx_messageInfo_IsAuthorizedReply proto.InternalMessageInfo
func (m *AuthorizeReply) GetIsValid() bool {
func (m *IsAuthorizedReply) GetIsValid() bool {
if m != nil {
return m.IsValid
}
return false
}
type IsAdminRequest struct {
UserToken string `protobuf:"bytes,1,opt,name=user_token,json=userToken,proto3" json:"user_token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *IsAdminRequest) Reset() { *m = IsAdminRequest{} }
func (m *IsAdminRequest) String() string { return proto.CompactTextString(m) }
func (*IsAdminRequest) ProtoMessage() {}
func (*IsAdminRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_ffbc3c71370bee9a, []int{2}
}
func (m *IsAdminRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IsAdminRequest.Unmarshal(m, b)
}
func (m *IsAdminRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_IsAdminRequest.Marshal(b, m, deterministic)
}
func (m *IsAdminRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_IsAdminRequest.Merge(m, src)
}
func (m *IsAdminRequest) XXX_Size() int {
return xxx_messageInfo_IsAdminRequest.Size(m)
}
func (m *IsAdminRequest) XXX_DiscardUnknown() {
xxx_messageInfo_IsAdminRequest.DiscardUnknown(m)
}
var xxx_messageInfo_IsAdminRequest proto.InternalMessageInfo
func (m *IsAdminRequest) GetUserToken() string {
if m != nil {
return m.UserToken
}
return ""
}
type IsAdminReply struct {
IsAdmin bool `protobuf:"varint,1,opt,name=is_admin,json=isAdmin,proto3" json:"is_admin,omitempty"`
IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -156,7 +251,7 @@ 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_ffbc3c71370bee9a, []int{2}
return fileDescriptor_ffbc3c71370bee9a, []int{3}
}
func (m *IsAdminReply) XXX_Unmarshal(b []byte) error {
@ -177,40 +272,51 @@ func (m *IsAdminReply) XXX_DiscardUnknown() {
var xxx_messageInfo_IsAdminReply proto.InternalMessageInfo
func (m *IsAdminReply) GetIsAdmin() bool {
func (m *IsAdminReply) GetIsValid() bool {
if m != nil {
return m.IsAdmin
return m.IsValid
}
return false
}
func init() {
proto.RegisterType((*Identity)(nil), "authorize.Identity")
proto.RegisterType((*AuthorizeReply)(nil), "authorize.AuthorizeReply")
proto.RegisterType((*IsAuthorizedRequest)(nil), "authorize.IsAuthorizedRequest")
proto.RegisterMapType((map[string]*IsAuthorizedRequest_Headers)(nil), "authorize.IsAuthorizedRequest.RequestHeadersEntry")
proto.RegisterType((*IsAuthorizedRequest_Headers)(nil), "authorize.IsAuthorizedRequest.Headers")
proto.RegisterType((*IsAuthorizedReply)(nil), "authorize.IsAuthorizedReply")
proto.RegisterType((*IsAdminRequest)(nil), "authorize.IsAdminRequest")
proto.RegisterType((*IsAdminReply)(nil), "authorize.IsAdminReply")
}
func init() { proto.RegisterFile("authorize.proto", fileDescriptor_ffbc3c71370bee9a) }
var fileDescriptor_ffbc3c71370bee9a = []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,
// 390 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xc1, 0x8e, 0xda, 0x30,
0x10, 0x86, 0x1b, 0x52, 0x08, 0x19, 0x28, 0x14, 0x53, 0xa9, 0x26, 0x6a, 0x0b, 0x8d, 0xd4, 0x8a,
0x5e, 0x52, 0x29, 0xbd, 0x54, 0x55, 0xa5, 0x8a, 0x43, 0x25, 0x38, 0xb4, 0x87, 0xa8, 0xed, 0xa5,
0x87, 0x28, 0x2b, 0x5b, 0xc2, 0x22, 0x60, 0xd6, 0x76, 0x90, 0xb2, 0xef, 0xb2, 0xef, 0xb7, 0x8f,
0xb1, 0x8a, 0xb1, 0xc3, 0xb2, 0x62, 0xd9, 0x3d, 0xe1, 0xf9, 0xe7, 0x9b, 0xdf, 0xe3, 0x5f, 0x04,
0xfa, 0x59, 0xa1, 0x96, 0x5c, 0xb0, 0x2b, 0x1a, 0x6d, 0x05, 0x57, 0x1c, 0xf9, 0xb5, 0x10, 0xde,
0xb8, 0x30, 0x5c, 0xc8, 0x99, 0xad, 0x49, 0x42, 0x2f, 0x0b, 0x2a, 0x15, 0x7a, 0x0b, 0x50, 0x48,
0x2a, 0x52, 0xc5, 0x57, 0x74, 0x83, 0x9d, 0x89, 0x33, 0xf5, 0x13, 0xbf, 0x52, 0xfe, 0x54, 0x02,
0xfa, 0x00, 0x3d, 0xb1, 0x27, 0xd3, 0x35, 0x55, 0x4b, 0x4e, 0x70, 0x43, 0x23, 0x2f, 0x8c, 0xfa,
0x4b, 0x8b, 0x68, 0x0c, 0x1d, 0x8b, 0x15, 0x22, 0xc7, 0xae, 0x66, 0xc0, 0x48, 0x7f, 0x45, 0x8e,
0xde, 0x43, 0xd7, 0x02, 0x4b, 0x2e, 0x15, 0x7e, 0xae, 0x09, 0x3b, 0x34, 0xe7, 0x52, 0xa1, 0x08,
0x86, 0x16, 0x39, 0x78, 0x31, 0xdc, 0xd4, 0xe4, 0xc0, 0x48, 0x89, 0xb5, 0x64, 0xc7, 0xfc, 0x9a,
0x2b, 0x9a, 0x66, 0x84, 0x08, 0xdc, 0xba, 0xc7, 0x57, 0x9d, 0x19, 0x21, 0x02, 0xfd, 0x87, 0x7e,
0xbd, 0x02, 0xcd, 0x08, 0x15, 0x12, 0x7b, 0x13, 0x77, 0xda, 0x89, 0xe3, 0xe8, 0x90, 0xdb, 0x89,
0x88, 0x22, 0xf3, 0x3b, 0xdf, 0x0f, 0xfd, 0xdc, 0x28, 0x51, 0x26, 0x36, 0x15, 0x23, 0x06, 0x63,
0xf0, 0xcc, 0x11, 0xbd, 0x82, 0xe6, 0x2e, 0xcb, 0x0b, 0x8a, 0x9d, 0x89, 0x3b, 0xf5, 0x93, 0x7d,
0x11, 0x30, 0x18, 0x9e, 0xf0, 0x41, 0x2f, 0xc1, 0x5d, 0xd1, 0xd2, 0xe4, 0x5e, 0x1d, 0xd1, 0x77,
0x3b, 0x5e, 0x05, 0xdd, 0x89, 0x3f, 0x3e, 0xb2, 0x9c, 0x71, 0x33, 0xd7, 0x7c, 0x6b, 0x7c, 0x75,
0xc2, 0x08, 0x06, 0xc7, 0xe4, 0x36, 0x2f, 0xd1, 0x08, 0xda, 0x4c, 0xa6, 0xbb, 0x2c, 0x67, 0x44,
0xdf, 0xd6, 0x4e, 0x3c, 0x26, 0xff, 0x55, 0x65, 0xf8, 0x19, 0x7a, 0x0b, 0x39, 0x23, 0x6b, 0xb6,
0x79, 0xda, 0x9f, 0x22, 0xfc, 0x04, 0xdd, 0x7a, 0xe0, 0xbc, 0x77, 0x7c, 0xed, 0x00, 0xd4, 0xab,
0x08, 0xf4, 0x5b, 0x4f, 0xd6, 0xab, 0xa1, 0x77, 0xe7, 0x5f, 0x17, 0xbc, 0x79, 0xb0, 0xbf, 0xcd,
0xcb, 0xf0, 0x19, 0xfa, 0x01, 0x9e, 0xd9, 0x04, 0x8d, 0x8e, 0xd1, 0x3b, 0xcf, 0x09, 0x5e, 0x9f,
0x6a, 0x69, 0x83, 0x8b, 0x96, 0xfe, 0x50, 0xbe, 0xdc, 0x06, 0x00, 0x00, 0xff, 0xff, 0x72, 0x3a,
0xa3, 0xe0, 0x3b, 0x03, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -225,8 +331,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 *Identity, opts ...grpc.CallOption) (*AuthorizeReply, error)
IsAdmin(ctx context.Context, in *Identity, opts ...grpc.CallOption) (*IsAdminReply, error)
IsAuthorized(ctx context.Context, in *IsAuthorizedRequest, opts ...grpc.CallOption) (*IsAuthorizedReply, error)
IsAdmin(ctx context.Context, in *IsAdminRequest, opts ...grpc.CallOption) (*IsAdminReply, error)
}
type authorizerClient struct {
@ -237,16 +343,16 @@ func NewAuthorizerClient(cc *grpc.ClientConn) AuthorizerClient {
return &authorizerClient{cc}
}
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...)
func (c *authorizerClient) IsAuthorized(ctx context.Context, in *IsAuthorizedRequest, opts ...grpc.CallOption) (*IsAuthorizedReply, error) {
out := new(IsAuthorizedReply)
err := c.cc.Invoke(ctx, "/authorize.Authorizer/IsAuthorized", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authorizerClient) IsAdmin(ctx context.Context, in *Identity, opts ...grpc.CallOption) (*IsAdminReply, error) {
func (c *authorizerClient) IsAdmin(ctx context.Context, in *IsAdminRequest, opts ...grpc.CallOption) (*IsAdminReply, error) {
out := new(IsAdminReply)
err := c.cc.Invoke(ctx, "/authorize.Authorizer/IsAdmin", in, out, opts...)
if err != nil {
@ -257,18 +363,18 @@ func (c *authorizerClient) IsAdmin(ctx context.Context, in *Identity, opts ...gr
// AuthorizerServer is the server API for Authorizer service.
type AuthorizerServer interface {
Authorize(context.Context, *Identity) (*AuthorizeReply, error)
IsAdmin(context.Context, *Identity) (*IsAdminReply, error)
IsAuthorized(context.Context, *IsAuthorizedRequest) (*IsAuthorizedReply, error)
IsAdmin(context.Context, *IsAdminRequest) (*IsAdminReply, error)
}
// UnimplementedAuthorizerServer can be embedded to have forward compatible implementations.
type UnimplementedAuthorizerServer struct {
}
func (*UnimplementedAuthorizerServer) Authorize(ctx context.Context, req *Identity) (*AuthorizeReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Authorize not implemented")
func (*UnimplementedAuthorizerServer) IsAuthorized(ctx context.Context, req *IsAuthorizedRequest) (*IsAuthorizedReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsAuthorized not implemented")
}
func (*UnimplementedAuthorizerServer) IsAdmin(ctx context.Context, req *Identity) (*IsAdminReply, error) {
func (*UnimplementedAuthorizerServer) IsAdmin(ctx context.Context, req *IsAdminRequest) (*IsAdminReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsAdmin not implemented")
}
@ -276,26 +382,26 @@ func RegisterAuthorizerServer(s *grpc.Server, srv AuthorizerServer) {
s.RegisterService(&_Authorizer_serviceDesc, srv)
}
func _Authorizer_Authorize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Identity)
func _Authorizer_IsAuthorized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(IsAuthorizedRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AuthorizerServer).Authorize(ctx, in)
return srv.(AuthorizerServer).IsAuthorized(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/authorize.Authorizer/Authorize",
FullMethod: "/authorize.Authorizer/IsAuthorized",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthorizerServer).Authorize(ctx, req.(*Identity))
return srv.(AuthorizerServer).IsAuthorized(ctx, req.(*IsAuthorizedRequest))
}
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)
in := new(IsAdminRequest)
if err := dec(in); err != nil {
return nil, err
}
@ -307,7 +413,7 @@ func _Authorizer_IsAdmin_Handler(srv interface{}, ctx context.Context, dec func(
FullMethod: "/authorize.Authorizer/IsAdmin",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthorizerServer).IsAdmin(ctx, req.(*Identity))
return srv.(AuthorizerServer).IsAdmin(ctx, req.(*IsAdminRequest))
}
return interceptor(ctx, in, info, handler)
}
@ -317,8 +423,8 @@ var _Authorizer_serviceDesc = grpc.ServiceDesc{
HandlerType: (*AuthorizerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Authorize",
Handler: _Authorizer_Authorize_Handler,
MethodName: "IsAuthorized",
Handler: _Authorizer_IsAuthorized_Handler,
},
{
MethodName: "IsAdmin",

View file

@ -3,23 +3,35 @@ syntax = "proto3";
package authorize;
service Authorizer {
rpc Authorize(Identity) returns (AuthorizeReply) {}
rpc IsAdmin(Identity) returns (IsAdminReply) {}
rpc IsAuthorized(IsAuthorizedRequest) returns (IsAuthorizedReply) {}
rpc IsAdmin(IsAdminRequest) returns (IsAdminReply) {}
}
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 IsAuthorizedRequest {
// User Context
//
string user_token = 1;
// Request Context
//
// Method specifies the HTTP method (GET, POST, PUT, etc.).
string request_method = 2;
// URL specifies either the URI being requested
string request_url = 3;
// host specifies the host on which the URL per RFC 7230, section 5.4
string request_host = 4;
// request_uri is the unmodified request-target of the
// Request-Line (RFC 7230, Section 3.1.1) as sent by the client
string request_request_uri = 5;
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
string request_remote_addr = 6;
// headers represents key-value pairs in an HTTP header; map[string][]string
message Headers { repeated string value = 1; }
map<string, Headers> request_headers = 7;
}
message AuthorizeReply { bool is_valid = 1; }
message IsAuthorizedReply { bool is_valid = 1; }
message IsAdminReply { bool is_admin = 1; }
message IsAdminRequest { string user_token = 1; }
message IsAdminReply { bool is_valid = 1; }

View file

@ -3,10 +3,10 @@ package client
import (
"context"
"errors"
"net/http"
"github.com/pomerium/pomerium/internal/grpc/authorize"
pb "github.com/pomerium/pomerium/internal/grpc/authorize"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"google.golang.org/grpc"
@ -16,9 +16,9 @@ import (
type Authorizer interface {
// Authorize takes a route and user session and returns whether the
// request is valid per access policy
Authorize(context.Context, string, *sessions.State) (bool, error)
Authorize(ctx context.Context, user string, r *http.Request) (bool, error)
// IsAdmin takes a session and returns whether the user is an administrator
IsAdmin(context.Context, *sessions.State) (bool, error)
IsAdmin(ctx context.Context, user string) (bool, error)
// Close closes the auth connection if any.
Close() error
}
@ -36,37 +36,47 @@ func New(conn *grpc.ClientConn) (p *Client, err error) {
// Authorize takes a route and user session and returns whether the
// request is valid per access policy
func (c *Client) Authorize(ctx context.Context, route string, s *sessions.State) (bool, error) {
func (c *Client) Authorize(ctx context.Context, user string, r *http.Request) (bool, error) {
ctx, span := trace.StartSpan(ctx, "grpc.authorize.client.Authorize")
defer span.End()
if s == nil {
return false, errors.New("session cannot be nil")
}
response, err := c.client.Authorize(ctx, &pb.Identity{
Route: route,
User: s.User,
Email: s.Email,
Groups: s.Groups,
ImpersonateEmail: s.ImpersonateEmail,
ImpersonateGroups: s.ImpersonateGroups,
// var h map[string]&structpb.ListValue{}
response, err := c.client.IsAuthorized(ctx, &pb.IsAuthorizedRequest{
UserToken: user,
RequestHost: r.Host,
RequestMethod: r.Method,
RequestHeaders: cloneHeaders(r.Header),
RequestRemoteAddr: r.RemoteAddr,
RequestRequestUri: r.RequestURI,
RequestUrl: r.URL.String(),
})
return response.GetIsValid(), err
}
// IsAdmin takes a session and returns whether the user is an administrator
func (c *Client) IsAdmin(ctx context.Context, s *sessions.State) (bool, error) {
// IsAdmin takes a route and user session and returns whether the
// request is valid per access policy
func (c *Client) IsAdmin(ctx context.Context, user string) (bool, error) {
ctx, span := trace.StartSpan(ctx, "grpc.authorize.client.IsAdmin")
defer span.End()
if s == nil {
return false, errors.New("session cannot be nil")
}
response, err := c.client.IsAdmin(ctx, &pb.Identity{Email: s.Email, Groups: s.Groups})
return response.GetIsAdmin(), err
response, err := c.client.IsAdmin(ctx, &pb.IsAdminRequest{
UserToken: user,
})
return response.GetIsValid(), err
}
// Close tears down the ClientConn and all underlying connections.
func (c *Client) Close() error {
return c.Conn.Close()
}
type protoHeader map[string]*authorize.IsAuthorizedRequest_Headers
func cloneHeaders(in http.Header) protoHeader {
out := make(protoHeader, len(in))
for key, values := range in {
newValues := make([]string, len(values))
copy(newValues, values)
out[key] = &authorize.IsAuthorizedRequest_Headers{Value: newValues}
}
return out
}

View file

@ -1,78 +0,0 @@
package client
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/pomerium/pomerium/internal/grpc/authorize"
"github.com/pomerium/pomerium/internal/grpc/authorize/client/mock_authorize"
"github.com/pomerium/pomerium/internal/sessions"
)
func TestClient_Authorize(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := mock_authorize.NewMockAuthorizerClient(ctrl)
client.EXPECT().Authorize(
gomock.Any(),
gomock.Any(),
).Return(&authorize.AuthorizeReply{IsValid: true}, nil).AnyTimes()
tests := []struct {
name string
route string
s *sessions.State
want bool
wantErr bool
}{
{"good", "hello.pomerium.io", &sessions.State{User: "admin@pomerium.io", Email: "admin@pomerium.io"}, true, false},
{"impersonate request", "hello.pomerium.io", &sessions.State{User: "admin@pomerium.io", Email: "admin@pomerium.io", ImpersonateEmail: "other@other.example"}, true, false},
{"session cannot be nil", "hello.pomerium.io", nil, false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Client{client: client}
got, err := a.Authorize(context.Background(), tt.route, tt.s)
if (err != nil) != tt.wantErr {
t.Errorf("Client.Authorize() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Client.Authorize() = %v, want %v", got, tt.want)
}
})
}
}
func TestClient_IsAdmin(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := mock_authorize.NewMockAuthorizerClient(ctrl)
client.EXPECT().IsAdmin(
gomock.Any(),
gomock.Any(),
).Return(&authorize.IsAdminReply{IsAdmin: true}, nil).AnyTimes()
tests := []struct {
name string
s *sessions.State
want bool
wantErr bool
}{
{"good", &sessions.State{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 := &Client{client: client}
got, err := a.IsAdmin(context.Background(), tt.s)
if (err != nil) != tt.wantErr {
t.Errorf("Client.IsAdmin() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Client.IsAdmin() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -2,8 +2,7 @@ package client
import (
"context"
"github.com/pomerium/pomerium/internal/sessions"
"net/http"
)
var _ Authorizer = &MockAuthorize{}
@ -21,11 +20,11 @@ type MockAuthorize struct {
func (a MockAuthorize) Close() error { return a.CloseError }
// Authorize is a mocked authorizer client function.
func (a MockAuthorize) Authorize(ctx context.Context, route string, s *sessions.State) (bool, error) {
func (a MockAuthorize) Authorize(ctx context.Context, user string, s *http.Request) (bool, error) {
return a.AuthorizeResponse, a.AuthorizeError
}
// IsAdmin is a mocked IsAdmin function.
func (a MockAuthorize) IsAdmin(ctx context.Context, s *sessions.State) (bool, error) {
func (a MockAuthorize) IsAdmin(ctx context.Context, user string) (bool, error) {
return a.IsAdminResponse, a.IsAdminError
}

View file

@ -1,130 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// 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/internal/grpc/authorize"
grpc "google.golang.org/grpc"
)
// MockAuthorizerClient is a mock of AuthorizerClient interface
type MockAuthorizerClient struct {
ctrl *gomock.Controller
recorder *MockAuthorizerClientMockRecorder
}
// MockAuthorizerClientMockRecorder is the mock recorder for MockAuthorizerClient
type MockAuthorizerClientMockRecorder struct {
mock *MockAuthorizerClient
}
// NewMockAuthorizerClient creates a new mock instance
func NewMockAuthorizerClient(ctrl *gomock.Controller) *MockAuthorizerClient {
mock := &MockAuthorizerClient{ctrl: ctrl}
mock.recorder = &MockAuthorizerClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAuthorizerClient) EXPECT() *MockAuthorizerClientMockRecorder {
return m.recorder
}
// Authorize mocks base method
func (m *MockAuthorizerClient) Authorize(ctx context.Context, in *authorize.Identity, opts ...grpc.CallOption) (*authorize.AuthorizeReply, error) {
m.ctrl.T.Helper()
varargs := []interface{}{ctx, in}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Authorize", varargs...)
ret0, _ := ret[0].(*authorize.AuthorizeReply)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Authorize indicates an expected call of Authorize
func (mr *MockAuthorizerClientMockRecorder) Authorize(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, "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)
}

View file

@ -51,26 +51,26 @@ func NewStore(o *Options) *Store {
// LoadSession looks for a preset query parameter in the request body
// representing the key to lookup from the cache.
func (s *Store) LoadSession(r *http.Request) (*sessions.State, error) {
func (s *Store) LoadSession(r *http.Request) (*sessions.State, string, error) {
// look for our cache's key in the default query param
sessionID := r.URL.Query().Get(s.queryParam)
if sessionID == "" {
return nil, sessions.ErrNoSessionFound
return nil, "", sessions.ErrNoSessionFound
}
exists, val, err := s.cache.Get(r.Context(), sessionID)
if err != nil {
log.FromRequest(r).Debug().Msg("sessions/cache: miss, trying wrapped loader")
return nil, err
return nil, "", err
}
if !exists {
return nil, sessions.ErrNoSessionFound
return nil, "", sessions.ErrNoSessionFound
}
var session sessions.State
if err := s.encoder.Unmarshal(val, &session); err != nil {
log.FromRequest(r).Error().Err(err).Msg("sessions/cache: unmarshal")
return nil, sessions.ErrMalformed
return nil, "", sessions.ErrMalformed
}
return &session, nil
return &session, string(val), nil
}
// ClearSession clears the session from the wrapped store.

View file

@ -187,7 +187,7 @@ func TestStore_LoadSession(t *testing.T) {
r.URL.RawQuery = q.Encode()
r.Header.Set("Accept", "application/json")
_, err := s.LoadSession(r)
_, _, err := s.LoadSession(r)
if (err != nil) != tt.wantErr {
t.Errorf("Store.LoadSession() error = %v, wantErr %v", err, tt.wantErr)
return

View file

@ -125,21 +125,21 @@ func getCookies(r *http.Request, name string) []*http.Cookie {
}
// LoadSession returns a State from the cookie in the request.
func (cs *Store) LoadSession(r *http.Request) (*sessions.State, error) {
func (cs *Store) LoadSession(r *http.Request) (*sessions.State, string, error) {
cookies := getCookies(r, cs.Name)
if len(cookies) == 0 {
return nil, sessions.ErrNoSessionFound
return nil, "", sessions.ErrNoSessionFound
}
for _, cookie := range cookies {
data := loadChunkedCookie(r, cookie)
jwt := loadChunkedCookie(r, cookie)
session := &sessions.State{}
err := cs.decoder.Unmarshal([]byte(data), session)
err := cs.decoder.Unmarshal([]byte(jwt), session)
if err == nil {
return session, nil
return session, jwt, nil
}
}
return nil, sessions.ErrMalformed
return nil, "", sessions.ErrMalformed
}
// SaveSession saves a session state to a request's cookie store.

View file

@ -138,7 +138,7 @@ func TestStore_SaveSession(t *testing.T) {
r.AddCookie(cookie)
}
state, err := s.LoadSession(r)
state, _, err := s.LoadSession(r)
if (err != nil) != tt.wantLoadErr {
t.Errorf("LoadSession() error = %v, wantErr %v", err, tt.wantLoadErr)
return

View file

@ -18,7 +18,7 @@ import (
func testAuthorizer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := sessions.FromContext(r.Context())
_, _, err := sessions.FromContext(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return

View file

@ -42,16 +42,16 @@ func NewStore(enc encoding.Unmarshaler, headerType string) *Store {
}
// LoadSession tries to retrieve the token string from the Authorization header.
func (as *Store) LoadSession(r *http.Request) (*sessions.State, error) {
cipherText := TokenFromHeader(r, as.authHeader, as.authType)
if cipherText == "" {
return nil, sessions.ErrNoSessionFound
func (as *Store) LoadSession(r *http.Request) (*sessions.State, string, error) {
jwt := TokenFromHeader(r, as.authHeader, as.authType)
if jwt == "" {
return nil, "", sessions.ErrNoSessionFound
}
var session sessions.State
if err := as.encoder.Unmarshal([]byte(cipherText), &session); err != nil {
return nil, sessions.ErrMalformed
if err := as.encoder.Unmarshal([]byte(jwt), &session); err != nil {
return nil, "", sessions.ErrMalformed
}
return &session, nil
return &session, jwt, nil
}
// TokenFromHeader retrieves the value of the authorization header from a given

View file

@ -18,7 +18,7 @@ import (
func testAuthorizer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := sessions.FromContext(r.Context())
_, _, err := sessions.FromContext(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return

View file

@ -11,6 +11,7 @@ import (
// Context keys
var (
SessionCtxKey = &contextKey{"Session"}
SessionJWTCtxKey = &contextKey{"SessionJWT"}
ErrorCtxKey = &contextKey{"Error"}
)
@ -26,8 +27,8 @@ func retrieve(s ...SessionLoader) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
hfn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
state, err := retrieveFromRequest(r, s...)
ctx = NewContext(ctx, state, err)
state, jwt, err := retrieveFromRequest(r, s...)
ctx = NewContext(ctx, state, jwt, err)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(hfn)
@ -36,33 +37,36 @@ func retrieve(s ...SessionLoader) func(http.Handler) http.Handler {
// retrieveFromRequest extracts sessions state from the request by calling
// token find functions in the order they where provided.
func retrieveFromRequest(r *http.Request, sessions ...SessionLoader) (*State, error) {
func retrieveFromRequest(r *http.Request, sessions ...SessionLoader) (*State, string, error) {
for _, s := range sessions {
state, err := s.LoadSession(r)
state, jwt, err := s.LoadSession(r)
if err != nil && !errors.Is(err, ErrNoSessionFound) {
return state, err
return state, jwt, err
}
if state != nil {
//todo(bdd): have authz verify
err := state.Verify(urlutil.StripPort(r.Host))
return state, err // N.B.: state is _not_ nil
return state, jwt, err // N.B.: state is _not_ nil
}
}
return nil, ErrNoSessionFound
return nil, "", ErrNoSessionFound
}
// NewContext sets context values for the user session state and error.
func NewContext(ctx context.Context, t *State, err error) context.Context {
func NewContext(ctx context.Context, t *State, jwt string, err error) context.Context {
ctx = context.WithValue(ctx, SessionCtxKey, t)
ctx = context.WithValue(ctx, SessionJWTCtxKey, jwt)
ctx = context.WithValue(ctx, ErrorCtxKey, err)
return ctx
}
// FromContext retrieves context values for the user session state and error.
func FromContext(ctx context.Context) (*State, error) {
func FromContext(ctx context.Context) (*State, string, error) {
state, _ := ctx.Value(SessionCtxKey).(*State)
jwt, _ := ctx.Value(SessionJWTCtxKey).(string)
err, _ := ctx.Value(ErrorCtxKey).(error)
return state, err
return state, jwt, err
}
// contextKey is a value for use with context.WithValue. It's used as

View file

@ -25,8 +25,8 @@ func TestNewContext(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctxOut := NewContext(tt.ctx, tt.t, tt.err)
stateOut, errOut := FromContext(ctxOut)
ctxOut := NewContext(tt.ctx, tt.t, "", tt.err)
stateOut, _, errOut := FromContext(ctxOut)
if diff := cmp.Diff(tt.t.Email, stateOut.Email); diff != "" {
t.Errorf("NewContext() = %s", diff)
}
@ -59,7 +59,7 @@ func Test_contextKey_String(t *testing.T) {
func testAuthorizer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := FromContext(r.Context())
_, _, err := FromContext(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
@ -84,8 +84,8 @@ func (ms *store) ClearSession(http.ResponseWriter, *http.Request) {
}
// LoadSession returns the session and a error
func (ms store) LoadSession(*http.Request) (*State, error) {
return ms.Session, ms.LoadError
func (ms store) LoadSession(*http.Request) (*State, string, error) {
return ms.Session, "", ms.LoadError
}
// SaveSession returns a save error.

View file

@ -13,6 +13,7 @@ var _ sessions.SessionLoader = &Store{}
// Store is a mock implementation of the SessionStore interface
type Store struct {
ResponseSession string
SessionJWT string
Session *sessions.State
SaveError error
LoadError error
@ -24,8 +25,8 @@ func (ms *Store) ClearSession(http.ResponseWriter, *http.Request) {
}
// LoadSession returns the session and a error
func (ms Store) LoadSession(*http.Request) (*sessions.State, error) {
return ms.Session, ms.LoadError
func (ms Store) LoadSession(*http.Request) (*sessions.State, string, error) {
return ms.Session, ms.SessionJWT, ms.LoadError
}
// SaveSession returns a save error.

View file

@ -35,7 +35,7 @@ func TestStore(t *testing.T) {
t.Errorf("MockCSRFStore.GetCSRF() error = %v, wantSaveErr %v", err, tt.wantSaveErr)
return
}
got, err := ms.LoadSession(nil)
got, _, err := ms.LoadSession(nil)
if (err != nil) != tt.wantLoadErr {
t.Errorf("MockCSRFStore.GetCSRF() error = %v, wantLoadErr %v", err, tt.wantLoadErr)
return

View file

@ -18,7 +18,7 @@ import (
func testAuthorizer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := sessions.FromContext(r.Context())
_, _, err := sessions.FromContext(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return

View file

@ -41,16 +41,16 @@ func NewStore(enc encoding.MarshalUnmarshaler, qp string) *Store {
}
// LoadSession tries to retrieve the token string from URL query parameters.
func (qp *Store) LoadSession(r *http.Request) (*sessions.State, error) {
cipherText := r.URL.Query().Get(qp.queryParamKey)
if cipherText == "" {
return nil, sessions.ErrNoSessionFound
func (qp *Store) LoadSession(r *http.Request) (*sessions.State, string, error) {
jwt := r.URL.Query().Get(qp.queryParamKey)
if jwt == "" {
return nil, "", sessions.ErrNoSessionFound
}
var session sessions.State
if err := qp.decoder.Unmarshal([]byte(cipherText), &session); err != nil {
return nil, sessions.ErrMalformed
if err := qp.decoder.Unmarshal([]byte(jwt), &session); err != nil {
return nil, "", sessions.ErrMalformed
}
return &session, nil
return &session, jwt, nil
}
// ClearSession clears the session cookie from a request's query param key `pomerium_session`.

View file

@ -15,5 +15,5 @@ type SessionStore interface {
// SessionLoader defines an interface for loading a session.
type SessionLoader interface {
LoadSession(*http.Request) (*State, error)
LoadSession(*http.Request) (*State, string, error)
}

View file

@ -81,7 +81,7 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
return httputil.NewError(http.StatusBadRequest, err)
}
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
if errors.Is(err, sessions.ErrNoSessionFound) || errors.Is(err, sessions.ErrExpired) {
if verifyOnly {
return httputil.NewError(http.StatusUnauthorized, err)
@ -104,7 +104,8 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
return httputil.NewError(http.StatusUnauthorized, err)
}
p.addPomeriumHeaders(w, r)
if err := p.authorize(uri.Host, r); err != nil {
r.Host = uri.Host
if err := p.authorize(r); err != nil {
return err
}

View file

@ -91,10 +91,10 @@ func TestProxy_ForwardAuth(t *testing.T) {
uri.RawQuery = queryString.Encode()
r := httptest.NewRequest(tt.method, uri.String(), nil)
state, _ := tt.sessionStore.LoadSession(r)
state, _, _ := tt.sessionStore.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
if len(tt.headers) != 0 {

View file

@ -87,12 +87,12 @@ func (p *Proxy) SignOut(w http.ResponseWriter, r *http.Request) {
// 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) error {
session, err := sessions.FromContext(r.Context())
session, jwt, err := sessions.FromContext(r.Context())
if err != nil {
return err
}
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), session)
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), jwt)
if err != nil {
return err
}
@ -112,11 +112,11 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) error {
// 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) error {
session, err := sessions.FromContext(r.Context())
session, jwt, err := sessions.FromContext(r.Context())
if err != nil {
return err
}
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), session)
isAdmin, err := p.AuthorizeClient.IsAdmin(r.Context(), jwt)
if err != nil {
return err
}

View file

@ -96,9 +96,9 @@ func TestProxy_UserDashboard(t *testing.T) {
p.AuthorizeClient = tt.authorizer
r := httptest.NewRequest(tt.method, "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
@ -159,9 +159,9 @@ func TestProxy_Impersonate(t *testing.T) {
uri := &url.URL{Path: "/"}
r := httptest.NewRequest(tt.method, uri.String(), bytes.NewBufferString(postForm.Encode()))
state, _ := tt.sessionStore.LoadSession(r)
state, _, _ := tt.sessionStore.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")

View file

@ -34,7 +34,7 @@ func (p *Proxy) AuthenticateSession(next http.Handler) http.Handler {
ctx, span := trace.StartSpan(r.Context(), "proxy.AuthenticateSession")
defer span.End()
_, err := sessions.FromContext(ctx)
_, _, err := sessions.FromContext(ctx)
if errors.Is(err, sessions.ErrExpired) {
ctx, err = p.refresh(ctx, w, r)
if err != nil {
@ -55,7 +55,7 @@ func (p *Proxy) AuthenticateSession(next http.Handler) http.Handler {
func (p *Proxy) refresh(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, error) {
ctx, span := trace.StartSpan(ctx, "proxy.AuthenticateSession/refresh")
defer span.End()
s, err := sessions.FromContext(ctx)
s, _, err := sessions.FromContext(ctx)
if !errors.Is(err, sessions.ErrExpired) || s == nil {
return nil, errors.New("proxy: unexpected session state for refresh")
}
@ -101,11 +101,11 @@ func (p *Proxy) refresh(ctx context.Context, w http.ResponseWriter, r *http.Requ
if err := state.Verify(urlutil.StripPort(r.Host)); err != nil {
return nil, err
}
return sessions.NewContext(r.Context(), &state, err), nil
return sessions.NewContext(r.Context(), &state, string(jwtBytes), err), nil
}
func (p *Proxy) redirectToSignin(w http.ResponseWriter, r *http.Request) error {
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
p.sessionStore.ClearSession(w, r)
if s != nil && err != nil && s.Programmatic {
return httputil.NewError(http.StatusUnauthorized, err)
@ -120,7 +120,7 @@ func (p *Proxy) redirectToSignin(w http.ResponseWriter, r *http.Request) error {
}
func (p *Proxy) addPomeriumHeaders(w http.ResponseWriter, r *http.Request) {
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
if err == nil && s != nil {
r.Header.Set(HeaderUserID, s.Subject)
r.Header.Set(HeaderEmail, s.RequestEmail())
@ -137,7 +137,7 @@ func (p *Proxy) AuthorizeSession(next http.Handler) http.Handler {
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ctx, span := trace.StartSpan(r.Context(), "proxy.AuthorizeSession")
defer span.End()
if err := p.authorize(r.Host, r.WithContext(ctx)); err != nil {
if err := p.authorize(r.WithContext(ctx)); err != nil {
log.FromRequest(r).Debug().Err(err).Msg("proxy: AuthorizeSession")
return err
}
@ -146,16 +146,16 @@ func (p *Proxy) AuthorizeSession(next http.Handler) http.Handler {
})
}
func (p *Proxy) authorize(host string, r *http.Request) error {
s, err := sessions.FromContext(r.Context())
func (p *Proxy) authorize(r *http.Request) error {
s, jwt, err := sessions.FromContext(r.Context())
if err != nil {
return httputil.NewError(http.StatusInternalServerError, err)
}
authorized, err := p.AuthorizeClient.Authorize(r.Context(), host, s)
authorized, err := p.AuthorizeClient.Authorize(r.Context(), jwt, r)
if err != nil {
return err
} else if !authorized {
return httputil.NewError(http.StatusForbidden, fmt.Errorf("%s is not authorized for %s", s.RequestEmail(), host))
return httputil.NewError(http.StatusForbidden, fmt.Errorf("%s is not authorized for %s", s.RequestEmail(), r.Host))
}
return nil
}
@ -167,7 +167,7 @@ func (p *Proxy) SignRequest(signer encoding.Marshaler) func(next http.Handler) h
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ctx, span := trace.StartSpan(r.Context(), "proxy.SignRequest")
defer span.End()
s, err := sessions.FromContext(r.Context())
s, _, err := sessions.FromContext(r.Context())
if err != nil {
return httputil.NewError(http.StatusForbidden, err)
}

View file

@ -73,9 +73,9 @@ func TestProxy_AuthenticateSession(t *testing.T) {
encoder: tt.encoder,
}
r := httptest.NewRequest(http.MethodGet, "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
w := httptest.NewRecorder()
@ -122,9 +122,9 @@ func TestProxy_AuthorizeSession(t *testing.T) {
AuthorizeClient: tt.authzClient,
}
r := httptest.NewRequest(http.MethodGet, "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
w := httptest.NewRecorder()
@ -181,9 +181,9 @@ func TestProxy_SignRequest(t *testing.T) {
sessionStore: tt.session,
}
r := httptest.NewRequest(http.MethodGet, "/", nil)
state, _ := tt.session.LoadSession(r)
state, _, _ := tt.session.LoadSession(r)
ctx := r.Context()
ctx = sessions.NewContext(ctx, state, tt.ctxError)
ctx = sessions.NewContext(ctx, state, "", tt.ctxError)
r = r.WithContext(ctx)
r.Header.Set("Accept", "application/json")
w := httptest.NewRecorder()

View file

@ -176,7 +176,8 @@ func New(opts config.Options) (*Proxy, error) {
return p, err
}
// UpdateOptions updates internal structures based on config.Options
// UpdateOptions implements the OptionsUpdater interface and updates internal
// structures based on config.Options
func (p *Proxy) UpdateOptions(o config.Options) error {
if p == nil {
return nil
@ -273,12 +274,13 @@ func (p *Proxy) reverseProxyHandler(r *mux.Router, policy config.Policy) (*mux.R
// 4. Retrieve the user session and add it to the request context
rp.Use(sessions.RetrieveSession(p.sessionLoaders...))
// 5. Strip the user session cookie from the downstream request
rp.Use(middleware.StripCookie(p.cookieOptions.Name))
// 6. AuthN - Verify the user is authenticated. Set email, group, & id headers
// 5. AuthN - Verify the user is authenticated. Set email, group, & id headers
rp.Use(p.AuthenticateSession)
// 7. AuthZ - Verify the user is authorized for route
// 6. AuthZ - Verify the user is authorized for route
rp.Use(p.AuthorizeSession)
// 7. Strip the user session cookie from the downstream request
rp.Use(middleware.StripCookie(p.cookieOptions.Name))
// Optional: Add a signed JWT attesting to the user's id, email, and group
if len(p.signingKey) != 0 {
signer, err := jws.NewES256Signer(p.signingKey, policy.Destination.Host)