mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 18:36:30 +02:00
authorize: use opa for policy engine (#474)
Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
parent
111aa8f4d5
commit
2f13488598
45 changed files with 1022 additions and 872 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
51
authorize/evaluator/evaluator.go
Normal file
51
authorize/evaluator/evaluator.go
Normal 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 !
|
||||
}
|
35
authorize/evaluator/mock/mock.go
Normal file
35
authorize/evaluator/mock/mock.go
Normal 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
|
||||
}
|
155
authorize/evaluator/opa/opa.go
Normal file
155
authorize/evaluator/opa/opa.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
87
authorize/evaluator/opa/rego.go
Normal file
87
authorize/evaluator/opa/rego.go
Normal 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)}
|
||||
`
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
|
@ -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},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
5
go.mod
|
@ -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
46
go.sum
|
@ -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
|
@ -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",
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
12
internal/sessions/cache/cache_store.go
vendored
12
internal/sessions/cache/cache_store.go
vendored
|
@ -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.
|
||||
|
|
2
internal/sessions/cache/cache_store_test.go
vendored
2
internal/sessions/cache/cache_store_test.go
vendored
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue