mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-14 17:47:54 +02:00
integration: remove tests (#2514)
This commit is contained in:
parent
c7bf038f41
commit
9aad155e1a
28 changed files with 0 additions and 2837 deletions
3
go.mod
3
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
|
||||
|
|
6
go.sum
6
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=
|
||||
|
|
|
@ -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.
|
|
@ -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...)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module github.com/pomerium/pomerium/integration/backends/httpdetails
|
||||
|
||||
go 1.14
|
|
@ -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)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
module github.com/pomerium/pomerium/integration/backends/ws-echo
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/gorilla/websocket v1.4.2
|
|
@ -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=
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
],
|
||||
),
|
||||
}
|
|
@ -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 },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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(),
|
||||
],
|
||||
}
|
|
@ -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(),
|
||||
],
|
||||
}
|
|
@ -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'),
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue