From 99e788a9b4efe828dc711499a8d7f218353d988c Mon Sep 17 00:00:00 2001 From: Travis Groth Date: Mon, 18 May 2020 16:34:31 -0400 Subject: [PATCH] envoy: Initial changes --- .github/Dockerfile-release | 1 - .github/Dockerfile-release.arm32v6 | 1 - .github/Dockerfile-release.arm32v7 | 1 - .github/Dockerfile-release.arm64v8 | 1 - .github/workflows/test.yaml | 32 +- .gitignore | 9 +- .pre-commit-config.yaml | 20 - Dockerfile | 1 - Makefile | 1 + VERSION | 2 +- authenticate/handlers.go | 13 +- authorize/authorize.go | 41 +- authorize/grpc.go | 235 ++++++++++-- authorize/grpc_test.go | 44 --- cmd/pomerium/main.go | 259 ++++++------- cmd/pomerium/main_test.go | 89 +---- config/options.go | 112 +----- config/options_test.go | 40 +- docs/.vuepress/config.js | 4 +- docs/.vuepress/public/_redirects | 1 - docs/_posts/2020-05-11-release-0-8.md | 34 -- docs/configuration/examples.md | 47 +-- .../examples/config/config.example.env | 10 +- .../examples/config/config.minimal.env | 3 +- .../examples/config/config.minimal.yaml | 6 +- .../docker/autocert.docker-compose.yml | 18 - .../examples/docker/basic.docker-compose.yml | 2 +- .../examples/docker/nginx.docker-compose.yml | 8 +- .../examples/kubernetes/istio/gateway.yml | 41 -- .../examples/kubernetes/istio/grafana.ini.yml | 12 - .../kubernetes/istio/pomerium-helm-values.yml | 13 - .../kubernetes/istio/service-entry.yml | 14 - .../kubernetes/istio/virtual-services.yml | 30 -- .../examples/kubernetes/values.yaml | 28 -- docs/configuration/readme.md | 85 +---- docs/docs/CHANGELOG.md | 34 +- docs/docs/community/contributing.md | 3 +- docs/docs/quick-start/binary.md | 6 +- docs/docs/quick-start/from-source.md | 4 +- docs/docs/quick-start/helm.md | 12 +- docs/docs/quick-start/kubernetes.md | 10 +- docs/docs/quick-start/readme.md | 22 +- docs/docs/quick-start/synology.md | 48 +-- docs/docs/reference/certificates.md | 121 +++--- docs/docs/reference/getting-users-identity.md | 31 +- docs/docs/upgrading.md | 3 +- docs/jobs/Backend-Engineer.md | 40 -- docs/jobs/Frontend-Engineer.md | 36 -- docs/jobs/readme.md | 12 - docs/recipes/kubernetes.md | 74 ++-- docs/recipes/yml/pomerium.ingress.yaml | 33 ++ go.mod | 37 +- go.sum | 356 +++--------------- integration/backends/httpdetails/go.mod | 3 - integration/backends/httpdetails/index.js | 29 ++ integration/backends/httpdetails/main.go | 94 ----- integration/backends/ws-echo/go.mod | 5 - integration/backends/ws-echo/go.sum | 2 - integration/backends/ws-echo/main.go | 60 --- ...ashboard_test.go => control_plane_test.go} | 26 ++ integration/internal/cluster/certs.go | 139 +++---- integration/internal/cluster/cluster.go | 8 +- integration/internal/cluster/cmd.go | 7 - integration/internal/cluster/setup.go | 36 +- integration/internal/httputil/httputil.go | 47 +++ integration/internal/netutil/netutil.go | 50 --- integration/manifests/lib/backends.libsonnet | 159 -------- .../manifests/lib/httpdetails.libsonnet | 79 ++++ integration/manifests/lib/pomerium.libsonnet | 276 ++++---------- .../lib/reference-openid-provider.libsonnet | 2 +- integration/manifests/lib/tls.libsonnet | 30 +- integration/manifests/manifests.jsonnet | 4 +- integration/policy_test.go | 338 ----------------- internal/controlplane/grpc_accesslog.go | 39 ++ internal/controlplane/grpc_xds.go | 135 +++++++ internal/controlplane/http.go | 39 ++ internal/controlplane/server.go | 139 +++++++ internal/controlplane/xds.go | 105 ++++++ internal/controlplane/xds_clusters.go | 105 ++++++ internal/controlplane/xds_listeners.go | 269 +++++++++++++ internal/controlplane/xds_routes.go | 136 +++++++ internal/cryptutil/tls.go | 103 ----- internal/cryptutil/tls_test.go | 49 --- internal/envoy/embed.go | 64 ++++ internal/envoy/envoy.go | 158 ++++++++ internal/envoy/envoy_linux.go | 9 + internal/envoy/envoy_notlinux.go | 7 + internal/grpc/server.go | 19 +- internal/grpc/server_test.go | 11 +- internal/httputil/options.go | 13 +- internal/httputil/server.go | 48 ++- internal/httputil/server_test.go | 83 ++-- internal/kv/autocache/autocache.go | 2 +- proxy/forward_auth.go | 49 +-- proxy/forward_auth_test.go | 60 +-- proxy/handlers.go | 1 - proxy/handlers_test.go | 4 - proxy/middleware.go | 130 ++----- proxy/middleware_test.go | 61 --- proxy/proxy.go | 217 ++--------- proxy/proxy_test.go | 183 --------- renovate.json | 7 +- scripts/build-dev-docker.bash | 18 +- scripts/embed-envoy.bash | 50 +++ .../sh => scripts}/generate_wildcard_cert.sh | 0 .../examples/helm => scripts}/helm_aws.sh | 0 .../examples/helm => scripts}/helm_gke.sh | 17 +- 107 files changed, 2542 insertions(+), 3322 deletions(-) delete mode 100644 .pre-commit-config.yaml delete mode 100644 docs/_posts/2020-05-11-release-0-8.md delete mode 100644 docs/configuration/examples/docker/autocert.docker-compose.yml delete mode 100644 docs/configuration/examples/kubernetes/istio/gateway.yml delete mode 100644 docs/configuration/examples/kubernetes/istio/grafana.ini.yml delete mode 100644 docs/configuration/examples/kubernetes/istio/pomerium-helm-values.yml delete mode 100644 docs/configuration/examples/kubernetes/istio/service-entry.yml delete mode 100644 docs/configuration/examples/kubernetes/istio/virtual-services.yml delete mode 100644 docs/configuration/examples/kubernetes/values.yaml delete mode 100644 docs/jobs/Backend-Engineer.md delete mode 100644 docs/jobs/Frontend-Engineer.md delete mode 100644 docs/jobs/readme.md create mode 100644 docs/recipes/yml/pomerium.ingress.yaml delete mode 100644 integration/backends/httpdetails/go.mod create mode 100644 integration/backends/httpdetails/index.js delete mode 100644 integration/backends/httpdetails/main.go delete mode 100644 integration/backends/ws-echo/go.mod delete mode 100644 integration/backends/ws-echo/go.sum delete mode 100644 integration/backends/ws-echo/main.go rename integration/{dashboard_test.go => control_plane_test.go} (66%) create mode 100644 integration/internal/httputil/httputil.go delete mode 100644 integration/internal/netutil/netutil.go delete mode 100644 integration/manifests/lib/backends.libsonnet create mode 100644 integration/manifests/lib/httpdetails.libsonnet delete mode 100644 integration/policy_test.go create mode 100644 internal/controlplane/grpc_accesslog.go create mode 100644 internal/controlplane/grpc_xds.go create mode 100644 internal/controlplane/http.go create mode 100644 internal/controlplane/server.go create mode 100644 internal/controlplane/xds.go create mode 100644 internal/controlplane/xds_clusters.go create mode 100644 internal/controlplane/xds_listeners.go create mode 100644 internal/controlplane/xds_routes.go delete mode 100644 internal/cryptutil/tls.go delete mode 100644 internal/cryptutil/tls_test.go create mode 100644 internal/envoy/embed.go create mode 100644 internal/envoy/envoy.go create mode 100644 internal/envoy/envoy_linux.go create mode 100644 internal/envoy/envoy_notlinux.go create mode 100755 scripts/embed-envoy.bash rename {docs/docs/reference/sh => scripts}/generate_wildcard_cert.sh (100%) rename {docs/configuration/examples/helm => scripts}/helm_aws.sh (100%) rename {docs/configuration/examples/helm => scripts}/helm_gke.sh (72%) diff --git a/.github/Dockerfile-release b/.github/Dockerfile-release index 8767ba0ef..490a374da 100644 --- a/.github/Dockerfile-release +++ b/.github/Dockerfile-release @@ -2,7 +2,6 @@ FROM busybox:latest as build RUN touch /config.yaml FROM gcr.io/distroless/static -ENV AUTOCERT_DIR /data/autocert WORKDIR /pomerium COPY pomerium* /bin/ COPY --from=build /config.yaml /pomerium/config.yaml diff --git a/.github/Dockerfile-release.arm32v6 b/.github/Dockerfile-release.arm32v6 index f695f051e..940eb4d0a 100644 --- a/.github/Dockerfile-release.arm32v6 +++ b/.github/Dockerfile-release.arm32v6 @@ -2,7 +2,6 @@ FROM busybox:latest as build RUN touch /config.yaml FROM arm32v7/alpine -ENV AUTOCERT_DIR /data/autocert WORKDIR /pomerium COPY --from=multiarch/qemu-user-static /usr/bin/qemu-aarch64-static /usr/bin/ RUN apk --no-cache add ca-certificates diff --git a/.github/Dockerfile-release.arm32v7 b/.github/Dockerfile-release.arm32v7 index f695f051e..940eb4d0a 100644 --- a/.github/Dockerfile-release.arm32v7 +++ b/.github/Dockerfile-release.arm32v7 @@ -2,7 +2,6 @@ FROM busybox:latest as build RUN touch /config.yaml FROM arm32v7/alpine -ENV AUTOCERT_DIR /data/autocert WORKDIR /pomerium COPY --from=multiarch/qemu-user-static /usr/bin/qemu-aarch64-static /usr/bin/ RUN apk --no-cache add ca-certificates diff --git a/.github/Dockerfile-release.arm64v8 b/.github/Dockerfile-release.arm64v8 index 75e8e500a..fa98cc92d 100644 --- a/.github/Dockerfile-release.arm64v8 +++ b/.github/Dockerfile-release.arm64v8 @@ -2,7 +2,6 @@ FROM busybox:latest as build RUN touch /config.yaml FROM arm64v8/alpine -ENV AUTOCERT_DIR /data/autocert WORKDIR /pomerium COPY --from=multiarch/qemu-user-static /usr/bin/qemu-aarch64-static /usr/bin/ RUN apk --no-cache add ca-certificates diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f678b5003..d9ba212dd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -89,32 +89,46 @@ jobs: integration-tests: runs-on: ubuntu-latest steps: - - name: install mkcert + - name: cache binaries + uses: actions/cache@v1 + env: + cache-name: cache-binaries + with: + path: /opt/binaries/ + key: ${{ runner.os }}-binaries + - name: install binaries run: | #!/bin/bash + sudo mkdir -p /usr/local/bin/ + sudo mkdir -p /opt/minikube/bin/ + cd /opt/minikube/bin + + if [ ! -f minikube ]; then + echo "downloading minikube" + sudo curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 + sudo chmod +x minikube + fi + sudo install minikube /usr/local/bin/ + if [ ! -f mkcert ]; then echo "downloading mkcert" sudo curl -Lo mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-linux-amd64 sudo chmod +x mkcert fi sudo install mkcert /usr/local/bin/ - - - name: Create kind cluster - uses: helm/kind-action@v1.0.0-rc.1 - with: - cluster_name: kind + - name: start minikube + run: | + minikube start --driver=docker + kubectl cluster-info - name: install go uses: actions/setup-go@v1 with: go-version: 1.14.x - - name: checkout code uses: actions/checkout@v2 - - name: build dev docker image run: | ./scripts/build-dev-docker.bash - - name: test run: go test -v ./integration/... diff --git a/.gitignore b/.gitignore index 52fc045c0..cc7d48b43 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out - + *.cgo1.go *.cgo2.c _cgo_defun.c @@ -78,9 +78,4 @@ node_modules i18n/* docs/.vuepress/dist/ .firebase/ -.changes.md - -# autocert -.pomerium/ - -!.pre-commit-config.yaml +.changes.md \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index a25f15666..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,20 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - repo: https://github.com/syntaqx/git-hooks - rev: v0.0.16 - hooks: - - id: go-mod-tidy - - repo: local - hooks: - - id: lint - name: lint - language: system - entry: make - args: ["lint"] - types: ["go"] diff --git a/Dockerfile b/Dockerfile index ad4f4b70e..81a21b736 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,6 @@ RUN make RUN touch /config.yaml FROM gcr.io/distroless/base:debug -ENV AUTOCERT_DIR /data/autocert WORKDIR /pomerium COPY --from=build /go/src/github.com/pomerium/pomerium/bin/* /bin/ COPY --from=build /config.yaml /pomerium/config.yaml diff --git a/Makefile b/Makefile index 637b50cb0..5539d1f3e 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,7 @@ define buildrelease GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 GO111MODULE=on go build ${GO_LDFLAGS} \ -o $(BUILDDIR)/$(NAME)-$(1)-$(2) \ ${GO_LDFLAGS_STATIC} ./cmd/$(NAME); +GOOS=$(1) GOARCH=$(2) ./scripts/embed-envoy.bash "$(BUILDDIR)/$(NAME)-$(1)-$(2)" || true; md5sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).md5; sha256sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).sha256; endef diff --git a/VERSION b/VERSION index 4ea5cafac..adca65575 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.8.0 \ No newline at end of file +v0.7.5 \ No newline at end of file diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 24b2e62c2..9ef8497dd 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -11,8 +11,6 @@ import ( "strings" "time" - "github.com/rs/cors" - "github.com/pomerium/csrf" "github.com/pomerium/pomerium/internal/cryptutil" "github.com/pomerium/pomerium/internal/httputil" @@ -22,11 +20,20 @@ import ( "github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/telemetry/trace" "github.com/pomerium/pomerium/internal/urlutil" + + "github.com/gorilla/mux" + "github.com/rs/cors" ) // Handler returns the authenticate service's handler chain. func (a *Authenticate) Handler() http.Handler { r := httputil.NewRouter() + a.Mount(r) + return r +} + +// Mount mounts the authenticate routes to the given router. +func (a *Authenticate) Mount(r *mux.Router) { r.Use(middleware.SetHeaders(httputil.HeadersContentSecurityPolicy)) r.Use(csrf.Protect( a.cookieSecret, @@ -67,8 +74,6 @@ func (a *Authenticate) Handler() http.Handler { api := r.PathPrefix("/api").Subrouter() api.Use(sessions.RetrieveSession(a.sessionLoaders...)) api.Path("/v1/refresh").Handler(httputil.HandlerFunc(a.RefreshAPI)) - - return r } // VerifySession is the middleware used to enforce a valid authentication diff --git a/authorize/authorize.go b/authorize/authorize.go index 35b3f8a5a..ce0bcfe76 100644 --- a/authorize/authorize.go +++ b/authorize/authorize.go @@ -6,8 +6,7 @@ import ( "context" "encoding/base64" "fmt" - - "gopkg.in/square/go-jose.v2" + "sync/atomic" "github.com/pomerium/pomerium/authorize/evaluator" "github.com/pomerium/pomerium/authorize/evaluator/opa" @@ -16,11 +15,27 @@ import ( "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/telemetry/metrics" "github.com/pomerium/pomerium/internal/telemetry/trace" + + "gopkg.in/square/go-jose.v2" ) +type atomicOptions struct { + value atomic.Value +} + +func (a *atomicOptions) Load() config.Options { + return a.value.Load().(config.Options) +} + +func (a *atomicOptions) Store(options config.Options) { + a.value.Store(options) +} + // Authorize struct holds type Authorize struct { pe evaluator.Evaluator + + currentOptions atomicOptions } // New validates and creates a new Authorize service from a set of config options. @@ -29,8 +44,9 @@ func New(opts config.Options) (*Authorize, error) { return nil, fmt.Errorf("authorize: bad options: %w", err) } var a Authorize - var err error - if a.pe, err = newPolicyEvaluator(&opts); err != nil { + a.currentOptions.Store(config.Options{}) + err := a.UpdateOptions(opts) + if err != nil { return nil, err } return &a, nil @@ -76,10 +92,11 @@ func newPolicyEvaluator(opts *config.Options) (evaluator.Evaluator, error) { } data := map[string]interface{}{ - "shared_key": opts.SharedKey, - "route_policies": opts.Policies, - "admins": opts.Administrators, - "signing_key": jwk, + "shared_key": opts.SharedKey, + "route_policies": opts.Policies, + "admins": opts.Administrators, + "signing_key": jwk, + "authenticate_url": opts.AuthenticateURLString, } return opa.New(ctx, &opa.Options{Data: data}) @@ -91,11 +108,13 @@ func (a *Authorize) UpdateOptions(opts config.Options) error { if a == nil { return nil } + log.Info().Str("checksum", fmt.Sprintf("%x", opts.Checksum())).Msg("authorize: updating options") - pe, err := newPolicyEvaluator(&opts) - if err != nil { + a.currentOptions.Store(opts) + + var err error + if a.pe, err = newPolicyEvaluator(&opts); err != nil { return err } - a.pe = pe return nil } diff --git a/authorize/grpc.go b/authorize/grpc.go index fa1a70e90..7ad4b9e07 100644 --- a/authorize/grpc.go +++ b/authorize/grpc.go @@ -1,56 +1,166 @@ -//go:generate protoc -I ../internal/grpc/authorize/ --go_out=plugins=grpc:../internal/grpc/authorize/ ../internal/grpc/authorize/authorize.proto - package authorize import ( "context" + "net/http" "net/url" + "strings" "github.com/pomerium/pomerium/authorize/evaluator" - "github.com/pomerium/pomerium/internal/grpc/authorize" + "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/encoding/jws" "github.com/pomerium/pomerium/internal/log" + "github.com/pomerium/pomerium/internal/sessions" + "github.com/pomerium/pomerium/internal/sessions/cookie" "github.com/pomerium/pomerium/internal/telemetry/trace" + "github.com/pomerium/pomerium/internal/urlutil" + + envoy_api_v2_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + envoy_service_auth_v2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" + envoy_type "github.com/envoyproxy/go-control-plane/envoy/type" + "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc/codes" ) -// IsAuthorized checks to see if a given user is authorized to make a request. -func (a *Authorize) IsAuthorized(ctx context.Context, in *authorize.IsAuthorizedRequest) (*authorize.IsAuthorizedReply, error) { - ctx, span := trace.StartSpan(ctx, "authorize.grpc.IsAuthorized") +// Check implements the envoy auth server gRPC endpoint. +func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v2.CheckRequest) (*envoy_service_auth_v2.CheckResponse, error) { + ctx, span := trace.StartSpan(ctx, "authorize.grpc.Check") defer span.End() + opts := a.currentOptions.Load() + + // maybe rewrite http request for forward auth + isForwardAuth := handleForwardAuth(opts, in) + + hattrs := in.GetAttributes().GetRequest().GetHttp() + + hdrs := getCheckRequestHeaders(in) + sess, sesserr := a.loadSessionFromCheckRequest(in) + requestURL := getCheckRequestURL(in) req := &evaluator.Request{ - User: in.GetUserToken(), - Header: cloneHeaders(in.GetRequestHeaders()), - Host: in.GetRequestHost(), - Method: in.GetRequestMethod(), - RequestURI: in.GetRequestRequestUri(), - RemoteAddr: in.GetRequestRemoteAddr(), - URL: getFullURL(in.GetRequestUrl(), in.GetRequestHost()), + User: sess, + Header: hdrs, + Host: hattrs.GetHost(), + Method: hattrs.GetMethod(), + RequestURI: requestURL.String(), + RemoteAddr: in.GetAttributes().GetSource().GetAddress().String(), + URL: requestURL.String(), } reply, err := a.pe.IsAuthorized(ctx, req) - log.Info(). - // request - Str("method", req.Method). - Str("url", req.URL). - // reply - Bool("allow", reply.Allow). - Strs("deny-reasons", reply.DenyReasons). - Str("user", reply.User). - Str("email", reply.Email). - Strs("groups", reply.Groups). - Msg("authorize.grpc.IsAuthorized") - return reply, err + if err != nil { + return nil, err + } + + evt := log.Info().Str("service", "authorize") + // request + evt = evt.Str("request-id", hattrs.GetId()) + evt = evt.Str("method", hattrs.GetMethod()) + evt = evt.Str("path", hattrs.GetPath()) + evt = evt.Str("host", hattrs.GetHost()) + evt = evt.Str("query", hattrs.GetQuery()) + // reply + evt = evt.Bool("allow", reply.GetAllow()) + evt = evt.Bool("session-expired", reply.GetSessionExpired()) + evt = evt.Strs("deny-reasons", reply.GetDenyReasons()) + evt = evt.Str("email", reply.GetEmail()) + evt = evt.Strs("groups", reply.GetGroups()) + evt.Msg("authorize check") + + if reply.Allow { + return &envoy_service_auth_v2.CheckResponse{ + Status: &status.Status{Code: int32(codes.OK), Message: "OK"}, + HttpResponse: &envoy_service_auth_v2.CheckResponse_OkResponse{OkResponse: &envoy_service_auth_v2.OkHttpResponse{}}, + }, nil + } + + switch sesserr { + case sessions.ErrExpired, sessions.ErrIssuedInTheFuture, sessions.ErrMalformed, sessions.ErrNoSessionFound, sessions.ErrNotValidYet: + // redirect to login + default: + var msg string + if sesserr != nil { + msg = sesserr.Error() + } + // all other errors + return &envoy_service_auth_v2.CheckResponse{ + Status: &status.Status{Code: int32(codes.PermissionDenied), Message: msg}, + HttpResponse: &envoy_service_auth_v2.CheckResponse_DeniedResponse{ + DeniedResponse: &envoy_service_auth_v2.DeniedHttpResponse{ + Status: &envoy_type.HttpStatus{ + Code: envoy_type.StatusCode_Forbidden, + }, + }, + }, + }, nil + } + + // no redirect for forward auth, that's handled by a separate config setting + if isForwardAuth { + return &envoy_service_auth_v2.CheckResponse{ + Status: &status.Status{Code: int32(codes.Unauthenticated)}, + HttpResponse: &envoy_service_auth_v2.CheckResponse_DeniedResponse{ + DeniedResponse: &envoy_service_auth_v2.DeniedHttpResponse{ + Status: &envoy_type.HttpStatus{ + Code: envoy_type.StatusCode_Unauthorized, + }, + }, + }, + }, nil + } + + signinURL := opts.AuthenticateURL.ResolveReference(&url.URL{Path: "/.pomerium/sign_in"}) + q := signinURL.Query() + q.Set(urlutil.QueryRedirectURI, requestURL.String()) + signinURL.RawQuery = q.Encode() + redirectTo := urlutil.NewSignedURL(opts.SharedKey, signinURL).String() + + return &envoy_service_auth_v2.CheckResponse{ + Status: &status.Status{ + Code: int32(codes.Unauthenticated), + Message: "unauthenticated", + }, + HttpResponse: &envoy_service_auth_v2.CheckResponse_DeniedResponse{ + DeniedResponse: &envoy_service_auth_v2.DeniedHttpResponse{ + Status: &envoy_type.HttpStatus{ + Code: envoy_type.StatusCode_Found, + }, + Headers: []*envoy_api_v2_core.HeaderValueOption{{ + Header: &envoy_api_v2_core.HeaderValue{ + Key: "Location", + Value: redirectTo, + }, + }}, + }, + }, + }, nil } -type protoHeader map[string]*authorize.IsAuthorizedRequest_Headers +func (a *Authorize) loadSessionFromCheckRequest(req *envoy_service_auth_v2.CheckRequest) (string, error) { + opts := a.currentOptions.Load() -func cloneHeaders(in protoHeader) map[string][]string { - out := make(map[string][]string, len(in)) - for key, values := range in { - newValues := make([]string, len(values.Value)) - copy(newValues, values.Value) - out[key] = newValues + // used to load and verify JWT tokens signed by the authenticate service + encoder, err := jws.NewHS256Signer([]byte(opts.SharedKey), opts.AuthenticateURL.Host) + if err != nil { + return "", err } - return out + + cookieOptions := &cookie.Options{ + Name: opts.CookieName, + Domain: opts.CookieDomain, + Secure: opts.CookieSecure, + HTTPOnly: opts.CookieHTTPOnly, + Expire: opts.CookieExpire, + } + + cookieStore, err := cookie.NewStore(cookieOptions, encoder) + if err != nil { + return "", err + } + + sess, err := cookieStore.LoadSession(&http.Request{ + Header: http.Header(getCheckRequestHeaders(req)), + }) + return sess, err } func getFullURL(rawurl, host string) string { @@ -66,3 +176,62 @@ func getFullURL(rawurl, host string) string { } return u.String() } + +func getCheckRequestHeaders(req *envoy_service_auth_v2.CheckRequest) map[string][]string { + h := make(map[string][]string) + ch := req.GetAttributes().GetRequest().GetHttp().GetHeaders() + for k, v := range ch { + h[http.CanonicalHeaderKey(k)] = []string{v} + } + return h +} + +func getCheckRequestURL(req *envoy_service_auth_v2.CheckRequest) *url.URL { + h := req.GetAttributes().GetRequest().GetHttp() + u := &url.URL{ + Scheme: h.GetScheme(), + Host: h.GetHost(), + } + + // envoy sends the query string as part of the path + path := h.GetPath() + if idx := strings.Index(path, "?"); idx != -1 { + u.Path, u.RawQuery = path[:idx], path[idx+1:] + } else { + u.Path = path + } + + if h.Headers != nil { + if fwdProto, ok := h.Headers["x-forwarded-proto"]; ok { + u.Scheme = fwdProto + } + } + return u +} + +func handleForwardAuth(opts config.Options, req *envoy_service_auth_v2.CheckRequest) bool { + if opts.ForwardAuthURL == nil { + return false + } + + checkURL := getCheckRequestURL(req) + if urlutil.StripPort(checkURL.Host) == urlutil.StripPort(opts.ForwardAuthURL.Host) { + if (checkURL.Path == "/" || checkURL.Path == "/verify") && checkURL.Query().Get("uri") != "" { + verifyURL, err := url.Parse(checkURL.Query().Get("uri")) + if err != nil { + log.Warn().Str("uri", checkURL.Query().Get("uri")).Err(err).Msg("failed to parse uri for forward authentication") + return false + } + req.Attributes.Request.Http.Scheme = verifyURL.Scheme + req.Attributes.Request.Http.Host = verifyURL.Host + req.Attributes.Request.Http.Path = verifyURL.Path + // envoy sends the query string as part of the path + if verifyURL.RawQuery != "" { + req.Attributes.Request.Http.Path += "?" + verifyURL.RawQuery + } + return true + } + } + + return false +} diff --git a/authorize/grpc_test.go b/authorize/grpc_test.go index c4a765957..b61830b55 100644 --- a/authorize/grpc_test.go +++ b/authorize/grpc_test.go @@ -1,53 +1,9 @@ package authorize import ( - "context" - "errors" "testing" - - "github.com/golang/mock/gomock" - "github.com/google/go-cmp/cmp" - "github.com/pomerium/pomerium/authorize/evaluator/mock_evaluator" - "github.com/pomerium/pomerium/internal/grpc/authorize" ) -func TestAuthorize_IsAuthorized(t *testing.T) { - - tests := []struct { - name string - retDec *authorize.IsAuthorizedReply - retErr error - ctx context.Context - in *authorize.IsAuthorizedRequest - want *authorize.IsAuthorizedReply - wantErr bool - }{ - {"good", &authorize.IsAuthorizedReply{}, nil, context.TODO(), &authorize.IsAuthorizedRequest{UserToken: "good"}, &authorize.IsAuthorizedReply{}, false}, - {"error", &authorize.IsAuthorizedReply{}, errors.New("error"), context.TODO(), &authorize.IsAuthorizedRequest{UserToken: "good"}, &authorize.IsAuthorizedReply{}, true}, - {"headers", &authorize.IsAuthorizedReply{}, nil, context.TODO(), &authorize.IsAuthorizedRequest{UserToken: "good", RequestHeaders: nil}, &authorize.IsAuthorizedReply{}, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - pe := mock_evaluator.NewMockEvaluator(mockCtrl) - pe.EXPECT().IsAuthorized(gomock.Any(), gomock.Any()).Return(tt.retDec, tt.retErr).AnyTimes() - - a := &Authorize{ - pe: pe, - } - got, err := a.IsAuthorized(tt.ctx, tt.in) - if (err != nil) != tt.wantErr { - t.Errorf("Authorize.IsAuthorized() error = %v, wantErr %v", err, tt.wantErr) - return - } - if diff := cmp.Diff(got, tt.want); diff != "" { - t.Errorf("Authorize.IsAuthorized() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_getFullURL(t *testing.T) { tests := []struct { rawurl, host, expect string diff --git a/cmd/pomerium/main.go b/cmd/pomerium/main.go index 5e1b57339..d80b3f58b 100644 --- a/cmd/pomerium/main.go +++ b/cmd/pomerium/main.go @@ -1,33 +1,30 @@ package main import ( + "context" "flag" "fmt" - "net/http" + "net" "sync" - "time" "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" - pgrpc "github.com/pomerium/pomerium/internal/grpc" - pbAuthorize "github.com/pomerium/pomerium/internal/grpc/authorize" + "github.com/pomerium/pomerium/internal/controlplane" + "github.com/pomerium/pomerium/internal/envoy" 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" "github.com/pomerium/pomerium/internal/telemetry/metrics" "github.com/pomerium/pomerium/internal/telemetry/trace" "github.com/pomerium/pomerium/internal/urlutil" "github.com/pomerium/pomerium/internal/version" "github.com/pomerium/pomerium/proxy" + envoy_service_auth_v2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" "github.com/fsnotify/fsnotify" - "github.com/gorilla/mux" - "google.golang.org/grpc" - "google.golang.org/grpc/keepalive" + "golang.org/x/sync/errgroup" ) var versionFlag = flag.Bool("version", false, "prints the version") @@ -49,8 +46,10 @@ func run() error { if err != nil { return err } + var optionsUpdaters []config.OptionsUpdater + log.Info().Str("version", version.FullVersion()).Msg("cmd/pomerium") - // since we can have multiple listeners, we create a wait group + var wg sync.WaitGroup if err := setupMetrics(opt, &wg); err != nil { return err @@ -62,153 +61,114 @@ func run() error { return err } - r := newGlobalRouter(opt) - _, err = newAuthenticateService(*opt, r) + ctx := context.Background() + + // setup the control plane + controlPlane, err := controlplane.NewServer() if err != nil { + return fmt.Errorf("error creating control plane: %w", err) + } + optionsUpdaters = append(optionsUpdaters, controlPlane) + err = controlPlane.UpdateOptions(*opt) + if err != nil { + return fmt.Errorf("error updating control plane options: %w", err) + } + + _, grpcPort, _ := net.SplitHostPort(controlPlane.GRPCListener.Addr().String()) + _, httpPort, _ := net.SplitHostPort(controlPlane.HTTPListener.Addr().String()) + + // create envoy server + envoyServer, err := envoy.NewServer(grpcPort, httpPort) + if err != nil { + return fmt.Errorf("error creating envoy server") + } + + // add services + if err := setupAuthenticate(opt, controlPlane); err != nil { return err } - authz, err := newAuthorizeService(*opt) - if err != nil { + if err := setupAuthorize(opt, controlPlane, &optionsUpdaters); err != nil { + return err + } + if err := setupCache(opt, controlPlane); err != nil { + return err + } + if err := setupProxy(opt, controlPlane); 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 - } - if proxy != nil { - defer proxy.AuthorizeClient.Close() - } - + // start the config change listener opt.OnConfigChange(func(e fsnotify.Event) { log.Info().Str("file", e.Name).Msg("cmd/pomerium: config file changed") - opt = config.HandleConfigUpdate(*configFile, opt, []config.OptionsUpdater{authz, proxy}) + opt = config.HandleConfigUpdate(*configFile, opt, optionsUpdaters) }) - if err := newGRPCServer(*opt, authz, cacheSvc, &wg); err != nil { - return err - } - - srv, err := httputil.NewServer(httpServerOptions(opt), r, &wg) - if err != nil { - return err - } - go httputil.Shutdown(srv) - // Blocks and waits until ALL WaitGroup members have signaled completion - wg.Wait() - return nil + // run everything + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { + wg.Wait() + return nil + }) + eg.Go(func() error { + return controlPlane.Run(ctx) + }) + eg.Go(func() error { + return envoyServer.Run(ctx) + }) + return eg.Wait() } -func newAuthenticateService(opt config.Options, r *mux.Router) (*authenticate.Authenticate, error) { +func setupAuthenticate(opt *config.Options, controlPlane *controlplane.Server) error { if !config.IsAuthenticate(opt.Services) { - return nil, nil - } - service, err := authenticate.New(opt) - if err != nil { - return nil, err - } - sr := r.Host(urlutil.StripPort(opt.AuthenticateURL.Host)).Subrouter() - sr.PathPrefix("/").Handler(service.Handler()) - - return service, nil -} - -func newAuthorizeService(opt config.Options) (*authorize.Authorize, error) { - if !config.IsAuthorize(opt.Services) { - return nil, nil - } - 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) { - if as != nil { - pbAuthorize.RegisterAuthorizerServer(s, as) - } - if cs != nil { - pbCache.RegisterCacheServer(s, cs) - } - } - so := &pgrpc.ServerOptions{ - Addr: opt.GRPCAddr, - ServiceName: opt.Services, - KeepaliveParams: keepalive.ServerParameters{ - MaxConnectionAge: opt.GRPCServerMaxConnectionAge, - MaxConnectionAgeGrace: opt.GRPCServerMaxConnectionAgeGrace, - }, - InsecureServer: opt.GRPCInsecure, - } - if !opt.GRPCInsecure { - so.TLSCertificate = opt.TLSConfig.Certificates - } - grpcSrv, err := pgrpc.NewServer(so, regFn, wg) + + svc, err := authenticate.New(*opt) if err != nil { - return err + return fmt.Errorf("error creating authenticate service: %w", err) } - go pgrpc.Shutdown(grpcSrv) + host := urlutil.StripPort(opt.AuthenticateURL.Host) + sr := controlPlane.HTTPRouter.Host(host).Subrouter() + svc.Mount(sr) + log.Info().Str("host", host).Msg("enabled authenticate service") + return nil } -func newProxyService(opt config.Options, r *mux.Router) (*proxy.Proxy, error) { - if !config.IsProxy(opt.Services) { - return nil, nil +func setupAuthorize(opt *config.Options, controlPlane *controlplane.Server, optionsUpdaters *[]config.OptionsUpdater) error { + if !config.IsAuthorize(opt.Services) { + return nil } - service, err := proxy.New(opt) + + svc, err := authorize.New(*opt) if err != nil { - return nil, err + return fmt.Errorf("error creating authorize service: %w", err) } - r.PathPrefix("/").Handler(service) - return service, nil + envoy_service_auth_v2.RegisterAuthorizationServer(controlPlane.GRPCServer, svc) + + log.Info().Msg("enabled authorize service") + + *optionsUpdaters = append(*optionsUpdaters, svc) + err = svc.UpdateOptions(*opt) + if err != nil { + return fmt.Errorf("error updating authorize options: %w", err) + } + return nil } -func newGlobalRouter(o *config.Options) *mux.Router { - mux := httputil.NewRouter() - mux.SkipClean(true) - mux.Use(metrics.HTTPMetricsHandler(o.Services)) - mux.Use(log.NewHandler(log.Logger)) - mux.Use(log.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { - log.FromRequest(r).Debug(). - Dur("duration", duration). - Int("size", size). - Int("status", status). - Str("method", r.Method). - Str("service", o.Services). - Str("host", r.Host). - Str("path", r.URL.String()). - Msg("http-request") - })) - if len(o.Headers) != 0 { - mux.Use(middleware.SetHeaders(o.Headers)) +func setupCache(opt *config.Options, controlPlane *controlplane.Server) error { + if !config.IsCache(opt.Services) { + return nil } - mux.Use(log.HeadersHandler(httputil.HeadersXForwarded)) - mux.Use(log.RemoteAddrHandler("ip")) - mux.Use(log.UserAgentHandler("user_agent")) - mux.Use(log.RefererHandler("referer")) - mux.Use(log.RequestIDHandler("req_id", "Request-Id")) - mux.Use(middleware.Healthcheck("/ping", version.UserAgent())) - mux.HandleFunc("/healthz", httputil.HealthCheck) - mux.HandleFunc("/ping", httputil.HealthCheck) - mux.PathPrefix("/.pomerium/assets/").Handler(http.StripPrefix("/.pomerium/assets/", frontend.MustAssetHandler())) - return mux + svc, err := cache.New(*opt) + if err != nil { + return fmt.Errorf("error creating config service: %w", err) + } + defer svc.Close() + pbCache.RegisterCacheServer(controlPlane.GRPCServer, svc) + log.Info().Msg("enabled cache service") + return nil } func setupMetrics(opt *config.Options, wg *sync.WaitGroup) error { @@ -219,11 +179,7 @@ func setupMetrics(opt *config.Options, wg *sync.WaitGroup) error { } metrics.SetBuildInfo(opt.Services) metrics.RegisterInfoMetrics() - serverOpts := &httputil.ServerOptions{ - Addr: opt.MetricsAddr, - Insecure: true, - Service: "metrics", - } + serverOpts := &httputil.ServerOptions{Addr: opt.MetricsAddr} srv, err := httputil.NewServer(serverOpts, handler, wg) if err != nil { return err @@ -233,6 +189,19 @@ func setupMetrics(opt *config.Options, wg *sync.WaitGroup) error { return nil } +func setupProxy(opt *config.Options, controlPlane *controlplane.Server) error { + if !config.IsProxy(opt.Services) { + return nil + } + + svc, err := proxy.New(*opt) + if err != nil { + return fmt.Errorf("error creating proxy service: %w", err) + } + controlPlane.HTTPRouter.PathPrefix("/").Handler(svc) + return nil +} + func setupTracing(opt *config.Options) error { if opt.TracingProvider != "" { tracingOpts := &trace.TracingOptions{ @@ -251,20 +220,8 @@ func setupTracing(opt *config.Options) error { func setupHTTPRedirectServer(opt *config.Options, wg *sync.WaitGroup) error { if opt.HTTPRedirectAddr != "" { - serverOpts := httputil.ServerOptions{ - Addr: opt.HTTPRedirectAddr, - Insecure: true, - Service: "HTTP->HTTPS Redirect", - ReadHeaderTimeout: 5 * time.Second, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - IdleTimeout: 5 * time.Second, - } - h := httputil.RedirectHandler() - if opt.AutoCert { - h = opt.AutoCertHandler(h) - } - srv, err := httputil.NewServer(&serverOpts, h, wg) + serverOpts := httputil.ServerOptions{Addr: opt.HTTPRedirectAddr} + srv, err := httputil.NewServer(&serverOpts, httputil.RedirectHandler(), wg) if err != nil { return err } @@ -276,12 +233,10 @@ func setupHTTPRedirectServer(opt *config.Options, wg *sync.WaitGroup) error { func httpServerOptions(opt *config.Options) *httputil.ServerOptions { return &httputil.ServerOptions{ Addr: opt.Addr, - TLSConfig: opt.TLSConfig, - Insecure: opt.InsecureServer, + TLSCertificate: opt.TLSCertificate, ReadTimeout: opt.ReadTimeout, WriteTimeout: opt.WriteTimeout, ReadHeaderTimeout: opt.ReadHeaderTimeout, IdleTimeout: opt.IdleTimeout, - Service: opt.Services, } } diff --git a/cmd/pomerium/main_test.go b/cmd/pomerium/main_test.go index 54b5ae8c1..c60117353 100644 --- a/cmd/pomerium/main_test.go +++ b/cmd/pomerium/main_test.go @@ -1,11 +1,7 @@ package main import ( - "fmt" - "io" "io/ioutil" - "net/http" - "net/http/httptest" "os" "os/signal" "sync" @@ -18,37 +14,6 @@ import ( "github.com/pomerium/pomerium/internal/httputil" ) -func Test_newGlobalRouter(t *testing.T) { - o := config.Options{ - Services: "all", - Headers: map[string]string{ - "X-Content-Type-Options": "nosniff", - "X-Frame-Options": "SAMEORIGIN", - "X-XSS-Protection": "1; mode=block", - "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", - "Content-Security-Policy": "default-src 'none'; style-src 'self' 'sha256-pSTVzZsFAqd2U3QYu+BoBDtuJWaPM/+qMy/dBRrhb5Y='; img-src 'self';", - "Referrer-Policy": "Same-origin", - }} - req := httptest.NewRequest(http.MethodGet, "/404", nil) - rr := httptest.NewRecorder() - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `OK`) - }) - - out := newGlobalRouter(&o) - out.Handle("/404", h) - - out.ServeHTTP(rr, req) - expected := fmt.Sprintf("OK") - body := rr.Body.String() - - if body != expected { - t.Errorf("handler returned unexpected body: got %v want %v", body, expected) - } -} - func Test_httpServerOptions(t *testing.T) { tests := []struct { name string @@ -156,36 +121,8 @@ func Test_run(t *testing.T) { }{ {"simply print version", true, "", false}, {"nil configuration", false, "", true}, - {"simple proxy", false, ` - { - "address": ":9433", - "grpc_address": ":9444", - "grpc_insecure": true, - "insecure_server": true, - "authorize_service_url": "https://authorize.corp.example", - "authenticate_service_url": "https://authenticate.corp.example", - "shared_secret": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", - "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", - "services": "proxy", - "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } - `, false}, - {"simple authorize", false, ` - { - "address": ":9433", - "grpc_address": ":9444", - "grpc_insecure": true, - "insecure_server": true, - "authorize_service_url": "https://authorize.corp.example", - "authenticate_service_url": "https://authenticate.corp.example", - "shared_secret": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", - "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", - "services": "authorize", - "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } - `, false}, {"bad proxy no authenticate url", false, ` - { + { "address": ":9433", "grpc_address": ":9444", "insecure_server": true, @@ -194,10 +131,10 @@ func Test_run(t *testing.T) { "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "services": "proxy", "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } + } `, true}, {"bad authenticate no cookie secret", false, ` - { + { "address": ":9433", "grpc_address": ":9444", "insecure_server": true, @@ -205,10 +142,10 @@ func Test_run(t *testing.T) { "shared_secret": "YixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "services": "authenticate", "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } + } `, true}, {"bad authorize service bad shared key", false, ` - { + { "address": ":9433", "grpc_address": ":9444", "insecure_server": true, @@ -217,10 +154,10 @@ func Test_run(t *testing.T) { "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "services": "authorize", "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } + } `, true}, {"bad http port", false, ` - { + { "address": ":-1", "grpc_address": ":9444", "grpc_insecure": true, @@ -231,10 +168,10 @@ func Test_run(t *testing.T) { "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "services": "proxy", "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } + } `, true}, {"bad redirect port", false, ` - { + { "address": ":9433", "http_redirect_addr":":-1", "grpc_address": ":9444", @@ -246,10 +183,10 @@ func Test_run(t *testing.T) { "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "services": "proxy", "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } + } `, true}, {"bad metrics port ", false, ` - { + { "address": ":9433", "metrics_address": ":-1", "grpc_insecure": true, @@ -263,7 +200,7 @@ func Test_run(t *testing.T) { } `, true}, {"malformed tracing provider", false, ` - { + { "tracing_provider": "bad tracing provider", "address": ":9433", "grpc_address": ":9444", @@ -275,7 +212,7 @@ func Test_run(t *testing.T) { "cookie_secret": "zixWi1MYh77NMECGGIJQevoonYtVF+ZPRkQZrrmeRqM=", "services": "proxy", "policy": [{ "from": "https://pomerium.io", "to": "https://httpbin.org" }] - } + } `, true}, // {"simple cache", false, ` // { diff --git a/config/options.go b/config/options.go index f163ce898..1fe7875f8 100644 --- a/config/options.go +++ b/config/options.go @@ -5,10 +5,7 @@ import ( "encoding/base64" "errors" "fmt" - "net/http" "net/url" - "os" - "path/filepath" "reflect" "strings" "time" @@ -60,28 +57,7 @@ type Options struct { // This should be used only for testing. InsecureServer bool `mapstructure:"insecure_server" yaml:"insecure_server,omitempty"` - // AutoCert enables fully automated certificate management including issuance - // and renewal from LetsEncrypt. Must be used in conjunction with AutoCertFolder. - AutoCert bool `mapstructure:"autocert" yaml:"autocert,omitempty"` - - // AutoCertHandler is the HTTP challenge handler used in a http-01 acme - // https://letsencrypt.org/docs/challenge-types/#http-01-challenge - AutoCertHandler func(h http.Handler) http.Handler `hash:"ignore"` - - // AutoCertFolder specifies the location to store, and load autocert managed - // TLS certificates. - // defaults to $XDG_DATA_HOME/pomerium - AutoCertFolder string `mapstructure:"autocert_dir" yaml:"autocert_dir,omitempty"` - - // AutoCertUseStaging tells autocert to use Let's Encrypt's staging CA which - // has less strict usage limits then the (default) production CA. - // - // https://letsencrypt.org/docs/staging-environment/ - AutoCertUseStaging bool `mapstructure:"autocert_use_staging" yaml:"autocert_use_staging,omitempty"` - - Certificates []certificateFilePair `mapstructure:"certificates" yaml:"certificates,omitempty"` - - // Cert and Key is the x509 certificate used to create the HTTPS server. + // Cert and Key is the x509 certificate used to hydrate TLSCertificate Cert string `mapstructure:"certificate" yaml:"certificate,omitempty"` Key string `mapstructure:"certificate_key" yaml:"certificate_key,omitempty"` @@ -89,7 +65,8 @@ type Options struct { CertFile string `mapstructure:"certificate_file" yaml:"certificate_file,omitempty"` KeyFile string `mapstructure:"certificate_key_file" yaml:"certificate_key_file,omitempty"` - TLSConfig *tls.Config `hash:"ignore"` + // TLSCertificate is the hydrated tls.Certificate. + TLSCertificate *tls.Certificate `yaml:",omitempty"` // HttpRedirectAddr, if set, specifies the host and port to run the HTTP // to HTTPS redirect server on. If empty, no redirect server is started. @@ -232,12 +209,6 @@ type Options struct { viper *viper.Viper } -type certificateFilePair struct { - // CertFile and KeyFile is the x509 certificate used to hydrate TLSCertificate - CertFile string `mapstructure:"cert" yaml:"cert,omitempty"` - KeyFile string `mapstructure:"key" yaml:"key,omitempty"` -} - // DefaultOptions are the default configuration options for pomerium var defaultOptions = Options{ Debug: false, @@ -266,7 +237,6 @@ var defaultOptions = Options{ GRPCServerMaxConnectionAgeGrace: 5 * time.Minute, CacheStore: "autocache", AuthenticateCallbackPath: "/oauth2/callback", - AutoCertFolder: dataDir(), } // NewDefaultOptions returns a copy the default options. It's the caller's @@ -282,7 +252,7 @@ func NewDefaultOptions() *Options { func NewOptionsFromConfig(configFile string) (*Options, error) { o, err := optionsFromViper(configFile) if err != nil { - return nil, fmt.Errorf("config: options from config file %w", err) + return nil, fmt.Errorf("config: options from viper %w", err) } if o.Debug { log.SetDebugMode() @@ -457,10 +427,10 @@ func (o *Options) Validate() error { } // and we can set the corresponding client if o.AuthorizeURLString == "" { - o.AuthorizeURLString = "https://localhost" + DefaultAlternativeAddr + o.AuthorizeURLString = "http://localhost" + DefaultAlternativeAddr } if o.CacheURLString == "" { - o.CacheURLString = "https://localhost" + DefaultAlternativeAddr + o.CacheURLString = "http://localhost" + DefaultAlternativeAddr } } @@ -529,59 +499,21 @@ func (o *Options) Validate() error { o.Headers = make(map[string]string) } - if o.Cert != "" || o.Key != "" { - o.TLSConfig, err = cryptutil.TLSConfigFromBase64(o.TLSConfig, o.Cert, o.Key) - if err != nil { - return fmt.Errorf("config: bad cert base64 %w", err) - } + if o.InsecureServer { + log.Warn().Msg("config: insecure mode enabled") + } else if o.Cert != "" || o.Key != "" { + o.TLSCertificate, err = cryptutil.CertifcateFromBase64(o.Cert, o.Key) + } else if o.CertFile != "" || o.KeyFile != "" { + o.TLSCertificate, err = cryptutil.CertificateFromFile(o.CertFile, o.KeyFile) + } else { + err = errors.New("config:no certificates supplied nor was insecure mode set") } - - if len(o.Certificates) != 0 { - for _, c := range o.Certificates { - o.TLSConfig, err = cryptutil.TLSConfigFromFile(o.TLSConfig, c.CertFile, c.KeyFile) - if err != nil { - return fmt.Errorf("config: bad cert file %w", err) - } - } - } - - if o.CertFile != "" || o.KeyFile != "" { - o.TLSConfig, err = cryptutil.TLSConfigFromFile(o.TLSConfig, o.CertFile, o.KeyFile) - if err != nil { - return fmt.Errorf("config: bad cert file %w", err) - } - } - if o.AutoCert { - o.TLSConfig, o.AutoCertHandler, err = cryptutil.NewAutocert( - o.TLSConfig, - o.sourceHostnames(), - o.AutoCertUseStaging, - o.AutoCertFolder) - if err != nil { - return fmt.Errorf("config: autocert failed %w", err) - } - } - if !o.InsecureServer && o.TLSConfig == nil { - return fmt.Errorf("config: server must be run with `autocert`, " + - "`insecure_server` or manually provided certificates to start") + if err != nil { + return err } return nil } -func (o *Options) sourceHostnames() []string { - if len(o.Policies) == 0 { - return nil - } - var h []string - for _, p := range o.Policies { - h = append(h, p.Source.Hostname()) - } - if o.AuthenticateURL != nil { - h = append(h, o.AuthenticateURL.Hostname()) - } - return h -} - // OptionsUpdater updates local state based on an Options struct type OptionsUpdater interface { UpdateOptions(Options) error @@ -632,15 +564,3 @@ func HandleConfigUpdate(configFile string, opt *Options, services []OptionsUpdat } return newOpt } - -func dataDir() string { - homeDir, _ := os.UserHomeDir() - if homeDir == "" { - homeDir = "." - } - baseDir := filepath.Join(homeDir, ".local", "share") - if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" { - baseDir = xdgData - } - return filepath.Join(baseDir, "pomerium") -} diff --git a/config/options_test.go b/config/options_test.go index 0b6d6daac..56c67139d 100644 --- a/config/options_test.go +++ b/config/options_test.go @@ -218,7 +218,7 @@ func TestOptionsFromViper(t *testing.T) { wantErr bool }{ {"good", - []byte(`{"autocert_dir":"","insecure_server":true,"policy":[{"from": "https://from.example","to":"https://to.example"}]}`), + []byte(`{"insecure_server":true,"policy":[{"from": "https://from.example","to":"https://to.example"}]}`), &Options{ Policies: []Policy{{From: "https://from.example", To: "https://to.example"}}, CookieName: "_pomerium", @@ -235,7 +235,7 @@ func TestOptionsFromViper(t *testing.T) { }}, false}, {"good disable header", - []byte(`{"autocert_dir":"","insecure_server":true,"headers": {"disable":"true"},"policy":[{"from": "https://from.example","to":"https://to.example"}]}`), + []byte(`{"insecure_server":true,"headers": {"disable":"true"},"policy":[{"from": "https://from.example","to":"https://to.example"}]}`), &Options{ Policies: []Policy{{From: "https://from.example", To: "https://to.example"}}, CookieName: "_pomerium", @@ -423,39 +423,3 @@ func Test_HandleConfigUpdate(t *testing.T) { }) } } - -func TestOptions_sourceHostnames(t *testing.T) { - t.Parallel() - testOptions := func() *Options { - o := NewDefaultOptions() - o.SharedKey = "test" - o.Services = "all" - o.InsecureServer = true - return o - } - tests := []struct { - name string - policies []Policy - authenticateURL string - want []string - }{ - {"empty", []Policy{}, "", nil}, - {"good no authN", []Policy{{From: "https://from.example", To: "https://to.example"}}, "", []string{"from.example"}}, - {"good with authN", []Policy{{From: "https://from.example", To: "https://to.example"}}, "https://authn.example.com", []string{"from.example", "authn.example.com"}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - o := testOptions() - o.Policies = tt.policies - o.AuthenticateURLString = tt.authenticateURL - err := o.Validate() - if err != nil { - t.Fatal(err) - } - got := o.sourceHostnames() - if diff := cmp.Diff(got, tt.want); diff != "" { - t.Errorf("Options.sourceHostnames() = %v", diff) - } - }) - } -} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 3bbe16cd2..24d35c91e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -45,12 +45,12 @@ module.exports = { { text: "Configuration", link: "/configuration/" }, { text: "Recipes", link: "/recipes/" }, { text: "Enterprise", link: "/enterprise/" }, + { - text: "v0.8.x", // current tagged version + text: "v0.7.x", // current tagged version ariaLabel: "Version menu", items: [ { text: "🚧Dev", link: "https://master.docs.pomerium.io/docs" }, - { text: "v0.8.x", link: "https://0-8-0.docs.pomerium.io/docs" }, { text: "v0.7.x", link: "https://0-7-0.docs.pomerium.io/docs" }, { text: "v0.6.x", link: "https://0-6-0.docs.pomerium.io/docs" }, { text: "v0.5.x", link: "https://0-5-0.docs.pomerium.io/docs" }, diff --git a/docs/.vuepress/public/_redirects b/docs/.vuepress/public/_redirects index d59882d46..c2cc2ebd0 100644 --- a/docs/.vuepress/public/_redirects +++ b/docs/.vuepress/public/_redirects @@ -1,6 +1,5 @@ /docs/reference/reference /configuration/ /docs/reference/reference.html /configuration/ -/docs/configuration/ /configuration/ /community/ /docs/community/ /community/index.html /docs/community/ diff --git a/docs/_posts/2020-05-11-release-0-8.md b/docs/_posts/2020-05-11-release-0-8.md deleted file mode 100644 index c0d5ce43b..000000000 --- a/docs/_posts/2020-05-11-release-0-8.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Announcing Pomerium 0.8 -date: 2020-5-11 -tags: - - release - - pomerium - - announcement -author: Bobby DeSimone ---- - -# Announcing Pomerium 0.8 - -We are excited to announce the [0.8 release] of Pomerium which adds support for some of our most requested features including: - -- [**Automatic Certificate Management**] — Pomerium can now be configured to automatically retrieve and renew certificates, adding HTTPS to all Pomerium managed routes. In addition, Pomerium will do [OCSP stapling](https://en.wikipedia.org/wiki/OCSP_stapling) for automatic and custom certificates alike. -- [**Advanced Route Matching**] — Operators can now write access policy that supports route matching based on [regex], [prefix], and [path] settings. Pomerium now has the flexibility to support multiple and layered authorization policies across a single managed route. -- And finally, this release adds [**Github**](https://github.com/) as a supported identity provider. - -Pomerium had 95 commits from 8 authors across 5 organizations in this release. This release also includes additional new features, general improvements, and bug fixes, a complete list of which can be found in the [changelog]. - -As always, we recommend upgrading and testing this release in an isolated environment. If you experience any issues, please report them on the Pomerium GitHub [issue tracker]. - - - -[**advanced route matching**]: ../configuration/readme.md#policy -[**automatic certificate management**]: ../docs/reference/certificates.md#per-route-automatic-certificates -[0.8 release]: https://github.com/pomerium/pomerium/releases/tag/v8.0.0 -[changelog]: ../docs/CHANGELOG.md -[**github**]: ../docs/identity-providers/github.md -[issue tracker]: https://github.com/pomerium/pomerium/issues -[let's encrypt]: https://letsencrypt.org/ -[path]: ../configuration/readme.md#path -[prefix]: ../configuration/readme.md#prefix -[regex]: ../configuration/readme.md#regex diff --git a/docs/configuration/examples.md b/docs/configuration/examples.md index 44528e4cd..ae6f75f76 100644 --- a/docs/configuration/examples.md +++ b/docs/configuration/examples.md @@ -5,6 +5,7 @@ sidebarDepth: 2 meta: - name: keywords content: pomerium community help bugs updates features + description: >- This document describes how you users can stay up to date with pomerium, report issues, get help, and suggest new features. @@ -85,15 +86,15 @@ Customize for your identity provider run `docker-compose up -f nginx.docker-comp - Uses Google Kubernetes Engine's built-in ingress to do [HTTPS load balancing] -<<< @/docs/configuration/examples/helm/helm_gke.sh +<<< @/scripts/helm_gke.sh ### AWS ECS - Uses Amazon Elastic Container Service -<<< @/docs/configuration/examples/helm/helm_aws.sh +<<< @/scripts/helm_aws.sh -### Kubernetes +## Kubernetes - Uses Google Kubernetes Engine's built-in ingress to do [HTTPS load balancing] - HTTPS (TLS) between client, load balancer, and services @@ -128,43 +129,3 @@ Customize for your identity provider run `docker-compose up -f nginx.docker-comp [helloworld]: https://hub.docker.com/r/tutum/hello-world [httpbin]: https://httpbin.org/ [https load balancing]: https://cloud.google.com/kubernetes-engine/docs/concepts/ingress - -## Istio - -[istio]: https://github.com/istio/istio -[certmanager]: https://github.com/jetstack/cert-manager -[grafana]: https://github.com/grafana/grafana - -- Istio provides mutual TLS via sidecars and to make Istio play well with Pomerium we need to disable TLS on the Pomerium side. -- We need to provide Istio with information on how to route requests via Pomerium to their destinations. -- The following example shows how to make Grafana's [auth proxy](https://grafana.com/docs/grafana/latest/auth/auth-proxy) work with Pomerium inside of an Istio mesh. - -#### Gateway - -We are using the standard istio-ingressgateway that comes configured with Istio and attach a Gateway to it that deals with a subset of our ingress traffic based on the Host header (in this case `*.yourcompany.com`). This is the Gateway to which we will later attach VirtualServices for more granular routing decisions. Along with the Gateway, because we care about TLS, we are using Certmanager to provision a self-signed certificate (see Certmanager [docs](https://cert-manager.io/docs) for setup instructions). - -<<< @/docs/configuration/examples/kubernetes/istio/gateway.yml - -#### Virtual Services - -Here we are configuring two Virtual Services. One to route from the Gateway to the Authenticate service and one to route from the Gateway to the Pomerium Proxy, which will route the request to Grafana according to the configured Pomerium policy. - -<<< @/docs/configuration/examples/kubernetes/istio/virtual-services.yml - -#### Service Entry - -If you are enforcing mutual TLS in your service mesh you will need to add a ServiceEntry for your identity provider so that Istio knows not to expect a mutual TLS connection with, for example `https://yourcompany.okta.com`. - -<<< @/docs/configuration/examples/kubernetes/istio/service-entry.yml - -#### Pomerium Configuration - -For this example we're using the Pomerium Helm chart with the following `values.yaml` file. Things to note here are the `insecure` flag, where we are disabling TLS in Pomerium in favor of the Istio-provided TLS via sidecars. Also note the `extaEnv` arguments where we are asking Pomerium to extract the email property from the JWT and pass it on to Grafana in a header called `X-Pomerium-Claim-Email`. We need to do this because Grafana does not know how to read the Pomerium JWT but its auth-proxy authentication method can be configured to read user information from headers. The policy document contains a single route that will send all requests with a host header of `https://grafana.yourcompany.com` to the Grafana instance running in the monitoring namespace. We disable ingress because we are using the Istio ingressgateway for ingress traffic and don't need the Pomerium helm chart to create ingress objects for us. - -<<< @/docs/configuration/examples/kubernetes/istio/pomerium-helm-values.yml - -#### Grafana ini - -On the Grafana side we are using the Grafana Helm chart and what follows is the relevant section of the `values.yml` file. The most important thing here is that we need to tell Grafana from which request header to grab the username. In this case that's `X-Pomerium-Claim-Email` because we will be using the user's email (provided by your identity provider) as their username in Grafana. For all the configuration options check out the Grafana documentation about its auth-proxy authentication method. - -<<< @/docs/configuration/examples/kubernetes/istio/grafana.ini.yml diff --git a/docs/configuration/examples/config/config.example.env b/docs/configuration/examples/config/config.example.env index 1793f4f2c..154a46a9f 100644 --- a/docs/configuration/examples/config/config.example.env +++ b/docs/configuration/examples/config/config.example.env @@ -15,12 +15,10 @@ export AUTHENTICATE_SERVICE_URL=https://authenticate.corp.beyondperimeter.com # Certificates can be loaded as files or base64 encoded bytes. # See : https://www.pomerium.io/docs/reference/certificates -export AUTOCERT=TRUE # Use Let's Encrypt to fetch certs. Port 80/443 must be internet accessible. -# export AUTOCERT_DIR="./certs" # The path where you want to place your certificates -# export CERTIFICATE_FILE="xxxx" # optional, defaults to `./cert.pem` -# export CERTIFICATE_KEY_FILE="xxx" # optional, defaults to `./certprivkey.pem` -# export CERTIFICATE="xxx" # base64 encoded cert, eg. `base64 -i cert.pem` -# export CERTIFICATE_KEY="xxx" # base64 encoded key, eg. `base64 -i privkey.pem` +export CERTIFICATE_FILE="$HOME/.acme.sh/*.corp.beyondperimeter.com_ecc/fullchain.cer" # optional, defaults to `./cert.pem` +export CERTIFICATE_KEY_FILE="$HOME/.acme.sh/*.corp.beyondperimeter.com_ecc/*.corp.beyondperimeter.com.key" # optional, defaults to `./certprivkey.pem` +# export CERTIFICATE="xxxxxx" # base64 encoded cert, eg. `base64 -i cert.pem` +# export CERTIFICATE_KEY="xxxx" # base64 encoded key, eg. `base64 -i privkey.pem` # Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64` export SHARED_SECRET="$(head -c32 /dev/urandom | base64)" diff --git a/docs/configuration/examples/config/config.minimal.env b/docs/configuration/examples/config/config.minimal.env index 7c1f97527..0e01cbe9a 100644 --- a/docs/configuration/examples/config/config.minimal.env +++ b/docs/configuration/examples/config/config.minimal.env @@ -1,7 +1,8 @@ #!/bin/bash # See : https://www.pomerium.io/docs/reference/certificates -export AUTOCERT=TRUE # Use Let's Encrypt to fetch certs. Port 80/443 must be internet accessible. +export CERTIFICATE_FILE="$HOME/.acme.sh/*.corp.beyondperimeter.com_ecc/fullchain.cer" # optional, defaults to `./cert.pem` +export CERTIFICATE_KEY_FILE="$HOME/.acme.sh/*.corp.beyondperimeter.com_ecc/*.corp.beyondperimeter.com.key" # optional, defaults to `./certprivkey.pem` # 256 bit random keys export SHARED_SECRET="$(head -c32 /dev/urandom | base64)" diff --git a/docs/configuration/examples/config/config.minimal.yaml b/docs/configuration/examples/config/config.minimal.yaml index 7760fc46a..a0ee5850d 100644 --- a/docs/configuration/examples/config/config.minimal.yaml +++ b/docs/configuration/examples/config/config.minimal.yaml @@ -4,10 +4,8 @@ authenticate_service_url: https://authenticate.localhost.pomerium.io # certificate settings: https://www.pomerium.io/docs/reference/certificates.html -autocert: true - -# REMOVE FOR PRODUCTION -autocert_use_staging: true +certificate_file: "./_wildcard.localhost.pomerium.io.pem" +certificate_key_file: "./_wildcard.localhost.pomerium.io-key.pem" # identity provider settings : https://www.pomerium.io/docs/identity-providers.html idp_provider: google diff --git a/docs/configuration/examples/docker/autocert.docker-compose.yml b/docs/configuration/examples/docker/autocert.docker-compose.yml deleted file mode 100644 index c1921f275..000000000 --- a/docs/configuration/examples/docker/autocert.docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" -services: - pomerium: - image: pomerium/pomerium:v0.8.0 - environment: - # Generate new secret keys. e.g. `head -c32 /dev/urandom | base64` - - COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI= - volumes: - # Use a volume to store ACME certificates - - pomerium:/data:rw - ports: - - 443:443 - - # https://httpbin.corp.beyondperimeter.com --> Pomerium --> http://httpbin - httpbin: - image: kennethreitz/httpbin:latest - expose: - - 80 diff --git a/docs/configuration/examples/docker/basic.docker-compose.yml b/docs/configuration/examples/docker/basic.docker-compose.yml index 968fc2ab0..2906dd57f 100644 --- a/docs/configuration/examples/docker/basic.docker-compose.yml +++ b/docs/configuration/examples/docker/basic.docker-compose.yml @@ -1,7 +1,7 @@ version: "3" services: pomerium: - image: pomerium/pomerium:v0.8.0 + image: pomerium/pomerium:v0.7.0 environment: # Generate new secret keys. e.g. `head -c32 /dev/urandom | base64` - COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI= diff --git a/docs/configuration/examples/docker/nginx.docker-compose.yml b/docs/configuration/examples/docker/nginx.docker-compose.yml index 9ef102947..26f03b28b 100644 --- a/docs/configuration/examples/docker/nginx.docker-compose.yml +++ b/docs/configuration/examples/docker/nginx.docker-compose.yml @@ -12,7 +12,7 @@ services: - /var/run/docker.sock:/tmp/docker.sock:ro pomerium-authenticate: - image: pomerium/pomerium:v0.8.0 # or `build: .` to build from source + image: pomerium/pomerium:v0.7.0 # or `build: .` to build from source restart: always environment: - SERVICES=authenticate @@ -39,7 +39,7 @@ services: - 443 pomerium-proxy: - image: pomerium/pomerium:v0.8.0 # or `build: .` to build from source + image: pomerium/pomerium:v0.7.0 # or `build: .` to build from source restart: always environment: - SERVICES=proxy @@ -61,7 +61,7 @@ services: - 443 pomerium-authorize: - image: pomerium/pomerium:v0.8.0 # or `build: .` to build from source + image: pomerium/pomerium:v0.7.0 # or `build: .` to build from source restart: always environment: - SERVICES=authorize @@ -77,7 +77,7 @@ services: - 443 pomerium-cache: - image: pomerium/pomerium:v0.8.0 # or `build: .` to build from source + image: pomerium/pomerium:v0.7.0 # or `build: .` to build from source restart: always environment: - SERVICES=cache diff --git a/docs/configuration/examples/kubernetes/istio/gateway.yml b/docs/configuration/examples/kubernetes/istio/gateway.yml deleted file mode 100644 index 3fb19d672..000000000 --- a/docs/configuration/examples/kubernetes/istio/gateway.yml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: internal-gateway - namespace: istio-system -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 443 - protocol: HTTPS - name: https-default - tls: - mode: SIMPLE - serverCertificate: "sds" - privateKey: "sds" - credentialName: internal-cert - hosts: - - *.yourcompany.com ---- -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - name: internal-cert - namespace: istio-system -spec: - secretName: internal-cert - issuerRef: - name: self-signed-issuer - kind: ClusterIssuer - commonName: *.yourcompany.com - dnsNames: - - *.yourcompany.com ---- -apiVersion: cert-manager.io/v1alpha2 -kind: ClusterIssuer -metadata: - name: self-signed-issuer -spec: - selfSigned: {} diff --git a/docs/configuration/examples/kubernetes/istio/grafana.ini.yml b/docs/configuration/examples/kubernetes/istio/grafana.ini.yml deleted file mode 100644 index 2c12227e9..000000000 --- a/docs/configuration/examples/kubernetes/istio/grafana.ini.yml +++ /dev/null @@ -1,12 +0,0 @@ -grafana.ini: - users: - allow_sign_up: false - auto_assign_org: true - auto_assign_org_role: Editor - auth.proxy: - enabled: true - header_name: X-Pomerium-Claim-Email - header_property: username - auto_sign_up: true - sync_ttl: 60 - enable_login_token: false diff --git a/docs/configuration/examples/kubernetes/istio/pomerium-helm-values.yml b/docs/configuration/examples/kubernetes/istio/pomerium-helm-values.yml deleted file mode 100644 index 40b02a72c..000000000 --- a/docs/configuration/examples/kubernetes/istio/pomerium-helm-values.yml +++ /dev/null @@ -1,13 +0,0 @@ -config: - insecure: true - policy: - - from: https://grafana.yourcompany.com - to: "http://prometheus-grafana.monitoring.svc.cluster.local" - timeout: 30s - allowed_domains: - - yourcompany.com -ingress: - enabled: false - -extraEnv: - JWT_CLAIMS_HEADERS: email diff --git a/docs/configuration/examples/kubernetes/istio/service-entry.yml b/docs/configuration/examples/kubernetes/istio/service-entry.yml deleted file mode 100644 index b65dac23a..000000000 --- a/docs/configuration/examples/kubernetes/istio/service-entry.yml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: ServiceEntry -metadata: - name: external-idp - namespace: pomerium -spec: - hosts: - - yourcompany.okta.com - location: MESH_EXTERNAL - ports: - - number: 443 - name: https - protocol: TLS - resolution: DNS diff --git a/docs/configuration/examples/kubernetes/istio/virtual-services.yml b/docs/configuration/examples/kubernetes/istio/virtual-services.yml deleted file mode 100644 index c784dc475..000000000 --- a/docs/configuration/examples/kubernetes/istio/virtual-services.yml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: networking.istio.io/v1beta1 -kind: VirtualService -metadata: - name: grafana-virtual-service - namespace: pomerium -spec: - gateways: - - istio-system/internal-gateway - hosts: - - grafana.yourcompany.com - http: - - route: - - destination: - host: pomerium-proxy ---- -apiVersion: networking.istio.io/v1beta1 -kind: VirtualService -metadata: - name: authenticate-virtual-service - namespace: pomerium -spec: - gateways: - - istio-system/internal-gateway - hosts: - - authenticate.yourcompany.com - http: - - route: - - destination: - host: pomerium-authenticate ---- diff --git a/docs/configuration/examples/kubernetes/values.yaml b/docs/configuration/examples/kubernetes/values.yaml deleted file mode 100644 index d781e521e..000000000 --- a/docs/configuration/examples/kubernetes/values.yaml +++ /dev/null @@ -1,28 +0,0 @@ -authenticate: - idp: - provider: "google" - clientID: YOUR_CLIENT_ID - clientSecret: YOUR_SECRET - service: - annotations: - cloud.google.com/app-protocols: '{"https":"HTTPS"}' - -proxy: - service: - annotations: - cloud.google.com/app-protocols: '{"https":"HTTPS"}' - -service: - type: NodePort - -config: - rootDomain: corp.beyondperimeter.com - policy: - - from: https://hello.corp.beyondperimeter.com - to: http://nginx.default.svc.cluster.local:80 - allowed_domains: - - gmail.com - -ingress: - annotations: - kubernetes.io/ingress.allow-http: false \ No newline at end of file diff --git a/docs/configuration/readme.md b/docs/configuration/readme.md index e11345f58..e2b29110d 100644 --- a/docs/configuration/readme.md +++ b/docs/configuration/readme.md @@ -80,7 +80,7 @@ Enabling the debug flag will result in sensitive information being logged!!! ::: -By default, JSON encoded logs are produced. Debug enables colored, human-readable logs to be streamed to [standard out](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)>>>). In production, it's recommended to be set to `false`. +By default, JSON encoded logs are produced. Debug enables colored, human-readable logs to be streamed to [standard out](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)>). In production, it's recommended to be set to `false`. For example, if `true` @@ -127,77 +127,23 @@ Pomerium should _never_ be exposed to the internet without TLS encryption. ::: -### Autocert +### Certificate -- Environmental Variable: `AUTOCERT` -- Config File Key: `autocert` -- Type: `bool` -- Optional +- Environmental Variable: either `CERTIFICATE` or `CERTIFICATE_FILE` +- Config File Key: `certificate` or `certificate_file` +- Type: [base64 encoded] `string` or relative file location +- Required -Turning on autocert allows Pomerium to automatically retrieve, manage, and renew public facing TLS certificates from [Let's Encrypt][letsencrypt] for each of your managed pomerium routes as well as for the authenticate service. This setting must be used in conjunction with [Autocert Directory](./#autocert-directory) as Autocert must have a place to persist, and share certificate data between services. Provides [OCSP stapling](https://en.wikipedia.org/wiki/OCSP_stapling). +Certificate is the x509 _public-key_ used to establish secure HTTP and gRPC connections. -This setting can be useful in a situation where you do not have Pomerium behind a TLS terminating ingress or proxy that is already handling your public certificates on your behalf. +### Certificate Key -:::warning - -By using autocert, you agree to the [Let's Encrypt Subscriber Agreement](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf). There are [_strict_ usage limits](https://letsencrypt.org/docs/rate-limits/) per domain you should be aware of. Consider testing with `autocert_use_staging` first. - -::: - -:::warning - -Autocert requires that ports `80`/`443` be accessible from the internet in order to complete a [TLS-ALPN-01 challenge](https://letsencrypt.org/docs/challenge-types/#tls-alpn-01). - -::: - -### Autocert Directory - -- Environmental Variable: either `AUTOCERT_DIR` -- Config File Key: `autocert_dir` -- Type: `string` pointing to the path of the directory -- Required if using [Autocert](./#autocert) setting -- Default: - - - `/data/autocert` in published Pomerium docker images - - [$XDG_DATA_HOME](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) - - `$HOME/.local/share/pomerium` - -Autocert directory is path in which autocert will store x509 certificate data. - -### Autocert Use Staging - -- Environmental Variable: `AUTOCERT_USE_STAGING` -- Config File Key: `autocert_use_staging` -- Type: `bool` -- Optional - -Let's Encrypt has strict [usage limits](https://letsencrypt.org/docs/rate-limits/). Enabling this setting allows you to use Let's Encrypt's [staging environment](https://letsencrypt.org/docs/staging-environment/) which has much more lax usage limits. - -### Certificates - -- Config File Key: `certificates` (not yet settable using environmental variables) -- Config File Key: `certificate` / `certificate_key` -- Config File Key: `certificate_file` / `certificate_key_file` -- Environmental Variable: `CERTIFICATE` / `CERTIFICATE_KEY` -- Environmental Variable: `CERTIFICATE_FILE` / `CERTIFICATE_KEY_FILE` -- Type: array of relative file locations `string` +- Environmental Variable: either `CERTIFICATE_KEY` or `CERTIFICATE_KEY_FILE` +- Config File Key: `certificate_key` or `certificate_key_file` - Type: [base64 encoded] `string` -- Type: certificate relative file location `string` -- Required (if insecure not set) +- Required -Certificates are the x509 _public-key_ and _private-key_ used to establish secure HTTP and gRPC connections. Any combination of the above can be used together, and are additive. Use in conjunction with `Autocert` to get OCSP stapling. - -For example, if specifying multiple certificates at once: - -```yaml -certificates: - - cert: "$HOME/.acme.sh/authenticate.example.com_ecc/fullchain.cer" - key: "$HOME/.acme.sh/authenticate.example.com_ecc/authenticate.example.com.key" - - cert: "$HOME/.acme.sh/httpbin.example.com_ecc/fullchain.cer" - key: "$HOME/.acme.sh/httpbin.example.com_ecc/httpbin.example.com.key" - - cert: "$HOME/.acme.sh/prometheus.example.com_ecc/fullchain.cer" - key: "$HOME/.acme.sh/prometheus.example.com_ecc/prometheus.example.com.key" -``` +Certificate key is the x509 _private-key_ used to establish secure HTTP and gRPC connections. ### Global Timeouts @@ -967,7 +913,7 @@ Note: This setting will replace (not append) the system's trust store for a give - Type: [base64 encoded] `string` or relative file location - Optional -Pomerium supports client certificates which can be used to enforce [mutually authenticated and encrypted TLS connections](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS). For more details, see our [mTLS example repository](https://github.com/pomerium/examples/tree/master/mutual-tls) and the [certificate docs](../docs/reference/certificates.md). +Pomerium supports client certificates which can be used to enforce [mutually authenticated and encrypted TLS connections](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS). For more details, see our [mTLS example repository](https://github.com/pomerium/examples/tree/master/mutual-tls) and the [certificate docs](./certificates.md). ### Set Request Headers @@ -1009,17 +955,16 @@ See [ProxyPreserveHost](http://httpd.apache.org/docs/2.0/mod/mod_proxy.html#prox - Type: [base64 encoded] `string` - Optional -Signing key is the base64 encoded key used to sign outbound requests. For more information see the [signed headers] docs. +Signing key is the base64 encoded key used to sign outbound requests. For more information see the [signed headers](./signed-headers.md) docs. If no certificate is specified, one will be generated for you and the base64'd public key will be added to the logs. [base64 encoded]: https://en.wikipedia.org/wiki/Base64 [environmental variables]: https://en.wikipedia.org/wiki/Environment_variable -[identity provider]: ../docs/identity-providers/ +[identity provider]: ./identity-providers.md [json]: https://en.wikipedia.org/wiki/JSON [letsencrypt]: https://letsencrypt.org/ [oidc rfc]: https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest [script]: https://github.com/pomerium/pomerium/blob/master/scripts/generate_wildcard_cert.sh -[signed headers]: ./signed-headers.md [toml]: https://en.wikipedia.org/wiki/TOML [yaml]: https://en.wikipedia.org/wiki/YAML diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index f69414904..858a257ab 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -1,36 +1,5 @@ # Changelog -## v0.8.0 - -To see a complete list of changes [see the diff](https://github.com/pomerium/pomerium/compare/v0.7.0...v0.8.0). - -### New - -- cryptutil: add automatic certificate management @desimone [GH-644] -- implement path-based route matching @calebdoxsey [GH-615] -- internal/identity: implement github provider support @Lumexralph [GH-582] -- proxy: add configurable JWT claim headers @travisgroth (#596) -- proxy: remove extra session unmarshalling @desimone (#592) - -### Changes - -- ci: Switch integration tests from minikube to kind @travisgroth [GH-656] -- integration-tests: add CORS test @calebdoxsey [GH-662] -- integration-tests: add websocket enabled/disabled test @calebdoxsey [GH-661] -- integration-tests: set_request_headers and preserve_host_header options @calebdoxsey [GH-668] -- pre-commit: add pre-commit configuration @calebdoxsey [GH-666] -- proxy: improve JWT header behavior @travisgroth [GH-642] - -## Fixed - -- authorize: fix authorization check for allowed_domains to only match current route @calebdoxsey [GH-624] -- authorize: fix unexpected panic on reload @travisgroth [GH-652] -- site: fix site on mobile @desimone [GH-597] - -### Documentation - -- deploy: autocert documentation and defaults @travisgroth [GH-658] - ## v0.7.5 ### Fixed @@ -77,7 +46,7 @@ There were no changes in the v0.7.1 release, but we updated the build process sl ### New -- *: remove import path comments @desimone [GH-545] +- \*: remove import path comments @desimone [GH-545] - authenticate: make callback path configurable @desimone [GH-493] - authenticate: return 401 for some specific error codes @cuonglm [GH-561] - authorization: log audience claim failure @desimone [GH-553] @@ -162,7 +131,6 @@ There were no changes in the v0.7.1 release, but we updated the build process sl - config: Remove CookieRefresh [GH-428] @u5surf [GH-436] - config: validate that `shared_key` does not contain whitespace @travisgroth [GH-427] - httputil : wrap handlers for additional context @desimone [GH-413] -- forward-auth: validate using forwarded uri header @branchmispredictor [GH-600] ### Fixed diff --git a/docs/docs/community/contributing.md b/docs/docs/community/contributing.md index 6553dc67b..a505ce44a 100644 --- a/docs/docs/community/contributing.md +++ b/docs/docs/community/contributing.md @@ -5,6 +5,7 @@ sidebarDepth: 0 meta: - name: keywords content: pomerium community contributing pr code + description: >- This document describes how you can find issues to work on, fix/add documentation, and how setup Pomerium for local development. @@ -92,4 +93,4 @@ We use [Netlify](https://www.netlify.com) to build and host our docs. One of nic [httpbin]: https://httpbin.org/ [identity provider]: ../identity-providers/readme.md [make]: https://en.wikipedia.org/wiki/Make_(software) -[tls certificates]: ../reference/certificates.md +[wild-card tls certificate]: ../reference/certificates.md diff --git a/docs/docs/quick-start/binary.md b/docs/docs/quick-start/binary.md index bec9e60af..a0354afa7 100644 --- a/docs/docs/quick-start/binary.md +++ b/docs/docs/quick-start/binary.md @@ -13,7 +13,7 @@ The following quick-start guide covers how to configure and run Pomerium using t ## Prerequisites - A configured [identity provider] -- [TLS certificates] +- A [wild-card TLS certificate] ## Download @@ -52,6 +52,6 @@ Browse to `external-httpbin.your.domain.example`. Connections between you and [h [download]: https://github.com/pomerium/pomerium/releases [environmental configuration variables]: https://12factor.net/config [httpbin]: https://httpbin.org/ -[identity provider]: ../identity-providers/ +[identity provider]: ../docs/identity-providers/ [make]: https://en.wikipedia.org/wiki/Make_(software) -[tls certificates]: ../reference/certificates.md +[wild-card tls certificate]: ../reference/certificates.md diff --git a/docs/docs/quick-start/from-source.md b/docs/docs/quick-start/from-source.md index 9fbf7335f..f8e350641 100644 --- a/docs/docs/quick-start/from-source.md +++ b/docs/docs/quick-start/from-source.md @@ -73,6 +73,6 @@ Browse to `httpbin.localhost.pomerium.io`. Connections between you and [httpbin] [configuration variables]: ../../configuration/readme.md [httpbin]: https://httpbin.org/ -[identity provider]: ../identity-providers/ +[identity provider]: ../docs/identity-providers/ [make]: https://en.wikipedia.org/wiki/Make_(software) -[tls certificates]: ../reference/certificates.md +[wild-card tls certificate]: ../reference/certificates.md diff --git a/docs/docs/quick-start/helm.md b/docs/docs/quick-start/helm.md index ab942613a..b91babbbd 100644 --- a/docs/docs/quick-start/helm.md +++ b/docs/docs/quick-start/helm.md @@ -17,19 +17,17 @@ This quick-start will show you how to deploy Pomerium with [Helm](https://helm.s - Install [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - Install the [Google Cloud SDK](https://cloud.google.com/kubernetes-engine/docs/quickstart) - Install [helm](https://helm.sh/docs/using_helm/) -- [TLS certificates] +- A [wild-card TLS certificate] -Though there are [many ways](https://unofficial-kubernetes.readthedocs.io/en/latest/setup/pick-right-solution/) to work with Kubernetes, for the purpose of this guide, we will be using Google's [Kubernetes Engine](https://cloud.google.com/kubernetes-engine/). That said, most of the following steps should be very similar using any other provider. +Though there are [many ways](https://kubernetes.io/docs/setup/pick-right-solution/) to work with Kubernetes, for the purpose of this guide, we will be using Google's [Kubernetes Engine](https://cloud.google.com/kubernetes-engine/). That said, most of the following steps should be very similar using any other provider. In addition to sharing many of the same features as the Kubernetes quickstart guide, the default helm deployment script also includes a bootstrapped certificate authority enabling mutually authenticated and encrypted communication between services that does not depend on the external LetsEncrypt certificates. Having the external domain certificate de-coupled makes it easier to renew external certificates. ## Configure -Download and modify the following helm_gke.sh script and values file to match your [identity provider] and [TLS certificates] settings. +Download and modify the following [helm_gke.sh script][./scripts/helm_gke.sh] to match your [identity provider] and [wild-card tls certificate] settings. -<<<@/docs/configuration/examples/helm/helm_gke.sh - -<<<@/docs/configuration/examples/kubernetes/values.yaml +<<<@/scripts/helm_gke.sh ## Run @@ -58,4 +56,4 @@ You can also navigate to the special pomerium endpoint `httpbin.your.domain.exam [identity provider]: ../identity-providers/readme.md [letsencrypt]: https://letsencrypt.org/ [script]: https://github.com/pomerium/pomerium/blob/master/scripts/generate_wildcard_cert.sh -[tls certificates]: ../reference/certificates.md +[wild-card tls certificate]: ../reference/certificates.md diff --git a/docs/docs/quick-start/kubernetes.md b/docs/docs/quick-start/kubernetes.md index 8ce12edc8..c7082d363 100644 --- a/docs/docs/quick-start/kubernetes.md +++ b/docs/docs/quick-start/kubernetes.md @@ -13,7 +13,7 @@ This quickstart will cover how to deploy Pomerium with Kubernetes. ## Prerequisites - A configured [identity provider] -- [TLS certificates] +- A [wild-card TLS certificate] - A [Google Cloud Account](https://console.cloud.google.com/) - [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - [Google Cloud SDK](https://cloud.google.com/kubernetes-engine/docs/quickstart) @@ -29,7 +29,7 @@ cd $HOME/pomerium/docs/configuration/examples/kubernetes ## Configure -Edit [./kubernetes_gke.sh] making sure to change the identity provider secret value to match your [identity provider] and [TLS certificates] settings. +Edit [./kubernetes_gke.sh] making sure to change the identity provider secret value to match your [identity provider] and [wild-card tls certificate] settings. <<<@/docs/configuration/examples/kubernetes/kubernetes_gke.sh @@ -63,9 +63,9 @@ You can also navigate to the special pomerium endpoint `httpbin.your.domain.exam ![currently logged in user](./img/logged-in-as.png) -[./kubernetes_gke.sh]: ../../configuration/examples.md#google-kubernetes-engine -[example kubernetes files]: ../../configuration/examples.md#google-kubernetes-engine +[./kubernetes_gke.sh]: ../reference/examples#google-kubernetes-engine +[example kubernetes files]: ../reference/examples#google-kubernetes-engine [identity provider]: ../identity-providers/readme.md [letsencrypt]: https://letsencrypt.org/ [script]: https://github.com/pomerium/pomerium/blob/master/scripts/generate_wildcard_cert.sh -[tls certificates]: ../reference/certificates.md +[wild-card tls certificate]: ../reference/certificates.md diff --git a/docs/docs/quick-start/readme.md b/docs/docs/quick-start/readme.md index 94011fc74..09ac392c6 100644 --- a/docs/docs/quick-start/readme.md +++ b/docs/docs/quick-start/readme.md @@ -14,7 +14,7 @@ In the following quick-start, we'll create a minimal but complete environment fo - A configured [identity provider] - [Docker] and [docker-compose] -- [TLS certificates] +- A [wild-card TLS certificate] ## Configure @@ -26,26 +26,12 @@ Create a [configuration file] (e.g `config.yaml`) for defining Pomerium's config Ensure the `docker-compose.yml` contains the correct path to your `config.yaml`. -### Autocert Docker-compose -Ensure you have set up the requisite DNS and port forwarding in [TLS certificates] +### Docker-compose Download the following `docker-compose.yml` file and modify it to: - generate new secrets -- mount your [TLS certificates] -- mount your `config.yaml` [configuration file] -- Set `autocert_use_staging` to `false` once you have finished testing - -<<< @/docs/configuration/examples/docker/autocert.docker-compose.yml - -Please note that you should use a persistent volume to store certificate data, or you may exhaust your domain quota on Let's Encrypt. - -### Wildcard Docker-compose - -Download the following `docker-compose.yml` file and modify it to: - -- generate new secrets -- mount your [TLS certificates] +- mount your [wild-card TLS certificate] - mount your `config.yaml` [configuration file] <<< @/docs/configuration/examples/docker/basic.docker-compose.yml @@ -72,4 +58,4 @@ You can also navigate to the special pomerium endpoint `httpbin.corp.yourdomain. [docker-compose]: https://docs.docker.com/compose/install/ [httpbin]: https://httpbin.org/ [identity provider]: ../identity-providers/readme.md -[tls certificates]: ../reference/certificates.md +[wild-card tls certificate]: ../reference/certificates.md diff --git a/docs/docs/quick-start/synology.md b/docs/docs/quick-start/synology.md index cbe4cc746..d6510d4ea 100644 --- a/docs/docs/quick-start/synology.md +++ b/docs/docs/quick-start/synology.md @@ -24,7 +24,7 @@ Pomerium is lightweight, can easily handle hundreds of concurrent requests, and - A [docker-capable] synology product - A [Google Cloud Account](https://console.cloud.google.com/) - A configured Google OAuth2 [identity provider] -- [TLS certificates][certificate documentation] +- A [wild-card TLS certificate][certificate documentation] Though any supported [identity provider] would work, this guide uses google. @@ -46,17 +46,17 @@ Click **Create**. Set the following **Reverse Proxy Rules**. -Field | Description --------------------- | ----------- -Description | pomerium -Source Protocol | HTTPS -Source Hostname | * -Destination Port | 8443 -HTTP/2 | Enabled -HSTS | Enabled -Destination Protocol | HTTP -Destination Hostname | localhost -Destination Port | 32443 +| Field | Description | +| -------------------- | ----------- | +| Description | pomerium | +| Source Protocol | HTTPS | +| Source Hostname | \* | +| Destination Port | 8443 | +| HTTP/2 | Enabled | +| HSTS | Enabled | +| Destination Protocol | HTTP | +| Destination Hostname | localhost | +| Destination Port | 32443 | ![Synology setup nginx reverse proxy](./img/synology-reverse-proxy.png) @@ -76,9 +76,9 @@ Once the certificate is showing on the list of certificates screen we need to te **Click configure** -Services | Certificate --------- | ------------------- -*:8443 | `*.int.nas.example` +| Services | Certificate | +| -------- | ------------------- | +| \*:8443 | `*.int.nas.example` | ![Synology assign wildcard certificate](./img/synology-certifciate-assignment.png) @@ -170,15 +170,15 @@ These are the minimum set of configuration settings to get Pomerium running in t Go to **Environment** tab. -Field | Value ------------------------- | --------------------------------------------------------------- -POLICY | output of `base64 -i policy.yaml` -INSECURE_SERVER | `TRUE`, internal routing within docker will not be encrypted. -IDP_CLIENT_SECRET | Values from setting up your [identity provider] -IDP_CLIENT_ID | Values from setting up your [identity provider] -IDP_PROVIDER | Values from setting up your [identity provider] (e.g. `google`) -COOKIE_SECRET | output of `head -c32 /dev/urandom | base64` -AUTHENTICATE_SERVICE_URL | `https://authenticate.int.nas.example` +| Field | Value | +| ------------------------ | --------------------------------------------------------------- | +| POLICY | output of `base64 -i policy.yaml` | +| INSECURE_SERVER | `TRUE`, internal routing within docker will not be encrypted. | +| IDP_CLIENT_SECRET | Values from setting up your [identity provider] | +| IDP_CLIENT_ID | Values from setting up your [identity provider] | +| IDP_PROVIDER | Values from setting up your [identity provider] (e.g. `google`) | +| COOKIE_SECRET | output of `head -c32 /dev/urandom | base64` | +| AUTHENTICATE_SERVICE_URL | `https://authenticate.int.nas.example` | For a detailed explanation, and additional options, please refer to the [configuration variable docs]. Also note, though not covered in this guide, settings can be made via a mounted configuration file. diff --git a/docs/docs/reference/certificates.md b/docs/docs/reference/certificates.md index 3b6970b6c..b88581b65 100644 --- a/docs/docs/reference/certificates.md +++ b/docs/docs/reference/certificates.md @@ -8,9 +8,7 @@ meta: # Certificates -[Certificates](https://en.wikipedia.org/wiki/X.509) and [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) play a vital role in [zero-trust][principles] networks, and in Pomerium. - -This document covers a few options in how to generate and set up TLS certificates suitable for working with pomerium. +[Certificates](https://en.wikipedia.org/wiki/X.509) and [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) play a vital role in [zero-trust][principles] networks, and in Pomerium. This document covers how to generate and set up wild-card certificates suitable for working with pomerium. This guide uses the following tools and resources: @@ -18,7 +16,13 @@ This guide uses the following tools and resources: - [Google Domains](https://domains.google.com/) registrar will be used to set up our wildcard domain and certificate validation. But any registrar would do and some providers support [automatic renewal](https://github.com/Neilpang/acme.sh/wiki/dnsapi). - [acme.sh](https://github.com/Neilpang/acme.sh) will be used to retrieve the wild-card domain certificate. Any [LetsEncrypt client](https://letsencrypt.org/docs/client-options/) that supports wildcard domains would work. -It should be noted that there are countless ways of building and managing [public-key infrastructure](https://en.wikipedia.org/wiki/Public_key_infrastructure). And although we hope this guide serves as a helpful baseline for generating and securing pomerium with certificates, these instructions should be modified to meet your own organization's tools, needs, and constraints. In a production environment you will likely be using your corporate load balancer, or a key management system to manage your certificate authority infrastructure. +It should be noted that there are countless ways of building and managing [public-key infrastructure](https://en.wikipedia.org/wiki/Public_key_infrastructure). And although we hope this guide serves as a helpful baseline for generating and securing pomerium with certificates, these instructions should be modified to meet your own organization's tools, needs, and constraints. + +::: warning + +LetsEncrypt certificates must be renewed [every 90 days](https://letsencrypt.org/2015/11/09/why-90-days.html). + +::: ## Why @@ -28,43 +32,40 @@ Since one of Pomerium's core [principles] is to treat internal and external traf - Pomerium's services **regardless** of if the network is "trusted" - Pomerium and the destination application -## Setting up DNS +## How First, you'll want to set a [CNAME](https://en.wikipedia.org/wiki/CNAME_record) record for wild-card domain name you will be using with Pomerium. ![pomerium add a text entry to your dns records](./img/certificate-wildcard-domain.png) -## Certificates - -### Per-route automatic certificates - -Pomerium itself can be used to retrieve, manage, and renew certificates certificates for free using Let's Encrypt, the only requirement is that Pomerium is able to receive public traffic on ports `80`/`443`. This is probably the easiest option. - -```yaml -autocert: true -``` - -See the [Autocert] and [Autocert Directory] settings for more details. - -### Self-signed wildcard certificate - -In production, we'd use a public certificate authority such as LetsEncrypt. But for a local proof of concept or for development, we can use [mkcert](https://mkcert.dev/) to make locally trusted development certificates with any names you'd like. The easiest, is probably to use `*.localhost.pomerium.io` which we've already pre-configured to point back to localhost. - -```bash -# Install mkcert. -go get -u github.com/FiloSottile/mkcert -# Bootstrap mkcert's root certificate into your operating system's trust store. -mkcert -install -# Create your wildcard domain. -# *.localhost.pomerium.io is helper domain we've hard-coded to route to localhost -mkcert "*.localhost.pomerium.io" -``` - -### Manual DNS Let's Encrypt wildcard certificate - Once you've setup your wildcard domain, we can use acme.sh to create a certificate-signing request with LetsEncrypt. -<<< @/docs/docs/reference/sh/generate_wildcard_cert.sh +```bash +# Requires acme.sh @ https://github.com/Neilpang/acme.sh +# Install (after reviewing, obviously) by running : +# $ curl https://get.acme.sh | sh +$HOME/.acme.sh/acme.sh \ + --issue \ + -k ec-256 \ + -d '*.corp.example.com' \ + --dns \ + --yes-I-know-dns-manual-mode-enough-go-ahead-please + +Creating domain key +The domain key is here: $HOME/.acme.sh/*.corp.example.com_ecc/*.corp.example.com.key +Single domain='*.corp.example.com' +Getting domain auth token for each domain +Getting webroot for domain='*.corp.example.com' +Add the following TXT record: +Domain: '_acme-challenge.corp.example.com' +TXT value: 'Yz0B1Uf2xjyUI7Cr9-k96P2PQnw3RIK32dMViuvT58s' +Please be aware that you prepend _acme-challenge. before your domain +so the resulting subdomain will be: _acme-challenge.corp.example.com +Please add the TXT records to the domains, and re-run with --renew. +Please check log file for more details: $HOME/.acme.sh/acme.sh.log +Removing DNS records. +Not Found domain api file: +``` LetsEncrypt will respond with the corresponding `TXT` record needed to verify our domain. @@ -72,12 +73,40 @@ LetsEncrypt will respond with the corresponding `TXT` record needed to verify ou It may take a few minutes for the DNS records to propagate. Once it does, you can run the following command to complete the certificate request process. +```bash +# Complete the certificate request now that we have validated our domain +$HOME/.acme.sh/acme.sh \ + --renew \ + --ecc \ + -k ec-256 \ + -d '*.corp.example.com' \ + --dns \ + --yes-I-know-dns-manual-mode-enough-go-ahead-please + +Renew: '*.corp.example.com' +Single domain='*.corp.example.com' +Getting domain auth token for each domain +Verifying: *.corp.example.com +Success +Verify finished, start to sign. +Cert success. +-----BEGIN CERTIFICATE----- +.... snip... +-----END CERTIFICATE----- +Your cert is in $HOME/.acme.sh/*.corp.example.com_ecc/*.corp.example.com.cer +Your cert key is in $HOME/.acme.sh/*.corp.example.com_ecc/*.corp.example.com.key +The intermediate CA cert is in $HOME/.acme.sh/*.corp.example.com_ecc/ca.cer +And the full chain certs is there: $HOME/.acme.sh/*.corp.example.com_ecc/fullchain.cer +``` + Here's how the above certificates signed by LetsEncrypt correspond to their respective Pomerium configuration settings: -Pomerium Config | Certificate file ------------------------------- | -------------------------------------------------------------- -[CERTIFICATE] | `$HOME/.acme.sh/*.corp.example.com_ecc/fullchain.cer` -[CERTIFICATE_KEY][certificate] | `$HOME/.acme.sh/*.corp.example.com_ecc/*.corp.example.com.key` +| Pomerium Config | Certificate file | +| --------------------------- | -------------------------------------------------------------- | +| [CERTIFICATE] | `$HOME/.acme.sh/*.corp.example.com_ecc/fullchain.cer` | +| [CERTIFICATE_KEY] | `$HOME/.acme.sh/*.corp.example.com_ecc/*.corp.example.com.key` | +| [CERTIFICATE_AUTHORITY] | `$HOME/.acme.sh/*.corp.example.com_ecc/ca.cer` | +| [OVERRIDE_CERTIFICATE_NAME] | `*.corp.example.com` | Your end users will see a valid certificate for all domains delegated by Pomerium. @@ -85,12 +114,6 @@ Your end users will see a valid certificate for all domains delegated by Pomeriu ![pomerium certificates A+ ssl labs rating](./img/certificates-ssl-report.png) -::: warning - -LetsEncrypt certificates must be renewed [every 90 days](https://letsencrypt.org/2015/11/09/why-90-days.html). - -::: - ## Resources Certificates, TLS, and Public Key Cryptography is a vast subject we cannot adequately cover here so if you are new to or just need a brush up, the following resources may be helpful: @@ -100,11 +123,9 @@ Certificates, TLS, and Public Key Cryptography is a vast subject we cannot adequ - [Use TLS](https://smallstep.com/blog/use-tls.html) covers why TLS should be used everywhere; not just for securing typical internet traffic but for securing service communication in both "trusted" and adversarial situations. - [Everything you should know about certificates and PKI but are too afraid to ask](https://smallstep.com/blog/everything-pki.html) -[autocert]: ../../configuration/readme.md#autocert -[autocert directory]: ../../configuration/readme.md#autocert-directory -[certificate]: ../../configuration/readme.md#certificates +[certificate]: ../../configuration/readme.md#certificate [certificate_authority]: ../../configuration/readme.md#certificate-authority -[certificate_key]: ../../configuration/readme.md#certificates +[certificate_key]: ../../configuration/readme.md#certificate-key [override_certificate_name]: ../../configuration/readme.md#override-certificate-name -[principles]: ../#why -[zero-trust]: ../#why +[principles]: ../docs/#why +[zero-trust]: ../docs/#why diff --git a/docs/docs/reference/getting-users-identity.md b/docs/docs/reference/getting-users-identity.md index 3ab973c60..5c16967a7 100644 --- a/docs/docs/reference/getting-users-identity.md +++ b/docs/docs/reference/getting-users-identity.md @@ -1,6 +1,7 @@ --- title: Getting the user's identity -description: This article describes how to to get a user's identity with Pomerium. +description: >- + This article describes how to to get a user's identity with Pomerium. --- # Getting the user's identity @@ -18,19 +19,19 @@ To secure your app with signed headers, you'll need the following: A JWT attesting to the authorization of a given request is added to the downstream HTTP request header `x-pomerium-jwt-assertion`. You should verify that the JWT contains at least the following claims: - [JWT] | description -:------: | ------------------------------------------------------------------------------------------------------ - `exp` | Expiration time in seconds since the UNIX epoch. Allow 1 minute for skew. - `iat` | Issued-at time in seconds since the UNIX epoch. Allow 1 minute for skew. - `aud` | The client's final domain e.g. `httpbin.corp.example.com`. - `iss` | Issuer must be the URL of your authentication domain e.g. `authenticate.corp.example`. - `sub` | Subject is the user's id. Can be used instead of the `x-pomerium-authenticated-user-id` header. -`email` | Email is the user's email. Can be used instead of the `x-pomerium-authenticated-user-email` header. -`groups` | Groups is the user's groups. Can be used instead of the `x-pomerium-authenticated-user-groups` header. +| [JWT] | description | +| :------: | ------------------------------------------------------------------------------------------------------ | +| `exp` | Expiration time in seconds since the UNIX epoch. Allow 1 minute for skew. | +| `iat` | Issued-at time in seconds since the UNIX epoch. Allow 1 minute for skew. | +| `aud` | The client's final domain e.g. `httpbin.corp.example.com`. | +| `iss` | Issuer must be the URL of your authentication domain e.g. `authenticate.corp.example`. | +| `sub` | Subject is the user's id. Can be used instead of the `x-pomerium-authenticated-user-id` header. | +| `email` | Email is the user's email. Can be used instead of the `x-pomerium-authenticated-user-email` header. | +| `groups` | Groups is the user's groups. Can be used instead of the `x-pomerium-authenticated-user-groups` header. | ### Manual verification -Though you will very likely be verifying signed-headers programmatically in your application's middleware, and using a third-party JWT library, if you are new to JWT it may be helpful to show what manual verification looks like. +Though you will very likely be verifying signed-headers programmatically in your application's middleware, and using a third-party JWT library, if you are new to JWT it may be helpful to show what manual verification looks like. The following guide assumes you are using the provided [docker-compose.yml] as a base and [httpbin]. Httpbin gives us a convenient way of inspecting client headers. 1. Provide pomerium with a base64 encoded Elliptic Curve ([NIST P-256] aka [secp256r1] aka prime256v1) Private Key. In production, you'd likely want to get these from your KMS. @@ -48,17 +49,17 @@ Copy the base64 encoded value of your private key to `pomerium-proxy`'s environm SIGNING_KEY=ZxqyyIPPX0oWrrOwsxXgl0hHnTx3mBVhQ2kvW1YB4MM= ``` -1. Reload `pomerium-proxy`. Navigate to httpbin (by default, `https://httpbin.corp.${YOUR-DOMAIN}.com`), and login as usual. Click **request inspection**. Select `/headers'. Click **try it out** and then **execute**. You should see something like the following. +2. Reload `pomerium-proxy`. Navigate to httpbin (by default, `https://httpbin.corp.${YOUR-DOMAIN}.com`), and login as usual. Click **request inspection**. Select `/headers'. Click **try it out** and then **execute**. You should see something like the following. ![httpbin displaying jwt headers](./img/inspect-headers.png) -1. `X-Pomerium-Jwt-Assertion` is the signature value. It's less scary than it looks and basically just a compressed, json blob as described above. Navigate to [jwt.io] which provides a helpful GUI to manually verify JWT values. +3. `X-Pomerium-Jwt-Assertion` is the signature value. It's less scary than it looks and basically just a compressed, json blob as described above. Navigate to [jwt.io] which provides a helpful GUI to manually verify JWT values. -2. Paste the value of `X-Pomerium-Jwt-Assertion` header token into the `Encoded` form. You should notice that the decoded values look much more familiar. +4. Paste the value of `X-Pomerium-Jwt-Assertion` header token into the `Encoded` form. You should notice that the decoded values look much more familiar. ![httpbin displaying decoded jwt](./img/verifying-headers-1.png) -1. Finally, we want to cryptographically verify the validity of the token. To do this, we will need the signer's public key. You can simply copy and past the output of `cat ec_public.pem`. +5. Finally, we want to cryptographically verify the validity of the token. To do this, we will need the signer's public key. You can simply copy and past the output of `cat ec_public.pem`. ![httpbin displaying verified jwt](./img/verifying-headers-2.png) diff --git a/docs/docs/upgrading.md b/docs/docs/upgrading.md index b33749ab1..a838d9f24 100644 --- a/docs/docs/upgrading.md +++ b/docs/docs/upgrading.md @@ -5,7 +5,7 @@ description: >- for Pomerium. Please read it carefully. --- -# Since 0.7.0 +# Since 0.8.0 ## Breaking @@ -17,7 +17,6 @@ Although it's unlikely anyone ever used it, prior to 0.8.0 the policy configurat policy: - from: "https://example.com/some/path" ``` - The proxy and authorization server would simply ignore the path and route/authorize based on the host name. With the introduction of `prefix`, `path` and `regex` fields to the policy route configuration, we decided not to support using a path in the `from` url, since the behavior was somewhat ambiguous and better handled by the explicit fields. diff --git a/docs/jobs/Backend-Engineer.md b/docs/jobs/Backend-Engineer.md deleted file mode 100644 index 13f4c2523..000000000 --- a/docs/jobs/Backend-Engineer.md +++ /dev/null @@ -1,40 +0,0 @@ -# Backend Engineer - -Job Posted: May 12, 2020 9:59 AM Languages: Go Location: Remote US/CA - -# Backend Engineer - -Hi there! We're looking for a Backend Software Engineer to join the team! - -## Responsibilities: - -- Write robust, maintainable code -- Work with product and design to iterate on customer needs -- Review code and participate in group discussions - -## Qualifications: - -- 3+ years experience building web applications at scale -- Go (Golang) -- Experience with relational databases -- Knowledge of standard methodologies: monitoring, alerting, metrics -- Strong written communication and collaboration skills -- Experience with AWS, GCP, or Azure environments - -## Preferred Qualifications: - -- Remote work experience -- Experience with OAuth2, OIDC, SAML, and other IAM technologies -- Experience building gRPC and REST based services -- Familiarity with Kubernetes, Helm, and other Cloud Native applications - -## About Pomerium: - -Pomerium helps companies manage and secure internal access. We - -- are a fast growing, well funded, venture backed startup. -- are a fully remote team. While prior experience working remotely isn't required, we are looking for team members who perform well given a high level of independence and autonomy. -- offer competitive salaries -- are committed to building a team that represents a variety of backgrounds, perspectives, and skills. We believe the more inclusive we are, the better our company will be. - -Check out our [github](https://github.com/pomerium/pomerium) and [site](http://www.pomerium.com/) to learn more about us! diff --git a/docs/jobs/Frontend-Engineer.md b/docs/jobs/Frontend-Engineer.md deleted file mode 100644 index a12ca2424..000000000 --- a/docs/jobs/Frontend-Engineer.md +++ /dev/null @@ -1,36 +0,0 @@ -# Frontend Engineer - -Job Posted: May 12, 2020 10:04 AM Languages: Javascript, React Location: Remote US/CA - -Hi there! We're looking for a Frontend Software Engineer to join the team. - -## Responsibilities: - -- Write robust, maintainable code -- Work with product and design to iterate on customer needs -- Review code and participate in group discussions - -## Qualifications: - -- 3+ years experience building web applications at scale -- 3+ years of javascript, css, typescript, or other -- React, React Native, or similar framework experience -- Experience with relational databases -- Knowledge of standard methodologies: monitoring, alerting, metrics -- Strong written communication and collaboration skills -- Experience with AWS, GCP, or Azure environments - -## Preferred Qualifications: - -- Remote work experience -- Experience with OAuth2, OIDC, SAML, and other IAM technologies -- Familiarity with Kubernetes, Helm, and other Cloud Native applications - -## About Pomerium: - -Pomerium helps companies manage and secure internal access. We - -- are a fast growing, well funded, venture backed startup. -- are a fully remote team. While prior experience working remotely isn't required, we are looking for team members who perform well given a high level of independence and autonomy. -- offer competitive salaries -- are committed to building a team that represents a variety of backgrounds, perspectives, and skills. We believe the more inclusive we are, the better our company will be diff --git a/docs/jobs/readme.md b/docs/jobs/readme.md deleted file mode 100644 index 4b9eedb02..000000000 --- a/docs/jobs/readme.md +++ /dev/null @@ -1,12 +0,0 @@ -# Careers at Pomerium - -## Help us build the future secure application access - -Pomerium builds identity and access management infrastructure for the internet. We're helping small startups and the world's biggest companies improve their security posture, facilitate distributed remote work, and scale their efforts globally. And we'd like your help. - -Interested in joining our all-remote team? Check out some of our open positions. - -# Open Positions - -- [Frontend Engineer](./Frontend-Engineer.md) -- [Backend Engineer](./Backend-Engineer.md) diff --git a/docs/recipes/kubernetes.md b/docs/recipes/kubernetes.md index 7112cd07b..525e5e7e8 100644 --- a/docs/recipes/kubernetes.md +++ b/docs/recipes/kubernetes.md @@ -218,7 +218,7 @@ Pomerium is an identity-aware access proxy that can used to serve as an identity ### Configure -Before installing, we will configure Pomerium's configuration settings in `values.yaml`. Other than the typical configuration settings covered in the quick-start guides, we will add a few settings that will make working with Kubernetes Dashboard easier. +Before installing, we will configure Pomerium's configuration settings in `config.yaml`. Other than the typical configuration settings covered in the quick-start guides, we will add a few settings that will make working with Kubernetes Dashboard easier. We can retrieve the token to add to our proxied policy's authorization header as follows. @@ -245,43 +245,33 @@ token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9....... The above token then needs to be assigned to our route configuration and policy. ```yaml -# values.yaml -authenticate: - idp: - provider: "google" - clientID: YOUR_CLIENT_ID - clientSecret: YOUR_SECRET +# config.yaml +forward_auth_url: https://forwardauth.domain.example -forwardAuth: - enabled: true +policy: + # this route is directly proxied by pomerium & injects the authorization header + - from: https://dashboard-proxied.domain.example + to: https://helm-dashboard-kubernetes-dashboard + allowed_users: + - user@domain.example + tls_skip_verify: true # dashboard uses self-signed certificates in its default configuration + set_request_headers: + Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9..... -config: - sharedSecret: YOUR_SHARED_SECRET - cookieSecret: YOUR_COOKIE_SECRET - rootDomain: domain.example - - policy: - # this route is directly proxied by pomerium & injects the authorization header - - from: https://dashboard-proxied.domain.example - to: https://helm-dashboard-kubernetes-dashboard - allowed_users: - - user@domain.example - tls_skip_verify: true # dashboard uses self-signed certificates in its default configuration - set_request_headers: - Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9..... - - # this route is indirectly checked for access using forward-auth - - from: https://dashboard-forwardauth.domain.example - to: https://helm-dashboard-kubernetes-dashboard - allowed_users: - - user@domain.example -ingress: - annotations: - kubernetes.io/ingress.class: "nginx" - cert-manager.io/issuer: "letsencrypt-prod" # see `le.issuer.yaml` - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - secretName: pomerium-ingress-tls + # this route is indirectly checked for access using forward-auth + - from: https://dashboard-forwardauth.domain.example + to: https://helm-dashboard-kubernetes-dashboard + allowed_users: + - user@domain.example ``` + +We then add our configuration to Kubernetes as a [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/). + +```bash +# add our pomerium policy to kubernetes as a configmap +$ kubectl create configmap config --from-file="config.yaml"="config.yaml" +``` + ### Install Finally, we get to install Pomerium! 🎉 Once again, we will use Helm to deploy Pomerium. @@ -289,14 +279,24 @@ Finally, we get to install Pomerium! 🎉 Once again, we will use Helm to deploy ```bash helm install \ "helm-pomerium" \ - pomerium/pomerium \ - --values values.yaml + stable/pomerium \ + --set config.rootDomain="domain.example" \ + --set config.existingConfig="config" \ + --set authenticate.idp.provider="google" \ + --set authenticate.idp.clientID="YOUR_CLIENT_ID" \ + --set authenticate.idp.clientSecret="YOUR_SECRET" ``` ## Putting it all together Now we just need to tell external traffic how to route everything by deploying the following ingresses. +```sh +$kubectl apply -f docs/recipes/yml/pomerium.ingress.yaml +``` + +<<< @/docs/recipes/yml/pomerium.ingress.yaml + ```sh $kubectl apply -f docs/recipes/yml/dashboard-forwardauth.ingress.yaml ``` diff --git a/docs/recipes/yml/pomerium.ingress.yaml b/docs/recipes/yml/pomerium.ingress.yaml new file mode 100644 index 000000000..5cb3fb93c --- /dev/null +++ b/docs/recipes/yml/pomerium.ingress.yaml @@ -0,0 +1,33 @@ +# pomerium.ingress.yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: pomerium-authenticate + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/issuer: "letsencrypt-prod" # see `le.issuer.yaml` + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" +spec: + tls: + - hosts: + - authenticate.domain.example + secretName: pomerium-authenticate-external-tls + - hosts: + - forwardauth.domain.example + secretName: pomerium-forwardauth-external-tls + + rules: + - host: authenticate.domain.example + http: + paths: + - path: / + backend: + serviceName: helm-pomerium-authenticate + servicePort: https + - host: forwardauth.domain.example + http: + paths: + - path: / + backend: + serviceName: helm-pomerium-proxy + servicePort: https diff --git a/go.mod b/go.mod index 196d93597..eea6c7ad6 100644 --- a/go.mod +++ b/go.mod @@ -3,31 +3,35 @@ module github.com/pomerium/pomerium go 1.12 require ( + cloud.google.com/go v0.49.0 // indirect contrib.go.opencensus.io/exporter/jaeger v0.2.0 contrib.go.opencensus.io/exporter/prometheus v0.1.0 - github.com/caddyserver/certmagic v0.10.12 github.com/cespare/xxhash/v2 v2.1.1 github.com/coreos/go-oidc v2.2.1+incompatible + github.com/envoyproxy/go-control-plane v0.9.5 github.com/fsnotify/fsnotify v1.4.9 - github.com/go-acme/lego/v3 v3.7.0 github.com/go-redis/redis/v7 v7.2.0 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/mock v1.4.3 - github.com/golang/protobuf v1.4.2 - github.com/google/go-cmp v0.4.1 + github.com/golang/protobuf v1.4.0 + github.com/google/go-cmp v0.4.0 github.com/google/go-jsonnet v0.15.0 + github.com/google/uuid v1.1.1 + github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.4 - github.com/gorilla/websocket v1.4.2 + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mitchellh/hashstructure v1.0.0 + github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc github.com/onsi/ginkgo v1.11.0 // indirect github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5 github.com/onsi/gomega v1.8.1 // indirect github.com/open-policy-agent/opa v0.19.2 github.com/pelletier/go-toml v1.6.0 // indirect - github.com/pomerium/autocache v0.0.0-20200505053831-8c1cd659f055 + github.com/pkg/errors v0.9.1 // indirect + github.com/pomerium/autocache v0.0.0-20200309220911-227c9939d0ce github.com/pomerium/csrf v1.6.2-0.20190918035251-f3318380bad3 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect - github.com/prometheus/client_golang v1.6.0 + github.com/prometheus/client_golang v1.5.1 github.com/rakyll/statik v0.1.7 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect github.com/rs/cors v1.7.0 @@ -36,16 +40,21 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.7.0 - github.com/stretchr/testify v1.5.1 + github.com/spf13/viper v1.6.3 + github.com/stretchr/testify v1.4.0 github.com/uber/jaeger-client-go v2.20.1+incompatible // indirect go.etcd.io/bbolt v1.3.4 go.opencensus.io v0.22.3 - golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 - golang.org/x/net v0.0.0-20200506145744-7e3656a0809f + golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc + golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - google.golang.org/api v0.24.0 - google.golang.org/grpc v1.28.0 - gopkg.in/square/go-jose.v2 v2.5.1 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e + google.golang.org/api v0.22.0 + google.golang.org/appengine v1.6.5 // indirect + google.golang.org/genproto v0.0.0-20200204235621-fb4a7afc5178 + google.golang.org/grpc v1.27.1 + gopkg.in/cookieo9/resources-go.v2 v2.0.0-20150225115733-d27c04069d0d + gopkg.in/ini.v1 v1.51.1 // indirect + gopkg.in/square/go-jose.v2 v2.5.0 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 62def17c7..126c524ca 100644 --- a/go.sum +++ b/go.sum @@ -6,45 +6,17 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0 h1:0E3eE8MX426vUOs7aHfI7aN1BrIzzzf4ccKCSfSjGmc= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= contrib.go.opencensus.io/exporter/jaeger v0.2.0 h1:nhTv/Ry3lGmqbJ/JGvCjWxBl5ozRfqo86Ngz59UAlfk= contrib.go.opencensus.io/exporter/jaeger v0.2.0/go.mod h1:ukdzwIYYHgZ7QYtwVFQUjiT28BJHiMhTERo32s6qVgM= -contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE5H/ukPWBRo314xiDvg= contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= -github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw= -github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= -github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= -github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM= -github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= -github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= 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= @@ -53,84 +25,55 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.7 h1:fzrmmkskv067ZQbd9wERNGuxckWw67dyzoMG62p7LMo= github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8= 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/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +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/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.30.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= 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/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/caddyserver/certmagic v0.10.12 h1:aZtgzcIssiMSlP0jDdpDBbBzQ5INf5eKL9T6Nf3YzKM= -github.com/caddyserver/certmagic v0.10.12/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ= -github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU= -github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= 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= 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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533 h1:8wZizuKuZVu5COB7EsBYxBQz8nRcXXn5d4Gt91eJLvU= +github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok= -github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= -github.com/dnsimple/dnsimple-go v0.60.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.5 h1:lRJIqDD8yjV1YyPRqecMdytjDLs2fTXq363aCib5xPU= +github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -138,39 +81,25 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/lego/v3 v3.4.0 h1:deB9NkelA+TfjGHVw8J7iKl/rMtffcGMWSMmptvMv0A= -github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M= -github.com/go-acme/lego/v3 v3.7.0 h1:qC5/8/CbltyAE8fGLE6bGlqucj7pXc/vBxiLwLOsmAQ= -github.com/go-acme/lego/v3 v3.7.0/go.mod h1:4eDjjYkAsDXyNcwN8IhhZAwxz9Ltiks1Zmpv0q20J7A= -github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -178,8 +107,6 @@ github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= @@ -189,19 +116,12 @@ 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/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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= @@ -212,48 +132,35 @@ 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/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-jsonnet v0.15.0 h1:lEUXTDnVsHu+CLLzMeWAdWV4JpCgkJeDqdVNS8RtyuY= github.com/google/go-jsonnet v0.15.0/go.mod h1:ex9QcU8vzXQUDeNe4gaN1uhGQbTYpOeZ6AbWdy6JbX4= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 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= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= 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.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 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-cleanhttp v0.5.1/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= @@ -263,54 +170,36 @@ github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP 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-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 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-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 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/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 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.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/memberlist v0.2.0 h1:WeeNspppWi5s1OFefTviPQueC/Bq8dONfvNjPhiEQKE= +github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -320,10 +209,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= -github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= -github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= -github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= 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 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= @@ -335,27 +220,15 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39 h1:0E3wlIAcvD6zt/8UJgTd4JMT6UQhsnYyjCIqllyVLbs= github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= 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.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 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-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 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/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -363,32 +236,22 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw= -github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= -github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= -github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ= -github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= +github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc h1:7xGrl4tTpBQu5Zjll08WupHyq+Sp0Z/adtyf1cfk3Q8= +github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc/go.mod h1:1rLVY/DWf3U6vSZgH16S7pymfrhK2lcUlXjgGglw/lY= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/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/gocleanup v0.0.0-20140331211545-c1a5478700b5 h1:uuhPqmc+m7Nj7btxZEjdEUv+uFoBHNf2Tk/E7gGM+kY= github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5/go.mod h1:tHaogb+iP6wJXwCqVUlmxYuJb4XDyEKxxs3E4DvMBK0= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/open-policy-agent/opa v0.19.2 h1:H6Q56OHkBXr2TgX+qhlYWrM+H9lh6fKbg9IWVZWELwQ= github.com/open-policy-agent/opa v0.19.2/go.mod h1:rrwxoT/b011T0cyj+gg2VvxqTtn6N3gp/jzmr3fjW44= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= -github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= 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= @@ -398,7 +261,6 @@ github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzI github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d h1:zapSxdmZYY6vJWXFKLQ+MkI+agc+HQyfrCGowDSHiKs= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -407,8 +269,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pomerium/autocache v0.0.0-20200505053831-8c1cd659f055 h1:y73x4eEnZkCHdC46HG2h+ZAuNQUW1tu7hgvC7v+jI2o= -github.com/pomerium/autocache v0.0.0-20200505053831-8c1cd659f055/go.mod h1:xCwNSx2PcCbOG6XT0PpAGRkM5ZHnbXZLKg2ntE22j+M= +github.com/pomerium/autocache v0.0.0-20200309220911-227c9939d0ce h1:oqn4rqjp23rLyW3wwsYnWf01iNgeKsE29N6EMycTvlA= +github.com/pomerium/autocache v0.0.0-20200309220911-227c9939d0ce/go.mod h1:C6qF5bWZF7gIkJurnnWKx/PgJjYfR9aSfpOzpM454fo= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -417,15 +279,12 @@ github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prY github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= -github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= +github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= @@ -435,24 +294,19 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= -github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= @@ -467,24 +321,17 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8= github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -508,81 +355,54 @@ 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.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= +github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= 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= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY= -github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= 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= github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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-20190211182817-74369b46fc67/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-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 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/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc h1:ZGI/fILM2+ueot/UixBSoj9188jCAxVHEZEGhqq67I4= +golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -592,57 +412,39 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 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 h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= 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-20190724013045-ca1201d0de80/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-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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/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= @@ -654,23 +456,16 @@ 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/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -678,29 +473,16 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/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-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/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/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -709,10 +491,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -733,45 +512,22 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 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.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 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= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.23.0 h1:YlvGEOq2NA2my8cZ/9V8BcEO9okD48FlJcdqN0xJL3s= -google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -788,43 +544,25 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940 h1:MRHtG0U6SnaUb+s+LhNE1qt1FQ1wlhqr5E4usBKC0uA= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/genproto v0.0.0-20200204235621-fb4a7afc5178 h1:4mrurAiSXsNNb6GoJatrIsnI+JqKHAVQQ1SbMS5OtDI= +google.golang.org/genproto v0.0.0-20200204235621-fb4a7afc5178/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -832,22 +570,18 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 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/cookieo9/resources-go.v2 v2.0.0-20150225115733-d27c04069d0d h1:YjTGSRV59gG1DHCq68v2B771I9dGFxvMkugf7OKglpk= +gopkg.in/cookieo9/resources-go.v2 v2.0.0-20150225115733-d27c04069d0d/go.mod h1:kbUs813+JgwKQdecaTv87br/FZUaSEuPj8vbr2vq8sY= 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/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= -gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= -gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo= +gopkg.in/square/go-jose.v2 v2.5.0/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= @@ -858,14 +592,12 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/integration/backends/httpdetails/go.mod b/integration/backends/httpdetails/go.mod deleted file mode 100644 index b0ad06f39..000000000 --- a/integration/backends/httpdetails/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/pomerium/pomerium/integration/backends/httpdetails - -go 1.14 diff --git a/integration/backends/httpdetails/index.js b/integration/backends/httpdetails/index.js new file mode 100644 index 000000000..04612f15a --- /dev/null +++ b/integration/backends/httpdetails/index.js @@ -0,0 +1,29 @@ +const http = require("http"); + +const requestListener = function (req, res) { + const { + pathname: path, + hostname: host, + port: port, + search: query, + hash: hash, + } = new URL(req.url, `http://${req.headers.host}`); + + res.setHeader("Content-Type", "application/json"); + res.writeHead(200); + res.end( + JSON.stringify({ + headers: req.headers, + method: req.method, + host: host, + port: port, + path: path, + query: query, + hash: hash, + }) + ); +}; + +const server = http.createServer(requestListener); +console.log("starting http server on :8080"); +server.listen(8080); diff --git a/integration/backends/httpdetails/main.go b/integration/backends/httpdetails/main.go deleted file mode 100644 index 61395413a..000000000 --- a/integration/backends/httpdetails/main.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "crypto/tls" - "crypto/x509" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "net/http" - "os" -) - -func main() { - var ( - certFile, keyFile, mutualAuthCAFile, bindAddr string - ) - - flag.StringVar(&certFile, "cert-file", "", "the tls cert file to use") - flag.StringVar(&keyFile, "key-file", "", "the tls key file to use") - flag.StringVar(&mutualAuthCAFile, "mutual-auth-ca-file", "", "if set, require a client cert signed via this ca file") - flag.StringVar(&bindAddr, "bind-addr", "", "the address to listen on") - flag.Parse() - - srv := &http.Server{ - Handler: http.HandlerFunc(handle), - } - if mutualAuthCAFile != "" { - caCert, err := ioutil.ReadFile(mutualAuthCAFile) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to read mutual-auth-ca-file: %v", err) - os.Exit(1) - } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - srv.TLSConfig = &tls.Config{ - ClientCAs: caCertPool, - ClientAuth: tls.RequireAndVerifyClientCert, - } - srv.TLSConfig.BuildNameToCertificate() - } - - var err error - if certFile != "" && keyFile != "" { - if bindAddr == "" { - bindAddr = ":5443" - } - srv.Addr = bindAddr - fmt.Println("starting server on", bindAddr) - err = srv.ListenAndServeTLS(certFile, keyFile) - } else { - if bindAddr == "" { - bindAddr = ":5080" - } - srv.Addr = bindAddr - fmt.Println("starting server on", bindAddr) - err = srv.ListenAndServe() - } - if err != nil { - fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err) - os.Exit(1) - } -} - -type Result struct { - Headers map[string]string `json:"headers"` - Method string `json:"method"` - Host string `json:"host"` - Port string `json:"port"` - Path string `json:"path"` - Query string `json:"query"` - RequestURI string `json:"requestURI"` -} - -func handle(w http.ResponseWriter, r *http.Request) { - res := &Result{ - Headers: map[string]string{}, - Method: r.Method, - Host: r.Host, - Port: r.URL.Port(), - Path: r.URL.Path, - Query: r.URL.RawQuery, - RequestURI: r.RequestURI, - } - for k := range r.Header { - res.Headers[k] = r.Header.Get(k) - } - res.Headers["Host"] = r.Host - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - _ = json.NewEncoder(w).Encode(res) -} diff --git a/integration/backends/ws-echo/go.mod b/integration/backends/ws-echo/go.mod deleted file mode 100644 index e4f509383..000000000 --- a/integration/backends/ws-echo/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/pomerium/pomerium/integration/backends/ws-echo - -go 1.14 - -require github.com/gorilla/websocket v1.4.2 diff --git a/integration/backends/ws-echo/go.sum b/integration/backends/ws-echo/go.sum deleted file mode 100644 index 85efffd99..000000000 --- a/integration/backends/ws-echo/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/integration/backends/ws-echo/main.go b/integration/backends/ws-echo/main.go deleted file mode 100644 index b42f6505d..000000000 --- a/integration/backends/ws-echo/main.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "net/http" - "os" - - "github.com/gorilla/websocket" -) - -func main() { - var ( - certFile, keyFile, bindAddr string - ) - - flag.StringVar(&certFile, "cert-file", "", "the tls cert file to use") - flag.StringVar(&keyFile, "key-file", "", "the tls key file to use") - flag.StringVar(&bindAddr, "bind-addr", "", "the address to listen on") - flag.Parse() - - var err error - if certFile != "" && keyFile != "" { - if bindAddr == "" { - bindAddr = ":5443" - } - fmt.Println("starting server on", bindAddr) - err = http.ListenAndServeTLS(bindAddr, certFile, keyFile, http.HandlerFunc(handle)) - } else { - if bindAddr == "" { - bindAddr = ":5080" - } - fmt.Println("starting server on", bindAddr) - err = http.ListenAndServe(bindAddr, http.HandlerFunc(handle)) - } - if err != nil { - fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err) - os.Exit(1) - } -} - -func handle(w http.ResponseWriter, r *http.Request) { - conn, err := websocket.Upgrade(w, r, nil, 1024, 1024) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - defer conn.Close() - - for { - mt, p, err := conn.ReadMessage() - if err != nil { - return - } - - err = conn.WriteMessage(mt, p) - if err != nil { - return - } - } -} diff --git a/integration/dashboard_test.go b/integration/control_plane_test.go similarity index 66% rename from integration/dashboard_test.go rename to integration/control_plane_test.go index b973ecbf3..5eed79fbf 100644 --- a/integration/dashboard_test.go +++ b/integration/control_plane_test.go @@ -49,3 +49,29 @@ func TestDashboard(t *testing.T) { assert.Equal(t, "image/svg+xml", res.Header.Get("Content-Type")) }) } + +func TestHealth(t *testing.T) { + ctx := mainCtx + ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) + defer clearTimeout() + + for _, endpoint := range []string{"healthz", "ping"} { + endpoint := endpoint + t.Run(endpoint, func(t *testing.T) { + client := testcluster.NewHTTPClient() + + req, err := http.NewRequestWithContext(ctx, "GET", "https://restricted-httpdetails.localhost.pomerium.io/"+endpoint, nil) + if err != nil { + t.Fatal(err) + } + + res, err := client.Do(req) + if !assert.NoError(t, err, "unexpected http error") { + return + } + defer res.Body.Close() + + assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code") + }) + } +} diff --git a/integration/internal/cluster/certs.go b/integration/internal/cluster/certs.go index 1d8d7fcad..dc0c23cea 100644 --- a/integration/internal/cluster/certs.go +++ b/integration/internal/cluster/certs.go @@ -1,116 +1,69 @@ package cluster import ( + "bytes" "context" "fmt" "io/ioutil" "os" "path/filepath" "strings" + + "github.com/google/uuid" ) // TLSCerts holds the certificate authority, certificate and certificate key for a TLS connection. type TLSCerts struct { - CA []byte - Cert []byte - Key []byte - Client struct { - Cert []byte - Key []byte - } + CA string + Cert string + Key string } -// TLSCertsBundle holds various TLSCerts. -type TLSCertsBundle struct { - Trusted TLSCerts - WronglyNamed TLSCerts - Untrusted TLSCerts -} - -func bootstrapCerts(ctx context.Context) (*TLSCertsBundle, error) { - wd := filepath.Join(os.TempDir(), "pomerium-integration-tests", "certs") - err := os.MkdirAll(wd, 0755) +func bootstrapCerts(ctx context.Context) (*TLSCerts, error) { + err := run(ctx, "mkcert", withArgs("-install")) if err != nil { - return nil, fmt.Errorf("error creating integration tests working directory: %w", err) + return nil, fmt.Errorf("error install root certificate: %w", err) } - var bundle TLSCertsBundle - - var generators = []struct { - certs *TLSCerts - caroot string - install bool - name string - }{ - {&bundle.Trusted, filepath.Join(wd, "trusted"), true, "*.localhost.pomerium.io"}, - {&bundle.WronglyNamed, filepath.Join(wd, "wrongly-named"), true, "*.localhost.notpomerium.io"}, - {&bundle.Untrusted, filepath.Join(wd, "untrusted"), false, "*.localhost.pomerium.io"}, + var buf bytes.Buffer + err = run(ctx, "mkcert", withArgs("-CAROOT"), withStdout(&buf)) + if err != nil { + return nil, fmt.Errorf("error running mkcert") } - for _, generator := range generators { - err = os.MkdirAll(generator.caroot, 0755) - if err != nil { - return nil, fmt.Errorf("error creating integration tests %s working directory: %w", - filepath.Base(generator.caroot), err) - } - - args := []string{"-install"} - env := []string{"CAROOT=" + generator.caroot} - if !generator.install { - env = append(env, "TRUST_STORES=xxx") - } - err = run(ctx, "mkcert", withArgs(args...), withEnv(env...)) - if err != nil { - return nil, fmt.Errorf("error creating %s certificate authority: %w", - filepath.Base(generator.caroot), err) - } - - fp := filepath.Join(generator.caroot, "rootCA.pem") - generator.certs.CA, err = ioutil.ReadFile(fp) - if err != nil { - return nil, fmt.Errorf("error reading %s root ca: %w", - filepath.Base(generator.caroot), err) - } - - env = []string{"CAROOT=" + generator.caroot} - err = run(ctx, "mkcert", withArgs(generator.name), withWorkingDir(generator.caroot), withEnv(env...)) - if err != nil { - return nil, fmt.Errorf("error generating %s certificates: %w", - filepath.Base(generator.caroot), err) - } - err = run(ctx, "mkcert", withArgs("-client", generator.name), withWorkingDir(generator.caroot), withEnv(env...)) - if err != nil { - return nil, fmt.Errorf("error generating %s client certificates: %w", - filepath.Base(generator.caroot), err) - } - - fp = filepath.Join(generator.caroot, strings.ReplaceAll(generator.name, "*", "_wildcard")+".pem") - generator.certs.Cert, err = ioutil.ReadFile(fp) - if err != nil { - return nil, fmt.Errorf("error reading %s certificate: %w", - filepath.Base(generator.caroot), err) - } - - fp = filepath.Join(generator.caroot, strings.ReplaceAll(generator.name, "*", "_wildcard")+"-client.pem") - generator.certs.Client.Cert, err = ioutil.ReadFile(fp) - if err != nil { - return nil, fmt.Errorf("error reading %s client certificate: %w", - filepath.Base(generator.caroot), err) - } - - fp = filepath.Join(generator.caroot, strings.ReplaceAll(generator.name, "*", "_wildcard")+"-key.pem") - generator.certs.Key, err = ioutil.ReadFile(fp) - if err != nil { - return nil, fmt.Errorf("error reading %s certificate key: %w", - filepath.Base(generator.caroot), err) - } - fp = filepath.Join(generator.caroot, strings.ReplaceAll(generator.name, "*", "_wildcard")+"-client-key.pem") - generator.certs.Client.Key, err = ioutil.ReadFile(fp) - if err != nil { - return nil, fmt.Errorf("error reading %s client certificate key: %w", - filepath.Base(generator.caroot), err) - } + caPath := strings.TrimSpace(buf.String()) + ca, err := ioutil.ReadFile(filepath.Join(caPath, "rootCA.pem")) + if err != nil { + return nil, fmt.Errorf("error reading root ca: %w", err) } - return &bundle, nil + wd := filepath.Join(os.TempDir(), uuid.New().String()) + err = os.MkdirAll(wd, 0755) + if err != nil { + return nil, fmt.Errorf("error creating temporary directory: %w", err) + } + defer func() { + _ = os.RemoveAll(wd) + }() + + err = run(ctx, "mkcert", withArgs("*.localhost.pomerium.io"), withWorkingDir(wd)) + if err != nil { + return nil, fmt.Errorf("error generating certificates: %w", err) + } + + cert, err := ioutil.ReadFile(filepath.Join(wd, "_wildcard.localhost.pomerium.io.pem")) + if err != nil { + return nil, fmt.Errorf("error reading certificate: %w", err) + } + + key, err := ioutil.ReadFile(filepath.Join(wd, "_wildcard.localhost.pomerium.io-key.pem")) + if err != nil { + return nil, fmt.Errorf("error reading certificate key: %w", err) + } + + return &TLSCerts{ + CA: string(ca), + Cert: string(cert), + Key: string(key), + }, nil } diff --git a/integration/internal/cluster/cluster.go b/integration/internal/cluster/cluster.go index a06d1a9bc..7e07b3ed2 100644 --- a/integration/internal/cluster/cluster.go +++ b/integration/internal/cluster/cluster.go @@ -11,10 +11,10 @@ import ( // A Cluster is used to configure a kubernetes cluster. type Cluster struct { - Transport *http.Transport + workingDir string - workingDir string - certsBundle *TLSCertsBundle + transport http.RoundTripper + certs *TLSCerts } // New creates a new Cluster. @@ -32,7 +32,7 @@ func (cluster *Cluster) NewHTTPClient() *http.Client { panic(err) } return &http.Client{ - Transport: &loggingRoundTripper{cluster.Transport}, + Transport: &loggingRoundTripper{cluster.transport}, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, diff --git a/integration/internal/cluster/cmd.go b/integration/internal/cluster/cmd.go index 6664e4667..aa8286336 100644 --- a/integration/internal/cluster/cmd.go +++ b/integration/internal/cluster/cmd.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io" - "os" "os/exec" "github.com/rs/zerolog/log" @@ -19,12 +18,6 @@ func withArgs(args ...string) cmdOption { } } -func withEnv(env ...string) cmdOption { - return func(cmd *exec.Cmd) { - cmd.Env = append(os.Environ(), env...) - } -} - func withStdin(rdr io.Reader) cmdOption { return func(cmd *exec.Cmd) { cmd.Stdin = rdr diff --git a/integration/internal/cluster/setup.go b/integration/internal/cluster/setup.go index 82a66c557..a96323ff9 100644 --- a/integration/internal/cluster/setup.go +++ b/integration/internal/cluster/setup.go @@ -15,18 +15,17 @@ import ( "time" "github.com/google/go-jsonnet" + "github.com/pomerium/pomerium/integration/internal/httputil" "github.com/rs/zerolog/log" - - "github.com/pomerium/pomerium/integration/internal/netutil" ) var requiredDeployments = []string{ - "ingress-nginx/nginx-ingress-controller", "default/httpdetails", "default/openid", "default/pomerium-authenticate", "default/pomerium-authorize", "default/pomerium-proxy", + "ingress-nginx/nginx-ingress-controller", } // Setup configures the test cluster so that it is ready for the integration tests. @@ -36,7 +35,7 @@ func (cluster *Cluster) Setup(ctx context.Context) error { return fmt.Errorf("error running kubectl cluster-info: %w", err) } - cluster.certsBundle, err = bootstrapCerts(ctx) + cluster.certs, err = bootstrapCerts(ctx) if err != nil { return err } @@ -56,14 +55,13 @@ func (cluster *Cluster) Setup(ctx context.Context) error { return err } - cluster.Transport = &http.Transport{ - DialContext: netutil.NewLocalDialer((&net.Dialer{}), map[string]string{ - "443": hostport, - }).DialContext, + cluster.transport = httputil.NewLocalRoundTripper(&http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, - } + }, map[string]string{ + "443": hostport, + }) return nil } @@ -145,21 +143,9 @@ func (cluster *Cluster) generateManifests() (string, error) { } vm := jsonnet.MakeVM() - for _, item := range []struct { - name string - certs *TLSCerts - }{ - {"trusted", &cluster.certsBundle.Trusted}, - {"wrongly-named", &cluster.certsBundle.WronglyNamed}, - {"untrusted", &cluster.certsBundle.Untrusted}, - } { - - vm.ExtVar("tls-"+item.name+"-ca", string(item.certs.CA)) - vm.ExtVar("tls-"+item.name+"-cert", string(item.certs.Cert)) - vm.ExtVar("tls-"+item.name+"-key", string(item.certs.Key)) - vm.ExtVar("tls-"+item.name+"-client-cert", string(item.certs.Client.Cert)) - vm.ExtVar("tls-"+item.name+"-client-key", string(item.certs.Client.Key)) - } + vm.ExtVar("tls-ca", cluster.certs.CA) + vm.ExtVar("tls-cert", cluster.certs.Cert) + vm.ExtVar("tls-key", cluster.certs.Key) vm.Importer(&jsonnet.FileImporter{ JPaths: []string{filepath.Join(cluster.workingDir, "manifests")}, }) @@ -178,7 +164,7 @@ func applyManifests(ctx context.Context, jsonsrc string) error { } log.Info().Msg("waiting for deployments to come up") - ctx, clearTimeout := context.WithTimeout(ctx, 15*time.Minute) + ctx, clearTimeout := context.WithTimeout(ctx, 5*time.Minute) defer clearTimeout() ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() diff --git a/integration/internal/httputil/httputil.go b/integration/internal/httputil/httputil.go new file mode 100644 index 000000000..e0c5b4a02 --- /dev/null +++ b/integration/internal/httputil/httputil.go @@ -0,0 +1,47 @@ +// Package httputil has helper functions for working with HTTP. +package httputil + +import ( + "context" + "net" + "net/http" +) + +type localRoundTripper struct { + underlying http.RoundTripper + portToAddr map[string]string +} + +// NewLocalRoundTripper creates a new http.RoundTripper which routes localhost traffic to the remote destinations +// defined by `portToAddr`. +func NewLocalRoundTripper(underlying http.RoundTripper, portToAddr map[string]string) http.RoundTripper { + lrt := &localRoundTripper{underlying: underlying, portToAddr: portToAddr} + return lrt +} + +func (lrt *localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + req = req.Clone(req.Context()) + req.URL.Host = lrt.remapHost(req.Context(), req.Host) + return lrt.underlying.RoundTrip(req) +} + +func (lrt *localRoundTripper) remapHost(ctx context.Context, hostport string) string { + host, port, err := net.SplitHostPort(hostport) + if err != nil { + host = hostport + port = "443" + } + + dst, ok := lrt.portToAddr[port] + if !ok { + return hostport + } + + ips, err := net.DefaultResolver.LookupIPAddr(ctx, host) + if err != nil || len(ips) == 0 || ips[0].String() != "127.0.0.1" { + return hostport + } + + return dst + +} diff --git a/integration/internal/netutil/netutil.go b/integration/internal/netutil/netutil.go deleted file mode 100644 index 578a8ab95..000000000 --- a/integration/internal/netutil/netutil.go +++ /dev/null @@ -1,50 +0,0 @@ -// Package netutil has helper types for working with network connections. -package netutil - -import ( - "context" - "net" -) - -// Dialer is a type that has a DialContext method for making a network connection. -type Dialer = interface { - DialContext(ctx context.Context, network, addr string) (net.Conn, error) -} - -type localDialer struct { - underlying Dialer - portToAddr map[string]string -} - -// NewLocalDialer creates a new Dialer which routes localhost traffic to the remote destinations -// defined by `portToAddr`. -func NewLocalDialer(underlying Dialer, portToAddr map[string]string) Dialer { - d := &localDialer{underlying: underlying, portToAddr: portToAddr} - return d -} - -func (d *localDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - addr = d.remapHost(ctx, addr) - return d.underlying.DialContext(ctx, network, addr) -} - -func (d *localDialer) remapHost(ctx context.Context, hostport string) string { - host, port, err := net.SplitHostPort(hostport) - if err != nil { - host = hostport - port = "443" - } - - dst, ok := d.portToAddr[port] - if !ok { - return hostport - } - - ips, err := net.DefaultResolver.LookupIPAddr(ctx, host) - if err != nil || len(ips) == 0 || ips[0].String() != "127.0.0.1" { - return hostport - } - - return dst - -} diff --git a/integration/manifests/lib/backends.libsonnet b/integration/manifests/lib/backends.libsonnet deleted file mode 100644 index 0f004de95..000000000 --- a/integration/manifests/lib/backends.libsonnet +++ /dev/null @@ -1,159 +0,0 @@ -local configMap = function(name, data) { - apiVersion: 'v1', - kind: 'ConfigMap', - metadata: { - namespace: 'default', - name: name, - labels: { - app: name, - }, - }, - data: data, -}; - -local service = function(name, tlsName, requireMutualAuth) { - local fullName = (if tlsName != null then tlsName + '-' else '') + - (if requireMutualAuth then 'mtls-' else '') + - name, - - apiVersion: 'v1', - kind: 'Service', - metadata: { - namespace: 'default', - name: fullName, - labels: { app: fullName }, - }, - spec: { - selector: { app: fullName }, - ports: [ - { - name: 'http', - port: 80, - targetPort: 'http', - }, - { - name: 'https', - port: 443, - targetPort: 'https', - }, - ], - }, -}; - -local deployment = function(name, tlsName, requireMutualAuth) { - local fullName = (if tlsName != null then tlsName + '-' else '') + - (if requireMutualAuth then 'mtls-' else '') + - name, - - apiVersion: 'apps/v1', - kind: 'Deployment', - metadata: { - namespace: 'default', - name: fullName, - }, - spec: { - replicas: 1, - selector: { matchLabels: { app: fullName } }, - template: { - metadata: { - labels: { app: fullName }, - }, - spec: { - containers: [{ - name: 'main', - image: 'golang:buster', - imagePullPolicy: 'IfNotPresent', - args: [ - 'bash', - '-c', - 'cd /src && go run . ' + - (if tlsName != null then - ' -cert-file=/certs/tls.crt -key-file=/certs/tls.key' - else - '') + - (if requireMutualAuth then - ' -mutual-auth-ca-file=/certs/tls-ca.crt' - else - ''), - ], - ports: [ - { - name: 'http', - containerPort: 5080, - }, - { - name: 'https', - containerPort: 5443, - }, - ], - volumeMounts: [ - { - name: 'src', - mountPath: '/src', - }, - { - name: 'certs', - mountPath: '/certs', - }, - ], - }], - volumes: [ - { - name: 'src', - configMap: { - name: name, - }, - }, - ] + if tlsName != null then [ - { - name: 'certs', - secret: { - secretName: 'pomerium-' + tlsName + '-tls', - }, - }, - ] else [ - { - name: 'certs', - emptyDir: {}, - }, - ], - }, - }, - }, -}; - -local backends = [ - { name: 'httpdetails', files: { - 'main.go': importstr '../../backends/httpdetails/main.go', - 'go.mod': importstr '../../backends/httpdetails/go.mod', - } }, - { name: 'ws-echo', files: { - 'main.go': importstr '../../backends/ws-echo/main.go', - 'go.mod': importstr '../../backends/ws-echo/go.mod', - 'go.sum': importstr '../../backends/ws-echo/go.sum', - } }, -]; - -{ - apiVersion: 'v1', - kind: 'List', - items: std.flattenArrays( - [ - [ - configMap(backend.name, backend.files), - service(backend.name, null, false), - deployment(backend.name, null, false), - service(backend.name, 'wrongly-named', false), - deployment(backend.name, 'wrongly-named', false), - service(backend.name, 'untrusted', false), - deployment(backend.name, 'untrusted', false), - ] - for backend in backends - ] + [ - [ - service('httpdetails', 'trusted', true), - deployment('httpdetails', 'trusted', true), - ], - ], - ), -} diff --git a/integration/manifests/lib/httpdetails.libsonnet b/integration/manifests/lib/httpdetails.libsonnet new file mode 100644 index 000000000..1cc5bfb7b --- /dev/null +++ b/integration/manifests/lib/httpdetails.libsonnet @@ -0,0 +1,79 @@ +{ + apiVersion: 'v1', + kind: 'List', + items: [ + { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + namespace: 'default', + name: 'httpdetails', + labels: { + app: 'httpdetails', + }, + }, + data: { + 'index.js': importstr '../../backends/httpdetails/index.js', + }, + }, + { + apiVersion: 'v1', + kind: 'Service', + metadata: { + namespace: 'default', + name: 'httpdetails', + labels: { app: 'httpdetails' }, + }, + spec: { + selector: { app: 'httpdetails' }, + ports: [{ + name: 'http', + port: 80, + targetPort: 'http', + }], + }, + }, + { + apiVersion: 'apps/v1', + kind: 'Deployment', + metadata: { + namespace: 'default', + name: 'httpdetails', + }, + spec: { + replicas: 1, + selector: { matchLabels: { app: 'httpdetails' } }, + template: { + metadata: { + labels: { app: 'httpdetails' }, + }, + spec: { + containers: [{ + name: 'httpbin', + image: 'node:14-stretch-slim', + imagePullPolicy: 'IfNotPresent', + args: [ + 'node', + '/app/index.js', + ], + ports: [{ + name: 'http', + containerPort: 8080, + }], + volumeMounts: [{ + name: 'httpdetails', + mountPath: '/app', + }], + }], + volumes: [{ + name: 'httpdetails', + configMap: { + name: 'httpdetails', + }, + }], + }, + }, + }, + }, + ], +} diff --git a/integration/manifests/lib/pomerium.libsonnet b/integration/manifests/lib/pomerium.libsonnet index 8da9b3cd6..a871adef9 100644 --- a/integration/manifests/lib/pomerium.libsonnet +++ b/integration/manifests/lib/pomerium.libsonnet @@ -1,160 +1,66 @@ local tls = import './tls.libsonnet'; -local PomeriumPolicy = function() std.flattenArrays( +local PomeriumPolicy = function() std.flattenArrays([ [ - [ - // tls_skip_verify - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://untrusted-httpdetails.default.svc.cluster.local', - path: '/tls-skip-verify-enabled', - tls_skip_verify: true, - allow_public_unauthenticated_access: true, - }, - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://untrusted-httpdetails.default.svc.cluster.local', - path: '/tls-skip-verify-disabled', - tls_skip_verify: false, - allow_public_unauthenticated_access: true, - }, - // tls_server_name - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://wrongly-named-httpdetails.default.svc.cluster.local', - path: '/tls-server-name-enabled', - tls_server_name: 'httpdetails.localhost.notpomerium.io', - allow_public_unauthenticated_access: true, - }, - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://wrongly-named-httpdetails.default.svc.cluster.local', - path: '/tls-server-name-disabled', - allow_public_unauthenticated_access: true, - }, - // tls_custom_certificate_authority - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://untrusted-httpdetails.default.svc.cluster.local', - path: '/tls-custom-ca-enabled', - tls_custom_ca: std.base64(tls.untrusted.ca), - tls_server_name: 'httpdetails.localhost.pomerium.io', - allow_public_unauthenticated_access: true, - }, - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://untrusted-httpdetails.default.svc.cluster.local', - path: '/tls-custom-ca-disabled', - allow_public_unauthenticated_access: true, - }, - // tls_client_cert - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local', - path: '/tls-client-cert-enabled', - tls_client_cert: std.base64(tls.trusted.client.cert), - tls_client_key: std.base64(tls.trusted.client.key), - tls_server_name: 'httpdetails.localhost.pomerium.io', - allow_public_unauthenticated_access: true, - }, - { - from: 'http://httpdetails.localhost.pomerium.io', - to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local', - path: '/tls-client-cert-disabled', - allow_public_unauthenticated_access: true, - }, - ], - ] + [ - [ - { - from: 'http://' + domain + '.localhost.pomerium.io', - prefix: '/by-domain', - to: 'http://' + domain + '.default.svc.cluster.local', - allowed_domains: ['dogs.test'], - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - prefix: '/by-user', - to: 'http://' + domain + '.default.svc.cluster.local', - allowed_users: ['bob@dogs.test'], - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - prefix: '/by-group', - to: 'http://' + domain + '.default.svc.cluster.local', - allowed_groups: ['admin'], - }, - // cors_allow_preflight option - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - prefix: '/cors-enabled', - cors_allow_preflight: true, - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - prefix: '/cors-disabled', - cors_allow_preflight: false, - }, - // preserve_host_header option - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - path: '/preserve-host-header-enabled', - allow_public_unauthenticated_access: true, - preserve_host_header: true, - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - path: '/preserve-host-header-disabled', - allow_public_unauthenticated_access: true, - preserve_host_header: false, - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - allow_public_unauthenticated_access: true, - set_request_headers: { - 'X-Custom-Request-Header': 'custom-request-header-value', - }, - }, - ] - for domain in ['httpdetails', 'fa-httpdetails', 'ws-echo'] - ] + [ - [ - { - from: 'http://enabled-ws-echo.localhost.pomerium.io', - to: 'http://ws-echo.default.svc.cluster.local', - allow_public_unauthenticated_access: true, - allow_websockets: true, - }, - { - from: 'http://disabled-ws-echo.localhost.pomerium.io', - to: 'http://ws-echo.default.svc.cluster.local', - allow_public_unauthenticated_access: true, - }, - ], + { + from: 'http://' + domain + '.localhost.pomerium.io', + prefix: '/by-domain', + to: 'http://' + domain + '.default.svc.cluster.local', + allowed_domains: ['dogs.test'], + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + prefix: '/by-user', + to: 'http://' + domain + '.default.svc.cluster.local', + allowed_users: ['bob@dogs.test'], + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + prefix: '/by-group', + to: 'http://' + domain + '.default.svc.cluster.local', + allowed_groups: ['admin'], + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + allow_public_unauthenticated_access: true, + }, + { + from: 'http://restricted-' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + }, ] -); + for domain in ['httpdetails', 'fa-httpdetails'] +]); local PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), ''))); -local PomeriumTLSSecret = function(name) { +local PomeriumTLSSecret = function() { apiVersion: 'v1', kind: 'Secret', type: 'kubernetes.io/tls', metadata: { namespace: 'default', - name: 'pomerium-' + name + '-tls', + name: 'pomerium-tls', }, data: { - 'tls-ca.crt': std.base64(tls[name].ca), - 'tls.crt': std.base64(tls[name].cert), - 'tls.key': std.base64(tls[name].key), - 'tls-client.crt': std.base64(tls[name].client.cert), - 'tls-client.key': std.base64(tls[name].client.key), + 'tls.crt': std.base64(tls.cert), + 'tls.key': std.base64(tls.key), + }, +}; + +local PomeriumCAsConfigMap = function() { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + namespace: 'default', + name: 'pomerium-cas', + labels: { + 'app.kubernetes.io/part-of': 'pomerium', + }, + }, + data: { + 'pomerium.crt': tls.ca, }, }; @@ -184,8 +90,8 @@ local PomeriumConfigMap = function() { SHARED_SECRET: 'Wy+c0uSuIM0yGGXs82MBwTZwRiZ7Ki2T0LANnmzUtkI=', COOKIE_SECRET: 'eZ91a/j9fhgki9zPDU5zHdQWX4io89pJanChMVa5OoM=', - CERTIFICATE: std.base64(tls.trusted.cert), - CERTIFICATE_KEY: std.base64(tls.trusted.key), + CERTIFICATE: std.base64(tls.cert), + CERTIFICATE_KEY: std.base64(tls.key), IDP_PROVIDER: 'oidc', IDP_PROVIDER_URL: 'https://openid.localhost.pomerium.io', @@ -231,43 +137,25 @@ local PomeriumDeployment = function(svc) { 'openid.localhost.pomerium.io', ], }], - initContainers: [ - { - name: 'init', - image: 'buildpack-deps:buster-curl', - imagePullPolicy: 'IfNotPresent', - command: ['sh', '-c', ||| - cp /incoming-certs/trusted/tls-ca.crt /usr/local/share/ca-certificates/pomerium-trusted.crt - cp /incoming-certs/wrongly-named/tls-ca.crt /usr/local/share/ca-certificates/pomerium-wrongly-named.crt - update-ca-certificates - |||], - volumeMounts: [ - { - name: 'trusted-incoming-certs', - mountPath: '/incoming-certs/trusted', - }, - { - name: 'wrongly-named-incoming-certs', - mountPath: '/incoming-certs/wrongly-named', - }, - { - name: 'outgoing-certs', - mountPath: '/etc/ssl/certs', - }, - ], - }, - ] + if svc == 'authenticate' then [ - { - name: 'wait-for-openid', - image: 'buildpack-deps:buster-curl', - imagePullPolicy: 'IfNotPresent', - command: ['sh', '-c', ||| - while ! curl http://openid.default.svc.cluster.local/.well-known/openid-configuration ; do - sleep 5 - done - |||], - }, - ] else [], + initContainers: [{ + name: 'pomerium-' + svc + '-certs', + image: 'buildpack-deps:buster-curl', + imagePullPolicy: 'Always', + command: ['sh', '-c', ||| + cp /incoming-certs/* /usr/local/share/ca-certificates + update-ca-certificates + |||], + volumeMounts: [ + { + name: 'incoming-certs', + mountPath: '/incoming-certs', + }, + { + name: 'outgoing-certs', + mountPath: '/etc/ssl/certs', + }, + ], + }], containers: [{ name: 'pomerium-' + svc, image: 'pomerium/pomerium:dev', @@ -292,15 +180,9 @@ local PomeriumDeployment = function(svc) { }], volumes: [ { - name: 'trusted-incoming-certs', - secret: { - secretName: 'pomerium-trusted-tls', - }, - }, - { - name: 'wrongly-named-incoming-certs', - secret: { - secretName: 'pomerium-wrongly-named-tls', + name: 'incoming-certs', + configMap: { + name: 'pomerium-cas', }, }, { @@ -348,8 +230,7 @@ local PomeriumIngress = function() { 'forward-authenticate.localhost.pomerium.io', 'httpecho.localhost.pomerium.io', 'httpdetails.localhost.pomerium.io', - 'enabled-ws-echo.localhost.pomerium.io', - 'disabled-ws-echo.localhost.pomerium.io', + 'restricted-httpdetails.localhost.pomerium.io', ], apiVersion: 'extensions/v1beta1', @@ -369,7 +250,7 @@ local PomeriumIngress = function() { hosts: [ 'authenticate.localhost.pomerium.io', ] + proxyHosts, - secretName: 'pomerium-trusted-tls', + secretName: 'pomerium-tls', }, ], rules: [ @@ -421,7 +302,7 @@ local PomeriumForwardAuthIngress = function() { hosts: [ 'fa-httpdetails.localhost.pomerium.io', ], - secretName: 'pomerium-trusted-tls', + secretName: 'pomerium-tls', }, ], rules: [ @@ -455,9 +336,8 @@ local PomeriumForwardAuthIngress = function() { kind: 'List', items: [ PomeriumConfigMap(), - PomeriumTLSSecret('trusted'), - PomeriumTLSSecret('untrusted'), - PomeriumTLSSecret('wrongly-named'), + PomeriumCAsConfigMap(), + PomeriumTLSSecret(), PomeriumService('authenticate'), PomeriumDeployment('authenticate'), PomeriumService('authorize'), diff --git a/integration/manifests/lib/reference-openid-provider.libsonnet b/integration/manifests/lib/reference-openid-provider.libsonnet index 2d5ced32b..10875318a 100644 --- a/integration/manifests/lib/reference-openid-provider.libsonnet +++ b/integration/manifests/lib/reference-openid-provider.libsonnet @@ -73,7 +73,7 @@ local Ingress = function() { hosts: [ 'openid.localhost.pomerium.io', ], - secretName: 'pomerium-trusted-tls', + secretName: 'pomerium-tls', }, ], rules: [ diff --git a/integration/manifests/lib/tls.libsonnet b/integration/manifests/lib/tls.libsonnet index 707967865..5d91910ad 100644 --- a/integration/manifests/lib/tls.libsonnet +++ b/integration/manifests/lib/tls.libsonnet @@ -1,29 +1,5 @@ { - trusted: { - cert: std.extVar('tls-trusted-cert'), - key: std.extVar('tls-trusted-key'), - ca: std.extVar('tls-trusted-ca'), - client: { - cert: std.extVar('tls-trusted-client-cert'), - key: std.extVar('tls-trusted-client-key'), - }, - }, - 'wrongly-named': { - cert: std.extVar('tls-wrongly-named-cert'), - key: std.extVar('tls-wrongly-named-key'), - ca: std.extVar('tls-wrongly-named-ca'), - client: { - cert: std.extVar('tls-wrongly-named-client-cert'), - key: std.extVar('tls-wrongly-named-client-key'), - }, - }, - untrusted: { - cert: std.extVar('tls-untrusted-cert'), - key: std.extVar('tls-untrusted-key'), - ca: std.extVar('tls-untrusted-ca'), - client: { - cert: std.extVar('tls-untrusted-client-cert'), - key: std.extVar('tls-untrusted-client-key'), - }, - }, + cert: std.extVar('tls-cert'), + key: std.extVar('tls-key'), + ca: std.extVar('tls-ca'), } diff --git a/integration/manifests/manifests.jsonnet b/integration/manifests/manifests.jsonnet index 1ab57182d..470d51ff4 100644 --- a/integration/manifests/manifests.jsonnet +++ b/integration/manifests/manifests.jsonnet @@ -1,4 +1,4 @@ -local backends = import './lib/backends.libsonnet'; +local httpdetails = import './lib/httpdetails.libsonnet'; local nginxIngressController = import './lib/nginx-ingress-controller.libsonnet'; local pomerium = import './lib/pomerium.libsonnet'; local openid = import './lib/reference-openid-provider.libsonnet'; @@ -6,5 +6,5 @@ local openid = import './lib/reference-openid-provider.libsonnet'; { apiVersion: 'v1', kind: 'List', - items: nginxIngressController.items + pomerium.items + openid.items + backends.items, + items: nginxIngressController.items + pomerium.items + openid.items + httpdetails.items, } diff --git a/integration/policy_test.go b/integration/policy_test.go deleted file mode 100644 index 1ffaf9b2d..000000000 --- a/integration/policy_test.go +++ /dev/null @@ -1,338 +0,0 @@ -package main - -import ( - "context" - "crypto/tls" - "encoding/json" - "net/http" - "testing" - "time" - - "github.com/gorilla/websocket" - "github.com/stretchr/testify/assert" -) - -func TestCORS(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("enabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "OPTIONS", "https://httpdetails.localhost.pomerium.io/cors-enabled", nil) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Access-Control-Request-Method", "GET") - req.Header.Set("Origin", "https://httpdetails.localhost.pomerium.io") - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code") - }) - t.Run("disabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "OPTIONS", "https://httpdetails.localhost.pomerium.io/cors-disabled", nil) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Access-Control-Request-Method", "GET") - req.Header.Set("Origin", "https://httpdetails.localhost.pomerium.io") - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.NotEqual(t, http.StatusOK, res.StatusCode, "unexpected status code") - }) -} - -func TestPreserveHostHeader(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("enabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/preserve-host-header-enabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - var result struct { - Host string `json:"host"` - } - err = json.NewDecoder(res.Body).Decode(&result) - if !assert.NoError(t, err) { - return - } - - assert.Equal(t, "httpdetails.localhost.pomerium.io", result.Host, - "destination host should be preserved in %v", result) - }) - t.Run("disabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/preserve-host-header-disabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - var result struct { - Host string `json:"host"` - } - err = json.NewDecoder(res.Body).Decode(&result) - if !assert.NoError(t, err) { - return - } - - assert.NotEqual(t, "httpdetails.localhost.pomerium.io", result.Host, - "destination host should not be preserved in %v", result) - }) - -} - -func TestSetRequestHeaders(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - var result struct { - Headers map[string]string `json:"headers"` - } - err = json.NewDecoder(res.Body).Decode(&result) - if !assert.NoError(t, err) { - return - } - - assert.Equal(t, "custom-request-header-value", result.Headers["X-Custom-Request-Header"], - "expected custom request header to be sent upstream") - -} - -func TestWebsocket(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("disabled", func(t *testing.T) { - ws, _, err := (&websocket.Dialer{ - NetDialContext: testcluster.Transport.DialContext, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }).DialContext(ctx, "wss://disabled-ws-echo.localhost.pomerium.io", nil) - if !assert.Error(t, err, "expected bad handshake when websocket is not enabled") { - ws.Close() - return - } - }) - t.Run("enabled", func(t *testing.T) { - ws, _, err := (&websocket.Dialer{ - NetDialContext: testcluster.Transport.DialContext, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }).DialContext(ctx, "wss://enabled-ws-echo.localhost.pomerium.io", nil) - if !assert.NoError(t, err, "expected no error when creating websocket") { - return - } - defer ws.Close() - - msg := "hello world" - err = ws.WriteJSON("hello world") - assert.NoError(t, err, "expected no error when writing json to websocket") - err = ws.ReadJSON(&msg) - assert.NoError(t, err, "expected no error when reading json from websocket") - }) -} - -func TestTLSSkipVerify(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("enabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-skip-verify-enabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusOK, res.StatusCode) - }) - t.Run("disabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-skip-verify-disabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusBadGateway, res.StatusCode) - }) -} - -func TestTLSServerName(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("enabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-server-name-enabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusOK, res.StatusCode) - }) - t.Run("disabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-server-name-disabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusBadGateway, res.StatusCode) - }) -} - -func TestTLSCustomCA(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("enabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-custom-ca-enabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusOK, res.StatusCode) - }) - t.Run("disabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-custom-ca-disabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusBadGateway, res.StatusCode) - }) -} - -func TestTLSClientCert(t *testing.T) { - ctx := mainCtx - ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30) - defer clearTimeout() - - t.Run("enabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-client-cert-enabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusOK, res.StatusCode) - }) - t.Run("disabled", func(t *testing.T) { - client := testcluster.NewHTTPClient() - - req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/tls-client-cert-disabled", nil) - if err != nil { - t.Fatal(err) - } - - res, err := client.Do(req) - if !assert.NoError(t, err, "unexpected http error") { - return - } - defer res.Body.Close() - - assert.Equal(t, http.StatusBadGateway, res.StatusCode) - }) -} diff --git a/internal/controlplane/grpc_accesslog.go b/internal/controlplane/grpc_accesslog.go new file mode 100644 index 000000000..673de7d2f --- /dev/null +++ b/internal/controlplane/grpc_accesslog.go @@ -0,0 +1,39 @@ +package controlplane + +import ( + envoy_service_accesslog_v2 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v2" + "github.com/pomerium/pomerium/internal/log" +) + +func (srv *Server) registerAccessLogHandlers() { + envoy_service_accesslog_v2.RegisterAccessLogServiceServer(srv.GRPCServer, srv) +} + +// StreamAccessLogs receives logs from envoy and prints them to stdout. +func (srv *Server) StreamAccessLogs(stream envoy_service_accesslog_v2.AccessLogService_StreamAccessLogsServer) error { + for { + msg, err := stream.Recv() + if err != nil { + log.Error().Err(err).Msg("access log stream error, disconnecting") + return err + } + + for _, entry := range msg.GetHttpLogs().LogEntry { + evt := log.Info().Str("service", "envoy") + // common properties + evt = evt.Str("upstream-cluster", entry.GetCommonProperties().GetUpstreamCluster()) + // request properties + evt = evt.Str("method", entry.GetRequest().GetRequestMethod().String()) + evt = evt.Str("authority", entry.GetRequest().GetAuthority()) + evt = evt.Str("path", entry.GetRequest().GetPath()) + evt = evt.Str("user-agent", entry.GetRequest().GetUserAgent()) + evt = evt.Str("referer", entry.GetRequest().GetReferer()) + evt = evt.Str("forwarded-for", entry.GetRequest().GetForwardedFor()) + evt = evt.Str("request-id", entry.GetRequest().GetRequestId()) + // response properties + evt = evt.Uint32("response-code", entry.GetResponse().GetResponseCode().GetValue()) + evt = evt.Str("response-code-details", entry.GetResponse().GetResponseCodeDetails()) + evt.Msg("http-request") + } + } +} diff --git a/internal/controlplane/grpc_xds.go b/internal/controlplane/grpc_xds.go new file mode 100644 index 000000000..a2b7a87df --- /dev/null +++ b/internal/controlplane/grpc_xds.go @@ -0,0 +1,135 @@ +package controlplane + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + "github.com/pomerium/pomerium/internal/log" + "golang.org/x/sync/errgroup" +) + +func (srv *Server) registerXDSHandlers() { + envoy_service_discovery_v3.RegisterAggregatedDiscoveryServiceServer(srv.GRPCServer, srv) +} + +// StreamAggregatedResources streams xDS resources based on incoming discovery requests. +// +// This is setup as 3 concurrent goroutines: +// - The first retrieves the requests from the client. +// - The third sends responses back to the client. +// - The second waits for either the client to request a new resource type +// or for the config to have been updated +// - in either case, we loop over all of the current client versions +// and if any of them are different from the current version, we send +// the updated resource +func (srv *Server) StreamAggregatedResources(stream envoy_service_discovery_v3.AggregatedDiscoveryService_StreamAggregatedResourcesServer) error { + incoming := make(chan *envoy_service_discovery_v3.DiscoveryRequest) + outgoing := make(chan *envoy_service_discovery_v3.DiscoveryResponse) + + eg, ctx := errgroup.WithContext(stream.Context()) + // receive requests + eg.Go(func() error { + return srv.streamAggregatedResourcesIncomingStep(ctx, stream, incoming) + }) + eg.Go(func() error { + return srv.streamAggregatedResourcesProcessStep(ctx, incoming, outgoing) + }) + // send responses + eg.Go(func() error { + return srv.streamAggregatedResourcesOutgoingStep(ctx, stream, outgoing) + }) + return eg.Wait() +} + +func (srv *Server) streamAggregatedResourcesIncomingStep( + ctx context.Context, + stream envoy_service_discovery_v3.AggregatedDiscoveryService_StreamAggregatedResourcesServer, + incoming chan<- *envoy_service_discovery_v3.DiscoveryRequest, +) error { + for { + req, err := stream.Recv() + if err != nil { + return err + } + + select { + case incoming <- req: + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (srv *Server) streamAggregatedResourcesProcessStep( + ctx context.Context, + incoming <-chan *envoy_service_discovery_v3.DiscoveryRequest, + outgoing chan<- *envoy_service_discovery_v3.DiscoveryResponse, +) error { + versions := map[string]string{} + + for { + select { + case req := <-incoming: + if req.ErrorDetail != nil { + bs, _ := json.Marshal(req.ErrorDetail.Details) + log.Error(). + Err(errors.New(req.ErrorDetail.Message)). + Int32("code", req.ErrorDetail.Code). + RawJSON("details", bs).Msg("error applying configuration") + continue + } + + // update the currently stored version + // if this version is different from the current version + // we will send the response below + versions[req.TypeUrl] = req.VersionInfo + case <-srv.configUpdated: + case <-ctx.Done(): + return ctx.Err() + } + + current := srv.currentConfig.Load().(versionedOptions) + for typeURL, version := range versions { + // the versions are different, so the envoy config needs to be updated + if version != fmt.Sprint(current.version) { + res, err := srv.buildDiscoveryResponse(fmt.Sprint(current.version), typeURL, current.Options) + if err != nil { + return err + } + select { + case outgoing <- res: + case <-ctx.Done(): + return ctx.Err() + } + } + } + } +} + +func (srv *Server) streamAggregatedResourcesOutgoingStep( + ctx context.Context, + stream envoy_service_discovery_v3.AggregatedDiscoveryService_StreamAggregatedResourcesServer, + outgoing <-chan *envoy_service_discovery_v3.DiscoveryResponse, +) error { + for { + var res *envoy_service_discovery_v3.DiscoveryResponse + select { + case res = <-outgoing: + case <-ctx.Done(): + return ctx.Err() + } + + err := stream.Send(res) + if err != nil { + return err + } + } +} + +// DeltaAggregatedResources is not implemented. +func (srv *Server) DeltaAggregatedResources(in envoy_service_discovery_v3.AggregatedDiscoveryService_DeltaAggregatedResourcesServer) error { + return fmt.Errorf("DeltaAggregatedResources not implemented") +} diff --git a/internal/controlplane/http.go b/internal/controlplane/http.go new file mode 100644 index 000000000..f45b52a19 --- /dev/null +++ b/internal/controlplane/http.go @@ -0,0 +1,39 @@ +// Package controlplane contains the HTTP and gRPC base servers and the xDS gRPC implementation for envoy. +package controlplane + +import ( + "net/http" + "time" + + "github.com/gorilla/handlers" + "github.com/pomerium/pomerium/internal/frontend" + "github.com/pomerium/pomerium/internal/httputil" + "github.com/pomerium/pomerium/internal/log" + "github.com/pomerium/pomerium/internal/middleware" + "github.com/pomerium/pomerium/internal/version" +) + +func (srv *Server) addHTTPMiddleware() { + root := srv.HTTPRouter + root.Use(log.NewHandler(log.Logger)) + root.Use(log.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { + log.FromRequest(r).Debug(). + Dur("duration", duration). + Int("size", size). + Int("status", status). + Str("method", r.Method). + Str("host", r.Host). + Str("path", r.URL.String()). + Msg("http-request") + })) + root.Use(handlers.RecoveryHandler()) + root.Use(log.HeadersHandler(httputil.HeadersXForwarded)) + root.Use(log.RemoteAddrHandler("ip")) + root.Use(log.UserAgentHandler("user_agent")) + root.Use(log.RefererHandler("referer")) + root.Use(log.RequestIDHandler("req_id", "Request-Id")) + root.Use(middleware.Healthcheck("/ping", version.UserAgent())) + root.HandleFunc("/healthz", httputil.HealthCheck) + root.HandleFunc("/ping", httputil.HealthCheck) + root.PathPrefix("/.pomerium/assets/").Handler(http.StripPrefix("/.pomerium/assets/", frontend.MustAssetHandler())) +} diff --git a/internal/controlplane/server.go b/internal/controlplane/server.go new file mode 100644 index 000000000..b18f5dcac --- /dev/null +++ b/internal/controlplane/server.go @@ -0,0 +1,139 @@ +package controlplane + +import ( + "context" + "net" + "net/http" + "sync/atomic" + "time" + + "github.com/gorilla/mux" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/log" +) + +type versionedOptions struct { + config.Options + version int64 +} + +// A Server is the control-plane gRPC and HTTP servers. +type Server struct { + GRPCListener net.Listener + GRPCServer *grpc.Server + HTTPListener net.Listener + HTTPRouter *mux.Router + + currentConfig atomic.Value + configUpdated chan struct{} +} + +// NewServer creates a new Server. Listener ports are chosen by the OS. +func NewServer() (*Server, error) { + srv := &Server{ + configUpdated: make(chan struct{}, 1), + } + srv.currentConfig.Store(versionedOptions{}) + + var err error + + // setup gRPC + srv.GRPCListener, err = net.Listen("tcp4", "127.0.0.1:0") + if err != nil { + return nil, err + } + srv.GRPCServer = grpc.NewServer() + reflection.Register(srv.GRPCServer) + srv.registerXDSHandlers() + srv.registerAccessLogHandlers() + + // setup HTTP + srv.HTTPListener, err = net.Listen("tcp4", "127.0.0.1:0") + if err != nil { + _ = srv.GRPCListener.Close() + return nil, err + } + srv.HTTPRouter = mux.NewRouter() + srv.addHTTPMiddleware() + + return srv, nil +} + +// Run runs the control-plane gRPC and HTTP servers. +func (srv *Server) Run(ctx context.Context) error { + eg, ctx := errgroup.WithContext(ctx) + + // start the gRPC server + eg.Go(func() error { + log.Info().Str("addr", srv.GRPCListener.Addr().String()).Msg("starting control-plane gRPC server") + return srv.GRPCServer.Serve(srv.GRPCListener) + }) + + // gracefully stop the gRPC server on context cancellation + eg.Go(func() error { + <-ctx.Done() + + ctx, cancel := context.WithCancel(ctx) + ctx, cleanup := context.WithTimeout(ctx, time.Second*5) + defer cleanup() + + go func() { + srv.GRPCServer.GracefulStop() + cancel() + }() + + go func() { + <-ctx.Done() + srv.GRPCServer.Stop() + cancel() + }() + + <-ctx.Done() + + return nil + }) + + hsrv := (&http.Server{ + BaseContext: func(li net.Listener) context.Context { + return ctx + }, + Handler: srv.HTTPRouter, + }) + + // start the HTTP server + eg.Go(func() error { + log.Info().Str("addr", srv.HTTPListener.Addr().String()).Msg("starting control-plane HTTP server") + return hsrv.Serve(srv.HTTPListener) + }) + + // gracefully stop the HTTP server on context cancellation + eg.Go(func() error { + <-ctx.Done() + + ctx, cleanup := context.WithTimeout(ctx, time.Second*5) + defer cleanup() + + return hsrv.Shutdown(ctx) + }) + + return eg.Wait() +} + +// UpdateOptions updates the pomerium config options. +func (srv *Server) UpdateOptions(options config.Options) error { + select { + case <-srv.configUpdated: + default: + } + prev := srv.currentConfig.Load().(versionedOptions) + srv.currentConfig.Store(versionedOptions{ + Options: options, + version: prev.version + 1, + }) + srv.configUpdated <- struct{}{} + return nil +} diff --git a/internal/controlplane/xds.go b/internal/controlplane/xds.go new file mode 100644 index 000000000..837177308 --- /dev/null +++ b/internal/controlplane/xds.go @@ -0,0 +1,105 @@ +package controlplane + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strconv" + + "github.com/pomerium/pomerium/config" + + envoy_config_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" + envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_extensions_access_loggers_grpc_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" + envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/any" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (srv *Server) buildDiscoveryResponse(version string, typeURL string, options config.Options) (*envoy_service_discovery_v3.DiscoveryResponse, error) { + switch typeURL { + case "type.googleapis.com/envoy.config.listener.v3.Listener": + listeners := srv.buildListeners(options) + anys := make([]*any.Any, len(listeners)) + for i, listener := range listeners { + a, err := ptypes.MarshalAny(listener) + if err != nil { + return nil, status.Errorf(codes.Internal, "error marshaling type to any: %v", err) + } + anys[i] = a + } + return &envoy_service_discovery_v3.DiscoveryResponse{ + VersionInfo: version, + Resources: anys, + TypeUrl: typeURL, + }, nil + case "type.googleapis.com/envoy.config.cluster.v3.Cluster": + clusters := srv.buildClusters(options) + anys := make([]*any.Any, len(clusters)) + for i, cluster := range clusters { + a, err := ptypes.MarshalAny(cluster) + if err != nil { + return nil, status.Errorf(codes.Internal, "error marshaling type to any: %v", err) + } + anys[i] = a + } + return &envoy_service_discovery_v3.DiscoveryResponse{ + VersionInfo: version, + Resources: anys, + TypeUrl: typeURL, + }, nil + default: + return nil, status.Errorf(codes.Internal, "received request for unknown discovery request type: %s", typeURL) + } +} + +func (srv *Server) buildAccessLog() *envoy_config_accesslog_v3.AccessLog { + tc, _ := ptypes.MarshalAny(&envoy_extensions_access_loggers_grpc_v3.HttpGrpcAccessLogConfig{ + CommonConfig: &envoy_extensions_access_loggers_grpc_v3.CommonGrpcAccessLogConfig{ + LogName: "ingress-http", + GrpcService: &envoy_config_core_v3.GrpcService{ + TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "pomerium-control-plane-grpc", + }, + }, + }, + }, + }) + return &envoy_config_accesslog_v3.AccessLog{ + Name: "envoy.access_loggers.http_grpc", + ConfigType: &envoy_config_accesslog_v3.AccessLog_TypedConfig{TypedConfig: tc}, + } +} + +func buildAddress(hostport string, defaultPort int) *envoy_config_core_v3.Address { + host, strport, err := net.SplitHostPort(hostport) + if err != nil { + host = hostport + strport = fmt.Sprint(defaultPort) + } + port, err := strconv.Atoi(strport) + if err != nil { + port = defaultPort + } + if host == "" { + host = "0.0.0.0" + } + return &envoy_config_core_v3.Address{ + Address: &envoy_config_core_v3.Address_SocketAddress{SocketAddress: &envoy_config_core_v3.SocketAddress{ + Address: host, + PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{PortValue: uint32(port)}, + }}, + } +} + +func getAbsoluteFilePath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + wd, _ := os.Getwd() + return filepath.Join(wd, filename) +} diff --git a/internal/controlplane/xds_clusters.go b/internal/controlplane/xds_clusters.go new file mode 100644 index 000000000..1a0b380c0 --- /dev/null +++ b/internal/controlplane/xds_clusters.go @@ -0,0 +1,105 @@ +package controlplane + +import ( + "net" + "net/url" + "strings" + "time" + + envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_config_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" + "github.com/golang/protobuf/ptypes" + "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/urlutil" +) + +func (srv *Server) buildClusters(options config.Options) []*envoy_config_cluster_v3.Cluster { + grpcURL := &url.URL{ + Scheme: "grpc", + Host: srv.GRPCListener.Addr().String(), + } + httpURL := &url.URL{ + Scheme: "http", + Host: srv.HTTPListener.Addr().String(), + } + authzURL := &url.URL{ + Scheme: strings.Replace(options.AuthorizeURL.Scheme, "http", "grpc", -1), + Host: options.AuthorizeURL.Host, + } + + clusters := []*envoy_config_cluster_v3.Cluster{ + srv.buildCluster("pomerium-control-plane-grpc", grpcURL), + srv.buildCluster("pomerium-control-plane-http", httpURL), + srv.buildCluster("pomerium-authz", authzURL), + } + + if config.IsProxy(options.Services) { + type clusterDestination struct { + name, scheme, hostport string + } + clusterDestinations := map[clusterDestination]struct{}{} + for _, policy := range options.Policies { + name, scheme, hostport := srv.getClusterDetails(policy.Destination) + clusterDestinations[clusterDestination{name, scheme, hostport}] = struct{}{} + } + + for dst := range clusterDestinations { + name, scheme, hostport := dst.name, dst.scheme, dst.hostport + clusters = append(clusters, srv.buildCluster(name, &url.URL{ + Scheme: scheme, + Host: hostport, + })) + } + } + + return clusters +} + +func (srv *Server) getClusterDetails(endpoint *url.URL) (name, scheme, hostport string) { + name = endpoint.Scheme + "-" + strings.Replace(endpoint.Host, ":", "--", -1) + return name, endpoint.Scheme, endpoint.Host +} + +func (srv *Server) buildCluster(name string, endpoint *url.URL) *envoy_config_cluster_v3.Cluster { + defaultPort := 80 + if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" { + defaultPort = 443 + } + + cluster := &envoy_config_cluster_v3.Cluster{ + Name: name, + ConnectTimeout: ptypes.DurationProto(time.Second * 10), + LoadAssignment: &envoy_config_endpoint_v3.ClusterLoadAssignment{ + ClusterName: name, + Endpoints: []*envoy_config_endpoint_v3.LocalityLbEndpoints{{ + LbEndpoints: []*envoy_config_endpoint_v3.LbEndpoint{{ + HostIdentifier: &envoy_config_endpoint_v3.LbEndpoint_Endpoint{ + Endpoint: &envoy_config_endpoint_v3.Endpoint{ + Address: buildAddress(endpoint.Host, defaultPort), + }, + }, + }}, + }}, + }, + RespectDnsTtl: true, + } + + if endpoint.Scheme == "grpc" { + cluster.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{} + } + + if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" { + cluster.TransportSocket = &envoy_config_core_v3.TransportSocket{ + Name: "tls", + } + } + + if net.ParseIP(urlutil.StripPort(endpoint.Host)) == nil { + cluster.ClusterDiscoveryType = &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_LOGICAL_DNS} + } else { + cluster.ClusterDiscoveryType = &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_STATIC} + } + + return cluster +} diff --git a/internal/controlplane/xds_listeners.go b/internal/controlplane/xds_listeners.go new file mode 100644 index 000000000..47b90428c --- /dev/null +++ b/internal/controlplane/xds_listeners.go @@ -0,0 +1,269 @@ +package controlplane + +import ( + "encoding/base64" + "sort" + + envoy_config_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" + envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_config_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_extensions_filters_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3" + envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/any" + "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/urlutil" +) + +var disableExtAuthz *any.Any + +func init() { + disableExtAuthz, _ = ptypes.MarshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute{ + Override: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute_Disabled{ + Disabled: true, + }, + }) +} + +func (srv *Server) buildListeners(options config.Options) []*envoy_config_listener_v3.Listener { + var listeners []*envoy_config_listener_v3.Listener + + if config.IsAuthenticate(options.Services) || config.IsProxy(options.Services) { + listeners = append(listeners, srv.buildHTTPListener(options)) + } + + if config.IsAuthorize(options.Services) || config.IsCache(options.Services) { + listeners = append(listeners, srv.buildGRPCListener(options)) + } + + return listeners +} + +func (srv *Server) buildHTTPListener(options config.Options) *envoy_config_listener_v3.Listener { + defaultPort := 80 + var transportSocket *envoy_config_core_v3.TransportSocket + if !options.InsecureServer { + defaultPort = 443 + tlsConfig, _ := ptypes.MarshalAny(srv.buildDownstreamTLSContext(options)) + transportSocket = &envoy_config_core_v3.TransportSocket{ + Name: "tls", + ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{ + TypedConfig: tlsConfig, + }, + } + } + + var virtualHosts []*envoy_config_route_v3.VirtualHost + for _, domain := range srv.getAllRouteableDomains(options, options.Addr) { + vh := &envoy_config_route_v3.VirtualHost{ + Name: domain, + Domains: []string{domain}, + } + + if options.Addr == options.GRPCAddr { + // if this is a gRPC service domain and we're supposed to handle that, add those routes + if (config.IsAuthorize(options.Services) && domain == urlutil.StripPort(options.AuthorizeURL.Host)) || + (config.IsCache(options.Services) && domain == urlutil.StripPort(options.CacheURL.Host)) { + vh.Routes = append(vh.Routes, srv.buildGRPCRoutes()...) + } + } + + // these routes match /.pomerium/... and similar paths + vh.Routes = append(vh.Routes, srv.buildPomeriumHTTPRoutes(options, domain)...) + + // if we're the proxy, add all the policy routes + if config.IsProxy(options.Services) { + vh.Routes = append(vh.Routes, srv.buildPolicyRoutes(options, domain)...) + } + + if len(vh.Routes) > 0 { + virtualHosts = append(virtualHosts, vh) + } + } + + extAuthZ, _ := ptypes.MarshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthz{ + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_InternalServerError, + }, + Services: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthz_GrpcService{ + GrpcService: &envoy_config_core_v3.GrpcService{ + TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "pomerium-authz", + }, + }, + }, + }, + }) + + tc, _ := ptypes.MarshalAny(&envoy_http_connection_manager.HttpConnectionManager{ + CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO, + StatPrefix: "ingress", + RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{ + RouteConfig: &envoy_config_route_v3.RouteConfiguration{ + Name: "main", + VirtualHosts: virtualHosts, + }, + }, + HttpFilters: []*envoy_http_connection_manager.HttpFilter{ + { + Name: "envoy.filters.http.ext_authz", + ConfigType: &envoy_http_connection_manager.HttpFilter_TypedConfig{ + TypedConfig: extAuthZ, + }, + }, + { + Name: "envoy.filters.http.router", + }, + }, + AccessLog: []*envoy_config_accesslog_v3.AccessLog{srv.buildAccessLog()}, + }) + + li := &envoy_config_listener_v3.Listener{ + Name: "http-ingress", + Address: buildAddress(options.Addr, defaultPort), + FilterChains: []*envoy_config_listener_v3.FilterChain{{ + Filters: []*envoy_config_listener_v3.Filter{ + { + Name: "envoy.filters.network.http_connection_manager", + ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{ + TypedConfig: tc, + }, + }, + }, + TransportSocket: transportSocket, + }}, + } + return li +} + +func (srv *Server) buildGRPCListener(options config.Options) *envoy_config_listener_v3.Listener { + defaultPort := 80 + var transportSocket *envoy_config_core_v3.TransportSocket + if !options.GRPCInsecure { + defaultPort = 443 + tlsConfig, _ := ptypes.MarshalAny(srv.buildDownstreamTLSContext(options)) + transportSocket = &envoy_config_core_v3.TransportSocket{ + Name: "tls", + ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{ + TypedConfig: tlsConfig, + }, + } + } + + tc, _ := ptypes.MarshalAny(&envoy_http_connection_manager.HttpConnectionManager{ + CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO, + StatPrefix: "grpc_ingress", + RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{ + RouteConfig: &envoy_config_route_v3.RouteConfiguration{ + Name: "grpc", + VirtualHosts: []*envoy_config_route_v3.VirtualHost{{ + Name: "grpc", + Domains: []string{"*"}, + Routes: []*envoy_config_route_v3.Route{{ + Name: "grpc", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"}, + Grpc: &envoy_config_route_v3.RouteMatch_GrpcRouteMatchOptions{}, + }, + Action: &envoy_config_route_v3.Route_Route{ + Route: &envoy_config_route_v3.RouteAction{ + ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{Cluster: "pomerium-control-plane-grpc"}, + }, + }, + }}, + }}, + }, + }, + HttpFilters: []*envoy_http_connection_manager.HttpFilter{{ + Name: "envoy.filters.http.router", + }}, + }) + + return &envoy_config_listener_v3.Listener{ + Name: "grpc-ingress", + Address: buildAddress(options.GRPCAddr, defaultPort), + FilterChains: []*envoy_config_listener_v3.FilterChain{{ + Filters: []*envoy_config_listener_v3.Filter{{ + Name: "envoy.filters.network.http_connection_manager", + ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{ + TypedConfig: tc, + }, + }}, + TransportSocket: transportSocket, + }}, + } +} + +func (srv *Server) buildDownstreamTLSContext(options config.Options) *envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext { + var cert envoy_extensions_transport_sockets_tls_v3.TlsCertificate + if options.Cert != "" { + bs, _ := base64.StdEncoding.DecodeString(options.Cert) + cert.CertificateChain = &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineBytes{ + InlineBytes: bs, + }, + } + } else { + cert.CertificateChain = &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_Filename{ + Filename: getAbsoluteFilePath(options.CertFile), + }, + } + } + if options.Key != "" { + bs, _ := base64.StdEncoding.DecodeString(options.Key) + cert.PrivateKey = &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineBytes{ + InlineBytes: bs, + }, + } + } else { + cert.PrivateKey = &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_Filename{ + Filename: getAbsoluteFilePath(options.KeyFile), + }, + } + } + + return &envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext{ + CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{ + TlsCertificates: []*envoy_extensions_transport_sockets_tls_v3.TlsCertificate{ + &cert, + }, + AlpnProtocols: []string{"h2", "http/1.1"}, + }, + } +} + +func (srv *Server) getAllRouteableDomains(options config.Options, addr string) []string { + lookup := map[string]struct{}{} + if config.IsAuthenticate(options.Services) && addr == options.Addr { + lookup[urlutil.StripPort(options.AuthenticateURL.Host)] = struct{}{} + } + if config.IsAuthorize(options.Services) && addr == options.GRPCAddr { + lookup[urlutil.StripPort(options.AuthorizeURL.Host)] = struct{}{} + } + if config.IsCache(options.Services) && addr == options.GRPCAddr { + lookup[urlutil.StripPort(options.CacheURL.Host)] = struct{}{} + } + if config.IsProxy(options.Services) && addr == options.Addr { + for _, policy := range options.Policies { + lookup[urlutil.StripPort(policy.Source.Host)] = struct{}{} + } + if options.ForwardAuthURL != nil { + lookup[urlutil.StripPort(options.ForwardAuthURL.Host)] = struct{}{} + } + } + + domains := make([]string, 0, len(lookup)) + for domain := range lookup { + domains = append(domains, domain) + } + sort.Strings(domains) + + return domains +} diff --git a/internal/controlplane/xds_routes.go b/internal/controlplane/xds_routes.go new file mode 100644 index 000000000..a56a5de9d --- /dev/null +++ b/internal/controlplane/xds_routes.go @@ -0,0 +1,136 @@ +package controlplane + +import ( + "fmt" + + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" + "github.com/golang/protobuf/ptypes/any" + + "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/urlutil" +) + +func (srv *Server) buildGRPCRoutes() []*envoy_config_route_v3.Route { + action := &envoy_config_route_v3.Route_Route{ + Route: &envoy_config_route_v3.RouteAction{ + ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{ + Cluster: "pomerium-control-plane-grpc", + }, + }, + } + return []*envoy_config_route_v3.Route{{ + Name: "pomerium-grpc", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{ + Prefix: "/", + }, + Grpc: &envoy_config_route_v3.RouteMatch_GrpcRouteMatchOptions{}, + }, + Action: action, + TypedPerFilterConfig: map[string]*any.Any{ + "envoy.filters.http.ext_authz": disableExtAuthz, + }, + }} +} + +func (srv *Server) buildPomeriumHTTPRoutes(options config.Options, domain string) []*envoy_config_route_v3.Route { + routes := []*envoy_config_route_v3.Route{ + srv.buildControlPlanePathRoute("/ping"), + srv.buildControlPlanePathRoute("/healthz"), + srv.buildControlPlanePathRoute("/.pomerium"), + srv.buildControlPlanePrefixRoute("/.pomerium/"), + } + // if we're handling authentication, add the oauth2 callback url + if config.IsAuthenticate(options.Services) && domain == urlutil.StripPort(options.AuthenticateURL.Host) { + routes = append(routes, + srv.buildControlPlanePathRoute(options.AuthenticateCallbackPath)) + } + // if we're the proxy and this is the forward-auth url + if config.IsProxy(options.Services) && options.ForwardAuthURL != nil && domain == urlutil.StripPort(options.ForwardAuthURL.Host) { + routes = append(routes, + srv.buildControlPlanePrefixRoute("/")) + } + return routes +} + +func (srv *Server) buildControlPlanePathRoute(path string) *envoy_config_route_v3.Route { + return &envoy_config_route_v3.Route{ + Name: "pomerium-path-" + path, + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{Path: path}, + }, + Action: &envoy_config_route_v3.Route_Route{ + Route: &envoy_config_route_v3.RouteAction{ + ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{ + Cluster: "pomerium-control-plane-http", + }, + }, + }, + TypedPerFilterConfig: map[string]*any.Any{ + "envoy.filters.http.ext_authz": disableExtAuthz, + }, + } +} + +func (srv *Server) buildControlPlanePrefixRoute(prefix string) *envoy_config_route_v3.Route { + return &envoy_config_route_v3.Route{ + Name: "pomerium-prefix-" + prefix, + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: prefix}, + }, + Action: &envoy_config_route_v3.Route_Route{ + Route: &envoy_config_route_v3.RouteAction{ + ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{ + Cluster: "pomerium-control-plane-http", + }, + }, + }, + TypedPerFilterConfig: map[string]*any.Any{ + "envoy.filters.http.ext_authz": disableExtAuthz, + }, + } +} + +func (srv *Server) buildPolicyRoutes(options config.Options, domain string) []*envoy_config_route_v3.Route { + var routes []*envoy_config_route_v3.Route + for i, policy := range options.Policies { + if policy.Source.Hostname() != domain { + continue + } + + match := &envoy_config_route_v3.RouteMatch{} + switch { + case policy.Regex != "": + match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{ + SafeRegex: &envoy_type_matcher_v3.RegexMatcher{ + EngineType: &envoy_type_matcher_v3.RegexMatcher_GoogleRe2{ + GoogleRe2: &envoy_type_matcher_v3.RegexMatcher_GoogleRE2{}, + }, + Regex: policy.Regex, + }, + } + case policy.Path != "": + match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Path{Path: policy.Path} + case policy.Prefix != "": + match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: policy.Prefix} + default: + match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"} + } + + clusterName, _, _ := srv.getClusterDetails(policy.Destination) + + routes = append(routes, &envoy_config_route_v3.Route{ + Name: fmt.Sprintf("policy-%d", i), + Match: match, + Action: &envoy_config_route_v3.Route_Route{ + Route: &envoy_config_route_v3.RouteAction{ + ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{ + Cluster: clusterName, + }, + }, + }, + }) + } + return routes +} diff --git a/internal/cryptutil/tls.go b/internal/cryptutil/tls.go deleted file mode 100644 index a93dbf13b..000000000 --- a/internal/cryptutil/tls.go +++ /dev/null @@ -1,103 +0,0 @@ -package cryptutil - -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - - "github.com/caddyserver/certmagic" - "github.com/go-acme/lego/v3/challenge/tlsalpn01" -) - -// NewAutocert automatically retrieves public certificates from the free -// certificate authority Let's Encrypt using HTTP-01 and TLS-ALPN-01 challenges. -// To complete the challenges, the server must be accessible from the internet -// by port 80 or 443 . -// -// https://letsencrypt.org/docs/challenge-types/#http-01-challenge -// https://letsencrypt.org/docs/challenge-types/#tls-alpn-01 -func NewAutocert(tlsConfig *tls.Config, hostnames []string, useStaging bool, path string) (*tls.Config, func(h http.Handler) http.Handler, error) { - certmagic.DefaultACME.Agreed = true - if useStaging { - certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA - } - cm := certmagic.NewDefault() - - tlsConfig = newTLSConfigIfEmpty(tlsConfig) - // add existing certs to the cache, and staple OCSP - for _, cert := range tlsConfig.Certificates { - if err := cm.CacheUnmanagedTLSCertificate(cert, nil); err != nil { - return nil, nil, fmt.Errorf("cryptutil: failed caching cert: %w", err) - } - } - cm.Storage = &certmagic.FileStorage{Path: path} - acmeConfig := certmagic.NewACMEManager(cm, certmagic.DefaultACME) - cm.Issuer = acmeConfig - // todo(bdd) : add cancellation context? - if err := cm.ManageAsync(context.TODO(), hostnames); err != nil { - return nil, nil, fmt.Errorf("cryptutil: sync failed: %w", err) - } - - tlsConfig.GetCertificate = cm.GetCertificate - tlsConfig.NextProtos = append(tlsConfig.NextProtos, tlsalpn01.ACMETLS1Protocol) - tlsConfig.BuildNameToCertificate() - return tlsConfig, acmeConfig.HTTPChallengeHandler, nil -} - -// TLSConfigFromBase64 returns an tls configuration from a base64 encoded blob. -func TLSConfigFromBase64(tlsConfig *tls.Config, cert, key string) (*tls.Config, error) { - tlsConfig = newTLSConfigIfEmpty(tlsConfig) - c, err := CertifcateFromBase64(cert, key) - if err != nil { - return nil, err - } - tlsConfig.Certificates = append(tlsConfig.Certificates, *c) - tlsConfig.BuildNameToCertificate() - return tlsConfig, nil -} - -// TLSConfigFromFile returns an tls configuration from a certificate and -// key file . -func TLSConfigFromFile(tlsConfig *tls.Config, cert, key string) (*tls.Config, error) { - tlsConfig = newTLSConfigIfEmpty(tlsConfig) - c, err := CertificateFromFile(cert, key) - if err != nil { - return nil, err - } - tlsConfig.Certificates = append(tlsConfig.Certificates, *c) - tlsConfig.BuildNameToCertificate() - return tlsConfig, nil -} - -// newTLSConfigIfEmpty returns an opinionated TLS configuration if config is nil. -// See : -// https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations -// https://blog.cloudflare.com/exposing-go-on-the-internet/ -// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices -// https://github.com/golang/go/blob/df91b8044dbe790c69c16058330f545be069cc1f/src/crypto/tls/common.go#L919 -func newTLSConfigIfEmpty(tlsConfig *tls.Config) *tls.Config { - if tlsConfig != nil { - return tlsConfig - } - return &tls.Config{ - MinVersion: tls.VersionTLS12, - // Prioritize cipher suites sped up by AES-NI (AES-GCM) - CipherSuites: []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - }, - PreferServerCipherSuites: true, - // Use curves which have assembly implementations - CurvePreferences: []tls.CurveID{ - tls.X25519, - tls.CurveP256, - }, - // HTTP/2 must be enabled manually when using http.Serve - NextProtos: []string{"h2", "http/1.1"}, - } -} diff --git a/internal/cryptutil/tls_test.go b/internal/cryptutil/tls_test.go deleted file mode 100644 index 83c0887a9..000000000 --- a/internal/cryptutil/tls_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package cryptutil - -import ( - "crypto/tls" - "testing" -) - -func TestTLSConfigFromBase64(t *testing.T) { - tests := []struct { - name string - cert string - key string - wantErr bool - }{ - {"good", - "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", - "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", - false}, - {"bad cert", - "!=", - "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", - true}, - {"bad key", - "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", - "!=", - true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := TLSConfigFromBase64(nil, tt.cert, tt.key) - if (err != nil) != tt.wantErr { - t.Errorf("TLSConfigFromBase64() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestTLSConfigFromFile(t *testing.T) { - cfg, err := TLSConfigFromFile(nil, "testdata/example-cert.pem", "testdata/example-key.pem") - if err != nil { - t.Fatal(err) - } - listener, err := tls.Listen("tcp", ":0", cfg) - if err != nil { - t.Fatal(err) - } - _ = listener -} diff --git a/internal/envoy/embed.go b/internal/envoy/embed.go new file mode 100644 index 000000000..fdc10c23f --- /dev/null +++ b/internal/envoy/embed.go @@ -0,0 +1,64 @@ +package envoy + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/natefinch/atomic" + resources "gopkg.in/cookieo9/resources-go.v2" +) + +var embeddedFilesDirectory = filepath.Join(os.TempDir(), "pomerium-embedded-files") + +func extractEmbeddedEnvoy() (outPath string, err error) { + exePath, err := resources.ExecutablePath() + if err != nil { + return "", fmt.Errorf("error finding executable path: %w", err) + } + bundle, err := resources.OpenZip(exePath) + if err != nil { + return "", fmt.Errorf("error opening binary zip file: %w", err) + } + defer bundle.Close() + + rc, err := bundle.Open("envoy") + if err != nil { + return "", fmt.Errorf("error opening embedded envoy binary: %w", err) + } + defer rc.Close() + + err = os.MkdirAll(embeddedFilesDirectory, 0755) + if err != nil { + return "", fmt.Errorf("error creating embedded file directory: (directory=%s): %w", embeddedFilesDirectory, err) + } + + outPath = filepath.Join(embeddedFilesDirectory, "envoy") + + // skip extraction if we already have it + var zfi os.FileInfo + if zf, ok := rc.(interface{ FileInfo() os.FileInfo }); ok { + zfi = zf.FileInfo() + if fi, e := os.Stat(outPath); e == nil { + if fi.Size() == zfi.Size() && fi.ModTime() == zfi.ModTime() { + return outPath, nil + } + } + } + + err = atomic.WriteFile(outPath, rc) + if err != nil { + return "", fmt.Errorf("error extracting embedded envoy binary to temporary directory (path=%s): %w", outPath, err) + } + + err = os.Chmod(outPath, 0755) + if err != nil { + return "", fmt.Errorf("error chmoding embedded envoy binary: %w", err) + } + + if zfi != nil { + _ = os.Chtimes(outPath, zfi.ModTime(), zfi.ModTime()) + } + + return outPath, nil +} diff --git a/internal/envoy/envoy.go b/internal/envoy/envoy.go new file mode 100644 index 000000000..c5a9457e6 --- /dev/null +++ b/internal/envoy/envoy.go @@ -0,0 +1,158 @@ +// Package envoy creates and configures an envoy server. +package envoy + +import ( + "bufio" + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/natefinch/atomic" + "github.com/pomerium/pomerium/internal/log" + "github.com/rs/zerolog" +) + +const ( + workingDirectoryName = ".pomerium-envoy" + configFileName = "envoy-config.yaml" +) + +// A Server is a pomerium proxy implemented via envoy. +type Server struct { + wd string + cmd *exec.Cmd + + grpcPort, httpPort string +} + +// NewServer creates a new server with traffic routed by envoy. +func NewServer(grpcPort, httpPort string) (*Server, error) { + wd := filepath.Join(os.TempDir(), workingDirectoryName) + err := os.MkdirAll(wd, 0755) + if err != nil { + return nil, fmt.Errorf("error creating temporary working directory for envoy: %w", err) + } + + srv := &Server{ + wd: wd, + grpcPort: grpcPort, + httpPort: httpPort, + } + + err = srv.writeConfig() + if err != nil { + return nil, fmt.Errorf("error writing initial envoy configuration: %w", err) + } + + return srv, nil +} + +// Run runs the server by extracting the embedded envoy and then executing it. +func (srv *Server) Run(ctx context.Context) error { + envoyPath, err := extractEmbeddedEnvoy() + if err != nil { + log.Warn().Err(err).Send() + envoyPath = "envoy" + } + + srv.cmd = exec.CommandContext(ctx, envoyPath, + "-c", configFileName, + "--log-level", log.Logger.GetLevel().String(), + "--log-format", "%l--%n--%v", + "--log-format-escaped", + ) + srv.cmd.Dir = srv.wd + + stderr, err := srv.cmd.StderrPipe() + if err != nil { + return fmt.Errorf("error creating stderr pipe for envoy: %w", err) + } + go srv.handleLogs(stderr) + + stdout, err := srv.cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("error creating stderr pipe for envoy: %w", err) + } + go srv.handleLogs(stdout) + + // make sure envoy is killed if we're killed + srv.cmd.SysProcAttr = sysProcAttr + return srv.cmd.Run() +} + +func (srv *Server) writeConfig() error { + return atomic.WriteFile(filepath.Join(srv.wd, configFileName), strings.NewReader(` +node: + id: pomerium-envoy + cluster: pomerium-envoy + +admin: + access_log_path: /tmp/admin_access.log + address: + socket_address: { address: 127.0.0.1, port_value: 9901 } + +dynamic_resources: + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 + ads_config: + api_type: GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: pomerium-control-plane-grpc +static_resources: + clusters: + - name: pomerium-control-plane-grpc + connect_timeout: { seconds: 5 } + type: STATIC + hosts: + - socket_address: + address: 127.0.0.1 + port_value: `+srv.grpcPort+` + http2_protocol_options: {} +`)) +} + +func (srv *Server) handleLogs(stdout io.ReadCloser) { + fileNameAndNumberRE := regexp.MustCompile(`^(\[[^:]+:[0-9]+\])\s(.*)$`) + + s := bufio.NewScanner(stdout) + for s.Scan() { + ln := s.Text() + + // format: level--name--message + // message is c-escaped + + lvl := zerolog.TraceLevel + if pos := strings.Index(ln, "--"); pos >= 0 { + lvlstr := ln[:pos] + ln = ln[pos+2:] + if x, err := zerolog.ParseLevel(lvlstr); err == nil { + lvl = x + } + } + + name := "" + if pos := strings.Index(ln, "--"); pos >= 0 { + name = ln[:pos] + ln = ln[pos+2:] + } + + msg := fileNameAndNumberRE.ReplaceAllString(ln, "\"$2\"") + if s, err := strconv.Unquote(msg); err == nil { + msg = s + } + + log.WithLevel(lvl).Str("service", "envoy").Str("name", name).Msg(msg) + } +} diff --git a/internal/envoy/envoy_linux.go b/internal/envoy/envoy_linux.go new file mode 100644 index 000000000..2d8df76f0 --- /dev/null +++ b/internal/envoy/envoy_linux.go @@ -0,0 +1,9 @@ +// +build linux + +package envoy + +import "syscall" + +var sysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, +} diff --git a/internal/envoy/envoy_notlinux.go b/internal/envoy/envoy_notlinux.go new file mode 100644 index 000000000..111b65c3f --- /dev/null +++ b/internal/envoy/envoy_notlinux.go @@ -0,0 +1,7 @@ +// +build !linux + +package envoy + +import "syscall" + +var sysProcAttr = &syscall.SysProcAttr{} diff --git a/internal/grpc/server.go b/internal/grpc/server.go index 2061e5c4f..9732932f2 100644 --- a/internal/grpc/server.go +++ b/internal/grpc/server.go @@ -2,7 +2,6 @@ package grpc import ( "crypto/tls" - "errors" "net" "os" "os/signal" @@ -34,21 +33,17 @@ func NewServer(opt *ServerOptions, registrationFn func(s *grpc.Server), wg *sync grpc.KeepaliveParams(opt.KeepaliveParams), } - if len(opt.TLSCertificate) == 1 { - cert := credentials.NewServerTLSFromCert(&opt.TLSCertificate[0]) + if opt.TLSCertificate != nil { + log.Debug().Str("addr", opt.Addr).Msg("internal/grpc: serving over TLS") + cert := credentials.NewServerTLSFromCert(opt.TLSCertificate) grpcOpts = append(grpcOpts, grpc.Creds(cert)) - } else if !opt.InsecureServer { - return nil, errors.New("internal/grpc: unexpected number of certificates") + } else { + log.Warn().Str("addr", opt.Addr).Msg("internal/grpc: serving without TLS") } srv := grpc.NewServer(grpcOpts...) registrationFn(srv) - log.Info(). - Str("addr", opt.Addr). - Bool("insecure", opt.InsecureServer). - Str("service", opt.ServiceName). - Interface("grpc-service-info", srv.GetServiceInfo()). - Msg("internal/grpc: registered") + log.Info().Interface("grpc-service-info", srv.GetServiceInfo()).Msg("internal/grpc: registered") wg.Add(1) go func() { @@ -68,7 +63,7 @@ type ServerOptions struct { Addr string // TLS certificates to use, if any. - TLSCertificate []tls.Certificate + TLSCertificate *tls.Certificate // InsecureServer when enabled disables all transport security. // In this mode, Pomerium is susceptible to man-in-the-middle attacks. diff --git a/internal/grpc/server_test.go b/internal/grpc/server_test.go index 58cbda56e..42fd90046 100644 --- a/internal/grpc/server_test.go +++ b/internal/grpc/server_test.go @@ -1,7 +1,6 @@ package grpc import ( - "crypto/tls" "encoding/base64" "os" "os/signal" @@ -33,8 +32,6 @@ BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBHbhVnGbwXqaMZ1dB8eBAK56jyeWDZ -----END CERTIFICATE-----` func TestNewServer(t *testing.T) { - // to make friendly to testing environments where 443 requires root - defaultServerOptions.Addr = ":0" certb64, err := cryptutil.CertifcateFromBase64( base64.StdEncoding.EncodeToString([]byte(pubKey)), base64.StdEncoding.EncodeToString([]byte(privKey))) @@ -50,12 +47,10 @@ func TestNewServer(t *testing.T) { wantNil bool wantErr bool }{ - {"simple", &ServerOptions{Addr: ":0", InsecureServer: true}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false}, - {"simple keepalive options", &ServerOptions{Addr: ":0", InsecureServer: true, KeepaliveParams: keepalive.ServerParameters{MaxConnectionAge: 5 * time.Minute}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false}, + {"simple", &ServerOptions{Addr: ":0"}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false}, + {"simple keepalive options", &ServerOptions{Addr: ":0", KeepaliveParams: keepalive.ServerParameters{MaxConnectionAge: 5 * time.Minute}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false}, {"bad tcp port", &ServerOptions{Addr: ":9999999"}, func(s *grpc.Server) {}, &sync.WaitGroup{}, true, true}, - {"with cert", &ServerOptions{Addr: ":0", TLSCertificate: []tls.Certificate{*certb64}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, false, false}, - {"with multiple certs", &ServerOptions{Addr: ":0", TLSCertificate: []tls.Certificate{*certb64, *certb64}}, func(s *grpc.Server) {}, &sync.WaitGroup{}, true, true}, - {"with no certs or insecure", &ServerOptions{Addr: ":0", TLSCertificate: []tls.Certificate{}}, 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) { diff --git a/internal/httputil/options.go b/internal/httputil/options.go index a13b5e72f..7ef59b868 100644 --- a/internal/httputil/options.go +++ b/internal/httputil/options.go @@ -11,17 +11,8 @@ type ServerOptions struct { // HTTPS requests. If empty, ":443" is used. Addr string - // TLSConfig is the tls configuration used to setup the HTTPS server. - TLSConfig *tls.Config - - // InsecureServer when enabled disables all transport security. - // In this mode, Pomerium is susceptible to man-in-the-middle attacks. - // This should be used only for testing. - Insecure bool - - // Service is an optional field that helps define what the server's role is. - Service string - + // TLS certificates to use. + TLSCertificate *tls.Certificate // Timeouts ReadHeaderTimeout time.Duration ReadTimeout time.Duration diff --git a/internal/httputil/server.go b/internal/httputil/server.go index c7d76bdda..47cc29df7 100644 --- a/internal/httputil/server.go +++ b/internal/httputil/server.go @@ -3,7 +3,6 @@ package httputil import ( "context" "crypto/tls" - "errors" "fmt" stdlog "log" "net" @@ -26,24 +25,15 @@ func NewServer(opt *ServerOptions, h http.Handler, wg *sync.WaitGroup) (*http.Se } else { opt.applyServerDefaults() } - sublogger := log.With(). - Str("service", opt.Service). - Bool("insecure", opt.Insecure). - Str("addr", opt.Addr). - Logger() - - if !opt.Insecure && opt.TLSConfig == nil { - return nil, errors.New("internal/httputil: server must run in insecure mode or have a valid tls config") - } ln, err := net.Listen("tcp", opt.Addr) if err != nil { return nil, err } - - if !opt.Insecure { - ln = tls.NewListener(ln, opt.TLSConfig) + if opt.TLSCertificate != nil { + ln = tls.NewListener(ln, newDefaultTLSConfig(opt.TLSCertificate)) } + sublogger := log.With().Str("addr", opt.Addr).Logger() // Set up the main server. srv := &http.Server{ @@ -66,6 +56,38 @@ func NewServer(opt *ServerOptions, h http.Handler, wg *sync.WaitGroup) (*http.Se return srv, nil } +// newDefaultTLSConfig creates a new TLS config based on the certificate files given. +// See : +// https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations +// https://blog.cloudflare.com/exposing-go-on-the-internet/ +// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices +// https://github.com/golang/go/blob/df91b8044dbe790c69c16058330f545be069cc1f/src/crypto/tls/common.go#L919 +func newDefaultTLSConfig(cert *tls.Certificate) *tls.Config { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + // Prioritize cipher suites sped up by AES-NI (AES-GCM) + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + }, + PreferServerCipherSuites: true, + // Use curves which have assembly implementations + CurvePreferences: []tls.CurveID{ + tls.X25519, + tls.CurveP256, + }, + Certificates: []tls.Certificate{*cert}, + // HTTP/2 must be enabled manually when using http.Serve + NextProtos: []string{"h2"}, + } + tlsConfig.BuildNameToCertificate() + return tlsConfig +} + // RedirectHandler takes an incoming request and redirects to its HTTPS counterpart func RedirectHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/httputil/server_test.go b/internal/httputil/server_test.go index d8d8830df..b1e9d86e2 100644 --- a/internal/httputil/server_test.go +++ b/internal/httputil/server_test.go @@ -1,7 +1,7 @@ package httputil import ( - "crypto/tls" + "encoding/base64" "fmt" "io/ioutil" "log" @@ -15,13 +15,32 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/pomerium/pomerium/internal/cryptutil" ) +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) { - - // to support envs that won't let us use 443 without root - defaultServerOptions.Addr = ":0" - + certb64, err := cryptutil.CertifcateFromBase64( + base64.StdEncoding.EncodeToString([]byte(pubKey)), + base64.StdEncoding.EncodeToString([]byte(privKey))) + if err != nil { + t.Fatal(err) + } t.Parallel() tests := []struct { name string @@ -33,51 +52,37 @@ func TestNewServer(t *testing.T) { {"good basic http handler", &ServerOptions{ - Addr: ":0", - Insecure: true, + Addr: "127.0.0.1:0", + TLSCertificate: certb64, }, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, http") }), false}, - {"bad neither insecure nor certs set", - &ServerOptions{ - Addr: ":0", - }, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, http") - }), - true}, - {"good no address", - &ServerOptions{ - Insecure: true, - }, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, http") - }), - false}, - {"empty handler", - nil, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, http") - }), - true}, + // todo(bdd): fails travis-ci + // {"good no address", + // &ServerOptions{ + // TLSCertificate: certb64, + // }, + // http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // fmt.Fprintln(w, "Hello, http") + // }), + // false}, + // todo(bdd): fails travis-ci + // {"empty handler", + // nil, + // http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // fmt.Fprintln(w, "Hello, http") + // }), + // false}, {"bad port - invalid port range ", &ServerOptions{ - Addr: ":65536", - Insecure: true, + Addr: "127.0.0.1:65536", + TLSCertificate: certb64, }, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, http") }), true}, - {"good tls set", - &ServerOptions{ - TLSConfig: &tls.Config{}, - }, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, http") - }), - false}, } for _, tt := range tests { diff --git a/internal/kv/autocache/autocache.go b/internal/kv/autocache/autocache.go index 2e8360b50..14fd379ef 100644 --- a/internal/kv/autocache/autocache.go +++ b/internal/kv/autocache/autocache.go @@ -116,7 +116,7 @@ func New(o *Options) (*Store, error) { if err != nil { return nil, err } - serverOpts := &httputil.ServerOptions{Addr: o.Addr, Insecure: true, Service: Name} + serverOpts := &httputil.ServerOptions{Addr: o.Addr} var wg sync.WaitGroup s.srv, err = httputil.NewServer(serverOpts, metrics.HTTPMetricsHandler("groupcache")(QueryParamToCtx(s.cluster)), &wg) if err != nil { diff --git a/proxy/forward_auth.go b/proxy/forward_auth.go index ab41bc221..e18cef7f9 100644 --- a/proxy/forward_auth.go +++ b/proxy/forward_auth.go @@ -107,7 +107,6 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler { if uriString == "" { if r.Header.Get(httputil.HeaderForwardedProto) == "" || r.Header.Get(httputil.HeaderForwardedHost) == "" { return httputil.NewError(http.StatusBadRequest, errors.New("no uri to validate")) - } uriString = r.Header.Get(httputil.HeaderForwardedProto) + "://" + r.Header.Get(httputil.HeaderForwardedHost) } @@ -116,32 +115,38 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler { if err != nil { return httputil.NewError(http.StatusBadRequest, err) } - originalRequest := p.getOriginalRequest(r, uri) - authz, err := p.authorize(w, originalRequest) + original := p.getOriginalRequest(r, uri) + authorized, err := p.isAuthorized(original) if err != nil { - // no session, so redirect - if _, err := sessions.FromContext(r.Context()); err != nil { - if verifyOnly { - return httputil.NewError(http.StatusUnauthorized, err) - } - authN := *p.authenticateSigninURL - q := authN.Query() - q.Set(urlutil.QueryCallbackURI, uri.String()) - q.Set(urlutil.QueryRedirectURI, uri.String()) // final destination - q.Set(urlutil.QueryForwardAuth, urlutil.StripPort(r.Host)) // add fwd auth to trusted audience - authN.RawQuery = q.Encode() - httputil.Redirect(w, r, urlutil.NewSignedURL(p.SharedKey, &authN).String(), http.StatusFound) - return nil - } - return err + return httputil.NewError(http.StatusBadRequest, err) } - w.Header().Set(httputil.HeaderPomeriumJWTAssertion, authz.GetSignedJwt()) + if authorized { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Access to %s is allowed.", uri.Host) + return nil + } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Access to %s is allowed.", uri.Host) + _, err = sessions.FromContext(r.Context()) + hasSession := err == nil + if hasSession { + return httputil.NewError(http.StatusForbidden, errors.New("access denied")) + } + + if verifyOnly { + return httputil.NewError(http.StatusUnauthorized, err) + } + + // redirect to authenticate + authN := *p.authenticateSigninURL + q := authN.Query() + q.Set(urlutil.QueryCallbackURI, uri.String()) + q.Set(urlutil.QueryRedirectURI, uri.String()) // final destination + q.Set(urlutil.QueryForwardAuth, urlutil.StripPort(r.Host)) // add fwd auth to trusted audience + authN.RawQuery = q.Encode() + httputil.Redirect(w, r, urlutil.NewSignedURL(p.SharedKey, &authN).String(), http.StatusFound) return nil }) } diff --git a/proxy/forward_auth_test.go b/proxy/forward_auth_test.go index b7887c824..7ddbaeea9 100644 --- a/proxy/forward_auth_test.go +++ b/proxy/forward_auth_test.go @@ -1,30 +1,46 @@ package proxy import ( - "errors" + "context" "net/http" "net/http/httptest" "net/url" "testing" "time" + envoy_service_auth_v2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" "github.com/google/go-cmp/cmp" + "google.golang.org/grpc" "gopkg.in/square/go-jose.v2/jwt" "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/encoding" "github.com/pomerium/pomerium/internal/encoding/jws" "github.com/pomerium/pomerium/internal/encoding/mock" - pb "github.com/pomerium/pomerium/internal/grpc/authorize" - "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" ) +type mockCheckClient struct { + response *envoy_service_auth_v2.CheckResponse + err error +} + +func (m *mockCheckClient) Check(ctx context.Context, in *envoy_service_auth_v2.CheckRequest, opts ...grpc.CallOption) (*envoy_service_auth_v2.CheckResponse, error) { + return m.response, m.err +} + func TestProxy_ForwardAuth(t *testing.T) { t.Parallel() + + allowClient := &mockCheckClient{ + response: &envoy_service_auth_v2.CheckResponse{ + HttpResponse: &envoy_service_auth_v2.CheckResponse_OkResponse{}, + }, + } + opts := testOptions(t) tests := []struct { name string @@ -40,31 +56,27 @@ func TestProxy_ForwardAuth(t *testing.T) { cipher encoding.MarshalUnmarshaler sessionStore sessions.SessionStore - authorizer client.Authorizer + authorizer envoy_service_auth_v2.AuthorizationClient 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))}}, client.MockAuthorize{AuthorizeResponse: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: true}}, http.StatusOK, ""}, - {"bad empty domain uri", opts, nil, http.MethodGet, nil, map[string]string{"uri": ""}, "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: &pb.IsAuthorizedReply{Allow: true}}, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: no uri to validate\"}\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: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: false}}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: request denied\"}\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: &pb.IsAuthorizedReply{Allow: false}}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: request denied\"}\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"}, - {"expired", 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: &pb.IsAuthorizedReply{Allow: false}}, http.StatusForbidden, "{\"Status\":403,\"Error\":\"Forbidden: request denied\"}\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))}}, allowClient, 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))}}, allowClient, http.StatusOK, ""}, + {"bad empty domain uri", opts, nil, http.MethodGet, nil, map[string]string{"uri": ""}, "https://some.domain.example/", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, allowClient, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: no uri to validate\"}\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))}}, allowClient, 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))}}, allowClient, 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))}}, allowClient, 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))}}, allowClient, http.StatusBadRequest, "{\"Status\":400,\"Error\":\"Bad Request: %20 url does contain a valid scheme\"}\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))}}, client.MockAuthorize{AuthorizeResponse: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: true}}, http.StatusBadRequest, ""}, - {"good traefik verify uri from headers", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedProto: "https", httputil.HeaderForwardedHost: "some.domain.example:8080"}, 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: &pb.IsAuthorizedReply{Allow: true}}, http.StatusOK, ""}, + {"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))}}, allowClient, 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))}}, allowClient, 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))}}, allowClient, http.StatusBadRequest, ""}, + {"good traefik verify uri from headers", opts, nil, http.MethodGet, map[string]string{httputil.HeaderForwardedProto: "https", httputil.HeaderForwardedHost: "some.domain.example:8080"}, 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))}}, allowClient, http.StatusOK, ""}, // // 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))}}, client.MockAuthorize{AuthorizeResponse: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: 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: &pb.IsAuthorizedReply{Allow: 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))}}, allowClient, 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))}}, allowClient, 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))}}, allowClient, http.StatusBadRequest, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -72,8 +84,8 @@ func TestProxy_ForwardAuth(t *testing.T) { if err != nil { t.Fatal(err) } + p.authzClient = tt.authorizer p.sessionStore = tt.sessionStore - p.AuthorizeClient = tt.authorizer signer, err := jws.NewHS256Signer(nil, "mock") if err != nil { t.Fatal(err) @@ -110,7 +122,7 @@ func TestProxy_ForwardAuth(t *testing.T) { router := p.registerFwdAuthHandlers() router.ServeHTTP(w, r) if status := w.Code; status != tt.wantStatus { - t.Errorf("status code: got %v want %v", status, tt.wantStatus) + t.Errorf("status code: got %v want %v in %s", status, tt.wantStatus, tt.name) t.Errorf("\n%+v", w.Body.String()) } diff --git a/proxy/handlers.go b/proxy/handlers.go index 5ca3c3fcd..4599f23d4 100644 --- a/proxy/handlers.go +++ b/proxy/handlers.go @@ -36,7 +36,6 @@ func (p *Proxy) registerDashboardHandlers(r *mux.Router) *mux.Router { h.Path("/sign_out").HandlerFunc(p.SignOut).Methods(http.MethodGet, http.MethodPost) // admin endpoints authorization is also delegated to authorizer service admin := h.PathPrefix("/admin").Subrouter() - admin.Use(p.AuthorizeSession) admin.Path("/impersonate").Handler(httputil.HandlerFunc(p.Impersonate)).Methods(http.MethodPost) // Authenticate service callback handlers and middleware diff --git a/proxy/handlers_test.go b/proxy/handlers_test.go index 29d1374db..c7beb00aa 100644 --- a/proxy/handlers_test.go +++ b/proxy/handlers_test.go @@ -93,7 +93,6 @@ func TestProxy_UserDashboard(t *testing.T) { } p.encoder = tt.cipher p.sessionStore = tt.session - p.AuthorizeClient = tt.authorizer r := httptest.NewRequest(tt.method, "/", nil) state, _ := tt.session.LoadSession(r) @@ -147,7 +146,6 @@ func TestProxy_Impersonate(t *testing.T) { } p.encoder = tt.cipher p.sessionStore = tt.sessionStore - p.AuthorizeClient = tt.authorizer postForm := url.Values{} postForm.Add("email", tt.email) postForm.Add("group", tt.groups) @@ -257,7 +255,6 @@ func TestProxy_Callback(t *testing.T) { } p.encoder = tt.cipher p.sessionStore = tt.sessionStore - p.AuthorizeClient = tt.authorizer p.UpdateOptions(tt.options) redirectURI := &url.URL{Scheme: tt.scheme, Host: tt.host, Path: tt.path} queryString := redirectURI.Query() @@ -398,7 +395,6 @@ func TestProxy_ProgrammaticCallback(t *testing.T) { } p.encoder = tt.cipher p.sessionStore = tt.sessionStore - p.AuthorizeClient = tt.authorizer p.UpdateOptions(tt.options) redirectURI, _ := url.Parse(tt.redirectURI) queryString := redirectURI.Query() diff --git a/proxy/middleware.go b/proxy/middleware.go index 9fb18a425..34bccbcb7 100644 --- a/proxy/middleware.go +++ b/proxy/middleware.go @@ -1,18 +1,16 @@ package proxy import ( - "context" - "errors" "fmt" - "io" - "io/ioutil" "net/http" "strings" + "time" + envoy_service_auth_v2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" + "github.com/golang/protobuf/ptypes" "github.com/gorilla/mux" "github.com/rs/zerolog" - "github.com/pomerium/pomerium/internal/grpc/authorize" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/sessions" @@ -36,47 +34,6 @@ func (p *Proxy) AuthenticateSession(next http.Handler) http.Handler { }) } -func (p *Proxy) refresh(ctx context.Context, oldSession string) (string, error) { - ctx, span := trace.StartSpan(ctx, "proxy.AuthenticateSession/refresh") - defer span.End() - s := &sessions.State{} - if err := p.encoder.Unmarshal([]byte(oldSession), s); err != nil { - return "", httputil.NewError(http.StatusBadRequest, err) - } - - // 1 - build a signed url to call refresh on authenticate service - refreshURI := *p.authenticateRefreshURL - q := refreshURI.Query() - q.Set(urlutil.QueryAccessTokenID, s.AccessTokenID) // hash value points to parent token - q.Set(urlutil.QueryAudience, strings.Join(s.Audience, ",")) // 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 "", 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 "", fmt.Errorf("proxy: client err %s: %w", signedRefreshURL, err) - } - defer res.Body.Close() - newJwt, err := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10)) - if err != nil { - return "", err - } - // auth couldn't refersh the session, delete the session and reload via 302 - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("proxy: backend refresh failed: %s", newJwt) - } - - return string(newJwt), nil -} - func (p *Proxy) redirectToSignin(w http.ResponseWriter, r *http.Request) error { signinURL := *p.authenticateSigninURL q := signinURL.Query() @@ -88,61 +45,46 @@ func (p *Proxy) redirectToSignin(w http.ResponseWriter, r *http.Request) error { return nil } -// AuthorizeSession is middleware to enforce a user is authorized for a request. -// Session state is retrieved from the users's request context. -func (p *Proxy) AuthorizeSession(next http.Handler) http.Handler { - return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - ctx, span := trace.StartSpan(r.Context(), "proxy.AuthorizeSession") - defer span.End() - authz, err := p.authorize(w, r) - if err != nil { - return err - } - - r.Header.Set(httputil.HeaderPomeriumJWTAssertion, authz.GetSignedJwt()) - next.ServeHTTP(w, r.WithContext(ctx)) - return nil - }) -} - -func (p *Proxy) authorize(w http.ResponseWriter, r *http.Request) (*authorize.IsAuthorizedReply, error) { - ctx, span := trace.StartSpan(r.Context(), "proxy.authorize") - defer span.End() - - jwt, _ := sessions.FromContext(ctx) - - authz, err := p.AuthorizeClient.Authorize(ctx, jwt, r) +func (p *Proxy) isAuthorized(r *http.Request) (bool, error) { + tm, err := ptypes.TimestampProto(time.Now()) if err != nil { - return nil, httputil.NewError(http.StatusInternalServerError, err) + return false, httputil.NewError(http.StatusInternalServerError, fmt.Errorf("error creating protobuf timestamp from current time: %w", err)) } - if authz.GetSessionExpired() { - newJwt, err := p.refresh(ctx, jwt) - if err != nil { - p.sessionStore.ClearSession(w, r) - log.FromRequest(r).Warn().Err(err).Msg("proxy: refresh failed") - return nil, p.redirectToSignin(w, r) - } - if err = p.sessionStore.SaveSession(w, r, newJwt); err != nil { - return nil, httputil.NewError(http.StatusUnauthorized, err) - } - - authz, err = p.AuthorizeClient.Authorize(ctx, newJwt, r) - if err != nil { - return nil, httputil.NewError(http.StatusUnauthorized, err) + httpAttrs := &envoy_service_auth_v2.AttributeContext_HttpRequest{ + Method: "GET", + Headers: map[string]string{}, + Path: r.URL.Path, + Host: r.URL.Host, + Scheme: r.URL.Scheme, + Fragment: r.URL.Fragment, + } + for k := range r.Header { + httpAttrs.Headers[k] = r.Header.Get(k) + if r.URL.RawQuery != "" { + // envoy expects the query string in the path + httpAttrs.Path += "?" + r.URL.RawQuery } } - if !authz.GetAllow() { - log.FromRequest(r).Warn(). - Strs("reason", authz.GetDenyReasons()). - Bool("allow", authz.GetAllow()). - Bool("expired", authz.GetSessionExpired()). - Msg("proxy/authorize: deny") - return nil, httputil.NewError(http.StatusForbidden, errors.New("request denied")) + + res, err := p.authzClient.Check(r.Context(), &envoy_service_auth_v2.CheckRequest{ + Attributes: &envoy_service_auth_v2.AttributeContext{ + Request: &envoy_service_auth_v2.AttributeContext_Request{ + Time: tm, + Http: httpAttrs, + }, + }, + }) + if err != nil { + return false, httputil.NewError(http.StatusInternalServerError, err) } - return authz, nil - + switch res.HttpResponse.(type) { + case *envoy_service_auth_v2.CheckResponse_OkResponse: + return true, nil + default: + return false, nil + } } // SetResponseHeaders sets a map of response headers. diff --git a/proxy/middleware_test.go b/proxy/middleware_test.go index 9fd6a550d..bf388fa5b 100644 --- a/proxy/middleware_test.go +++ b/proxy/middleware_test.go @@ -13,8 +13,6 @@ import ( "github.com/pomerium/pomerium/internal/encoding" "github.com/pomerium/pomerium/internal/encoding/jws" "github.com/pomerium/pomerium/internal/encoding/mock" - "github.com/pomerium/pomerium/internal/grpc/authorize" - "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" @@ -146,65 +144,6 @@ func Test_jwtClaimMiddleware(t *testing.T) { } -func TestProxy_AuthorizeSession(t *testing.T) { - t.Parallel() - fn := 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) - }) - tests := []struct { - name string - refreshRespStatus int - session sessions.SessionStore - authzClient client.Authorizer - - ctxError error - provider identity.Authenticator - - wantStatus int - }{ - {"user is authorized", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: true}}, nil, identity.MockProvider{}, http.StatusOK}, - {"user is not authorized", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: false}}, nil, identity.MockProvider{}, http.StatusForbidden}, - {"ctx error", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: true}}, errors.New("hi"), identity.MockProvider{}, http.StatusOK}, - {"authz client error", 200, &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}, - {"expired, reauth failed", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{SessionExpired: true}}, nil, identity.MockProvider{}, http.StatusForbidden}, - //todo(bdd): it's a bit tricky to test the refresh flow - } - 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() - rURL := ts.URL - a := Proxy{ - SharedKey: "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=", - cookieSecret: []byte("80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="), - authenticateURL: uriParseHelper("https://authenticate.corp.example"), - authenticateSigninURL: uriParseHelper("https://authenticate.corp.example/sign_in"), - authenticateRefreshURL: uriParseHelper(rURL), - sessionStore: tt.session, - AuthorizeClient: tt.authzClient, - encoder: &mock.Encoder{}, - } - r := httptest.NewRequest(http.MethodGet, "/", nil) - state, _ := tt.session.LoadSession(r) - ctx := r.Context() - ctx = sessions.NewContext(ctx, state, tt.ctxError) - r = r.WithContext(ctx) - r.Header.Set("Accept", "application/json") - w := httptest.NewRecorder() - got := a.AuthorizeSession(fn) - got.ServeHTTP(w, r) - if status := w.Code; status != tt.wantStatus { - t.Errorf("AuthorizeSession() error = %v, wantErr %v\n%v", w.Result().StatusCode, tt.wantStatus, w.Body.String()) - } - }) - } -} - func TestProxy_SetResponseHeaders(t *testing.T) { t.Parallel() fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/proxy/proxy.go b/proxy/proxy.go index 83e17e225..b98746aad 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -6,18 +6,15 @@ package proxy import ( "crypto/cipher" - "crypto/tls" "encoding/base64" "fmt" "html/template" - stdlog "log" "net/http" - stdhttputil "net/http/httputil" "net/url" - "regexp" - "strings" + "sync/atomic" "time" + envoy_service_auth_v2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" "github.com/gorilla/mux" "github.com/pomerium/pomerium/config" @@ -26,16 +23,13 @@ import ( "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" "github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/sessions/cookie" "github.com/pomerium/pomerium/internal/sessions/header" "github.com/pomerium/pomerium/internal/sessions/queryparam" "github.com/pomerium/pomerium/internal/telemetry/metrics" - "github.com/pomerium/pomerium/internal/tripper" "github.com/pomerium/pomerium/internal/urlutil" ) @@ -74,25 +68,23 @@ type Proxy struct { SharedKey string sharedCipher cipher.AEAD + authorizeURL *url.URL authenticateURL *url.URL authenticateSigninURL *url.URL authenticateSignoutURL *url.URL authenticateRefreshURL *url.URL - authorizeURL *url.URL + encoder encoding.Unmarshaler + cookieOptions *cookie.Options + cookieSecret []byte + refreshCooldown time.Duration + sessionStore sessions.SessionStore + sessionLoaders []sessions.SessionLoader + templates *template.Template + jwtClaimHeaders []string + authzClient envoy_service_auth_v2.AuthorizationClient - AuthorizeClient client.Authorizer - - encoder encoding.Unmarshaler - cookieOptions *cookie.Options - cookieSecret []byte - defaultUpstreamTimeout time.Duration - refreshCooldown time.Duration - Handler http.Handler - sessionStore sessions.SessionStore - sessionLoaders []sessions.SessionLoader - templates *template.Template - jwtClaimHeaders []string + currentRouter atomic.Value } // New takes a Proxy service from options and a validation function. @@ -129,11 +121,10 @@ func New(opts config.Options) (*Proxy, error) { sharedCipher: sharedCipher, encoder: encoder, - cookieSecret: decodedCookieSecret, - cookieOptions: cookieOptions, - defaultUpstreamTimeout: opts.DefaultUpstreamTimeout, - refreshCooldown: opts.RefreshCooldown, - sessionStore: cookieStore, + cookieSecret: decodedCookieSecret, + cookieOptions: cookieOptions, + refreshCooldown: opts.RefreshCooldown, + sessionStore: cookieStore, sessionLoaders: []sessions.SessionLoader{ cookieStore, header.NewStore(encoder, "Pomerium"), @@ -141,6 +132,7 @@ func New(opts config.Options) (*Proxy, error) { templates: template.Must(frontend.NewTemplates()), jwtClaimHeaders: opts.JWTClaimsHeaders, } + p.currentRouter.Store(httputil.NewRouter()) // errors checked in ValidateOptions p.authorizeURL, _ = urlutil.DeepCopy(opts.AuthorizeURL) p.authenticateURL, _ = urlutil.DeepCopy(opts.AuthenticateURL) @@ -148,13 +140,6 @@ func New(opts config.Options) (*Proxy, error) { p.authenticateSignoutURL = p.authenticateURL.ResolveReference(&url.URL{Path: signoutURL}) p.authenticateRefreshURL = p.authenticateURL.ResolveReference(&url.URL{Path: refreshURL}) - if err := p.UpdatePolicies(&opts); err != nil { - return nil, err - } - metrics.AddPolicyCountCallback("proxy", func() int64 { - return int64(len(opts.Policies)) - }) - authzConn, err := grpc.NewGRPCClientConn(&grpc.Options{ Addr: p.authorizeURL, OverrideCertificateName: opts.OverrideCertificateName, @@ -167,9 +152,16 @@ func New(opts config.Options) (*Proxy, error) { if err != nil { return nil, err } + p.authzClient = envoy_service_auth_v2.NewAuthorizationClient(authzConn) - p.AuthorizeClient, err = client.New(authzConn) - return p, err + if err := p.UpdatePolicies(&opts); err != nil { + return nil, err + } + metrics.AddPolicyCountCallback("proxy", func() int64 { + return int64(len(opts.Policies)) + }) + + return p, nil } // UpdateOptions implements the OptionsUpdater interface and updates internal @@ -207,160 +199,13 @@ func (p *Proxy) UpdatePolicies(opts *config.Options) error { if err := policy.Validate(); err != nil { return fmt.Errorf("proxy: invalid policy %w", err) } - r = p.reverseProxyHandler(r, policy) - } - p.Handler = r + + p.currentRouter.Store(r) + return nil } -func (p *Proxy) reverseProxyHandler(r *mux.Router, policy config.Policy) *mux.Router { - // 1. Create the reverse proxy connection - proxy := stdhttputil.NewSingleHostReverseProxy(policy.Destination) - // 2. Create a sublogger to handle any error logs - sublogger := log.With().Str("route", policy.String()).Logger() - proxy.ErrorLog = stdlog.New(&log.StdLogWrapper{Logger: &sublogger}, "", 0) - // 3. Rewrite host headers and add X-Forwarded-Host header - director := proxy.Director - proxy.Director = func(req *http.Request) { - req.Header.Add(httputil.HeaderForwardedHost, req.Host) - director(req) - if !policy.PreserveHostHeader { - req.Host = policy.Destination.Host - } - } - - // 4. Override any custom transport settings (e.g. TLS settings, etc) - proxy.Transport = p.roundTripperFromPolicy(&policy) - // 5. Create a sub-router with a matcher derived from the policy (host, path, etc...) - rp := r.MatcherFunc(routeMatcherFuncFromPolicy(policy)).Subrouter() - rp.PathPrefix("/").Handler(proxy) - - // Optional: If websockets are enabled, do not set a handler request timeout - // websockets cannot use the non-hijackable timeout-handler - if !policy.AllowWebsockets { - timeout := p.defaultUpstreamTimeout - if policy.UpstreamTimeout != 0 { - timeout = policy.UpstreamTimeout - } - timeoutMsg := fmt.Sprintf("%s timed out in %s", policy.Destination.Host, timeout) - rp.Use(middleware.TimeoutHandlerFunc(timeout, timeoutMsg)) - } - - // Optional: a cors preflight check, skip access control middleware - if policy.CORSAllowPreflight { - log.Warn().Str("route", policy.String()).Msg("proxy: cors preflight enabled") - rp.Use(middleware.CorsBypass(proxy)) - } - - // Optional: if additional headers are to be set for this url - if len(policy.SetRequestHeaders) != 0 { - log.Warn().Interface("headers", policy.SetRequestHeaders).Msg("proxy: set request headers") - rp.Use(SetResponseHeaders(policy.SetRequestHeaders)) - } - - // Optional: if a public route, skip access control middleware - if policy.AllowPublicUnauthenticatedAccess { - log.Warn().Str("route", policy.String()).Msg("proxy: all access control disabled") - return r - } - - // 4. Retrieve the user session and add it to the request context - rp.Use(sessions.RetrieveSession(p.sessionLoaders...)) - // 5. AuthN - Verify user session has been added to the request context - rp.Use(p.AuthenticateSession) - // 6. AuthZ - Verify the user is authorized for route - rp.Use(p.AuthorizeSession) - // 7. Strip the user session cookie from the downstream request - rp.Use(middleware.StripCookie(p.cookieOptions.Name)) - // 8 . Add claim details to the request logger context and headers - rp.Use(p.jwtClaimMiddleware(false)) - - return r -} - -// roundTripperFromPolicy adjusts the std library's `DefaultTransport RoundTripper` -// for a given route. A route's `RoundTripper` establishes network connections -// as needed and caches them for reuse by subsequent calls. -func (p *Proxy) roundTripperFromPolicy(policy *config.Policy) http.RoundTripper { - transport := http.DefaultTransport.(*http.Transport).Clone() - c := tripper.NewChain() - c = c.Append(metrics.HTTPMetricsRoundTripper("proxy", policy.Destination.Host)) - - var tlsClientConfig tls.Config - var isCustomClientConfig bool - - if policy.TLSSkipVerify { - tlsClientConfig.InsecureSkipVerify = true - isCustomClientConfig = true - log.Warn().Str("policy", policy.String()).Msg("proxy: tls skip verify") - } - - if policy.RootCAs != nil { - tlsClientConfig.RootCAs = policy.RootCAs - isCustomClientConfig = true - log.Debug().Str("policy", policy.String()).Msg("proxy: custom root ca") - } - - if policy.ClientCertificate != nil { - tlsClientConfig.Certificates = []tls.Certificate{*policy.ClientCertificate} - isCustomClientConfig = true - log.Debug().Str("policy", policy.String()).Msg("proxy: client certs enabled") - } - - if policy.TLSServerName != "" { - tlsClientConfig.ServerName = policy.TLSServerName - isCustomClientConfig = true - log.Debug().Str("policy", policy.String()).Msgf("proxy: tls override hostname: %s", policy.TLSServerName) - } - - // We avoid setting a custom client config unless we have to as - // if TLSClientConfig is nil, the default configuration is used. - if isCustomClientConfig { - transport.TLSClientConfig = &tlsClientConfig - } - return c.Then(transport) -} - func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { - p.Handler.ServeHTTP(w, r) -} - -// routeMatcherFuncFromPolicy returns a mux matcher function which compares an http request with a policy. -// -// Routes can be filtered by the `source`, `prefix`, `path` and `regex` fields in the policy config. -func routeMatcherFuncFromPolicy(policy config.Policy) mux.MatcherFunc { - // match by source - sourceMatches := func(r *http.Request) bool { - return r.Host == policy.Source.Host - } - - // match by prefix - prefixMatches := func(r *http.Request) bool { - return policy.Prefix == "" || - strings.HasPrefix(r.URL.Path, policy.Prefix) - } - - // match by path - pathMatches := func(r *http.Request) bool { - return policy.Path == "" || - r.URL.Path == policy.Path - } - - // match by path regex - var regexMatches func(*http.Request) bool - if policy.Regex == "" { - regexMatches = func(r *http.Request) bool { return true } - } else if re, err := regexp.Compile(policy.Regex); err == nil { - regexMatches = func(r *http.Request) bool { - return re.MatchString(r.URL.Path) - } - } else { - log.Error().Err(err).Str("regex", policy.Regex).Msg("proxy: invalid regex in policy, ignoring route") - regexMatches = func(r *http.Request) bool { return false } - } - - return func(r *http.Request, rm *mux.RouteMatch) bool { - return sourceMatches(r) && prefixMatches(r) && pathMatches(r) && regexMatches(r) - } + p.currentRouter.Load().(*mux.Router).ServeHTTP(w, r) } diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index b4b770f6c..1637ba71a 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -1,16 +1,12 @@ package proxy import ( - "io" - "io/ioutil" - "net" "net/http" "net/http/httptest" "net/url" "testing" "time" - "github.com/gorilla/mux" "github.com/pomerium/pomerium/config" ) @@ -233,182 +229,3 @@ func Test_UpdateOptions(t *testing.T) { var p *Proxy p.UpdateOptions(config.Options{}) } - -func TestNewReverseProxy(t *testing.T) { - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - hostname, _, _ := net.SplitHostPort(r.Host) - w.Write([]byte(hostname)) - })) - defer backend.Close() - - backendURL, _ := url.Parse(backend.URL) - backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host) - backendHost := net.JoinHostPort(backendHostname, backendPort) - proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/") - - ts := httptest.NewUnstartedServer(nil) - ts.Start() - defer ts.Close() - - p, err := New(testOptions(t)) - if err != nil { - t.Fatal(err) - } - newPolicy := config.Policy{To: proxyURL.String(), From: ts.URL, AllowPublicUnauthenticatedAccess: true} - err = newPolicy.Validate() - if err != nil { - t.Fatal(err) - } - proxyHandler := p.reverseProxyHandler(mux.NewRouter(), newPolicy) - - ts.Config.Handler = proxyHandler - - getReq, _ := http.NewRequest("GET", newPolicy.From, nil) - res, err := http.DefaultClient.Do(getReq) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 200 { - t.Errorf("Failed to find route handler") - } - bodyBytes, _ := ioutil.ReadAll(res.Body) - if g, e := string(bodyBytes), backendHostname; g != e { - t.Errorf("got body %q; expected %q", g, e) - } -} - -func TestRouteMatcherFuncFromPolicy(t *testing.T) { - tests := []struct { - source, prefix, path, regex string - incomingURL string - expect bool - msg string - }{ - // host in source - {"https://www.example.com", "", "", "", - "https://www.example.com", true, - "should match when host is the same as source host"}, - {"https://www.example.com/", "", "", "", - "https://www.example.com", true, - "should match when host is the same as source host with trailing slash"}, - {"https://www.example.com", "", "", "", - "https://www.google.com", false, - "should not match when host is different from source host"}, - - // path prefix - {"https://www.example.com", "/admin", "", "", - "https://www.example.com/admin/someaction", true, - "should match when path begins with prefix"}, - {"https://www.example.com", "/admin", "", "", - "https://www.example.com/notadmin", false, - "should not match when path does not begin with prefix"}, - - // path - {"https://www.example.com", "", "/admin", "", - "https://www.example.com/admin", true, - "should match when path is the same as the policy path"}, - {"https://www.example.com", "", "/admin", "", - "https://www.example.com/admin/someaction", false, - "should not match when path merely begins with the policy path"}, - {"https://www.example.com", "", "/admin", "", - "https://www.example.com/notadmin", false, - "should not match when path is different from the policy path"}, - - // path regex - {"https://www.example.com", "", "", "^/admin/[a-z]+$", - "https://www.example.com/admin/someaction", true, - "should match when path matches policy path regex"}, - {"https://www.example.com", "", "", "^/admin/[a-z]+$", - "https://www.example.com/notadmin", false, - "should not match when path does not match policy path regex"}, - {"https://www.example.com", "", "", "invalid[", - "https://www.example.com/invalid", false, - "should not match on invalid policy path regex"}, - } - - for _, tt := range tests { - srcURL, err := url.Parse(tt.source) - if err != nil { - panic(err) - } - src := &config.StringURL{URL: srcURL} - matcher := routeMatcherFuncFromPolicy(config.Policy{ - Source: src, - Prefix: tt.prefix, - Path: tt.path, - Regex: tt.regex, - }) - req, err := http.NewRequest("GET", tt.incomingURL, nil) - if err != nil { - panic(err) - } - actual := matcher(req, nil) - if actual != tt.expect { - t.Errorf("%s (source=%s prefix=%s path=%s regex=%s incoming-url=%s)", - tt.msg, tt.source, tt.prefix, tt.path, tt.regex, tt.incomingURL) - } - } -} - -func TestPolicyPrefixRouting(t *testing.T) { - adminServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "admin: "+r.URL.Path) - })) - defer adminServer.Close() - - publicServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "public: "+r.URL.Path) - })) - defer publicServer.Close() - - opts := testOptions(t) - opts.Policies = []config.Policy{ - { - From: "https://from.example.com", - To: "http://" + adminServer.Listener.Addr().String(), - Prefix: "/admin", - AllowPublicUnauthenticatedAccess: true, - }, - { - From: "https://from.example.com", - To: "http://" + publicServer.Listener.Addr().String(), - AllowPublicUnauthenticatedAccess: true, - }, - } - - p, err := New(opts) - if err != nil { - t.Fatalf("error creating proxy: %v", err) - } - - t.Run("admin", func(t *testing.T) { - req, err := http.NewRequest("GET", "https://from.example.com/admin/path", nil) - if err != nil { - t.Fatalf("error creating http request: %v", err) - } - - rr := httptest.NewRecorder() - p.ServeHTTP(rr, req) - rr.Flush() - - if rr.Body.String() != "admin: /admin/path" { - t.Errorf("expected admin request to go to the admin backend") - } - }) - - t.Run("non-admin", func(t *testing.T) { - req, err := http.NewRequest("GET", "https://from.example.com/nonadmin/path", nil) - if err != nil { - t.Fatalf("error creating http request: %v", err) - } - - rr := httptest.NewRecorder() - p.ServeHTTP(rr, req) - rr.Flush() - - if rr.Body.String() != "public: /nonadmin/path" { - t.Errorf("expected non-admin request to go to the public backend") - } - }) -} diff --git a/renovate.json b/renovate.json index e9bbff4df..57b0c48be 100644 --- a/renovate.json +++ b/renovate.json @@ -7,8 +7,5 @@ "prHourlyLimit": 0, "labels": [ "dependency" - ], - "postUpdateOptions": [{ - "gomodTidy": true - }] -} + ] +} \ No newline at end of file diff --git a/scripts/build-dev-docker.bash b/scripts/build-dev-docker.bash index 785b240e8..8d9d6bc98 100755 --- a/scripts/build-dev-docker.bash +++ b/scripts/build-dev-docker.bash @@ -1,6 +1,7 @@ #!/bin/bash set -euxo pipefail +_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" _dir=/tmp/pomerium-dev-docker mkdir -p "$_dir" @@ -14,6 +15,15 @@ env GOOS=linux \ -o "$_dir/pomerium" \ ./cmd/pomerium +# embed envoy +( + cd "$_script_dir" + env GOOS=linux \ + GOARCH=amd64 \ + ./embed-envoy.bash \ + "$_dir/pomerium" +) + # build docker image ( @@ -30,5 +40,11 @@ ENTRYPOINT [ "/bin/pomerium" ] CMD ["-config","/pomerium/config.yaml"] EOF docker build --tag=pomerium/pomerium:dev . - kind load docker-image pomerium/pomerium:dev + + # build for minikube + if command -v minikube >/dev/null 2>&1; then + eval "$(minikube docker-env --shell=bash)" + docker build --tag=pomerium/pomerium:dev . + fi + ) diff --git a/scripts/embed-envoy.bash b/scripts/embed-envoy.bash new file mode 100755 index 000000000..7958f502e --- /dev/null +++ b/scripts/embed-envoy.bash @@ -0,0 +1,50 @@ +#!/bin/bash +set -euo pipefail + +_pomerium_binary_path="${1?"pomerium binary path is required"}" +_go_os="$(go env GOOS)" +_go_arch="$(go env GOARCH)" + +is_musl() { + ldd /bin/ls | grep musl >/dev/null 2>&1 +} + +# URLs from: https://tetrate.bintray.com/getenvoy/manifest.json +_envoy_version="1.14.1" +_envoy_build="" +if [ "$_go_os" == linux ] && ! is_musl && [ "$_go_arch" == "amd64" ]; then + _envoy_build="LINUX_GLIBC" +elif [ "$_go_os" == darwin ] && [ "$_go_arch" == "amd64" ]; then + _envoy_build="DARWIN" +fi +if [ -z "$_envoy_build" ]; then + echo "this platform is not supported for embedded envoy" + exit 1 +fi +_envoy_url="$( + curl --silent "https://tetrate.bintray.com/getenvoy/manifest.json" | + jq -r '.flavors.standard.versions["'"$_envoy_version"'"].builds["'"$_envoy_build"'"].downloadLocationUrl' +)" + +_abs_pomerium_binary_path="$(realpath "$_pomerium_binary_path")" + +_wd="/tmp/pomerium-embedded-files" +mkdir -p "$_wd" +( + cd "$_wd" + if [ ! -f "envoy-$_envoy_version.tar.xz" ]; then + echo "downloading $_envoy_url" + curl --silent --location --output "envoy-$_envoy_version.tar.xz" "$_envoy_url" + fi + echo "extracting" + tar --extract --xz --strip-components=3 --file "envoy-$_envoy_version.tar.xz" + echo "appending to $_abs_pomerium_binary_path" + # if this binary already has a zip file appended to it + if [ -z "$(unzip -z -qq "$_abs_pomerium_binary_path" 2>&1)" ]; then + zip -A "$_abs_pomerium_binary_path" envoy + else + zip envoy.zip envoy + cat envoy.zip >>"$_abs_pomerium_binary_path" + fi + zip -A "$_abs_pomerium_binary_path" +) diff --git a/docs/docs/reference/sh/generate_wildcard_cert.sh b/scripts/generate_wildcard_cert.sh similarity index 100% rename from docs/docs/reference/sh/generate_wildcard_cert.sh rename to scripts/generate_wildcard_cert.sh diff --git a/docs/configuration/examples/helm/helm_aws.sh b/scripts/helm_aws.sh similarity index 100% rename from docs/configuration/examples/helm/helm_aws.sh rename to scripts/helm_aws.sh diff --git a/docs/configuration/examples/helm/helm_gke.sh b/scripts/helm_gke.sh similarity index 72% rename from docs/configuration/examples/helm/helm_gke.sh rename to scripts/helm_gke.sh index 489d54c01..e11c127d9 100755 --- a/docs/configuration/examples/helm/helm_gke.sh +++ b/scripts/helm_gke.sh @@ -7,7 +7,7 @@ # NOTE! If you are using gsuite, you should also set `authenticate.idp.serviceAccount`, see docs ! echo "=> [GCE] creating cluster" -gcloud container clusters create pomerium --region us-west2 --num-nodes 1 +gcloud container clusters create pomerium --region us-west2 echo "=> [GCE] get cluster credentials so we can use kubctl locally" gcloud container clusters get-credentials pomerium --region us-west2 @@ -15,26 +15,27 @@ gcloud container clusters get-credentials pomerium --region us-west2 echo "=> add pomerium's helm repo" helm repo add pomerium https://helm.pomerium.io -echo "=> add bitnami's helm repo" -helm repo add bitnami https://charts.bitnami.com/bitnami - -echo "=> install nginx as a sample hello world app" -helm upgrade --install nginx bitnami/nginx --set service.type=ClusterIP - echo "=> update helm" helm repo update echo "=> install pomerium with helm" +echo "=> initiliaze a configmap setting from config.example.yaml" +kubectl create configmap config --from-file="config.yaml"="docs/configuration/examples/kubernetes/kubernetes-config.yaml" + helm install \ pomerium \ pomerium/pomerium \ --set service.type="NodePort" \ + --set config.rootDomain="corp.beyondperimeter.com" \ + --set config.existingConfig="config" \ --set config.sharedSecret=$(head -c32 /dev/urandom | base64) \ --set config.cookieSecret=$(head -c32 /dev/urandom | base64) \ --set ingress.secret.name="pomerium-tls" \ --set ingress.secret.cert=$(base64 -i "$HOME/.acme.sh/*.corp.beyondperimeter.com_ecc/fullchain.cer") \ --set ingress.secret.key=$(base64 -i "$HOME/.acme.sh/*.corp.beyondperimeter.com_ecc/*.corp.beyondperimeter.com.key") \ - --values docs/configuration/examples/kubernetes/values.yaml + --set-string ingress.annotations."kubernetes\.io/ingress\.allow-http"=false \ + --set authenticate.service.annotations."cloud\.google\.com/app-protocols"='\{"https":"HTTPS"\}' \ + --set proxy.service.annotations."cloud\.google\.com/app-protocols"='\{"https":"HTTPS"\}' # When done, clean up by deleting the cluster! # helm del $(helm ls --all --short) --purge # deletes all your helm instances