integration-tests: TLS policy configuration options (#708)

* integration-tests: switch to go for backends to support TLS scenarios

* fix apply order

* generate additional tls certs

* integration-tests: tls_skip_verify option

* integration-tests: wait for openid to come up before starting authenticate

* add tls_server_name test

* add test for tls_custom_ca

* increase setup timeout to 15 minutes

* fix secret name reference

* mtls wip

* mtls wip

* add test for client_cert
This commit is contained in:
Caleb Doxsey 2020-05-15 16:37:09 -06:00 committed by GitHub
parent 397d4a9f51
commit 49067c8f06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 606 additions and 209 deletions

1
go.mod
View file

@ -16,7 +16,6 @@ require (
github.com/golang/protobuf v1.4.1 github.com/golang/protobuf v1.4.1
github.com/google/go-cmp v0.4.0 github.com/google/go-cmp v0.4.0
github.com/google/go-jsonnet v0.15.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/mux v1.7.4
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/mitchellh/hashstructure v1.0.0 github.com/mitchellh/hashstructure v1.0.0

View file

@ -1,36 +1,61 @@
package main package main
import ( import (
"crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"os" "os"
) )
func main() { func main() {
var ( var (
certFile, keyFile, bindAddr string certFile, keyFile, mutualAuthCAFile, bindAddr string
) )
flag.StringVar(&certFile, "cert-file", "", "the tls cert file to use") flag.StringVar(&certFile, "cert-file", "", "the tls cert file to use")
flag.StringVar(&keyFile, "key-file", "", "the tls key 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.StringVar(&bindAddr, "bind-addr", "", "the address to listen on")
flag.Parse() 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 var err error
if certFile != "" && keyFile != "" { if certFile != "" && keyFile != "" {
if bindAddr == "" { if bindAddr == "" {
bindAddr = ":5443" bindAddr = ":5443"
} }
srv.Addr = bindAddr
fmt.Println("starting server on", bindAddr) fmt.Println("starting server on", bindAddr)
err = http.ListenAndServeTLS(bindAddr, certFile, keyFile, http.HandlerFunc(handle)) err = srv.ListenAndServeTLS(certFile, keyFile)
} else { } else {
if bindAddr == "" { if bindAddr == "" {
bindAddr = ":5080" bindAddr = ":5080"
} }
srv.Addr = bindAddr
fmt.Println("starting server on", bindAddr) fmt.Println("starting server on", bindAddr)
err = http.ListenAndServe(bindAddr, http.HandlerFunc(handle)) err = srv.ListenAndServe()
} }
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err) fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err)

View file

@ -1,66 +1,116 @@
package cluster package cluster
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/google/uuid"
) )
// TLSCerts holds the certificate authority, certificate and certificate key for a TLS connection. // TLSCerts holds the certificate authority, certificate and certificate key for a TLS connection.
type TLSCerts struct { type TLSCerts struct {
CA string CA []byte
Cert string Cert []byte
Key string Key []byte
Client struct {
Cert []byte
Key []byte
}
} }
func bootstrapCerts(ctx context.Context) (*TLSCerts, error) { // TLSCertsBundle holds various TLSCerts.
err := run(ctx, "mkcert", withArgs("-install")) type TLSCertsBundle struct {
if err != nil { Trusted TLSCerts
return nil, fmt.Errorf("error install root certificate: %w", err) WronglyNamed TLSCerts
} Untrusted TLSCerts
}
var buf bytes.Buffer
err = run(ctx, "mkcert", withArgs("-CAROOT"), withStdout(&buf)) func bootstrapCerts(ctx context.Context) (*TLSCertsBundle, error) {
if err != nil { wd := filepath.Join(os.TempDir(), "pomerium-integration-tests", "certs")
return nil, fmt.Errorf("error running mkcert") err := os.MkdirAll(wd, 0755)
} if err != nil {
return nil, fmt.Errorf("error creating integration tests working directory: %w", err)
caPath := strings.TrimSpace(buf.String()) }
ca, err := ioutil.ReadFile(filepath.Join(caPath, "rootCA.pem"))
if err != nil { var bundle TLSCertsBundle
return nil, fmt.Errorf("error reading root ca: %w", err)
} var generators = []struct {
certs *TLSCerts
wd := filepath.Join(os.TempDir(), uuid.New().String()) caroot string
err = os.MkdirAll(wd, 0755) install bool
if err != nil { name string
return nil, fmt.Errorf("error creating temporary directory: %w", err) }{
} {&bundle.Trusted, filepath.Join(wd, "trusted"), true, "*.localhost.pomerium.io"},
{&bundle.WronglyNamed, filepath.Join(wd, "wrongly-named"), true, "*.localhost.notpomerium.io"},
err = run(ctx, "mkcert", withArgs("*.localhost.pomerium.io"), withWorkingDir(wd)) {&bundle.Untrusted, filepath.Join(wd, "untrusted"), false, "*.localhost.pomerium.io"},
if err != nil { }
return nil, fmt.Errorf("error generating certificates: %w", err)
} for _, generator := range generators {
err = os.MkdirAll(generator.caroot, 0755)
cert, err := ioutil.ReadFile(filepath.Join(wd, "_wildcard.localhost.pomerium.io.pem")) if err != nil {
if err != nil { return nil, fmt.Errorf("error creating integration tests %s working directory: %w",
return nil, fmt.Errorf("error reading certificate: %w", err) filepath.Base(generator.caroot), err)
} }
key, err := ioutil.ReadFile(filepath.Join(wd, "_wildcard.localhost.pomerium.io-key.pem")) args := []string{"-install"}
if err != nil { env := []string{"CAROOT=" + generator.caroot}
return nil, fmt.Errorf("error reading certificate key: %w", err) if !generator.install {
} env = append(env, "TRUST_STORES=xxx")
}
return &TLSCerts{ err = run(ctx, "mkcert", withArgs(args...), withEnv(env...))
CA: string(ca), if err != nil {
Cert: string(cert), return nil, fmt.Errorf("error creating %s certificate authority: %w",
Key: string(key), filepath.Base(generator.caroot), err)
}, nil }
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
} }

View file

@ -13,8 +13,8 @@ import (
type Cluster struct { type Cluster struct {
Transport *http.Transport Transport *http.Transport
workingDir string workingDir string
certs *TLSCerts certsBundle *TLSCertsBundle
} }
// New creates a new Cluster. // New creates a new Cluster.

View file

@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"os"
"os/exec" "os/exec"
"github.com/rs/zerolog/log" "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 { func withStdin(rdr io.Reader) cmdOption {
return func(cmd *exec.Cmd) { return func(cmd *exec.Cmd) {
cmd.Stdin = rdr cmd.Stdin = rdr

View file

@ -36,7 +36,7 @@ func (cluster *Cluster) Setup(ctx context.Context) error {
return fmt.Errorf("error running kubectl cluster-info: %w", err) return fmt.Errorf("error running kubectl cluster-info: %w", err)
} }
cluster.certs, err = bootstrapCerts(ctx) cluster.certsBundle, err = bootstrapCerts(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -145,9 +145,21 @@ func (cluster *Cluster) generateManifests() (string, error) {
} }
vm := jsonnet.MakeVM() vm := jsonnet.MakeVM()
vm.ExtVar("tls-ca", cluster.certs.CA) for _, item := range []struct {
vm.ExtVar("tls-cert", cluster.certs.Cert) name string
vm.ExtVar("tls-key", cluster.certs.Key) 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{ vm.Importer(&jsonnet.FileImporter{
JPaths: []string{filepath.Join(cluster.workingDir, "manifests")}, 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") 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() defer clearTimeout()
ticker := time.NewTicker(time.Second * 5) ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop() defer ticker.Stop()

View file

@ -11,16 +11,20 @@ local configMap = function(name, data) {
data: 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', apiVersion: 'v1',
kind: 'Service', kind: 'Service',
metadata: { metadata: {
namespace: 'default', namespace: 'default',
name: name, name: fullName,
labels: { app: name }, labels: { app: fullName },
}, },
spec: { spec: {
selector: { app: name }, selector: { app: fullName },
ports: [ ports: [
{ {
name: 'http', 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', apiVersion: 'apps/v1',
kind: 'Deployment', kind: 'Deployment',
metadata: { metadata: {
namespace: 'default', namespace: 'default',
name: name, name: fullName,
}, },
spec: { spec: {
replicas: 1, replicas: 1,
selector: { matchLabels: { app: name } }, selector: { matchLabels: { app: fullName } },
template: { template: {
metadata: { metadata: {
labels: { app: name }, labels: { app: fullName },
}, },
spec: { spec: {
containers: [{ containers: [{
name: name, name: 'main',
image: 'golang:buster', image: 'golang:buster',
imagePullPolicy: 'IfNotPresent', imagePullPolicy: 'IfNotPresent',
args: [ args: [
'bash', 'bash',
'-c', '-c',
||| 'cd /src && go run . ' +
cd /src (if tlsName != null then
go run . ' -cert-file=/certs/tls.crt -key-file=/certs/tls.key'
|||, else
'') +
(if requireMutualAuth then
' -mutual-auth-ca-file=/certs/tls-ca.crt'
else
''),
], ],
ports: [ ports: [
{ {
@ -78,6 +91,10 @@ local deployment = function(name) {
name: 'src', name: 'src',
mountPath: '/src', mountPath: '/src',
}, },
{
name: 'certs',
mountPath: '/certs',
},
], ],
}], }],
volumes: [ volumes: [
@ -87,29 +104,56 @@ local deployment = function(name) {
name: 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', apiVersion: 'v1',
kind: 'List', kind: 'List',
items: [ items: std.flattenArrays(
configMap('httpdetails', { [
'main.go': importstr '../../backends/httpdetails/main.go', [
'go.mod': importstr '../../backends/httpdetails/go.mod', configMap(backend.name, backend.files),
}), service(backend.name, null, false),
service('httpdetails'), deployment(backend.name, null, false),
deployment('httpdetails'), service(backend.name, 'wrongly-named', false),
deployment(backend.name, 'wrongly-named', false),
configMap('ws-echo', { service(backend.name, 'untrusted', false),
'main.go': importstr '../../backends/ws-echo/main.go', deployment(backend.name, 'untrusted', false),
'go.mod': importstr '../../backends/ws-echo/go.mod', ]
'go.sum': importstr '../../backends/ws-echo/go.sum', for backend in backends
}), ] + [
service('ws-echo'), [
deployment('ws-echo'), service('httpdetails', 'trusted', true),
], deployment('httpdetails', 'trusted', true),
],
],
),
} }

View file

@ -1,105 +1,160 @@
local tls = import './tls.libsonnet'; local tls = import './tls.libsonnet';
local PomeriumPolicy = function() std.flattenArrays([ local PomeriumPolicy = function() std.flattenArrays(
[ [
{ [
from: 'http://' + domain + '.localhost.pomerium.io', // tls_skip_verify
prefix: '/by-domain', {
to: 'http://' + domain + '.default.svc.cluster.local', from: 'http://httpdetails.localhost.pomerium.io',
allowed_domains: ['dogs.test'], to: 'https://untrusted-httpdetails.default.svc.cluster.local',
}, path: '/tls-skip-verify-enabled',
{ tls_skip_verify: true,
from: 'http://' + domain + '.localhost.pomerium.io', allow_public_unauthenticated_access: true,
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',
}, },
}, {
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 PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), '')));
local PomeriumTLSSecret = function() { local PomeriumTLSSecret = function(name) {
apiVersion: 'v1', apiVersion: 'v1',
kind: 'Secret', kind: 'Secret',
type: 'kubernetes.io/tls', type: 'kubernetes.io/tls',
metadata: { metadata: {
namespace: 'default', namespace: 'default',
name: 'pomerium-tls', name: 'pomerium-' + name + '-tls',
}, },
data: { data: {
'tls.crt': std.base64(tls.cert), 'tls-ca.crt': std.base64(tls[name].ca),
'tls.key': std.base64(tls.key), '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),
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,
}, },
}; };
@ -129,8 +184,8 @@ local PomeriumConfigMap = function() {
SHARED_SECRET: 'Wy+c0uSuIM0yGGXs82MBwTZwRiZ7Ki2T0LANnmzUtkI=', SHARED_SECRET: 'Wy+c0uSuIM0yGGXs82MBwTZwRiZ7Ki2T0LANnmzUtkI=',
COOKIE_SECRET: 'eZ91a/j9fhgki9zPDU5zHdQWX4io89pJanChMVa5OoM=', COOKIE_SECRET: 'eZ91a/j9fhgki9zPDU5zHdQWX4io89pJanChMVa5OoM=',
CERTIFICATE: std.base64(tls.cert), CERTIFICATE: std.base64(tls.trusted.cert),
CERTIFICATE_KEY: std.base64(tls.key), CERTIFICATE_KEY: std.base64(tls.trusted.key),
IDP_PROVIDER: 'oidc', IDP_PROVIDER: 'oidc',
IDP_PROVIDER_URL: 'https://openid.localhost.pomerium.io', IDP_PROVIDER_URL: 'https://openid.localhost.pomerium.io',
@ -176,25 +231,43 @@ local PomeriumDeployment = function(svc) {
'openid.localhost.pomerium.io', 'openid.localhost.pomerium.io',
], ],
}], }],
initContainers: [{ initContainers: [
name: 'pomerium-' + svc + '-certs', {
image: 'buildpack-deps:buster-curl', name: 'init',
imagePullPolicy: 'Always', image: 'buildpack-deps:buster-curl',
command: ['sh', '-c', ||| imagePullPolicy: 'IfNotPresent',
cp /incoming-certs/* /usr/local/share/ca-certificates command: ['sh', '-c', |||
update-ca-certificates 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
volumeMounts: [ update-ca-certificates
{ |||],
name: 'incoming-certs', volumeMounts: [
mountPath: '/incoming-certs', {
}, name: 'trusted-incoming-certs',
{ mountPath: '/incoming-certs/trusted',
name: 'outgoing-certs', },
mountPath: '/etc/ssl/certs', {
}, 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: [{ containers: [{
name: 'pomerium-' + svc, name: 'pomerium-' + svc,
image: 'pomerium/pomerium:dev', image: 'pomerium/pomerium:dev',
@ -219,9 +292,15 @@ local PomeriumDeployment = function(svc) {
}], }],
volumes: [ volumes: [
{ {
name: 'incoming-certs', name: 'trusted-incoming-certs',
configMap: { secret: {
name: 'pomerium-cas', secretName: 'pomerium-trusted-tls',
},
},
{
name: 'wrongly-named-incoming-certs',
secret: {
secretName: 'pomerium-wrongly-named-tls',
}, },
}, },
{ {
@ -290,7 +369,7 @@ local PomeriumIngress = function() {
hosts: [ hosts: [
'authenticate.localhost.pomerium.io', 'authenticate.localhost.pomerium.io',
] + proxyHosts, ] + proxyHosts,
secretName: 'pomerium-tls', secretName: 'pomerium-trusted-tls',
}, },
], ],
rules: [ rules: [
@ -342,7 +421,7 @@ local PomeriumForwardAuthIngress = function() {
hosts: [ hosts: [
'fa-httpdetails.localhost.pomerium.io', 'fa-httpdetails.localhost.pomerium.io',
], ],
secretName: 'pomerium-tls', secretName: 'pomerium-trusted-tls',
}, },
], ],
rules: [ rules: [
@ -376,8 +455,9 @@ local PomeriumForwardAuthIngress = function() {
kind: 'List', kind: 'List',
items: [ items: [
PomeriumConfigMap(), PomeriumConfigMap(),
PomeriumCAsConfigMap(), PomeriumTLSSecret('trusted'),
PomeriumTLSSecret(), PomeriumTLSSecret('untrusted'),
PomeriumTLSSecret('wrongly-named'),
PomeriumService('authenticate'), PomeriumService('authenticate'),
PomeriumDeployment('authenticate'), PomeriumDeployment('authenticate'),
PomeriumService('authorize'), PomeriumService('authorize'),

View file

@ -73,7 +73,7 @@ local Ingress = function() {
hosts: [ hosts: [
'openid.localhost.pomerium.io', 'openid.localhost.pomerium.io',
], ],
secretName: 'pomerium-tls', secretName: 'pomerium-trusted-tls',
}, },
], ],
rules: [ rules: [

View file

@ -1,5 +1,29 @@
{ {
cert: std.extVar('tls-cert'), trusted: {
key: std.extVar('tls-key'), cert: std.extVar('tls-trusted-cert'),
ca: std.extVar('tls-ca'), 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'),
},
},
} }

View file

@ -180,3 +180,159 @@ func TestWebsocket(t *testing.T) {
assert.NoError(t, err, "expected no error when reading json from websocket") 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)
})
}