diff --git a/cmd/pomerium/main.go b/cmd/pomerium/main.go
index 8dcd50eb1..153605e19 100644
--- a/cmd/pomerium/main.go
+++ b/cmd/pomerium/main.go
@@ -1,6 +1,7 @@
package main // import "github.com/pomerium/pomerium/cmd/pomerium"
import (
+ "errors"
"flag"
"fmt"
"net/http"
@@ -21,116 +22,148 @@ import (
"github.com/pomerium/pomerium/proxy"
)
-var (
- 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")
-)
+var versionFlag = flag.Bool("version", false, "prints the version")
func main() {
flag.Parse()
if *versionFlag {
- fmt.Printf("%s\n", version.FullVersion())
+ fmt.Println(version.FullVersion())
os.Exit(0)
}
- mainOpts, err := optionsFromEnvConfig()
+ opt, err := parseOptions()
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)}
grpcServer := grpc.NewServer(grpcOpts...)
- var authenticateService *authenticate.Authenticate
- var authHost string
- 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) {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/ping", func(rw http.ResponseWriter, _ *http.Request) {
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{
- Addr: mainOpts.Addr,
- Cert: mainOpts.Cert,
- Key: mainOpts.Key,
- CertFile: mainOpts.CertFile,
- KeyFile: mainOpts.KeyFile,
+ Addr: opt.Addr,
+ Cert: opt.Cert,
+ Key: opt.Key,
+ CertFile: opt.CertFile,
+ KeyFile: opt.KeyFile,
+ ReadTimeout: opt.ReadTimeout,
+ WriteTimeout: opt.WriteTimeout,
+ ReadHeaderTimeout: opt.ReadHeaderTimeout,
+ IdleTimeout: opt.IdleTimeout,
}
- if mainOpts.HTTPRedirectAddr != "" {
- // stand up another http server that just redirect HTTP to HTTPS traffic
- 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") }()
+ if srv, err := startRedirectServer(opt.HTTPRedirectAddr); err != nil {
+ log.Debug().Err(err).Msg("cmd/pomerium: http redirect server not started")
} 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
}
diff --git a/cmd/pomerium/main_test.go b/cmd/pomerium/main_test.go
new file mode 100644
index 000000000..40edb3bd9
--- /dev/null
+++ b/cmd/pomerium/main_test.go
@@ -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)
+
+ }
+ })
+ }
+}
diff --git a/cmd/pomerium/options.go b/cmd/pomerium/options.go
index 4abf7334e..4654287cc 100644
--- a/cmd/pomerium/options.go
+++ b/cmd/pomerium/options.go
@@ -3,6 +3,7 @@ package main // import "github.com/pomerium/pomerium/cmd/pomerium"
import (
"errors"
"fmt"
+ "time"
"github.com/pomerium/envconfig"
)
@@ -43,6 +44,12 @@ type Options struct {
// to HTTPS redirect server on. For example, ":http" would start a server
// on port 80. If empty, no redirect server is started.
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{
@@ -68,8 +75,8 @@ func optionsFromEnvConfig() (*Options, error) {
}
// isValidService checks to see if a service is a valid service mode
-func isValidService(service string) bool {
- switch service {
+func isValidService(s string) bool {
+ switch s {
case
"all",
"proxy",
@@ -79,3 +86,33 @@ func isValidService(service string) bool {
}
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
+}
diff --git a/cmd/pomerium/options_test.go b/cmd/pomerium/options_test.go
index acd594744..df7408b30 100644
--- a/cmd/pomerium/options_test.go
+++ b/cmd/pomerium/options_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/docs/.vuepress/public/logo-long-white.svg b/docs/.vuepress/public/logo-long-white.svg
new file mode 100644
index 000000000..e76e287b9
--- /dev/null
+++ b/docs/.vuepress/public/logo-long-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/.vuepress/public/logo-long.svg b/docs/.vuepress/public/logo-long.svg
new file mode 100644
index 000000000..95e947038
--- /dev/null
+++ b/docs/.vuepress/public/logo-long.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/.vuepress/public/logo-no-text.svg b/docs/.vuepress/public/logo-no-text.svg
new file mode 100644
index 000000000..528a33564
--- /dev/null
+++ b/docs/.vuepress/public/logo-no-text.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/.vuepress/public/logo-only.png b/docs/.vuepress/public/logo-only.png
new file mode 100644
index 000000000..f6965796c
Binary files /dev/null and b/docs/.vuepress/public/logo-only.png differ
diff --git a/docs/.vuepress/public/logo-stacked.svg b/docs/.vuepress/public/logo-stacked.svg
new file mode 100644
index 000000000..41d399be9
--- /dev/null
+++ b/docs/.vuepress/public/logo-stacked.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/.vuepress/public/logo.svg b/docs/.vuepress/public/logo.svg
index e9e3d3a70..41d399be9 100644
--- a/docs/.vuepress/public/logo.svg
+++ b/docs/.vuepress/public/logo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/readme.md b/docs/readme.md
index 445fc11da..02d01868f 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -1,8 +1,8 @@
---
home: true
heroImage: logo.svg
-heroText: Pomerium
-tagline: Identity-aware access proxy.
+heroText: " "
+tagline: Pomerium is a context and identity aware access proxy.
actionText: Read the docs
actionLink: /docs/
---
diff --git a/docs/reference/readme.md b/docs/reference/readme.md
index 1d6859d9f..eef5bbd38 100644
--- a/docs/reference/readme.md
+++ b/docs/reference/readme.md
@@ -61,10 +61,75 @@ head -c32 /dev/urandom | base64
- Filetype: `json` or `yaml`
- 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
+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
- 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`.
+### 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 URL
@@ -241,8 +319,16 @@ Certificate Authority is set when behind-the-ingress service communication uses
- Type: map of `strings` key value pairs
- Example: `X-Content-Type-Options:nosniff,X-Frame-Options:SAMEORIGIN`
- 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.
diff --git a/internal/https/https.go b/internal/https/https.go
index 263b83e81..ff459f3f3 100644
--- a/internal/https/https.go
+++ b/internal/https/https.go
@@ -23,18 +23,27 @@ type Options struct {
// HTTPS requests. If empty, ":https" is used.
Addr string
- // Cert and Key specifies the base64 encoded TLS certificates to use.
- Cert string
- Key string
- // CertFile and KeyFile specifies the TLS certificates to use.
+ // TLS certificates to use.
+ Cert string
+ Key string
CertFile string
KeyFile string
+
+ // Timeouts
+ ReadHeaderTimeout time.Duration
+ ReadTimeout time.Duration
+ WriteTimeout time.Duration
+ IdleTimeout time.Duration
}
var defaultOptions = &Options{
- Addr: ":https",
- CertFile: filepath.Join(findKeyDir(), "cert.pem"),
- KeyFile: filepath.Join(findKeyDir(), "privkey.pem"),
+ Addr: ":https",
+ CertFile: filepath.Join(findKeyDir(), "cert.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 {
@@ -45,15 +54,27 @@ func findKeyDir() string {
return p
}
-func (opt *Options) applyDefaults() {
- if opt.Addr == "" {
- opt.Addr = defaultOptions.Addr
+func (o *Options) applyDefaults() {
+ if o.Addr == "" {
+ o.Addr = defaultOptions.Addr
}
- if opt.Cert == "" && opt.CertFile == "" {
- opt.CertFile = defaultOptions.CertFile
+ if o.Cert == "" && o.CertFile == "" {
+ o.CertFile = defaultOptions.CertFile
}
- if opt.Key == "" && opt.KeyFile == "" {
- opt.KeyFile = defaultOptions.KeyFile
+ if o.Key == "" && o.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.
server := &http.Server{
- ReadHeaderTimeout: 10 * time.Second,
- ReadTimeout: 30 * time.Second,
- // WriteTimeout is set to 0 for streaming replies
- WriteTimeout: 0,
- IdleTimeout: 5 * time.Minute,
- TLSConfig: config,
- Handler: h,
- ErrorLog: stdlog.New(&log.StdLogWrapper{Logger: &sublogger}, "", 0),
+ ReadHeaderTimeout: opt.ReadHeaderTimeout,
+ ReadTimeout: opt.ReadTimeout,
+ WriteTimeout: opt.WriteTimeout,
+ IdleTimeout: opt.IdleTimeout,
+ TLSConfig: config,
+ Handler: h,
+ ErrorLog: stdlog.New(&log.StdLogWrapper{Logger: &sublogger}, "", 0),
}
return server.Serve(ln)
diff --git a/internal/policy/policy.go b/internal/policy/policy.go
index 52672d123..2fc92cb2a 100644
--- a/internal/policy/policy.go
+++ b/internal/policy/policy.go
@@ -14,11 +14,9 @@ import (
// Policy contains authorization policy information.
// todo(bdd) : add upstream timeout and configuration settings
type Policy struct {
- // proxy related
+ //
From string `yaml:"from"`
To string `yaml:"to"`
- // upstream transport settings
- UpstreamTimeout time.Duration `yaml:"timeout"`
// Identity related policy
AllowedEmails []string `yaml:"allowed_users"`
AllowedGroups []string `yaml:"allowed_groups"`
@@ -30,6 +28,10 @@ type Policy struct {
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
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) {
diff --git a/policy.example.yaml b/policy.example.yaml
index 110e2ed6c..918b6b699 100644
--- a/policy.example.yaml
+++ b/policy.example.yaml
@@ -1,24 +1,21 @@
- from: httpbin.corp.beyondperimeter.com
to: http://httpbin
allowed_domains:
- - pomerium.io
+ - pomerium.io
+ cors_allow_preflight: true
+ timeout: 30s
- from: external-httpbin.corp.beyondperimeter.com
to: httpbin.org
allowed_domains:
- - gmail.com
+ - gmail.com
- from: weirdlyssl.corp.beyondperimeter.com
to: http://neverssl.com
allowed_users:
- - bdd@pomerium.io
+ - bdd@pomerium.io
allowed_groups:
- - admins
- - developers
+ - admins
+ - developers
- from: hello.corp.beyondperimeter.com
to: http://hello:8080
allowed_groups:
- - admins
-- from: cross-origin.corp.beyondperimeter.com
- to: httpbin.org
- allowed_domains:
- - gmail.com
- cors_allow_preflight: true
+ - admins
\ No newline at end of file
diff --git a/proxy/proxy.go b/proxy/proxy.go
index fe05813e0..7f84235bd 100755
--- a/proxy/proxy.go
+++ b/proxy/proxy.go
@@ -109,13 +109,19 @@ func OptionsFromEnvConfig() (*Options, error) {
// Validate checks that proper configuration settings are set to create
// a proper Proxy instance
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 {
return errors.New("routes setting is deprecated, use policy instead")
}
if o.Policy == "" && o.PolicyFile == "" {
return errors.New("proxy: either `POLICY` or `POLICY_FILE` must be non-nil")
}
- var err error
if o.Policy != "" {
confBytes, err := base64.StdEncoding.DecodeString(o.Policy)
if err != nil {
@@ -148,9 +154,6 @@ func (o *Options) Validate() error {
if o.CookieSecret == "" {
return errors.New("missing setting: cookie-secret")
}
- if o.SharedKey == "" {
- return errors.New("missing setting: client-secret")
- }
decodedCookieSecret, err := base64.StdEncoding.DecodeString(o.CookieSecret)
if err != nil {
return fmt.Errorf("cookie secret is invalid base64: %v", err)