envoy: Initial changes

This commit is contained in:
Travis Groth 2020-05-18 16:34:31 -04:00
parent 8f78497e99
commit 99e788a9b4
107 changed files with 2542 additions and 3322 deletions

View file

@ -1,3 +0,0 @@
module github.com/pomerium/pomerium/integration/backends/httpdetails
go 1.14

View 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);

View file

@ -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)
}

View file

@ -1,5 +0,0 @@
module github.com/pomerium/pomerium/integration/backends/ws-echo
go 1.14
require github.com/gorilla/websocket v1.4.2

View file

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

View file

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

View file

@ -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")
})
}
}

View file

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

View file

@ -11,10 +11,10 @@ import (
// A Cluster is used to configure a kubernetes cluster.
type Cluster struct {
Transport *http.Transport
workingDir string
workingDir string
certsBundle *TLSCertsBundle
transport http.RoundTripper
certs *TLSCerts
}
// New creates a new Cluster.
@ -32,7 +32,7 @@ func (cluster *Cluster) NewHTTPClient() *http.Client {
panic(err)
}
return &http.Client{
Transport: &loggingRoundTripper{cluster.Transport},
Transport: &loggingRoundTripper{cluster.transport},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},

View file

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

View file

@ -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()

View 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
}

View file

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

View file

@ -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),
],
],
),
}

View 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',
},
}],
},
},
},
},
],
}

View file

@ -1,160 +1,66 @@
local tls = import './tls.libsonnet';
local PomeriumPolicy = function() std.flattenArrays(
local PomeriumPolicy = function() std.flattenArrays([
[
[
// tls_skip_verify
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-skip-verify-enabled',
tls_skip_verify: true,
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-skip-verify-disabled',
tls_skip_verify: false,
allow_public_unauthenticated_access: true,
},
// tls_server_name
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://wrongly-named-httpdetails.default.svc.cluster.local',
path: '/tls-server-name-enabled',
tls_server_name: 'httpdetails.localhost.notpomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://wrongly-named-httpdetails.default.svc.cluster.local',
path: '/tls-server-name-disabled',
allow_public_unauthenticated_access: true,
},
// tls_custom_certificate_authority
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-custom-ca-enabled',
tls_custom_ca: std.base64(tls.untrusted.ca),
tls_server_name: 'httpdetails.localhost.pomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-custom-ca-disabled',
allow_public_unauthenticated_access: true,
},
// tls_client_cert
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local',
path: '/tls-client-cert-enabled',
tls_client_cert: std.base64(tls.trusted.client.cert),
tls_client_key: std.base64(tls.trusted.client.key),
tls_server_name: 'httpdetails.localhost.pomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local',
path: '/tls-client-cert-disabled',
allow_public_unauthenticated_access: true,
},
],
] + [
[
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-domain',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_domains: ['dogs.test'],
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-user',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_users: ['bob@dogs.test'],
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-group',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_groups: ['admin'],
},
// cors_allow_preflight option
{
from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
prefix: '/cors-enabled',
cors_allow_preflight: true,
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
prefix: '/cors-disabled',
cors_allow_preflight: false,
},
// preserve_host_header option
{
from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
path: '/preserve-host-header-enabled',
allow_public_unauthenticated_access: true,
preserve_host_header: true,
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
path: '/preserve-host-header-disabled',
allow_public_unauthenticated_access: true,
preserve_host_header: false,
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
allow_public_unauthenticated_access: true,
set_request_headers: {
'X-Custom-Request-Header': 'custom-request-header-value',
},
},
]
for domain in ['httpdetails', 'fa-httpdetails', 'ws-echo']
] + [
[
{
from: 'http://enabled-ws-echo.localhost.pomerium.io',
to: 'http://ws-echo.default.svc.cluster.local',
allow_public_unauthenticated_access: true,
allow_websockets: true,
},
{
from: 'http://disabled-ws-echo.localhost.pomerium.io',
to: 'http://ws-echo.default.svc.cluster.local',
allow_public_unauthenticated_access: true,
},
],
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-domain',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_domains: ['dogs.test'],
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-user',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_users: ['bob@dogs.test'],
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-group',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_groups: ['admin'],
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
allow_public_unauthenticated_access: true,
},
{
from: 'http://restricted-' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
},
]
);
for domain in ['httpdetails', 'fa-httpdetails']
]);
local PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), '')));
local PomeriumTLSSecret = function(name) {
local PomeriumTLSSecret = function() {
apiVersion: 'v1',
kind: 'Secret',
type: 'kubernetes.io/tls',
metadata: {
namespace: 'default',
name: 'pomerium-' + name + '-tls',
name: 'pomerium-tls',
},
data: {
'tls-ca.crt': std.base64(tls[name].ca),
'tls.crt': std.base64(tls[name].cert),
'tls.key': std.base64(tls[name].key),
'tls-client.crt': std.base64(tls[name].client.cert),
'tls-client.key': std.base64(tls[name].client.key),
'tls.crt': std.base64(tls.cert),
'tls.key': std.base64(tls.key),
},
};
local PomeriumCAsConfigMap = function() {
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
namespace: 'default',
name: 'pomerium-cas',
labels: {
'app.kubernetes.io/part-of': 'pomerium',
},
},
data: {
'pomerium.crt': tls.ca,
},
};
@ -184,8 +90,8 @@ local PomeriumConfigMap = function() {
SHARED_SECRET: 'Wy+c0uSuIM0yGGXs82MBwTZwRiZ7Ki2T0LANnmzUtkI=',
COOKIE_SECRET: 'eZ91a/j9fhgki9zPDU5zHdQWX4io89pJanChMVa5OoM=',
CERTIFICATE: std.base64(tls.trusted.cert),
CERTIFICATE_KEY: std.base64(tls.trusted.key),
CERTIFICATE: std.base64(tls.cert),
CERTIFICATE_KEY: std.base64(tls.key),
IDP_PROVIDER: 'oidc',
IDP_PROVIDER_URL: 'https://openid.localhost.pomerium.io',
@ -231,43 +137,25 @@ local PomeriumDeployment = function(svc) {
'openid.localhost.pomerium.io',
],
}],
initContainers: [
{
name: 'init',
image: 'buildpack-deps:buster-curl',
imagePullPolicy: 'IfNotPresent',
command: ['sh', '-c', |||
cp /incoming-certs/trusted/tls-ca.crt /usr/local/share/ca-certificates/pomerium-trusted.crt
cp /incoming-certs/wrongly-named/tls-ca.crt /usr/local/share/ca-certificates/pomerium-wrongly-named.crt
update-ca-certificates
|||],
volumeMounts: [
{
name: 'trusted-incoming-certs',
mountPath: '/incoming-certs/trusted',
},
{
name: 'wrongly-named-incoming-certs',
mountPath: '/incoming-certs/wrongly-named',
},
{
name: 'outgoing-certs',
mountPath: '/etc/ssl/certs',
},
],
},
] + if svc == 'authenticate' then [
{
name: 'wait-for-openid',
image: 'buildpack-deps:buster-curl',
imagePullPolicy: 'IfNotPresent',
command: ['sh', '-c', |||
while ! curl http://openid.default.svc.cluster.local/.well-known/openid-configuration ; do
sleep 5
done
|||],
},
] else [],
initContainers: [{
name: 'pomerium-' + svc + '-certs',
image: 'buildpack-deps:buster-curl',
imagePullPolicy: 'Always',
command: ['sh', '-c', |||
cp /incoming-certs/* /usr/local/share/ca-certificates
update-ca-certificates
|||],
volumeMounts: [
{
name: 'incoming-certs',
mountPath: '/incoming-certs',
},
{
name: 'outgoing-certs',
mountPath: '/etc/ssl/certs',
},
],
}],
containers: [{
name: 'pomerium-' + svc,
image: 'pomerium/pomerium:dev',
@ -292,15 +180,9 @@ local PomeriumDeployment = function(svc) {
}],
volumes: [
{
name: 'trusted-incoming-certs',
secret: {
secretName: 'pomerium-trusted-tls',
},
},
{
name: 'wrongly-named-incoming-certs',
secret: {
secretName: 'pomerium-wrongly-named-tls',
name: 'incoming-certs',
configMap: {
name: 'pomerium-cas',
},
},
{
@ -348,8 +230,7 @@ local PomeriumIngress = function() {
'forward-authenticate.localhost.pomerium.io',
'httpecho.localhost.pomerium.io',
'httpdetails.localhost.pomerium.io',
'enabled-ws-echo.localhost.pomerium.io',
'disabled-ws-echo.localhost.pomerium.io',
'restricted-httpdetails.localhost.pomerium.io',
],
apiVersion: 'extensions/v1beta1',
@ -369,7 +250,7 @@ local PomeriumIngress = function() {
hosts: [
'authenticate.localhost.pomerium.io',
] + proxyHosts,
secretName: 'pomerium-trusted-tls',
secretName: 'pomerium-tls',
},
],
rules: [
@ -421,7 +302,7 @@ local PomeriumForwardAuthIngress = function() {
hosts: [
'fa-httpdetails.localhost.pomerium.io',
],
secretName: 'pomerium-trusted-tls',
secretName: 'pomerium-tls',
},
],
rules: [
@ -455,9 +336,8 @@ local PomeriumForwardAuthIngress = function() {
kind: 'List',
items: [
PomeriumConfigMap(),
PomeriumTLSSecret('trusted'),
PomeriumTLSSecret('untrusted'),
PomeriumTLSSecret('wrongly-named'),
PomeriumCAsConfigMap(),
PomeriumTLSSecret(),
PomeriumService('authenticate'),
PomeriumDeployment('authenticate'),
PomeriumService('authorize'),

View file

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

View file

@ -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'),
}

View file

@ -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,
}

View file

@ -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)
})
}