diff --git a/go.mod b/go.mod index b5010887c..b8dfe1ae4 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 10741aa92..eadb23909 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/integration/README.md b/integration/README.md deleted file mode 100644 index d65c5b82e..000000000 --- a/integration/README.md +++ /dev/null @@ -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. diff --git a/integration/authorization_test.go b/integration/authorization_test.go deleted file mode 100644 index a967e9829..000000000 --- a/integration/authorization_test.go +++ /dev/null @@ -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...) -} diff --git a/integration/backends/httpdetails/go.mod b/integration/backends/httpdetails/go.mod deleted file mode 100644 index b0ad06f39..000000000 --- a/integration/backends/httpdetails/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/pomerium/pomerium/integration/backends/httpdetails - -go 1.14 diff --git a/integration/backends/httpdetails/main.go b/integration/backends/httpdetails/main.go deleted file mode 100644 index 64b304a2a..000000000 --- a/integration/backends/httpdetails/main.go +++ /dev/null @@ -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) -} diff --git a/integration/backends/ws-echo/go.mod b/integration/backends/ws-echo/go.mod deleted file mode 100644 index e4f509383..000000000 --- a/integration/backends/ws-echo/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/pomerium/pomerium/integration/backends/ws-echo - -go 1.14 - -require github.com/gorilla/websocket v1.4.2 diff --git a/integration/backends/ws-echo/go.sum b/integration/backends/ws-echo/go.sum deleted file mode 100644 index 85efffd99..000000000 --- a/integration/backends/ws-echo/go.sum +++ /dev/null @@ -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= diff --git a/integration/backends/ws-echo/main.go b/integration/backends/ws-echo/main.go deleted file mode 100644 index f928a7d30..000000000 --- a/integration/backends/ws-echo/main.go +++ /dev/null @@ -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 - } - } -} diff --git a/integration/control_plane_test.go b/integration/control_plane_test.go deleted file mode 100644 index 0bbc6bad5..000000000 --- a/integration/control_plane_test.go +++ /dev/null @@ -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") - }) - } - } -} diff --git a/integration/forward_auth_test.go b/integration/forward_auth_test.go deleted file mode 100644 index 9a9eb6bad..000000000 --- a/integration/forward_auth_test.go +++ /dev/null @@ -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) -} diff --git a/integration/internal/cluster/certs.go b/integration/internal/cluster/certs.go deleted file mode 100644 index dcdea65d1..000000000 --- a/integration/internal/cluster/certs.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/cluster/cluster.go b/integration/internal/cluster/cluster.go deleted file mode 100644 index 958f53e04..000000000 --- a/integration/internal/cluster/cluster.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/cluster/cmd.go b/integration/internal/cluster/cmd.go deleted file mode 100644 index 5c1080429..000000000 --- a/integration/internal/cluster/cmd.go +++ /dev/null @@ -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()) - } -} diff --git a/integration/internal/cluster/cmd_linux.go b/integration/internal/cluster/cmd_linux.go deleted file mode 100644 index 60a0e2c1b..000000000 --- a/integration/internal/cluster/cmd_linux.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/cluster/cmd_notlinux.go b/integration/internal/cluster/cmd_notlinux.go deleted file mode 100644 index af8633a6d..000000000 --- a/integration/internal/cluster/cmd_notlinux.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/cluster/setup.go b/integration/internal/cluster/setup.go deleted file mode 100644 index 0ee0a5d8c..000000000 --- a/integration/internal/cluster/setup.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/flows/flows.go b/integration/internal/flows/flows.go deleted file mode 100644 index 36ef523c4..000000000 --- a/integration/internal/flows/flows.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/forms/forms.go b/integration/internal/forms/forms.go deleted file mode 100644 index 2161106e4..000000000 --- a/integration/internal/forms/forms.go +++ /dev/null @@ -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 -} diff --git a/integration/internal/netutil/netutil.go b/integration/internal/netutil/netutil.go deleted file mode 100644 index 6096bd61d..000000000 --- a/integration/internal/netutil/netutil.go +++ /dev/null @@ -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 -} diff --git a/integration/main_test.go b/integration/main_test.go index 4502641e8..22289fe86 100644 --- a/integration/main_test.go +++ b/integration/main_test.go @@ -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) -} diff --git a/integration/manifests/lib/backends.libsonnet b/integration/manifests/lib/backends.libsonnet deleted file mode 100644 index 0f004de95..000000000 --- a/integration/manifests/lib/backends.libsonnet +++ /dev/null @@ -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), - ], - ], - ), -} diff --git a/integration/manifests/lib/nginx-ingress-controller.libsonnet b/integration/manifests/lib/nginx-ingress-controller.libsonnet deleted file mode 100644 index 018de0fd6..000000000 --- a/integration/manifests/lib/nginx-ingress-controller.libsonnet +++ /dev/null @@ -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 }, - ], - }, - }, - ], -} diff --git a/integration/manifests/lib/pomerium.libsonnet b/integration/manifests/lib/pomerium.libsonnet deleted file mode 100644 index ab47622f3..000000000 --- a/integration/manifests/lib/pomerium.libsonnet +++ /dev/null @@ -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(), - ], -} diff --git a/integration/manifests/lib/reference-openid-provider.libsonnet b/integration/manifests/lib/reference-openid-provider.libsonnet deleted file mode 100644 index 2d5ced32b..000000000 --- a/integration/manifests/lib/reference-openid-provider.libsonnet +++ /dev/null @@ -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(), - ], -} diff --git a/integration/manifests/lib/tls.libsonnet b/integration/manifests/lib/tls.libsonnet deleted file mode 100644 index 707967865..000000000 --- a/integration/manifests/lib/tls.libsonnet +++ /dev/null @@ -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'), - }, - }, -} diff --git a/integration/manifests/manifests.jsonnet b/integration/manifests/manifests.jsonnet deleted file mode 100644 index 1ab57182d..000000000 --- a/integration/manifests/manifests.jsonnet +++ /dev/null @@ -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, -} diff --git a/integration/policy_test.go b/integration/policy_test.go deleted file mode 100644 index 50dadb61a..000000000 --- a/integration/policy_test.go +++ /dev/null @@ -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)) - } - }) - } -}