integration: add single-cluster integration tests (#2516)

* integration: add single-cluster integration tests

* remove kind load
This commit is contained in:
Caleb Doxsey 2021-08-24 15:35:05 -06:00 committed by GitHub
parent f5a558d4a0
commit 48cd10d46b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 7455 additions and 31 deletions

View file

@ -96,9 +96,12 @@ jobs:
integration:
strategy:
fail-fast: false
matrix:
go-version: [1.17.x]
platform: [ubuntu-latest]
deployment: [single]
idp: [auth0, azure, github, gitlab, google, oidc, okta, onelogin, ping]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/setup-go@v2
@ -118,27 +121,21 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: install mkcert
if: runner.os == 'Linux'
run: |
#!/bin/bash
if [ ! -f mkcert ]; then
echo "downloading mkcert"
sudo curl -Lo mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-linux-amd64
sudo chmod +x mkcert
fi
sudo install mkcert /usr/local/bin/
- name: Create kind cluster
uses: helm/kind-action@v1.2.0
with:
cluster_name: kind
- name: build dev docker image
run: |
./scripts/build-dev-docker.bash
- name: start cluster
run: |
export POMERIUM_TAG=dev
cd ./integration/clusters/${{matrix.idp}}-${{matrix.deployment}}
docker-compose up -d
- name: integration tests
run: go test -v ./integration/...
run: |
(cd ./integration/clusters/${{matrix.idp}}-${{matrix.deployment}} && docker-compose logs -f &)
go test -v ./integration/...
build:
strategy:

View file

@ -4,7 +4,7 @@ repos:
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: "docs/.*"
exclude: "(docs/.*|integration/tpl/files/.*)"
- id: check-yaml
exclude: "examples/.*"
- id: check-added-large-files

21
go.mod
View file

@ -13,6 +13,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.1
github.com/client9/misspell v0.3.4
github.com/coreos/go-oidc/v3 v3.0.0
github.com/docker/docker v20.10.7+incompatible
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0
github.com/envoyproxy/protoc-gen-validate v0.6.1
github.com/fsnotify/fsnotify v1.5.0
@ -24,9 +25,11 @@ 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
@ -38,6 +41,7 @@ require (
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
github.com/peterbourgon/ff/v3 v3.1.0
github.com/pomerium/csrf v1.7.0
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/client_model v0.2.0
@ -68,6 +72,7 @@ require (
google.golang.org/protobuf v1.27.1
gopkg.in/auth0.v5 v5.19.2
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
sigs.k8s.io/yaml v1.2.0
)
require (
@ -79,7 +84,7 @@ require (
github.com/DataDog/datadog-go v3.5.0+incompatible // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/OpenPeeDeeP/depguard v1.0.1 // indirect
@ -96,13 +101,14 @@ require (
github.com/charithe/durationcheck v0.0.8 // indirect
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed // indirect
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
github.com/containerd/containerd v1.5.5 // indirect
github.com/containerd/continuity v0.1.0 // indirect
github.com/daixiang0/gci v0.2.9 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingajkin/go-header v0.4.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/cli v20.10.7+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/esimonov/ifshort v1.0.2 // indirect
@ -167,7 +173,7 @@ require (
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 // indirect
github.com/mgechev/revive v1.1.0 // indirect
@ -176,14 +182,15 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/moricho/tparallel v0.2.1 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/nakabonne/nestif v0.3.0 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/nishanths/exhaustive v0.2.3 // indirect
github.com/nishanths/predeclared v0.2.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v1.0.0-rc9 // indirect
github.com/opencontainers/runc v1.0.1 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect
github.com/philhofer/fwd v1.0.0 // indirect
@ -206,7 +213,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ssgreg/nlreturn/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b // indirect
github.com/tetafro/godot v1.4.8 // indirect

414
go.sum

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,78 @@
package main
import (
"context"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/integration/flows"
)
func TestAuthorization(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second*30)
defer clearTimeout()
accessType := []string{"direct", "api"}
for _, at := range accessType {
t.Run(at, func(t *testing.T) {
var withAPI, withForwardAuth flows.AuthenticateOption
if at == "api" {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
withAPI = flows.WithAPI()
}
if ClusterType == "nginx" {
withForwardAuth = flows.WithForwardAuth(true)
}
t.Run("public", func(t *testing.T) {
client := getClient()
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 := getClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-domain"),
withAPI, withForwardAuth, flows.WithEmail("user1@dogs.test"))
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 := getClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-domain"),
withAPI, withForwardAuth, flows.WithEmail("user1@cats.test"))
if assert.NoError(t, err) {
assertDeniedAccess(t, res, "expected Forbidden for cats.test, but got: %d", res.StatusCode)
}
})
})
})
}
}
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...)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,93 @@
package main
import (
"context"
"errors"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"github.com/google/go-jsonnet"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/rs/zerolog/log"
"sigs.k8s.io/yaml"
)
func main() {
generateCmd := &ffcli.Command{
Name: "generate-configuration",
Exec: func(ctx context.Context, args []string) error {
return runGenerateConfiguration(ctx)
},
}
rootCmd := &ffcli.Command{
Subcommands: []*ffcli.Command{generateCmd},
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
}
err := rootCmd.ParseAndRun(context.Background(), os.Args[1:])
if err != nil && !errors.Is(err, flag.ErrHelp) {
log.Fatal().Err(err).Send()
}
}
func runGenerateConfiguration(ctx context.Context) error {
log.Info().Msg("generating configuration")
root := filepath.Join(".", "integration")
if _, err := os.Stat(root); err != nil {
return fmt.Errorf("expected integration subfolder in cwd")
}
tplRoot := filepath.Join(root, "tpl")
var allSrcPaths []string
err := filepath.WalkDir(tplRoot, func(srcPath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
allSrcPaths = append(allSrcPaths, srcPath)
return err
})
if err != nil {
return err
}
sort.Strings(allSrcPaths)
vm := jsonnet.MakeVM()
vm.Importer(&jsonnet.FileImporter{JPaths: allSrcPaths})
for _, srcPath := range allSrcPaths {
if filepath.Ext(srcPath) != ".jsonnet" {
continue
}
dstPath := filepath.Join(root, srcPath[len(tplRoot)+1:])
dstPath = dstPath[:len(dstPath)-len(filepath.Ext(dstPath))]
contents, err := vm.EvaluateFile(srcPath)
if err != nil {
return fmt.Errorf("error evaluating jsonnet (path=%s): %w", srcPath, err)
}
asYAML, _ := yaml.JSONToYAML([]byte(contents))
err = os.MkdirAll(filepath.Dir(dstPath), 0755)
if err != nil {
return fmt.Errorf("error creating directory (path=%s): %w", dstPath, err)
}
err = os.WriteFile(dstPath, asYAML, 0600)
if err != nil {
return fmt.Errorf("error writing file (path=%s): %w", dstPath, err)
}
}
return nil
}

View file

@ -0,0 +1,103 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDashboard(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second*30)
defer clearTimeout()
t.Run("user dashboard", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://authenticate.localhost.pomerium.io/.pomerium/", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().Do(req)
if !assert.NoError(t, err, "unexpected http error") {
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
assert.Equal(t, http.StatusFound, res.StatusCode, "unexpected status code: %s", body)
})
t.Run("dashboard strict slash redirect", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://authenticate.localhost.pomerium.io/.pomerium", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().Do(req)
if !assert.NoError(t, err, "unexpected http error") {
return
}
defer res.Body.Close()
assert.Equal(t, 3, res.StatusCode/100, "unexpected status code")
})
t.Run("image asset", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://authenticate.localhost.pomerium.io/.pomerium/assets/img/pomerium.svg", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().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) {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second*30)
defer clearTimeout()
pomeriumRoutes := []string{
"https://authenticate.localhost.pomerium.io",
"https://httpdetails.localhost.pomerium.io",
"https://restricted-httpdetails.localhost.pomerium.io",
"https://unknown.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) {
req, err := http.NewRequestWithContext(ctx, "GET", routeToCheck, nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().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")
})
}
}
}

275
integration/flows/flows.go Normal file
View file

@ -0,0 +1,275 @@
// 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/forms"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/urlutil"
)
const (
authenticateHostname = "authenticate.localhost.pomerium.io"
forwardAuthenticateHostname = "forward-authenticate.localhost.pomerium.io"
idpHostname = "mock-idp.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 1 to %s: %w", authenticateHostname, err)
}
}
// (2) redirect to idp
for req.URL.Hostname() == authenticateHostname || req.URL.Hostname() == forwardAuthenticateHostname {
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 2 to %s: %w", idpHostname, err)
}
}
// (3) submit the form
for req.URL.Hostname() == idpHostname {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
fs := forms.Parse(res.Body)
if len(fs) > 0 {
f := fs[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 3 to %s: %w", idpHostname, 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 4 to %s: %w", originalHostname, err)
}
}
// (5) finally to callback
if !cfg.forwardAuth && req.URL.Path != pomeriumCallbackPath {
return nil, fmt.Errorf("expected to redirect 5 back to %s, but got %s", pomeriumCallbackPath, req.URL.String())
}
if cfg.forwardAuth {
for i := 0; ; i++ {
res, err = client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 302 {
break
}
originalURL := req.URL.String()
req, err = requestFromRedirectResponse(ctx, res, req)
if err != nil {
return nil, fmt.Errorf("expected redirect to %s: %w", originalHostname, err)
}
log.Info(ctx).
Int("count", i).
Str("from", originalURL).
Str("to", req.URL.String()).
Msg("forward-auth redirect")
}
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, fmt.Errorf("error parsing location: %w", err)
}
location = req.URL.ResolveReference(location)
newreq, err := http.NewRequestWithContext(ctx, "GET", location.String(), nil)
if err != nil {
return nil, err
}
return newreq, nil
}

View file

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

View file

@ -1,14 +1,29 @@
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"path/filepath"
"regexp"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/net/publicsuffix"
)
var IDP, ClusterType string
func TestMain(m *testing.M) {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
@ -19,6 +34,139 @@ func TestMain(m *testing.M) {
log.Logger = log.Logger.Level(zerolog.InfoLevel)
}
logger := log.With().Logger()
ctx := logger.WithContext(context.Background())
if err := waitForHealthy(ctx); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "services not healthy")
os.Exit(1)
return
}
setIDPAndClusterType(ctx)
status := m.Run()
os.Exit(status)
}
func getClient() *http.Client {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
panic(err)
}
rootCAs, err := x509.SystemCertPool()
if err != nil {
panic(err)
}
bs, err := os.ReadFile(filepath.Join(".", "tpl", "files", "ca.pem"))
if err != nil {
panic(err)
}
_ = rootCAs.AppendCertsFromPEM(bs)
return &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
DisableKeepAlives: true,
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
},
},
Jar: jar,
}
}
func waitForHealthy(ctx context.Context) error {
client := getClient()
check := func(endpoint string) error {
reqCtx, clearTimeout := context.WithTimeout(ctx, time.Second)
defer clearTimeout()
req, err := http.NewRequestWithContext(reqCtx, "GET", endpoint, nil)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode/100 != 2 {
return fmt.Errorf("%s unavailable: %s", endpoint, res.Status)
}
log.Info().Int("status", res.StatusCode).Msgf("%s healthy", endpoint)
return nil
}
ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop()
endpoints := []string{
"https://authenticate.localhost.pomerium.io/.well-known/pomerium/jwks.json",
"https://mock-idp.localhost.pomerium.io/.well-known/jwks.json",
}
for {
var err error
for _, endpoint := range endpoints {
err = check(endpoint)
if err != nil {
break
}
}
if err == nil {
return nil
}
log.Ctx(ctx).Info().Err(err).Msg("waiting for healthy")
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
func setIDPAndClusterType(ctx context.Context) {
IDP = "oidc"
ClusterType = "single"
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
log.Error().Err(err).Msg("failed to create docker client")
return
}
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
if err != nil {
log.Error().Err(err).Msg("failed to retrieve docker containers")
}
for _, container := range containers {
for _, name := range container.Names {
parts := regexp.MustCompile(`^/(\w+?)-(\w+?)_pomerium.*$`).FindStringSubmatch(name)
if len(parts) == 3 {
IDP = parts[1]
ClusterType = parts[2]
}
}
}
log.Info().Str("idp", IDP).Str("cluster-type", ClusterType).Send()
}
func mustParseURL(str string) *url.URL {
u, err := url.Parse(str)
if err != nil {
panic(err)
}
return u
}

250
integration/policy_test.go Normal file
View file

@ -0,0 +1,250 @@
package main
import (
"context"
"crypto/tls"
"encoding/json"
"net/http"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
)
func TestCORS(t *testing.T) {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
ctx := context.Background()
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
t.Run("enabled", func(t *testing.T) {
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 := getClient().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) {
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 := getClient().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) {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
ctx := context.Background()
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
t.Run("enabled", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/preserve-host-header-enabled", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().Do(req)
if !assert.NoError(t, err, "unexpected http error") {
return
}
defer res.Body.Close()
var result struct {
Headers struct {
Host string `json:"host"`
} `json:"headers"`
}
err = json.NewDecoder(res.Body).Decode(&result)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, "httpdetails.localhost.pomerium.io", result.Headers.Host,
"destination host should be preserved in %v", result)
})
t.Run("disabled", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/preserve-host-header-disabled", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().Do(req)
if !assert.NoError(t, err, "unexpected http error") {
return
}
defer res.Body.Close()
var result struct {
Headers struct {
Host string `json:"host"`
} `json:"headers"`
}
err = json.NewDecoder(res.Body).Decode(&result)
if !assert.NoError(t, err) {
return
}
assert.NotEqual(t, "httpdetails.localhost.pomerium.io", result.Headers.Host,
"destination host should not be preserved in %v", result)
})
}
func TestSetRequestHeaders(t *testing.T) {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
ctx := context.Background()
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().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 := context.Background()
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
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 := getClient().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) {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
ctx := context.Background()
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
t.Run("disabled", func(t *testing.T) {
ws, _, err := (&websocket.Dialer{
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{
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 TestGoogleCloudRun(t *testing.T) {
ctx := context.Background()
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
req, err := http.NewRequestWithContext(ctx, "GET", "https://cloudrun.localhost.pomerium.io/", nil)
if err != nil {
t.Fatal(err)
}
res, err := getClient().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
}
if result.Headers["x-idp"] == "google" {
assert.NotEmpty(t, result.Headers["authorization"], "expected authorization header when cloudrun is enabled")
}
}

View file

@ -0,0 +1,62 @@
local utils = import '../utils.libsonnet';
function() {
local name = 'fortio',
local image = 'fortio/fortio:1.17.0',
compose: {
services:
utils.ComposeService(name, {
image: image,
depends_on: {
[name + '-init']: {
condition: 'service_completed_successfully',
},
},
command: [
'server',
'-cert',
'/fortio_config/trusted.pem',
'-key',
'/fortio_config/trusted-key.pem',
],
ports: [
'8079:8079/tcp',
],
volumes: [
'fortio_config:/fortio_config',
],
}) +
utils.ComposeService(name + '-init', {
image: 'busybox:latest',
command: [
'sh',
'-c',
|||
echo "$$CERT" >/fortio_config/trusted.pem
echo "$$KEY" >/fortio_config/trusted-key.pem
|||,
],
environment: {
CERT: importstr '../files/trusted.pem',
KEY: importstr '../files/trusted-key.pem',
},
volumes: [
'fortio_config:/fortio_config',
],
}) +
utils.ComposeService(name + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
'http://' + name + ':8080',
'-timeout',
'10m',
],
}),
volumes: {
fortio_config: {},
},
},
kubernetes: [],
}

View file

@ -0,0 +1,76 @@
local utils = import '../utils.libsonnet';
local Variations() =
[
{
name: 'trusted',
cert: importstr '../files/trusted.pem',
key: importstr '../files/trusted-key.pem',
},
{
name: 'untrusted',
cert: importstr '../files/untrusted.pem',
key: importstr '../files/untrusted-key.pem',
},
{
name: 'wrongly-named',
cert: importstr '../files/invalid.pem',
key: importstr '../files/invalid-key.pem',
},
];
local Command(variation) =
[
'sh',
'-c',
|||
cat <<-END_OF_HTTPDETAILS | tee /app/fullchain.pem
%s
END_OF_HTTPDETAILS
cat <<-END_OF_HTTPDETAILS | tee /app/privkey.pem
%s
END_OF_HTTPDETAILS
node ./index.js
||| % [variation.cert, variation.key],
];
function() {
local suffix = 'httpdetails',
local image = 'mendhak/http-https-echo:19',
compose: {
services: std.foldl(
function(acc, variation)
acc +
utils.ComposeService(variation.name + '-' + suffix, {
image: image,
command: Command(variation),
}) +
utils.ComposeService(variation.name + '-' + suffix + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
'http://' + variation.name + '-' + suffix + ':8080',
'-timeout',
'10m',
],
}),
Variations(),
{}
),
},
kubernetes: std.foldl(
function(acc, variation)
acc + [
utils.KubernetesDeployment(variation.name + '-' + suffix, image, Command(variation), [
{ name: 'http', containerPort: 8080 },
{ name: 'https', containerPort: 8443 },
]),
utils.KubernetesService(variation.name + '-' + suffix, [
{ name: 'http', port: 8080, targetPort: 'http' },
{ name: 'https', port: 8443, targetPort: 'https' },
]),
], Variations(), []
),
}

View file

@ -0,0 +1,43 @@
local utils = import '../utils.libsonnet';
function(idp) {
local name = 'mock-idp',
local image = 'pomerium/mock-idps:${MOCK_IDPS_TAG:-master}',
local command = [
'--provider',
idp,
'--port',
'8024',
'--root-url',
'https://mock-idp.localhost.pomerium.io/',
],
compose: {
services:
utils.ComposeService(name, {
image: image,
command: command,
ports: [
'8024:8024/tcp',
],
}) +
utils.ComposeService(name + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
'http://' + name + ':8024/.well-known/openid-configuration',
'-timeout',
'10m',
],
}),
volumes: {},
},
kubernetes: [
utils.KubernetesDeployment(name, image, command, [
{ name: 'http', containerPort: 8024 },
]),
utils.KubernetesService(name, [
{ name: 'http', port: 8024, targetPort: 'http' },
]),
],
}

View file

@ -0,0 +1,215 @@
local utils = import '../utils.libsonnet';
local Routes = (import './routes.libsonnet').Routes;
local GoogleCloudServerlessAuthenticationServiceAccount(dns_suffix='') =
{
type: 'service_account',
project_id: 'pomerium-redacted',
private_key_id: 'e07f7c93870c7e03f883560ecd8fd0f4d27b0081',
private_key: importstr '../files/trusted-key.pem',
client_email: 'redacted@pomerium-redacted.iam.gserviceaccount.com',
client_id: '101215990458000334387',
auth_uri: 'http://mock-idp' + dns_suffix + ':8024',
token_uri: 'http://mock-idp' + dns_suffix + ':8024/token',
auth_provider_x509_cert_url: 'http://mock-idp' + dns_suffix + ':8024',
client_x509_cert_url: 'http://mock-idp' + dns_suffix + ':8024',
};
local KubernetesDeployment(name, image, environment) =
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
namespace: 'default',
name: name,
},
spec: {
replicas: 1,
selector: { matchLabels: { app: name } },
template: {
metadata: {
labels: { app: name },
},
spec: {
containers: [{
name: name,
image: image,
imagePullPolicy: 'IfNotPresent',
ports: [
{ name: 'http', containerPort: 80 },
{ name: 'https', containerPort: 443 },
{ name: 'grpc', containerPort: 5443 },
],
env: [
{
name: k,
value: environment[k],
}
for k in std.objectFields(environment)
],
}],
},
},
},
};
local KubernetesService(name) =
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'default',
name: name,
labels: { app: name },
},
spec: {
type: 'NodePort',
selector: { app: name },
ports: [
{ name: 'http', port: 80, targetPort: 'http', nodePort: 80 },
{ name: 'https', port: 443, targetPort: 'https', nodePort: 443 },
{ name: 'grpc', port: 5443, targetPort: 'grpc', nodePort: 5443 },
],
},
};
local Environment(mode, idp, dns_suffix) =
{
AUTHENTICATE_SERVICE_URL: 'https://authenticate.localhost.pomerium.io',
CERTIFICATE: std.base64(importstr '../files/trusted.pem'),
CERTIFICATE_KEY: std.base64(importstr '../files/trusted-key.pem'),
CERTIFICATE_AUTHORITY: std.base64(importstr '../files/ca.pem'),
COOKIE_SECRET: 'UYgnt8bxxK5G2sFaNzyqi5Z+OgF8m2akNc0xdQx718w=',
DATABROKER_STORAGE_TYPE: 'redis',
DATABROKER_STORAGE_CONNECTION_STRING: 'redis://redis:6379',
ENVOY_ADMIN_ADDRESS: '0.0.0.0:9901',
GOOGLE_CLOUD_SERVERLESS_AUTHENTICATION_SERVICE_ACCOUNT: std.base64(std.manifestJsonEx(
GoogleCloudServerlessAuthenticationServiceAccount(dns_suffix), ''
)),
IDP_PROVIDER: idp,
IDP_PROVIDER_URL: 'https://mock-idp.localhost.pomerium.io/',
IDP_CLIENT_ID: 'CLIENT_ID',
IDP_CLIENT_SECRET: 'CLIENT_SECRET',
JWT_CLAIMS_HEADERS: 'email,groups,user',
LOG_LEVEL: 'info',
POLICY: std.base64(std.manifestJsonEx(Routes(mode, idp, dns_suffix), '')),
SHARED_SECRET: 'UYgnt8bxxK5G2sFaNzyqi5Z+OgF8m2akNc0xdQx718w=',
SIGNING_KEY: std.base64(importstr '../files/signing-key.pem'),
SIGNING_KEY_ALGORITHM: 'ES256',
} + if mode == 'multi' then {
AUTHORIZE_SERVICE_URL: 'https://pomerium-authorize:5443',
DATABROKER_SERVICE_URL: 'https://pomerium-databroker:5443',
GRPC_ADDRESS: ':5443',
GRPC_INSECURE: 'false',
OVERRIDE_CERTIFICATE_NAME: '*.localhost.pomerium.io',
} else if mode == 'traefik' then {
FORWARD_AUTH_URL: 'https://forward-authenticate.localhost.pomerium.io',
} else if mode == 'nginx' then {
ADDRESS: ':80',
INSECURE_SERVER: 'true',
FORWARD_AUTH_URL: 'https://forward-authenticate.localhost.pomerium.io',
} else {};
local ComposeService(name, definition, additionalAliases=[]) =
utils.ComposeService(name, definition {
depends_on: {
[name + '-ready']: {
condition: 'service_completed_successfully',
}
for name in [
'fortio',
'mock-idp',
'redis',
'trusted-httpdetails',
'untrusted-httpdetails',
'verify',
'websocket-echo',
'wrongly-named-httpdetails',
]
},
}, additionalAliases);
function(mode, idp, dns_suffix='') {
local name = 'pomerium',
local image = 'pomerium/pomerium:${POMERIUM_TAG:-master}',
local environment = Environment(mode, idp, dns_suffix),
compose: {
services: if mode == 'multi' then
ComposeService(name + '-authorize', {
image: image,
environment: environment {
SERVICES: 'authorize',
},
ports: [
'9904:9901/tcp',
'5446:5443/tcp',
],
}) +
ComposeService(name + '-authenticate', {
image: image,
environment: environment {
SERVICES: 'authenticate',
},
ports: [
'9903:9901/tcp',
'5445:5443/tcp',
],
}, ['authenticate.localhost.pomerium.io']) +
ComposeService(name + '-databroker', {
image: image,
environment: environment {
SERVICES: 'databroker',
},
ports: [
'9902:9901/tcp',
'5444:5443/tcp',
],
}) +
ComposeService(name + '-proxy', {
image: image,
environment: environment {
SERVICES: 'proxy',
},
ports: [
'80:80/tcp',
'443:443/tcp',
'5443:5443/tcp',
'9901:9901/tcp',
],
}, ['mock-idp.localhost.pomerium.io'])
else if mode == 'traefik' || mode == 'nginx' then
ComposeService(name, {
image: image,
environment: environment,
}, ['authenticate.localhost.pomerium.io', 'forward-authenticate.localhost.pomerium.io']) +
ComposeService(name + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
if mode == 'nginx' then
'http://' + name + ':80/healthz'
else
'https://' + name + ':443/healthz',
'-timeout',
'10m',
],
})
else
ComposeService(name, {
image: image,
environment: environment,
ports: [
'80:80/tcp',
'443:443/tcp',
'9901:9901/tcp',
],
}, ['authenticate.localhost.pomerium.io']),
volumes: {},
},
kubernetes: [
KubernetesService(name),
KubernetesDeployment(name, image, environment),
],
}

View file

@ -0,0 +1,30 @@
local utils = import '../utils.libsonnet';
function() {
local name = 'redis',
local image = 'redis:6.2.5-alpine',
compose: {
services:
utils.ComposeService(name, {
image: image,
}) +
utils.ComposeService(name + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
'tcp://' + name + ':6379',
'-timeout',
'10m',
],
}),
},
kubernetes: [
utils.KubernetesDeployment(name, image, null, [
{ name: 'tcp', containerPort: 6379 },
]),
utils.KubernetesService(name, [
{ name: 'tcp', port: 6379, targetPort: 'tcp' },
]),
],
}

View file

@ -0,0 +1,197 @@
local Routes(mode, idp, dns_suffix) =
[
{
from: 'https://mock-idp.localhost.pomerium.io',
to: 'http://mock-idp' + dns_suffix + ':8024',
allow_public_unauthenticated_access: true,
preserve_host_header: true,
},
{
from: 'https://envoy.localhost.pomerium.io',
to: 'http://localhost:9901',
allow_public_unauthenticated_access: true,
},
{
from: 'https://verify.localhost.pomerium.io',
to: 'http://verify' + dns_suffix + ':80',
allow_any_authenticated_user: true,
pass_identity_headers: true,
},
{
from: 'https://websocket-echo.localhost.pomerium.io',
to: 'http://websocket-echo' + dns_suffix + ':80',
allow_public_unauthenticated_access: true,
allow_websockets: true,
},
{
from: 'https://fortio-ui.localhost.pomerium.io',
to: 'https://fortio' + dns_suffix + ':8080',
allow_any_authenticated_user: true,
},
{
from: 'https://fortio-ping.localhost.pomerium.io',
to: 'https://fortio' + dns_suffix + ':8079',
allow_public_unauthenticated_access: true,
tls_custom_ca: std.base64(importstr '../files/ca.pem'),
tls_server_name: 'fortio-ping.localhost.pomerium.io',
},
{
from: 'tcp+https://redis.localhost.pomerium.io:6379',
to: 'tcp://redis' + dns_suffix + ':6379',
allow_any_authenticated_user: true,
},
// tls_skip_verify
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'https://trusted-httpdetails' + dns_suffix + ':8443',
path: '/tls-skip-verify-enabled',
tls_skip_verify: true,
allow_public_unauthenticated_access: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'https://trusted-httpdetails' + dns_suffix + ':8443',
path: '/tls-skip-verify-disabled',
tls_skip_verify: false,
allow_public_unauthenticated_access: true,
},
// tls_server_name
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'https://wrongly-named-httpdetails' + dns_suffix + ':8443',
path: '/tls-server-name-enabled',
tls_server_name: 'httpdetails.localhost.notpomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'https://wrongly-named-httpdetails' + dns_suffix + ':8443',
path: '/tls-server-name-disabled',
allow_public_unauthenticated_access: true,
},
// tls_custom_certificate_authority
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails' + dns_suffix + ':8443',
path: '/tls-custom-ca-enabled',
tls_custom_ca: std.base64(importstr '../files/untrusted-ca.pem'),
tls_server_name: 'httpdetails.localhost.pomerium.io',
allow_public_unauthenticated_access: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'https://untrusted-httpdetails' + dns_suffix + ':8443',
path: '/tls-custom-ca-disabled',
allow_public_unauthenticated_access: true,
},
// tls_client_cert
// {
// from: 'http://httpdetails.localhost.pomerium.io',
// to: 'https://mtls-http-details' + dns_suffix + ':8443',
// 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://mtls-http-details' + dns_suffix + ':8443',
// path: '/tls-client-cert-disabled',
// allow_public_unauthenticated_access: true,
// },
// cors_allow_preflight option
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
prefix: '/cors-enabled',
cors_allow_preflight: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
prefix: '/cors-disabled',
cors_allow_preflight: false,
},
// preserve_host_header option
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
prefix: '/preserve-host-header-enabled',
allow_public_unauthenticated_access: true,
preserve_host_header: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
prefix: '/preserve-host-header-disabled',
allow_public_unauthenticated_access: true,
preserve_host_header: false,
},
// authorization policy
{
from: 'https://restricted-httpdetails.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
allow_any_authenticated_user: true,
pass_identity_headers: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
prefix: '/by-domain',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
allowed_domains: ['dogs.test'],
pass_identity_headers: true,
},
{
from: 'https://httpdetails.localhost.pomerium.io',
prefix: '/by-user',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
allowed_users: ['user1@dogs.test'],
pass_identity_headers: true,
},
// catch-all
{
from: 'https://httpdetails.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
allow_public_unauthenticated_access: true,
pass_identity_headers: true,
set_request_headers: {
'X-Custom-Request-Header': 'custom-request-header-value',
},
},
// websockets
{
from: 'https://enabled-ws-echo.localhost.pomerium.io',
to: 'http://websocket-echo' + dns_suffix + ':80',
allow_public_unauthenticated_access: true,
allow_websockets: true,
},
{
from: 'https://disabled-ws-echo.localhost.pomerium.io',
to: 'http://websocket-echo' + dns_suffix + ':80',
allow_public_unauthenticated_access: true,
},
// cloudrun
{
from: 'https://cloudrun.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
allow_public_unauthenticated_access: true,
pass_identity_headers: true,
enable_google_cloud_serverless_authentication: true,
set_request_headers: {
'x-idp': idp,
},
},
] + if mode == 'multi' then [
{
from: 'https://authenticate.localhost.pomerium.io',
to: 'https://pomerium-authenticate',
allow_public_unauthenticated_access: true,
host_rewrite: 'authenticate.localhost.pomerium.io',
tls_skip_verify: true,
},
] else [];
{
Routes: Routes,
}

View file

@ -0,0 +1,55 @@
local utils = import '../utils.libsonnet';
function(mode) {
local name = 'verify',
local image = 'pomerium/verify:${VERIFY_TAG:-latest}',
compose: {
services:
utils.ComposeService(name, {
image: image,
depends_on: {
[name + '-init']: {
condition: 'service_completed_successfully',
},
},
environment: {
SSL_CERT_FILE: '/verify_config/ca.pem',
},
volumes: [
'verify_config:/verify_config',
],
}) +
utils.ComposeService(name + '-init', {
image: 'busybox:latest',
command: [
'sh',
'-c',
"echo '" + (importstr '../files/ca.pem') + "' > /verify_config/ca.pem",
],
volumes: [
'verify_config:/verify_config',
],
}) +
utils.ComposeService(name + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
'http://' + name + ':80/',
'-timeout',
'10m',
],
}),
volumes: {
verify_config: {},
},
},
kubernetes: [
utils.KubernetesService(name, [
{ name: 'http', port: 80, targetPort: 'http' },
]),
utils.KubernetesDeployment(name, image, null, [
{ name: 'http', containerPort: 80 },
]),
],
}

View file

@ -0,0 +1,33 @@
local utils = import '../utils.libsonnet';
function() {
local name = 'websocket-echo',
local image = 'pvtmert/websocketd:latest',
local command = ['--port', '80', 'tee'],
compose: {
services:
utils.ComposeService(name, {
image: image,
command: command,
}) +
utils.ComposeService(name + '-ready', {
image: 'jwilder/dockerize:0.6.1',
command: [
'-wait',
'tcp://' + name + ':80',
'-timeout',
'10m',
],
}),
volumes: {},
},
kubernetes: [
utils.KubernetesDeployment(name, image, command, [
{ name: 'http', containerPort: 80 },
]),
utils.KubernetesService(name, [
{ name: 'http', port: 80, targetPort: 'http' },
]),
],
}

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('auth0')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('azure')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('github')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('gitlab')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('google')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('oidc')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('okta')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('onelogin')

View file

@ -0,0 +1 @@
(import '../../deployments/single.libsonnet')('ping')

View file

@ -0,0 +1,16 @@
local utils = import '../utils.libsonnet';
function(idp) utils.Merge([
(import '../backends/fortio.libsonnet')().compose,
(import '../backends/httpdetails.libsonnet')().compose,
(import '../backends/mock-idp.libsonnet')(idp).compose,
(import '../backends/pomerium.libsonnet')('single', idp).compose,
(import '../backends/redis.libsonnet')().compose,
(import '../backends/verify.libsonnet')('single').compose,
(import '../backends/websocket-echo.libsonnet')().compose,
{
networks: {
main: {},
},
},
])

View file

@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQDWysjM+TFVumCn
Y4daCDdTJ+zxNqR+UmyUqVsXQBoXQZAZKQGuzsWq4x+SlZAk0mu6J3JWu6jbiWUu
pgZqMRfkfnEd48hnVAGJaM5dY5X6iyHwv/oO1e1ZjiZ/fLYweNn2rcS5eljwMNSQ
zbFrUH4SoW3B7re0bBdKzDjh3MMsE0eyzsgpiHpdaXJqVeIW1XE+nlcKDx18iUmv
XKmjEOV63aIGouCKmWSBix3i7E1brToCRVxHr5V/0E97FqJhHLc4d/CbOPQ4gDw+
neGO/wjy2BH5NvSUPTWYaYA7IvYbuRRx2XF8H9SB5cPwgVYJ+o/o5ryis/dDGtaK
rr91unbbYnf8QnCxrtkvvek868pJqMuVWnC7VRD+NWJqFZbPda5aO6JaXkFhW55n
KO9/UyP+Ifh6HaJaXq0BEUJe7gp6SlT2d1qJCrkYeaZKpEKf2q0I7cHEbRuNwqw6
W5frkozAgMYWvefHk2L7aI0zJ7y7bBiA9YRl56pcPuSoyq4ER60CAwEAAQKCAYEA
rbQBAALmqvW3BKew5laOp2k6bThLfv0ZYoAzcnIcp4IWDsLi4YPGx5Q2DE137M9e
34TXKt1IiYJrmXYa6fYZ5Gd9Azca8rU1KPLhMRVATQYHxLL8ftLyx+H1sFQi4sBZ
ROzyky0mj9htj01JlgfabSavpI5xci+YBp1xwvbNv+83pJsltLDyhGiqZuRdmymy
E3np2fCvX++G75J4fa9EZkCmr8c43919MmZXHRUM2n8U0pFVN4P/2wWTwW9kkjSt
XCcX4hTOT2lYHaDMTCL9R5b4kbgQt8lTvUEov7alLEvSLeTY/GTlb+kTMCxmctOT
J6YIBRp6noserU7UV4wOD8/ADxqtLeGMaHbZuKfJ5gDNaEnUj7UWEAdKxvYXlz55
3ya9U72Tsxk6b6o2tySrSXD7OIomGqlXlxI9M/12AW3rjJ1cb3GUk5FQnHAATs/i
U+CbWhACq/pFaaSKbyrBBUcvTYt3qngTAUbTBoBN/Vzb6E6jjceBCBMK+QxrhroB
AoHBAPOt0FhSZlgxG2T39TT0eaRZ1WKQSqzLMxV1v+maTvxgUoSJzqlueytCj9og
LITkpUqZTAhZNEyC192rOj39e913xamF7NVHkUQqBYHXjpVw2TWLuGfmV1PplAvs
fy6TH0UQqmTddnVVzJMI8KmYWzN4yivIp08aR8VyhYT4swxOLApr1uoz9lFa9Ii0
crljcnUZoJZwD0eMzPeRCRR13b/rK8o424zdWqxJiHsFauwdTV9Q+C5q4CjpkmcW
UuknMQKBwQDhpw8Fg+aJwPfvDRY3HN54edStmdyQVmcaK95M0ozox7Rk6UezSZZy
MjNixppRc2ZpApfFMiaTfTrMygOLbw1T8X+D4AtppPEVQZKWA7iQu4sUiZsHOTew
agyDBTOKXcgDWeV588D84qEPB+QWoYopsse0lxBtnMvc3LyW5wjWkZABXuW/Jx2j
RVDRHrMyAktKgvKQqaL+2rXHl7mFb/5pEe3RqjwADfQ/Yp7Lv3PxBy9D583PBlIa
0JyVSf4KwT0CgcAFszIlbsAAHh3y7a3psDJMOuG37YIhqpsmTFvR3g8s7h/gA802
v2PYLjVpN8lyzlpjdVSG+Xc0tvbPs5qoKo7ELnIMNhrFHmhyfL0mPWGTc1FRZFRK
8eNnDhatdLnA4CYiGnKx61BDDF+9rL7caLjxakjoX0gynH3DS5t98cdWaVm0YLNl
RRPk8Ui5DeeFGKNrw86y1io1VUDSJa1dsigeviSHFW9lSyQ81XeA0S6gGUtfCGjV
xSA7NMN879O+qnECgcEAlGcHJQxrKLuFE21a5+IPmdIeAhIHkdGROxAQwhtS2qDE
Tf1xz0KdM/s5+kM9KEYp2vP+loz1+9fHPPm6vQ/LByLzRuqo9tCoUN9wJULLNjxx
Ko+ZKnYB3v7PvbdE/0HQEgwkNEEP8gCmBbmd0xhoQiY22tji0APnuxhc2y2UjXDr
8UrU9BMolcE3dmCnX0NM+vMFzggSj2ONW3e4Zj6SZc2Jx3MaxLpooOseHkeKW8Dq
39DqdLXmd4YtBK3F6pLNAoHBAPJnnPMxZZNJhkSELZ6CbSWbcT9FTrdfwTxF+Y4O
VvxujzWhPPNrUg10UdqrJdB9cqpN4Hlkc9Xd8/d4G3ph8qrrbZSiD5g2Pn1X0MB+
eaC5IvB+wgEBmOeN/UaeR0K8Omfa0H5+0yuFL0lU1htq1pxMkY7o14FvDF68uJf1
2GvEJ932BuPdwh+67hA9yj6lQ458xgsme3dE2C36BCwHlb1uGvc75405InRf9mf4
JO5gS4pQR3MBP6W0qDfF/mb0NA==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIE1zCCAz+gAwIBAgIQZ139cd/paPdkS2JyAu7kEDANBgkqhkiG9w0BAQsFADCB
gzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSwwKgYDVQQLDCNjYWxl
YkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTEzMDEGA1UEAwwqbWtjZXJ0
IGNhbGViQGNhbGViLXBjLWxpbnV4IChDYWxlYiBEb3hzZXkpMB4XDTIxMDgxMDE3
MzIwOVoXDTMxMDgxMDE3MzIwOVowgYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9w
bWVudCBDQTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXggKENhbGViIERv
eHNleSkxMzAxBgNVBAMMKm1rY2VydCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2Fs
ZWIgRG94c2V5KTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBANbKyMz5
MVW6YKdjh1oIN1Mn7PE2pH5SbJSpWxdAGhdBkBkpAa7OxarjH5KVkCTSa7oncla7
qNuJZS6mBmoxF+R+cR3jyGdUAYlozl1jlfqLIfC/+g7V7VmOJn98tjB42fatxLl6
WPAw1JDNsWtQfhKhbcHut7RsF0rMOOHcwywTR7LOyCmIel1pcmpV4hbVcT6eVwoP
HXyJSa9cqaMQ5Xrdogai4IqZZIGLHeLsTVutOgJFXEevlX/QT3sWomEctzh38Js4
9DiAPD6d4Y7/CPLYEfk29JQ9NZhpgDsi9hu5FHHZcXwf1IHlw/CBVgn6j+jmvKKz
90Ma1oquv3W6dttid/xCcLGu2S+96Tzrykmoy5VacLtVEP41YmoVls91rlo7olpe
QWFbnmco739TI/4h+HodolperQERQl7uCnpKVPZ3WokKuRh5pkqkQp/arQjtwcRt
G43CrDpbl+uSjMCAxha958eTYvtojTMnvLtsGID1hGXnqlw+5KjKrgRHrQIDAQAB
o0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
FgQUhYZYWIBHyk6ZVTnp3lRt/tyBP00wDQYJKoZIhvcNAQELBQADggGBAA1F/apr
l6pNT3Mp/MxhUUgo6usEJCryGQcLRfexyQXGN3huCmIrP55VFa8ETPAtjsr6PMe7
7vvEj8eFu2JtKovlQwNewYU9cjAMCVaFiNbrQa20hzhWc2js6dyildE6/DPzbeds
KDAxhFNp35SlwtRtKk1SzxJxsqSwjfxI8fp+R/0wO8g0fWTdM2gCpRwYMNwJELEg
+dSlvJCwuu+rzxLalzaPF1PMTW72OELal/j5sD+2VytQ4k+HUDbyt2DnQT7YQ3zo
q02x2u2sm1WW/o/uh8pjPxkGQqL2mryZs6VH9VCU3QkKNDssNd71lr3wPoE4YRHe
UvzD1eDeelzBUFNIpDCjdCsL55yIPqUsr6lmjpBPL0vea33QTMbcsSxu0umGXDbU
66juU4Z1jOE0wClIvaO699J+E2gBe1jUN6At6b8BSoZqCqXYoDHGei9RBUdvgqto
kVsoJfDI/TFMekYgpL5UVYmLdfgqLPPRP9pQBLDx3mszeAqnvfTICAzfXg==
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDeC7rZneOT2JwR
pa+PGG7WAigfI/oQCNzzMV12T2cdicHz2e/HioWfWeWy9PlVSosgl4C4jF5pV8hR
rkARvlpZklOn6Cl55w1pk0lbVa84AFsIKCrqBJQhtAGx6KxACvrkuWlA5uDTWYn5
DvMeYWxExb/VKETaSLvjN2J4N1HlZiSqzA9D+uPM5rqCmj8425EjJGw8xjpv2FS3
LW3sjm1V3FbFTrPLj3hYE7f7U+CqNQd3gW6lAG3FaKeh9SlGvmb5rEF3z1iWrcpO
DZ8KzbdZgKbFISJgecvPdAHuhFkg3onk3RbYdTZT0fo/R5Aj6FT3/v4DX6Xx7xZY
tegm3VkzAgMBAAECggEAfbVoWHGh+P7mBLuns7yiaziXXGHy3YcXB/vHrQZxG3D1
REf7Dx4mXjv35iP40Me+EnqTXmfhv4P6Hfwnu6Pn5NQJ1oKCkMg1eLLxEetloq/Y
2bpb6VckQhx15TIT6sIMntc1do//csg7a6yCCY4gTKMj4trEeEw7Svz9G5A+Jj9q
uAJWbXWpCw97Qu8qrIqmdobb1fbp5/9p54XmcgHAtYfYPcdNGlc4IeDZhMV5Pya7
7ULfeIIfAMWKG2iGKQfzGTqk2+2hZnc8y4SQc84hbL3GRphEr5cIz91NTcStRTC1
O+5Vzpw7A42ktDW1ZQwLQ6JHDubjXSFyy1bzz/Wn2QKBgQDpgi+yq0u84BJXYOVH
zzvKzMQv8aW9ezn6y7te3fbygU3x7F2GvMcpbju2xjSP8GBJTjpyEjqIjzP+fPgo
xERVbwrjzmJ1OrOiAkXQ9T3Y7TNzABG8XspEiXy+TpFFaeKy/rU5MTLc8RPGaOlW
bi8ePTjhJ7XvAIdrCcd/uibEnwKBgQDzbuXTqoh7i4wHGbbN8oThMJrMOsT4oiv0
Z5mtW5PMHSTu9GRt+sco3iYdFb/YCeqGUKQ7Lb5NWY5bmVc2OFh3TdwFC3zROebp
jT4Su9rVJpXj/GDXOZUH3u6DoERcvD+VtMcHummard6ROBX5cNOsCmugCL034LV9
1XMmgWNu7QKBgQDKSTOU7nvwJZT+CTzXFpnPt9AFUKuqGEREFZY+Or+hmY6yk3b6
MDPAmnQ0hEQopa5kEtbi1xPKFXSPdCdu+YfREx819iapM69GG/3rZWisseAuMdMr
glprQUfFfT6wCWiQc8L+xrYvXNEqwtvROiarZZIOy136rFSjz5b5+YN4NQKBgQDK
axmCYxgwGv5p3RDruVCS69acIfYthLQl+4uG4lJIdKeEZwWnidLXgbmRj8dBPiWc
YCvf5Y6LRP+h3STuufWd5skgDMhSNeJzq/XEoB48BWS3+eEQthndPJt6KecOcZ4x
vuuM2o37h749ZLSpAQ+Ry+xoWzvz3c8sfjPM/eQPlQKBgHGTKABY8IvQZ+dFY8Kj
VdXAfstMx9T/usfZHl/d5/J1brh18Sj+XGT/FMrG1Zk1++ql+IPWwGYaUtNntaQw
X4KGlLJUat2+aEYO3IcHrQqpZuq1Oy7peUNrOxCEtFTuWZQT2LLYa6JMOiMG5/Y7
WFGqCtnQc9LUg5a1h02k6Eyp
-----END PRIVATE KEY-----

View file

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIESzCCArOgAwIBAgIRAOw5c1g686jRWoMVoRgwfHgwDQYJKoZIhvcNAQELBQAw
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMTA4MTEy
MTU1NDZaFw0yMzExMTEyMjU1NDZaMFcxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
bWVudCBjZXJ0aWZpY2F0ZTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXgg
KENhbGViIERveHNleSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDe
C7rZneOT2JwRpa+PGG7WAigfI/oQCNzzMV12T2cdicHz2e/HioWfWeWy9PlVSosg
l4C4jF5pV8hRrkARvlpZklOn6Cl55w1pk0lbVa84AFsIKCrqBJQhtAGx6KxACvrk
uWlA5uDTWYn5DvMeYWxExb/VKETaSLvjN2J4N1HlZiSqzA9D+uPM5rqCmj8425Ej
JGw8xjpv2FS3LW3sjm1V3FbFTrPLj3hYE7f7U+CqNQd3gW6lAG3FaKeh9SlGvmb5
rEF3z1iWrcpODZ8KzbdZgKbFISJgecvPdAHuhFkg3onk3RbYdTZT0fo/R5Aj6FT3
/v4DX6Xx7xZYtegm3VkzAgMBAAGjZTBjMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE
FjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUhYZYWIBHyk6ZVTnp
3lRt/tyBP00wEQYDVR0RBAowCIIGY2xpZW50MA0GCSqGSIb3DQEBCwUAA4IBgQAi
rSAwrwxiYIsAet5GWL+twMP1LiiifNY5REWJ1k/yu6hvG2bvAjSSVwO9pasMnD04
kwf5SvPcbWB/5hfwgYLJfDiuoYRptobLVXbvBtgj9EXzveJV6pZr9/oNaPBNArfr
a7N+R1IUNwnwJ5rIFdkpvrNjcxPbYV/XYb7LbuVuuQnmggUZDrKRKhoMIsq491tf
g6ZO5i4tBqDWUJk7S83AypTOBZNK2ugIQUWwq02gKEtchAACxg0jsuFD/wxtTQS2
TDY9pu0m4z01vNzpZy3oPkx48ZN2U9CGXaiEzjEhr87n8gkDBHmntCMJKbIiPmqD
YJvJSxQEHyGPuB5FcmC+yIWQ5FhoICynSUQlXn4gCH7EBEflf3TDoYBt0PNoW8Bc
xqfpLRltpIDWcOccmjtK+Ucpqgkvy3uAEo7GPfI8Dzv3m4FYG3GctO0RQgeA//VT
WsJPFK7HZdMiCJDEzZ0zvPg8O8Dcp17KW0KAn2BO3R7gZDrZ4lSNct3I91n69hI=
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVDWPhOpNWAYNT
QZ4BR5ZU13HqRg/6B49duFcHPY+hkbQPSZdN+GZjCeRVIK8iAkgM3cvyRs40dygZ
eogu9LYo6AN/h6cVCF9ENg5jo7/PjK5/6aIf8/Ss22tCuhUL7UHV6ttf6y0+4Nq1
hRQcbyIPij89nmO+mT4Fhs9gNSsj2y0gQQWqN2lGhhBnnaCUxh3ElxIYQsCr85Fy
W8wWtPxn6mdFHc/iSUh0edeiExWsbPTdfEAj93J5bidXAi27uxTC8X2vHBBIbnZi
pb9zmZxBjDjslEnN4vVc9weW5N3nKcu+7QXJdiHFP32YSET2Opu3OIkJji4rpJqx
G1Z7MvPzAgMBAAECggEBAM3XhRO7+1QSXCaZdCZ6WuWXzojxrkf8++gpzXPCZ75L
vvMyP8xmXc38Za5VyL+MAr7joENxY5NPON/9AgyUBFdbat3RW323vAt0Ssy8Dfti
ScpuGWTT2CcWS/iJPwJp9bzPj6qJ1wo0Rzsv23FpcjgfcuB+4pHpDwJZ8IxcclTN
jv5XdmanN0Ai2ONDkIHQyvMTsYAX99OK7nXIs3OW7s4wsm8Wg+loCqTvojTzWuwE
TZNFonHAZ81jkrYfNjz+sM/tPuOYD+vWQ89+1IeQKFw1U0iBpF1VvhA7UeQZMeI8
S1NpDQTQW0kxmUAlLj7ldnIvknT/x0lKzoafVpk47/kCgYEA+SxnMLHe3Wxb4Kkf
7Gwktbth/wlWzUWzQ7c0TdhfEDjcRB7SeGIjrL4/HPyXEsCcGIj84TEob1EA0KVP
l6Jeqh5t/sr9da+uLFf6H41yZUaTccoyclnjHsqT+WLTtiTKqf7cXACg5NKbJwUT
ldCEu+4Ovur+8Ax6s/mGWNEzar0CgYEA2uOmD+SCIhj16P+3GnpZ0UzyDhUKedTy
LisZznroF6RI3BHzNT+YotHORDMiJtmX0slFcInAWaB3htLPbHmvredjlsH35eHW
B6wkWmbniJEovPysWdg7xjrj8DoL2dcm6liM1KpSo9k6XWJu36//xF4RTnL8JPEH
RPuBWmBXHG8CgYBjJy886lr0I61//eztKK+G/bTmRvIapzTJqnqOy54wl1/XX6iD
LRJjKCV3RHBdjvXOsZxnhCdB/KrlXBMLFRq0eX1t2Zr4nNsjXDL1IVU3Rdlge4SN
ioVdeGFf6Nq0bXmUIg3QMpPT2pbQ9S0w/ZQEMJv/jwW5wk2FlrLGXyElxQKBgQC3
skUzITp1Ey2NFM290uB93m1llBLum9+DD3jg6BTPgngC+K17Cpw2SI0qfx8yK3pW
08MK5xAeJ6Un6NNa3eSptX7GjpJUwmq0lasMkz/MRMZDlGmwHOBNRC729D/t2bo3
AYlvEGG6UBvDM1CJOVMUoT008Rrahczr/4ZXKnLw0QKBgExc+SXb5IRJIMHEQLkg
E7va23sR7x4j75mK6HnSwAM3jKx4GDgpkY1EO+rh+99mq/bIouL8ob/PG7A5RtKp
+Sgpqk5N6NpSFMaubsu1EQhqT5pmy0dN5KXecR4s1IylPvth/h3tdXPKGcLMD2M2
EN59YIA1o4qWjJsfEiuQ6x7M
-----END PRIVATE KEY-----

View file

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEWDCCAsCgAwIBAgIRAK1MkqoHP+DPILewhMcnnu4wDQYJKoZIhvcNAQELBQAw
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMTA4MTEy
MTU0MzRaFw0yMzExMTEyMjU0MzRaMFcxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
bWVudCBjZXJ0aWZpY2F0ZTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXgg
KENhbGViIERveHNleSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDV
DWPhOpNWAYNTQZ4BR5ZU13HqRg/6B49duFcHPY+hkbQPSZdN+GZjCeRVIK8iAkgM
3cvyRs40dygZeogu9LYo6AN/h6cVCF9ENg5jo7/PjK5/6aIf8/Ss22tCuhUL7UHV
6ttf6y0+4Nq1hRQcbyIPij89nmO+mT4Fhs9gNSsj2y0gQQWqN2lGhhBnnaCUxh3E
lxIYQsCr85FyW8wWtPxn6mdFHc/iSUh0edeiExWsbPTdfEAj93J5bidXAi27uxTC
8X2vHBBIbnZipb9zmZxBjDjslEnN4vVc9weW5N3nKcu+7QXJdiHFP32YSET2Opu3
OIkJji4rpJqxG1Z7MvPzAgMBAAGjcjBwMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE
DDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSFhlhYgEfKTplVOeneVG3+3IE/TTAo
BgNVHREEITAfgh1pbnZhbGlkLmxvY2FsaG9zdC5wb21lcml1bS5pbzANBgkqhkiG
9w0BAQsFAAOCAYEABsSByXWA7e8hpKWZK4APWzkvDwiTGrDDE7k0hueJksTZ5Nqw
fRdGoUpweWIYzAv1etPAr+B2gsZM/jVRidaGDI1tKPytZ3pP6mQ52CVXkeJQytPr
rNDnP3Lbpbs8PHoHw3PVxIyRps1ZbZkgbUsXrSvpp/l+ZObbGQjr3Fdx5oXI6a1V
NNC39LkPhjTKtcG+H8dO5GRuDb/9PrzrnDwnl6CoORbEjTKRIFuA+vkFBRjyuccr
GQiMNmMxy5CMOsK+Od4+8qhv2ZgnREHyBnjFFhgVLFJ2PwUxk3N4GIzCC8tsD+vb
+YJgCS7n6JmcB9SFeyRy+qpolnfEaMvRwnJl6Evj17VCBy7x0gEO6B4lILPpziN8
VVhSuRsC0V8aXJJx89mwrg9pzN9w771rFVOCrAEdZei34/yfo8VyBbIR1gUxkRNJ
crTI9pT0PK+9OWQ57HtnGmFsPtWT8r7P8xukAPy50wSLF3InjEo8VR2df+V7DVVU
aTjNbuaG1NLNyWLH
-----END CERTIFICATE-----

View file

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIPRGWwLh75nNXnkk37zDfN8onLwfCiaLPTD+nc8Lx5hcoAoGCCqGSM49
AwEHoUQDQgAEkpBkO0TKmh4JdQfLOeeMd53Knga1WdQXr5Fcepk+dLVKdVKxXCGq
h1ojuhuW11GIoOzS9GoSKlNVSRFWVEWDvw==
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8HLBAIzXkPeeg
ldUfRKK2jQxSVZD5g+qsjAzpmrq/AtmweK1cGcOtZ6eOL+p8brPDyVhDT0QlI/O/
EKgCOFFxUDqoR82iY06SacAjHni6+PO9tVRbFV0w14BDAJSpB+VvWyl+FoPDV/vs
Z31FtYw+EwqkbDx/kaT9uzf+LJdlkf14nQQj8Eky/8d3mWJbb/9tjObsaQgJ5LLx
CYdImkr77X2LMuDw/1tpH642GE25Nrgm6QHlyKSfYXo38v83ebEqbZUDG+ZioArP
mqmkawUWw3ekhj80SJg/TK9PRaN/VvcI1PgAd7LZztUReSmTy5hd9r6rOBxpxwnT
DvHkBn6vAgMBAAECggEAB28i0AYUNSb1JnWFbKzruUctu3tCNXovJg6K3BiPVMkq
DT1XrJIgF5RHHOlr3OsLE6u7Xz2ctdML6PshiKTtIwtGpivgRpCiJEslmr2zi8AW
8eJeqRLZEfsSSJOXTG7RdGsn4qHFJ00s2ZTlcIHSPwnFm+XjJi99U8G4XsUoXo0r
Gy+0VCuU7M8gICEHHsrQO9XDD3nT2jiu5TjrKwjut3EmoJssI5bqx33+OBu5BpCP
CT473D43P9p3qi/XnfvqGSG2Oj4OajV4fr0o9B3KvIxkMem7WlI3jyy1kApyXqVT
bLkLFyWBNTWUZ2R/2wxmuoC6mLZw879MLCKMvk1doQKBgQDhmwGafJNymTiEQZRI
SsQx4seqfOKfgFC7ohqH9cROOu8IJ1o7q2pM2W4XiV+S3wTdPGmca6IOjX23isVB
2uqNi9S4MnI2/d22Gd/BR9rvBw1eGJoKbrWx22fE8QCEWT1AnO+DuD0jC85yRls7
axzlaMrxEu3LI9UE7NtrdQiByQKBgQDVdI6ceIVBT6RgvVGt8zkLjPIFjhQEHAIp
uhirgqpS6CX9Blyf2+o40zmfj3he5rCcEoB5MseM+DgFbcVh2e/MVnYiNNw6JCDB
BQkF408pZpSeKXvL/oyV/kImMTJ/tUDY0EXxMwSPJB0WltbWreVIHopigXRCbaey
uBHVBv/4twKBgHwHuePy5SU1s2qSmzD7Wc2LPfYu3nCOHNRrFGb26MuRfuReri7r
2G8TgoESFycp0QTIN8+1JM0XYKxNcJD6B8V1wKbbpQsymneI1gjutiB/Igw/PkDK
CL4VP4F4da5NWW1yWgNygLoJvZ/5qiKKisJc0GWk4HKz6mLgzOjQ2LJxAoGBALHZ
fN2YeYbyYcaM11p1VilulVTVjY3i/FZiDR4SL/IGJWjN/Szg4iXYsKFmu+dulOZl
cBALpEKrqpmzXYtrN6bsv18+5eO3qGbK2DrEq3eWVev2KoTMobxz7g++XBIWJmLA
Hhaa6IiPkYD5yyVyHKDbeXgb3o9eqCR7w7fYLjy/AoGAI4D+MFkivwUF7hqf5edS
KrltwmodHiqXNbVkwbW1AFPJbiYai4YFfK4IAbif/Ymxf9G78aOkr9ZpCIzOkDPZ
YpEwQGWsAhElCFvc8E/5dHESSp+tWtP+NluimpFqiDg3/SUnMwO2xH0nhLa0zejh
gmLh4w/CcPyb9ZyXceWU/nU=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEUjCCArqgAwIBAgIRAKNaEqCmmZfhmcYgZy01WCswDQYJKoZIhvcNAQELBQAw
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMTA4MTAx
NzMyMTBaFw0yMzExMTAxODMyMTBaMFcxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
bWVudCBjZXJ0aWZpY2F0ZTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXgg
KENhbGViIERveHNleSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8
HLBAIzXkPeegldUfRKK2jQxSVZD5g+qsjAzpmrq/AtmweK1cGcOtZ6eOL+p8brPD
yVhDT0QlI/O/EKgCOFFxUDqoR82iY06SacAjHni6+PO9tVRbFV0w14BDAJSpB+Vv
Wyl+FoPDV/vsZ31FtYw+EwqkbDx/kaT9uzf+LJdlkf14nQQj8Eky/8d3mWJbb/9t
jObsaQgJ5LLxCYdImkr77X2LMuDw/1tpH642GE25Nrgm6QHlyKSfYXo38v83ebEq
bZUDG+ZioArPmqmkawUWw3ekhj80SJg/TK9PRaN/VvcI1PgAd7LZztUReSmTy5hd
9r6rOBxpxwnTDvHkBn6vAgMBAAGjbDBqMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE
DDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSFhlhYgEfKTplVOeneVG3+3IE/TTAi
BgNVHREEGzAZghcqLmxvY2FsaG9zdC5wb21lcml1bS5pbzANBgkqhkiG9w0BAQsF
AAOCAYEAufQAF79s7c1gmZ9CIKBSGkHh+SH01CuKYnnHiMowHsTioFaUAQsd/P4X
c2XBqc34eT3mCvpgZjHbjz6JlnTYJxuLvVqnVB3emtWrb1cQvh8BphxspTlS8uiE
AEf/ngtpzfA/f4lpGkzrQ0cyPkEJGz511q97itzn9RZZzVTZxNVFSP2vVhNNQVsW
OxakcvYRgnz8AOQS3OPHj2FQc3iibshct5leIwYZFcxINGHR6KL6+/LSePNCEMmK
qymVPkQGsIcU6GQ9fxaSu4mp+IUALProizEVI8SVk5nOm3HIez+ZfXhzfnGx06SI
6NuoQQPqUBeZeXn2YFYhipeRdrQxvA36/YXa/AkXCeU0pXxbtXKcvatfri5KnYJD
kH59a+aFkTsl41tfI2cnRYVddqXVl3OzLbcgAFLn1WeC1xx3xRXi7KldokOlvgv+
B6naWfCxRlWZ/lsmHae4kc1WH4Kc7nK+ITb40EkjV68/A7krZsN1VcqNtpomYkgE
xjUE8XUu
-----END CERTIFICATE-----

View file

@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDWYpVeBSnee2cA
BYofSoWxGMyFaMQ0nJkY0UWM9ckyUh7VfgN+/aFSW2ZSmXuv5drcpi20z3elhPTe
98bANbj+/bi0015QWnMenK05ZK6qDtFwo/HVC/Ycaruu96+1J2toeWuEtykW3MCp
C1pHYS5g9iVDkpdrznvXKlYuSikjrj7K5toiTvum97LxKkuj6DXjapPD5vteSN1d
QgO9CS3sqlcwYA6RjUHwY2VEh2adP37BZrZwO+yJq9qF5y5Glgi8lN4cKlIlFUs/
xSpQsxNbNQXtN9mk4imYlZGzYYbbm+foBVPPboa5jVwKDpZ65mOs7JGP6yj+7V7U
BMFpW+gKmJtgh/kkAx185h93qwLFPc8/T7n++P1bu+fakXPGPE21rDeLPnUmucIZ
pJo5NpYVQv4WvTKq/zMR9Sspz2PFJnERTfTvq+F1q3ZNafEziPsB9oeSnjxwmaZO
SV0vXq/qeoqx4v6MBzVAY0/8R2LcpJ4ug0OZ3w0b2t6yo86P5Q8CAwEAAQKCAYEA
mE0Ey/xjGDkWnT9SNpSckYmKkiQxbybo5GaXQGLEAkmwuf6BwU+xsW5ZLMj4w6dJ
aoNr6Q2SdDYWN+hSe+4udIgPFfcrA26eZdrsfN937jwEsj7l6HJM7zcsCkrPuqQ8
e8X2ihwMxr8g53a3NgpmBmAXbP/RLrdL5zmea9gnjb+VwFNsF/+Aa8eAii9/+PtY
fS0TuJJ5dSvShxQEz+CbjYwd0LIM534wn/Qc1yhRz9hx1jd/2A/aJJu/7GylxWOD
mDwFOMcV0rU18r2e3u78GYTNozWCNE05mNhm7j8fYA4XVqR9wURFkBOPRSqg7Bd3
kyQmFQYonPW7xEHKOBGR2f3QUhB+ZKsM8ATpfXUmdoeVgx9Po+5FiqQVaNny1OIJ
vJeeYU++93ijZqvxvmgVmg97PxeA0CctTp2+ZFwmn1ZNTE7Zon/6lJ7/u999EyuX
s3uApNmjpBjA6davvgSIMdbn810f25yn2XVbzDrRJKSUM+Z6twUYZV53rxMWMQBR
AoHBAPRxozUFEUdz50dcuJYv2atfXR64T81XXD4i88K4DhEX60xCkTUFgVTgE0Y1
ZXgfe0ZNdGDm0zkQJsUcmxvtGCczYnbJZzOVXKfwyO8+z7dKXdXt0BQ7V7JvH50Y
KNh2QCtN1I7nZ5og7snDQkjOcrWD/JNNNkrW680O3lXeq4Cs8A6fCT73bX/KE53K
MltyxnfMpmGBqx+SwqLelvMY6fD44lhdIlOBi5/ACWCSMh4KnmIBVJTKKaNFdjKI
cv5bXQKBwQDghSlkKHuzdkzui8elNk5vfGl/IyUC3Ncx/ViF8kvZyErs4DgTKFT6
FmLx6mEboM4PQzofFLYtgmEkqVIo0FswrGS3BYgL5DGI3BSdWLO+Wqk07A3nRvhS
LvgM05Gwrin24Udu2ftrXbj6WVGix5en5xgLD0zopxDx5jISUADzH90V1ty6E5UX
W5JU1jVmRlWftg7kwfMJocm8PqgT49JSf9ryEV93Y6x5n+oxAAoorjUFmbS0j/Wo
VskI8FG/Z1sCgcBDQMyldrp1TTcxlBoZABtEIh2tqQoTtdhkJBuq1BbSryEGvz3S
N6yInInRBDnhnc+93OuLCZbNmVsBWkh2m3nMtz987Raew5ZVglLWOBLQG/7LL/3S
wyzyo84v24jJXWd0QpqboqEHb84i5rzi6SH4PNMN9+1bE9yWc2PKflPzOCFn9GuH
zm1q/j79Z7cJH//oz/5qz1E0g51XUCR5x739lYw4wY8DKJ4wmpY54p81UriWwET0
Ftbz29WUO6RfxOUCgcA/GO1C+qWZD9wbBil7YsG0TzCOzF+waEQKBnsEWc27TLDR
1UmtCJ6pEfWIqyfTTePbIjeJWJbCP2vxk6xFUBjwmuJLFUDgpqbNIZyhg7Yv/uai
utxFbQqIfi6z3BmLn8anXTRoENa5m8NKiCOLLbCPPUDiitBAagM3GExmHRnHOeM2
KgYqPSqfP9rmALVNIuMZWV7iJyeYQ2Ggh7NQs6v+B3SOpxc/REHKhIiacLpqKFs6
UbIZNIQDZTBmVLOEqRUCgcA8CZ4Q3OyltYcI0WkrJooNv1u9xdhW0Ch+wJ8JjXlw
6+BBsXQDci7UWH2sK6YFLCZFoL/K6BFHywi91R6vDGV9cPYL4LY4EEl0HHEuqGp3
o80O6s9fTwZYJYhxRBSRpI3f87LWM76CEVCwQl4D59eQXtvVLod5TL+71jMFddhb
L9a2u7f4Nz6iPNZyDrqiqzBzaBUpd79Xt1WGfdB0gyjm9YXAHh7UfS80xtGh5DTJ
dzutiHsOfQuQZN0aFKkMw18=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIE2DCCA0CgAwIBAgIRALd9GaJR92qi7qL1eHGM6K0wDQYJKoZIhvcNAQELBQAw
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMTA4MTEy
MTU2MTBaFw0zMTA4MTEyMTU2MTBaMIGDMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxv
cG1lbnQgQ0ExLDAqBgNVBAsMI2NhbGViQGNhbGViLXBjLWxpbnV4IChDYWxlYiBE
b3hzZXkpMTMwMQYDVQQDDCpta2NlcnQgY2FsZWJAY2FsZWItcGMtbGludXggKENh
bGViIERveHNleSkwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDWYpVe
BSnee2cABYofSoWxGMyFaMQ0nJkY0UWM9ckyUh7VfgN+/aFSW2ZSmXuv5drcpi20
z3elhPTe98bANbj+/bi0015QWnMenK05ZK6qDtFwo/HVC/Ycaruu96+1J2toeWuE
tykW3MCpC1pHYS5g9iVDkpdrznvXKlYuSikjrj7K5toiTvum97LxKkuj6DXjapPD
5vteSN1dQgO9CS3sqlcwYA6RjUHwY2VEh2adP37BZrZwO+yJq9qF5y5Glgi8lN4c
KlIlFUs/xSpQsxNbNQXtN9mk4imYlZGzYYbbm+foBVPPboa5jVwKDpZ65mOs7JGP
6yj+7V7UBMFpW+gKmJtgh/kkAx185h93qwLFPc8/T7n++P1bu+fakXPGPE21rDeL
PnUmucIZpJo5NpYVQv4WvTKq/zMR9Sspz2PFJnERTfTvq+F1q3ZNafEziPsB9oeS
njxwmaZOSV0vXq/qeoqx4v6MBzVAY0/8R2LcpJ4ug0OZ3w0b2t6yo86P5Q8CAwEA
AaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0O
BBYEFLcY8EoNofMcrrxzyxIn3W6ZOMVXMA0GCSqGSIb3DQEBCwUAA4IBgQCZzDCv
KIHX3GvjNSY5w5bOn4E3w7QHP09ABjT/wuT4LDkZHJMmlrLo3s8bcsQ0sMD1Y///
s07cp4xYlqD7BA0AcpvYVYq58xKxsoCwVXmG5cEeOoZmWf3qY2mS8eW96vOFrdIb
L4OF4xYUOMRqAOGAAr6VlO7gXa406HzrsA1hYZwreXhOTCZZPZOUnAu05SHFdgaM
TJNB/o01tpwQlrTxNmfropoOzyuvH0zU2RrMs0+EbOuC4A2cQ83DIFxvq67lyU0A
s1Q6tRM0+UDmJOLz3SdgN+D00hcuuj92GV4bH8BfyUv8NCY0vDij0TSjj4c4Qtc7
IPLTZ2g545oczhNgAmT7d+B5InyfiSIKemXqes2jpiAfzPNl9BVxsakcs/YzoYs1
+qTjAWuaDsKohEnO4BJuzv0xrce40enRgXyGGFvXu2s4FY2vJqTSo6ysDWnhI3LW
dcg6O2F4APCGGe7zsuqiqkpcknBabgzEs9foHq2mfo7XiEzedMN8BNqfSbA=
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCgVDM59lGzCRjd
UQCyzokqC4sEr7Ln2FpXfHjMWPuNK5vQYccTxto3JlAVXl+oOLHGoryKGDOkRV1S
Db3zAxYQNDuYUnraiVgLPrM9NFpHSk/IlACQjRlFRYG0Go3PDR2vJX4qTwgrqQtL
OJ5tHnqrt6idtvNp1ISYOIscXf/WIAhh+IuOvas4eie7GETX4eqPqpc6AEFuklmo
BHBfMCrGg89WBUTWCZYrHE9BYDL0LG/VwLYn2tDKBrS9iZIlTqPwve8VoGIlx4uv
HTdzaVStcRKOXCsbSwXRdt0842d4C1nohQkRHqHoBjQzrJiWJzxGmgByfa2rxbNg
15PFwF+ZAgMBAAECggEADTzGefunZTPUFLnSZ/D7jDglwz5KdC/9zYleY+jY5B/8
nmjkSfK6I6GLLSh8l2QO8YqQLIqxANglS1gNHdpcYPwfC4WL1S1P0qXboKsI5Sfy
jGoD3et4caq6ecdTfAvmLobW8uFRmGE9qHlFQ1cn47OnPVZUpKFCTVslyTLNo70h
28gx/lnpgkbeWotJ5GygE/H0jKJlG8/V3+Ppfuq6wypA5ELcGUeMAwmCfUNNlDy3
BhXSa6STgL26ar70KZIjTp9B97hIfDWObxgjzMX2JoiWXziszvbfaknfBsmfTm45
oUZYO0DuvLdLpxic0GZQwZCT6GzuexxJ9zR/pdahrQKBgQDEiwc0e+M1KaOoIIcw
V7pxoGjvd+CC5whS00jSf/rXPSPFxat9Ml5serOzLdRLM/NQ5wB9S7TYc6PJi3Mb
8pmbGadIXiGIJY8vX79P/velHT4csgULJAKJF9U65knhaidPPPmXloHOhRWrE8Zq
mexVgJZrHLI8197qmi+ctT5rEwKBgQDQ1J84AwI1hEsXHxoSetSznt+ae7pSUb/J
byqK9KEp0DLyf8GcS7vxyYGQo0mJDlHaJt56LKv+zdX4wGG85ztbOFVPee6XLKSs
I+h7rzc2hKrl+SaI91h1234WsTeJvfUSHyBy9vAwLhd0hplNrt7Tql5Z0VTWHmFE
2XbEwcTUIwKBgQDBpioHMDmBW/F/6ezJWOa+pco+h+KRl4i/8qVBog9Im1jvt/9r
b4FRaOQ9mt4c6qbGA5Sb30fkLKwoHFniI3ntM616xCRNvJQDnVcmPpVJ/jIAm/YU
L/q/kNfrHJOWobzxeaaCESz8imv7D5Tj25zb8cJC7xc+k4Nzq09WG83QOQKBgG28
LOZ7/j8tA2BlAYhQb1Dr3UgKWEBFoOgyuEJIhh+4vezb4VtGGL7XSnQ8ubmBgtWF
s0a0DrVYaGXMgg+H2pL2qS2YPx3FYcrrG5FS40qMsFkkcXFruFpGOp2mBi8lWJBr
NtvykwheUAj1ab1+dKz5S5ca/t99G1PYiiaeQ9XNAoGAVXk4HvdUc5q+BNiYvKUS
M2/TDU3cYY72mPCEw7G6Kpn6zMaakQcA1+Z8LkYcLaQKRD/66n99WWT+BcY+QXtC
0ZPHjeepDL8q+yXRY8zlcgAukg18Ta5yD1J1014y8UIV+HY8ongTni1sI8N+vKd4
+TF2C2Cynf5vQr5man7ShPw=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEUjCCArqgAwIBAgIRAKKYU7PSAFxZbhuLUlbv3iAwDQYJKoZIhvcNAQELBQAw
gYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjY2Fs
ZWJAY2FsZWItcGMtbGludXggKENhbGViIERveHNleSkxMzAxBgNVBAMMKm1rY2Vy
dCBjYWxlYkBjYWxlYi1wYy1saW51eCAoQ2FsZWIgRG94c2V5KTAeFw0yMTA4MTEy
MTU2MTFaFw0yMzExMTEyMjU2MTFaMFcxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
bWVudCBjZXJ0aWZpY2F0ZTEsMCoGA1UECwwjY2FsZWJAY2FsZWItcGMtbGludXgg
KENhbGViIERveHNleSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCg
VDM59lGzCRjdUQCyzokqC4sEr7Ln2FpXfHjMWPuNK5vQYccTxto3JlAVXl+oOLHG
oryKGDOkRV1SDb3zAxYQNDuYUnraiVgLPrM9NFpHSk/IlACQjRlFRYG0Go3PDR2v
JX4qTwgrqQtLOJ5tHnqrt6idtvNp1ISYOIscXf/WIAhh+IuOvas4eie7GETX4eqP
qpc6AEFuklmoBHBfMCrGg89WBUTWCZYrHE9BYDL0LG/VwLYn2tDKBrS9iZIlTqPw
ve8VoGIlx4uvHTdzaVStcRKOXCsbSwXRdt0842d4C1nohQkRHqHoBjQzrJiWJzxG
mgByfa2rxbNg15PFwF+ZAgMBAAGjbDBqMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE
DDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBS3GPBKDaHzHK68c8sSJ91umTjFVzAi
BgNVHREEGzAZghcqLmxvY2FsaG9zdC5wb21lcml1bS5pbzANBgkqhkiG9w0BAQsF
AAOCAYEAizMhh+VYIMp07wGn7+rzAE/651yiMC6kZHIOMHilvimyYvCf+Yc0MrcD
mVQgqlUpkn/f2SOFsBQonjAACkWlSHah9KStL0iTvOIH+oGLnv3Y9wrKvwJol3KR
c/+mO9R9TS71DoX+rTGRY3BNldpMBZF7HsYt/bg0RSpF0zkZarW+PEMmPw6IgIaD
RPGpOiQOqIxQn4d6MyiNGS0QmDeGSZvsC07ZcZ+JxsYi4S+yN6GXt11pstiRXjDv
zrO3s8TnVsBux7VDdIYfzMxqz+874MbsUUlb4txr3V48UDRLm7VDQ2/F+o0+Y5wt
XAnXTn/6GFpjJvPGr0A1QLOvnhR0DZ4Fl97athu44pqeQywDU5LPP3HqrWRXLy3j
BPBC4waHayL9Hnh4zQUe/h6hwC5Nxl/gqfB3Aaqr5PWX6rMFss8AYpB81ci+UJdm
KSIn/pMoK6TWkCveoQRQOZD8wfwPF4cUUmWcLFwSveZSiniFrAXQqZbO1k6RDhQf
havcwKlK
-----END CERTIFICATE-----

View file

@ -0,0 +1,119 @@
local MergeArrays(merge, arrays) =
std.foldl(function(acc, el) acc + el, arrays, []);
local MergeObjects(merge, objects) =
std.foldl(function(acc, el) {
[f]: if std.objectHas(acc, f) && std.objectHas(el, f) then
merge([acc[f], el[f]])
else if std.objectHas(el, f) then
el[f]
else
acc[f]
for f in std.setUnion(
std.set(std.objectFields(acc)),
std.set(std.objectFields(el))
)
}, objects, {});
local Merge(items) =
if std.foldl(function(acc, el) acc && std.isArray(el), items, true) then
MergeArrays(Merge, items)
else if std.foldl(function(acc, el) acc && std.isObject(el), items, true) then
MergeObjects(Merge, items)
else
items[std.length(items) - 1];
local KubernetesDeployment(name, image, command, ports) =
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
namespace: 'default',
name: name,
},
spec: {
replicas: 1,
selector: { matchLabels: { app: name } },
template: {
metadata: {
labels: { app: name },
},
spec: {
containers: [
{
name: name,
image: image,
ports: ports,
} + if std.type(command) == 'null' then {} else {
args: command,
},
],
},
},
},
};
local KubernetesService(name, ports) =
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
namespace: 'default',
name: name,
labels: { app: name },
},
spec: {
selector: { app: name },
ports: ports,
},
};
local ParseURL(rawURL) =
{
local splitLeft(str, pat) =
local idxs = std.findSubstr(pat, str);
if std.length(idxs) == 0 then
[str, '']
else
[std.substr(str, 0, idxs[0]), std.substr(str, idxs[0] + std.length(pat), std.length(str))],
local splitRight(str, pat) =
local idxs = std.findSubstr(pat, str);
if std.length(idxs) == 0 then
['', str]
else
[std.substr(str, 0, idxs[0]), std.substr(str, idxs[0] + std.length(pat), std.length(str))],
local p0 = ['', rawURL],
local p1 = splitRight(p0[1], '://'),
local p2 = splitRight(p1[1], '@'),
local p3 = splitLeft(p2[1], '/'),
local p4 = splitLeft(p3[1], '?'),
local p5 = splitLeft(p4[1], '#'),
scheme: p1[0],
user: p2[0],
host: p3[0],
path: if p4[0] == '' then '' else '/' + p4[0],
query: p5[0],
fragment: p5[1],
};
local ComposeService(name, definition, additionalAliases=[]) =
{
[name]: definition {
networks: {
main: {
aliases: [name] + additionalAliases,
},
},
},
};
{
ComposeService: ComposeService,
Merge: Merge,
KubernetesDeployment: KubernetesDeployment,
KubernetesService: KubernetesService,
ParseURL: ParseURL,
}

View file

@ -24,5 +24,4 @@ ENTRYPOINT [ "/bin/pomerium" ]
CMD ["-config","/pomerium/config.yaml"]
EOF
docker build --tag=pomerium/pomerium:dev .
kind load docker-image pomerium/pomerium:dev
)