cmd/pomerium : refactor main to more testable (#112)
- cmd/pomerium: refactor global timeouts to be configurable - cmd/pomerium: add tests - cmd/pomerium: remove debug flag, set with env vars only - cmd/pomerium: global ping now returns version not OK - proxy: validate shared secret encoding and length - docs: add timeout to example policy - docs: document timeouts and cors - docs: update pomerium logo - docs: add policy authorization docs
|
@ -1,6 +1,7 @@
|
||||||
package main // import "github.com/pomerium/pomerium/cmd/pomerium"
|
package main // import "github.com/pomerium/pomerium/cmd/pomerium"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -21,116 +22,148 @@ import (
|
||||||
"github.com/pomerium/pomerium/proxy"
|
"github.com/pomerium/pomerium/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var versionFlag = flag.Bool("version", false, "prints the version")
|
||||||
debugFlag = flag.Bool("debug", false, "run server in debug mode, changes log output to STDOUT and level to info")
|
|
||||||
versionFlag = flag.Bool("version", false, "prints the version")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *versionFlag {
|
if *versionFlag {
|
||||||
fmt.Printf("%s\n", version.FullVersion())
|
fmt.Println(version.FullVersion())
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
mainOpts, err := optionsFromEnvConfig()
|
opt, err := parseOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: settings error")
|
log.Fatal().Err(err).Msg("cmd/pomerium: options")
|
||||||
}
|
}
|
||||||
if *debugFlag || mainOpts.Debug {
|
|
||||||
log.SetDebugMode()
|
|
||||||
}
|
|
||||||
if mainOpts.LogLevel != "" {
|
|
||||||
log.SetLevel(mainOpts.LogLevel)
|
|
||||||
}
|
|
||||||
log.Info().Str("version", version.FullVersion()).Str("user-agent", version.UserAgent()).Str("service", mainOpts.Services).Msg("cmd/pomerium")
|
|
||||||
|
|
||||||
grpcAuth := middleware.NewSharedSecretCred(mainOpts.SharedKey)
|
grpcAuth := middleware.NewSharedSecretCred(opt.SharedKey)
|
||||||
grpcOpts := []grpc.ServerOption{grpc.UnaryInterceptor(grpcAuth.ValidateRequest)}
|
grpcOpts := []grpc.ServerOption{grpc.UnaryInterceptor(grpcAuth.ValidateRequest)}
|
||||||
grpcServer := grpc.NewServer(grpcOpts...)
|
grpcServer := grpc.NewServer(grpcOpts...)
|
||||||
|
|
||||||
var authenticateService *authenticate.Authenticate
|
mux := http.NewServeMux()
|
||||||
var authHost string
|
mux.HandleFunc("/ping", func(rw http.ResponseWriter, _ *http.Request) {
|
||||||
if mainOpts.Services == "all" || mainOpts.Services == "authenticate" {
|
|
||||||
opts, err := authenticate.OptionsFromEnvConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: authenticate settings")
|
|
||||||
}
|
|
||||||
authenticateService, err = authenticate.New(opts)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: new authenticate")
|
|
||||||
}
|
|
||||||
authHost = urlutil.StripPort(opts.AuthenticateURL.Host)
|
|
||||||
pbAuthenticate.RegisterAuthenticatorServer(grpcServer, authenticateService)
|
|
||||||
}
|
|
||||||
|
|
||||||
var authorizeService *authorize.Authorize
|
|
||||||
if mainOpts.Services == "all" || mainOpts.Services == "authorize" {
|
|
||||||
opts, err := authorize.OptionsFromEnvConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: authorize settings")
|
|
||||||
}
|
|
||||||
authorizeService, err = authorize.New(opts)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: new authorize")
|
|
||||||
}
|
|
||||||
pbAuthorize.RegisterAuthorizerServer(grpcServer, authorizeService)
|
|
||||||
}
|
|
||||||
|
|
||||||
var proxyService *proxy.Proxy
|
|
||||||
if mainOpts.Services == "all" || mainOpts.Services == "proxy" {
|
|
||||||
proxyOpts, err := proxy.OptionsFromEnvConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: proxy settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyService, err = proxy.New(proxyOpts)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("cmd/pomerium: new proxy")
|
|
||||||
}
|
|
||||||
// cleanup our RPC services
|
|
||||||
defer proxyService.AuthenticateClient.Close()
|
|
||||||
defer proxyService.AuthorizeClient.Close()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
topMux := http.NewServeMux()
|
|
||||||
topMux.HandleFunc("/ping", func(rw http.ResponseWriter, _ *http.Request) {
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(rw, "OK")
|
fmt.Fprintf(rw, version.UserAgent())
|
||||||
})
|
})
|
||||||
if authenticateService != nil {
|
|
||||||
topMux.Handle(authHost+"/", authenticateService.Handler())
|
_, err = newAuthenticateService(opt.Services, mux, grpcServer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("cmd/pomerium: authenticate")
|
||||||
}
|
}
|
||||||
if proxyService != nil {
|
|
||||||
topMux.Handle("/", proxyService.Handler())
|
_, err = newAuthorizeService(opt.Services, grpcServer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("cmd/pomerium: authorize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = newProxyService(opt.Services, mux)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("cmd/pomerium: proxy")
|
||||||
|
}
|
||||||
|
// defer statements ignored anyway : https://stackoverflow.com/a/17888654
|
||||||
|
// defer proxyService.AuthenticateClient.Close()
|
||||||
|
// defer proxyService.AuthorizeClient.Close()
|
||||||
|
|
||||||
httpOpts := &https.Options{
|
httpOpts := &https.Options{
|
||||||
Addr: mainOpts.Addr,
|
Addr: opt.Addr,
|
||||||
Cert: mainOpts.Cert,
|
Cert: opt.Cert,
|
||||||
Key: mainOpts.Key,
|
Key: opt.Key,
|
||||||
CertFile: mainOpts.CertFile,
|
CertFile: opt.CertFile,
|
||||||
KeyFile: mainOpts.KeyFile,
|
KeyFile: opt.KeyFile,
|
||||||
|
ReadTimeout: opt.ReadTimeout,
|
||||||
|
WriteTimeout: opt.WriteTimeout,
|
||||||
|
ReadHeaderTimeout: opt.ReadHeaderTimeout,
|
||||||
|
IdleTimeout: opt.IdleTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
if mainOpts.HTTPRedirectAddr != "" {
|
if srv, err := startRedirectServer(opt.HTTPRedirectAddr); err != nil {
|
||||||
// stand up another http server that just redirect HTTP to HTTPS traffic
|
log.Debug().Err(err).Msg("cmd/pomerium: http redirect server not started")
|
||||||
srv := &http.Server{
|
|
||||||
Addr: mainOpts.HTTPRedirectAddr,
|
|
||||||
ReadTimeout: 5 * time.Second,
|
|
||||||
WriteTimeout: 5 * time.Second,
|
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Connection", "close")
|
|
||||||
url := fmt.Sprintf("https://%s%s", urlutil.StripPort(r.Host), r.URL.String())
|
|
||||||
http.Redirect(w, r, url, http.StatusMovedPermanently)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
log.Info().Str("Addr", mainOpts.HTTPRedirectAddr).Msg("cmd/pomerium: http redirect server started")
|
|
||||||
go func() { log.Fatal().Err(srv.ListenAndServe()).Msg("cmd/pomerium: http server") }()
|
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Msg("cmd/pomerium: http redirect server not started")
|
defer srv.Close()
|
||||||
|
}
|
||||||
|
if err := https.ListenAndServeTLS(httpOpts, mux, grpcServer); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("cmd/pomerium: https server")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
log.Fatal().Err(https.ListenAndServeTLS(httpOpts, topMux, grpcServer)).Msg("cmd/pomerium: https server")
|
|
||||||
|
// startRedirectServer starts a http server that redirect HTTP to HTTPS traffic
|
||||||
|
func startRedirectServer(addr string) (*http.Server, error) {
|
||||||
|
if addr == "" {
|
||||||
|
return nil, errors.New("no http redirect addr provided")
|
||||||
|
}
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 5 * time.Second,
|
||||||
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
url := fmt.Sprintf("https://%s%s", urlutil.StripPort(r.Host), r.URL.String())
|
||||||
|
http.Redirect(w, r, url, http.StatusMovedPermanently)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
log.Info().Str("Addr", addr).Msg("cmd/pomerium: http redirect server started")
|
||||||
|
go func() { log.Error().Err(srv.ListenAndServe()).Msg("cmd/pomerium: http server closed") }()
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAuthenticateService(s string, mux *http.ServeMux, rpc *grpc.Server) (*authenticate.Authenticate, error) {
|
||||||
|
if !isAuthenticate(s) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
opts, err := authenticate.OptionsFromEnvConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service, err := authenticate.New(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbAuthenticate.RegisterAuthenticatorServer(rpc, service)
|
||||||
|
mux.Handle(urlutil.StripPort(opts.AuthenticateURL.Host)+"/", service.Handler())
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAuthorizeService(s string, rpc *grpc.Server) (*authorize.Authorize, error) {
|
||||||
|
if !isAuthorize(s) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
opts, err := authorize.OptionsFromEnvConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service, err := authorize.New(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbAuthorize.RegisterAuthorizerServer(rpc, service)
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProxyService(s string, mux *http.ServeMux) (*proxy.Proxy, error) {
|
||||||
|
if !isProxy(s) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
opts, err := proxy.OptionsFromEnvConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service, err := proxy.New(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mux.Handle("/", service.Handler())
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOptions() (*Options, error) {
|
||||||
|
o, err := optionsFromEnvConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if o.Debug {
|
||||||
|
log.SetDebugMode()
|
||||||
|
}
|
||||||
|
if o.LogLevel != "" {
|
||||||
|
log.SetLevel(o.LogLevel)
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
199
cmd/pomerium/main_test.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/middleware"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_startRedirectServer(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"empty", "", "", true},
|
||||||
|
{":http", ":http", ":http", false},
|
||||||
|
{"localhost:80", "localhost:80", "localhost:80", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := startRedirectServer(tt.addr)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("startRedirectServer() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != nil {
|
||||||
|
defer got.Close()
|
||||||
|
ts := httptest.NewServer(got.Handler)
|
||||||
|
defer ts.Close()
|
||||||
|
_, err := http.Get(ts.URL)
|
||||||
|
if !strings.Contains(err.Error(), "https") {
|
||||||
|
t.Errorf("startRedirectServer() = %v, want %v", err, tt.want)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_newAuthenticateService(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
grpcAuth := middleware.NewSharedSecretCred("test")
|
||||||
|
grpcOpts := []grpc.ServerOption{grpc.UnaryInterceptor(grpcAuth.ValidateRequest)}
|
||||||
|
grpcServer := grpc.NewServer(grpcOpts...)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
envKey string
|
||||||
|
envValue string
|
||||||
|
|
||||||
|
wantHostname string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"wrong service", "proxy", "", "", "", false},
|
||||||
|
{"bad", "authenticate", "SHARED_SECRET", "error!", "", true},
|
||||||
|
{"bad emv", "authenticate", "COOKIE_REFRESH", "error!", "", true},
|
||||||
|
{"good", "authenticate", "IDP_CLIENT_ID", "test", "auth.server.com", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
os.Setenv("IDP_PROVIDER", "google")
|
||||||
|
os.Setenv("IDP_CLIENT_SECRET", "TEST")
|
||||||
|
os.Setenv("SHARED_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=")
|
||||||
|
os.Setenv("COOKIE_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=")
|
||||||
|
os.Setenv("AUTHENTICATE_SERVICE_URL", "http://auth.server.com")
|
||||||
|
defer os.Unsetenv("IDP_CLIENT_ID")
|
||||||
|
defer os.Unsetenv("IDP_CLIENT_SECRET")
|
||||||
|
defer os.Unsetenv("SHARED_SECRET")
|
||||||
|
defer os.Unsetenv("COOKIE_SECRET")
|
||||||
|
|
||||||
|
os.Setenv(tt.envKey, tt.envValue)
|
||||||
|
defer os.Unsetenv(tt.envKey)
|
||||||
|
|
||||||
|
_, err := newAuthenticateService(tt.s, mux, grpcServer)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("newAuthenticateService() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_newAuthorizeService(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
grpcAuth := middleware.NewSharedSecretCred("test")
|
||||||
|
grpcOpts := []grpc.ServerOption{grpc.UnaryInterceptor(grpcAuth.ValidateRequest)}
|
||||||
|
grpcServer := grpc.NewServer(grpcOpts...)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
envKey string
|
||||||
|
envValue string
|
||||||
|
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"wrong service", "proxy", "", "", false},
|
||||||
|
{"bad option parsing", "authorize", "SHARED_SECRET", "false", true},
|
||||||
|
{"bad env", "authorize", "POLICY", "error!", true},
|
||||||
|
{"good", "authorize", "SHARED_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
os.Setenv("POLICY", "LSBmcm9tOiBodHRwYmluLmNvcnAuYmV5b25kcGVyaW1ldGVyLmNvbQogIHRvOiBodHRwOi8vaHR0cGJpbgogIGFsbG93ZWRfZG9tYWluczoKICAgIC0gcG9tZXJpdW0uaW8KICBjb3JzX2FsbG93X3ByZWZsaWdodDogdHJ1ZQogIHRpbWVvdXQ6IDMwcwotIGZyb206IGV4dGVybmFsLWh0dHBiaW4uY29ycC5iZXlvbmRwZXJpbWV0ZXIuY29tCiAgdG86IGh0dHBiaW4ub3JnCiAgYWxsb3dlZF9kb21haW5zOgogICAgLSBnbWFpbC5jb20KLSBmcm9tOiB3ZWlyZGx5c3NsLmNvcnAuYmV5b25kcGVyaW1ldGVyLmNvbQogIHRvOiBodHRwOi8vbmV2ZXJzc2wuY29tCiAgYWxsb3dlZF91c2VyczoKICAgIC0gYmRkQHBvbWVyaXVtLmlvCiAgYWxsb3dlZF9ncm91cHM6CiAgICAtIGFkbWlucwogICAgLSBkZXZlbG9wZXJzCi0gZnJvbTogaGVsbG8uY29ycC5iZXlvbmRwZXJpbWV0ZXIuY29tCiAgdG86IGh0dHA6Ly9oZWxsbzo4MDgwCiAgYWxsb3dlZF9ncm91cHM6CiAgICAtIGFkbWlucw==")
|
||||||
|
os.Setenv("COOKIE_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=")
|
||||||
|
defer os.Unsetenv("SHARED_SECRET")
|
||||||
|
defer os.Unsetenv("COOKIE_SECRET")
|
||||||
|
os.Setenv(tt.envKey, tt.envValue)
|
||||||
|
defer os.Unsetenv(tt.envKey)
|
||||||
|
_, err := newAuthorizeService(tt.s, grpcServer)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("newAuthorizeService() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_newProxyeService(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
envKey string
|
||||||
|
envValue string
|
||||||
|
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"wrong service", "authenticate", "", "", false},
|
||||||
|
{"bad option parsing", "proxy", "SHARED_SECRET", "false", true},
|
||||||
|
{"bad env", "proxy", "POLICY", "error!", true},
|
||||||
|
{"bad encoding for envar", "proxy", "COOKIE_REFRESH", "error!", true},
|
||||||
|
{"good", "proxy", "SHARED_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
os.Setenv("AUTHENTICATE_SERVICE_URL", "https://authenticate.example.com")
|
||||||
|
os.Setenv("AUTHORIZE_SERVICE_URL", "https://authorize.example.com")
|
||||||
|
os.Setenv("POLICY", "LSBmcm9tOiBodHRwYmluLmNvcnAuYmV5b25kcGVyaW1ldGVyLmNvbQogIHRvOiBodHRwOi8vaHR0cGJpbgogIGFsbG93ZWRfZG9tYWluczoKICAgIC0gcG9tZXJpdW0uaW8KICBjb3JzX2FsbG93X3ByZWZsaWdodDogdHJ1ZQogIHRpbWVvdXQ6IDMwcwotIGZyb206IGV4dGVybmFsLWh0dHBiaW4uY29ycC5iZXlvbmRwZXJpbWV0ZXIuY29tCiAgdG86IGh0dHBiaW4ub3JnCiAgYWxsb3dlZF9kb21haW5zOgogICAgLSBnbWFpbC5jb20KLSBmcm9tOiB3ZWlyZGx5c3NsLmNvcnAuYmV5b25kcGVyaW1ldGVyLmNvbQogIHRvOiBodHRwOi8vbmV2ZXJzc2wuY29tCiAgYWxsb3dlZF91c2VyczoKICAgIC0gYmRkQHBvbWVyaXVtLmlvCiAgYWxsb3dlZF9ncm91cHM6CiAgICAtIGFkbWlucwogICAgLSBkZXZlbG9wZXJzCi0gZnJvbTogaGVsbG8uY29ycC5iZXlvbmRwZXJpbWV0ZXIuY29tCiAgdG86IGh0dHA6Ly9oZWxsbzo4MDgwCiAgYWxsb3dlZF9ncm91cHM6CiAgICAtIGFkbWlucw==")
|
||||||
|
os.Setenv("COOKIE_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=")
|
||||||
|
defer os.Unsetenv("AUTHENTICATE_SERVICE_URL")
|
||||||
|
defer os.Unsetenv("AUTHORIZE_SERVICE_URL")
|
||||||
|
defer os.Unsetenv("SHARED_SECRET")
|
||||||
|
defer os.Unsetenv("COOKIE_SECRET")
|
||||||
|
os.Setenv(tt.envKey, tt.envValue)
|
||||||
|
defer os.Unsetenv(tt.envKey)
|
||||||
|
_, err := newProxyService(tt.s, mux)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("newProxyService() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseOptions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
envKey string
|
||||||
|
envValue string
|
||||||
|
|
||||||
|
want *Options
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"no shared secret", "", "", nil, true},
|
||||||
|
{"good", "SHARED_SECRET", "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", &Options{Services: "all", SharedKey: "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", LogLevel: "debug"}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
os.Setenv(tt.envKey, tt.envValue)
|
||||||
|
defer os.Unsetenv(tt.envKey)
|
||||||
|
|
||||||
|
got, err := parseOptions()
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseOptions() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("parseOptions()\n")
|
||||||
|
t.Errorf("got: %+v\n", got)
|
||||||
|
t.Errorf("want: %+v\n", tt.want)
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package main // import "github.com/pomerium/pomerium/cmd/pomerium"
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pomerium/envconfig"
|
"github.com/pomerium/envconfig"
|
||||||
)
|
)
|
||||||
|
@ -43,6 +44,12 @@ type Options struct {
|
||||||
// to HTTPS redirect server on. For example, ":http" would start a server
|
// to HTTPS redirect server on. For example, ":http" would start a server
|
||||||
// on port 80. If empty, no redirect server is started.
|
// on port 80. If empty, no redirect server is started.
|
||||||
HTTPRedirectAddr string `envconfig:"HTTP_REDIRECT_ADDR"`
|
HTTPRedirectAddr string `envconfig:"HTTP_REDIRECT_ADDR"`
|
||||||
|
|
||||||
|
// Timeout settings : https://github.com/pomerium/pomerium/issues/40
|
||||||
|
ReadTimeout time.Duration `envconfig:"TIMEOUT_READ"`
|
||||||
|
WriteTimeout time.Duration `envconfig:"TIMEOUT_WRITE"`
|
||||||
|
ReadHeaderTimeout time.Duration `envconfig:"TIMEOUT_READ_HEADER"`
|
||||||
|
IdleTimeout time.Duration `envconfig:"TIMEOUT_IDLE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultOptions = &Options{
|
var defaultOptions = &Options{
|
||||||
|
@ -68,8 +75,8 @@ func optionsFromEnvConfig() (*Options, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidService checks to see if a service is a valid service mode
|
// isValidService checks to see if a service is a valid service mode
|
||||||
func isValidService(service string) bool {
|
func isValidService(s string) bool {
|
||||||
switch service {
|
switch s {
|
||||||
case
|
case
|
||||||
"all",
|
"all",
|
||||||
"proxy",
|
"proxy",
|
||||||
|
@ -79,3 +86,33 @@ func isValidService(service string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAuthenticate(s string) bool {
|
||||||
|
switch s {
|
||||||
|
case
|
||||||
|
"all",
|
||||||
|
"authenticate":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAuthorize(s string) bool {
|
||||||
|
switch s {
|
||||||
|
case
|
||||||
|
"all",
|
||||||
|
"authorize":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProxy(s string) bool {
|
||||||
|
switch s {
|
||||||
|
case
|
||||||
|
"all",
|
||||||
|
"proxy":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -66,3 +66,26 @@ func Test_isValidService(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_isAuthenticate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
service string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"proxy", "proxy", false},
|
||||||
|
{"all", "all", true},
|
||||||
|
{"authenticate", "authenticate", true},
|
||||||
|
{"authenticate bad case", "AuThenticate", false},
|
||||||
|
{"authorize implemented", "authorize", false},
|
||||||
|
{"jiberish", "xd23", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := isAuthenticate(tt.service); got != tt.want {
|
||||||
|
t.Errorf("isAuthenticate() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1
docs/.vuepress/public/logo-long-white.svg
Normal file
After Width: | Height: | Size: 5.2 KiB |
1
docs/.vuepress/public/logo-long.svg
Normal file
After Width: | Height: | Size: 5.1 KiB |
1
docs/.vuepress/public/logo-no-text.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 597.47 295"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#443266;}</style></defs><title>logo-no-text</title><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-2" d="M90.42,497c0-162.92,133.75-295,298.73-295S687.89,334.08,687.89,497" transform="translate(-90.42 -202)"/><path class="cls-1" d="M599.29,335.48a39.21,39.21,0,0,0-39.2-39.21H218.22A39.21,39.21,0,0,0,179,335.48V497h27.09V451.87h0c0-30.64,24.6-55.47,54.89-55.47s54.84,24.83,54.88,55.47h0V497h19.6V451.87h0c0-30.64,24.59-55.47,54.88-55.47s54.85,24.83,54.89,55.47h0V497h19.6V451.87h0c0-30.64,24.6-55.47,54.89-55.47s54.84,24.83,54.88,55.47h0V497h24.66ZM206.11,376.21c0-30.67,24.57-55.54,54.89-55.54s54.88,24.87,54.88,55.54Zm129.37,0c0-30.67,24.58-55.54,54.89-55.54s54.89,24.87,54.89,55.54Zm129.38,0c0-30.67,24.57-55.54,54.89-55.54s54.88,24.87,54.88,55.54Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
docs/.vuepress/public/logo-only.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
1
docs/.vuepress/public/logo-stacked.svg
Normal file
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1,023 B After Width: | Height: | Size: 5.5 KiB |
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
home: true
|
home: true
|
||||||
heroImage: logo.svg
|
heroImage: logo.svg
|
||||||
heroText: Pomerium
|
heroText: " "
|
||||||
tagline: Identity-aware access proxy.
|
tagline: Pomerium is a context and identity aware access proxy.
|
||||||
actionText: Read the docs
|
actionText: Read the docs
|
||||||
actionLink: /docs/
|
actionLink: /docs/
|
||||||
---
|
---
|
||||||
|
|
|
@ -61,10 +61,75 @@ head -c32 /dev/urandom | base64
|
||||||
- Filetype: `json` or `yaml`
|
- Filetype: `json` or `yaml`
|
||||||
- Required
|
- Required
|
||||||
|
|
||||||
Policy contains the routes, and their access policies. For example,
|
Policy contains route specific settings, and access control details. For example,
|
||||||
|
|
||||||
<<< @/policy.example.yaml
|
<<< @/policy.example.yaml
|
||||||
|
|
||||||
|
A list of policy configuration variables follows.
|
||||||
|
|
||||||
|
#### From
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `from`
|
||||||
|
- Type: `string` domain
|
||||||
|
- Required
|
||||||
|
- Example: `httpbin.corp.example.com`
|
||||||
|
|
||||||
|
`From` is externally accessible source of the proxied request.
|
||||||
|
|
||||||
|
#### To
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `to`
|
||||||
|
- Type: `string` domain
|
||||||
|
- Required
|
||||||
|
- Example: `httpbin` , `192.1.20.12:20`, `http://neverssl.com`
|
||||||
|
|
||||||
|
`To` is the destination of a proxied request. It can be an internal resource, or an external resource.
|
||||||
|
|
||||||
|
#### Allowed Users
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `allowed_users`
|
||||||
|
- Type: collection of `strings`
|
||||||
|
- Required
|
||||||
|
- Example: `alice@pomerium.io` , `bob@contractor.co`
|
||||||
|
|
||||||
|
Allowed users is a collection of whitelisted users to authorize for a given route.
|
||||||
|
|
||||||
|
#### Allowed Groups
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `allowed_groups`
|
||||||
|
- Type: collection of `strings`
|
||||||
|
- Required
|
||||||
|
- Example: `admins` , `support@company.com`
|
||||||
|
|
||||||
|
Allowed groups is a collection of whitelisted groups to authorize for a given route.
|
||||||
|
|
||||||
|
#### Allowed Domains
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `allowed_domains`
|
||||||
|
- Type: collection of `strings`
|
||||||
|
- Required
|
||||||
|
- Example: `pomerium.io` , `gmail.com`
|
||||||
|
|
||||||
|
Allowed domains is a collection of whitelisted domains to authorize for a given route.
|
||||||
|
|
||||||
|
#### CORS Preflight
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `cors_allow_preflight`
|
||||||
|
- Type: `bool`
|
||||||
|
- Optional
|
||||||
|
- Default: `false`
|
||||||
|
|
||||||
|
Allow unauthenticated HTTP OPTIONS requests as [per the CORS spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests).
|
||||||
|
|
||||||
|
### Timeout
|
||||||
|
|
||||||
|
- `yaml`/`json` setting: `timeout`
|
||||||
|
- Type: [Go Duration](https://golang.org/pkg/time/#Duration.String) `string`
|
||||||
|
- Optional
|
||||||
|
- Default: `30s`
|
||||||
|
|
||||||
|
Policy timeout establishes the per-route timeout value. Cannot exceed global timeout values.
|
||||||
|
|
||||||
### Debug
|
### Debug
|
||||||
|
|
||||||
- Environmental Variable: `POMERIUM_DEBUG`
|
- Environmental Variable: `POMERIUM_DEBUG`
|
||||||
|
@ -116,6 +181,19 @@ Certificate is the x509 _public-key_ used to establish secure HTTP and gRPC conn
|
||||||
|
|
||||||
Certificate key is the x509 _private-key_ used to establish secure HTTP and gRPC connections. If unset, pomerium will attempt to find and use `./privkey.pem`.
|
Certificate key is the x509 _private-key_ used to establish secure HTTP and gRPC connections. If unset, pomerium will attempt to find and use `./privkey.pem`.
|
||||||
|
|
||||||
|
### Timeouts
|
||||||
|
|
||||||
|
- Environmental Variables: `TIMEOUT_READ` `TIMEOUT_WRITE` `TIMEOUT_READ_HEADER` `TIMEOUT_IDLE`
|
||||||
|
- Type: [Go Duration](https://golang.org/pkg/time/#Duration.String) `string`
|
||||||
|
- Example: `TIMEOUT_READ=30s`
|
||||||
|
- Defaults: `TIMEOUT_READ_HEADER=10s` `TIMEOUT_READ=30s` `TIMEOUT_WRITE=0` `TIMEOUT_IDLE=5m`
|
||||||
|
|
||||||
|
Timeouts set the global server timeouts. For route-specific timeouts, see `Policy`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> For a deep dive on timeout values see [these](https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/) [two](https://blog.cloudflare.com/exposing-go-on-the-internet/) excellent blog posts.
|
||||||
|
|
||||||
## Authenticate Service
|
## Authenticate Service
|
||||||
|
|
||||||
### Authenticate Service URL
|
### Authenticate Service URL
|
||||||
|
@ -241,8 +319,16 @@ Certificate Authority is set when behind-the-ingress service communication uses
|
||||||
- Type: map of `strings` key value pairs
|
- Type: map of `strings` key value pairs
|
||||||
- Example: `X-Content-Type-Options:nosniff,X-Frame-Options:SAMEORIGIN`
|
- Example: `X-Content-Type-Options:nosniff,X-Frame-Options:SAMEORIGIN`
|
||||||
- To disable: `disable:true`
|
- To disable: `disable:true`
|
||||||
|
- Default :
|
||||||
|
|
||||||
Headers specifies a mapping of [HTTP Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) to be added to proxied requests. *Nota bene* Downstream application headers will be overwritten by Pomerium's headers on conflict.
|
```javascript
|
||||||
|
X-Content-Type-Options : nosniff,
|
||||||
|
X-Frame-Options:SAMEORIGIN,
|
||||||
|
X-XSS-Protection:1; mode=block,
|
||||||
|
Strict-Transport-Security:max-age=31536000; includeSubDomains; preload,
|
||||||
|
```
|
||||||
|
|
||||||
|
Headers specifies a mapping of [HTTP Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) to be added to proxied requests. _Nota bene_ Downstream application headers will be overwritten by Pomerium's headers on conflict.
|
||||||
|
|
||||||
By default, conservative [secure HTTP headers](https://www.owasp.org/index.php/OWASP_Secure_Headers_Project) are set.
|
By default, conservative [secure HTTP headers](https://www.owasp.org/index.php/OWASP_Secure_Headers_Project) are set.
|
||||||
|
|
||||||
|
|
|
@ -23,18 +23,27 @@ type Options struct {
|
||||||
// HTTPS requests. If empty, ":https" is used.
|
// HTTPS requests. If empty, ":https" is used.
|
||||||
Addr string
|
Addr string
|
||||||
|
|
||||||
// Cert and Key specifies the base64 encoded TLS certificates to use.
|
// TLS certificates to use.
|
||||||
Cert string
|
Cert string
|
||||||
Key string
|
Key string
|
||||||
// CertFile and KeyFile specifies the TLS certificates to use.
|
|
||||||
CertFile string
|
CertFile string
|
||||||
KeyFile string
|
KeyFile string
|
||||||
|
|
||||||
|
// Timeouts
|
||||||
|
ReadHeaderTimeout time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultOptions = &Options{
|
var defaultOptions = &Options{
|
||||||
Addr: ":https",
|
Addr: ":https",
|
||||||
CertFile: filepath.Join(findKeyDir(), "cert.pem"),
|
CertFile: filepath.Join(findKeyDir(), "cert.pem"),
|
||||||
KeyFile: filepath.Join(findKeyDir(), "privkey.pem"),
|
KeyFile: filepath.Join(findKeyDir(), "privkey.pem"),
|
||||||
|
ReadHeaderTimeout: 10 * time.Second,
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 0, // support streaming by default
|
||||||
|
IdleTimeout: 5 * time.Minute,
|
||||||
}
|
}
|
||||||
|
|
||||||
func findKeyDir() string {
|
func findKeyDir() string {
|
||||||
|
@ -45,15 +54,27 @@ func findKeyDir() string {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Options) applyDefaults() {
|
func (o *Options) applyDefaults() {
|
||||||
if opt.Addr == "" {
|
if o.Addr == "" {
|
||||||
opt.Addr = defaultOptions.Addr
|
o.Addr = defaultOptions.Addr
|
||||||
}
|
}
|
||||||
if opt.Cert == "" && opt.CertFile == "" {
|
if o.Cert == "" && o.CertFile == "" {
|
||||||
opt.CertFile = defaultOptions.CertFile
|
o.CertFile = defaultOptions.CertFile
|
||||||
}
|
}
|
||||||
if opt.Key == "" && opt.KeyFile == "" {
|
if o.Key == "" && o.KeyFile == "" {
|
||||||
opt.KeyFile = defaultOptions.KeyFile
|
o.KeyFile = defaultOptions.KeyFile
|
||||||
|
}
|
||||||
|
if o.ReadHeaderTimeout == 0 {
|
||||||
|
o.ReadHeaderTimeout = defaultOptions.ReadHeaderTimeout
|
||||||
|
}
|
||||||
|
if o.ReadTimeout == 0 {
|
||||||
|
o.ReadTimeout = defaultOptions.ReadTimeout
|
||||||
|
}
|
||||||
|
if o.WriteTimeout == 0 {
|
||||||
|
o.WriteTimeout = defaultOptions.WriteTimeout
|
||||||
|
}
|
||||||
|
if o.IdleTimeout == 0 {
|
||||||
|
o.IdleTimeout = defaultOptions.IdleTimeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,14 +117,13 @@ func ListenAndServeTLS(opt *Options, httpHandler http.Handler, grpcHandler *grpc
|
||||||
|
|
||||||
// Set up the main server.
|
// Set up the main server.
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
ReadHeaderTimeout: 10 * time.Second,
|
ReadHeaderTimeout: opt.ReadHeaderTimeout,
|
||||||
ReadTimeout: 30 * time.Second,
|
ReadTimeout: opt.ReadTimeout,
|
||||||
// WriteTimeout is set to 0 for streaming replies
|
WriteTimeout: opt.WriteTimeout,
|
||||||
WriteTimeout: 0,
|
IdleTimeout: opt.IdleTimeout,
|
||||||
IdleTimeout: 5 * time.Minute,
|
TLSConfig: config,
|
||||||
TLSConfig: config,
|
Handler: h,
|
||||||
Handler: h,
|
ErrorLog: stdlog.New(&log.StdLogWrapper{Logger: &sublogger}, "", 0),
|
||||||
ErrorLog: stdlog.New(&log.StdLogWrapper{Logger: &sublogger}, "", 0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.Serve(ln)
|
return server.Serve(ln)
|
||||||
|
|
|
@ -14,11 +14,9 @@ import (
|
||||||
// Policy contains authorization policy information.
|
// Policy contains authorization policy information.
|
||||||
// todo(bdd) : add upstream timeout and configuration settings
|
// todo(bdd) : add upstream timeout and configuration settings
|
||||||
type Policy struct {
|
type Policy struct {
|
||||||
// proxy related
|
//
|
||||||
From string `yaml:"from"`
|
From string `yaml:"from"`
|
||||||
To string `yaml:"to"`
|
To string `yaml:"to"`
|
||||||
// upstream transport settings
|
|
||||||
UpstreamTimeout time.Duration `yaml:"timeout"`
|
|
||||||
// Identity related policy
|
// Identity related policy
|
||||||
AllowedEmails []string `yaml:"allowed_users"`
|
AllowedEmails []string `yaml:"allowed_users"`
|
||||||
AllowedGroups []string `yaml:"allowed_groups"`
|
AllowedGroups []string `yaml:"allowed_groups"`
|
||||||
|
@ -30,6 +28,10 @@ type Policy struct {
|
||||||
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
|
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
|
||||||
CORSAllowPreflight bool `yaml:"cors_allow_preflight"`
|
CORSAllowPreflight bool `yaml:"cors_allow_preflight"`
|
||||||
|
|
||||||
|
// UpstreamTimeout is the route specific timeout. Must be less than the global
|
||||||
|
// timeout. If unset, route will fallback to the proxy's DefaultUpstreamTimeout.
|
||||||
|
UpstreamTimeout time.Duration `yaml:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) validate() (err error) {
|
func (p *Policy) validate() (err error) {
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
- from: httpbin.corp.beyondperimeter.com
|
- from: httpbin.corp.beyondperimeter.com
|
||||||
to: http://httpbin
|
to: http://httpbin
|
||||||
allowed_domains:
|
allowed_domains:
|
||||||
- pomerium.io
|
- pomerium.io
|
||||||
|
cors_allow_preflight: true
|
||||||
|
timeout: 30s
|
||||||
- from: external-httpbin.corp.beyondperimeter.com
|
- from: external-httpbin.corp.beyondperimeter.com
|
||||||
to: httpbin.org
|
to: httpbin.org
|
||||||
allowed_domains:
|
allowed_domains:
|
||||||
- gmail.com
|
- gmail.com
|
||||||
- from: weirdlyssl.corp.beyondperimeter.com
|
- from: weirdlyssl.corp.beyondperimeter.com
|
||||||
to: http://neverssl.com
|
to: http://neverssl.com
|
||||||
allowed_users:
|
allowed_users:
|
||||||
- bdd@pomerium.io
|
- bdd@pomerium.io
|
||||||
allowed_groups:
|
allowed_groups:
|
||||||
- admins
|
- admins
|
||||||
- developers
|
- developers
|
||||||
- from: hello.corp.beyondperimeter.com
|
- from: hello.corp.beyondperimeter.com
|
||||||
to: http://hello:8080
|
to: http://hello:8080
|
||||||
allowed_groups:
|
allowed_groups:
|
||||||
- admins
|
- admins
|
||||||
- from: cross-origin.corp.beyondperimeter.com
|
|
||||||
to: httpbin.org
|
|
||||||
allowed_domains:
|
|
||||||
- gmail.com
|
|
||||||
cors_allow_preflight: true
|
|
|
@ -109,13 +109,19 @@ func OptionsFromEnvConfig() (*Options, error) {
|
||||||
// Validate checks that proper configuration settings are set to create
|
// Validate checks that proper configuration settings are set to create
|
||||||
// a proper Proxy instance
|
// a proper Proxy instance
|
||||||
func (o *Options) Validate() error {
|
func (o *Options) Validate() error {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(o.SharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("authorize: `SHARED_SECRET` setting is invalid base64: %v", err)
|
||||||
|
}
|
||||||
|
if len(decoded) != 32 {
|
||||||
|
return fmt.Errorf("authorize: `SHARED_SECRET` want 32 but got %d bytes", len(decoded))
|
||||||
|
}
|
||||||
if len(o.Routes) != 0 {
|
if len(o.Routes) != 0 {
|
||||||
return errors.New("routes setting is deprecated, use policy instead")
|
return errors.New("routes setting is deprecated, use policy instead")
|
||||||
}
|
}
|
||||||
if o.Policy == "" && o.PolicyFile == "" {
|
if o.Policy == "" && o.PolicyFile == "" {
|
||||||
return errors.New("proxy: either `POLICY` or `POLICY_FILE` must be non-nil")
|
return errors.New("proxy: either `POLICY` or `POLICY_FILE` must be non-nil")
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
if o.Policy != "" {
|
if o.Policy != "" {
|
||||||
confBytes, err := base64.StdEncoding.DecodeString(o.Policy)
|
confBytes, err := base64.StdEncoding.DecodeString(o.Policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -148,9 +154,6 @@ func (o *Options) Validate() error {
|
||||||
if o.CookieSecret == "" {
|
if o.CookieSecret == "" {
|
||||||
return errors.New("missing setting: cookie-secret")
|
return errors.New("missing setting: cookie-secret")
|
||||||
}
|
}
|
||||||
if o.SharedKey == "" {
|
|
||||||
return errors.New("missing setting: client-secret")
|
|
||||||
}
|
|
||||||
decodedCookieSecret, err := base64.StdEncoding.DecodeString(o.CookieSecret)
|
decodedCookieSecret, err := base64.StdEncoding.DecodeString(o.CookieSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cookie secret is invalid base64: %v", err)
|
return fmt.Errorf("cookie secret is invalid base64: %v", err)
|
||||||
|
|