integration: remove tests (#2514)

This commit is contained in:
Caleb Doxsey 2021-08-23 13:07:18 -06:00 committed by GitHub
parent c7bf038f41
commit 9aad155e1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 0 additions and 2837 deletions

3
go.mod
View file

@ -24,11 +24,9 @@ require (
github.com/golangci/golangci-lint v1.42.0
github.com/google/btree v1.0.1
github.com/google/go-cmp v0.5.6
github.com/google/go-jsonnet v0.17.0
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/golang-lru v0.5.4
@ -37,7 +35,6 @@ require (
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mitchellh/mapstructure v1.4.1
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09
github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5
github.com/open-policy-agent/opa v0.31.0
github.com/openzipkin/zipkin-go v0.2.5
github.com/ory/dockertest/v3 v3.7.0

6
go.sum
View file

@ -416,8 +416,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-jsonnet v0.17.0 h1:/9NIEfhK1NQRKl3sP2536b2+x5HnZMdql7x3yK/l8JY=
github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -470,7 +468,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
@ -736,8 +733,6 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5 h1:uuhPqmc+m7Nj7btxZEjdEUv+uFoBHNf2Tk/E7gGM+kY=
github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5/go.mod h1:tHaogb+iP6wJXwCqVUlmxYuJb4XDyEKxxs3E4DvMBK0=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
@ -881,7 +876,6 @@ github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQw
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/securego/gosec/v2 v2.8.1 h1:Tyy/nsH39TYCOkqf5HAgRE+7B5D8sHDwPdXRgFWokh8=
github.com/securego/gosec/v2 v2.8.1/go.mod h1:pUmsq6+VyFEElJMUX+QB3p3LWNHXg1R3xh2ssVJPs8Q=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs=

View file

@ -1,10 +0,0 @@
# Integration Tests
These tests are full end-to-end integration tests using Pomerium in a kubernetes cluster.
## Usage
The following applications are needed:
* `kubectl`: to apply the manifests to kubernetes
* `mkcert`: to generate a root CA and wildcard certificates
The test suite will apply the manifests to your current Kubernetes context before running the tests.

View file

@ -1,96 +0,0 @@
package main
import (
"context"
"net/http"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/integration/internal/flows"
)
func TestAuthorization(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(mainCtx, time.Second*30)
defer clearTimeout()
accessType := []string{"direct", "api"}
for _, at := range accessType {
t.Run(at, func(t *testing.T) {
var withAPI flows.AuthenticateOption
if at == "api" {
withAPI = flows.WithAPI()
}
t.Run("public", func(t *testing.T) {
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()
assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code, headers=%v", res.Header)
})
t.Run("domains", func(t *testing.T) {
t.Run("allowed", func(t *testing.T) {
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-domain"),
withAPI, flows.WithEmail("bob@dogs.test"), flows.WithGroups("user"))
if assert.NoError(t, err) {
assert.Equal(t, http.StatusOK, res.StatusCode, "expected OK for dogs.test")
}
})
t.Run("not allowed", func(t *testing.T) {
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-domain"),
withAPI, flows.WithEmail("joe@cats.test"), flows.WithGroups("user"))
if assert.NoError(t, err) {
assertDeniedAccess(t, res, "expected Forbidden for cats.test")
}
})
})
t.Run("users", func(t *testing.T) {
t.Run("allowed", func(t *testing.T) {
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-user"),
withAPI, flows.WithEmail("bob@dogs.test"), flows.WithGroups("user"))
if assert.NoError(t, err) {
assert.Equal(t, http.StatusOK, res.StatusCode, "expected OK for bob@dogs.test")
}
})
t.Run("not allowed", func(t *testing.T) {
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-user"),
withAPI, flows.WithEmail("joe@cats.test"), flows.WithGroups("user"))
if assert.NoError(t, err) {
assertDeniedAccess(t, res, "expected Forbidden for joe@cats.test")
}
})
})
})
}
}
func mustParseURL(str string) *url.URL {
u, err := url.Parse(str)
if err != nil {
panic(err)
}
return u
}
func assertDeniedAccess(t *testing.T, res *http.Response, msgAndArgs ...interface{}) bool {
return assert.Condition(t, func() bool {
return res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized
}, msgAndArgs...)
}

View file

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

View file

@ -1,93 +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)
}
}
// Result is a result used for rendering.
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,63 +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\n", err)
os.Exit(1)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
conn, err := (&websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}).Upgrade(w, r, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "error upgrading websocket connection: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()
for {
mt, p, err := conn.ReadMessage()
if err != nil {
return
}
err = conn.WriteMessage(mt, p)
if err != nil {
return
}
}
}

View file

@ -1,120 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDashboard(t *testing.T) {
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
t.Run("user dashboard", func(t *testing.T) {
client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/.pomerium/", 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.StatusFound, res.StatusCode, "unexpected status code")
})
t.Run("dashboard strict slash redirect", func(t *testing.T) {
client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/.pomerium", 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.StatusMovedPermanently, res.StatusCode, "unexpected status code")
})
t.Run("image asset", func(t *testing.T) {
client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/.pomerium/assets/img/pomerium.svg", 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")
assert.Equal(t, "image/svg+xml", res.Header.Get("Content-Type"))
})
t.Run("forward auth image asset", func(t *testing.T) {
client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", "https://fa-httpdetails.localhost.pomerium.io/.pomerium/assets/img/pomerium.svg", 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")
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()
pomeriumRoutes := []string{
"https://authenticate.localhost.pomerium.io",
"https://forward-authenticate.localhost.pomerium.io",
"https://httpdetails.localhost.pomerium.io",
"https://restricted-httpdetails.localhost.pomerium.io",
}
endpoints := []string{"healthz", "ping"}
for _, route := range pomeriumRoutes {
route := route
for _, endpoint := range endpoints {
endpoint := endpoint
routeToCheck := fmt.Sprintf("%s/%s", route, endpoint)
t.Run(routeToCheck, func(t *testing.T) {
client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", routeToCheck, 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,27 +0,0 @@
package main
import (
"context"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/integration/internal/flows"
)
func TestForwardAuth(t *testing.T) {
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://fa-httpdetails.localhost.pomerium.io/by-user"),
flows.WithForwardAuth(true), flows.WithEmail("bob@dogs.test"), flows.WithGroups("user"))
if !assert.NoError(t, err, "unexpected http error") {
return
}
defer res.Body.Close()
assert.Equal(t, http.StatusOK, res.StatusCode)
}

View file

@ -1,120 +0,0 @@
package cluster
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// 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
}
}
// 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, 0o755)
if err != nil {
return nil, fmt.Errorf("error creating integration tests working directory: %w", err)
}
var bundle TLSCertsBundle
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, "trusted"), true, "*.localhost.notpomerium.io"},
{&bundle.Untrusted, filepath.Join(wd, "untrusted"), false, "*.localhost.pomerium.io"},
}
for _, generator := range generators {
err = os.MkdirAll(generator.caroot, 0o755)
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)
}
root := filepath.Join(generator.caroot, strings.ReplaceAll(generator.name, "*", "_wildcard"))
fileMap := map[string]*[]byte{
root + ".pem": &generator.certs.Cert,
root + "-client.pem": &generator.certs.Client.Cert,
root + "-key.pem": &generator.certs.Key,
root + "-client-key.pem": &generator.certs.Client.Key,
}
regenerate := false
for name := range fileMap {
if _, err := os.Stat(name); err != nil {
regenerate = true
}
}
if regenerate {
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)
}
}
for name, ptr := range fileMap {
*ptr, err = ioutil.ReadFile(name)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w",
name, err)
}
}
}
return &bundle, nil
}

View file

@ -1,57 +0,0 @@
// Package cluster is used to configure a kubernetes cluster for testing.
package cluster
import (
"net/http"
"net/http/cookiejar"
"github.com/pomerium/pomerium/internal/log"
"golang.org/x/net/publicsuffix"
)
// A Cluster is used to configure a kubernetes cluster.
type Cluster struct {
Transport *http.Transport
workingDir string
certsBundle *TLSCertsBundle
}
// New creates a new Cluster.
func New(workingDir string) *Cluster {
return &Cluster{
workingDir: workingDir,
}
}
// NewHTTPClient calls NewHTTPClientWithTransport with the default cluster transport.
func (cluster *Cluster) NewHTTPClient() *http.Client {
return cluster.NewHTTPClientWithTransport(cluster.Transport)
}
// NewHTTPClientWithTransport creates a new *http.Client, with a cookie jar, and a LocalRoundTripper
// which routes traffic to the nginx ingress controller.
func (cluster *Cluster) NewHTTPClientWithTransport(transport http.RoundTripper) *http.Client {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
panic(err)
}
return &http.Client{
Transport: &loggingRoundTripper{transport},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Jar: jar,
}
}
type loggingRoundTripper struct {
http.RoundTripper
}
func (rt *loggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) {
res, err = rt.RoundTripper.RoundTrip(req)
log.Debug(req.Context()).Str("method", req.Method).Str("url", req.URL.String()).Msg("http request")
return res, err
}

View file

@ -1,77 +0,0 @@
package cluster
import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"
"github.com/pomerium/pomerium/internal/log"
)
type cmdOption func(*exec.Cmd)
func withArgs(args ...string) cmdOption {
return func(cmd *exec.Cmd) {
cmd.Args = append([]string{"kubectl"}, args...)
}
}
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
}
}
func withStdout(w io.Writer) cmdOption {
return func(cmd *exec.Cmd) {
cmd.Stdout = w
}
}
func withWorkingDir(wd string) cmdOption {
return func(cmd *exec.Cmd) {
cmd.Dir = wd
}
}
func run(ctx context.Context, name string, options ...cmdOption) error {
cmd := commandContext(ctx, name)
for _, o := range options {
o(cmd)
}
if cmd.Stderr == nil {
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("failed to create stderr pipe for %s: %w", name, err)
}
go cmdLogger(ctx, stderr)
defer stderr.Close()
}
if cmd.Stdout == nil {
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to create stdout pipe for %s: %w", name, err)
}
go cmdLogger(ctx, stdout)
defer stdout.Close()
}
log.Debug(ctx).Strs("args", cmd.Args).Msgf("running %s", name)
return cmd.Run()
}
func cmdLogger(ctx context.Context, rdr io.Reader) {
s := bufio.NewScanner(rdr)
for s.Scan() {
log.Debug(ctx).Msg(s.Text())
}
}

View file

@ -1,25 +0,0 @@
//go:build linux
// +build linux
package cluster
import (
"context"
"os/exec"
"syscall"
"github.com/onsi/gocleanup"
)
func commandContext(ctx context.Context, name string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, name, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM,
}
gocleanup.Register(func() {
if cmd.Process != nil {
_ = cmd.Process.Kill()
}
})
return cmd
}

View file

@ -1,21 +0,0 @@
//go:build !linux
// +build !linux
package cluster
import (
"context"
"os/exec"
"github.com/onsi/gocleanup"
)
func commandContext(ctx context.Context, name string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, name, args...)
gocleanup.Register(func() {
if cmd.Process != nil {
_ = cmd.Process.Kill()
}
})
return cmd
}

View file

@ -1,239 +0,0 @@
package cluster
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/google/go-jsonnet"
"github.com/pomerium/pomerium/integration/internal/netutil"
"github.com/pomerium/pomerium/internal/log"
)
var requiredDeployments = []string{
"ingress-nginx/nginx-ingress-controller",
"default/httpdetails",
"default/openid",
"default/pomerium-authenticate",
"default/pomerium-authorize",
"default/pomerium-proxy",
}
// Setup configures the test cluster so that it is ready for the integration tests.
func (cluster *Cluster) Setup(ctx context.Context) error {
err := run(ctx, "kubectl", withArgs("cluster-info"))
if err != nil {
return fmt.Errorf("error running kubectl cluster-info: %w", err)
}
cluster.certsBundle, err = bootstrapCerts(ctx)
if err != nil {
return err
}
jsonsrc, err := cluster.generateManifests()
if err != nil {
return err
}
err = applyManifests(ctx, jsonsrc)
if err != nil {
return err
}
hostport, err := cluster.GetNodePortAddr(ctx, "ingress-nginx", "ingress-nginx-nodeport")
if err != nil {
return err
}
cluster.Transport = &http.Transport{
DialContext: netutil.NewLocalDialer((&net.Dialer{}), map[string]string{
"443": hostport,
}).DialContext,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
return nil
}
// GetNodePortAddr returns the node:port address for a NodePort kubernetes service.
func (cluster *Cluster) GetNodePortAddr(ctx context.Context, namespace, svcName string) (hostport string, err error) {
var buf bytes.Buffer
args := []string{
"get", "service", "--namespace", namespace, "--output", "json",
svcName,
}
err = run(ctx, "kubectl", withArgs(args...), withStdout(&buf))
if err != nil {
return "", fmt.Errorf("error getting service details with kubectl: %w", err)
}
var svcResult struct {
Spec struct {
Ports []struct {
Name string `json:"name"`
NodePort int `json:"nodePort"`
} `json:"ports"`
Selector map[string]string `json:"selector"`
} `json:"spec"`
}
err = json.Unmarshal(buf.Bytes(), &svcResult)
if err != nil {
return "", fmt.Errorf("error unmarshaling service details from kubectl: %w", err)
}
buf.Reset()
args = []string{"get", "pods", "--namespace", namespace, "--output", "json"}
var sel []string
for k, v := range svcResult.Spec.Selector {
sel = append(sel, k+"="+v)
}
args = append(args, "--selector", strings.Join(sel, ","))
err = run(ctx, "kubectl", withArgs(args...), withStdout(&buf))
if err != nil {
return "", fmt.Errorf("error getting pod details with kubectl: %w", err)
}
var podsResult struct {
Items []struct {
Status struct {
HostIP string `json:"hostIP"`
} `json:"status"`
} `json:"items"`
}
err = json.Unmarshal(buf.Bytes(), &podsResult)
if err != nil {
return "", fmt.Errorf("error unmarshaling pod details from kubectl (json=%s): %w", buf.String(), err)
}
var port string
for _, p := range svcResult.Spec.Ports {
if p.Name == "https" {
port = strconv.Itoa(p.NodePort)
}
}
if port == "" {
return "", fmt.Errorf("failed to find https port in kubectl service results (result=%v)", svcResult)
}
var hostIP string
for _, item := range podsResult.Items {
hostIP = item.Status.HostIP
}
if hostIP == "" {
return "", fmt.Errorf("failed to find host ip in kubectl pod results: %w", err)
}
return net.JoinHostPort(hostIP, port), nil
}
func (cluster *Cluster) generateManifests() (string, error) {
src, err := ioutil.ReadFile(filepath.Join(cluster.workingDir, "manifests", "manifests.jsonnet"))
if err != nil {
return "", fmt.Errorf("error reading manifest jsonnet src: %w", err)
}
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.Importer(&jsonnet.FileImporter{
JPaths: []string{filepath.Join(cluster.workingDir, "manifests")},
})
jsonsrc, err := vm.EvaluateSnippet("manifests.jsonnet", string(src))
if err != nil {
return "", fmt.Errorf("error evaluating jsonnet (filename=manifests.jsonnet): %w", err)
}
return jsonsrc, nil
}
func applyManifests(ctx context.Context, jsonsrc string) error {
err := run(ctx, "kubectl", withArgs("apply", "-f", "-"), withStdin(strings.NewReader(jsonsrc)))
if err != nil {
return fmt.Errorf("error applying manifests: %w", err)
}
log.Info(ctx).Msg("waiting for deployments to come up")
ctx, clearTimeout := context.WithTimeout(ctx, 15*time.Minute)
defer clearTimeout()
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for {
var buf bytes.Buffer
err = run(ctx, "kubectl", withArgs("get", "deployments", "--all-namespaces", "--output", "json"),
withStdout(&buf))
if err != nil {
return fmt.Errorf("error polling for deployment status: %w", err)
}
var results struct {
Items []struct {
Metadata struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
} `json:"metadata"`
Status struct {
AvailableReplicas int `json:"availableReplicas"`
} `json:"status"`
} `json:"items"`
}
err = json.Unmarshal(buf.Bytes(), &results)
if err != nil {
return fmt.Errorf("error unmarshaling kubectl results: %w", err)
}
byName := map[string]int{}
for _, item := range results.Items {
byName[item.Metadata.Namespace+"/"+item.Metadata.Name] = item.Status.AvailableReplicas
}
done := true
for _, dep := range requiredDeployments {
if byName[dep] < 1 {
done = false
log.Warn(ctx).Str("deployment", dep).Msg("deployment is not ready yet")
}
}
if done {
break
}
select {
case <-ticker.C:
case <-ctx.Done():
return ctx.Err()
}
<-ticker.C
}
time.Sleep(time.Minute)
log.Info(ctx).Msg("all deployments are ready")
return nil
}

View file

@ -1,267 +0,0 @@
// Package flows has helper functions for working with pomerium end-user use-case flows.
package flows
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"time"
"github.com/pomerium/pomerium/integration/internal/forms"
"github.com/pomerium/pomerium/internal/urlutil"
)
const (
authenticateHostname = "authenticate.localhost.pomerium.io"
openidHostname = "openid.localhost.pomerium.io"
pomeriumCallbackPath = "/.pomerium/callback/"
pomeriumAPIPath = "/.pomerium/api/v1/login"
)
type authenticateConfig struct {
email string
groups []string
tokenExpiration time.Duration
apiPath string
forwardAuth bool
}
// An AuthenticateOption is an option for authentication.
type AuthenticateOption func(cfg *authenticateConfig)
func getAuthenticateConfig(options ...AuthenticateOption) *authenticateConfig {
cfg := &authenticateConfig{
tokenExpiration: time.Hour * 24,
}
for _, option := range options {
if option != nil {
option(cfg)
}
}
return cfg
}
// WithForwardAuth enables/disables forward auth.
func WithForwardAuth(fa bool) AuthenticateOption {
return func(cfg *authenticateConfig) {
cfg.forwardAuth = fa
}
}
// WithEmail sets the email to use.
func WithEmail(email string) AuthenticateOption {
return func(cfg *authenticateConfig) {
cfg.email = email
}
}
// WithGroups sets the groups to use.
func WithGroups(groups ...string) AuthenticateOption {
return func(cfg *authenticateConfig) {
cfg.groups = groups
}
}
// WithTokenExpiration sets the token expiration.
func WithTokenExpiration(tokenExpiration time.Duration) AuthenticateOption {
return func(cfg *authenticateConfig) {
cfg.tokenExpiration = tokenExpiration
}
}
// WithAPI tells authentication to use API authentication flow.
func WithAPI() AuthenticateOption {
return func(cfg *authenticateConfig) {
cfg.apiPath = pomeriumAPIPath
}
}
// Authenticate submits a request to a URL, expects a redirect to authenticate and then openid and logs in.
// Finally it expects to redirect back to the original page.
func Authenticate(ctx context.Context, client *http.Client, url *url.URL, options ...AuthenticateOption) (*http.Response, error) {
cfg := getAuthenticateConfig(options...)
originalHostname := url.Hostname()
var err error
// Serve a local callback for programmatic redirect flow
srv := httptest.NewUnstartedServer(http.RedirectHandler(url.String(), http.StatusFound))
defer srv.Close()
if cfg.apiPath != "" {
srv.Start()
apiLogin := url
q := apiLogin.Query()
q.Set(urlutil.QueryRedirectURI, srv.URL)
apiLogin.RawQuery = q.Encode()
apiLogin.Path = cfg.apiPath
req, err := http.NewRequestWithContext(ctx, "GET", apiLogin.String(), nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, err
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
defer res.Body.Close()
url, err = url.Parse(string(bodyBytes))
if err != nil {
return nil, err
}
}
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
if err != nil {
return nil, err
}
var res *http.Response
// (1) redirect to authenticate
for req.URL.Hostname() == originalHostname {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", authenticateHostname, err)
}
}
// (2) redirect to openid
for req.URL.Hostname() == authenticateHostname {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", openidHostname, err)
}
}
// (3) submit the form
for req.URL.Hostname() == openidHostname {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
forms := forms.Parse(res.Body)
if len(forms) > 0 {
f := forms[0]
f.Inputs["email"] = cfg.email
if len(cfg.groups) > 0 {
f.Inputs["groups"] = strings.Join(cfg.groups, ",")
}
f.Inputs["token_expiration"] = strconv.Itoa(int(cfg.tokenExpiration.Seconds()))
req, err = f.NewRequestWithContext(ctx, req.URL)
if err != nil {
return nil, err
}
} else {
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", openidHostname, err)
}
}
}
// (4) back to authenticate
for req.URL.Hostname() == authenticateHostname {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", originalHostname, err)
}
}
// (5) finally to callback
if !cfg.forwardAuth && req.URL.Path != pomeriumCallbackPath {
return nil, fmt.Errorf("expected to redirect back to %s, but got %s", pomeriumCallbackPath, req.URL.String())
}
if cfg.forwardAuth {
for {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 302 {
break
}
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", originalHostname, err)
}
}
return res, err
}
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", originalHostname, err)
}
// Programmatic flow: Follow redirect from local callback
if cfg.apiPath != "" {
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", srv.URL, err)
}
res, err = client.Do(req)
if err != nil {
return nil, err
}
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", originalHostname, err)
}
}
return client.Do(req)
}
func requestFromRedirectResponse(ctx context.Context, res *http.Response, req *http.Request) (*http.Request, error) {
if res.Header.Get("Location") == "" {
return nil, fmt.Errorf("no location header found in response headers")
}
location, err := url.Parse(res.Header.Get("Location"))
if err != nil {
return nil, err
}
location = req.URL.ResolveReference(location)
newreq, err := http.NewRequestWithContext(ctx, "GET", location.String(), nil)
if err != nil {
return nil, err
}
return newreq, nil
}

View file

@ -1,93 +0,0 @@
// Package forms has helper functions for working with HTML forms.
package forms
import (
"context"
"io"
"net/http"
"net/url"
"strings"
"golang.org/x/net/html"
)
// A Form represents an HTML form.
type Form struct {
Action string
Method string
Inputs map[string]string
}
// Parse parses all the forms in an HTML document.
func Parse(r io.Reader) []Form {
root, err := html.Parse(r)
if err != nil {
return nil
}
var forms []Form
var currentForm *Form
var visit func(*html.Node)
visit = func(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "form" {
currentForm = &Form{Action: "", Method: "GET", Inputs: make(map[string]string)}
for _, attr := range node.Attr {
switch attr.Key {
case "action":
currentForm.Action = attr.Val
case "method":
currentForm.Method = strings.ToUpper(attr.Val)
}
}
}
if currentForm != nil && node.Type == html.ElementNode && node.Data == "input" {
var name, value string
for _, attr := range node.Attr {
switch attr.Key {
case "name":
name = attr.Val
case "value":
value = attr.Val
}
}
if name != "" {
currentForm.Inputs[name] = value
}
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
visit(c)
}
if node.Type == html.ElementNode && node.Data == "form" {
if currentForm != nil {
forms = append(forms, *currentForm)
}
currentForm = nil
}
}
visit(root)
return forms
}
// NewRequestWithContext creates a new request from the form details.
func (f *Form) NewRequestWithContext(ctx context.Context, baseURL *url.URL) (*http.Request, error) {
actionURL, err := url.Parse(f.Action)
if err != nil {
return nil, err
}
actionURL = baseURL.ResolveReference(actionURL)
vs := make(url.Values)
for k, v := range f.Inputs {
vs.Set(k, v)
}
req, err := http.NewRequestWithContext(ctx, f.Method, actionURL.String(), strings.NewReader(vs.Encode()))
if err != nil {
return nil, err
}
// TODO: handle multipart forms
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return req, nil
}

View file

@ -1,49 +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,24 +1,12 @@
package main
import (
"context"
"flag"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/onsi/gocleanup"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/pomerium/pomerium/integration/internal/cluster"
)
var (
mainCtx context.Context
testcluster *cluster.Cluster
)
func TestMain(m *testing.M) {
@ -31,26 +19,6 @@ func TestMain(m *testing.M) {
log.Logger = log.Logger.Level(zerolog.InfoLevel)
}
mainCtx = context.Background()
var cancel func()
mainCtx, cancel = context.WithCancel(mainCtx)
var clearTimeout func()
mainCtx, clearTimeout = context.WithTimeout(mainCtx, time.Minute*10)
defer clearTimeout()
testcluster = cluster.New(getBaseDir())
if err := testcluster.Setup(mainCtx); err != nil {
log.Fatal().Err(err).Send()
}
status := m.Run()
cancel()
gocleanup.Cleanup()
os.Exit(status)
}
// getBaseDir returns the directory that main_test resides in
func getBaseDir() string {
_, file, _, _ := runtime.Caller(0) //nolint
return filepath.Dir(file)
}

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

@ -1,143 +0,0 @@
{
apiVersion: 'v1',
kind: 'List',
items: [
{
apiVersion: 'v1',
kind: 'Namespace',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'ingress-nginx' },
},
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-configuration', namespace: 'ingress-nginx' },
},
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'tcp-services', namespace: 'ingress-nginx' },
},
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'udp-services', namespace: 'ingress-nginx' },
},
{
apiVersion: 'v1',
kind: 'ServiceAccount',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-ingress-serviceaccount', namespace: 'ingress-nginx' },
},
{
apiVersion: 'rbac.authorization.k8s.io/v1beta1',
kind: 'ClusterRole',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-ingress-clusterrole' },
rules: [{ apiGroups: [''], resources: ['configmaps', 'endpoints', 'nodes', 'pods', 'secrets'], verbs: ['list', 'watch'] }, { apiGroups: [''], resources: ['nodes'], verbs: ['get'] }, { apiGroups: [''], resources: ['services'], verbs: ['get', 'list', 'watch'] }, { apiGroups: [''], resources: ['events'], verbs: ['create', 'patch'] }, { apiGroups: ['extensions', 'networking.k8s.io'], resources: ['ingresses'], verbs: ['get', 'list', 'watch'] }, { apiGroups: ['extensions', 'networking.k8s.io'], resources: ['ingresses/status'], verbs: ['update'] }],
},
{
apiVersion: 'rbac.authorization.k8s.io/v1beta1',
kind: 'Role',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-ingress-role', namespace: 'ingress-nginx' },
rules: [{ apiGroups: [''], resources: ['configmaps', 'pods', 'secrets', 'namespaces'], verbs: ['get'] }, { apiGroups: [''], resourceNames: ['ingress-controller-leader-nginx'], resources: ['configmaps'], verbs: ['get', 'update'] }, { apiGroups: [''], resources: ['configmaps'], verbs: ['create'] }, { apiGroups: [''], resources: ['endpoints'], verbs: ['get'] }],
},
{
apiVersion: 'rbac.authorization.k8s.io/v1beta1',
kind: 'RoleBinding',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-ingress-role-nisa-binding', namespace: 'ingress-nginx' },
roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'Role', name: 'nginx-ingress-role' },
subjects: [{ kind: 'ServiceAccount', name: 'nginx-ingress-serviceaccount', namespace: 'ingress-nginx' }],
},
{
apiVersion: 'rbac.authorization.k8s.io/v1beta1',
kind: 'ClusterRoleBinding',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-ingress-clusterrole-nisa-binding' },
roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'nginx-ingress-clusterrole' },
subjects: [{ kind: 'ServiceAccount', name: 'nginx-ingress-serviceaccount', namespace: 'ingress-nginx' }],
},
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'nginx-ingress-controller', namespace: 'ingress-nginx' },
spec: {
replicas: 1,
selector: { matchLabels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' } },
template: {
metadata: { annotations: { 'prometheus.io/port': '10254', 'prometheus.io/scrape': 'true' }, labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' } },
spec: {
containers: [{
name: 'nginx-ingress-controller',
image: 'quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0',
args: [
'/nginx-ingress-controller',
'--configmap=$(POD_NAMESPACE)/nginx-configuration',
'--tcp-services-configmap=$(POD_NAMESPACE)/tcp-services',
'--udp-services-configmap=$(POD_NAMESPACE)/udp-services',
'--publish-service=$(POD_NAMESPACE)/ingress-nginx',
'--annotations-prefix=nginx.ingress.kubernetes.io',
'--v=2',
],
env: [
{ name: 'POD_NAME', valueFrom: { fieldRef: { fieldPath: 'metadata.name' } } },
{ name: 'POD_NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } } },
],
lifecycle: { preStop: { exec: { command: ['/wait-shutdown'] } } },
livenessProbe: { failureThreshold: 3, httpGet: { path: '/healthz', port: 10254, scheme: 'HTTP' }, initialDelaySeconds: 10, periodSeconds: 10, successThreshold: 1, timeoutSeconds: 10 },
ports: [{ containerPort: 80, name: 'http', protocol: 'TCP' }, { containerPort: 443, name: 'https', protocol: 'TCP' }],
readinessProbe: { failureThreshold: 3, httpGet: { path: '/healthz', port: 10254, scheme: 'HTTP' }, periodSeconds: 10, successThreshold: 1, timeoutSeconds: 10 },
securityContext: { allowPrivilegeEscalation: true, capabilities: { add: ['NET_BIND_SERVICE'], drop: ['ALL'] }, runAsUser: 101 },
}],
nodeSelector: { 'kubernetes.io/os': 'linux' },
serviceAccountName: 'nginx-ingress-serviceaccount',
terminationGracePeriodSeconds: 300,
},
},
},
},
{
apiVersion: 'v1',
kind: 'LimitRange',
metadata: { labels: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' }, name: 'ingress-nginx', namespace: 'ingress-nginx' },
spec: { limits: [{ min: { cpu: '100m', memory: '90Mi' }, type: 'Container' }] },
},
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'ingress-nginx',
name: 'ingress-nginx',
labels: {
'app.kubernetes.io/name': 'ingress-nginx',
'app.kubernetes.io/part-of': 'ingress-nginx',
},
},
spec: {
type: 'ClusterIP',
clusterIP: '10.96.1.1',
selector: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' },
ports: [
{ name: 'http', port: 80, protocol: 'TCP', targetPort: 'http' },
{ name: 'https', port: 443, protocol: 'TCP', targetPort: 'https' },
],
},
},
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'ingress-nginx',
name: 'ingress-nginx-nodeport',
labels: {
'app.kubernetes.io/name': 'ingress-nginx',
'app.kubernetes.io/part-of': 'ingress-nginx',
},
},
spec: {
type: 'NodePort',
selector: { 'app.kubernetes.io/name': 'ingress-nginx', 'app.kubernetes.io/part-of': 'ingress-nginx' },
ports: [
{ name: 'http', port: 80, protocol: 'TCP', targetPort: 'http', nodePort: 30080 },
{ name: 'https', port: 443, protocol: 'TCP', targetPort: 'https', nodePort: 30443 },
],
},
},
],
}

View file

@ -1,508 +0,0 @@
local tls = import './tls.libsonnet';
local PomeriumPolicy = function() std.flattenArrays(
[
[
// tls_skip_verify
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-skip-verify-enabled',
tls_skip_verify: true,
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-skip-verify-disabled',
tls_skip_verify: false,
allow_public_unauthenticated_access: true,
},
// tls_server_name
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://wrongly-named-httpdetails.default.svc.cluster.local',
path: '/tls-server-name-enabled',
tls_server_name: 'httpdetails.localhost.notpomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://wrongly-named-httpdetails.default.svc.cluster.local',
path: '/tls-server-name-disabled',
allow_public_unauthenticated_access: true,
},
// tls_custom_certificate_authority
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-custom-ca-enabled',
tls_custom_ca: std.base64(tls.untrusted.ca),
tls_server_name: 'httpdetails.localhost.pomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails.default.svc.cluster.local',
path: '/tls-custom-ca-disabled',
allow_public_unauthenticated_access: true,
},
// tls_client_cert
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local',
path: '/tls-client-cert-enabled',
tls_client_cert: std.base64(tls.trusted.client.cert),
tls_client_key: std.base64(tls.trusted.client.key),
tls_server_name: 'httpdetails.localhost.pomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'http://httpdetails.localhost.pomerium.io',
to: 'https://trusted-mtls-httpdetails.default.svc.cluster.local',
path: '/tls-client-cert-disabled',
allow_public_unauthenticated_access: true,
},
],
] + [
[
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-domain',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_domains: ['dogs.test'],
pass_identity_headers: false,
},
{
from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-user',
to: 'http://' + domain + '.default.svc.cluster.local',
allowed_users: ['bob@dogs.test'],
pass_identity_headers: true,
},
{
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',
},
remove_request_headers: ['X-Custom-Request-Header-To-Remove'],
},
{
from: 'http://restricted-' + domain + '.localhost.pomerium.io',
to: 'http://' + domain + '.default.svc.cluster.local',
},
]
for domain in ['httpdetails', 'fa-httpdetails', 'ws-echo']
] + [
[
{
from: 'http://enabled-ws-echo.localhost.pomerium.io',
to: 'http://ws-echo.default.svc.cluster.local',
allow_public_unauthenticated_access: true,
allow_websockets: true,
},
{
from: 'http://disabled-ws-echo.localhost.pomerium.io',
to: 'http://ws-echo.default.svc.cluster.local',
allow_public_unauthenticated_access: true,
},
],
]
);
local PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), '')));
local PomeriumTLSSecret = function(name) {
apiVersion: 'v1',
kind: 'Secret',
type: 'kubernetes.io/tls',
metadata: {
namespace: 'default',
name: 'pomerium-' + name + '-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),
},
};
local PomeriumConfigMap = function() {
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
namespace: 'default',
name: 'pomerium',
labels: {
'app.kubernetes.io/part-of': 'pomerium',
},
},
data: {
ADDRESS: ':443',
GRPC_ADDRESS: ':5443',
DEBUG: 'true',
LOG_LEVEL: 'debug',
AUTHENTICATE_SERVICE_URL: 'https://authenticate.localhost.pomerium.io',
AUTHENTICATE_CALLBACK_PATH: '/oauth2/callback',
AUTHORIZE_SERVICE_URL: 'https://authorize.default.svc.cluster.local:5443',
DATABROKER_SERVICE_URL: 'https://databroker.default.svc.cluster.local:5443',
FORWARD_AUTH_URL: 'https://forward-authenticate.localhost.pomerium.io',
HEADERS: 'X-Frame-Options:SAMEORIGIN',
JWT_CLAIMS_HEADERS: 'email',
SHARED_SECRET: 'Wy+c0uSuIM0yGGXs82MBwTZwRiZ7Ki2T0LANnmzUtkI=',
COOKIE_SECRET: 'eZ91a/j9fhgki9zPDU5zHdQWX4io89pJanChMVa5OoM=',
CERTIFICATE: std.base64(tls.trusted.cert),
CERTIFICATE_KEY: std.base64(tls.trusted.key),
CERTIFICATE_AUTHORITY: std.base64(tls.trusted.ca),
OVERRIDE_CERTIFICATE_NAME: 'pomerium.localhost.pomerium.io',
IDP_PROVIDER: 'oidc',
IDP_PROVIDER_URL: 'https://openid.localhost.pomerium.io',
IDP_CLIENT_ID: 'pomerium-authenticate',
IDP_CLIENT_SECRET: 'pomerium-authenticate-secret',
IDP_SERVICE_ACCOUNT: 'pomerium-service-account',
POLICY: std.base64(std.manifestYamlDoc(PomeriumPolicy())),
},
};
local PomeriumDeployment = function(svc) {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
namespace: 'default',
name: 'pomerium-' + svc,
labels: {
app: 'pomerium-' + svc,
'app.kubernetes.io/part-of': 'pomerium',
},
},
spec: {
replicas: 1,
selector: {
matchLabels: {
app: 'pomerium-' + svc,
},
},
template: {
metadata: {
labels: {
app: 'pomerium-' + svc,
'app.kubernetes.io/part-of': 'pomerium',
},
annotations: {
'policy-version': PomeriumPolicyHash,
},
},
spec: {
hostAliases: [{
ip: '10.96.1.1',
hostnames: [
'openid.localhost.pomerium.io',
'authenticate.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 [],
containers: [{
name: 'pomerium-' + svc,
image: 'pomerium/pomerium:dev',
imagePullPolicy: 'IfNotPresent',
envFrom: [{
configMapRef: { name: 'pomerium' },
}],
env: [{
name: 'SERVICES',
value: svc,
}],
ports: [
{ name: 'https', containerPort: 443 },
{ name: 'grpc', containerPort: 5443 },
],
volumeMounts: [
{
name: 'outgoing-certs',
mountPath: '/etc/ssl/certs',
},
],
}],
volumes: [
{
name: 'trusted-incoming-certs',
secret: {
secretName: 'pomerium-trusted-tls',
},
},
{
name: 'wrongly-named-incoming-certs',
secret: {
secretName: 'pomerium-wrongly-named-tls',
},
},
{
name: 'outgoing-certs',
emptyDir: {},
},
],
},
},
},
};
local PomeriumService = function(svc) {
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'default',
name: svc,
labels: {
app: 'pomerium-' + svc,
'app.kubernetes.io/part-of': 'pomerium',
},
},
spec: {
ports: [
{
name: 'https',
port: 443,
targetPort: 'https',
},
{
name: 'grpc',
port: 5443,
targetPort: 'grpc',
},
],
selector: {
app: 'pomerium-' + svc,
},
},
};
local PomeriumNodePortServce = function() {
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'default',
name: 'pomerium-proxy-nodeport',
labels: {
app: 'pomerium-proxy',
'app.kubernetes.io/part-of': 'pomerium',
},
},
spec: {
type: 'NodePort',
ports: [
{ name: 'https', port: 443, protocol: 'TCP', targetPort: 'https', nodePort: 31443 },
],
selector: {
app: 'pomerium-proxy',
},
},
};
local PomeriumIngress = function() {
local proxyHosts = [
'forward-authenticate.localhost.pomerium.io',
'httpecho.localhost.pomerium.io',
'httpdetails.localhost.pomerium.io',
'restricted-httpdetails.localhost.pomerium.io',
'enabled-ws-echo.localhost.pomerium.io',
'disabled-ws-echo.localhost.pomerium.io',
],
apiVersion: 'extensions/v1beta1',
kind: 'Ingress',
metadata: {
namespace: 'default',
name: 'pomerium',
annotations: {
'kubernetes.io/ingress.class': 'nginx',
'nginx.ingress.kubernetes.io/backend-protocol': 'HTTPS',
'nginx.ingress.kubernetes.io/proxy-buffer-size': '16k',
},
},
spec: {
tls: [
{
hosts: [
'authenticate.localhost.pomerium.io',
] + proxyHosts,
secretName: 'pomerium-trusted-tls',
},
],
rules: [
{
host: 'authenticate.localhost.pomerium.io',
http: {
paths: [
{
path: '/',
backend: {
serviceName: 'authenticate',
servicePort: 'https',
},
},
],
},
},
] + [{
host: host,
http: {
paths: [{
path: '/',
backend: {
serviceName: 'proxy',
servicePort: 'https',
},
}],
},
} for host in proxyHosts],
},
};
local PomeriumForwardAuthIngress = function() {
apiVersion: 'extensions/v1beta1',
kind: 'Ingress',
metadata: {
namespace: 'default',
name: 'pomerium-fa',
annotations: {
'kubernetes.io/ingress.class': 'nginx',
'nginx.ingress.kubernetes.io/auth-url': 'https://forward-authenticate.localhost.pomerium.io/verify?uri=$scheme://$host$request_uri',
'nginx.ingress.kubernetes.io/auth-signin': 'https://forward-authenticate.localhost.pomerium.io/?uri=$scheme://$host$request_uri',
'nginx.ingress.kubernetes.io/proxy-buffer-size': '16k',
},
},
spec: {
tls: [
{
hosts: [
'fa-httpdetails.localhost.pomerium.io',
],
secretName: 'pomerium-trusted-tls',
},
],
rules: [
{
host: 'fa-httpdetails.localhost.pomerium.io',
http: {
paths: [
{
path: '/.pomerium/',
backend: {
serviceName: 'proxy',
servicePort: 'https',
},
},
{
path: '/',
backend: {
serviceName: 'httpdetails',
servicePort: 'http',
},
},
],
},
},
],
},
};
{
apiVersion: 'v1',
kind: 'List',
items: [
PomeriumConfigMap(),
PomeriumTLSSecret('trusted'),
PomeriumTLSSecret('untrusted'),
PomeriumTLSSecret('wrongly-named'),
PomeriumService('authenticate'),
PomeriumDeployment('authenticate'),
PomeriumService('authorize'),
PomeriumDeployment('authorize'),
PomeriumService('databroker'),
PomeriumDeployment('databroker'),
PomeriumService('proxy'),
PomeriumDeployment('proxy'),
PomeriumNodePortServce(),
PomeriumIngress(),
PomeriumForwardAuthIngress(),
],
}

View file

@ -1,106 +0,0 @@
local Service = function() {
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'default',
name: 'openid',
labels: {
app: 'openid',
'app.kubernetes.io/part-of': 'openid',
},
},
spec: {
selector: { app: 'openid' },
ports: [
{
name: 'http',
port: 80,
targetPort: 'http',
},
],
},
};
local Deployment = function() {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
namespace: 'default',
name: 'openid',
labels: {
app: 'openid',
'app.kubernetes.io/part-of': 'openid',
},
},
spec: {
replicas: 1,
selector: { matchLabels: { app: 'openid' } },
template: {
metadata: {
labels: {
app: 'openid',
'app.kubernetes.io/part-of': 'openid',
},
},
spec: {
containers: [{
name: 'openid',
image: 'quay.io/calebdoxsey/reference-openid-provider:latest',
imagePullPolicy: 'IfNotPresent',
ports: [
{ name: 'http', containerPort: 6080 },
],
}],
},
},
},
};
local Ingress = function() {
apiVersion: 'extensions/v1beta1',
kind: 'Ingress',
metadata: {
namespace: 'default',
name: 'openid',
annotations: {
'kubernetes.io/ingress.class': 'nginx',
'nginx.ingress.kubernetes.io/backend-protocol': 'HTTP',
},
},
spec: {
tls: [
{
hosts: [
'openid.localhost.pomerium.io',
],
secretName: 'pomerium-trusted-tls',
},
],
rules: [
{
host: 'openid.localhost.pomerium.io',
http: {
paths: [
{
path: '/',
backend: {
serviceName: 'openid',
servicePort: 'http',
},
},
],
},
},
],
},
};
{
apiVersion: 'v1',
kind: 'List',
items: [
Service(),
Deployment(),
Ingress(),
],
}

View file

@ -1,29 +0,0 @@
{
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'),
},
},
}

View file

@ -1,10 +0,0 @@
local backends = import './lib/backends.libsonnet';
local nginxIngressController = import './lib/nginx-ingress-controller.libsonnet';
local pomerium = import './lib/pomerium.libsonnet';
local openid = import './lib/reference-openid-provider.libsonnet';
{
apiVersion: 'v1',
kind: 'List',
items: nginxIngressController.items + pomerium.items + openid.items + backends.items,
}

View file

@ -1,474 +0,0 @@
package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/integration/internal/flows"
"github.com/pomerium/pomerium/integration/internal/netutil"
)
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 TestRemoveRequestHeaders(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)
}
req.Header.Add("X-Custom-Request-Header-To-Remove", "foo")
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
}
_, exist := result.Headers["X-Custom-Request-Header-To-Remove"]
assert.False(t, exist, "expected X-Custom-Request-Header-To-Remove not to be present.")
}
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.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, 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.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, 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.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, 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.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
})
}
func TestSNIMismatch(t *testing.T) {
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
// Browsers will coalesce connections for the same IP address and TLS certificate
// even if the request was made to different domain names. We need to support this
// so this test makes a request with an incorrect TLS server name to make sure it
// gets routed properly
hostport, err := testcluster.GetNodePortAddr(ctx, "default", "pomerium-proxy-nodeport")
if err != nil {
t.Fatal(err)
}
client := testcluster.NewHTTPClientWithTransport(&http.Transport{
DialContext: netutil.NewLocalDialer((&net.Dialer{}), map[string]string{
"443": hostport,
}).DialContext,
TLSClientConfig: &tls.Config{
ServerName: "ws-echo.localhost.pomerium.io",
},
})
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/ping", 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)
}
func TestAttestationJWT(t *testing.T) {
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-user"),
nil, flows.WithEmail("bob@dogs.test"), flows.WithGroups("user"))
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.NotEmpty(t, result.Headers["X-Pomerium-Jwt-Assertion"], "Expected JWT assertion")
}
func TestPassIdentityHeaders(t *testing.T) {
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
tests := []struct {
name string
path string
wantExist bool
}{
{"enabled", "/by-user", true},
{"disabled", "/by-domain", false},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io"+tc.path),
nil, flows.WithEmail("bob@dogs.test"), flows.WithGroups("user"))
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
}
for _, header := range []string{"X-Pomerium-Jwt-Assertion", "X-Pomerium-Claim-Email"} {
_, exist := result.Headers[header]
assert.True(t, exist == tc.wantExist, fmt.Sprintf("Header %s, expected: %v, got: %v", header, tc.wantExist, exist))
}
})
}
}