mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-28 18:06:34 +02:00
cache : add cache service (#457)
Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
parent
8a9cb0f803
commit
dccc7cd2ff
46 changed files with 1837 additions and 587 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
*.db
|
||||
.*.yml
|
||||
.*.yaml
|
||||
pem
|
||||
|
|
|
@ -120,7 +120,6 @@ linters-settings:
|
|||
- initClause
|
||||
- methodExprCall
|
||||
- nilValReturn
|
||||
- octalLiteral
|
||||
- offBy1
|
||||
- rangeExprCopy
|
||||
- regexpMust
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/grpc"
|
||||
"github.com/pomerium/pomerium/internal/grpc/cache/client"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/sessions/cache"
|
||||
|
@ -82,6 +84,9 @@ type Authenticate struct {
|
|||
// provider is the interface to interacting with the identity provider (IdP)
|
||||
provider identity.Authenticator
|
||||
|
||||
// cacheClient is the interface for setting and getting sessions from a cache
|
||||
cacheClient client.Cacher
|
||||
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
|
@ -115,7 +120,29 @@ func New(opts config.Options) (*Authenticate, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheStore := cache.NewStore(encryptedEncoder, cookieStore, opts.CookieName)
|
||||
|
||||
cacheConn, err := grpc.NewGRPCClientConn(
|
||||
&grpc.Options{
|
||||
Addr: opts.CacheURL,
|
||||
OverrideCertificateName: opts.OverrideCertificateName,
|
||||
CA: opts.CA,
|
||||
CAFile: opts.CAFile,
|
||||
RequestTimeout: opts.GRPCClientTimeout,
|
||||
ClientDNSRoundRobin: opts.GRPCClientDNSRoundRobin,
|
||||
WithInsecure: opts.GRPCInsecure,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheClient := client.New(cacheConn)
|
||||
|
||||
cacheStore := cache.NewStore(&cache.Options{
|
||||
Cache: cacheClient,
|
||||
Encoder: encryptedEncoder,
|
||||
QueryParam: urlutil.QueryAccessTokenID,
|
||||
WrappedStore: cookieStore})
|
||||
|
||||
qpStore := queryparam.NewStore(encryptedEncoder, "pomerium_programmatic_token")
|
||||
headerStore := header.NewStore(encryptedEncoder, "Pomerium")
|
||||
|
||||
|
@ -152,6 +179,8 @@ func New(opts config.Options) (*Authenticate, error) {
|
|||
sessionLoaders: []sessions.SessionLoader{cacheStore, qpStore, headerStore, cookieStore},
|
||||
// IdP
|
||||
provider: provider,
|
||||
// grpc client for cache
|
||||
cacheClient: cacheClient,
|
||||
|
||||
templates: template.Must(frontend.NewTemplates()),
|
||||
}, nil
|
||||
|
|
|
@ -84,6 +84,9 @@ func TestNew(t *testing.T) {
|
|||
badProvider := newTestOptions(t)
|
||||
badProvider.Provider = ""
|
||||
badProvider.CookieName = "C"
|
||||
badGRPCConn := newTestOptions(t)
|
||||
badGRPCConn.CacheURL = nil
|
||||
badGRPCConn.CookieName = "D"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -96,6 +99,7 @@ func TestNew(t *testing.T) {
|
|||
{"fails to validate", badRedirectURL, true},
|
||||
{"bad cookie name", badCookieName, true},
|
||||
{"bad provider", badProvider, true},
|
||||
{"bad cache url", badGRPCConn, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -150,9 +150,11 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) error {
|
|||
// user impersonation
|
||||
if impersonate := r.FormValue(urlutil.QueryImpersonateAction); impersonate != "" {
|
||||
s.SetImpersonation(r.FormValue(urlutil.QueryImpersonateEmail), r.FormValue(urlutil.QueryImpersonateGroups))
|
||||
if err := a.sessionStore.SaveSession(w, r, s); err != nil {
|
||||
return httputil.NewError(http.StatusBadRequest, err)
|
||||
}
|
||||
}
|
||||
|
||||
// re-persist the session, useful when session was evicted from session
|
||||
if err := a.sessionStore.SaveSession(w, r, s); err != nil {
|
||||
return httputil.NewError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
newSession := s.NewSession(a.RedirectURL.Host, jwtAudience)
|
||||
|
@ -353,7 +355,7 @@ func (a *Authenticate) RefreshAPI(w http.ResponseWriter, r *http.Request) error
|
|||
|
||||
// Refresh is called by the proxy service to handle backend session refresh.
|
||||
//
|
||||
// NOTE: The actual refresh is actually handled as part of the "VerifySession"
|
||||
// NOTE: The actual refresh is handled as part of the "VerifySession"
|
||||
// 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 {
|
||||
|
@ -362,7 +364,7 @@ func (a *Authenticate) Refresh(w http.ResponseWriter, r *http.Request) error {
|
|||
return httputil.NewError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
routeSession := s.NewSession(r.Host, []string{r.Host, r.FormValue("aud")})
|
||||
routeSession := s.NewSession(r.Host, []string{r.Host, r.FormValue(urlutil.QueryAudience)})
|
||||
routeSession.AccessTokenID = s.AccessTokenID
|
||||
|
||||
signedJWT, err := a.sharedEncoder.Marshal(routeSession.RouteSession())
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
//go:generate protoc -I ../proto/authorize --go_out=plugins=grpc:../proto/authorize ../proto/authorize/authorize.proto
|
||||
//go:generate protoc -I ../internal/grpc/authorize/ --go_out=plugins=grpc:../internal/grpc/authorize/ ../internal/grpc/authorize/authorize.proto
|
||||
|
||||
package authorize // import "github.com/pomerium/pomerium/authorize"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/grpc/authorize"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
pb "github.com/pomerium/pomerium/proto/authorize"
|
||||
)
|
||||
|
||||
// Authorize validates the user identity, device, and context of a request for
|
||||
// a given route. Currently only checks identity.
|
||||
func (a *Authorize) Authorize(ctx context.Context, in *pb.Identity) (*pb.AuthorizeReply, error) {
|
||||
func (a *Authorize) Authorize(ctx context.Context, in *authorize.Identity) (*authorize.AuthorizeReply, error) {
|
||||
_, span := trace.StartSpan(ctx, "authorize.grpc.Authorize")
|
||||
defer span.End()
|
||||
|
||||
|
@ -22,11 +22,11 @@ func (a *Authorize) Authorize(ctx context.Context, in *pb.Identity) (*pb.Authori
|
|||
ImpersonateEmail: in.ImpersonateEmail,
|
||||
ImpersonateGroups: in.ImpersonateGroups,
|
||||
})
|
||||
return &pb.AuthorizeReply{IsValid: ok}, nil
|
||||
return &authorize.AuthorizeReply{IsValid: ok}, nil
|
||||
}
|
||||
|
||||
// IsAdmin validates the user is an administrative user.
|
||||
func (a *Authorize) IsAdmin(ctx context.Context, in *pb.Identity) (*pb.IsAdminReply, error) {
|
||||
func (a *Authorize) IsAdmin(ctx context.Context, in *authorize.Identity) (*authorize.IsAdminReply, error) {
|
||||
_, span := trace.StartSpan(ctx, "authorize.grpc.IsAdmin")
|
||||
defer span.End()
|
||||
ok := a.identityAccess.IsAdmin(
|
||||
|
@ -34,5 +34,5 @@ func (a *Authorize) IsAdmin(ctx context.Context, in *pb.Identity) (*pb.IsAdminRe
|
|||
Email: in.Email,
|
||||
Groups: in.Groups,
|
||||
})
|
||||
return &pb.IsAdminReply{IsAdmin: ok}, nil
|
||||
return &authorize.IsAdminReply{IsAdmin: ok}, nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//go:generate protoc -I ../proto/authorize --go_out=plugins=grpc:../proto/authorize ../proto/authorize/authorize.proto
|
||||
|
||||
package authorize
|
||||
|
||||
import (
|
||||
|
@ -7,7 +5,7 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
pb "github.com/pomerium/pomerium/proto/authorize"
|
||||
pb "github.com/pomerium/pomerium/internal/grpc/authorize"
|
||||
)
|
||||
|
||||
func TestAuthorize_Authorize(t *testing.T) {
|
||||
|
|
68
cache/cache.go
vendored
Normal file
68
cache/cache.go
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package cache // import "github.com/pomerium/pomerium/cache"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
stdlog "log"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/kv"
|
||||
"github.com/pomerium/pomerium/internal/kv/autocache"
|
||||
"github.com/pomerium/pomerium/internal/kv/bolt"
|
||||
"github.com/pomerium/pomerium/internal/kv/redis"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
// Cache represents the cache service. The cache service is a simple interface
|
||||
// for storing keyed blobs (bytes) of unstructured data.
|
||||
type Cache struct {
|
||||
cache kv.Store
|
||||
}
|
||||
|
||||
// New creates a new cache service.
|
||||
func New(opts config.Options) (*Cache, error) {
|
||||
cache, err := NewCacheStore(opts.CacheStore, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Cache{
|
||||
cache: cache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewCacheStore creates a new cache store by name and given a set of
|
||||
// configuration options.
|
||||
func NewCacheStore(name string, o *config.Options) (s kv.Store, err error) {
|
||||
switch name {
|
||||
case bolt.Name:
|
||||
s, err = bolt.New(&bolt.Options{Path: o.CacheStorePath})
|
||||
case redis.Name:
|
||||
// todo(bdd): make path configurable in config.Options
|
||||
s, err = redis.New(&redis.Options{
|
||||
Addr: o.CacheStoreAddr,
|
||||
Password: o.CacheStorePassword,
|
||||
})
|
||||
case autocache.Name:
|
||||
acLog := log.Logger.With().Str("service", "autocache").Logger()
|
||||
s, err = autocache.New(&autocache.Options{
|
||||
SharedKey: o.SharedKey,
|
||||
Log: stdlog.New(acLog, "", 0),
|
||||
ClusterDomain: o.CacheURL.Hostname(),
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("cache: unknown store: %s", name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Close shuts down the underlying cache store, services, or both -- if any.
|
||||
func (c *Cache) Close() error {
|
||||
if c.cache == nil {
|
||||
return errors.New("cache: cannot close nil cache")
|
||||
}
|
||||
return c.cache.Close(context.TODO())
|
||||
}
|
34
cache/grpc.go
vendored
Normal file
34
cache/grpc.go
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
//go:generate protoc -I ../internal/grpc/cache/ --go_out=plugins=grpc:../internal/grpc/cache/ ../internal/grpc/cache/cache.proto
|
||||
|
||||
package cache // import "github.com/pomerium/pomerium/cache"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/grpc/cache"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Get retrieves a key the cache store and returns the value, if found.
|
||||
func (c *Cache) Get(ctx context.Context, in *cache.GetRequest) (*cache.GetReply, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "cache.grpc.Get")
|
||||
defer span.End()
|
||||
exists, value, err := c.cache.Get(ctx, in.GetKey())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, "cache.grpc.Get error: %v", err)
|
||||
}
|
||||
return &cache.GetReply{Exists: exists, Value: value}, nil
|
||||
}
|
||||
|
||||
// Set persists a key value pair in the cache store.
|
||||
func (c *Cache) Set(ctx context.Context, in *cache.SetRequest) (*cache.SetReply, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "cache.grpc.Set")
|
||||
defer span.End()
|
||||
err := c.cache.Set(ctx, in.GetKey(), in.GetValue())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, "cache.grpc.Set error: %v", err)
|
||||
}
|
||||
return &cache.SetReply{}, nil
|
||||
}
|
|
@ -13,9 +13,12 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/authenticate"
|
||||
"github.com/pomerium/pomerium/authorize"
|
||||
"github.com/pomerium/pomerium/cache"
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/grpcutil"
|
||||
pgrpc "github.com/pomerium/pomerium/internal/grpc"
|
||||
pbAuthorize "github.com/pomerium/pomerium/internal/grpc/authorize"
|
||||
pbCache "github.com/pomerium/pomerium/internal/grpc/cache"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
|
@ -23,7 +26,6 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/internal/version"
|
||||
pbAuthorize "github.com/pomerium/pomerium/proto/authorize"
|
||||
"github.com/pomerium/pomerium/proxy"
|
||||
)
|
||||
|
||||
|
@ -64,11 +66,19 @@ func run() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authz, err := newAuthorizeService(*opt, &wg)
|
||||
authz, err := newAuthorizeService(*opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cacheSvc, err := newCacheService(*opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cacheSvc != nil {
|
||||
defer cacheSvc.Close()
|
||||
}
|
||||
|
||||
proxy, err := newProxyService(*opt, r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -82,6 +92,10 @@ func run() error {
|
|||
opt = config.HandleConfigUpdate(*configFile, opt, []config.OptionsUpdater{authz, proxy})
|
||||
})
|
||||
|
||||
if err := newGRPCServer(*opt, authz, cacheSvc, &wg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv, err := httputil.NewServer(httpServerOptions(opt), r, &wg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -106,27 +120,43 @@ func newAuthenticateService(opt config.Options, r *mux.Router) (*authenticate.Au
|
|||
return service, nil
|
||||
}
|
||||
|
||||
func newAuthorizeService(opt config.Options, wg *sync.WaitGroup) (*authorize.Authorize, error) {
|
||||
func newAuthorizeService(opt config.Options) (*authorize.Authorize, error) {
|
||||
if !config.IsAuthorize(opt.Services) {
|
||||
return nil, nil
|
||||
}
|
||||
service, err := authorize.New(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return authorize.New(opt)
|
||||
}
|
||||
|
||||
func newCacheService(opt config.Options) (*cache.Cache, error) {
|
||||
if !config.IsCache(opt.Services) {
|
||||
return nil, nil
|
||||
}
|
||||
return cache.New(opt)
|
||||
}
|
||||
|
||||
func newGRPCServer(opt config.Options, as *authorize.Authorize, cs *cache.Cache, wg *sync.WaitGroup) error {
|
||||
if as == nil && cs == nil {
|
||||
return nil
|
||||
}
|
||||
regFn := func(s *grpc.Server) {
|
||||
pbAuthorize.RegisterAuthorizerServer(s, service)
|
||||
}
|
||||
so := &grpcutil.ServerOptions{
|
||||
Addr: opt.GRPCAddr,
|
||||
SharedKey: opt.SharedKey,
|
||||
if as != nil {
|
||||
pbAuthorize.RegisterAuthorizerServer(s, as)
|
||||
}
|
||||
if cs != nil {
|
||||
pbCache.RegisterCacheServer(s, cs)
|
||||
|
||||
}
|
||||
}
|
||||
so := &pgrpc.ServerOptions{Addr: opt.GRPCAddr}
|
||||
if !opt.GRPCInsecure {
|
||||
so.TLSCertificate = opt.TLSCertificate
|
||||
}
|
||||
grpcSrv := grpcutil.NewServer(so, regFn, wg)
|
||||
go grpcutil.Shutdown(grpcSrv)
|
||||
return service, nil
|
||||
grpcSrv, err := pgrpc.NewServer(so, regFn, wg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go pgrpc.Shutdown(grpcSrv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newProxyService(opt config.Options, r *mux.Router) (*proxy.Proxy, error) {
|
||||
|
|
|
@ -277,6 +277,51 @@ func Test_run(t *testing.T) {
|
|||
"policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }]
|
||||
}
|
||||
`, true},
|
||||
// {"simple cache", false, `
|
||||
// {
|
||||
// "address": ":9433",
|
||||
// "grpc_address": ":9444",
|
||||
// "grpc_insecure": false,
|
||||
// "insecure_server": true,
|
||||
// "cache_service_url": "https://authorize.corp.example",
|
||||
// "authenticate_service_url": "https://authenticate.corp.example",
|
||||
// "shared_secret": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=",
|
||||
// "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=",
|
||||
// "services": "cache",
|
||||
// "cache_store": "bolt",
|
||||
// "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }]
|
||||
// }
|
||||
// `, false},
|
||||
// {"malformed cache", false, `
|
||||
// {
|
||||
// "address": ":9433",
|
||||
// "grpc_address": ":9444",
|
||||
// "grpc_insecure": false,
|
||||
// "insecure_server": true,
|
||||
// "cache_service_url": "https://authorize.corp.example",
|
||||
// "authenticate_service_url": "https://authenticate.corp.example",
|
||||
// "shared_secret": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=",
|
||||
// "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=",
|
||||
// "services": "cache",
|
||||
// "cache_store": "bad bolt",
|
||||
// "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }]
|
||||
// }
|
||||
// `, true},
|
||||
// {"bad cache port", false, `
|
||||
// {
|
||||
// "address": ":9433",
|
||||
// "grpc_address": ":9999999",
|
||||
// "grpc_insecure": false,
|
||||
// "insecure_server": true,
|
||||
// "cache_service_url": "https://authorize.corp.example",
|
||||
// "authenticate_service_url": "https://authenticate.corp.example",
|
||||
// "shared_secret": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=",
|
||||
// "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=",
|
||||
// "services": "cache",
|
||||
// "cache_store": "bolt",
|
||||
// "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }]
|
||||
// }
|
||||
// `, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -9,6 +9,8 @@ const (
|
|||
ServiceAuthorize = "authorize"
|
||||
// ServiceAuthenticate represents running the authenticate service component
|
||||
ServiceAuthenticate = "authenticate"
|
||||
// ServiceCache represents running the cache service component
|
||||
ServiceCache = "cache"
|
||||
)
|
||||
|
||||
// IsValidService checks to see if a service is a valid service mode
|
||||
|
@ -16,9 +18,10 @@ func IsValidService(s string) bool {
|
|||
switch s {
|
||||
case
|
||||
ServiceAll,
|
||||
ServiceProxy,
|
||||
ServiceAuthenticate,
|
||||
ServiceAuthorize,
|
||||
ServiceAuthenticate:
|
||||
ServiceCache,
|
||||
ServiceProxy:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -57,6 +60,17 @@ func IsProxy(s string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IsCache checks to see if we should be running the proxy service
|
||||
func IsCache(s string) bool {
|
||||
switch s {
|
||||
case
|
||||
ServiceAll,
|
||||
ServiceCache:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAll checks to see if we should be running all services
|
||||
func IsAll(s string) bool {
|
||||
return s == ServiceAll
|
||||
|
|
|
@ -16,6 +16,7 @@ func Test_isValidService(t *testing.T) {
|
|||
{"authenticate bad case", "AuThenticate", false},
|
||||
{"authorize implemented", "authorize", true},
|
||||
{"jiberish", "xd23", false},
|
||||
{"cache", "cache", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -27,6 +28,7 @@ func Test_isValidService(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_isAuthenticate(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
service string
|
||||
|
@ -50,6 +52,7 @@ func Test_isAuthenticate(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_isAuthorize(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
service string
|
||||
|
@ -92,3 +95,27 @@ func Test_IsProxy(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
service string
|
||||
want bool
|
||||
}{
|
||||
{"proxy", "proxy", false},
|
||||
{"all", "all", true},
|
||||
{"authorize", "authorize", false},
|
||||
{"proxy bad case", "PrOxY", false},
|
||||
{"jiberish", "xd23", false},
|
||||
{"cache", "cache", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsCache(tt.service); got != tt.want {
|
||||
t.Errorf("IsCache() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,6 +174,25 @@ type Options struct {
|
|||
ForwardAuthURLString string `mapstructure:"forward_auth_url" yaml:"forward_auth_url,omitempty"`
|
||||
ForwardAuthURL *url.URL `yaml:",omitempty"`
|
||||
|
||||
// CacheStore is the name of session cache backend to use.
|
||||
// Options are : "bolt", "redis", and "autocache".
|
||||
// Default is "autocache".
|
||||
CacheStore string `mapstructure:"cache_store" yaml:"cache_store,omitempty"`
|
||||
|
||||
// CacheURL is the routable destination of the cache service's
|
||||
// gRPC endpoint. NOTE: As many load balancers do not support
|
||||
// externally routed gRPC so this may be an internal location.
|
||||
CacheURLString string `mapstructure:"cache_service_url" yaml:"cache_service_url,omitempty"`
|
||||
CacheURL *url.URL `yaml:",omitempty"`
|
||||
|
||||
// CacheStoreAddr specifies the host and port on which the cache store
|
||||
// should connect to. e.g. (localhost:6379)
|
||||
CacheStoreAddr string `mapstructure:"cache_store_address" yaml:"cache_store_address,omitempty"`
|
||||
// CacheStorePassword is the password used to connect to the cache store.
|
||||
CacheStorePassword string `mapstructure:"cache_store_password" yaml:"cache_store_password,omitempty"`
|
||||
// CacheStorePath is the path to use for a given cache store. e.g. /etc/bolt.db
|
||||
CacheStorePath string `mapstructure:"cache_store_path" yaml:"cache_store_path,omitempty"`
|
||||
|
||||
viper *viper.Viper
|
||||
}
|
||||
|
||||
|
@ -201,6 +220,7 @@ var defaultOptions = Options{
|
|||
GRPCAddr: ":443",
|
||||
GRPCClientTimeout: 10 * time.Second, // Try to withstand transient service failures for a single request
|
||||
GRPCClientDNSRoundRobin: true,
|
||||
CacheStore: "autocache",
|
||||
}
|
||||
|
||||
// NewDefaultOptions returns a copy the default options. It's the caller's
|
||||
|
@ -397,9 +417,12 @@ func (o *Options) Validate() error {
|
|||
if o.AuthorizeURLString == "" {
|
||||
o.AuthorizeURLString = "https://localhost" + DefaultAlternativeAddr
|
||||
}
|
||||
if o.CacheURLString == "" {
|
||||
o.CacheURLString = "https://localhost" + DefaultAlternativeAddr
|
||||
}
|
||||
}
|
||||
|
||||
if IsAuthorize(o.Services) {
|
||||
if IsAuthorize(o.Services) || IsCache(o.Services) {
|
||||
// if authorize is set, we don't really need a http server
|
||||
// but we'll still set one up incase the user wants to use
|
||||
// the HTTP health check api
|
||||
|
@ -433,6 +456,14 @@ func (o *Options) Validate() error {
|
|||
o.AuthorizeURL = u
|
||||
}
|
||||
|
||||
if o.CacheURLString != "" {
|
||||
u, err := urlutil.ParseAndValidateURL(o.CacheURLString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: bad cache service url %s : %w", o.CacheURLString, err)
|
||||
}
|
||||
o.CacheURL = u
|
||||
}
|
||||
|
||||
if o.ForwardAuthURLString != "" {
|
||||
u, err := urlutil.ParseAndValidateURL(o.ForwardAuthURLString)
|
||||
if err != nil {
|
||||
|
|
|
@ -205,7 +205,7 @@ func Test_Checksum(t *testing.T) {
|
|||
func TestOptionsFromViper(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := []cmp.Option{
|
||||
cmpopts.IgnoreFields(Options{}, "CookieSecret", "GRPCInsecure", "GRPCAddr", "AuthorizeURL", "AuthorizeURLString", "DefaultUpstreamTimeout", "CookieExpire", "Services", "Addr", "RefreshCooldown", "LogLevel", "KeyFile", "CertFile", "SharedKey", "ReadTimeout", "ReadHeaderTimeout", "IdleTimeout", "GRPCClientTimeout", "GRPCClientDNSRoundRobin"),
|
||||
cmpopts.IgnoreFields(Options{}, "CacheStore", "CookieSecret", "GRPCInsecure", "GRPCAddr", "CacheURLString", "CacheURL", "AuthorizeURL", "AuthorizeURLString", "DefaultUpstreamTimeout", "CookieExpire", "Services", "Addr", "RefreshCooldown", "LogLevel", "KeyFile", "CertFile", "SharedKey", "ReadTimeout", "ReadHeaderTimeout", "IdleTimeout", "GRPCClientTimeout", "GRPCClientDNSRoundRobin"),
|
||||
cmpopts.IgnoreFields(Policy{}, "Source", "Destination"),
|
||||
cmpOptIgnoreUnexported,
|
||||
}
|
||||
|
@ -280,6 +280,9 @@ func Test_NewOptionsFromConfigEnvVar(t *testing.T) {
|
|||
{"bad no certs no insecure mode set", map[string]string{"SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
||||
{"good disable headers ", map[string]string{"HEADERS": "disable:true", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
||||
{"bad whitespace in secret", map[string]string{"INSECURE_SERVER": "true", "SERVICES": "authenticate", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=\n"}, true},
|
||||
{"bad cache url", map[string]string{"CACHE_SERVICE_URL": "cache.example", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
||||
{"bad forward auth url", map[string]string{"FORWARD_AUTH_URL": "cache.example", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, true},
|
||||
{"same addr and grpc addr", map[string]string{"SERVICES": "cache", "ADDRESS": "0", "GRPC_ADDRESS": "0", "INSECURE_SERVER": "true", "SHARED_SECRET": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM="}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -8,13 +8,17 @@ require (
|
|||
contrib.go.opencensus.io/exporter/prometheus v0.1.0
|
||||
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-20191227052852-215e87163ea7
|
||||
github.com/golang/mock v1.3.1
|
||||
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/pelletier/go-toml v1.6.0 // indirect
|
||||
github.com/pomerium/autocache v0.0.0-20200117194553-700b15fea434
|
||||
github.com/pomerium/csrf v1.6.2-0.20190918035251-f3318380bad3
|
||||
github.com/pomerium/go-oidc v2.0.0+incompatible
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
|
@ -28,15 +32,15 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.6.2
|
||||
github.com/uber/jaeger-client-go v2.20.1+incompatible // indirect
|
||||
go.etcd.io/bbolt v1.3.2
|
||||
go.opencensus.io v0.22.2
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
|
||||
golang.org/x/net v0.0.0-20191125084936-ffdde1057850 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e // indirect
|
||||
google.golang.org/api v0.14.0
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect
|
||||
google.golang.org/grpc v1.26.0
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.4.1
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
)
|
||||
|
|
97
go.sum
97
go.sum
|
@ -20,17 +20,24 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
|||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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/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=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU=
|
||||
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
|
@ -38,6 +45,8 @@ github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReG
|
|||
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
|
@ -52,6 +61,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
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 v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
@ -60,6 +70,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
|
@ -82,12 +94,14 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
|
@ -108,11 +122,34 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo
|
|||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=
|
||||
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/memberlist v0.1.5 h1:AYBsgJOW9gab/toO5tEB8lWetVgDKZycqkebJ8xxpqM=
|
||||
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/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=
|
||||
|
@ -131,8 +168,15 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/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/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=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
|
@ -143,6 +187,14 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
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/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/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=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
|
@ -152,10 +204,13 @@ 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/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-20200117194553-700b15fea434 h1:gC7WWP2nvqkLKP8nkeyLdwUx97SBSHGLahivmB4tXIY=
|
||||
github.com/pomerium/autocache v0.0.0-20200117194553-700b15fea434/go.mod h1:6eBAk305Ex3fI8KjDC0+5SpZUnPE09uQNosrUQxcOaE=
|
||||
github.com/pomerium/csrf v1.6.2-0.20190918035251-f3318380bad3 h1:FmzFXnCAepHZwl6QPhTFqBHcbcGevdiEQjutK+M5bj4=
|
||||
github.com/pomerium/csrf v1.6.2-0.20190918035251-f3318380bad3/go.mod h1:UE2U4JOsjXNeq+MX/lqhZpUFsNAxbXERuYsWK2iULh0=
|
||||
github.com/pomerium/go-oidc v2.0.0+incompatible h1:gVvG/ExWsHQqatV+uceROnGmbVYF44mDNx5nayBhC0o=
|
||||
github.com/pomerium/go-oidc v2.0.0+incompatible/go.mod h1:DRsGVw6MOgxbfq4Y57jKOE8lbEfayxeiY0A8/4vxjBM=
|
||||
github.com/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.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
|
@ -195,6 +250,9 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.17.2 h1:RMRHFw2+wF7LO0QqtELQwo8hqSmqISyCJeFeAAuWcRo=
|
||||
github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
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.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
|
@ -217,8 +275,7 @@ 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=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
|
||||
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
|
||||
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -229,6 +286,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/uber/jaeger-client-go v2.15.0+incompatible h1:NP3qsSqNxh8VYr956ur1N/1C1PjvOJnJykCzcD5QHbk=
|
||||
github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.20.1+incompatible h1:HgqpYBng0n7tLJIlyT4kPCIv5XgCsF+kai1NnnrJzEU=
|
||||
|
@ -237,6 +295,7 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
|
|||
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/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=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
|
@ -247,13 +306,11 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
|
||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -274,8 +331,11 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
|||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -290,15 +350,15 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0O
|
|||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191125084936-ffdde1057850 h1:Vq85/r8R9IdcUHmZ0/nQlUg1v15rzvQ2sHdnZAj/x7s=
|
||||
golang.org/x/net v0.0.0-20191125084936-ffdde1057850/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -309,8 +369,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha
|
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
|
@ -322,9 +385,10 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c h1:gUYreENmqtjZb2brVfUas1sC6UivSY8XwKwPo8tloLs=
|
||||
golang.org/x/sys v0.0.0-20200117145432-59e60aa80a0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -352,7 +416,10 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
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=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
@ -390,12 +457,18 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package clients // import "github.com/pomerium/pomerium/proxy/clients"
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
pb "github.com/pomerium/pomerium/internal/grpc/authorize"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
pb "github.com/pomerium/pomerium/proto/authorize"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
@ -22,38 +22,27 @@ type Authorizer interface {
|
|||
Close() error
|
||||
}
|
||||
|
||||
// NewAuthorizeClient returns a new authorize service client.
|
||||
func NewAuthorizeClient(name string, opts *Options) (a Authorizer, err error) {
|
||||
// Only gRPC is supported and is always returned so name is ignored
|
||||
return NewGRPCAuthorizeClient(opts)
|
||||
}
|
||||
|
||||
// NewGRPCAuthorizeClient returns a new authorize service client.
|
||||
func NewGRPCAuthorizeClient(opts *Options) (p *AuthorizeGRPC, err error) {
|
||||
conn, err := NewGRPCClientConn(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := pb.NewAuthorizerClient(conn)
|
||||
return &AuthorizeGRPC{Conn: conn, client: client}, nil
|
||||
}
|
||||
|
||||
// AuthorizeGRPC is a gRPC implementation of an authenticator (authorize client)
|
||||
type AuthorizeGRPC struct {
|
||||
// Client is a gRPC implementation of an authenticator (authorize client)
|
||||
type Client struct {
|
||||
Conn *grpc.ClientConn
|
||||
client pb.AuthorizerClient
|
||||
}
|
||||
|
||||
// New returns a new authorize service client.
|
||||
func New(conn *grpc.ClientConn) (p *Client, err error) {
|
||||
return &Client{Conn: conn, client: pb.NewAuthorizerClient(conn)}, nil
|
||||
}
|
||||
|
||||
// Authorize takes a route and user session and returns whether the
|
||||
// request is valid per access policy
|
||||
func (a *AuthorizeGRPC) Authorize(ctx context.Context, route string, s *sessions.State) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "proxy.client.grpc.Authorize")
|
||||
func (c *Client) Authorize(ctx context.Context, route string, s *sessions.State) (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 := a.client.Authorize(ctx, &pb.Identity{
|
||||
response, err := c.client.Authorize(ctx, &pb.Identity{
|
||||
Route: route,
|
||||
User: s.User,
|
||||
Email: s.Email,
|
||||
|
@ -65,18 +54,18 @@ func (a *AuthorizeGRPC) Authorize(ctx context.Context, route string, s *sessions
|
|||
}
|
||||
|
||||
// IsAdmin takes a session and returns whether the user is an administrator
|
||||
func (a *AuthorizeGRPC) IsAdmin(ctx context.Context, s *sessions.State) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "proxy.client.grpc.IsAdmin")
|
||||
func (c *Client) IsAdmin(ctx context.Context, s *sessions.State) (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 := a.client.IsAdmin(ctx, &pb.Identity{Email: s.Email, Groups: s.Groups})
|
||||
response, err := c.client.IsAdmin(ctx, &pb.Identity{Email: s.Email, Groups: s.Groups})
|
||||
return response.GetIsAdmin(), err
|
||||
}
|
||||
|
||||
// Close tears down the ClientConn and all underlying connections.
|
||||
func (a *AuthorizeGRPC) Close() error {
|
||||
return a.Conn.Close()
|
||||
func (c *Client) Close() error {
|
||||
return c.Conn.Close()
|
||||
}
|
78
internal/grpc/authorize/client/authorize_client_test.go
Normal file
78
internal/grpc/authorize/client/authorize_client_test.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package clients // import "github.com/pomerium/pomerium/proxy/clients"
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -6,6 +6,8 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
)
|
||||
|
||||
var _ Authorizer = &MockAuthorize{}
|
||||
|
||||
// MockAuthorize provides a mocked implementation of the authorizer interface.
|
||||
type MockAuthorize struct {
|
||||
AuthorizeResponse bool
|
|
@ -9,7 +9,7 @@ import (
|
|||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
authorize "github.com/pomerium/pomerium/proto/authorize"
|
||||
authorize "github.com/pomerium/pomerium/internal/grpc/authorize"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
329
internal/grpc/cache/cache.pb.go
vendored
Normal file
329
internal/grpc/cache/cache.pb.go
vendored
Normal file
|
@ -0,0 +1,329 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: cache.proto
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type GetRequest struct {
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetRequest) Reset() { *m = GetRequest{} }
|
||||
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetRequest) ProtoMessage() {}
|
||||
func (*GetRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_5fca3b110c9bbf3a, []int{0}
|
||||
}
|
||||
|
||||
func (m *GetRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetRequest.Merge(m, src)
|
||||
}
|
||||
func (m *GetRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_GetRequest.Size(m)
|
||||
}
|
||||
func (m *GetRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *GetRequest) GetKey() string {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetReply struct {
|
||||
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"`
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetReply) Reset() { *m = GetReply{} }
|
||||
func (m *GetReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetReply) ProtoMessage() {}
|
||||
func (*GetReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_5fca3b110c9bbf3a, []int{1}
|
||||
}
|
||||
|
||||
func (m *GetReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetReply.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetReply.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetReply) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetReply.Merge(m, src)
|
||||
}
|
||||
func (m *GetReply) XXX_Size() int {
|
||||
return xxx_messageInfo_GetReply.Size(m)
|
||||
}
|
||||
func (m *GetReply) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetReply.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetReply proto.InternalMessageInfo
|
||||
|
||||
func (m *GetReply) GetExists() bool {
|
||||
if m != nil {
|
||||
return m.Exists
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *GetReply) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SetRequest struct {
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SetRequest) Reset() { *m = SetRequest{} }
|
||||
func (m *SetRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*SetRequest) ProtoMessage() {}
|
||||
func (*SetRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_5fca3b110c9bbf3a, []int{2}
|
||||
}
|
||||
|
||||
func (m *SetRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_SetRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *SetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_SetRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *SetRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_SetRequest.Merge(m, src)
|
||||
}
|
||||
func (m *SetRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_SetRequest.Size(m)
|
||||
}
|
||||
func (m *SetRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_SetRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_SetRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *SetRequest) GetKey() string {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *SetRequest) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SetReply struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SetReply) Reset() { *m = SetReply{} }
|
||||
func (m *SetReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*SetReply) ProtoMessage() {}
|
||||
func (*SetReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_5fca3b110c9bbf3a, []int{3}
|
||||
}
|
||||
|
||||
func (m *SetReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_SetReply.Unmarshal(m, b)
|
||||
}
|
||||
func (m *SetReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_SetReply.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *SetReply) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_SetReply.Merge(m, src)
|
||||
}
|
||||
func (m *SetReply) XXX_Size() int {
|
||||
return xxx_messageInfo_SetReply.Size(m)
|
||||
}
|
||||
func (m *SetReply) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_SetReply.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_SetReply proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*GetRequest)(nil), "cache.GetRequest")
|
||||
proto.RegisterType((*GetReply)(nil), "cache.GetReply")
|
||||
proto.RegisterType((*SetRequest)(nil), "cache.SetRequest")
|
||||
proto.RegisterType((*SetReply)(nil), "cache.SetReply")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("cache.proto", fileDescriptor_5fca3b110c9bbf3a) }
|
||||
|
||||
var fileDescriptor_5fca3b110c9bbf3a = []byte{
|
||||
// 176 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4e, 0x4e, 0x4c, 0xce,
|
||||
0x48, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0xe4, 0xb8, 0xb8, 0xdc,
|
||||
0x53, 0x4b, 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x04, 0xb8, 0x98, 0xb3, 0x53, 0x2b,
|
||||
0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x40, 0x4c, 0x25, 0x0b, 0x2e, 0x0e, 0xb0, 0x7c, 0x41,
|
||||
0x4e, 0xa5, 0x90, 0x18, 0x17, 0x5b, 0x6a, 0x45, 0x66, 0x71, 0x49, 0x31, 0x58, 0x01, 0x47, 0x10,
|
||||
0x94, 0x27, 0x24, 0xc2, 0xc5, 0x5a, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1,
|
||||
0x13, 0x04, 0xe1, 0x28, 0x99, 0x70, 0x71, 0x05, 0xe3, 0x31, 0x19, 0x87, 0x2e, 0x2e, 0x2e, 0x8e,
|
||||
0x60, 0xa8, 0x7d, 0x46, 0x89, 0x5c, 0xac, 0xce, 0x20, 0x47, 0x0a, 0x69, 0x73, 0x31, 0xbb, 0xa7,
|
||||
0x96, 0x08, 0x09, 0xea, 0x41, 0x3c, 0x80, 0x70, 0xb0, 0x14, 0x3f, 0xb2, 0x50, 0x41, 0x4e, 0xa5,
|
||||
0x12, 0x03, 0x48, 0x71, 0x30, 0x92, 0xe2, 0x60, 0x4c, 0xc5, 0xc1, 0x70, 0xc5, 0x49, 0x6c, 0xe0,
|
||||
0xc0, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xef, 0x5f, 0x9e, 0x1b, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// CacheClient is the client API for Cache service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type CacheClient interface {
|
||||
Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetReply, error)
|
||||
Set(ctx context.Context, in *SetRequest, opts ...grpc.CallOption) (*SetReply, error)
|
||||
}
|
||||
|
||||
type cacheClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewCacheClient(cc *grpc.ClientConn) CacheClient {
|
||||
return &cacheClient{cc}
|
||||
}
|
||||
|
||||
func (c *cacheClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetReply, error) {
|
||||
out := new(GetReply)
|
||||
err := c.cc.Invoke(ctx, "/cache.Cache/Get", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *cacheClient) Set(ctx context.Context, in *SetRequest, opts ...grpc.CallOption) (*SetReply, error) {
|
||||
out := new(SetReply)
|
||||
err := c.cc.Invoke(ctx, "/cache.Cache/Set", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CacheServer is the server API for Cache service.
|
||||
type CacheServer interface {
|
||||
Get(context.Context, *GetRequest) (*GetReply, error)
|
||||
Set(context.Context, *SetRequest) (*SetReply, error)
|
||||
}
|
||||
|
||||
// UnimplementedCacheServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedCacheServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedCacheServer) Get(ctx context.Context, req *GetRequest) (*GetReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
|
||||
}
|
||||
func (*UnimplementedCacheServer) Set(ctx context.Context, req *SetRequest) (*SetReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Set not implemented")
|
||||
}
|
||||
|
||||
func RegisterCacheServer(s *grpc.Server, srv CacheServer) {
|
||||
s.RegisterService(&_Cache_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Cache_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CacheServer).Get(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/cache.Cache/Get",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CacheServer).Get(ctx, req.(*GetRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cache_Set_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SetRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CacheServer).Set(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/cache.Cache/Set",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CacheServer).Set(ctx, req.(*SetRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Cache_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "cache.Cache",
|
||||
HandlerType: (*CacheServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Get",
|
||||
Handler: _Cache_Get_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Set",
|
||||
Handler: _Cache_Set_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "cache.proto",
|
||||
}
|
20
internal/grpc/cache/cache.proto
vendored
Normal file
20
internal/grpc/cache/cache.proto
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package cache;
|
||||
|
||||
service Cache {
|
||||
rpc Get(GetRequest) returns (GetReply) {}
|
||||
rpc Set(SetRequest) returns (SetReply) {}
|
||||
}
|
||||
|
||||
message GetRequest { string key = 1; }
|
||||
message GetReply {
|
||||
bool exists = 1;
|
||||
bytes value = 2;
|
||||
}
|
||||
|
||||
message SetRequest {
|
||||
string key = 1;
|
||||
bytes value = 2;
|
||||
}
|
||||
message SetReply {}
|
57
internal/grpc/cache/client/cache_client.go
vendored
Normal file
57
internal/grpc/cache/client/cache_client.go
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/grpc/cache"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Cacher specifies an interface for remote clients connecting to the cache service.
|
||||
type Cacher interface {
|
||||
Get(ctx context.Context, key string) (keyExists bool, value []byte, err error)
|
||||
Set(ctx context.Context, key string, value []byte) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Client represents a gRPC cache service client.
|
||||
type Client struct {
|
||||
conn *grpc.ClientConn
|
||||
client cache.CacheClient
|
||||
}
|
||||
|
||||
// New returns a new gRPC cache service client.
|
||||
func New(conn *grpc.ClientConn) (p *Client) {
|
||||
return &Client{conn: conn, client: cache.NewCacheClient(conn)}
|
||||
}
|
||||
|
||||
// Get retrieves a value from the cache service.
|
||||
func (a *Client) Get(ctx context.Context, key string) (keyExists bool, value []byte, err error) {
|
||||
ctx, span := trace.StartSpan(ctx, "grpc.cache.client.Get")
|
||||
defer span.End()
|
||||
|
||||
response, err := a.client.Get(ctx, &cache.GetRequest{Key: key})
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return response.GetExists(), response.GetValue(), nil
|
||||
}
|
||||
|
||||
// Set stores a key value pair in the cache service.
|
||||
func (a *Client) Set(ctx context.Context, key string, value []byte) error {
|
||||
ctx, span := trace.StartSpan(ctx, "grpc.cache.client.Set")
|
||||
defer span.End()
|
||||
|
||||
_, err := a.client.Set(ctx, &cache.SetRequest{Key: key, Value: value})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close tears down the ClientConn and all underlying connections.
|
||||
func (a *Client) Close() error {
|
||||
return a.conn.Close()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package clients // import "github.com/pomerium/pomerium/proxy/clients"
|
||||
package grpc // import "github.com/pomerium/pomerium/internal/grpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -12,7 +12,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/grpcutil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/metrics"
|
||||
|
||||
|
@ -28,15 +27,10 @@ const defaultGRPCPort = 443
|
|||
type Options struct {
|
||||
// Addr is the location of the service. e.g. "service.corp.example:8443"
|
||||
Addr *url.URL
|
||||
// InternalAddr is the internal (behind the ingress) address to use when
|
||||
// making a connection. If empty, Addr is used.
|
||||
InternalAddr *url.URL
|
||||
// OverrideCertificateName overrides the server name used to verify the hostname on the
|
||||
// returned certificates from the server. gRPC internals also use it to override the virtual
|
||||
// returned certificates from the server. gRPC internals also use it to override the virtual
|
||||
// hosting name if it is set.
|
||||
OverrideCertificateName string
|
||||
// Shared secret is used to mutually authenticate a client and server.
|
||||
SharedSecret string
|
||||
// CA specifies the base64 encoded TLS certificate authority to use.
|
||||
CA string
|
||||
// CAFile specifies the TLS certificate authority file to use.
|
||||
|
@ -46,46 +40,35 @@ type Options struct {
|
|||
// ClientDNSRoundRobin enables or disables DNS resolver based load balancing
|
||||
ClientDNSRoundRobin bool
|
||||
|
||||
// WithInsecure disables transport security for this ClientConn.
|
||||
// WithInsecure disables transport security for this ClientConn.
|
||||
// Note that transport security is required unless WithInsecure is set.
|
||||
WithInsecure bool
|
||||
}
|
||||
|
||||
// NewGRPCClientConn returns a new gRPC pomerium service client connection.
|
||||
func NewGRPCClientConn(opts *Options) (*grpc.ClientConn, error) {
|
||||
// gRPC uses a pre-shared secret middleware to establish authentication b/w server and client
|
||||
if opts.SharedSecret == "" {
|
||||
return nil, errors.New("proxy/clients: grpc client requires shared secret")
|
||||
if opts.Addr == nil {
|
||||
return nil, errors.New("internal/grpc: connection address required")
|
||||
}
|
||||
if opts.InternalAddr == nil && opts.Addr == nil {
|
||||
return nil, errors.New("proxy/clients: connection address required")
|
||||
connAddr := opts.Addr.Host
|
||||
|
||||
}
|
||||
|
||||
var connAddr string
|
||||
if opts.InternalAddr != nil {
|
||||
connAddr = opts.InternalAddr.Host
|
||||
} else {
|
||||
connAddr = opts.Addr.Host
|
||||
}
|
||||
// no colon exists in the connection string, assume one must be added manually
|
||||
if !strings.Contains(connAddr, ":") {
|
||||
connAddr = fmt.Sprintf("%s:%d", connAddr, defaultGRPCPort)
|
||||
}
|
||||
dialOptions := []grpc.DialOption{
|
||||
grpc.WithPerRPCCredentials(grpcutil.NewSharedSecretCred(opts.SharedSecret)),
|
||||
grpc.WithChainUnaryInterceptor(metrics.GRPCClientInterceptor("proxy"), grpcTimeoutInterceptor(opts.RequestTimeout)),
|
||||
grpc.WithChainUnaryInterceptor(metrics.GRPCClientInterceptor(connAddr), grpcTimeoutInterceptor(opts.RequestTimeout)),
|
||||
grpc.WithStatsHandler(&ocgrpc.ClientHandler{}),
|
||||
grpc.WithDefaultCallOptions([]grpc.CallOption{grpc.WaitForReady(true)}...),
|
||||
}
|
||||
|
||||
if opts.WithInsecure {
|
||||
log.Info().Str("addr", connAddr).Msg("proxy/clients: grpc with insecure")
|
||||
log.Info().Str("addr", connAddr).Msg("internal/grpc: grpc with insecure")
|
||||
dialOptions = append(dialOptions, grpc.WithInsecure())
|
||||
} else {
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
log.Warn().Msg("proxy/clients: failed getting system cert pool making new one")
|
||||
log.Warn().Msg("internal/grpc: failed getting system cert pool making new one")
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
if opts.CA != "" || opts.CAFile != "" {
|
||||
|
@ -105,14 +88,14 @@ func NewGRPCClientConn(opts *Options) (*grpc.ClientConn, error) {
|
|||
if ok := rootCAs.AppendCertsFromPEM(ca); !ok {
|
||||
return nil, fmt.Errorf("failed to append CA cert to certPool")
|
||||
}
|
||||
log.Debug().Msg("proxy/clients: added custom certificate authority")
|
||||
log.Debug().Msg("internal/grpc: added custom certificate authority")
|
||||
}
|
||||
|
||||
cert := credentials.NewTLS(&tls.Config{RootCAs: rootCAs})
|
||||
|
||||
// override allowed certificate name string, typically used when doing behind ingress connection
|
||||
if opts.OverrideCertificateName != "" {
|
||||
log.Debug().Str("cert-override-name", opts.OverrideCertificateName).Msg("proxy/clients: grpc")
|
||||
log.Debug().Str("cert-override-name", opts.OverrideCertificateName).Msg("internal/grpc: grpc")
|
||||
err := cert.OverrideServerName(opts.OverrideCertificateName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -120,7 +103,6 @@ func NewGRPCClientConn(opts *Options) (*grpc.ClientConn, error) {
|
|||
}
|
||||
// finally add our credential
|
||||
dialOptions = append(dialOptions, grpc.WithTransportCredentials(cert))
|
||||
|
||||
}
|
||||
|
||||
if opts.ClientDNSRoundRobin {
|
||||
|
@ -139,10 +121,8 @@ func grpcTimeoutInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
|
|||
if timeout <= 0 {
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
|
||||
}
|
||||
}
|
78
internal/grpc/client_test.go
Normal file
78
internal/grpc/client_test.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package grpc // import "github.com/pomerium/pomerium/internal/grpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func Test_grpcTimeoutInterceptor(t *testing.T) {
|
||||
|
||||
mockInvoker := func(sleepTime time.Duration, wantFail bool) grpc.UnaryInvoker {
|
||||
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
|
||||
time.Sleep(sleepTime)
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
t.Fatal("No deadline set")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if ok && now.After(deadline) && !wantFail {
|
||||
t.Errorf("Deadline exceeded, but should not have. now=%v, deadline=%v", now, deadline)
|
||||
} else if now.Before(deadline) && wantFail {
|
||||
t.Errorf("Deadline not exceeded, but should have. now=%v, deadline=%v", now, deadline)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
timeOut := 300 * time.Millisecond
|
||||
to := grpcTimeoutInterceptor(timeOut)
|
||||
|
||||
to(context.Background(), "test", nil, nil, nil, mockInvoker(timeOut*2, true))
|
||||
to(context.Background(), "test", nil, nil, nil, mockInvoker(timeOut/2, false))
|
||||
|
||||
}
|
||||
|
||||
func TestNewGRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *Options
|
||||
wantErr bool
|
||||
wantErrStr string
|
||||
wantTarget string
|
||||
}{
|
||||
{"empty connection", &Options{Addr: nil}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"both internal and addr empty", &Options{Addr: nil}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"addr with port", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}}, false, "", "localhost.example:8443"},
|
||||
{"addr without port", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example"}}, false, "", "localhost.example:443"},
|
||||
{"cert override", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:443"}, OverrideCertificateName: "*.local"}, false, "", "localhost.example:443"},
|
||||
{"custom ca", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:443"}, OverrideCertificateName: "*.local", CA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFVENDQWZrQ0ZBWHhneFg5K0hjWlBVVVBEK0laV0NGNUEvVTdNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1FVXgKQ3pBSkJnTlZCQVlUQWtGVk1STXdFUVlEVlFRSURBcFRiMjFsTFZOMFlYUmxNU0V3SHdZRFZRUUtEQmhKYm5SbApjbTVsZENCWGFXUm5hWFJ6SUZCMGVTQk1kR1F3SGhjTk1Ua3dNakk0TVRnMU1EQTNXaGNOTWprd01qSTFNVGcxCk1EQTNXakJGTVFzd0NRWURWUVFHRXdKQlZURVRNQkVHQTFVRUNBd0tVMjl0WlMxVGRHRjBaVEVoTUI4R0ExVUUKQ2d3WVNXNTBaWEp1WlhRZ1YybGtaMmwwY3lCUWRIa2dUSFJrTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQwpBUThBTUlJQkNnS0NBUUVBOVRFMEFiaTdnMHhYeURkVUtEbDViNTBCT05ZVVVSc3F2THQrSWkwdlpjMzRRTHhOClJrT0hrOFZEVUgzcUt1N2UrNGVubUdLVVNUdzRPNFlkQktiSWRJTFpnb3o0YitNL3FVOG5adVpiN2pBVTdOYWkKajMzVDVrbXB3L2d4WHNNUzNzdUpXUE1EUDB3Z1BUZUVRK2J1bUxVWmpLdUVIaWNTL0l5dmtaVlBzRlE4NWlaUwpkNXE2a0ZGUUdjWnFXeFg0dlhDV25Sd3E3cHY3TThJd1RYc1pYSVRuNXB5Z3VTczNKb29GQkg5U3ZNTjRKU25GCmJMK0t6ekduMy9ScXFrTXpMN3FUdkMrNWxVT3UxUmNES21mZXBuVGVaN1IyVnJUQm42NndWMjVHRnBkSDIzN00KOXhJVkJrWEd1U2NvWHVPN1lDcWFrZkt6aXdoRTV4UmRaa3gweXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQQpBNElCQVFCaHRWUEI0OCs4eFZyVmRxM1BIY3k5QkxtVEtrRFl6N2Q0ODJzTG1HczBuVUdGSTFZUDdmaFJPV3ZxCktCTlpkNEI5MUpwU1NoRGUrMHpoNno4WG5Ha01mYnRSYWx0NHEwZ3lKdk9hUWhqQ3ZCcSswTFk5d2NLbXpFdnMKcTRiNUZ5NXNpRUZSekJLTmZtTGwxTTF2cW1hNmFCVnNYUUhPREdzYS83dE5MalZ2ay9PYm52cFg3UFhLa0E3cQpLMTQvV0tBRFBJWm9mb00xMzB4Q1RTYXVpeXROajlnWkx1WU9leEZhblVwNCt2MHBYWS81OFFSNTk2U0ROVTlKClJaeDhwTzBTaUYvZXkxVUZXbmpzdHBjbTQzTFVQKzFwU1hFeVhZOFJrRTI2QzNvdjNaTFNKc2pMbC90aXVqUlgKZUJPOWorWDdzS0R4amdtajBPbWdpVkpIM0YrUAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}, false, "", "localhost.example:443"},
|
||||
{"bad ca encoding", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:443"}, OverrideCertificateName: "*.local", CA: "^"}, true, "", "localhost.example:443"},
|
||||
{"custom ca file", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:443"}, OverrideCertificateName: "*.local", CAFile: "testdata/example.crt"}, false, "", "localhost.example:443"},
|
||||
{"bad custom ca file", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:443"}, OverrideCertificateName: "*.local", CAFile: "testdata/example.crt2"}, true, "", "localhost.example:443"},
|
||||
{"valid with insecure", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, WithInsecure: true}, false, "", "localhost.example:8443"},
|
||||
{"valid client round robin", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, ClientDNSRoundRobin: true}, false, "", "dns:///localhost.example:8443"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewGRPCClientConn(tt.opts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
|
||||
if !strings.EqualFold(err.Error(), tt.wantErrStr) {
|
||||
t.Errorf("New() error = %v did not contain wantErr %v", err, tt.wantErrStr)
|
||||
}
|
||||
}
|
||||
if got != nil && got.Target() != tt.wantTarget {
|
||||
t.Errorf("New() target = %v expected %v", got.Target(), tt.wantTarget)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package grpcutil // import "github.com/pomerium/pomerium/internal/grpcutil"
|
||||
package grpc // import "github.com/pomerium/pomerium/internal/grpc"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
@ -10,13 +10,14 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/metrics"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// NewServer creates a new gRPC serve.
|
||||
// It is the callers responsibility to close the resturned server.
|
||||
func NewServer(opt *ServerOptions, registrationFn func(s *grpc.Server), wg *sync.WaitGroup) *grpc.Server {
|
||||
func NewServer(opt *ServerOptions, registrationFn func(s *grpc.Server), wg *sync.WaitGroup) (*grpc.Server, error) {
|
||||
if opt == nil {
|
||||
opt = defaultServerOptions
|
||||
} else {
|
||||
|
@ -24,33 +25,31 @@ func NewServer(opt *ServerOptions, registrationFn func(s *grpc.Server), wg *sync
|
|||
}
|
||||
ln, err := net.Listen("tcp", opt.Addr)
|
||||
if err != nil {
|
||||
log.Fatal().Str("addr", opt.Addr).Err(err).Msg("internal/grpcutil: unexpected ")
|
||||
return nil, err
|
||||
}
|
||||
grpcAuth := NewSharedSecretCred(opt.SharedKey)
|
||||
grpcOpts := []grpc.ServerOption{
|
||||
grpc.UnaryInterceptor(grpcAuth.ValidateRequest),
|
||||
grpc.StatsHandler(metrics.NewGRPCServerStatsHandler(opt.Addr))}
|
||||
grpcOpts := []grpc.ServerOption{grpc.StatsHandler(metrics.NewGRPCServerStatsHandler(opt.Addr))}
|
||||
|
||||
if opt.TLSCertificate != nil {
|
||||
log.Debug().Str("addr", opt.Addr).Msg("internal/grpcutil: with TLS")
|
||||
log.Debug().Str("addr", opt.Addr).Msg("internal/grpc: serving over TLS")
|
||||
cert := credentials.NewServerTLSFromCert(opt.TLSCertificate)
|
||||
grpcOpts = append(grpcOpts, grpc.Creds(cert))
|
||||
} else {
|
||||
log.Warn().Str("addr", opt.Addr).Msg("internal/grpcutil: insecure server")
|
||||
log.Warn().Str("addr", opt.Addr).Msg("internal/grpc: serving without TLS")
|
||||
}
|
||||
|
||||
srv := grpc.NewServer(grpcOpts...)
|
||||
registrationFn(srv)
|
||||
log.Info().Interface("grpc-service-info", srv.GetServiceInfo()).Msg("internal/grpc: registered")
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := srv.Serve(ln); err != grpc.ErrServerStopped {
|
||||
log.Error().Str("addr", opt.Addr).Err(err).Msg("internal/grpcutil: unexpected shutdown")
|
||||
log.Error().Str("addr", opt.Addr).Err(err).Msg("internal/grpc: unexpected shutdown")
|
||||
}
|
||||
}()
|
||||
|
||||
return srv
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// ServerOptions contains the configurations settings for a gRPC server.
|
||||
|
@ -59,10 +58,6 @@ type ServerOptions struct {
|
|||
// gRPC requests. If empty, ":443" is used.
|
||||
Addr string
|
||||
|
||||
// SharedKey is the shared secret authorization key used to mutually authenticate
|
||||
// requests between services.
|
||||
SharedKey string
|
||||
|
||||
// TLS certificates to use, if any.
|
||||
TLSCertificate *tls.Certificate
|
||||
|
||||
|
@ -93,8 +88,7 @@ func Shutdown(srv *grpc.Server) {
|
|||
signal.Notify(sigint, os.Interrupt)
|
||||
signal.Notify(sigint, syscall.SIGTERM)
|
||||
rec := <-sigint
|
||||
log.Info().Str("signal", rec.String()).Msg("internal/grpcutil: shutting down servers")
|
||||
log.Info().Str("signal", rec.String()).Msg("internal/grpc: shutting down servers")
|
||||
srv.GracefulStop()
|
||||
log.Info().Str("signal", rec.String()).Msg("internal/grpcutil: shut down servers")
|
||||
|
||||
log.Info().Str("signal", rec.String()).Msg("internal/grpc: shut down servers")
|
||||
}
|
84
internal/grpc/server_test.go
Normal file
84
internal/grpc/server_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package grpc
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const privKey = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIMQiDy26/R4ca/OdnjIf8OEDeHcw8yB5SDV9FD500CW5oAoGCCqGSM49
|
||||
AwEHoUQDQgAEFumdSrEe9dnPEUU3LuyC8l6MM6PefNgpSsRL4GrD22XITMjqDKFr
|
||||
jqJTf0Fo1ZWm4v+Eds6s88rsLzEC+cKLRQ==
|
||||
-----END EC PRIVATE KEY-----`
|
||||
const pubKey = `-----BEGIN CERTIFICATE-----
|
||||
MIIBeDCCAR+gAwIBAgIUUGE8w2S7XzpkVLbNq5QUxyVOwqEwCgYIKoZIzj0EAwIw
|
||||
ETEPMA0GA1UEAwwGdW51c2VkMCAXDTE5MDcxNTIzNDQyOVoYDzQ3NTcwNjExMjM0
|
||||
NDI5WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
|
||||
AAQW6Z1KsR712c8RRTcu7ILyXowzo9582ClKxEvgasPbZchMyOoMoWuOolN/QWjV
|
||||
labi/4R2zqzzyuwvMQL5wotFo1MwUTAdBgNVHQ4EFgQURYdcaniRqBHXeaM79LtV
|
||||
pyJ4EwAwHwYDVR0jBBgwFoAURYdcaniRqBHXeaM79LtVpyJ4EwAwDwYDVR0TAQH/
|
||||
BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBHbhVnGbwXqaMZ1dB8eBAK56jyeWDZ
|
||||
2PWXmFMTu7+RywIgaZ7UwVNB2k7KjEEBiLm0PIRcpJmczI2cP9+ZMIkPHHw=
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
certb64, err := cryptutil.CertifcateFromBase64(
|
||||
base64.StdEncoding.EncodeToString([]byte(pubKey)),
|
||||
base64.StdEncoding.EncodeToString([]byte(privKey)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opt *ServerOptions
|
||||
registrationFn func(s *grpc.Server)
|
||||
wg *sync.WaitGroup
|
||||
wantNil bool
|
||||
wantErr bool
|
||||
}{
|
||||
{"simple", &ServerOptions{Addr: ":0"}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
|
||||
{"bad tcp port", &ServerOptions{Addr: ":9999999"}, func(s *grpc.Server) {}, &sync.WaitGroup{}, true, true},
|
||||
{"with certs", &ServerOptions{Addr: ":0", TLSCertificate: certb64}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewServer(tt.opt, tt.registrationFn, tt.wg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewServer() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if (got == nil) != tt.wantNil {
|
||||
t.Errorf("NewServer() = %v, want %v", got, tt.wantNil)
|
||||
}
|
||||
if got != nil {
|
||||
// simulate a sigterm and cleanup the server
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
defer signal.Stop(c)
|
||||
go Shutdown(got)
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||
waitSig(t, c, syscall.SIGINT)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
|
||||
select {
|
||||
case s := <-c:
|
||||
if s != sig {
|
||||
t.Fatalf("signal was %v, want %v", s, sig)
|
||||
}
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatalf("timeout waiting for %v", sig)
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package grpcutil // import "github.com/pomerium/pomerium/internal/grpcutil"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// SharedSecretCred is a simple token-based method of mutual authentication.
|
||||
type SharedSecretCred struct{ sharedSecret string }
|
||||
|
||||
// NewSharedSecretCred returns a new instance of shared secret credential middleware for gRPC clients
|
||||
func NewSharedSecretCred(secret string) *SharedSecretCred {
|
||||
return &SharedSecretCred{sharedSecret: secret}
|
||||
}
|
||||
|
||||
// GetRequestMetadata sets the value for "authorization" key
|
||||
func (s SharedSecretCred) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
|
||||
return map[string]string{"authorization": s.sharedSecret}, nil
|
||||
}
|
||||
|
||||
// RequireTransportSecurity indicates whether the credentials requires
|
||||
// transport security.
|
||||
func (s SharedSecretCred) RequireTransportSecurity() bool { return false }
|
||||
|
||||
// ValidateRequest ensures a valid token exists within a request's metadata. If
|
||||
// the token is missing or invalid, the interceptor blocks execution of the
|
||||
// handler and returns an error. Otherwise, the interceptor invokes the unary
|
||||
// handler.
|
||||
func (s SharedSecretCred) ValidateRequest(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "middleware.grpc.ValidateRequest")
|
||||
defer span.End()
|
||||
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "missing metadata")
|
||||
}
|
||||
// The keys within metadata.MD are normalized to lowercase.
|
||||
// See: https://godoc.org/google.golang.org/grpc/metadata#New
|
||||
elem, ok := md["authorization"]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "no auth details supplied")
|
||||
}
|
||||
if elem[0] != s.sharedSecret {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "invalid shared secrets")
|
||||
}
|
||||
// Continue execution of handler after ensuring a valid token.
|
||||
return handler(ctx, req)
|
||||
}
|
235
internal/kv/autocache/autocache.go
Normal file
235
internal/kv/autocache/autocache.go
Normal file
|
@ -0,0 +1,235 @@
|
|||
package autocache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
stdlog "log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/groupcache"
|
||||
"github.com/pomerium/autocache"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/kv"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
// Name represents autocache's shorthand named.
|
||||
const Name = "autocache"
|
||||
|
||||
const defaultQueryParamKey = "ati"
|
||||
|
||||
var _ kv.Store = &Store{}
|
||||
|
||||
// Store implements a the store interface for autocache, a distributed cache
|
||||
// with gossip based peer membership enrollment.
|
||||
// https://github.com/pomerium/autocache
|
||||
type Store struct {
|
||||
db *groupcache.Group
|
||||
cluster *autocache.Autocache
|
||||
sharedKey string
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
// Options represent autocache options.
|
||||
type Options struct {
|
||||
Addr string
|
||||
CacheSize int64
|
||||
ClusterDomain string
|
||||
GetterFn groupcache.GetterFunc
|
||||
Group string
|
||||
Log *stdlog.Logger
|
||||
Port int
|
||||
Scheme string
|
||||
SharedKey string
|
||||
}
|
||||
|
||||
// DefaultOptions are the default options used by the autocache service.
|
||||
var DefaultOptions = &Options{
|
||||
Addr: ":8333",
|
||||
Port: 8333,
|
||||
Scheme: "http",
|
||||
CacheSize: 10 << 20,
|
||||
Group: "default",
|
||||
GetterFn: func(ctx context.Context, id string, dest groupcache.Sink) error {
|
||||
b := fromContext(ctx)
|
||||
if len(b) == 0 {
|
||||
return fmt.Errorf("autocache: empty ctx for id: %s", id)
|
||||
}
|
||||
if err := dest.SetBytes(b); err != nil {
|
||||
return fmt.Errorf("autocache: sink error %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// New creates a new autocache key value store. Autocache will start several
|
||||
// services to support distributed cluster management and membership.
|
||||
// A HTTP server will be used by groupcache to perform cross node-RPC. By
|
||||
// default that server will start on port ``:8333`.
|
||||
// Memberlist will likewise start and listen for group membership on port
|
||||
//
|
||||
//
|
||||
// NOTE: RPC communication between nodes is _authenticated_ but not encrypted.
|
||||
// NOTE: Groupchache starts a HTTP listener (Default: :8333)
|
||||
// NOTE: Memberlist starts a GOSSIP listener on TCP/UDP. (Default: :7946)
|
||||
func New(o *Options) (*Store, error) {
|
||||
var s Store
|
||||
var err error
|
||||
if o.SharedKey == "" {
|
||||
return nil, errors.New("autocache: shared secret must be set")
|
||||
}
|
||||
if o.Addr == "" {
|
||||
o.Addr = DefaultOptions.Addr
|
||||
}
|
||||
if o.Scheme == "" {
|
||||
o.Scheme = DefaultOptions.Scheme
|
||||
}
|
||||
if o.Port == 0 {
|
||||
o.Port = DefaultOptions.Port
|
||||
}
|
||||
if o.Group == "" {
|
||||
o.Group = DefaultOptions.Group
|
||||
}
|
||||
if o.GetterFn == nil {
|
||||
o.GetterFn = DefaultOptions.GetterFn
|
||||
}
|
||||
if o.CacheSize == 0 {
|
||||
o.CacheSize = DefaultOptions.CacheSize
|
||||
}
|
||||
if o.ClusterDomain == "" {
|
||||
o.Log.Println("")
|
||||
}
|
||||
s.db = groupcache.NewGroup(o.Group, o.CacheSize, o.GetterFn)
|
||||
s.cluster, err = autocache.New(&autocache.Options{
|
||||
PoolTransportFn: s.addSessionToCtx,
|
||||
PoolScheme: o.Scheme,
|
||||
PoolPort: o.Port,
|
||||
Logger: o.Log,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverOpts := &httputil.ServerOptions{Addr: o.Addr}
|
||||
var wg sync.WaitGroup
|
||||
s.srv, err = httputil.NewServer(serverOpts, QueryParamToCtx(s.cluster), &wg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := s.cluster.Join([]string{o.ClusterDomain}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Set stores a key value pair. Since group cache actually only implements
|
||||
// Get, we have to be a little creative in how we smuggle in value using
|
||||
// context.
|
||||
func (s Store) Set(ctx context.Context, k string, v []byte) error {
|
||||
// smuggle the the value pair as a context value
|
||||
ctx = newContext(ctx, v)
|
||||
if err := s.db.Get(ctx, k, groupcache.AllocatingByteSliceSink(&v)); err != nil {
|
||||
return fmt.Errorf("autocache: set %s failed: %w", k, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves the value for a key in the bucket.
|
||||
func (s *Store) Get(ctx context.Context, k string) (bool, []byte, error) {
|
||||
var value []byte
|
||||
if err := s.db.Get(ctx, k, groupcache.AllocatingByteSliceSink(&value)); err != nil {
|
||||
return false, nil, fmt.Errorf("autocache: get %s failed: %w", k, err)
|
||||
}
|
||||
return true, value, nil
|
||||
}
|
||||
|
||||
// Close shuts down any HTTP server used for groupcache pool, and
|
||||
// also stop any background maintenance of memberlist.
|
||||
func (s Store) Close(ctx context.Context) error {
|
||||
var retErr error
|
||||
if s.srv != nil {
|
||||
if err := s.srv.Shutdown(ctx); err != nil {
|
||||
retErr = fmt.Errorf("autocache: http shutdown error: %w", err)
|
||||
}
|
||||
}
|
||||
if s.cluster.Memberlist != nil {
|
||||
if err := s.cluster.Memberlist.Shutdown(); err != nil {
|
||||
retErr = fmt.Errorf("autocache: memberlist shutdown error: %w", err)
|
||||
}
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
// addSessionToCtx is a wrapper function that allows us to add a session
|
||||
// into http client's round trip and sign the outgoing request.
|
||||
func (s *Store) addSessionToCtx(ctx context.Context) http.RoundTripper {
|
||||
var sh signedSession
|
||||
sh.session = string(fromContext(ctx))
|
||||
sh.sharedKey = s.sharedKey
|
||||
return sh
|
||||
}
|
||||
|
||||
type signedSession struct {
|
||||
session string
|
||||
sharedKey string
|
||||
}
|
||||
|
||||
// RoundTrip copies the request's session context and adds it to the
|
||||
// outgoing client request as a query param. The whole URL is then signed for
|
||||
// authenticity.
|
||||
func (s signedSession) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// clone request before mutating
|
||||
// https://golang.org/src/net/http/client.go?s=4306:5535#L105
|
||||
newReq := cloneRequest(req)
|
||||
session := s.session
|
||||
newReqURL := *newReq.URL
|
||||
q := newReqURL.Query()
|
||||
q.Set(defaultQueryParamKey, session)
|
||||
newReqURL.RawQuery = q.Encode()
|
||||
newReq.URL = urlutil.NewSignedURL(s.sharedKey, &newReqURL).Sign()
|
||||
return http.DefaultTransport.RoundTrip(newReq)
|
||||
}
|
||||
|
||||
// QueryParamToCtx takes a value from a query param and adds it to the
|
||||
// current request request context.
|
||||
func QueryParamToCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.FormValue(defaultQueryParamKey)
|
||||
ctx := newContext(r.Context(), []byte(session))
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
var sessionCtxKey = &contextKey{"PomeriumCachedSessionBytes"}
|
||||
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func newContext(ctx context.Context, b []byte) context.Context {
|
||||
ctx = context.WithValue(ctx, sessionCtxKey, b)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func fromContext(ctx context.Context) []byte {
|
||||
b, _ := ctx.Value(sessionCtxKey).([]byte)
|
||||
return b
|
||||
}
|
||||
|
||||
func cloneRequest(req *http.Request) *http.Request {
|
||||
r := new(http.Request)
|
||||
*r = *req
|
||||
r.Header = cloneHeaders(req.Header)
|
||||
return r
|
||||
}
|
||||
|
||||
func cloneHeaders(in http.Header) http.Header {
|
||||
out := make(http.Header, len(in))
|
||||
for key, values := range in {
|
||||
newValues := make([]string, len(values))
|
||||
copy(newValues, values)
|
||||
out[key] = newValues
|
||||
}
|
||||
return out
|
||||
}
|
109
internal/kv/bolt/bolt.go
Normal file
109
internal/kv/bolt/bolt.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/kv"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var _ kv.Store = &Store{}
|
||||
|
||||
// Name represents bbolt's shorthand named.
|
||||
const Name = "bolt"
|
||||
|
||||
// Store implements a the Store interface for bolt.
|
||||
// https://godoc.org/github.com/etcd-io/bbolt
|
||||
type Store struct {
|
||||
db *bolt.DB
|
||||
bucket string
|
||||
}
|
||||
|
||||
// Options represents options for configuring the boltdb cache store.
|
||||
type Options struct {
|
||||
// Buckets are collections of key/value pairs within the database.
|
||||
// All keys in a bucket must be unique.
|
||||
Bucket string
|
||||
// Path is where the database file will be stored.
|
||||
Path string
|
||||
}
|
||||
|
||||
// DefaultOptions contain's bolts default options.
|
||||
var DefaultOptions = &Options{
|
||||
Bucket: "default",
|
||||
Path: Name + ".db",
|
||||
}
|
||||
|
||||
// New creates a new bolt cache store.
|
||||
// It is up to the operator to make sure that the store's path
|
||||
// is writeable.
|
||||
func New(o *Options) (*Store, error) {
|
||||
if o.Path == "" {
|
||||
o.Path = DefaultOptions.Path
|
||||
}
|
||||
if o.Bucket == "" {
|
||||
o.Bucket = DefaultOptions.Bucket
|
||||
}
|
||||
|
||||
db, err := bolt.Open(o.Path, 0600, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(o.Bucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Store{db: db, bucket: o.Bucket}, nil
|
||||
}
|
||||
|
||||
// Set sets the value for a key in the bucket.
|
||||
// If the key exist then its previous value will be overwritten.
|
||||
// Supplied value must remain valid for the life of the transaction.
|
||||
// Returns an error if the bucket was created from a read-only transaction,
|
||||
// if the key is blank, if the key is too large, or if the value is too large.
|
||||
func (s Store) Set(ctx context.Context, k string, v []byte) error {
|
||||
err := s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(s.bucket))
|
||||
return b.Put([]byte(k), v)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves the value for a key in the bucket.
|
||||
// Returns a nil value if the key does not exist or if the key is a nested bucket.
|
||||
// The returned value is only valid for the life of the transaction.
|
||||
func (s *Store) Get(ctx context.Context, k string) (bool, []byte, error) {
|
||||
var value []byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(s.bucket))
|
||||
txData := b.Get([]byte(k)) // only valid in transaction
|
||||
value = append(txData[:0:0], txData...)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if value == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
return true, value, nil
|
||||
}
|
||||
|
||||
// Close releases all database resources.
|
||||
// It will block waiting for any open transactions to finish
|
||||
// before closing the database and returning.
|
||||
func (s Store) Close(ctx context.Context) error {
|
||||
return s.db.Close()
|
||||
}
|
89
internal/kv/redis/redis.go
Normal file
89
internal/kv/redis/redis.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/pomerium/pomerium/internal/kv"
|
||||
)
|
||||
|
||||
var _ kv.Store = &Store{}
|
||||
|
||||
// Name represents redis's shorthand name.
|
||||
const Name = "redis"
|
||||
|
||||
// Store implements a the Store interface for redis.
|
||||
// https://godoc.org/github.com/go-redis/redis
|
||||
type Store struct {
|
||||
db *redis.Client
|
||||
}
|
||||
|
||||
// Options represents options for configuring the redis store.
|
||||
type Options struct {
|
||||
// host:port Addr.
|
||||
Addr string
|
||||
// Optional password. Must match the password specified in the
|
||||
// requirepass server configuration option.
|
||||
Password string
|
||||
// Database to be selected after connecting to the server.
|
||||
DB int
|
||||
// TLS Config to use. When set TLS will be negotiated.
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// New creates a new redis cache store.
|
||||
// It is up to the operator to make sure that the store's path
|
||||
// is writeable.
|
||||
func New(o *Options) (*Store, error) {
|
||||
if o.Addr == "" {
|
||||
return nil, fmt.Errorf("kv/redis: connection address is required")
|
||||
}
|
||||
|
||||
db := redis.NewClient(
|
||||
&redis.Options{
|
||||
Addr: o.Addr,
|
||||
Password: o.Password,
|
||||
DB: o.DB,
|
||||
TLSConfig: o.TLSConfig,
|
||||
})
|
||||
|
||||
if _, err := db.Ping().Result(); err != nil {
|
||||
return nil, fmt.Errorf("kv/redis: error connecting to redis: %w", err)
|
||||
}
|
||||
|
||||
return &Store{db: db}, nil
|
||||
}
|
||||
|
||||
// Set is equivalent to redis `SET key value [expiration]` command.
|
||||
//
|
||||
// Use expiration for `SETEX`-like behavior.
|
||||
// Zero expiration means the key has no expiration time.
|
||||
func (s Store) Set(ctx context.Context, k string, v []byte) error {
|
||||
if err := s.db.Set(k, string(v), 0).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get is equivalent to Redis `GET key` command.
|
||||
// It returns redis.Nil error when key does not exist.
|
||||
func (s *Store) Get(ctx context.Context, k string) (bool, []byte, error) {
|
||||
v, err := s.db.Get(k).Result()
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return false, nil, nil
|
||||
} else if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, []byte(v), nil
|
||||
}
|
||||
|
||||
// Close closes the client, releasing any open resources.
|
||||
//
|
||||
// It is rare to Close a Client, as the Client is meant to be
|
||||
// long-lived and shared between many goroutines.
|
||||
func (s Store) Close(ctx context.Context) error {
|
||||
return s.db.Close()
|
||||
}
|
10
internal/kv/store.go
Normal file
10
internal/kv/store.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package kv
|
||||
|
||||
import "context"
|
||||
|
||||
// Store specifies a key value storage interface.
|
||||
type Store interface {
|
||||
Set(ctx context.Context, key string, value []byte) error
|
||||
Get(ctx context.Context, key string) (keyExists bool, value []byte, err error)
|
||||
Close(ctx context.Context) error
|
||||
}
|
122
internal/sessions/cache/cache_store.go
vendored
122
internal/sessions/cache/cache_store.go
vendored
|
@ -1,14 +1,12 @@
|
|||
package cache // import "github.com/pomerium/pomerium/internal/sessions/cache"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/groupcache"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/grpc/cache/client"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
)
|
||||
|
@ -16,83 +14,69 @@ import (
|
|||
var _ sessions.SessionStore = &Store{}
|
||||
var _ sessions.SessionLoader = &Store{}
|
||||
|
||||
const (
|
||||
defaultQueryParamKey = "ati"
|
||||
)
|
||||
|
||||
// Store implements the session store interface using a distributed cache.
|
||||
// Store implements the session store interface using a cache service.
|
||||
type Store struct {
|
||||
name string
|
||||
encoder encoding.Marshaler
|
||||
decoder encoding.Unmarshaler
|
||||
|
||||
cache *groupcache.Group
|
||||
cache client.Cacher
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
queryParam string
|
||||
wrappedStore sessions.SessionStore
|
||||
}
|
||||
|
||||
// defaultCacheSize is ~10MB
|
||||
var defaultCacheSize int64 = 10 << 20
|
||||
|
||||
// NewStore creates a new session store built on the distributed caching library
|
||||
// groupcache. On a cache miss, the cache store attempts to fallback to another
|
||||
// SessionStore implementation.
|
||||
func NewStore(enc encoding.MarshalUnmarshaler, wrappedStore sessions.SessionStore, name string) *Store {
|
||||
store := &Store{
|
||||
name: name,
|
||||
encoder: enc,
|
||||
decoder: enc,
|
||||
wrappedStore: wrappedStore,
|
||||
}
|
||||
|
||||
store.cache = groupcache.NewGroup(name, defaultCacheSize, groupcache.GetterFunc(
|
||||
func(ctx context.Context, id string, dest groupcache.Sink) error {
|
||||
// fill the cache with session set as part of the request
|
||||
// context set previously as part of SaveSession.
|
||||
b := fromContext(ctx)
|
||||
if len(b) == 0 {
|
||||
return fmt.Errorf("sessions/cache: cannot fill key %s from ctx", id)
|
||||
}
|
||||
if err := dest.SetBytes(b); err != nil {
|
||||
return fmt.Errorf("sessions/cache: sink error %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
))
|
||||
|
||||
return store
|
||||
// Options represent cache store's available configurations.
|
||||
type Options struct {
|
||||
Cache client.Cacher
|
||||
Encoder encoding.MarshalUnmarshaler
|
||||
QueryParam string
|
||||
WrappedStore sessions.SessionStore
|
||||
}
|
||||
|
||||
// LoadSession implements SessionLoaders's LoadSession method for cache store.
|
||||
var defaultOptions = &Options{
|
||||
QueryParam: "cache_store_key",
|
||||
}
|
||||
|
||||
// NewStore creates a new cache
|
||||
func NewStore(o *Options) *Store {
|
||||
if o.QueryParam == "" {
|
||||
o.QueryParam = defaultOptions.QueryParam
|
||||
}
|
||||
return &Store{
|
||||
cache: o.Cache,
|
||||
encoder: o.Encoder,
|
||||
queryParam: o.QueryParam,
|
||||
wrappedStore: o.WrappedStore,
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// look for our cache's key in the default query param
|
||||
sessionID := r.URL.Query().Get(defaultQueryParamKey)
|
||||
sessionID := r.URL.Query().Get(s.queryParam)
|
||||
if sessionID == "" {
|
||||
// if unset, fallback to default cache store
|
||||
log.FromRequest(r).Debug().Msg("sessions/cache: no query param, trying wrapped loader")
|
||||
return s.wrappedStore.LoadSession(r)
|
||||
return nil, sessions.ErrNoSessionFound
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if err := s.cache.Get(r.Context(), sessionID, groupcache.AllocatingByteSliceSink(&b)); err != nil {
|
||||
log.FromRequest(r).Debug().Err(err).Msg("sessions/cache: miss, trying wrapped loader")
|
||||
return s.wrappedStore.LoadSession(r)
|
||||
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
|
||||
}
|
||||
if !exists {
|
||||
return nil, sessions.ErrNoSessionFound
|
||||
}
|
||||
var session sessions.State
|
||||
if err := s.decoder.Unmarshal(b, &session); err != nil {
|
||||
if err := s.encoder.Unmarshal(val, &session); err != nil {
|
||||
log.FromRequest(r).Error().Err(err).Msg("sessions/cache: unmarshal")
|
||||
return nil, sessions.ErrMalformed
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
// ClearSession implements SessionStore's ClearSession for the cache store.
|
||||
// Since group cache has no explicit eviction, we just call the wrapped
|
||||
// store's ClearSession method here.
|
||||
// ClearSession clears the session from the wrapped store.
|
||||
func (s *Store) ClearSession(w http.ResponseWriter, r *http.Request) {
|
||||
s.wrappedStore.ClearSession(w, r)
|
||||
}
|
||||
|
||||
// SaveSession implements SessionStore's SaveSession method for cache store.
|
||||
// SaveSession saves the session to the cache, and wrapped store.
|
||||
func (s *Store) SaveSession(w http.ResponseWriter, r *http.Request, x interface{}) error {
|
||||
err := s.wrappedStore.SaveSession(w, r, x)
|
||||
if err != nil {
|
||||
|
@ -101,7 +85,7 @@ func (s *Store) SaveSession(w http.ResponseWriter, r *http.Request, x interface{
|
|||
|
||||
state, ok := x.(*sessions.State)
|
||||
if !ok {
|
||||
return errors.New("internal/sessions: cannot cache non state type")
|
||||
return errors.New("sessions/cache: cannot cache non state type")
|
||||
}
|
||||
|
||||
data, err := s.encoder.Marshal(&state)
|
||||
|
@ -109,23 +93,5 @@ func (s *Store) SaveSession(w http.ResponseWriter, r *http.Request, x interface{
|
|||
return fmt.Errorf("sessions/cache: marshal %w", err)
|
||||
}
|
||||
|
||||
ctx := newContext(r.Context(), data)
|
||||
var b []byte
|
||||
return s.cache.Get(ctx, state.AccessTokenID, groupcache.AllocatingByteSliceSink(&b))
|
||||
}
|
||||
|
||||
var sessionCtxKey = &contextKey{"PomeriumCachedSessionBytes"}
|
||||
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func newContext(ctx context.Context, b []byte) context.Context {
|
||||
ctx = context.WithValue(ctx, sessionCtxKey, b)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func fromContext(ctx context.Context) []byte {
|
||||
b, _ := ctx.Value(sessionCtxKey).([]byte)
|
||||
return b
|
||||
return s.cache.Set(r.Context(), state.AccessTokenID, data)
|
||||
}
|
||||
|
|
220
internal/sessions/cache/cache_store_test.go
vendored
220
internal/sessions/cache/cache_store_test.go
vendored
|
@ -1,7 +1,8 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -9,125 +10,188 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
mock_encoder "github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/grpc/cache/client"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/sessions/cookie"
|
||||
"github.com/pomerium/pomerium/internal/sessions/mock"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
func testAuthorizer(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := sessions.FromContext(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
type mockCache struct {
|
||||
Key string
|
||||
KeyExists bool
|
||||
Value []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
func TestVerifier(t *testing.T) {
|
||||
fnh := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, http.StatusText(http.StatusOK))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
func (mc *mockCache) Get(ctx context.Context, key string) (keyExists bool, value []byte, err error) {
|
||||
return mc.KeyExists, mc.Value, mc.Err
|
||||
}
|
||||
func (mc *mockCache) Set(ctx context.Context, key string, value []byte) error {
|
||||
return mc.Err
|
||||
}
|
||||
func (mc *mockCache) Close() error {
|
||||
return mc.Err
|
||||
}
|
||||
|
||||
func TestNewStore(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
skipSave bool
|
||||
cacheSize int64
|
||||
state sessions.State
|
||||
name string
|
||||
Options *Options
|
||||
State *sessions.State
|
||||
|
||||
wantBody string
|
||||
wantStatus int
|
||||
wantErr bool
|
||||
wantLoadErr bool
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", false, 1 << 10, sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, http.StatusText(http.StatusOK), http.StatusOK},
|
||||
{"expired", false, 1 << 10, sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}, "internal/sessions: validation failed, token is expired (exp)\n", http.StatusUnauthorized},
|
||||
{"empty", false, 1 << 10, sessions.State{AccessTokenID: "", Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}, "internal/sessions: session is not found\n", http.StatusUnauthorized},
|
||||
{"miss", true, 1 << 10, sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, "internal/sessions: session is not found\n", http.StatusUnauthorized},
|
||||
{"cache eviction", false, 1, sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, "internal/sessions: session is not found\n", http.StatusUnauthorized},
|
||||
{"simple good",
|
||||
&Options{
|
||||
Cache: &mockCache{},
|
||||
WrappedStore: &mock.Store{},
|
||||
Encoder: mock_encoder.Encoder{MarshalResponse: []byte("ok")},
|
||||
},
|
||||
&sessions.State{Email: "user@domain.com", User: "user"},
|
||||
false, false,
|
||||
http.StatusOK},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defaultCacheSize = tt.cacheSize
|
||||
cipher, err := cryptutil.NewAEADCipherFromBase64(cryptutil.NewBase64Key())
|
||||
encoder := ecjson.New(cipher)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cs, err := cookie.NewStore(&cookie.Options{Name: t.Name()}, encoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cacheStore := NewStore(encoder, cs, t.Name())
|
||||
got := NewStore(tt.Options)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
q := r.URL.Query()
|
||||
|
||||
q.Set(defaultQueryParamKey, tt.state.AccessTokenID)
|
||||
r.URL.RawQuery = q.Encode()
|
||||
r.Header.Set("Accept", "application/json")
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
got := sessions.RetrieveSession(cacheStore)(testAuthorizer((fnh)))
|
||||
|
||||
if !tt.skipSave {
|
||||
cacheStore.SaveSession(w, r, &tt.state)
|
||||
if err := got.SaveSession(w, r, tt.State); (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewStore.SaveSession() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
for i := 1; i <= 10; i++ {
|
||||
s := tt.state
|
||||
s.AccessTokenID = cryptutil.NewBase64Key()
|
||||
cacheStore.SaveSession(w, r, s)
|
||||
}
|
||||
r = httptest.NewRequest("GET", "/", nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
got.ServeHTTP(w, r)
|
||||
|
||||
gotBody := w.Body.String()
|
||||
gotStatus := w.Result().StatusCode
|
||||
|
||||
if diff := cmp.Diff(gotBody, tt.wantBody); diff != "" {
|
||||
t.Errorf("RetrieveSession() = %v", diff)
|
||||
}
|
||||
if diff := cmp.Diff(gotStatus, tt.wantStatus); diff != "" {
|
||||
t.Errorf("RetrieveSession() = %v", diff)
|
||||
got.ClearSession(w, r)
|
||||
status := w.Result().StatusCode
|
||||
if diff := cmp.Diff(status, tt.wantStatus); diff != "" {
|
||||
t.Errorf("ClearSession() = %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_SaveSession(t *testing.T) {
|
||||
|
||||
cipher, err := cryptutil.NewAEADCipherFromBase64(cryptutil.NewBase64Key())
|
||||
encoder := ecjson.New(cipher)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cs, err := cookie.NewStore(&cookie.Options{
|
||||
Name: "_pomerium",
|
||||
}, encoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
Options *Options
|
||||
|
||||
x interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{"good", &sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, false},
|
||||
{"bad type", "bad type!", true},
|
||||
{"good", &Options{Cache: &mockCache{}, WrappedStore: cs, Encoder: mock_encoder.Encoder{MarshalResponse: []byte("ok")}}, &sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, false},
|
||||
{"encoder error", &Options{Cache: &mockCache{}, WrappedStore: cs, Encoder: mock_encoder.Encoder{MarshalError: errors.New("err")}}, &sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, true},
|
||||
{"good", &Options{Cache: &mockCache{}, WrappedStore: &mock.Store{SaveError: errors.New("err")}}, &sessions.State{AccessTokenID: cryptutil.NewBase64Key(), Email: "user@pomerium.io", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}, true},
|
||||
{"bad type", &Options{Cache: &mockCache{}, WrappedStore: cs, Encoder: mock_encoder.Encoder{MarshalError: errors.New("err")}}, "bad type!", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cipher, err := cryptutil.NewAEADCipherFromBase64(cryptutil.NewBase64Key())
|
||||
encoder := ecjson.New(cipher)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
o := tt.Options
|
||||
if o.WrappedStore == nil {
|
||||
o.WrappedStore = cs
|
||||
|
||||
}
|
||||
cs, err := cookie.NewStore(&cookie.Options{
|
||||
Name: "_pomerium",
|
||||
}, encoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cacheStore := NewStore(encoder, cs, t.Name())
|
||||
cacheStore := NewStore(tt.Options)
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r.Header.Set("Accept", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if err := cacheStore.SaveSession(w, r, tt.x); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Store.SaveSession() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_LoadSession(t *testing.T) {
|
||||
key := cryptutil.NewBase64Key()
|
||||
tests := []struct {
|
||||
name string
|
||||
state *sessions.State
|
||||
cache client.Cacher
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
queryParam string
|
||||
wrappedStore sessions.SessionStore
|
||||
wantErr bool
|
||||
}{
|
||||
{"good",
|
||||
&sessions.State{AccessTokenID: key, Email: "user@pomerium.io"},
|
||||
&mockCache{KeyExists: true},
|
||||
mock_encoder.Encoder{MarshalResponse: []byte("ok")},
|
||||
defaultOptions.QueryParam,
|
||||
&mock.Store{Session: &sessions.State{AccessTokenID: key, Email: "user@pomerium.io"}},
|
||||
false},
|
||||
{"missing param with key",
|
||||
&sessions.State{AccessTokenID: key, Email: "user@pomerium.io"},
|
||||
&mockCache{KeyExists: true},
|
||||
mock_encoder.Encoder{MarshalResponse: []byte("ok")},
|
||||
"bad_query",
|
||||
&mock.Store{Session: &sessions.State{AccessTokenID: key, Email: "user@pomerium.io"}},
|
||||
true},
|
||||
{"doesn't exist",
|
||||
&sessions.State{AccessTokenID: key, Email: "user@pomerium.io"},
|
||||
&mockCache{KeyExists: false},
|
||||
mock_encoder.Encoder{MarshalResponse: []byte("ok")},
|
||||
defaultOptions.QueryParam,
|
||||
&mock.Store{Session: &sessions.State{AccessTokenID: key, Email: "user@pomerium.io"}},
|
||||
true},
|
||||
{"retrieval error",
|
||||
&sessions.State{AccessTokenID: key, Email: "user@pomerium.io"},
|
||||
&mockCache{Err: errors.New("err")},
|
||||
mock_encoder.Encoder{MarshalResponse: []byte("ok")},
|
||||
defaultOptions.QueryParam,
|
||||
&mock.Store{Session: &sessions.State{AccessTokenID: key, Email: "user@pomerium.io"}},
|
||||
true},
|
||||
{"unmarshal failure",
|
||||
&sessions.State{AccessTokenID: key, Email: "user@pomerium.io"},
|
||||
&mockCache{KeyExists: true},
|
||||
mock_encoder.Encoder{UnmarshalError: errors.New("err")},
|
||||
defaultOptions.QueryParam,
|
||||
&mock.Store{Session: &sessions.State{AccessTokenID: key, Email: "user@pomerium.io"}},
|
||||
true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Store{
|
||||
cache: tt.cache,
|
||||
encoder: tt.encoder,
|
||||
queryParam: tt.queryParam,
|
||||
wrappedStore: tt.wrappedStore,
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
q := r.URL.Query()
|
||||
|
||||
q.Set(defaultOptions.QueryParam, tt.state.AccessTokenID)
|
||||
r.URL.RawQuery = q.Encode()
|
||||
r.Header.Set("Accept", "application/json")
|
||||
|
||||
_, err := s.LoadSession(r)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Store.LoadSession() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ const (
|
|||
QuerySessionEncrypted = "pomerium_session_encrypted"
|
||||
QueryRedirectURI = "pomerium_redirect_uri"
|
||||
QueryRefreshToken = "pomerium_refresh_token"
|
||||
QueryAccessTokenID = "pomerium_session_access_token_id"
|
||||
QueryAudience = "pomerium_session_audience"
|
||||
)
|
||||
|
||||
// URL signature based query params used for verifying the authenticity of a URL.
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
package clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/proto/authorize"
|
||||
mock "github.com/pomerium/pomerium/proto/authorize/mock_authorize"
|
||||
)
|
||||
|
||||
func TestAuthorizeGRPC_Authorize(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
client := mock.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 := &AuthorizeGRPC{client: client}
|
||||
got, err := a.Authorize(context.Background(), tt.route, tt.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AuthorizeGRPC.Authorize() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("AuthorizeGRPC.Authorize() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestAuthorizeGRPC_IsAdmin(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
client := mock.NewMockAuthorizerClient(ctrl)
|
||||
client.EXPECT().IsAdmin(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).Return(&authorize.IsAdminReply{IsAdmin: true}, nil).AnyTimes()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
s *sessions.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 := &AuthorizeGRPC{client: client}
|
||||
got, err := a.IsAdmin(context.Background(), tt.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AuthorizeGRPC.IsAdmin() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("AuthorizeGRPC.IsAdmin() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *Options
|
||||
wantErr bool
|
||||
wantErrStr string
|
||||
wantTarget string
|
||||
}{
|
||||
{"no shared secret", &Options{}, true, "proxy/authenticator: grpc client requires shared secret", ""},
|
||||
{"empty connection", &Options{Addr: nil, SharedSecret: "shh"}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"both internal and addr empty", &Options{Addr: nil, InternalAddr: nil, SharedSecret: "shh"}, true, "proxy/authenticator: connection address required", ""},
|
||||
{"addr with port", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, SharedSecret: "shh"}, false, "", "localhost.example:8443"},
|
||||
{"addr without port", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example"}, SharedSecret: "shh"}, false, "", "localhost.example:443"},
|
||||
{"internal addr with port", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, SharedSecret: "shh"}, false, "", "localhost.example:8443"},
|
||||
{"internal addr without port", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, SharedSecret: "shh"}, false, "", "localhost.example:443"},
|
||||
{"cert override", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh"}, false, "", "localhost.example:443"},
|
||||
{"custom ca", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFVENDQWZrQ0ZBWHhneFg5K0hjWlBVVVBEK0laV0NGNUEvVTdNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1FVXgKQ3pBSkJnTlZCQVlUQWtGVk1STXdFUVlEVlFRSURBcFRiMjFsTFZOMFlYUmxNU0V3SHdZRFZRUUtEQmhKYm5SbApjbTVsZENCWGFXUm5hWFJ6SUZCMGVTQk1kR1F3SGhjTk1Ua3dNakk0TVRnMU1EQTNXaGNOTWprd01qSTFNVGcxCk1EQTNXakJGTVFzd0NRWURWUVFHRXdKQlZURVRNQkVHQTFVRUNBd0tVMjl0WlMxVGRHRjBaVEVoTUI4R0ExVUUKQ2d3WVNXNTBaWEp1WlhRZ1YybGtaMmwwY3lCUWRIa2dUSFJrTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQwpBUThBTUlJQkNnS0NBUUVBOVRFMEFiaTdnMHhYeURkVUtEbDViNTBCT05ZVVVSc3F2THQrSWkwdlpjMzRRTHhOClJrT0hrOFZEVUgzcUt1N2UrNGVubUdLVVNUdzRPNFlkQktiSWRJTFpnb3o0YitNL3FVOG5adVpiN2pBVTdOYWkKajMzVDVrbXB3L2d4WHNNUzNzdUpXUE1EUDB3Z1BUZUVRK2J1bUxVWmpLdUVIaWNTL0l5dmtaVlBzRlE4NWlaUwpkNXE2a0ZGUUdjWnFXeFg0dlhDV25Sd3E3cHY3TThJd1RYc1pYSVRuNXB5Z3VTczNKb29GQkg5U3ZNTjRKU25GCmJMK0t6ekduMy9ScXFrTXpMN3FUdkMrNWxVT3UxUmNES21mZXBuVGVaN1IyVnJUQm42NndWMjVHRnBkSDIzN00KOXhJVkJrWEd1U2NvWHVPN1lDcWFrZkt6aXdoRTV4UmRaa3gweXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQQpBNElCQVFCaHRWUEI0OCs4eFZyVmRxM1BIY3k5QkxtVEtrRFl6N2Q0ODJzTG1HczBuVUdGSTFZUDdmaFJPV3ZxCktCTlpkNEI5MUpwU1NoRGUrMHpoNno4WG5Ha01mYnRSYWx0NHEwZ3lKdk9hUWhqQ3ZCcSswTFk5d2NLbXpFdnMKcTRiNUZ5NXNpRUZSekJLTmZtTGwxTTF2cW1hNmFCVnNYUUhPREdzYS83dE5MalZ2ay9PYm52cFg3UFhLa0E3cQpLMTQvV0tBRFBJWm9mb00xMzB4Q1RTYXVpeXROajlnWkx1WU9leEZhblVwNCt2MHBYWS81OFFSNTk2U0ROVTlKClJaeDhwTzBTaUYvZXkxVUZXbmpzdHBjbTQzTFVQKzFwU1hFeVhZOFJrRTI2QzNvdjNaTFNKc2pMbC90aXVqUlgKZUJPOWorWDdzS0R4amdtajBPbWdpVkpIM0YrUAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}, false, "", "localhost.example:443"},
|
||||
{"bad ca encoding", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CA: "^"}, true, "", "localhost.example:443"},
|
||||
{"custom ca file", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CAFile: "testdata/example.crt"}, false, "", "localhost.example:443"},
|
||||
{"bad custom ca file", &Options{Addr: nil, InternalAddr: &url.URL{Scheme: "https", Host: "localhost.example"}, OverrideCertificateName: "*.local", SharedSecret: "shh", CAFile: "testdata/example.crt2"}, true, "", "localhost.example:443"},
|
||||
{"valid with insecure", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, SharedSecret: "shh", WithInsecure: true}, false, "", "localhost.example:8443"},
|
||||
{"valid client round robin", &Options{Addr: &url.URL{Scheme: "https", Host: "localhost.example:8443"}, SharedSecret: "shh", ClientDNSRoundRobin: true}, false, "", "dns:///localhost.example:8443"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewGRPCAuthorizeClient(tt.opts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewGRPCAuthorizeClient() error = %v, wantErr %v", err, tt.wantErr)
|
||||
if !strings.EqualFold(err.Error(), tt.wantErrStr) {
|
||||
t.Errorf("NewGRPCAuthorizeClient() error = %v did not contain wantErr %v", err, tt.wantErrStr)
|
||||
}
|
||||
}
|
||||
if got != nil && got.Conn.Target() != tt.wantTarget {
|
||||
t.Errorf("NewGRPCAuthorizeClient() target = %v expected %v", got.Conn.Target(), tt.wantTarget)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package clients // import "github.com/pomerium/pomerium/proxy/clients"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func Test_grpcTimeoutInterceptor(t *testing.T) {
|
||||
|
||||
mockInvoker := func(sleepTime time.Duration, wantFail bool) grpc.UnaryInvoker {
|
||||
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
|
||||
time.Sleep(sleepTime)
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
t.Fatal("No deadline set")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if ok && now.After(deadline) && !wantFail {
|
||||
t.Errorf("Deadline exceeded, but should not have. now=%v, deadline=%v", now, deadline)
|
||||
} else if now.Before(deadline) && wantFail {
|
||||
t.Errorf("Deadline not exceeded, but should have. now=%v, deadline=%v", now, deadline)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
timeOut := 300 * time.Millisecond
|
||||
to := grpcTimeoutInterceptor(timeOut)
|
||||
|
||||
to(context.Background(), "test", nil, nil, nil, mockInvoker(timeOut*2, true))
|
||||
to(context.Background(), "test", nil, nil, nil, mockInvoker(timeOut/2, false))
|
||||
|
||||
}
|
|
@ -14,11 +14,11 @@ import (
|
|||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/grpc/authorize/client"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
mstore "github.com/pomerium/pomerium/internal/sessions/mock"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
)
|
||||
|
||||
func TestProxy_ForwardAuth(t *testing.T) {
|
||||
|
@ -38,33 +38,33 @@ func TestProxy_ForwardAuth(t *testing.T) {
|
|||
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
authorizer client.Authorizer
|
||||
wantStatus int
|
||||
wantBody string
|
||||
}{
|
||||
{"good redirect not required", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, "Access to some.domain.example is allowed."},
|
||||
{"good verify only, no redirect", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""},
|
||||
{"bad claim", opts, nil, http.MethodGet, nil, nil, "/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{LoadError: sessions.ErrInvalidAudience}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, invalid audience claim (aud)\"}\n"},
|
||||
{"bad naked domain uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "a.naked.domain", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: a.naked.domain url does contain a valid scheme\"}\n"},
|
||||
{"bad naked domain uri verify only", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "a.naked.domain", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: a.naked.domain url does contain a valid scheme\"}\n"},
|
||||
{"bad empty verification uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", " ", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: %20 url does contain a valid scheme\"}\n"},
|
||||
{"bad empty verification uri verify only", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", " ", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: %20 url does contain a valid scheme\"}\n"},
|
||||
{"not authorized", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized verify endpoint", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized expired, redirect to auth", opts, sessions.ErrExpired, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusFound, ""},
|
||||
{"not authorized expired, don't redirect!", opts, sessions.ErrExpired, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"not authorized because of error", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeError: errors.New("authz error")}, http.StatusInternalServerError, "{\"Status\":500,\"Error\":\"Internal Server Error: authz error\"}\n"},
|
||||
{"not authorized expired, do not redirect to auth", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"not authorized, bad audience request uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Audience: []string{"not.domain.example"}, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, invalid audience claim (aud)\"}\n"},
|
||||
{"not authorized, bad audience verify uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://fwdauth.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Audience: []string{"some.domain.example"}, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, invalid audience claim (aud)\"}\n"},
|
||||
{"good redirect not required", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, "Access to some.domain.example is allowed."},
|
||||
{"good verify only, no redirect", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusOK, ""},
|
||||
{"bad claim", opts, nil, http.MethodGet, nil, nil, "/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{LoadError: sessions.ErrInvalidAudience}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, invalid audience claim (aud)\"}\n"},
|
||||
{"bad naked domain uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "a.naked.domain", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: a.naked.domain url does contain a valid scheme\"}\n"},
|
||||
{"bad naked domain uri verify only", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "a.naked.domain", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: a.naked.domain url does contain a valid scheme\"}\n"},
|
||||
{"bad empty verification uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", " ", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: %20 url does contain a valid scheme\"}\n"},
|
||||
{"bad empty verification uri verify only", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", " ", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: %20 url does contain a valid scheme\"}\n"},
|
||||
{"not authorized", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized verify endpoint", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: false}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: user@test.example is not authorized for some.domain.example\"}\n"},
|
||||
{"not authorized expired, redirect to auth", opts, sessions.ErrExpired, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: false}, http.StatusFound, ""},
|
||||
{"not authorized expired, don't redirect!", opts, sessions.ErrExpired, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"not authorized because of error", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeError: errors.New("authz error")}, http.StatusInternalServerError, "{\"Status\":500,\"Error\":\"Internal Server Error: authz error\"}\n"},
|
||||
{"not authorized expired, do not redirect to auth", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: false}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, token is expired (exp)\"}\n"},
|
||||
{"not authorized, bad audience request uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Audience: []string{"not.domain.example"}, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, invalid audience claim (aud)\"}\n"},
|
||||
{"not authorized, bad audience verify uri", opts, nil, http.MethodGet, nil, nil, "https://some.domain.example/", "https://fwdauth.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Audience: []string{"some.domain.example"}, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, "{\"Status\":401,\"Error\":\"Unauthorized: internal/sessions: validation failed, invalid audience claim (aud)\"}\n"},
|
||||
// traefik
|
||||
{"good traefik callback", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedURI: "https://some.domain.example?" + urlutil.QuerySessionEncrypted + "=" + goodEncryptionString}, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad traefik callback bad session", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedURI: "https://some.domain.example?" + urlutil.QuerySessionEncrypted + "=" + goodEncryptionString + "garbage"}, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad traefik callback bad url", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedURI: urlutil.QuerySessionEncrypted + ""}, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"good traefik callback", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedURI: "https://some.domain.example?" + urlutil.QuerySessionEncrypted + "=" + goodEncryptionString}, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad traefik callback bad session", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedURI: "https://some.domain.example?" + urlutil.QuerySessionEncrypted + "=" + goodEncryptionString + "garbage"}, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad traefik callback bad url", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedURI: urlutil.QuerySessionEncrypted + ""}, nil, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
// nginx
|
||||
{"good nginx callback redirect", opts, nil, http.MethodGet, nil, map[string]string{urlutil.QueryRedirectURI: "https://some.domain.example/", urlutil.QuerySessionEncrypted: goodEncryptionString}, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"good nginx callback set session okay but return unauthorized", opts, nil, http.MethodGet, nil, map[string]string{urlutil.QueryRedirectURI: "https://some.domain.example/", urlutil.QuerySessionEncrypted: goodEncryptionString}, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, ""},
|
||||
{"bad nginx callback failed to set session", opts, nil, http.MethodGet, nil, map[string]string{urlutil.QueryRedirectURI: "https://some.domain.example/", urlutil.QuerySessionEncrypted: goodEncryptionString + "nope"}, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"good nginx callback redirect", opts, nil, http.MethodGet, nil, map[string]string{urlutil.QueryRedirectURI: "https://some.domain.example/", urlutil.QuerySessionEncrypted: goodEncryptionString}, "https://some.domain.example/", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"good nginx callback set session okay but return unauthorized", opts, nil, http.MethodGet, nil, map[string]string{urlutil.QueryRedirectURI: "https://some.domain.example/", urlutil.QuerySessionEncrypted: goodEncryptionString}, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusUnauthorized, ""},
|
||||
{"bad nginx callback failed to set session", opts, nil, http.MethodGet, nil, map[string]string{urlutil.QueryRedirectURI: "https://some.domain.example/", urlutil.QuerySessionEncrypted: goodEncryptionString + "nope"}, "https://some.domain.example/verify", "https://some.domain.example", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -17,10 +17,10 @@ import (
|
|||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/grpc/authorize/client"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
@ -74,15 +74,15 @@ func TestProxy_UserDashboard(t *testing.T) {
|
|||
method string
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
session sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
authorizer client.Authorizer
|
||||
|
||||
wantAdminForm bool
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", nil, opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusOK},
|
||||
{"session context error", errors.New("error"), opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{}, false, http.StatusInternalServerError},
|
||||
{"want admin form good admin authorization", nil, opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK},
|
||||
{"is admin but authorization fails", nil, opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError},
|
||||
{"good", nil, opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{}, false, http.StatusOK},
|
||||
{"session context error", errors.New("error"), opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{}, false, http.StatusInternalServerError},
|
||||
{"want admin form good admin authorization", nil, opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{IsAdminResponse: true}, true, http.StatusOK},
|
||||
{"is admin but authorization fails", nil, opts, http.MethodGet, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{IsAdminError: errors.New("err")}, false, http.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -133,15 +133,15 @@ func TestProxy_Impersonate(t *testing.T) {
|
|||
csrf string
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
authorizer client.Authorizer
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"good", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"session load error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"non admin users rejected", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden},
|
||||
{"non admin users rejected on error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusInternalServerError},
|
||||
{"groups", false, opts, nil, http.MethodPost, "user@blah.com", "group1,group2", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, clients.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example"}}, client.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"good", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example"}}, client.MockAuthorize{IsAdminResponse: true}, http.StatusInternalServerError},
|
||||
{"session load error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{LoadError: errors.New("err"), Session: &sessions.State{Email: "user@test.example"}}, client.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
{"non admin users rejected", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, client.MockAuthorize{IsAdminResponse: false}, http.StatusForbidden},
|
||||
{"non admin users rejected on error", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, client.MockAuthorize{IsAdminResponse: true, IsAdminError: errors.New("err")}, http.StatusInternalServerError},
|
||||
{"groups", false, opts, nil, http.MethodPost, "user@blah.com", "group1,group2", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), Email: "user@test.example"}}, client.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -242,16 +242,16 @@ func TestProxy_Callback(t *testing.T) {
|
|||
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
authorizer client.Authorizer
|
||||
wantStatus int
|
||||
wantBody string
|
||||
}{
|
||||
{"good", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"good programmatic", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad decrypt", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QuerySessionEncrypted: "KBEjQ9rnCxaAX-GOqexGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad save session", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad base64", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QuerySessionEncrypted: "^"}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"malformed redirect", opts, http.MethodGet, "http", "example.com", "/", nil, nil, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"good", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"good programmatic", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad decrypt", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QuerySessionEncrypted: "KBEjQ9rnCxaAX-GOqexGw9ivEQURqts3zZ2mNGy0wnVa3SbtM399KlBq2nZ-9wM21FfsZX52er4jlmC7kPEKM3P7uZ41zR0zeys1-_74a5tQp-vsf1WXZfRsgVOuBcWPkMiWEoc379JFHxGDudp5VhU8B-dcQt4f3_PtLTHARkuH54io1Va2gNMq4Hiy8sQ1MPGCQeltH_JMzzdDpXdmdusWrXUvCGkba24muvAV06D8XRVJj6Iu9eK94qFnqcHc7wzziEbb8ADBues9dwbtb6jl8vMWz5rN6XvXqA5YpZv_MQZlsrO4oXFFQDevdgB84cX1tVbVu6qZvK_yQBZqzpOjWA9uIaoSENMytoXuWAlFO_sXjswfX8JTNdGwzB7qQRNPqxVG_sM_tzY3QhPm8zqwEzsXG5DokxZfVt2I5WJRUEovFDb4BnK9KFnnkEzLEdMudixVnXeGmTtycgJvoTeTCQRPfDYkcgJ7oKf4tGea-W7z5UAVa2RduJM9ZoM6YtJX7jgDm__PvvqcE0knJUF87XHBzdcOjoDF-CUze9xDJgNBlvPbJqVshKrwoqSYpePSDH9GUCNKxGequW3Ma8GvlFfhwd0rK6IZG-XWkyk0XSWQIGkDSjAvhB1wsOusCCguDjbpVZpaW5MMyTkmx68pl6qlIKT5UCcrVPl4ix5ZEj91mUDF0O1t04haD7VZuLVFXVGmqtFrBKI76sdYN-zkokaa1_chPRTyqMQFlqu_8LD6-RiK3UccGM-dEmnX72i91NP9F9OK0WJr9Cheup1C_P0mjqAO4Cb8oIHm0Oxz_mRqv5QbTGJtb3xwPLPuVjVCiE4gGBcuU2ixpSVf5HUF7y1KicVMCKiX9ATCBtg8sTdQZQnPEtHcHHAvdsnDVwev1LGfqA-Gdvg="}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad save session", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{SaveError: errors.New("hi")}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad base64", opts, http.MethodGet, "http", "example.com", "/", nil, map[string]string{urlutil.QuerySessionEncrypted: "^"}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"malformed redirect", opts, http.MethodGet, "http", "example.com", "/", nil, nil, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -383,16 +383,16 @@ func TestProxy_ProgrammaticCallback(t *testing.T) {
|
|||
|
||||
cipher encoding.MarshalUnmarshaler
|
||||
sessionStore sessions.SessionStore
|
||||
authorizer clients.Authorizer
|
||||
authorizer client.Authorizer
|
||||
wantStatus int
|
||||
wantBody string
|
||||
}{
|
||||
{"good", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"good programmatic", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad decrypt", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QuerySessionEncrypted: goodEncryptionString + cryptutil.NewBase64Key()}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad save session", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{SaveError: errors.New("hi")}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad base64", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QuerySessionEncrypted: "^"}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"malformed redirect", opts, http.MethodGet, "http://pomerium.io/", nil, nil, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, clients.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"good", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"good programmatic", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryCallbackURI: "ok", urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusFound, ""},
|
||||
{"bad decrypt", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QuerySessionEncrypted: goodEncryptionString + cryptutil.NewBase64Key()}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad save session", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QuerySessionEncrypted: goodEncryptionString}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{SaveError: errors.New("hi")}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"bad base64", opts, http.MethodGet, "http://pomerium.io/", nil, map[string]string{urlutil.QuerySessionEncrypted: "^"}, &mock.Encoder{MarshalResponse: []byte("x")}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
{"malformed redirect", opts, http.MethodGet, "http://pomerium.io/", nil, nil, &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, client.MockAuthorize{AuthorizeResponse: true}, http.StatusBadRequest, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -62,25 +62,32 @@ func (p *Proxy) refresh(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||
// 1 - build a signed url to call refresh on authenticate service
|
||||
refreshURI := *p.authenticateRefreshURL
|
||||
q := refreshURI.Query()
|
||||
q.Set("ati", s.AccessTokenID) // hash value points to parent token
|
||||
q.Set("aud", urlutil.StripPort(r.Host)) // request's audience, this route
|
||||
q.Set(urlutil.QueryAccessTokenID, s.AccessTokenID) // hash value points to parent token
|
||||
q.Set(urlutil.QueryAudience, urlutil.StripPort(r.Host)) // request's audience, this route
|
||||
refreshURI.RawQuery = q.Encode()
|
||||
signedRefreshURL := urlutil.NewSignedURL(p.SharedKey, &refreshURI).String()
|
||||
|
||||
// 2 - http call to authenticate service
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, signedRefreshURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy: backend refresh: new request: %v", err)
|
||||
return nil, fmt.Errorf("proxy: refresh request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("X-Requested-With", "XmlHttpRequest")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
res, err := httputil.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy: fetch %v: %w", signedRefreshURL, err)
|
||||
return nil, fmt.Errorf("proxy: client err %s: %w", signedRefreshURL, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
jwtBytes, err := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// auth couldn't refersh the session, delete the session and reload via 302
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("proxy: backend refresh failed: %s", jwtBytes)
|
||||
}
|
||||
|
||||
// 3 - save refreshed session to the client's session store
|
||||
if err = p.sessionStore.SaveSession(w, r, jwtBytes); err != nil {
|
||||
|
@ -99,10 +106,10 @@ func (p *Proxy) refresh(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
|||
|
||||
func (p *Proxy) redirectToSignin(w http.ResponseWriter, r *http.Request) error {
|
||||
s, err := sessions.FromContext(r.Context())
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
if s != nil && err != nil && s.Programmatic {
|
||||
return httputil.NewError(http.StatusUnauthorized, err)
|
||||
}
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
signinURL := *p.authenticateSigninURL
|
||||
q := signinURL.Query()
|
||||
q.Set(urlutil.QueryRedirectURI, urlutil.GetAbsoluteURL(r).String())
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/grpc/authorize/client"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
mstore "github.com/pomerium/pomerium/internal/sessions/mock"
|
||||
|
@ -29,28 +29,32 @@ func TestProxy_AuthenticateSession(t *testing.T) {
|
|||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
errOnFailure bool
|
||||
session sessions.SessionStore
|
||||
ctxError error
|
||||
provider identity.Authenticator
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
refreshURL string
|
||||
name string
|
||||
refreshRespStatus int
|
||||
errOnFailure bool
|
||||
session sessions.SessionStore
|
||||
ctxError error
|
||||
provider identity.Authenticator
|
||||
encoder encoding.MarshalUnmarshaler
|
||||
refreshURL string
|
||||
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, nil, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"invalid session", false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, errors.New("hi"), identity.MockProvider{}, &mock.Encoder{}, "", http.StatusFound},
|
||||
{"expired", false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"expired and programmatic", false, &mstore.Store{Session: &sessions.State{Programmatic: true, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"invalid session and programmatic", false, &mstore.Store{Session: &sessions.State{Programmatic: true, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, errors.New("hi"), identity.MockProvider{}, &mock.Encoder{}, "", http.StatusUnauthorized},
|
||||
{"expired and refreshed ok", false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"expired and save failed", false, &mstore.Store{SaveError: errors.New("err"), Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusFound},
|
||||
{"expired and unmarshal failed", false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{UnmarshalError: errors.New("err")}, "", http.StatusFound},
|
||||
{"good", 200, false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, nil, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"invalid session", 200, false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, errors.New("hi"), identity.MockProvider{}, &mock.Encoder{}, "", http.StatusFound},
|
||||
{"expired", 200, false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"expired and programmatic", 200, false, &mstore.Store{Session: &sessions.State{Programmatic: true, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"invalid session and programmatic", 200, false, &mstore.Store{Session: &sessions.State{Programmatic: true, Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, errors.New("hi"), identity.MockProvider{}, &mock.Encoder{}, "", http.StatusUnauthorized},
|
||||
{"expired and refreshed ok", 200, false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusOK},
|
||||
{"expired and save failed", 200, false, &mstore.Store{SaveError: errors.New("err"), Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusFound},
|
||||
{"expired and unmarshal failed", 200, false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{UnmarshalError: errors.New("err")}, "", http.StatusFound},
|
||||
{"expired and malformed session", 200, false, &mstore.Store{Session: nil}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusFound},
|
||||
{"expired and refresh failed", 500, false, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{}, &mock.Encoder{}, "", http.StatusFound},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(tt.refreshRespStatus)
|
||||
fmt.Fprintln(w, "REFRESH GOOD")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
@ -94,17 +98,17 @@ func TestProxy_AuthorizeSession(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
session sessions.SessionStore
|
||||
authzClient clients.Authorizer
|
||||
authzClient client.Authorizer
|
||||
|
||||
ctxError error
|
||||
provider identity.Authenticator
|
||||
|
||||
wantStatus int
|
||||
}{
|
||||
{"user is authorized", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, clients.MockAuthorize{AuthorizeResponse: true}, nil, identity.MockProvider{}, http.StatusOK},
|
||||
{"user is not authorized", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, clients.MockAuthorize{AuthorizeResponse: false}, nil, identity.MockProvider{}, http.StatusForbidden},
|
||||
{"ctx error", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, clients.MockAuthorize{AuthorizeResponse: true}, errors.New("hi"), identity.MockProvider{}, http.StatusInternalServerError},
|
||||
{"authz client error", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, clients.MockAuthorize{AuthorizeError: errors.New("err")}, nil, identity.MockProvider{}, http.StatusInternalServerError},
|
||||
{"user is authorized", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: true}, nil, identity.MockProvider{}, http.StatusOK},
|
||||
{"user is not authorized", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: false}, nil, identity.MockProvider{}, http.StatusForbidden},
|
||||
{"ctx error", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: true}, errors.New("hi"), identity.MockProvider{}, http.StatusInternalServerError},
|
||||
{"authz client error", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeError: errors.New("err")}, nil, identity.MockProvider{}, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -17,6 +17,8 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
"github.com/pomerium/pomerium/internal/grpc"
|
||||
"github.com/pomerium/pomerium/internal/grpc/authorize/client"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
|
@ -27,7 +29,6 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/telemetry/metrics"
|
||||
"github.com/pomerium/pomerium/internal/tripper"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/proxy/clients"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -78,7 +79,7 @@ type Proxy struct {
|
|||
|
||||
authorizeURL *url.URL
|
||||
|
||||
AuthorizeClient clients.Authorizer
|
||||
AuthorizeClient client.Authorizer
|
||||
|
||||
encoder encoding.Unmarshaler
|
||||
cookieOptions *cookie.Options
|
||||
|
@ -151,17 +152,21 @@ func New(opts config.Options) (*Proxy, error) {
|
|||
metrics.AddPolicyCountCallback("proxy", func() int64 {
|
||||
return int64(len(opts.Policies))
|
||||
})
|
||||
p.AuthorizeClient, err = clients.NewAuthorizeClient("grpc",
|
||||
&clients.Options{
|
||||
Addr: p.authorizeURL,
|
||||
OverrideCertificateName: opts.OverrideCertificateName,
|
||||
SharedSecret: opts.SharedKey,
|
||||
CA: opts.CA,
|
||||
CAFile: opts.CAFile,
|
||||
RequestTimeout: opts.GRPCClientTimeout,
|
||||
ClientDNSRoundRobin: opts.GRPCClientDNSRoundRobin,
|
||||
WithInsecure: opts.GRPCInsecure,
|
||||
})
|
||||
|
||||
authzConn, err := grpc.NewGRPCClientConn(&grpc.Options{
|
||||
Addr: p.authorizeURL,
|
||||
OverrideCertificateName: opts.OverrideCertificateName,
|
||||
CA: opts.CA,
|
||||
CAFile: opts.CAFile,
|
||||
RequestTimeout: opts.GRPCClientTimeout,
|
||||
ClientDNSRoundRobin: opts.GRPCClientDNSRoundRobin,
|
||||
WithInsecure: opts.GRPCInsecure,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.AuthorizeClient, err = client.New(authzConn)
|
||||
return p, err
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue