mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-09 23:27:43 +02:00
envoy: Initial changes
This commit is contained in:
parent
8f78497e99
commit
99e788a9b4
107 changed files with 2542 additions and 3322 deletions
1
.github/Dockerfile-release
vendored
1
.github/Dockerfile-release
vendored
|
@ -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
|
||||
|
|
1
.github/Dockerfile-release.arm32v6
vendored
1
.github/Dockerfile-release.arm32v6
vendored
|
@ -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
|
||||
|
|
1
.github/Dockerfile-release.arm32v7
vendored
1
.github/Dockerfile-release.arm32v7
vendored
|
@ -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
|
||||
|
|
1
.github/Dockerfile-release.arm64v8
vendored
1
.github/Dockerfile-release.arm64v8
vendored
|
@ -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
|
||||
|
|
32
.github/workflows/test.yaml
vendored
32
.github/workflows/test.yaml
vendored
|
@ -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/...
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -79,8 +79,3 @@ i18n/*
|
|||
docs/.vuepress/dist/
|
||||
.firebase/
|
||||
.changes.md
|
||||
|
||||
# autocert
|
||||
.pomerium/
|
||||
|
||||
!.pre-commit-config.yaml
|
||||
|
|
|
@ -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"]
|
|
@ -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
|
||||
|
|
1
Makefile
1
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
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
v0.8.0
|
||||
v0.7.5
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -80,6 +96,7 @@ func newPolicyEvaluator(opts *config.Options) (evaluator.Evaluator, error) {
|
|||
"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
|
||||
}
|
||||
|
|
|
@ -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().
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
evt := log.Info().Str("service", "authorize")
|
||||
// request
|
||||
Str("method", req.Method).
|
||||
Str("url", req.URL).
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,155 +61,116 @@ 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
|
||||
// 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
|
||||
return 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
|
||||
svc, err := authenticate.New(*opt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating authenticate service: %w", err)
|
||||
}
|
||||
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 newAuthorizeService(opt config.Options) (*authorize.Authorize, error) {
|
||||
func setupAuthorize(opt *config.Options, controlPlane *controlplane.Server, optionsUpdaters *[]config.OptionsUpdater) error {
|
||||
if !config.IsAuthorize(opt.Services) {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
return authorize.New(opt)
|
||||
|
||||
svc, err := authorize.New(*opt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating authorize service: %w", err)
|
||||
}
|
||||
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 newCacheService(opt config.Options) (*cache.Cache, error) {
|
||||
func setupCache(opt *config.Options, controlPlane *controlplane.Server) 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 := cache.New(*opt)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error creating config service: %w", err)
|
||||
}
|
||||
go pgrpc.Shutdown(grpcSrv)
|
||||
defer svc.Close()
|
||||
pbCache.RegisterCacheServer(controlPlane.GRPCServer, svc)
|
||||
log.Info().Msg("enabled cache service")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newProxyService(opt config.Options, r *mux.Router) (*proxy.Proxy, error) {
|
||||
if !config.IsProxy(opt.Services) {
|
||||
return nil, nil
|
||||
}
|
||||
service, err := proxy.New(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.PathPrefix("/").Handler(service)
|
||||
return service, 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))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func setupMetrics(opt *config.Options, wg *sync.WaitGroup) error {
|
||||
if opt.MetricsAddr != "" {
|
||||
handler, err := metrics.PrometheusHandler()
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,34 +121,6 @@ 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",
|
||||
|
|
|
@ -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 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 err != nil {
|
||||
return fmt.Errorf("config: bad cert base64 %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/docs/reference/reference /configuration/
|
||||
/docs/reference/reference.html /configuration/
|
||||
/docs/configuration/ /configuration/
|
||||
|
||||
/community/ /docs/community/
|
||||
/community/index.html /docs/community/
|
||||
|
|
|
@ -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].
|
||||
|
||||
<SimpleNewsletter/>
|
||||
|
||||
[**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
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
---
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|||
|
||||

|
||||
|
||||
[./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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
||||

|
||||
|
||||
|
@ -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` |
|
||||
|
||||

|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
## 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
|
|||
|
||||

|
||||
|
||||
::: 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
|
||||
|
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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`.
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!
|
|
@ -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
|
|
@ -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)
|
|
@ -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,22 +245,10 @@ 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
|
||||
|
||||
config:
|
||||
sharedSecret: YOUR_SHARED_SECRET
|
||||
cookieSecret: YOUR_COOKIE_SECRET
|
||||
rootDomain: domain.example
|
||||
|
||||
policy:
|
||||
policy:
|
||||
# this route is directly proxied by pomerium & injects the authorization header
|
||||
- from: https://dashboard-proxied.domain.example
|
||||
to: https://helm-dashboard-kubernetes-dashboard
|
||||
|
@ -275,13 +263,15 @@ config:
|
|||
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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
|
33
docs/recipes/yml/pomerium.ingress.yaml
Normal file
33
docs/recipes/yml/pomerium.ingress.yaml
Normal file
|
@ -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
|
37
go.mod
37
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
|
||||
)
|
||||
|
|
356
go.sum
356
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=
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module github.com/pomerium/pomerium/integration/backends/httpdetails
|
||||
|
||||
go 1.14
|
29
integration/backends/httpdetails/index.js
Normal file
29
integration/backends/httpdetails/index.js
Normal file
|
@ -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);
|
|
@ -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)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
module github.com/pomerium/pomerium/integration/backends/ws-echo
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/gorilla/websocket v1.4.2
|
|
@ -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=
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating integration tests working directory: %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"},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return &bundle, nil
|
||||
func bootstrapCerts(ctx context.Context) (*TLSCerts, error) {
|
||||
err := run(ctx, "mkcert", withArgs("-install"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error install root certificate: %w", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = run(ctx, "mkcert", withArgs("-CAROOT"), withStdout(&buf))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error running mkcert")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ import (
|
|||
|
||||
// A Cluster is used to configure a kubernetes cluster.
|
||||
type Cluster struct {
|
||||
Transport *http.Transport
|
||||
|
||||
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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
47
integration/internal/httputil/httputil.go
Normal file
47
integration/internal/httputil/httputil.go
Normal file
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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),
|
||||
],
|
||||
],
|
||||
),
|
||||
}
|
79
integration/manifests/lib/httpdetails.libsonnet
Normal file
79
integration/manifests/lib/httpdetails.libsonnet
Normal file
|
@ -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',
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
|
@ -1,70 +1,6 @@
|
|||
local tls = import './tls.libsonnet';
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
] + [
|
||||
local PomeriumPolicy = function() std.flattenArrays([
|
||||
[
|
||||
{
|
||||
from: 'http://' + domain + '.localhost.pomerium.io',
|
||||
|
@ -84,77 +20,47 @@ local PomeriumPolicy = function() std.flattenArrays(
|
|||
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',
|
||||
},
|
||||
{
|
||||
from: 'http://restricted-' + domain + '.localhost.pomerium.io',
|
||||
to: 'http://' + domain + '.default.svc.cluster.local',
|
||||
},
|
||||
]
|
||||
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,
|
||||
},
|
||||
],
|
||||
]
|
||||
);
|
||||
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',
|
||||
initContainers: [{
|
||||
name: 'pomerium-' + svc + '-certs',
|
||||
image: 'buildpack-deps:buster-curl',
|
||||
imagePullPolicy: 'IfNotPresent',
|
||||
imagePullPolicy: 'Always',
|
||||
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
|
||||
cp /incoming-certs/* /usr/local/share/ca-certificates
|
||||
update-ca-certificates
|
||||
|||],
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'trusted-incoming-certs',
|
||||
mountPath: '/incoming-certs/trusted',
|
||||
},
|
||||
{
|
||||
name: 'wrongly-named-incoming-certs',
|
||||
mountPath: '/incoming-certs/wrongly-named',
|
||||
name: 'incoming-certs',
|
||||
mountPath: '/incoming-certs',
|
||||
},
|
||||
{
|
||||
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 [],
|
||||
}],
|
||||
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'),
|
||||
|
|
|
@ -73,7 +73,7 @@ local Ingress = function() {
|
|||
hosts: [
|
||||
'openid.localhost.pomerium.io',
|
||||
],
|
||||
secretName: 'pomerium-trusted-tls',
|
||||
secretName: 'pomerium-tls',
|
||||
},
|
||||
],
|
||||
rules: [
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
39
internal/controlplane/grpc_accesslog.go
Normal file
39
internal/controlplane/grpc_accesslog.go
Normal file
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
135
internal/controlplane/grpc_xds.go
Normal file
135
internal/controlplane/grpc_xds.go
Normal file
|
@ -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")
|
||||
}
|
39
internal/controlplane/http.go
Normal file
39
internal/controlplane/http.go
Normal file
|
@ -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()))
|
||||
}
|
139
internal/controlplane/server.go
Normal file
139
internal/controlplane/server.go
Normal file
|
@ -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
|
||||
}
|
105
internal/controlplane/xds.go
Normal file
105
internal/controlplane/xds.go
Normal file
|
@ -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)
|
||||
}
|
105
internal/controlplane/xds_clusters.go
Normal file
105
internal/controlplane/xds_clusters.go
Normal file
|
@ -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
|
||||
}
|
269
internal/controlplane/xds_listeners.go
Normal file
269
internal/controlplane/xds_listeners.go
Normal file
|
@ -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
|
||||
}
|
136
internal/controlplane/xds_routes.go
Normal file
136
internal/controlplane/xds_routes.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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"},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
64
internal/envoy/embed.go
Normal file
64
internal/envoy/embed.go
Normal file
|
@ -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
|
||||
}
|
158
internal/envoy/envoy.go
Normal file
158
internal/envoy/envoy.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
9
internal/envoy/envoy_linux.go
Normal file
9
internal/envoy/envoy_linux.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build linux
|
||||
|
||||
package envoy
|
||||
|
||||
import "syscall"
|
||||
|
||||
var sysProcAttr = &syscall.SysProcAttr{
|
||||
Pdeathsig: syscall.SIGTERM,
|
||||
}
|
7
internal/envoy/envoy_notlinux.go
Normal file
7
internal/envoy/envoy_notlinux.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !linux
|
||||
|
||||
package envoy
|
||||
|
||||
import "syscall"
|
||||
|
||||
var sysProcAttr = &syscall.SysProcAttr{}
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,15 +115,31 @@ 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 {
|
||||
return httputil.NewError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
_, 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())
|
||||
|
@ -133,16 +148,6 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
|
|||
authN.RawQuery = q.Encode()
|
||||
httputil.Redirect(w, r, urlutil.NewSignedURL(p.SharedKey, &authN).String(), http.StatusFound)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set(httputil.HeaderPomeriumJWTAssertion, authz.GetSignedJwt())
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
func (p *Proxy) isAuthorized(r *http.Request) (bool, error) {
|
||||
tm, err := ptypes.TimestampProto(time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
return false, httputil.NewError(http.StatusInternalServerError, fmt.Errorf("error creating protobuf timestamp from current time: %w", err))
|
||||
}
|
||||
|
||||
r.Header.Set(httputil.HeaderPomeriumJWTAssertion, authz.GetSignedJwt())
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
return nil
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, httputil.NewError(http.StatusInternalServerError, err)
|
||||
return false, httputil.NewError(http.StatusInternalServerError, 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)
|
||||
switch res.HttpResponse.(type) {
|
||||
case *envoy_service_auth_v2.CheckResponse_OkResponse:
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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"))
|
||||
}
|
||||
|
||||
return authz, nil
|
||||
|
||||
}
|
||||
|
||||
// SetResponseHeaders sets a map of response headers.
|
||||
|
|
|
@ -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) {
|
||||
|
|
195
proxy/proxy.go
195
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
|
||||
|
||||
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
|
||||
authzClient envoy_service_auth_v2.AuthorizationClient
|
||||
|
||||
currentRouter atomic.Value
|
||||
}
|
||||
|
||||
// New takes a Proxy service from options and a validation function.
|
||||
|
@ -131,7 +123,6 @@ func New(opts config.Options) (*Proxy, error) {
|
|||
|
||||
cookieSecret: decodedCookieSecret,
|
||||
cookieOptions: cookieOptions,
|
||||
defaultUpstreamTimeout: opts.DefaultUpstreamTimeout,
|
||||
refreshCooldown: opts.RefreshCooldown,
|
||||
sessionStore: cookieStore,
|
||||
sessionLoaders: []sessions.SessionLoader{
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue