diff --git a/go.mod b/go.mod index c486b2d41..9031f5eaf 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/golang/protobuf v1.4.1 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/mux v1.7.4 github.com/gorilla/websocket v1.4.2 github.com/mitchellh/hashstructure v1.0.0 diff --git a/integration/backends/httpdetails/main.go b/integration/backends/httpdetails/main.go index fe98808e9..61395413a 100644 --- a/integration/backends/httpdetails/main.go +++ b/integration/backends/httpdetails/main.go @@ -1,36 +1,61 @@ package main import ( + "crypto/tls" + "crypto/x509" "encoding/json" "flag" "fmt" + "io/ioutil" "net/http" "os" ) func main() { var ( - certFile, keyFile, bindAddr string + 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 = http.ListenAndServeTLS(bindAddr, certFile, keyFile, http.HandlerFunc(handle)) + err = srv.ListenAndServeTLS(certFile, keyFile) } else { if bindAddr == "" { bindAddr = ":5080" } + srv.Addr = bindAddr fmt.Println("starting server on", bindAddr) - err = http.ListenAndServe(bindAddr, http.HandlerFunc(handle)) + err = srv.ListenAndServe() } if err != nil { fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err) diff --git a/integration/internal/cluster/certs.go b/integration/internal/cluster/certs.go index f3c0f5884..1d8d7fcad 100644 --- a/integration/internal/cluster/certs.go +++ b/integration/internal/cluster/certs.go @@ -1,66 +1,116 @@ 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 string - Cert string - Key string + CA []byte + Cert []byte + Key []byte + Client struct { + Cert []byte + Key []byte + } } -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) - } - - 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 +// 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 } diff --git a/integration/internal/cluster/cluster.go b/integration/internal/cluster/cluster.go index 5d0d0d28a..a06d1a9bc 100644 --- a/integration/internal/cluster/cluster.go +++ b/integration/internal/cluster/cluster.go @@ -13,8 +13,8 @@ import ( type Cluster struct { Transport *http.Transport - workingDir string - certs *TLSCerts + workingDir string + certsBundle *TLSCertsBundle } // New creates a new Cluster. diff --git a/integration/internal/cluster/cmd.go b/integration/internal/cluster/cmd.go index aa8286336..6664e4667 100644 --- a/integration/internal/cluster/cmd.go +++ b/integration/internal/cluster/cmd.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "os" "os/exec" "github.com/rs/zerolog/log" @@ -18,6 +19,12 @@ func withArgs(args ...string) cmdOption { } } +func withEnv(env ...string) cmdOption { + return func(cmd *exec.Cmd) { + cmd.Env = append(os.Environ(), env...) + } +} + func withStdin(rdr io.Reader) cmdOption { return func(cmd *exec.Cmd) { cmd.Stdin = rdr diff --git a/integration/internal/cluster/setup.go b/integration/internal/cluster/setup.go index 4cbd7e26f..82a66c557 100644 --- a/integration/internal/cluster/setup.go +++ b/integration/internal/cluster/setup.go @@ -36,7 +36,7 @@ func (cluster *Cluster) Setup(ctx context.Context) error { return fmt.Errorf("error running kubectl cluster-info: %w", err) } - cluster.certs, err = bootstrapCerts(ctx) + cluster.certsBundle, err = bootstrapCerts(ctx) if err != nil { return err } @@ -145,9 +145,21 @@ func (cluster *Cluster) generateManifests() (string, error) { } vm := jsonnet.MakeVM() - vm.ExtVar("tls-ca", cluster.certs.CA) - vm.ExtVar("tls-cert", cluster.certs.Cert) - vm.ExtVar("tls-key", cluster.certs.Key) + 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.Importer(&jsonnet.FileImporter{ JPaths: []string{filepath.Join(cluster.workingDir, "manifests")}, }) @@ -166,7 +178,7 @@ func applyManifests(ctx context.Context, jsonsrc string) error { } log.Info().Msg("waiting for deployments to come up") - ctx, clearTimeout := context.WithTimeout(ctx, 5*time.Minute) + ctx, clearTimeout := context.WithTimeout(ctx, 15*time.Minute) defer clearTimeout() ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() diff --git a/integration/manifests/lib/backends.libsonnet b/integration/manifests/lib/backends.libsonnet index d2e1f064d..0f004de95 100644 --- a/integration/manifests/lib/backends.libsonnet +++ b/integration/manifests/lib/backends.libsonnet @@ -11,16 +11,20 @@ local configMap = function(name, data) { data: data, }; -local service = function(name) { +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: name, - labels: { app: name }, + name: fullName, + labels: { app: fullName }, }, spec: { - selector: { app: name }, + selector: { app: fullName }, ports: [ { name: 'http', @@ -36,32 +40,41 @@ local service = function(name) { }, }; -local deployment = function(name) { +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: name, + name: fullName, }, spec: { replicas: 1, - selector: { matchLabels: { app: name } }, + selector: { matchLabels: { app: fullName } }, template: { metadata: { - labels: { app: name }, + labels: { app: fullName }, }, spec: { containers: [{ - name: name, + name: 'main', image: 'golang:buster', imagePullPolicy: 'IfNotPresent', args: [ 'bash', '-c', - ||| - cd /src - go run . - |||, + '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: [ { @@ -78,6 +91,10 @@ local deployment = function(name) { name: 'src', mountPath: '/src', }, + { + name: 'certs', + mountPath: '/certs', + }, ], }], volumes: [ @@ -87,29 +104,56 @@ local deployment = function(name) { 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: [ - configMap('httpdetails', { - 'main.go': importstr '../../backends/httpdetails/main.go', - 'go.mod': importstr '../../backends/httpdetails/go.mod', - }), - service('httpdetails'), - deployment('httpdetails'), - - configMap('ws-echo', { - 'main.go': importstr '../../backends/ws-echo/main.go', - 'go.mod': importstr '../../backends/ws-echo/go.mod', - 'go.sum': importstr '../../backends/ws-echo/go.sum', - }), - service('ws-echo'), - deployment('ws-echo'), - ], + items: std.flattenArrays( + [ + [ + configMap(backend.name, backend.files), + service(backend.name, null, false), + deployment(backend.name, null, false), + service(backend.name, 'wrongly-named', false), + deployment(backend.name, 'wrongly-named', false), + service(backend.name, 'untrusted', false), + deployment(backend.name, 'untrusted', false), + ] + for backend in backends + ] + [ + [ + service('httpdetails', 'trusted', true), + deployment('httpdetails', 'trusted', true), + ], + ], + ), } diff --git a/integration/manifests/lib/pomerium.libsonnet b/integration/manifests/lib/pomerium.libsonnet index d1d6a045e..8da9b3cd6 100644 --- a/integration/manifests/lib/pomerium.libsonnet +++ b/integration/manifests/lib/pomerium.libsonnet @@ -1,105 +1,160 @@ local tls = import './tls.libsonnet'; -local PomeriumPolicy = function() std.flattenArrays([ +local PomeriumPolicy = function() std.flattenArrays( [ - { - from: 'http://' + domain + '.localhost.pomerium.io', - prefix: '/by-domain', - to: 'http://' + domain + '.default.svc.cluster.local', - allowed_domains: ['dogs.test'], - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - prefix: '/by-user', - to: 'http://' + domain + '.default.svc.cluster.local', - allowed_users: ['bob@dogs.test'], - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - prefix: '/by-group', - to: 'http://' + domain + '.default.svc.cluster.local', - allowed_groups: ['admin'], - }, - // cors_allow_preflight option - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - prefix: '/cors-enabled', - cors_allow_preflight: true, - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - prefix: '/cors-disabled', - cors_allow_preflight: false, - }, - // preserve_host_header option - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - path: '/preserve-host-header-enabled', - allow_public_unauthenticated_access: true, - preserve_host_header: true, - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - path: '/preserve-host-header-disabled', - allow_public_unauthenticated_access: true, - preserve_host_header: false, - }, - { - from: 'http://' + domain + '.localhost.pomerium.io', - to: 'http://' + domain + '.default.svc.cluster.local', - allow_public_unauthenticated_access: true, - set_request_headers: { - 'X-Custom-Request-Header': 'custom-request-header-value', + [ + // tls_skip_verify + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://untrusted-httpdetails.default.svc.cluster.local', + path: '/tls-skip-verify-enabled', + tls_skip_verify: true, + allow_public_unauthenticated_access: true, }, - }, + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://untrusted-httpdetails.default.svc.cluster.local', + path: '/tls-skip-verify-disabled', + tls_skip_verify: false, + allow_public_unauthenticated_access: true, + }, + // tls_server_name + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://wrongly-named-httpdetails.default.svc.cluster.local', + path: '/tls-server-name-enabled', + tls_server_name: 'httpdetails.localhost.notpomerium.io', + allow_public_unauthenticated_access: true, + }, + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://wrongly-named-httpdetails.default.svc.cluster.local', + path: '/tls-server-name-disabled', + allow_public_unauthenticated_access: true, + }, + // tls_custom_certificate_authority + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://untrusted-httpdetails.default.svc.cluster.local', + path: '/tls-custom-ca-enabled', + tls_custom_ca: std.base64(tls.untrusted.ca), + tls_server_name: 'httpdetails.localhost.pomerium.io', + allow_public_unauthenticated_access: true, + }, + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://untrusted-httpdetails.default.svc.cluster.local', + path: '/tls-custom-ca-disabled', + allow_public_unauthenticated_access: true, + }, + // tls_client_cert + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local', + path: '/tls-client-cert-enabled', + tls_client_cert: std.base64(tls.trusted.client.cert), + tls_client_key: std.base64(tls.trusted.client.key), + tls_server_name: 'httpdetails.localhost.pomerium.io', + allow_public_unauthenticated_access: true, + }, + { + from: 'http://httpdetails.localhost.pomerium.io', + to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local', + path: '/tls-client-cert-disabled', + allow_public_unauthenticated_access: true, + }, + ], + ] + [ + [ + { + from: 'http://' + domain + '.localhost.pomerium.io', + prefix: '/by-domain', + to: 'http://' + domain + '.default.svc.cluster.local', + allowed_domains: ['dogs.test'], + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + prefix: '/by-user', + to: 'http://' + domain + '.default.svc.cluster.local', + allowed_users: ['bob@dogs.test'], + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + prefix: '/by-group', + to: 'http://' + domain + '.default.svc.cluster.local', + allowed_groups: ['admin'], + }, + // cors_allow_preflight option + { + from: 'http://' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + prefix: '/cors-enabled', + cors_allow_preflight: true, + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + prefix: '/cors-disabled', + cors_allow_preflight: false, + }, + // preserve_host_header option + { + from: 'http://' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + path: '/preserve-host-header-enabled', + allow_public_unauthenticated_access: true, + preserve_host_header: true, + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + path: '/preserve-host-header-disabled', + allow_public_unauthenticated_access: true, + preserve_host_header: false, + }, + { + from: 'http://' + domain + '.localhost.pomerium.io', + to: 'http://' + domain + '.default.svc.cluster.local', + allow_public_unauthenticated_access: true, + set_request_headers: { + 'X-Custom-Request-Header': 'custom-request-header-value', + }, + }, + ] + for domain in ['httpdetails', 'fa-httpdetails', 'ws-echo'] + ] + [ + [ + { + from: 'http://enabled-ws-echo.localhost.pomerium.io', + to: 'http://ws-echo.default.svc.cluster.local', + allow_public_unauthenticated_access: true, + allow_websockets: true, + }, + { + from: 'http://disabled-ws-echo.localhost.pomerium.io', + to: 'http://ws-echo.default.svc.cluster.local', + allow_public_unauthenticated_access: true, + }, + ], ] - 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, - }, -]; +); local PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), ''))); -local PomeriumTLSSecret = function() { +local PomeriumTLSSecret = function(name) { apiVersion: 'v1', kind: 'Secret', type: 'kubernetes.io/tls', metadata: { namespace: 'default', - name: 'pomerium-tls', + name: 'pomerium-' + name + '-tls', }, data: { - '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, + '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), }, }; @@ -129,8 +184,8 @@ local PomeriumConfigMap = function() { SHARED_SECRET: 'Wy+c0uSuIM0yGGXs82MBwTZwRiZ7Ki2T0LANnmzUtkI=', COOKIE_SECRET: 'eZ91a/j9fhgki9zPDU5zHdQWX4io89pJanChMVa5OoM=', - CERTIFICATE: std.base64(tls.cert), - CERTIFICATE_KEY: std.base64(tls.key), + CERTIFICATE: std.base64(tls.trusted.cert), + CERTIFICATE_KEY: std.base64(tls.trusted.key), IDP_PROVIDER: 'oidc', IDP_PROVIDER_URL: 'https://openid.localhost.pomerium.io', @@ -176,25 +231,43 @@ local PomeriumDeployment = function(svc) { 'openid.localhost.pomerium.io', ], }], - initContainers: [{ - name: 'pomerium-' + svc + '-certs', - image: 'buildpack-deps:buster-curl', - imagePullPolicy: 'Always', - command: ['sh', '-c', ||| - cp /incoming-certs/* /usr/local/share/ca-certificates - update-ca-certificates - |||], - volumeMounts: [ - { - name: 'incoming-certs', - mountPath: '/incoming-certs', - }, - { - name: 'outgoing-certs', - mountPath: '/etc/ssl/certs', - }, - ], - }], + initContainers: [ + { + name: 'init', + image: 'buildpack-deps:buster-curl', + imagePullPolicy: 'IfNotPresent', + command: ['sh', '-c', ||| + cp /incoming-certs/trusted/tls-ca.crt /usr/local/share/ca-certificates/pomerium-trusted.crt + cp /incoming-certs/wrongly-named/tls-ca.crt /usr/local/share/ca-certificates/pomerium-wrongly-named.crt + update-ca-certificates + |||], + volumeMounts: [ + { + name: 'trusted-incoming-certs', + mountPath: '/incoming-certs/trusted', + }, + { + name: 'wrongly-named-incoming-certs', + mountPath: '/incoming-certs/wrongly-named', + }, + { + name: 'outgoing-certs', + mountPath: '/etc/ssl/certs', + }, + ], + }, + ] + if svc == 'authenticate' then [ + { + name: 'wait-for-openid', + image: 'buildpack-deps:buster-curl', + imagePullPolicy: 'IfNotPresent', + command: ['sh', '-c', ||| + while ! curl http://openid.default.svc.cluster.local/.well-known/openid-configuration ; do + sleep 5 + done + |||], + }, + ] else [], containers: [{ name: 'pomerium-' + svc, image: 'pomerium/pomerium:dev', @@ -219,9 +292,15 @@ local PomeriumDeployment = function(svc) { }], volumes: [ { - name: 'incoming-certs', - configMap: { - name: 'pomerium-cas', + name: 'trusted-incoming-certs', + secret: { + secretName: 'pomerium-trusted-tls', + }, + }, + { + name: 'wrongly-named-incoming-certs', + secret: { + secretName: 'pomerium-wrongly-named-tls', }, }, { @@ -290,7 +369,7 @@ local PomeriumIngress = function() { hosts: [ 'authenticate.localhost.pomerium.io', ] + proxyHosts, - secretName: 'pomerium-tls', + secretName: 'pomerium-trusted-tls', }, ], rules: [ @@ -342,7 +421,7 @@ local PomeriumForwardAuthIngress = function() { hosts: [ 'fa-httpdetails.localhost.pomerium.io', ], - secretName: 'pomerium-tls', + secretName: 'pomerium-trusted-tls', }, ], rules: [ @@ -376,8 +455,9 @@ local PomeriumForwardAuthIngress = function() { kind: 'List', items: [ PomeriumConfigMap(), - PomeriumCAsConfigMap(), - PomeriumTLSSecret(), + PomeriumTLSSecret('trusted'), + PomeriumTLSSecret('untrusted'), + PomeriumTLSSecret('wrongly-named'), PomeriumService('authenticate'), PomeriumDeployment('authenticate'), PomeriumService('authorize'), diff --git a/integration/manifests/lib/reference-openid-provider.libsonnet b/integration/manifests/lib/reference-openid-provider.libsonnet index 10875318a..2d5ced32b 100644 --- a/integration/manifests/lib/reference-openid-provider.libsonnet +++ b/integration/manifests/lib/reference-openid-provider.libsonnet @@ -73,7 +73,7 @@ local Ingress = function() { hosts: [ 'openid.localhost.pomerium.io', ], - secretName: 'pomerium-tls', + secretName: 'pomerium-trusted-tls', }, ], rules: [ diff --git a/integration/manifests/lib/tls.libsonnet b/integration/manifests/lib/tls.libsonnet index 5d91910ad..707967865 100644 --- a/integration/manifests/lib/tls.libsonnet +++ b/integration/manifests/lib/tls.libsonnet @@ -1,5 +1,29 @@ { - cert: std.extVar('tls-cert'), - key: std.extVar('tls-key'), - ca: std.extVar('tls-ca'), + 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'), + }, + }, } diff --git a/integration/policy_test.go b/integration/policy_test.go index 72b527de7..1ffaf9b2d 100644 --- a/integration/policy_test.go +++ b/integration/policy_test.go @@ -180,3 +180,159 @@ func TestWebsocket(t *testing.T) { 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) + }) +}