├── .github
└── workflows
│ └── pr_build.yml
├── .gitignore
├── LICENSE
├── README.md
├── docker-compose
├── federation
│ ├── .gitignore
│ ├── 1-start-spire-agents.sh
│ ├── 2-bootstrap-federation.sh
│ ├── 3-create-registration-entries.sh
│ ├── README.md
│ ├── build.sh
│ ├── docker-compose.yaml
│ ├── docker
│ │ ├── broker-webapp
│ │ │ ├── Dockerfile
│ │ │ └── conf
│ │ │ │ ├── agent.conf
│ │ │ │ ├── agent.crt.pem
│ │ │ │ └── agent.key.pem
│ │ ├── spire-server-broker.example
│ │ │ ├── Dockerfile
│ │ │ └── conf
│ │ │ │ ├── agent-cacert.pem
│ │ │ │ └── server.conf
│ │ ├── spire-server-stockmarket.example
│ │ │ ├── Dockerfile
│ │ │ └── conf
│ │ │ │ ├── agent-cacert.pem
│ │ │ │ └── server.conf
│ │ └── stock-quotes-service
│ │ │ ├── Dockerfile
│ │ │ └── conf
│ │ │ ├── agent.conf
│ │ │ ├── agent.crt.pem
│ │ │ └── agent.key.pem
│ ├── scripts
│ │ ├── clean-env.sh
│ │ └── set-env.sh
│ ├── src
│ │ ├── broker-webapp
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── main.go
│ │ │ └── quotes
│ │ │ │ └── page.go
│ │ └── stock-quotes-service
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ └── main.go
│ └── test.sh
├── metrics
│ ├── README.md
│ ├── docker-compose.yaml
│ ├── images
│ │ ├── graphite_graph.png
│ │ └── prometheus_graph.png
│ ├── prometheus
│ │ └── prometheus.yml
│ ├── scripts
│ │ ├── clean-env.sh
│ │ ├── create-workload-registration-entry.sh
│ │ ├── fetch_svid.sh
│ │ └── set-env.sh
│ ├── spire
│ │ ├── agent
│ │ │ ├── agent.conf
│ │ │ ├── agent.crt.pem
│ │ │ ├── agent.key.pem
│ │ │ └── bootstrap.crt
│ │ └── server
│ │ │ ├── agent-cacert.pem
│ │ │ └── server.conf
│ └── test.sh
├── nested-spire
│ ├── .gitignore
│ ├── .go-version
│ ├── README.md
│ ├── docker-compose.yaml
│ ├── images
│ │ └── Nested_SPIRE_Diagram.png
│ ├── nestedA
│ │ ├── agent
│ │ │ └── agent.conf
│ │ └── server
│ │ │ └── server.conf
│ ├── nestedB
│ │ ├── agent
│ │ │ └── agent.conf
│ │ └── server
│ │ │ └── server.conf
│ ├── root
│ │ ├── agent
│ │ │ └── agent.conf
│ │ └── server
│ │ │ └── server.conf
│ ├── scripts
│ │ ├── clean-env.sh
│ │ ├── create-workload-registration-entries.sh
│ │ ├── gencerts.go
│ │ └── set-env.sh
│ └── test.sh
└── test-all.sh
└── k8s
├── envoy-jwt-auth-helper
├── .gitignore
├── Dockerfile
├── README.md
├── envoy-jwt-auth-helper.conf
├── go.mod
├── go.sum
├── main.go
└── pkg
│ ├── auth
│ └── ext_auth_server.go
│ └── config
│ └── config.go
├── envoy-jwt-opa
├── README.md
├── images
│ ├── SPIRE-Envoy_JWT_OPA_diagram.png
│ ├── frontend-2_view.png
│ ├── frontend-2_view_no_details.png
│ └── frontend_view.png
├── k8s
│ ├── backend
│ │ ├── backend-deployment.yaml
│ │ └── config
│ │ │ ├── envoy-jwt-auth-helper.conf
│ │ │ ├── envoy.yaml
│ │ │ ├── opa-config.yaml
│ │ │ └── opa-policy.rego
│ └── kustomization.yaml
├── scripts
│ ├── backend-opa-logs.sh
│ ├── backend-update-policy.sh
│ ├── build-helper.sh
│ ├── clean-env.sh
│ ├── pre-set-env.sh
│ └── set-env.sh
└── test.sh
├── envoy-jwt
├── README.md
├── create-registration-entries.sh
├── images
│ ├── SPIRE-Envoy_JWT-SVID_diagram.png
│ ├── frontend-2_view.png
│ ├── frontend-2_view_no_details.png
│ └── frontend_view.png
├── k8s
│ ├── backend
│ │ ├── backend-deployment.yaml
│ │ └── config
│ │ │ ├── envoy-jwt-auth-helper.conf
│ │ │ └── envoy.yaml
│ ├── frontend-2
│ │ ├── config
│ │ │ ├── envoy-jwt-auth-helper.conf
│ │ │ └── envoy.yaml
│ │ ├── create-registration-entry.sh
│ │ ├── frontend-2-deployment.yaml
│ │ └── kustomization.yaml
│ ├── frontend
│ │ ├── config
│ │ │ ├── envoy-jwt-auth-helper.conf
│ │ │ └── envoy.yaml
│ │ └── frontend-deployment.yaml
│ └── kustomization.yaml
├── scripts
│ ├── build-helper.sh
│ ├── clean-env.sh
│ ├── pre-set-env.sh
│ └── set-env.sh
└── test.sh
├── envoy-opa
├── README.md
├── images
│ ├── SPIRE_Envoy_OPA_X509_diagram.png
│ ├── frontend-2_view.png
│ ├── frontend-2_view_no_details.png
│ └── frontend_view.png
├── k8s
│ ├── backend
│ │ ├── backend-deployment.yaml
│ │ └── config
│ │ │ ├── envoy.yaml
│ │ │ ├── opa-config.yaml
│ │ │ └── opa-policy.rego
│ └── kustomization.yaml
├── scripts
│ ├── backend-opa-logs.sh
│ ├── backend-update-policy.sh
│ ├── clean-env.sh
│ ├── pre-set-env.sh
│ └── set-env.sh
└── test.sh
├── envoy-x509
├── README.md
├── backend-envoy-configmap-rbac-update.yaml
├── backend-envoy-configmap-update.yaml
├── create-registration-entries.sh
├── images
│ ├── SPIRE_Envoy_diagram.png
│ ├── frontend-2_view.png
│ ├── frontend-2_view_no_details.png
│ └── frontend_view.png
├── k8s
│ ├── backend
│ │ ├── backend-deployment.yaml
│ │ ├── backend-service.yaml
│ │ ├── config
│ │ │ └── envoy.yaml
│ │ └── json
│ │ │ ├── balancedata.json
│ │ │ ├── balances
│ │ │ ├── balance_1
│ │ │ └── balance_2
│ │ │ ├── profiledata.json
│ │ │ ├── profiles
│ │ │ ├── profile_1
│ │ │ └── profile_2
│ │ │ ├── transactions
│ │ │ ├── transaction_1
│ │ │ └── transaction_2
│ │ │ └── transactionsdata.json
│ ├── frontend-2
│ │ ├── config
│ │ │ ├── envoy.yaml
│ │ │ └── symbank-webapp-2.conf
│ │ ├── frontend-2-deployment.yaml
│ │ └── frontend-2-service.yaml
│ ├── frontend
│ │ ├── config
│ │ │ ├── envoy.yaml
│ │ │ └── symbank-webapp.conf
│ │ ├── frontend-deployment.yaml
│ │ └── frontend-service.yaml
│ ├── kustomization.yaml
│ └── metallb.yaml
├── metallb-config.yaml
├── scripts
│ ├── clean-env.sh
│ ├── pre-set-env.sh
│ └── set-env.sh
└── test.sh
├── oidc-aws
├── agent-daemonset.yaml
├── client-deployment.yaml
├── ingress.yaml
├── oidc-dp-configmap.yaml
├── server-configmap.yaml
├── server-oidc-service.yaml
└── server-statefulset.yaml
├── oidc-vault
├── README.md
├── k8s
│ ├── ingress.yaml
│ ├── oidc-dp-configmap.yaml
│ ├── server-configmap.yaml
│ ├── server-oidc-service.yaml
│ └── server-statefulset.yaml
└── vault
│ ├── config.hcl
│ └── vault-policy.hcl
├── quickstart
├── agent-account.yaml
├── agent-cluster-role.yaml
├── agent-configmap.yaml
├── agent-daemonset.yaml
├── client-deployment.yaml
├── create-node-registration-entry.sh
├── kustomization.yaml
├── server-account.yaml
├── server-cluster-role.yaml
├── server-configmap.yaml
├── server-service.yaml
├── server-statefulset.yaml
├── spire-bundle-configmap.yaml
├── spire-namespace.yaml
└── test.sh
└── test-all.sh
/.github/workflows/pr_build.yml:
--------------------------------------------------------------------------------
1 | name: PR Build
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request: {}
7 | workflow_dispatch: {}
8 | env:
9 | GO_VERSION: 1.24.0
10 | CHANGE_MINIKUBE_NONE_USER: true
11 | TERM: xterm
12 | jobs:
13 | test-all:
14 | runs-on: ubuntu-24.04
15 | timeout-minutes: 30
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | - name: Setup go
20 | uses: actions/setup-go@v5
21 | with:
22 | go-version: ${{ env.GO_VERSION }}
23 | - name: install minikube
24 | id: minikube
25 | uses: medyagh/setup-minikube@master
26 | - name: Envoy
27 | run: docker-compose/test-all.sh
28 | - name: K8s
29 | run: k8s/test-all.sh
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | # Visual Code
5 | .vscode
6 |
7 | # Ignore certs and keys generated as part of the tutorial
8 | *.pem
9 | *.crt.pem
10 | *.key.pem
11 | *.crt
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SPIRE Tutorials
2 |
3 | The tutorials in this repo describe how to install SPIRE and integrate it with software typically used with SPIRE. The following tutorials are available:
4 |
5 | | Tutorial | Platform |
6 | | --- | --- |
7 | | [Quickstart for Kubernetes](https://spiffe.io/spire/try/getting-started-k8s/) | Kubernetes |
8 | | [AWS OIDC Authentication](https://spiffe.io/spire/try/oidc-federation-aws/) | Kubernetes |
9 | | [Vault OIDC Authentication](k8s/oidc-vault) | Kubernetes |
10 | | [Integrating with Envoy using X.509 certs](k8s/envoy-x509) | Kubernetes |
11 | | [Integrating with Envoy using JWT](k8s/envoy-jwt) | Kubernetes |
12 | | [Using SPIFFE X.509 IDs with Envoy and Open Policy Agent Authorization](k8s/envoy-opa) | Kubernetes |
13 | | [Using SPIFFE JWT IDs with Envoy and Open Policy Agent Authorization](k8s/envoy-jwt-opa) | Kubernetes |
14 | | [Nested SPIRE](docker-compose/nested-spire) | Docker Compose |
15 | | [Federation](docker-compose/federation) | Docker Compose |
16 | | [Configure SPIRE to Emit Telemetry](docker-compose/metrics) | Docker Compose |
17 |
18 | Additional examples of how to install and deploy SPIRE are available. The spiffe.io [Try SPIRE](https://spiffe.io/spire/try/) page includes a [Quickstart for Linux and MacOS X](https://spiffe.io/spire/try/getting-started-linux-macos-x/) and [SPIFFE Library Usage Examples](https://spiffe.io/spire/try/spiffe-library-usage-examples/). The [SPIRE Examples](https://github.com/spiffe/spire-examples) repo on GitHub includes more usage examples for Kubernetes deployments, including Postgres integration, and a Docker-based Envoy example.
19 |
20 | For general information about SPIRE and the [SPIFFE](https://github.com/spiffe/spiffe) zero-trust authentication spec that SPIRE implements, see the SPIRE [GitHub repo](https://github.com/spiffe/spire) and [spiffe.io website](https://spiffe.io).
21 |
--------------------------------------------------------------------------------
/docker-compose/federation/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries
2 | broker-webapp
3 | stock-quotes-service
4 |
5 | # Ignore bundles generated as part of the tutorial
6 | *.bundle
7 |
8 | # goenv
9 | .go-version
10 |
--------------------------------------------------------------------------------
/docker-compose/federation/1-start-spire-agents.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9 |
10 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-broker bin/spire-server bundle show
11 |
12 | # Bootstrap trust to the SPIRE server for each agent by copying over the
13 | # trust bundle into each agent container.
14 | echo "${bb}Bootstrapping trust between SPIRE agents and SPIRE servers...${nn}"
15 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-broker bin/spire-server bundle show |
16 | docker compose -f "${DIR}"/docker-compose.yaml exec -T broker-webapp tee conf/agent/bootstrap.crt
17 |
18 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-stock bin/spire-server bundle show |
19 | docker compose -f "${DIR}"/docker-compose.yaml exec -T stock-quotes-service tee conf/agent/bootstrap.crt
20 |
21 | # Start up the broker-webapp SPIRE agent.
22 | echo "${bb}Starting broker-webapp SPIRE agent...${nn}"
23 | docker compose -f "${DIR}"/docker-compose.yaml exec -d broker-webapp bin/spire-agent run
24 |
25 | # Start up the stock-quotes-service SPIRE agent.
26 | echo "${bb}Starting stock-quotes-service SPIRE agent...${nn}"
27 | docker compose -f "${DIR}"/docker-compose.yaml exec -d stock-quotes-service bin/spire-agent run
28 |
--------------------------------------------------------------------------------
/docker-compose/federation/2-bootstrap-federation.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9 |
10 | echo "${bb}bootstrapping bundle from broker to quotes-service server...${nn}"
11 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-broker \
12 | /opt/spire/bin/spire-server bundle show -format spiffe > "${DIR}"/docker/spire-server-stockmarket.example/conf/broker.example.bundle
13 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-stock \
14 | /opt/spire/bin/spire-server bundle set -format spiffe -id spiffe://broker.example -path /opt/spire/conf/server/broker.example.bundle
15 |
16 | echo "${bb}bootstrapping bundle from quotes-service to broker server...${nn}"
17 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-stock \
18 | /opt/spire/bin/spire-server bundle show -format spiffe > "${DIR}"/docker/spire-server-broker.example/conf/stockmarket.example.bundle
19 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-broker \
20 | /opt/spire/bin/spire-server bundle set -format spiffe -id spiffe://stockmarket.example -path /opt/spire/conf/server/stockmarket.example.bundle
21 |
--------------------------------------------------------------------------------
/docker-compose/federation/3-create-registration-entries.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9 |
10 | fingerprint() {
11 | # calculate the SHA1 digest of the DER bytes of the certificate using the
12 | # "coreutils" output format (`-r`) to provide uniform output from
13 | # `openssl sha1` on macOS and linux.
14 | cat $1 | openssl x509 -outform DER | openssl sha1 -r | awk '{print $1}'
15 | }
16 |
17 | BROKER_WEBAPP_AGENT_FINGERPRINT=$(fingerprint ${DIR}/docker/broker-webapp/conf/agent.crt.pem)
18 | QUOTES_SERVICE_AGENT_FINGERPRINT=$(fingerprint ${DIR}/docker/stock-quotes-service/conf/agent.crt.pem)
19 |
20 | echo "${bb}Creating registration entry for the broker-webapp...${nn}"
21 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-broker bin/spire-server entry create \
22 | -parentID spiffe://broker.example/spire/agent/x509pop/${BROKER_WEBAPP_AGENT_FINGERPRINT} \
23 | -spiffeID spiffe://broker.example/webapp \
24 | -selector unix:uid:0 \
25 | -federatesWith "spiffe://stockmarket.example"
26 |
27 | echo "${bb}Creating registration entry for the stock-quotes-service...${nn}"
28 | docker compose -f "${DIR}"/docker-compose.yaml exec -T spire-server-stock bin/spire-server entry create \
29 | -parentID spiffe://stockmarket.example/spire/agent/x509pop/${QUOTES_SERVICE_AGENT_FINGERPRINT} \
30 | -spiffeID spiffe://stockmarket.example/quotes-service \
31 | -selector unix:uid:0 \
32 | -federatesWith "spiffe://broker.example"
33 |
--------------------------------------------------------------------------------
/docker-compose/federation/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | (cd "${DIR}"/src/broker-webapp && CGO_ENABLED=0 GOOS=linux go build -v -o "${DIR}"/docker/broker-webapp/broker-webapp)
8 | (cd "${DIR}"/src/stock-quotes-service && CGO_ENABLED=0 GOOS=linux go build -v -o "${DIR}"/docker/stock-quotes-service/stock-quotes-service)
9 |
10 | docker compose -f "${DIR}"/docker-compose.yaml build
11 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | spire-server-stock:
4 | build: ./docker/spire-server-stockmarket.example
5 | hostname: spire-server-stock
6 | tty: true
7 | privileged: true
8 | volumes:
9 | - ./docker/spire-server-stockmarket.example/conf:/opt/spire/conf/server
10 |
11 | spire-server-broker:
12 | build: ./docker/spire-server-broker.example
13 | hostname: spire-server-broker
14 | tty: true
15 | privileged: true
16 | volumes:
17 | - ./docker/spire-server-broker.example/conf:/opt/spire/conf/server
18 |
19 | stock-quotes-service:
20 | build: ./docker/stock-quotes-service
21 | hostname: stock-quotes-service
22 | tty: true
23 | privileged: true
24 | links:
25 | - spire-server-stock
26 |
27 | broker-webapp:
28 | build: ./docker/broker-webapp
29 | hostname: broker-webapp
30 | tty: true
31 | privileged: true
32 | links:
33 | - spire-server-broker
34 | - stock-quotes-service
35 | ports:
36 | - 8080:8080
37 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/broker-webapp/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/spiffe/spire-agent:1.5.1
2 |
3 | COPY conf/agent.conf /opt/spire/conf/agent/agent.conf
4 | COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem
5 | COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem
6 | COPY broker-webapp /usr/local/bin/broker-webapp
7 |
8 | # Copy convenient tools to be used in the tutorial
9 | COPY --link --from=alpine:3.17 /bin/cat /bin/cat
10 | COPY --link --from=alpine:3.17 /bin/sh /bin/sh
11 | COPY --link --from=alpine:3.17 /usr/bin/tee /usr/bin/tee
12 | COPY --link --from=alpine:3.17 /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
13 |
14 | WORKDIR /opt/spire
15 | ENTRYPOINT []
16 |
17 | CMD broker-webapp
18 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/broker-webapp/conf/agent.conf:
--------------------------------------------------------------------------------
1 | agent {
2 | data_dir = "/opt/spire/data/agent"
3 | log_level = "DEBUG"
4 | log_file = "/opt/spire/agent.log"
5 | server_address = "spire-server-broker"
6 | server_port = "8081"
7 | socket_path ="/tmp/agent.sock"
8 | trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
9 | trust_domain = "broker.example"
10 | }
11 |
12 | plugins {
13 | NodeAttestor "x509pop" {
14 | plugin_data {
15 | private_key_path = "/opt/spire/conf/agent/agent.key.pem"
16 | certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
17 | }
18 | }
19 | KeyManager "disk" {
20 | plugin_data {
21 | directory = "/opt/spire/data/agent"
22 | }
23 | }
24 | WorkloadAttestor "unix" {
25 | plugin_data {
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/broker-webapp/conf/agent.crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBcTCB/KADAgECAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWJyb2tl
3 | ciBDQTAiGA8wMDAxMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAXMRUwEwYD
4 | VQQDEwx3ZWJhcHAgYWdlbnQwfDANBgkqhkiG9w0BAQEFAANrADBoAmEA2t2N2LyK
5 | jb5A32zI5ChQmnnCSacjERlIedTkW2URnriW/IirLUHJSmSNoybqq0ubbEbV2LTX
6 | IKU7dbvabF9TI3M9N3J+eL2yvkabqoDj6srN5QZavuTWb/gMB7J6AskrAgMBAAGj
7 | EjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAANhAHXsRyWbt+adfKty
8 | a2fPwwJyBiq836cF2PI0F6KIztXSNBxAkRE+Ky4vE21b52N56KpuopbIf/ibaapW
9 | 7k/o5PdjUp3iAXIJXsP7d0qJ5By83/swsOkDY9g2/XYpnr2YBQ==
10 | -----END CERTIFICATE-----
11 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/broker-webapp/conf/agent.key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIB5gIBADANBgkqhkiG9w0BAQEFAASCAdAwggHMAgEAAmEA2t2N2LyKjb5A32zI
3 | 5ChQmnnCSacjERlIedTkW2URnriW/IirLUHJSmSNoybqq0ubbEbV2LTXIKU7dbva
4 | bF9TI3M9N3J+eL2yvkabqoDj6srN5QZavuTWb/gMB7J6AskrAgMBAAECYHXF9wiE
5 | HIK9uCcCYO/1ibo2fwgnOkV/N3LnzqIntt2UMxtdZ8+IsQqpJVaAIJNgsRjL/pTK
6 | akSbwXXQ8RrmjXEifHVl+XqNibqgLgIuFLJR3C2fixWzzIihlbYcxw5WQQIxAPvO
7 | vlvTocP0PfT92FrYkIVwVNQH9SKXkQf9I5GhII5BFA5fBguOENj5avAwziwBOwIx
8 | AN6CZ3pCW1WXfugyO/9SsgK5kIoTN6rp3U0Lg+XbOiHZ68jzUGxS6IR/X90U5GTY
9 | 0QIxALyBy5Qm3MU7hV5w4pUv5xFeRMLuqh8ZZGOcqBIPk7WrFn6juHzR/97O6bWi
10 | c9YRnQIxAIgsbn+YFKVxLa2U8Lr1NRQN1LNrx2nF7jW0kmgdnpoQ8AfvQIzKwJo2
11 | CckXfB9rwQIxAK8yIMZZdGcwb6rHVZTkleorHwT6JZCV01oEBy6/KWHiJI11QdMt
12 | 4vLzQVDYWlYXwQ==
13 | -----END PRIVATE KEY-----
14 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/spire-server-broker.example/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/spiffe/spire-server:1.11.2
2 |
3 | # Override spire configurations
4 | COPY conf/server.conf /opt/spire/conf/server/server.conf
5 | COPY conf/agent-cacert.pem /opt/spire/conf/server/agent-cacert.pem
6 |
7 | # Copy convenient tools to be used in the tutorial
8 | COPY --link --from=alpine:3.17 /bin/cat /bin/cat
9 | COPY --link --from=alpine:3.17 /bin/sh /bin/sh
10 | COPY --link --from=alpine:3.17 /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
11 |
12 | WORKDIR /opt/spire
13 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/spire-server-broker.example/conf/agent-cacert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBbzCB+qADAgECAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWJyb2tl
3 | ciBDQTAiGA8wMDAxMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAUMRIwEAYD
4 | VQQDEwlicm9rZXIgQ0EwfDANBgkqhkiG9w0BAQEFAANrADBoAmEArbgurz3YC9Me
5 | VlNPuk3yXPvSUfWelptozX3EZzCqL5yLuokSKgMHrZEjwcjYz5+OvwndjkpbZ6hp
6 | atBOqv9ETjji0G1RnIWapq2iLYBQ45whPfyRFQYXCqad33CShiE3AgMBAAGjEzAR
7 | MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADYQAVX3ZQm6sY/ZxIf3HL
8 | O81sPL1rMdxie/r+JUHgmg2hRAFDsagsP7GEd7MRVX8N7bltLsI0vAs33cMF++RS
9 | 1oadKVRaK6+UU1ouelfF4ESXOPxREeQc4kCRLueqoVwraxU=
10 | -----END CERTIFICATE-----
11 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/spire-server-broker.example/conf/server.conf:
--------------------------------------------------------------------------------
1 | server {
2 | bind_address = "0.0.0.0"
3 | bind_port = "8081"
4 | socket_path = "/tmp/spire-server/private/api.sock"
5 | trust_domain = "broker.example"
6 | data_dir = "/opt/spire/data/server"
7 | log_level = "DEBUG"
8 | log_file = "/opt/spire/server.log"
9 | ca_subject = {
10 | country = ["US"],
11 | organization = ["SPIFFE"],
12 | common_name = "",
13 | }
14 |
15 | federation {
16 | bundle_endpoint {
17 | address = "0.0.0.0"
18 | port = 8443
19 | }
20 | federates_with "stockmarket.example" {
21 | bundle_endpoint_url = "https://spire-server-stock:8443"
22 | bundle_endpoint_profile "https_spiffe" {
23 | endpoint_spiffe_id = "spiffe://stockmarket.example/spire/server"
24 | }
25 | }
26 | }
27 | }
28 |
29 | plugins {
30 | DataStore "sql" {
31 | plugin_data {
32 | database_type = "sqlite3"
33 | connection_string = "/opt/spire/data/server/datastore.sqlite3"
34 | }
35 | }
36 |
37 | NodeAttestor "x509pop" {
38 | plugin_data {
39 | ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
40 | }
41 | }
42 |
43 | KeyManager "memory" {
44 | plugin_data = {}
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/spire-server-stockmarket.example/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/spiffe/spire-server:1.11.2
2 |
3 | # Override spire configurations
4 | COPY conf/server.conf /opt/spire/conf/server/server.conf
5 | COPY conf/agent-cacert.pem /opt/spire/conf/server/agent-cacert.pem
6 |
7 | # Copy convenient tools to be used in the tutorial
8 | COPY --link --from=alpine:3.17 /bin/cat /bin/cat
9 | COPY --link --from=alpine:3.17 /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
10 |
11 | WORKDIR /opt/spire
12 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/spire-server-stockmarket.example/conf/agent-cacert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBfDCCAQagAwIBAgIBATANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9zdG9j
3 | ayBtYXJrZXQgQ0EwIhgPMDAwMTAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVow
4 | GjEYMBYGA1UEAxMPc3RvY2sgbWFya2V0IENBMHwwDQYJKoZIhvcNAQEBBQADawAw
5 | aAJhAOOiuSYdyfFhep+OJBkMy5RMbOa5aMEICur7euGWfclyco9enF5DEfd/wAs/
6 | TGmx5a/cYfIbI/+LKGk51l6gKxlI7W7PLwm++chC9XDbKXNvGUW6Ljr0qPFgORHW
7 | sWfTuwIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA2EA
8 | BGIufwHpf45XAFlhtycAPTQJnwWYXfGAJ236q1/YinyY/PrcW3qXx+98mEBQ1G88
9 | rD3gMy9vgUIooimHvpWbs3XkRXjW6GOcWgNgccYsT2PivOP3Tg2dqwKxrM+Mj+AZ
10 | -----END CERTIFICATE-----
11 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/spire-server-stockmarket.example/conf/server.conf:
--------------------------------------------------------------------------------
1 | server {
2 | bind_address = "0.0.0.0"
3 | bind_port = "8081"
4 | socket_path = "/tmp/spire-server/private/api.sock"
5 | trust_domain = "stockmarket.example"
6 | data_dir = "/opt/spire/data/server"
7 | log_level = "DEBUG"
8 | log_file = "/opt/spire/server.log"
9 | ca_subject = {
10 | country = ["US"],
11 | organization = ["SPIFFE"],
12 | common_name = "",
13 | }
14 |
15 | federation {
16 | bundle_endpoint {
17 | address = "0.0.0.0"
18 | port = 8443
19 | }
20 | federates_with "broker.example" {
21 | bundle_endpoint_url = "https://spire-server-broker:8443"
22 | bundle_endpoint_profile "https_spiffe" {
23 | endpoint_spiffe_id = "spiffe://broker.example/spire/server"
24 | }
25 | }
26 | }
27 | }
28 |
29 | plugins {
30 | DataStore "sql" {
31 | plugin_data {
32 | database_type = "sqlite3"
33 | connection_string = "/opt/spire/data/server/datastore.sqlite3"
34 | }
35 | }
36 |
37 | NodeAttestor "x509pop" {
38 | plugin_data {
39 | ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
40 | }
41 | }
42 |
43 | KeyManager "memory" {
44 | plugin_data = {}
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/stock-quotes-service/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/spiffe/spire-agent:1.5.1 as spire
2 |
3 | COPY conf/agent.conf /opt/spire/conf/agent/agent.conf
4 | COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem
5 | COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem
6 | COPY stock-quotes-service /usr/local/bin/stock-quotes-service
7 |
8 | # Copy convenient tools to be used in the tutorial
9 | COPY --link --from=alpine:3.17 /bin/cat /bin/cat
10 | COPY --link --from=alpine:3.17 /bin/sh /bin/sh
11 | COPY --link --from=alpine:3.17 /usr/bin/tee /usr/bin/tee
12 | COPY --link --from=alpine:3.17 /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
13 |
14 | WORKDIR /opt/spire
15 | ENTRYPOINT []
16 | CMD stock-quotes-service
17 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/stock-quotes-service/conf/agent.conf:
--------------------------------------------------------------------------------
1 | agent {
2 | data_dir = "/opt/spire/data/agent"
3 | log_level = "DEBUG"
4 | log_file = "/opt/spire/agent.log"
5 | server_address = "spire-server-stock"
6 | server_port = "8081"
7 | socket_path ="/tmp/agent.sock"
8 | trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
9 | trust_domain = "stockmarket.example"
10 | }
11 |
12 | plugins {
13 | NodeAttestor "x509pop" {
14 | plugin_data {
15 | private_key_path = "/opt/spire/conf/agent/agent.key.pem"
16 | certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
17 | }
18 | }
19 | KeyManager "disk" {
20 | plugin_data {
21 | directory = "/opt/spire/data/agent"
22 | }
23 | }
24 | WorkloadAttestor "unix" {
25 | plugin_data {
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/stock-quotes-service/conf/agent.crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBgDCCAQqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9zdG9j
3 | ayBtYXJrZXQgQ0EwIhgPMDAwMTAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVow
4 | HzEdMBsGA1UEAxMUcXVvdGVzLXNlcnZpY2UgYWdlbnQwfDANBgkqhkiG9w0BAQEF
5 | AANrADBoAmEAtmHF8/92WExZWZpM0qMf/K07jZIGJYhksDFBsfP96vybCoLaqJqa
6 | S2kF/ZHNci1/JZmQj88+PBSsw1cgU9t+hGRiO4Q1gMETAoTTVKjwrZFPH9qxu2+A
7 | ziiBxLHNr3YNAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
8 | AANhALtDWHNK2KbJlBC8MTzP/CtswmpIySVM9fKq2FQIgy7Sljrz5gGEUFSL1TFz
9 | bn3XN50dovVCSauXd8w5PO3QQ6J/SLa0z8gHH+wJvlWvAF4FTMTJkGqK/ZKDIwbK
10 | pZ4H6g==
11 | -----END CERTIFICATE-----
12 |
--------------------------------------------------------------------------------
/docker-compose/federation/docker/stock-quotes-service/conf/agent.key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIB5AIBADANBgkqhkiG9w0BAQEFAASCAc4wggHKAgEAAmEAtmHF8/92WExZWZpM
3 | 0qMf/K07jZIGJYhksDFBsfP96vybCoLaqJqaS2kF/ZHNci1/JZmQj88+PBSsw1cg
4 | U9t+hGRiO4Q1gMETAoTTVKjwrZFPH9qxu2+AziiBxLHNr3YNAgMBAAECYCb62Ksg
5 | o3OVxdb/woGWecSwZbUJS6UD9LkvneHhyxhJKv3hH8i/WlDZvn0Gh4lqrZCnk6a5
6 | lC75lVsW/xMHHEra45UUcaHA/COw5acuykh+62YAkAYrhXhw5SJBa0McZQIxAM0q
7 | MeItgVnk3eHd3/yhMhuayJw6S1jgV7kard8PL0UjevBFLfBI+0tUA3BzydSHiwIx
8 | AOOSb975bjYGkfPfKJSrkiKVd6DcK0o1UUne8hUcRz34BR0bbUNv2o16hpGtkNlr
9 | xwIwCVEBMuQeG5bo/Hi20yH+xIIi2fVLtp15Xk531sk5vEoAKyj5DRBDWQhXn6Oi
10 | ZqRBAjEA24bDjCCphExKNyqqhuALFHmC8RXyXJ+aTtxWQq8Iumqq5C009bzM43Wy
11 | oo0AEfy5AjAhRc0I+wZDnHqJUE+JQzDpeuKhrAf0gY1zjDhFh6Bax+oGqZVCXttP
12 | S0UyEhdses0=
13 | -----END PRIVATE KEY-----
14 |
--------------------------------------------------------------------------------
/docker-compose/federation/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | PARENT_DIR="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
6 |
7 | norm=$(tput sgr0) || true
8 | green=$(tput setaf 2) || true
9 |
10 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml down
11 |
12 | echo "${green}Cleaning completed.${norm}"
13 |
--------------------------------------------------------------------------------
/docker-compose/federation/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | PARENT_DIR="$(dirname "$DIR")"
7 |
8 | norm=$(tput sgr0) || true
9 | green=$(tput setaf 2) || true
10 | red=$(tput setaf 1) || true
11 | bold=$(tput bold) || true
12 |
13 |
14 | timestamp() {
15 | date -u "+[%Y-%m-%dT%H:%M:%SZ]"
16 | }
17 |
18 | log() {
19 | echo "${bold}$(timestamp) $*${norm}"
20 | }
21 |
22 | check-entry-is-propagated() {
23 | # Check at most 30 times that the agent has successfully synced down the workload entry.
24 | # Wait one second between checks.
25 | log "Checking registration entry is propagated..."
26 | for ((i=1;i<=30;i++)); do
27 | if docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T $1 cat /opt/spire/agent.log 2>&1 | grep -qe "$2"; then
28 | log "${green}Entry is propagated.${nn}"
29 | return 0
30 | fi
31 | sleep 1
32 | done
33 |
34 | log "${red}timed out waiting for the entry to be progagated to the agent${norm}"
35 | exit 1
36 | }
37 |
38 |
39 | log "Building"
40 | bash "${PARENT_DIR}"/build.sh
41 |
42 | log "Starting container"
43 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d
44 |
45 | bash "${PARENT_DIR}"/1-start-spire-agents.sh
46 |
47 | bash "${PARENT_DIR}"/2-bootstrap-federation.sh
48 |
49 | bash "${PARENT_DIR}"/3-create-registration-entries.sh
50 |
51 | check-entry-is-propagated stock-quotes-service spiffe://stockmarket.example/quotes-service
52 | check-entry-is-propagated broker-webapp spiffe://broker.example/webapp
53 |
--------------------------------------------------------------------------------
/docker-compose/federation/src/broker-webapp/go.mod:
--------------------------------------------------------------------------------
1 | module broker-webapp
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/spiffe/go-spiffe/v2 v2.1.0
7 | github.com/zeebo/errs v1.3.0 // indirect
8 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
9 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
10 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
11 | google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
12 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect
13 | )
14 |
--------------------------------------------------------------------------------
/docker-compose/federation/src/broker-webapp/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "time"
11 |
12 | "broker-webapp/quotes"
13 |
14 | "github.com/spiffe/go-spiffe/v2/spiffeid"
15 | "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
16 | "github.com/spiffe/go-spiffe/v2/workloadapi"
17 | )
18 |
19 | const (
20 | port = 8080
21 | quotesURL = "https://stock-quotes-service:8090/quotes"
22 | socketPath = "unix:///tmp/agent.sock"
23 | )
24 |
25 | var (
26 | latestQuotes = []*quotes.Quote(nil)
27 | latestUpdate = time.Now()
28 | // Stock quotes provider SPIFFE ID
29 | quotesProviderSpiffeID = spiffeid.RequireFromString("spiffe://stockmarket.example/quotes-service")
30 | x509Src *workloadapi.X509Source
31 | bundleSrc *workloadapi.BundleSource
32 | )
33 |
34 | func main() {
35 | log.Print("Webapp waiting for an X.509 SVID...")
36 |
37 | ctx := context.Background()
38 |
39 | var err error
40 | x509Src, err = workloadapi.NewX509Source(ctx,
41 | workloadapi.WithClientOptions(
42 | workloadapi.WithAddr(socketPath),
43 | //workloadapi.WithLogger(logger.Std),
44 | ),
45 | )
46 | if err != nil {
47 | log.Fatal(err)
48 | }
49 |
50 | log.Print("Webapp waiting for a trust bundle...")
51 |
52 | bundleSrc, err = workloadapi.NewBundleSource(ctx,
53 | workloadapi.WithClientOptions(
54 | workloadapi.WithAddr(socketPath),
55 | ),
56 | )
57 | if err != nil {
58 | log.Fatal(err)
59 | }
60 |
61 | server := &http.Server{
62 | Addr: fmt.Sprintf(":%d", port),
63 | }
64 | http.HandleFunc("/quotes", quotesHandler)
65 |
66 | log.Printf("Webapp listening on port %d...", port)
67 |
68 | err = server.ListenAndServe()
69 | if err != nil {
70 | log.Fatal(err)
71 | }
72 | }
73 |
74 | func quotesHandler(resp http.ResponseWriter, req *http.Request) {
75 | if req.Method != http.MethodGet {
76 | resp.WriteHeader(http.StatusMethodNotAllowed)
77 | return
78 | }
79 |
80 | data, err := getQuotesData()
81 |
82 | if data != nil {
83 | latestQuotes = data
84 | latestUpdate = time.Now()
85 | } else {
86 | data = latestQuotes
87 | }
88 |
89 | quotes.Page.Execute(resp, map[string]interface{}{
90 | "Data": data,
91 | "Err": err,
92 | "LastUpdated": latestUpdate,
93 | })
94 | }
95 |
96 | func getQuotesData() ([]*quotes.Quote, error) {
97 | client := http.Client{
98 | Transport: &http.Transport{
99 | TLSClientConfig: tlsconfig.MTLSClientConfig(x509Src, bundleSrc, tlsconfig.AuthorizeID(quotesProviderSpiffeID)),
100 | },
101 | }
102 |
103 | resp, err := client.Get(quotesURL)
104 | if err != nil {
105 | log.Printf("Error getting quotes: %v", err)
106 | return nil, err
107 | }
108 |
109 | if resp.StatusCode != http.StatusOK {
110 | log.Printf("Quotes unavailables: %s", resp.Status)
111 | return nil, err
112 | }
113 |
114 | jsonData, err := ioutil.ReadAll(resp.Body)
115 | if err != nil {
116 | log.Printf("Error reading response body: %v", err)
117 | return nil, err
118 | }
119 |
120 | data := []*quotes.Quote{}
121 | err = json.Unmarshal(jsonData, &data)
122 | if err != nil {
123 | log.Printf("Error unmarshaling json quotes: %v", err)
124 | return nil, err
125 | }
126 |
127 | return data, nil
128 | }
129 |
--------------------------------------------------------------------------------
/docker-compose/federation/src/broker-webapp/quotes/page.go:
--------------------------------------------------------------------------------
1 | package quotes
2 |
3 | import (
4 | "html/template"
5 | "log"
6 | "time"
7 | )
8 |
9 | const markup = `
10 |
11 |
12 |
13 |
14 |
15 |
16 |
38 | {{if .Err}}
39 | Quotes service unavailable
40 | {{end}}
41 |
42 | Last Updated: {{.LastUpdated.Format "Jan 2 15:04:05"}}
43 |
44 |
45 | Symbol |
46 | Price |
47 | Open |
48 | Low |
49 | High |
50 | Close |
51 | Time |
52 |
53 |
54 |
55 | {{range .Data}}
56 |
57 | {{.Symbol}} |
58 | {{if .Time}}
59 | {{.Price | printf "%.2f"}} |
60 | {{.Open | printf "%.2f"}} |
61 | {{.Low | printf "%.2f"}} |
62 | {{.High | printf "%.2f"}} |
63 | {{.Close | printf "%.2f"}} |
64 | {{.Time.Format "15:04:05"}} |
65 | {{else}}
66 | - |
67 | - |
68 | - |
69 | - |
70 | - |
71 | - |
72 | {{end}}
73 |
74 | {{end}}
75 |
76 |
77 |
83 |
84 |
85 | `
86 |
87 | // Page is the quotes page template already parsed.
88 | var Page *template.Template
89 |
90 | // Quote represent a quote for a specific symbol in a specific time.
91 | type Quote struct {
92 | Symbol string
93 | Price float64
94 | Open float64
95 | Low float64
96 | High float64
97 | Close float64
98 | Time *time.Time
99 | }
100 |
101 | func init() {
102 | var err error
103 | Page, err = template.New("quotes").Parse(markup)
104 | if err != nil {
105 | log.Fatal(err)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/docker-compose/federation/src/stock-quotes-service/go.mod:
--------------------------------------------------------------------------------
1 | module stock-quotes-service
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/spiffe/go-spiffe/v2 v2.1.0
7 | github.com/zeebo/errs v1.3.0 // indirect
8 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
9 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
10 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
11 | google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
12 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect
13 | )
14 |
--------------------------------------------------------------------------------
/docker-compose/federation/src/stock-quotes-service/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "math"
9 | "math/rand"
10 | "net/http"
11 | "sync"
12 | "time"
13 |
14 | "github.com/spiffe/go-spiffe/v2/spiffeid"
15 | "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
16 | "github.com/spiffe/go-spiffe/v2/workloadapi"
17 | )
18 |
19 | const (
20 | port = 8090
21 | socketPath = "unix:///tmp/agent.sock"
22 | )
23 |
24 | var (
25 | quotes = []*Quote{
26 | {Symbol: "AAAA"},
27 | {Symbol: "BBBB"},
28 | {Symbol: "CCCC"},
29 | {Symbol: "DDDD"},
30 | {Symbol: "EEEE"},
31 | {Symbol: "FFFF"},
32 | {Symbol: "GGGG"},
33 | {Symbol: "HHHH"},
34 | {Symbol: "IIII"},
35 | {Symbol: "JJJJ"},
36 | {Symbol: "KKKK"},
37 | }
38 | quotesMtx = sync.RWMutex{}
39 | brokerSpiffeID = spiffeid.RequireFromString("spiffe://broker.example/webapp")
40 | )
41 |
42 | func main() {
43 | log.Print("Service waiting for an X.509 SVID...")
44 |
45 | ctx := context.Background()
46 | x509Src, err := workloadapi.NewX509Source(ctx,
47 | workloadapi.WithClientOptions(
48 | workloadapi.WithAddr(socketPath),
49 | //workloadapi.WithLogger(logger.Std),
50 | ),
51 | )
52 | if err != nil {
53 | log.Fatal(err)
54 | }
55 |
56 | log.Print("Service waiting for a trust bundle...")
57 |
58 | bundleSrc, err := workloadapi.NewBundleSource(ctx,
59 | workloadapi.WithClientOptions(
60 | workloadapi.WithAddr(socketPath),
61 | ),
62 | )
63 | if err != nil {
64 | log.Fatal(err)
65 | }
66 |
67 | server := &http.Server{
68 | Addr: fmt.Sprintf(":%d", port),
69 | TLSConfig: tlsconfig.MTLSServerConfig(x509Src, bundleSrc, tlsconfig.AuthorizeID(brokerSpiffeID)),
70 | }
71 | http.HandleFunc("/quotes", quotesHandler)
72 |
73 | log.Printf("Stock quotes service listening on port %d...", port)
74 |
75 | err = server.ListenAndServeTLS("", "")
76 | if err != nil {
77 | log.Fatal(err)
78 | }
79 | }
80 |
81 | // Quote represent a quote for a specific symbol in a specific time.
82 | type Quote struct {
83 | Symbol string
84 | Price float64
85 | Open float64
86 | Low float64
87 | High float64
88 | Close float64
89 | Time *time.Time
90 | }
91 |
92 | func quotesHandler(resp http.ResponseWriter, req *http.Request) {
93 | randomizeQuotes()
94 |
95 | encoder := json.NewEncoder(resp)
96 | quotesMtx.RLock()
97 | err := encoder.Encode(quotes)
98 | quotesMtx.RUnlock()
99 | if err != nil {
100 | log.Printf("Error encoding data: %v", err)
101 | resp.WriteHeader(http.StatusInternalServerError)
102 | return
103 | }
104 | }
105 |
106 | func randomizeQuotes() {
107 | quotesMtx.Lock()
108 | for _, quote := range quotes {
109 | if rand.Int()%4 == 0 {
110 | priceDelta := rand.NormFloat64() * 1.5
111 | now := time.Now()
112 | if quote.Time == nil {
113 | quote.Open = priceDelta + 10 + 100*rand.Float64()
114 | quote.Low = quote.Open
115 | quote.High = quote.Open
116 | quote.Close = quote.Open - rand.NormFloat64()*1.5
117 | quote.Price = quote.Open
118 | } else {
119 | quote.Price += priceDelta
120 | }
121 | quote.Time = &now
122 | quote.Low = math.Min(quote.Price, quote.Low)
123 | quote.High = math.Max(quote.Price, quote.High)
124 | }
125 | }
126 | quotesMtx.Unlock()
127 | }
128 |
--------------------------------------------------------------------------------
/docker-compose/federation/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | norm=$(tput sgr0) || true
4 | green=$(tput setaf 2) || true
5 | red=$(tput setaf 1) || true
6 | bold=$(tput bold) || true
7 |
8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9 |
10 | timestamp() {
11 | date -u "+[%Y-%m-%dT%H:%M:%SZ]"
12 | }
13 |
14 | log() {
15 | echo "${bold}$(timestamp) $*${norm}"
16 | }
17 |
18 | fail() {
19 | echo "${red}$(timestamp) $*${norm}"
20 | exit 1
21 | }
22 |
23 | clean-env() {
24 | log "Cleaning up..."
25 | bash "${DIR}"/scripts/clean-env.sh
26 | }
27 |
28 | trap clean-env EXIT
29 |
30 |
31 | log "Preparing Nested SPIRE environment..."
32 | clean-env
33 |
34 | bash "${DIR}"/scripts/set-env.sh
35 |
36 | for ((i=0;i<60;i++)); do
37 | if docker compose -f "${DIR}"/docker-compose.yaml exec -T broker-webapp wget localhost:8080/quotes -O - 2>&1 | grep -qe "Quotes service unavailable"; then
38 | log "Service not found, retrying..."
39 | sleep 1
40 | continue
41 | fi
42 | CONNECTION_OK=1
43 | break
44 | done
45 |
46 | if [ "${CONNECTION_OK}" ]; then
47 | echo "${green}Success${norm}"
48 | exit 0
49 | fi
50 |
51 | fail "Failed!. Timed out waiting quote service communicate with webapp from SPIRE."
52 | exit 1
53 |
--------------------------------------------------------------------------------
/docker-compose/metrics/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | graphite-statsd:
3 | image: graphiteapp/graphite-statsd:1.1.7-6
4 | container_name: graphite
5 | hostname: graphite-statsd
6 | restart: always
7 | ports:
8 | - "80:80"
9 | - "8125:8125/udp"
10 | prometheus:
11 | image: prom/prometheus:v2.20.1
12 | container_name: prometheus
13 | hostname: prometheus
14 | restart: always
15 | volumes:
16 | - ./prometheus:/etc/prometheus
17 | ports:
18 | - "9090:9090"
19 | spire-server:
20 | image: ghcr.io/spiffe/spire-server:1.11.2
21 | hostname: spire-server
22 | volumes:
23 | - ./spire/server:/opt/spire/conf/server
24 | command: ["-config", "/opt/spire/conf/server/server.conf"]
25 | spire-agent:
26 | image: ghcr.io/spiffe/spire-agent:1.11.2
27 | depends_on: ["spire-server"]
28 | hostname: spire-agent
29 | volumes:
30 | - ./spire/agent:/opt/spire/conf/agent
31 | - /var/run/:/var/run/
32 | command: ["-config", "/opt/spire/conf/agent/agent.conf"]
33 |
--------------------------------------------------------------------------------
/docker-compose/metrics/images/graphite_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/docker-compose/metrics/images/graphite_graph.png
--------------------------------------------------------------------------------
/docker-compose/metrics/images/prometheus_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/docker-compose/metrics/images/prometheus_graph.png
--------------------------------------------------------------------------------
/docker-compose/metrics/prometheus/prometheus.yml:
--------------------------------------------------------------------------------
1 | global:
2 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
3 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
4 | # scrape_timeout is set to the global default (10s).
5 |
6 | scrape_configs:
7 | - job_name: 'spire-server'
8 | metrics_path: '/'
9 | static_configs:
10 | - targets: ['spire-server:8088']
11 |
12 | - job_name: 'spire-agent'
13 | metrics_path: '/'
14 | static_configs:
15 | - targets: ['spire-agent:8089']
16 |
--------------------------------------------------------------------------------
/docker-compose/metrics/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | PARENT_DIR="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
6 |
7 | norm=$(tput sgr0) || true
8 | green=$(tput setaf 2) || true
9 |
10 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml down
11 |
12 | echo "${green}Cleaning completed.${norm}"
13 |
--------------------------------------------------------------------------------
/docker-compose/metrics/scripts/create-workload-registration-entry.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | PARENT_DIR="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
6 |
7 | norm=$(tput sgr0) || true
8 | green=$(tput setaf 2) || true
9 | red=$(tput setaf 1) || true
10 | bold=$(tput bold) || true
11 |
12 | timestamp() {
13 | date -u "+[%Y-%m-%dT%H:%M:%SZ]"
14 | }
15 |
16 | log() {
17 | echo "${bold}$(timestamp) $*${norm}"
18 | }
19 |
20 | fingerprint() {
21 | # calculate the SHA1 digest of the DER bytes of the certificate using the
22 | # "coreutils" output format (`-r`) to provide uniform output from
23 | # `openssl sha1` on macOS and linux.
24 | openssl x509 -in "$1" -outform DER | openssl sha1 -r | awk '{print $1}'
25 | }
26 |
27 | check-entry-is-propagated() {
28 | # Check at most 30 times that the agent has successfully synced down the workload entry.
29 | # Wait one second between checks.
30 | log "Checking registration entry is propagated..."
31 | for ((i=1;i<=30;i++)); do
32 | if docker compose -f "${PARENT_DIR}"/docker-compose.yaml logs $1 | grep -qe "$2"; then
33 | log "${green}Entry is propagated.${nn}"
34 | return 0
35 | fi
36 | sleep 1
37 | done
38 |
39 | log "${red}timed out waiting for the entry to be progagated to the agent${red}"
40 | exit 1
41 | }
42 |
43 |
44 | # Workload for workload-A deployment
45 | log "creating workload-A workload registration entries..."
46 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T spire-server \
47 | /opt/spire/bin/spire-server entry create \
48 | -parentID "spiffe://example.org/spire/agent/x509pop/$(fingerprint "${PARENT_DIR}"/spire/agent/agent.crt.pem)" \
49 | -spiffeID "spiffe://example.org/workload-A" \
50 | -selector "unix:uid:1001" \
51 | -x509SVIDTTL 120
52 |
53 | check-entry-is-propagated spire-agent spiffe://example.org/workload
54 |
--------------------------------------------------------------------------------
/docker-compose/metrics/scripts/fetch_svid.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | echo "Will call api fetch x509 100 times in a random interval between 1 and 10 of seconds."
6 | for ((i=0;i<100;i++)); do
7 | docker compose exec -u 1001 -T spire-agent \
8 | /opt/spire/bin/spire-agent api fetch x509 \
9 | -socketPath /opt/spire/sockets/workload_api.sock > /dev/null
10 | sleep $(( $RANDOM % 10 + 1 ))
11 | continue
12 | done
13 |
14 | echo "Process completed."
15 | exit 0
16 |
--------------------------------------------------------------------------------
/docker-compose/metrics/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | PARENT_DIR="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
6 |
7 | norm=$(tput sgr0) || true
8 | bold=$(tput bold) || true
9 |
10 | log() {
11 | echo "${bold}$*${norm}"
12 | }
13 |
14 | log "Start StatsD-Graphite server"
15 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d graphite-statsd
16 |
17 | log "Start prometheus server"
18 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d prometheus
19 |
20 | log "Start SPIRE Server"
21 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d spire-server
22 |
23 | log "bootstrapping SPIRE Agent..."
24 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T spire-server /opt/spire/bin/spire-server bundle show > "${PARENT_DIR}"/spire/agent/bootstrap.crt
25 |
26 | log "Start SPIRE Agent"
27 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d spire-agent
28 |
--------------------------------------------------------------------------------
/docker-compose/metrics/spire/agent/agent.conf:
--------------------------------------------------------------------------------
1 | agent {
2 | data_dir = "/opt/spire/data/agent"
3 | log_level = "DEBUG"
4 | server_address = "spire-server"
5 | server_port = "8081"
6 | socket_path ="/opt/spire/sockets/workload_api.sock"
7 | trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
8 | trust_domain = "example.org"
9 | }
10 |
11 | plugins {
12 | NodeAttestor "x509pop" {
13 | plugin_data {
14 | private_key_path = "/opt/spire/conf/agent/agent.key.pem"
15 | certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
16 | }
17 | }
18 | KeyManager "disk" {
19 | plugin_data {
20 | directory = "/opt/spire/data/agent"
21 | }
22 | }
23 | WorkloadAttestor "unix" {
24 | plugin_data {
25 | }
26 | }
27 | }
28 |
29 | telemetry {
30 | Prometheus {
31 | host = "spire-agent"
32 | port = 8089
33 | }
34 |
35 | Statsd = [
36 | {
37 | address = "graphite-statsd:8125"
38 | },
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/docker-compose/metrics/spire/agent/agent.crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBKDCBzqADAgECAgEAMAoGCCqGSM49BAMCMBMxETAPBgNVBAMTCEFnZW50IENB
3 | MCIYDzAwMDEwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBAxDjAMBgNVBAMT
4 | BWFnZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb6vaKQe/6IDUzLtA6Ied
5 | HUP0g0pJuR9cVb3ZWnjEHIgyOgeSEYZI5Dbok6jcSD5xTRtOWZRRDT4fZmopeqYE
6 | 1qMSMBAwDgYDVR0PAQH/BAQDAgeAMAoGCCqGSM49BAMCA0kAMEYCIQCjUIrXgArS
7 | BeP2z956KGC4NbKX1sOHEOaPveeDgATlCwIhAMrVHJxZvR3FG2Y/hUB4seRDTha4
8 | VldZ++61XO9/XZ0I
9 | -----END CERTIFICATE-----
10 |
--------------------------------------------------------------------------------
/docker-compose/metrics/spire/agent/agent.key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgadqVCIlKCQLUf//N
3 | YljwTx7LuM/FVNq2+AOyBi/Fr5OhRANCAARvq9opB7/ogNTMu0Doh50dQ/SDSkm5
4 | H1xVvdlaeMQciDI6B5IRhkjkNuiTqNxIPnFNG05ZlFENPh9mail6pgTW
5 | -----END PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/docker-compose/metrics/spire/agent/bootstrap.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICADCCAaegAwIBAgIQWb1fwpq1CRgRMWgIPjUkZDAKBggqhkjOPQQDAjBQMQsw
3 | CQYDVQQGEwJVUzEPMA0GA1UEChMGU1BJRkZFMTAwLgYDVQQFEycxMTkyODQ1Nzc5
4 | NzgxNzYwMTk4MTcyOTkzMzgxMjQ3NjIzNTg4ODQwHhcNMjUwMjI3MjE0ODUyWhcN
5 | MjUwMjI4MjE0OTAyWjBQMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGU1BJRkZFMTAw
6 | LgYDVQQFEycxMTkyODQ1Nzc5NzgxNzYwMTk4MTcyOTkzMzgxMjQ3NjIzNTg4ODQw
7 | WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ/KNCfkxzU3697S+m7zv5KjiD24BXW
8 | shqDlv/87hPBXg3rcYMI0bKl5JYQEzfG1no9nm+zESsJBRG/G9tOR7rvo2MwYTAO
9 | BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU6P1ZcANp
10 | jpC1nQ+73YfelEbZYoEwHwYDVR0RBBgwFoYUc3BpZmZlOi8vZXhhbXBsZS5vcmcw
11 | CgYIKoZIzj0EAwIDRwAwRAIgcWulpz7Sju1wQ6O821WwXajecMSR0k1Mog8iAyDL
12 | oZsCIFT7nYK+/F3BBeOndbsrSPGd2jrlDknQrPFHsZB9sJoY
13 | -----END CERTIFICATE-----
14 |
--------------------------------------------------------------------------------
/docker-compose/metrics/spire/server/agent-cacert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBKzCB0qADAgECAgEBMAoGCCqGSM49BAMCMBMxETAPBgNVBAMTCEFnZW50IENB
3 | MCIYDzAwMDEwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBMxETAPBgNVBAMT
4 | CEFnZW50IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDIm4F4psxRsi70CT
5 | eQWBB0bYdMTJ9T85SynWxwsUsAOPZEIV5chNr0LxlnsIe0ooa9eJrkZ54JVDQZMI
6 | AdCacKMTMBEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAupl5
7 | dD+w50kTGfdU0syStOJnKqFa+Y9Wzn591YJPOAkCIBotMiCDk/KatTl4wFLcmFmp
8 | rrUsQNR256L0wtDPYqhJ
9 | -----END CERTIFICATE-----
10 |
--------------------------------------------------------------------------------
/docker-compose/metrics/spire/server/server.conf:
--------------------------------------------------------------------------------
1 | server {
2 | bind_address = "0.0.0.0"
3 | bind_port = "8081"
4 | socket_path = "/tmp/spire-server/private/api.sock"
5 | trust_domain = "example.org"
6 | data_dir = "/opt/spire/data/server"
7 | log_level = "DEBUG"
8 | ca_ttl = "24h"
9 | }
10 |
11 | plugins {
12 | DataStore "sql" {
13 | plugin_data {
14 | database_type = "sqlite3"
15 | connection_string = "/opt/spire/data/server/datastore.sqlite3"
16 | }
17 | }
18 | NodeAttestor "x509pop" {
19 | plugin_data {
20 | ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
21 | }
22 | }
23 | KeyManager "memory" {
24 | plugin_data = {}
25 | }
26 | }
27 |
28 | telemetry {
29 | Prometheus {
30 | host = "spire-server"
31 | port = 8088
32 | }
33 |
34 | Statsd = [
35 | {
36 | address = "graphite-statsd:8125"
37 | },
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/docker-compose/metrics/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | norm=$(tput sgr0) || true
4 | green=$(tput setaf 2) || true
5 | red=$(tput setaf 1) || true
6 | bold=$(tput bold) || true
7 |
8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9 |
10 |
11 | log() {
12 | echo "${bold}$*${norm}"
13 | }
14 |
15 | clean-env() {
16 | log "Cleaning up..."
17 | bash "${DIR}"/scripts/clean-env.sh
18 | }
19 |
20 | trap clean-env EXIT
21 |
22 |
23 | log "Preparing environment..."
24 | clean-env
25 | bash "${DIR}"/scripts/set-env.sh
26 | bash "${DIR}"/scripts/create-workload-registration-entry.sh
27 |
28 | log "Checking Statsd received metrics pushed by SPIRE..."
29 |
30 | STATSD_LOG_LINE="MetricLineReceiver connection with .* established"
31 | for ((i=0;i<60;i++)); do
32 | if ! docker compose -f "${DIR}"/docker-compose.yaml logs --tail=10 -t graphite-statsd | grep -qe "${STATSD_LOG_LINE}" ; then
33 | sleep 1
34 | continue
35 | fi
36 | METRIC_RECEIVED=1
37 | break
38 | done
39 | if [ -z "${METRIC_RECEIVED}" ]; then
40 | echo "${red}Failed!. Timed out waiting for SPIRE to push metrics to Statsd.${nn}"
41 | exit 1
42 | fi
43 |
44 | log "Checking that Prometheus can reach the endpoint exposed by SPIRE..."
45 | for ((i=0;i<60;i++)); do
46 | if ! docker compose -f "${DIR}"/docker-compose.yaml exec -T prometheus wget -S spire-server:8088/ 2>&1 | grep -qe "200 OK" ; then
47 | sleep 1
48 | continue
49 | fi
50 | CONNECTION_OK=1
51 | break
52 | done
53 | if [ -z "${CONNECTION_OK}" ]; then
54 | echo "${red}Failed!. Timed out waiting for Prometheus to successfully fetch metrics from SPIRE.${nn}"
55 | exit 1
56 | fi
57 |
58 | echo "${green}Success${norm}"
59 | exit 0
60 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore certs and keys generated as part of the tutorial
2 | *.pem
3 | *.crt.pem
4 | *.key.pem
5 | *.crt
--------------------------------------------------------------------------------
/docker-compose/nested-spire/.go-version:
--------------------------------------------------------------------------------
1 | 1.14.4
2 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | # Root
3 | root-server:
4 | image: ghcr.io/spiffe/spire-server:1.11.2
5 | hostname: root-server
6 | volumes:
7 | - ./root/server:/opt/spire/conf/server
8 | command: ["-config", "/opt/spire/conf/server/server.conf"]
9 | root-agent:
10 | # Share the host pid namespace so this agent can attest the nested servers
11 | pid: "host"
12 | image: ghcr.io/spiffe/spire-agent:1.11.2
13 | depends_on: ["root-server"]
14 | hostname: root-agent
15 | volumes:
16 | # Share root-agent socket to be accessed by nested servers
17 | - ./sharedRootSocket:/opt/spire/sockets
18 | - ./root/agent:/opt/spire/conf/agent
19 | - /var/run/:/var/run/
20 | command: ["-config", "/opt/spire/conf/agent/agent.conf"]
21 | # NestedA
22 | nestedA-server:
23 | # Share the host pid namespace so this server can be attested by the root agent
24 | pid: "host"
25 | image: ghcr.io/spiffe/spire-server:1.11.2
26 | hostname: nestedA-server
27 | labels:
28 | # label to attest server against root-agent
29 | - org.example.name=nestedA-server
30 | depends_on: ["root-server","root-agent"]
31 | volumes:
32 | # Add root-agent socket
33 | - ./sharedRootSocket:/opt/spire/sockets
34 | - ./nestedA/server:/opt/spire/conf/server
35 | command: ["-config", "/opt/spire/conf/server/server.conf"]
36 | nestedA-agent:
37 | image: ghcr.io/spiffe/spire-agent:1.11.2
38 | hostname: nestedA-agent
39 | depends_on: ["nestedA-server"]
40 | volumes:
41 | - ./nestedA/agent:/opt/spire/conf/agent
42 | - /var/run/:/var/run/
43 | command: ["-config", "/opt/spire/conf/agent/agent.conf"]
44 | nestedB-server:
45 | # Share the host pid namespace so this server can be attested by the root agent
46 | pid: "host"
47 | image: ghcr.io/spiffe/spire-server:1.11.2
48 | hostname: nestedB-server
49 | depends_on: ["root-server","root-agent"]
50 | labels:
51 | # Label to attest server against root-agent
52 | - org.example.name=nestedB-server
53 | volumes:
54 | # Add root-agent socket
55 | - ./sharedRootSocket:/opt/spire/sockets
56 | - ./nestedB/server:/opt/spire/conf/server
57 | command: ["-config", "/opt/spire/conf/server/server.conf"]
58 | nestedB-agent:
59 | image: ghcr.io/spiffe/spire-agent:1.11.2
60 | hostname: nestedB-agent
61 | depends_on: ["nestedB-server"]
62 | volumes:
63 | - ./nestedB/agent:/opt/spire/conf/agent
64 | - /var/run/:/var/run/
65 | command: ["-config", "/opt/spire/conf/agent/agent.conf"]
66 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/images/Nested_SPIRE_Diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/docker-compose/nested-spire/images/Nested_SPIRE_Diagram.png
--------------------------------------------------------------------------------
/docker-compose/nested-spire/nestedA/agent/agent.conf:
--------------------------------------------------------------------------------
1 | agent {
2 | data_dir = "/opt/spire/data/agent"
3 | log_level = "DEBUG"
4 | server_address = "nestedA-server"
5 | server_port = "8081"
6 | socket_path = "/opt/spire/sockets/workload_api.sock"
7 | trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
8 | trust_domain = "example.org"
9 | }
10 |
11 | plugins {
12 | NodeAttestor "x509pop" {
13 | plugin_data {
14 | private_key_path = "/opt/spire/conf/agent/agent.key.pem"
15 | certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
16 | }
17 | }
18 | KeyManager "disk" {
19 | plugin_data {
20 | directory = "/opt/spire/data/agent"
21 | }
22 | }
23 | WorkloadAttestor "unix" {
24 | plugin_data {
25 | }
26 | }
27 | WorkloadAttestor "docker" {
28 | plugin_data {
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/nestedA/server/server.conf:
--------------------------------------------------------------------------------
1 | server {
2 | bind_address = "0.0.0.0"
3 | bind_port = "8081"
4 | socket_path = "/tmp/spire-server/private/api.sock"
5 | trust_domain = "example.org"
6 | data_dir = "/opt/spire/data/server"
7 | log_level = "DEBUG"
8 | ca_ttl = "24h"
9 | }
10 |
11 | plugins {
12 | DataStore "sql" {
13 | plugin_data {
14 | database_type = "sqlite3"
15 | connection_string = "/opt/spire/data/server/datastore.sqlite3"
16 | }
17 | }
18 | NodeAttestor "x509pop" {
19 | plugin_data {
20 | ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
21 | }
22 | }
23 | KeyManager "memory" {
24 | plugin_data = {}
25 | }
26 | UpstreamAuthority "spire" {
27 | plugin_data = {
28 | server_address = "root-server"
29 | server_port = 8081
30 | workload_api_socket = "/opt/spire/sockets/workload_api.sock"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/nestedB/agent/agent.conf:
--------------------------------------------------------------------------------
1 | agent {
2 | data_dir = "/opt/spire/data/agent"
3 | log_level = "DEBUG"
4 | server_address = "nestedB-server"
5 | server_port = "8081"
6 | socket_path = "/opt/spire/sockets/workload_api.sock"
7 | trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
8 | trust_domain = "example.org"
9 | }
10 |
11 | plugins {
12 | NodeAttestor "x509pop" {
13 | plugin_data {
14 | private_key_path = "/opt/spire/conf/agent/agent.key.pem"
15 | certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
16 | }
17 | }
18 | KeyManager "disk" {
19 | plugin_data {
20 | directory = "/opt/spire/data/agent"
21 | }
22 | }
23 | WorkloadAttestor "unix" {
24 | plugin_data {
25 | }
26 | }
27 | WorkloadAttestor "docker" {
28 | plugin_data {
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/nestedB/server/server.conf:
--------------------------------------------------------------------------------
1 | server {
2 | bind_address = "0.0.0.0"
3 | bind_port = "8081"
4 | socket_path = "/tmp/spire-server/private/api.sock"
5 | trust_domain = "example.org"
6 | data_dir = "/opt/spire/data/server"
7 | log_level = "DEBUG"
8 | ca_ttl = "24h"
9 | }
10 |
11 | plugins {
12 | DataStore "sql" {
13 | plugin_data {
14 | database_type = "sqlite3"
15 | connection_string = "/opt/spire/data/server/datastore.sqlite3"
16 | }
17 | }
18 | NodeAttestor "x509pop" {
19 | plugin_data {
20 | ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
21 | }
22 | }
23 | KeyManager "memory" {
24 | plugin_data = {}
25 | }
26 | UpstreamAuthority "spire" {
27 | plugin_data = {
28 | server_address = "root-server"
29 | server_port = 8081
30 | workload_api_socket = "/opt/spire/sockets/workload_api.sock"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/root/agent/agent.conf:
--------------------------------------------------------------------------------
1 | agent {
2 | data_dir = "/opt/spire/data/agent"
3 | log_level = "DEBUG"
4 | server_address = "root-server"
5 | server_port = "8081"
6 | socket_path = "/opt/spire/sockets/workload_api.sock"
7 | trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
8 | trust_domain = "example.org"
9 | }
10 |
11 | plugins {
12 | NodeAttestor "x509pop" {
13 | plugin_data {
14 | private_key_path = "/opt/spire/conf/agent/agent.key.pem"
15 | certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
16 | }
17 | }
18 | KeyManager "disk" {
19 | plugin_data {
20 | directory = "/opt/spire/data/agent"
21 | }
22 | }
23 | WorkloadAttestor "docker" {
24 | plugin_data {
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/root/server/server.conf:
--------------------------------------------------------------------------------
1 | server {
2 | bind_address = "0.0.0.0"
3 | bind_port = "8081"
4 | socket_path = "/tmp/spire-server/private/api.sock"
5 | trust_domain = "example.org"
6 | data_dir = "/opt/spire/data/server"
7 | log_level = "DEBUG"
8 | ca_ttl = "24h"
9 | }
10 |
11 | plugins {
12 | DataStore "sql" {
13 | plugin_data {
14 | database_type = "sqlite3"
15 | connection_string = "/opt/spire/data/server/datastore.sqlite3"
16 | }
17 | }
18 | NodeAttestor "x509pop" {
19 | plugin_data {
20 | ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
21 | }
22 | }
23 | KeyManager "memory" {
24 | plugin_data = {}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | PARENT_DIR="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
6 |
7 | norm=$(tput sgr0) || true
8 | green=$(tput setaf 2) || true
9 |
10 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml down
11 |
12 | echo "${green}Cleaning completed.${norm}"
13 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/scripts/create-workload-registration-entries.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | PARENT_DIR="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
6 |
7 | norm=$(tput sgr0) || true
8 | green=$(tput setaf 2) || true
9 | red=$(tput setaf 1) || true
10 | bold=$(tput bold) || true
11 |
12 | timestamp() {
13 | date -u "+[%Y-%m-%dT%H:%M:%SZ]"
14 | }
15 |
16 | log() {
17 | echo "${bold}$(timestamp) $*${norm}"
18 | }
19 |
20 | fingerprint() {
21 | # calculate the SHA1 digest of the DER bytes of the certificate using the
22 | # "coreutils" output format (`-r`) to provide uniform output from
23 | # `openssl sha1` on macOS and linux.
24 | openssl x509 -in "$1" -outform DER | openssl sha1 -r | awk '{print $1}'
25 | }
26 |
27 | check-entry-is-propagated() {
28 | # Check at most 30 times that the agent has successfully synced down the workload entry.
29 | # Wait one second between checks.
30 | log "Checking registration entry is propagated..."
31 | for ((i=1;i<=30;i++)); do
32 | if docker compose -f "${PARENT_DIR}"/docker-compose.yaml logs $1 | grep -qe "$2"; then
33 | log "${green}Entry is propagated.${nn}"
34 | return 0
35 | fi
36 | sleep 1
37 | done
38 |
39 | log "${red}timed out waiting for the entry to be progagated to the agent${red}"
40 | exit 1
41 | }
42 |
43 |
44 | # Workload for nestedA deployment
45 | log "creating nestedA workload registration entry..."
46 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T nestedA-server \
47 | /opt/spire/bin/spire-server entry create \
48 | -parentID "spiffe://example.org/spire/agent/x509pop/$(fingerprint "${PARENT_DIR}"/nestedA/agent/agent.crt.pem)" \
49 | -spiffeID "spiffe://example.org/nestedA/workload" \
50 | -selector "unix:uid:1001"
51 |
52 | check-entry-is-propagated nestedA-agent spiffe://example.org/nestedA/workload
53 |
54 |
55 | # Workload for nestedB deployment
56 | log "creating nestedB workload registration entry..."
57 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T nestedB-server \
58 | /opt/spire/bin/spire-server entry create \
59 | -parentID "spiffe://example.org/spire/agent/x509pop/$(fingerprint "${PARENT_DIR}"/nestedB/agent/agent.crt.pem)" \
60 | -spiffeID "spiffe://example.org/nestedB/workload" \
61 | -selector "unix:uid:1001"
62 |
63 | check-entry-is-propagated nestedB-agent spiffe://example.org/nestedB/workload
64 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/scripts/gencerts.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto"
6 | "crypto/ecdsa"
7 | "crypto/elliptic"
8 | "crypto/rand"
9 | "crypto/x509"
10 | "crypto/x509/pkix"
11 | "encoding/pem"
12 | "fmt"
13 | "io/ioutil"
14 | "math/big"
15 | "os"
16 | "path/filepath"
17 | "time"
18 | )
19 |
20 | func main() {
21 | if len(os.Args) < 3 {
22 | fmt.Fprintln(os.Stderr, "usage: gencerts SERVERDIR AGENTDIR [AGENTDIR...]")
23 | os.Exit(1)
24 | }
25 |
26 | notAfter := time.Now().Add(time.Hour * 2)
27 |
28 | caKey := generateKey()
29 | caCert := createRootCertificate(caKey, &x509.Certificate{
30 | SerialNumber: big.NewInt(1),
31 | BasicConstraintsValid: true,
32 | IsCA: true,
33 | NotAfter: notAfter,
34 | Subject: pkix.Name{CommonName: "Agent CA"},
35 | })
36 |
37 | writeCerts(filepath.Join(os.Args[1], "agent-cacert.pem"), caCert)
38 |
39 | for i, dir := range os.Args[2:] {
40 | agentKey := generateKey()
41 | agentCert := createCertificate(agentKey, &x509.Certificate{
42 | SerialNumber: big.NewInt(int64(i)),
43 | KeyUsage: x509.KeyUsageDigitalSignature,
44 | NotAfter: notAfter,
45 | Subject: pkix.Name{CommonName: filepath.Base(dir)},
46 | }, caKey, caCert)
47 |
48 | writeKey(filepath.Join(dir, "agent.key.pem"), agentKey)
49 | writeCerts(filepath.Join(dir, "agent.crt.pem"), agentCert)
50 | }
51 | }
52 |
53 | func createRootCertificate(key crypto.Signer, tmpl *x509.Certificate) *x509.Certificate {
54 | return createCertificate(key, tmpl, key, tmpl)
55 | }
56 |
57 | func createCertificate(key crypto.Signer, tmpl *x509.Certificate, parentKey crypto.Signer, parent *x509.Certificate) *x509.Certificate {
58 | certDER, err := x509.CreateCertificate(rand.Reader, tmpl, parent, key.Public(), parentKey)
59 | checkErr(err)
60 | cert, err := x509.ParseCertificate(certDER)
61 | checkErr(err)
62 | return cert
63 | }
64 |
65 | func generateKey() crypto.Signer {
66 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
67 | checkErr(err)
68 | return key
69 | }
70 |
71 | func writeKey(path string, key crypto.Signer) {
72 | keyBytes, err := x509.MarshalPKCS8PrivateKey(key)
73 | checkErr(err)
74 | pemBytes := pem.EncodeToMemory(&pem.Block{
75 | Type: "PRIVATE KEY",
76 | Bytes: keyBytes,
77 | })
78 | writeFile(path, pemBytes, 0600)
79 | }
80 |
81 | func writeCerts(path string, certs ...*x509.Certificate) {
82 | data := new(bytes.Buffer)
83 | for _, cert := range certs {
84 | err := pem.Encode(data, &pem.Block{
85 | Type: "CERTIFICATE",
86 | Bytes: cert.Raw,
87 | })
88 | checkErr(err)
89 | }
90 | writeFile(path, data.Bytes(), 0644)
91 | }
92 |
93 | func writeFile(path string, data []byte, mode os.FileMode) {
94 | err := ioutil.WriteFile(path, data, mode)
95 | checkErr(err)
96 | }
97 |
98 | func checkErr(err error) {
99 | if err != nil {
100 | panic(err)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | PARENT_DIR="$(dirname "$DIR")"
7 |
8 | norm=$(tput sgr0) || true
9 | green=$(tput setaf 2) || true
10 | red=$(tput setaf 1) || true
11 | bold=$(tput bold) || true
12 |
13 |
14 | timestamp() {
15 | date -u "+[%Y-%m-%dT%H:%M:%SZ]"
16 | }
17 |
18 | log() {
19 | echo "${bold}$(timestamp) $*${norm}"
20 | }
21 |
22 | setup() {
23 | # Generates certs
24 | go run "${DIR}/gencerts.go" "$@"
25 | }
26 |
27 | fingerprint() {
28 | # calculate the SHA1 digest of the DER bytes of the certificate using the
29 | # "coreutils" output format (`-r`) to provide uniform output from
30 | # `openssl sha1` on macOS and linux.
31 | openssl x509 -in "$1" -outform DER | openssl sha1 -r | awk '{print $1}'
32 | }
33 |
34 | check-entry-is-propagated() {
35 | # Check at most 30 times that the agent has successfully synced down the workload entry.
36 | # Wait one second between checks.
37 | log "Checking registration entry is propagated..."
38 | for ((i=1;i<=30;i++)); do
39 | if docker compose -f "${PARENT_DIR}"/docker-compose.yaml logs $1 | grep -qe "$2"; then
40 | log "${green}Entry is propagated.${nn}"
41 | return 0
42 | fi
43 | sleep 1
44 | done
45 |
46 | log "${red}timed out waiting for the entry to be progagated to the agent${norm}"
47 | exit 1
48 | }
49 |
50 | check-server-is-ready() {
51 | # Check at most 30 times that the agent has successfully synced down the workload entry.
52 | # Wait one second between checks.
53 | log "Checking server is ready..."
54 | for ((i=1;i<=30;i++)); do
55 | if docker compose -f "${PARENT_DIR}"/docker-compose.yaml logs $1 | grep -qe "Starting Server APIs"; then
56 | log "${green}Server is ready.${nn}"
57 | return 0
58 | fi
59 | sleep 1
60 | done
61 |
62 | log "${red}timed out waiting for the entry to be progagated to the agent${norm}"
63 | exit 1
64 | }
65 |
66 | # create a shared folder for root agent socket to be accessed by nestedA and nestedB servers
67 | mkdir -p "${PARENT_DIR}"/sharedRootSocket
68 |
69 |
70 | # Starts root SPIRE deployment
71 | log "Generate certificates for the root SPIRE deployment"
72 | setup "${PARENT_DIR}"/root/server "${PARENT_DIR}"/root/agent
73 |
74 | log "Start root server"
75 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d root-server
76 |
77 | log "bootstrapping root-agent."
78 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T root-server /opt/spire/bin/spire-server bundle show > "${PARENT_DIR}"/root/agent/bootstrap.crt
79 |
80 | log "Start root agent"
81 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d root-agent
82 |
83 | # Creates registration entries for the nested servers
84 | log "creating nestedA downstream registration entry..."
85 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T root-server \
86 | /opt/spire/bin/spire-server entry create \
87 | -parentID "spiffe://example.org/spire/agent/x509pop/$(fingerprint "${PARENT_DIR}"/root/agent/agent.crt.pem)" \
88 | -spiffeID "spiffe://example.org/nestedA" \
89 | -selector "docker:label:org.example.name:nestedA-server" \
90 | -downstream
91 |
92 | check-entry-is-propagated root-agent spiffe://example.org/nestedA
93 |
94 | log "creating nestedB downstream registration entry..."
95 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T root-server \
96 | /opt/spire/bin/spire-server entry create \
97 | -parentID "spiffe://example.org/spire/agent/x509pop/$(fingerprint "${PARENT_DIR}"/root/agent/agent.crt.pem)" \
98 | -spiffeID "spiffe://example.org/nestedB" \
99 | -selector "docker:label:org.example.name:nestedB-server" \
100 | -downstream
101 |
102 | check-entry-is-propagated root-agent spiffe://example.org/nestedB
103 |
104 |
105 | # Starts nestedA SPIRE deployment
106 | log "Generate certificates for the nestedA deployment"
107 | setup "${PARENT_DIR}"/nestedA/server "${PARENT_DIR}"/nestedA/agent
108 |
109 | log "Starting nestedA-server.."
110 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d nestedA-server
111 |
112 | check-server-is-ready nestedA-server
113 |
114 | log "bootstrapping nestedA agent..."
115 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T nestedA-server /opt/spire/bin/spire-server bundle show > "${PARENT_DIR}"/nestedA/agent/bootstrap.crt
116 |
117 | log "Starting nestedA-agent..."
118 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d nestedA-agent
119 |
120 |
121 | # Starts nestedB SPIRE deployment
122 | log "Generate certificates for the nestedB deployment"
123 | setup "${PARENT_DIR}"/nestedB/server "${PARENT_DIR}"/nestedB/agent
124 |
125 | log "Starting nestedB-server.."
126 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d nestedB-server
127 |
128 | check-server-is-ready nestedB-server
129 |
130 | log "bootstrapping nestedB agent..."
131 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml exec -T nestedB-server /opt/spire/bin/spire-server bundle show > "${PARENT_DIR}"/nestedB/agent/bootstrap.crt
132 |
133 | log "Starting nestedB-agent..."
134 | docker compose -f "${PARENT_DIR}"/docker-compose.yaml up -d nestedB-agent
135 |
--------------------------------------------------------------------------------
/docker-compose/nested-spire/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | norm=$(tput sgr0) || true
4 | green=$(tput setaf 2) || true
5 | red=$(tput setaf 1) || true
6 | bold=$(tput bold) || true
7 |
8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9 |
10 | timestamp() {
11 | date -u "+[%Y-%m-%dT%H:%M:%SZ]"
12 | }
13 |
14 | log() {
15 | echo "${bold}$(timestamp) $*${norm}"
16 | }
17 |
18 | fail() {
19 | echo "${red}$(timestamp) $*${norm}"
20 | exit 1
21 | }
22 |
23 | clean-env() {
24 | log "Cleaning up..."
25 | bash "${DIR}"/scripts/clean-env.sh
26 | }
27 |
28 | trap clean-env EXIT
29 |
30 |
31 | log "Preparing Nested SPIRE environment..."
32 | clean-env
33 | bash "${DIR}"/scripts/set-env.sh
34 |
35 | log "Creating workload registration entries..."
36 | bash "${DIR}"/scripts/create-workload-registration-entries.sh
37 |
38 | log "checking nested JWT-SVID..."
39 | # Fetch JWT-SVID and extract token
40 | token=$(docker compose -f "${DIR}"/docker-compose.yaml exec -u 1001 -T nestedA-agent \
41 | /opt/spire/bin/spire-agent api fetch jwt -audience testIt -socketPath /opt/spire/sockets/workload_api.sock | sed -n '2p' | tr -d '\t') || fail "JWT-SVID check failed"
42 |
43 | # Validate token
44 | validation_result=$(docker compose -f "${DIR}"/docker-compose.yaml exec -u 1001 -T nestedB-agent \
45 | /opt/spire/bin/spire-agent api validate jwt -audience testIt -svid "${token}" -socketPath /opt/spire/sockets/workload_api.sock)
46 |
47 | if echo $validation_result | grep -qe "SVID is valid."; then
48 | echo "${green}Success${norm}"
49 | exit 0
50 | fi
51 |
52 | echo "${red}Failed! JTW-SVID cannot be validated.${norm}".
53 | exit 1
54 |
--------------------------------------------------------------------------------
/docker-compose/test-all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This script runs the test.sh script in each subdirectory to test if each
3 | # tutorial is working properly. It is run by the Travis CI tool when a PR
4 | # is submitted or merged on GitHub, but you can also run it interactively.
5 |
6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
7 |
8 | bold=$(tput bold) || true
9 | norm=$(tput sgr0) || true
10 | red=$(tput setaf 1) || true
11 | green=$(tput setaf 2) || true
12 |
13 | fail() {
14 | echo "${red}$*${norm}."
15 | exit 1
16 | }
17 |
18 | echo "${bold}Running all tests...${norm}"
19 | for testdir in "${DIR}"/*; do
20 | if [[ -x "${testdir}/test.sh" ]]; then
21 | testname=$(basename "$testdir")
22 | echo "${bold}Running \"$testname\" test...${norm}"
23 | if ${testdir}/test.sh; then
24 | echo "${green}\"$testname\" test succeeded${norm}"
25 | else
26 | echo "${red}\"$testname\" test failed${norm}"
27 | FAILED=true
28 | fi
29 | fi
30 | done
31 |
32 | if [ -n "${FAILED}" ]; then
33 | fail "There were test failures"
34 | fi
35 | echo "${green}Done. All test passed!${norm}"
36 | exit 0
37 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/.gitignore:
--------------------------------------------------------------------------------
1 | # Binary
2 | envoy-jwt-auth-helper
3 |
4 | # Editor specific configuration
5 | .idea
6 | .vscode
7 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:bookworm AS build-stage
2 |
3 | WORKDIR /app
4 | COPY . .
5 | RUN go mod download
6 | RUN go build
7 |
8 | FROM debian:bookworm-slim AS production-stage
9 | RUN apt update && DEBIAN_FRONTEND=noninteractive apt full-upgrade -y && \
10 | apt install -y dumb-init iputils-ping curl procps
11 |
12 | RUN mkdir /opt/helper
13 | COPY --from=build-stage /app/envoy-jwt-auth-helper /opt/helper
14 | ENTRYPOINT ["/usr/bin/dumb-init", "/opt/helper/envoy-jwt-auth-helper"]
15 | CMD []
16 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/README.md:
--------------------------------------------------------------------------------
1 | # Envoy JWT Auth Helper
2 | Simple gRPC service that implements [Envoy's External Authorization Filter](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz).
3 |
4 |
5 | _Envoy JWT Auth Helper_ needs to be configured as an External Authorization filter for Envoy. Then, for every HTTP request sent to the Envoy forward proxy, it obtains a JWT-SVID from the SPIRE Agent and inject it as a new request header. Finally the request is sent back to Envoy.
6 | On the other side, when the HTTP request arrives at the reverse proxy, the Envoy External Authorization module send the request to the _Envoy JWT Auth Helper_ which extracts the JWT-SVID from the header and connect to the SPIRE Agent to perform the validation. Once validated, the request is sent back to Envoy. If validation fails the request is denied.
7 |
8 |
9 |
10 | ## Modes
11 | This simple authentication server supports 2 modes:
12 |
13 | ### jwt_injection
14 |
15 | Connects to the SPIRE Agent to fetch a JWT-SVID which then is injected it into the request as a new header.
16 |
17 | ### jwt_svid_validator
18 |
19 | Extracts the added header from the request and connects to the SPIRE Agent to validate it.
20 |
21 | ## Build
22 |
23 | ```console
24 | go build
25 | ```
26 |
27 | ## Run:
28 |
29 | ```console
30 | ./envoy-jwt-auth-helper -config envoy-jwt-auth-helper.conf
31 | ```
32 |
33 | ## Configuration example:
34 |
35 | ```
36 | socket_path = "unix:///tmp/agent.sock"
37 | host = "127.0.0.1"
38 | port = 9010
39 | jwt_mode = "jwt_svid_validator"
40 | audience = "spiffe://example.org/myservice"
41 | ```
42 |
43 | ## As Envoy External Authorization filter
44 |
45 | Include an External Authorization Filter in the Envoy configuration that connects to the service. This is accomplish by adding a new HTTP filter:
46 |
47 | ``` console
48 | http_filters:
49 | - name: envoy.ext_authz
50 | config:
51 | grpc_service:
52 | envoy_grpc:
53 | cluster_name: ext-authz
54 | timeout: 0.5s
55 | ```
56 |
57 | And the corresponding cluster:
58 |
59 | ``` console
60 | - name: ext-authz
61 | connect_timeout: 1s
62 | type: strict_dns
63 | http2_protocol_options: {}
64 | hosts:
65 | - socket_address:
66 | address: 127.0.0.1
67 | port_value: 9010
68 | ```
69 |
70 | Note that the cluster is configured to talk to `127.0.0.1:9010`, the host and port set on the [configuration example](#configuration-example).
71 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/envoy-jwt-auth-helper.conf:
--------------------------------------------------------------------------------
1 | # Path to the domain socket used to communicate with the Workload API
2 | socket_path = "unix:///run/spire/sockets/agent.sock"
3 |
4 | # Host where the app will be listening
5 | host = "127.0.0.1"
6 | # Port where the app will be listening
7 | port = 9010
8 |
9 | # Options: "jwt_injection", "jwt_svid_validator"
10 | jwt_mode = "jwt_svid_validator"
11 |
12 | # JWT audience value
13 | # Used in:
14 | # - AUTH module: jwt_injection (for JWT injection, set in the JWT-SVID)
15 | # - AUTH module: jwt_svid_validator (for JWT validation, compared against the JWT-SVID)
16 | audience = "spiffe://example.org/myservice"
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/spiffe/envoy-jwt-auth-helper
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/envoyproxy/go-control-plane v0.11.0
7 | github.com/gogo/googleapis v1.4.1
8 | github.com/golang/protobuf v1.5.3
9 | github.com/hashicorp/hcl v1.0.0
10 | github.com/spiffe/go-spiffe/v2 v2.1.3
11 | google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5
12 | google.golang.org/grpc v1.54.0
13 | )
14 |
15 | require (
16 | github.com/Microsoft/go-winio v0.6.0 // indirect
17 | github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 // indirect
18 | github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect
19 | github.com/go-jose/go-jose/v3 v3.0.0 // indirect
20 | github.com/gogo/protobuf v1.3.2 // indirect
21 | github.com/zeebo/errs v1.3.0 // indirect
22 | golang.org/x/crypto v0.7.0 // indirect
23 | golang.org/x/mod v0.9.0 // indirect
24 | golang.org/x/net v0.8.0 // indirect
25 | golang.org/x/sys v0.6.0 // indirect
26 | golang.org/x/text v0.8.0 // indirect
27 | golang.org/x/tools v0.7.0 // indirect
28 | google.golang.org/protobuf v1.30.0 // indirect
29 | )
30 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "log"
7 | "net"
8 | "strconv"
9 |
10 | auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
11 | authExternal "github.com/spiffe/envoy-jwt-auth-helper/pkg/auth"
12 | "github.com/spiffe/envoy-jwt-auth-helper/pkg/config"
13 | "github.com/spiffe/go-spiffe/v2/workloadapi"
14 | "google.golang.org/grpc"
15 | )
16 |
17 | func main() {
18 | configFilePath := flag.String("config", "envoy-jwt-auth-helper.conf", "Path to configuration file")
19 | flag.Parse()
20 |
21 | c, err := config.ParseConfigFile(*configFilePath)
22 | if err != nil {
23 | log.Fatalf("Error parsing configuration file: %v", err)
24 | }
25 |
26 | lis, err := net.Listen("tcp", net.JoinHostPort(c.Host, strconv.Itoa(c.Port)))
27 | if err != nil {
28 | log.Fatalf("Failed to listen: %v", err)
29 | }
30 |
31 | s := grpc.NewServer([]grpc.ServerOption{grpc.MaxConcurrentStreams(10)}...)
32 |
33 | // Create options to configure Sources to use socket path passed via config file.
34 | clientOptions := workloadapi.WithClientOptions(workloadapi.WithAddr(c.SocketPath))
35 |
36 | // Create a JWTSource to validate provided tokens from clients
37 | jwtSource, err := workloadapi.NewJWTSource(context.Background(), clientOptions)
38 | if err != nil {
39 | log.Fatalf("Unable to create JWTSource: %v", err)
40 | }
41 | defer jwtSource.Close()
42 |
43 | authExternal, err := authExternal.NewAuthServer(c.SocketPath, c.Audience, c.JWTMode, jwtSource)
44 | if err != nil {
45 | log.Fatalf("Error creating AuthServer: %v", err)
46 | }
47 |
48 | auth.RegisterAuthorizationServer(s, authExternal)
49 |
50 | log.Printf("Starting gRPC Server at %d", c.Port)
51 | s.Serve(lis)
52 | }
53 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-auth-helper/pkg/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/hashicorp/hcl"
10 | )
11 |
12 | // Config available configurations
13 | type Config struct {
14 | SocketPath string `hcl:"socket_path"`
15 | Host string `hcl:"host"`
16 | Port int `hcl:"port"`
17 | JWTMode string `hcl:"jwt_mode"`
18 | Audience string `hcl:"audience"`
19 | }
20 |
21 | //ParseConfigFile parse config file
22 | func ParseConfigFile(filePath string) (*Config, error) {
23 | data, err := ioutil.ReadFile(filePath)
24 | if err != nil {
25 | if os.IsNotExist(err) {
26 | msg := "could not find config file %s: please use the -config flag"
27 | p, err := filepath.Abs(filePath)
28 | if err != nil {
29 | p = filePath
30 | msg = "config file not found at %s: use -config"
31 | }
32 | return nil, fmt.Errorf(msg, p)
33 | }
34 | return nil, err
35 | }
36 |
37 | c := new(Config)
38 | if err := hcl.Decode(c, string(data)); err != nil {
39 | return nil, fmt.Errorf("unable to decode configuration: %v", err)
40 | }
41 |
42 | return c, nil
43 | }
44 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/images/SPIRE-Envoy_JWT_OPA_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt-opa/images/SPIRE-Envoy_JWT_OPA_diagram.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/images/frontend-2_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt-opa/images/frontend-2_view.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/images/frontend-2_view_no_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt-opa/images/frontend-2_view_no_details.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/images/frontend_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt-opa/images/frontend_view.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/k8s/backend/backend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: backend
5 | labels:
6 | app: backend
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: backend
11 | template:
12 | metadata:
13 | labels:
14 | app: backend
15 | spec:
16 | hostPID: true
17 | hostNetwork: true
18 | dnsPolicy: ClusterFirstWithHostNet
19 | containers:
20 | - name: envoy
21 | image: envoyproxy/envoy:v1.25.1
22 | imagePullPolicy: IfNotPresent
23 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml"]
24 | ports:
25 | - containerPort: 9001
26 | volumeMounts:
27 | - name: envoy-config
28 | mountPath: "/run/envoy"
29 | readOnly: true
30 | - name: spire-agent-socket
31 | mountPath: /run/spire/sockets
32 | readOnly: true
33 | - name: auth-helper
34 | image: envoy-jwt-auth-helper:latest
35 | imagePullPolicy: IfNotPresent
36 | args: ["-config", "/run/envoy-jwt-auth-helper/config/envoy-jwt-auth-helper.conf"]
37 | ports:
38 | - containerPort: 9010
39 | volumeMounts:
40 | - name: envoy-jwt-auth-helper-config
41 | mountPath: "/run/envoy-jwt-auth-helper/config"
42 | readOnly: true
43 | - name: spire-agent-socket
44 | mountPath: /run/spire/sockets
45 | readOnly: true
46 | - name: backend
47 | image: nginx
48 | ports:
49 | - containerPort: 80
50 | volumeMounts:
51 | - name: backend-balance-json-data
52 | mountPath: "/usr/share/nginx/html/balances"
53 | readOnly: true
54 | - name: backend-profile-json-data
55 | mountPath: "/usr/share/nginx/html/profiles"
56 | readOnly: true
57 | - name: backend-transactions-json-data
58 | mountPath: "/usr/share/nginx/html/transactions"
59 | readOnly: true
60 | - name: opa
61 | image: openpolicyagent/opa:0.50.2-envoy
62 | imagePullPolicy: IfNotPresent
63 | ports:
64 | - name: opa-envoy
65 | containerPort: 8182
66 | protocol: TCP
67 | - name: opa-api-port
68 | containerPort: 8181
69 | protocol: TCP
70 | args:
71 | - "run"
72 | - "--server"
73 | - "--config-file=/run/opa/opa-config.yaml"
74 | - "/run/opa/opa-policy.rego"
75 | volumeMounts:
76 | - name: backend-opa-policy
77 | mountPath: /run/opa
78 | readOnly: true
79 | volumes:
80 | - name: envoy-config
81 | configMap:
82 | name: backend-envoy
83 | - name: backend-opa-policy
84 | configMap:
85 | name: backend-opa-policy-config
86 | - name: spire-agent-socket
87 | hostPath:
88 | path: /run/spire/sockets
89 | type: Directory
90 | - name: envoy-jwt-auth-helper-config
91 | configMap:
92 | name: be-envoy-jwt-auth-helper-config
93 | - name: backend-balance-json-data
94 | configMap:
95 | name: backend-balance-json-data
96 | - name: backend-profile-json-data
97 | configMap:
98 | name: backend-profile-json-data
99 | - name: backend-transactions-json-data
100 | configMap:
101 | name: backend-transactions-json-data
102 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/k8s/backend/config/envoy-jwt-auth-helper.conf:
--------------------------------------------------------------------------------
1 | socket_path = "unix:///run/spire/sockets/agent.sock"
2 | host = "0.0.0.0"
3 | port = 9010
4 | jwt_mode = "jwt_svid_validator"
5 | audience = "spiffe://example.org/ns/default/sa/default/backend"
6 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/k8s/backend/config/opa-config.yaml:
--------------------------------------------------------------------------------
1 | decision_logs:
2 | console: true
3 | plugins:
4 | envoy_ext_authz_grpc:
5 | addr: :8182
6 | query: data.envoy.authz.allow
7 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/k8s/backend/config/opa-policy.rego:
--------------------------------------------------------------------------------
1 | package envoy.authz
2 |
3 | import input.attributes.request.http as http_request
4 |
5 | default allow = false
6 |
7 | # allow Frontend service to access Backend service
8 | allow {
9 | valid_path
10 | http_request.method == "GET"
11 | svc_spiffe_id == "spiffe://example.org/ns/default/sa/default/frontend"
12 | }
13 |
14 | svc_spiffe_id = payload.sub {
15 | [_, encoded_token] := split(http_request.headers.authorization, " ")
16 | [_, payload, _] := io.jwt.decode(encoded_token)
17 | }
18 |
19 | valid_path {
20 | glob.match("/balances/*", [], http_request.path)
21 | }
22 |
23 | valid_path {
24 | glob.match("/profiles/*", [], http_request.path)
25 | }
26 |
27 | valid_path {
28 | glob.match("/transactions/*", [], http_request.path)
29 | }
30 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/k8s/kustomization.yaml:
--------------------------------------------------------------------------------
1 | configMapGenerator:
2 | - name: backend-envoy
3 | files:
4 | - backend/config/envoy.yaml
5 | - name: backend-opa-policy-config
6 | files:
7 | - backend/config/opa-policy.rego
8 | - backend/config/opa-config.yaml
9 |
10 | generatorOptions:
11 | disableNameSuffixHash: true
12 |
13 | resources:
14 | - backend/backend-deployment.yaml
15 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/scripts/backend-opa-logs.sh:
--------------------------------------------------------------------------------
1 | POD=$(kubectl get pod -l app=backend -o jsonpath="{.items[0].metadata.name}")
2 | kubectl logs $POD -c opa | jq .
3 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/scripts/backend-update-policy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | kubectl edit configmap backend-opa-policy-config
4 |
5 | # Restart pod
6 | kubectl scale deployment backend --replicas=0
7 | kubectl scale deployment backend --replicas=1
8 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/scripts/build-helper.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | #/bin/bash
4 |
5 | set -e
6 |
7 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
8 | EXAMPLEDIR="$(dirname "$DIR")"
9 | K8SDIR="$(dirname "$EXAMPLEDIR")"
10 |
11 | DOCKER_IMAGE="envoy-jwt-auth-helper"
12 | SERVICE_VERSION="1.0.0"
13 |
14 | echo "Building ${DOCKER_IMAGE}"
15 | (cd $K8SDIR/envoy-jwt-auth-helper; docker build --no-cache --tag ${DOCKER_IMAGE} .)
16 |
17 | case $1 in
18 | "minikube")
19 | echo "Loading image into minikube"
20 | minikube image load $DOCKER_IMAGE:latest;;
21 | "kind")
22 | echo "Load image into kind"
23 | kind load docker-image $DOCKER_IMAGE:latest;;
24 | *)
25 | echo "Image builded successfully";;
26 | esac
27 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | green=$(tput setaf 2) || true
12 |
13 | echo "${bb}Deleting new backend resources...${nn}"
14 | kubectl delete -k "${EXAMPLEDIR}"/k8s/. --ignore-not-found
15 |
16 | echo "${bb}Deleting pre-set resources...${nn}"
17 | bash "${K8SDIR}"/envoy-jwt/scripts/clean-env.sh > /dev/null
18 |
19 | echo "${green}Cleaning completed.${nn}"
20 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/scripts/pre-set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | #Creates Envoy-JWT scenario
10 | bash "${K8SDIR}"/envoy-jwt/scripts/set-env.sh
11 |
12 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | red=$(tput setaf 1) || true
12 |
13 | restart_deployment() {
14 | kubectl scale deployment backend --replicas=0
15 |
16 | # Let's be sure that there is no pod running before starting the new pod
17 | for ((i=0;i<30;i++)); do
18 | if ! kubectl get pod --selector=app=backend 2>&1 | grep -qe "No resources found in default namespace." ; then
19 | sleep 5
20 | echo "Waiting until backend pod is terminated..."
21 | continue
22 | fi
23 | echo "Backend pod is terminated. Let's re-start it."
24 | POD_TERMINATED=1
25 | break
26 | done
27 | if [ -z "${POD_TERMINATED}" ]; then
28 | echo "${red}Timed out waiting for pods to be terminated.${nn}"
29 | exit 1
30 | fi
31 |
32 | kubectl scale deployment backend --replicas=1
33 | }
34 |
35 | wait_for_envoy() {
36 | # waits until backend deployment is completed and Envoy ready
37 | kubectl rollout status deployment/backend --timeout=60s
38 |
39 | LOGLINE="all dependencies initialized. starting workers"
40 | LOGLINE2="membership update for TLS cluster backend added 1 removed 1"
41 | for ((i=0;i<30;i++)); do
42 | if ! kubectl logs --tail=1000 --selector=app=backend -c envoy | grep -qe "${LOGLINE}" ; then
43 | sleep 5
44 | echo "Waiting until backend envoy instance is ready..."
45 | continue
46 | fi
47 | if ! kubectl logs --tail=10 --selector=app=frontend -c envoy | grep -qe "${LOGLINE2}" ; then
48 | sleep 5
49 | echo "Waiting until frontend envoy instance is in sync with the backend envoy..."
50 | continue
51 | fi
52 | echo "${bb}Workloads ready.${nn}"
53 | WK_READY=1
54 | break
55 | done
56 | if [ -z "${WK_READY}" ]; then
57 | echo "${red}Timed out waiting for workloads to be ready.${nn}"
58 | exit 1
59 | fi
60 | }
61 |
62 | #Creates Envoy-JWT scenario
63 | bash "${EXAMPLEDIR}"/scripts/pre-set-env.sh
64 |
65 | echo "${bb}Applying new OPA configuration...${nn}"
66 | kubectl apply -k "${EXAMPLEDIR}"/k8s/. > /dev/null
67 |
68 | echo "${bb}Restarting backend pod...${nn}"
69 | restart_deployment
70 |
71 | echo "${bb}Waiting until deployments and Envoy are ready...${nn}"
72 | wait_for_envoy
73 |
74 | echo "${bb}Envoy JWT OPA Environment creation completed.${nn}"
75 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt-opa/test.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | bb=$(tput bold) || true
8 | nn=$(tput sgr0) || true
9 | red=$(tput setaf 1) || true
10 | green=$(tput setaf 2) || true
11 |
12 | clean-env() {
13 | echo "${bb}Cleaning up...${nn}"
14 | bash "${DIR}"/scripts/clean-env.sh > /dev/null
15 | }
16 |
17 | trap clean-env EXIT
18 |
19 | echo "${bb}Preparing environment...${nm}"
20 | clean-env
21 |
22 | # Build helper image
23 | bash "${DIR}"/scripts/build-helper.sh minikube
24 |
25 | # Creates Envoy JWT OPA scenario
26 | bash "${DIR}"/scripts/set-env.sh
27 |
28 | echo "${bb}Running test...${nm}"
29 | # If balance is part of the response, then the request has a valid token and it was authorized by the OPA rules.
30 | BALANCE_LINE="Your current balance is 10.95"
31 | if curl -s $(minikube service frontend --url) | grep -qe "$BALANCE_LINE"; then
32 | echo "${green}Success${nn}"
33 | exit 0
34 | fi
35 |
36 | echo "${red}Failed! Request did not make it through the proxies.${nn}"
37 | exit 1
38 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/create-registration-entries.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 | register() {
9 | kubectl exec -n spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry create $@
10 | }
11 |
12 | echo "${bb}Creating registration entry for the backend - auth-server...${nn}"
13 | register \
14 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \
15 | -spiffeID spiffe://example.org/ns/default/sa/default/backend \
16 | -selector k8s:ns:default \
17 | -selector k8s:sa:default \
18 | -selector k8s:pod-label:app:backend \
19 | -selector k8s:container-name:auth-helper
20 |
21 | echo "${bb}Creating registration entry for the frontend - auth-server...${nn}"
22 | register \
23 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \
24 | -spiffeID spiffe://example.org/ns/default/sa/default/frontend \
25 | -selector k8s:ns:default \
26 | -selector k8s:sa:default \
27 | -selector k8s:pod-label:app:frontend \
28 | -selector k8s:container-name:auth-helper
29 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/images/SPIRE-Envoy_JWT-SVID_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt/images/SPIRE-Envoy_JWT-SVID_diagram.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt/images/frontend-2_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt/images/frontend-2_view.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt/images/frontend-2_view_no_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt/images/frontend-2_view_no_details.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt/images/frontend_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-jwt/images/frontend_view.png
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/backend/backend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: backend
5 | labels:
6 | app: backend
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: backend
11 | template:
12 | metadata:
13 | labels:
14 | app: backend
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: IfNotPresent
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml"]
21 | ports:
22 | - containerPort: 9001
23 | volumeMounts:
24 | - name: envoy-config
25 | mountPath: "/run/envoy"
26 | readOnly: true
27 | - name: spire-agent-socket
28 | mountPath: /run/spire/sockets
29 | readOnly: true
30 | - name: auth-helper
31 | image: envoy-jwt-auth-helper:latest
32 | imagePullPolicy: IfNotPresent
33 | args: ["-config", "/run/envoy-jwt-auth-helper/config/envoy-jwt-auth-helper.conf"]
34 | ports:
35 | - containerPort: 9010
36 | volumeMounts:
37 | - name: envoy-jwt-auth-helper-config
38 | mountPath: "/run/envoy-jwt-auth-helper/config"
39 | readOnly: true
40 | - name: spire-agent-socket
41 | mountPath: /run/spire/sockets
42 | readOnly: true
43 | - name: backend
44 | image: nginx
45 | ports:
46 | - containerPort: 80
47 | volumeMounts:
48 | - name: backend-balance-json-data
49 | mountPath: "/usr/share/nginx/html/balances"
50 | readOnly: true
51 | - name: backend-profile-json-data
52 | mountPath: "/usr/share/nginx/html/profiles"
53 | readOnly: true
54 | - name: backend-transactions-json-data
55 | mountPath: "/usr/share/nginx/html/transactions"
56 | readOnly: true
57 | volumes:
58 | - name: envoy-config
59 | configMap:
60 | name: backend-envoy
61 | - name: spire-agent-socket
62 | hostPath:
63 | path: /run/spire/sockets
64 | type: Directory
65 | - name: envoy-jwt-auth-helper-config
66 | configMap:
67 | name: be-envoy-jwt-auth-helper-config
68 | - name: backend-balance-json-data
69 | configMap:
70 | name: backend-balance-json-data
71 | - name: backend-profile-json-data
72 | configMap:
73 | name: backend-profile-json-data
74 | - name: backend-transactions-json-data
75 | configMap:
76 | name: backend-transactions-json-data
77 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/backend/config/envoy-jwt-auth-helper.conf:
--------------------------------------------------------------------------------
1 | socket_path = "unix:///run/spire/sockets/agent.sock"
2 | host = "0.0.0.0"
3 | port = 9010
4 | jwt_mode = "jwt_svid_validator"
5 | audience = "spiffe://example.org/ns/default/sa/default/backend"
6 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/backend/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "backend"
3 | cluster: "demo-cluster-spire"
4 | static_resources:
5 | listeners:
6 | - name: local_service
7 | address:
8 | socket_address:
9 | address: 0.0.0.0
10 | port_value: 9001
11 | filter_chains:
12 | - filters:
13 | - name: envoy.filters.network.http_connection_manager
14 | typed_config:
15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
16 | common_http_protocol_options:
17 | idle_timeout: 1s
18 | codec_type: auto
19 | access_log:
20 | - name: envoy.access_loggers.file
21 | typed_config:
22 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
23 | path: "/tmp/inbound-proxy.log"
24 | stat_prefix: ingress_http
25 | route_config:
26 | name: local_route
27 | virtual_hosts:
28 | - name: local_service
29 | domains: ["*"]
30 | routes:
31 | - match:
32 | prefix: "/"
33 | route:
34 | cluster: local_service
35 | http_filters:
36 | - name: envoy.filters.http.ext_authz
37 | typed_config:
38 | "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
39 | transport_api_version: V3
40 | grpc_service:
41 | envoy_grpc:
42 | cluster_name: ext-authz
43 | timeout: 0.5s
44 | - name: envoy.filters.http.router
45 | typed_config:
46 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
47 | transport_socket:
48 | name: envoy.transport_sockets.tls
49 | typed_config:
50 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
51 | common_tls_context:
52 | tls_certificate_sds_secret_configs:
53 | - name: "spiffe://example.org/ns/default/sa/default/backend"
54 | sds_config:
55 | resource_api_version: V3
56 | api_config_source:
57 | api_type: GRPC
58 | transport_api_version: V3
59 | grpc_services:
60 | envoy_grpc:
61 | cluster_name: spire_agent
62 | combined_validation_context:
63 | # validate the SPIFFE ID of incoming clients (optionally)
64 | default_validation_context:
65 | match_typed_subject_alt_names:
66 | - san_type: URI
67 | matcher:
68 | exact: "spiffe://example.org/ns/default/sa/default/frontend"
69 | - san_type: URI
70 | matcher:
71 | exact: "spiffe://example.org/ns/default/sa/default/frontend-2"
72 | # obtain the trust bundle from SDS
73 | validation_context_sds_secret_config:
74 | name: "spiffe://example.org"
75 | sds_config:
76 | resource_api_version: V3
77 | api_config_source:
78 | api_type: GRPC
79 | transport_api_version: V3
80 | grpc_services:
81 | envoy_grpc:
82 | cluster_name: spire_agent
83 | tls_params:
84 | ecdh_curves:
85 | - X25519:P-256:P-521:P-384
86 | clusters:
87 | - name: spire_agent
88 | connect_timeout: 0.25s
89 | http2_protocol_options: {}
90 | load_assignment:
91 | cluster_name: spire_agent
92 | endpoints:
93 | - lb_endpoints:
94 | - endpoint:
95 | address:
96 | pipe:
97 | path: /run/spire/sockets/agent.sock
98 | - name: local_service
99 | connect_timeout: 1s
100 | type: strict_dns
101 | load_assignment:
102 | cluster_name: local_service
103 | endpoints:
104 | - lb_endpoints:
105 | - endpoint:
106 | address:
107 | socket_address:
108 | address: 127.0.0.1
109 | port_value: 80
110 | - name: ext-authz
111 | connect_timeout: 1s
112 | type: strict_dns
113 | http2_protocol_options: {}
114 | load_assignment:
115 | cluster_name: ext-authz
116 | endpoints:
117 | - lb_endpoints:
118 | - endpoint:
119 | address:
120 | socket_address:
121 | address: 127.0.0.1
122 | port_value: 9010
123 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend-2/config/envoy-jwt-auth-helper.conf:
--------------------------------------------------------------------------------
1 | socket_path = "unix:///run/spire/sockets/agent.sock"
2 | host = "0.0.0.0"
3 | port = 9012
4 | jwt_mode = "jwt_injection"
5 | audience = "spiffe://example.org/ns/default/sa/default/backend"
6 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend-2/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "frontend-2"
3 | cluster: "demo-cluster-spire"
4 | admin:
5 | access_log:
6 | - name: envoy.access_loggers.file
7 | typed_config:
8 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
9 | path: "/tmp/admin_access0.log"
10 | address:
11 | socket_address:
12 | protocol: TCP
13 | address: 127.0.0.1
14 | port_value: 8102
15 | static_resources:
16 | listeners:
17 | - name: outbound_proxy
18 | address:
19 | socket_address:
20 | address: 127.0.0.1
21 | port_value: 3003
22 | filter_chains:
23 | - filters:
24 | - name: envoy.filters.network.http_connection_manager
25 | typed_config:
26 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
27 | common_http_protocol_options:
28 | idle_timeout: 1s
29 | codec_type: auto
30 | access_log:
31 | - name: envoy.access_loggers.file
32 | typed_config:
33 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
34 | path: "/tmp/outbound-proxy.log"
35 | stat_prefix: ingress_http
36 | route_config:
37 | name: service_route
38 | virtual_hosts:
39 | - name: outbound_proxy
40 | domains: ["*"]
41 | routes:
42 | - match:
43 | prefix: "/"
44 | route:
45 | cluster: backend
46 | http_filters:
47 | - name: envoy.filters.http.ext_authz
48 | typed_config:
49 | "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
50 | grpc_service:
51 | envoy_grpc:
52 | cluster_name: ext-authz
53 | timeout: 0.5s
54 | transport_api_version: V3
55 | - name: envoy.filters.http.router
56 | typed_config:
57 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
58 |
59 | clusters:
60 | - name: spire_agent
61 | connect_timeout: 0.25s
62 | http2_protocol_options: {}
63 | load_assignment:
64 | cluster_name: spire_agent
65 | endpoints:
66 | - lb_endpoints:
67 | - endpoint:
68 | address:
69 | pipe:
70 | path: /run/spire/sockets/agent.sock
71 | - name: ext-authz
72 | connect_timeout: 1s
73 | type: strict_dns
74 | http2_protocol_options: {}
75 | load_assignment:
76 | cluster_name: ext-authz
77 | endpoints:
78 | - lb_endpoints:
79 | - endpoint:
80 | address:
81 | socket_address:
82 | address: 127.0.0.1
83 | port_value: 9012
84 | - name: backend
85 | connect_timeout: 0.25s
86 | type: strict_dns
87 | lb_policy: ROUND_ROBIN
88 | load_assignment:
89 | cluster_name: backend
90 | endpoints:
91 | - lb_endpoints:
92 | - endpoint:
93 | address:
94 | socket_address:
95 | address: backend-envoy
96 | port_value: 9001
97 | transport_socket:
98 | name: envoy.transport_sockets.tls
99 | typed_config:
100 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
101 | common_tls_context:
102 | tls_certificate_sds_secret_configs:
103 | - name: "spiffe://example.org/ns/default/sa/default/frontend-2"
104 | sds_config:
105 | resource_api_version: V3
106 | api_config_source:
107 | api_type: GRPC
108 | transport_api_version: V3
109 | grpc_services:
110 | envoy_grpc:
111 | cluster_name: spire_agent
112 | combined_validation_context:
113 | # validate the SPIFFE ID of the server (recommended)
114 | default_validation_context:
115 | match_typed_subject_alt_names:
116 | - san_type: URI
117 | matcher:
118 | exact: "spiffe://example.org/ns/default/sa/default/backend"
119 | validation_context_sds_secret_config:
120 | name: "spiffe://example.org"
121 | sds_config:
122 | resource_api_version: V3
123 | api_config_source:
124 | api_type: GRPC
125 | transport_api_version: V3
126 | grpc_services:
127 | envoy_grpc:
128 | cluster_name: spire_agent
129 | tls_params:
130 | ecdh_curves:
131 | - X25519:P-256:P-521:P-384
132 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend-2/create-registration-entry.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 | register() {
9 | kubectl exec -n spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry create $@
10 | }
11 |
12 | echo "${bb}Creating registration entry for the frontend-2 - auth-server...${nn}"
13 | register \
14 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \
15 | -spiffeID spiffe://example.org/ns/default/sa/default/frontend-2 \
16 | -selector k8s:ns:default \
17 | -selector k8s:sa:default \
18 | -selector k8s:pod-label:app:frontend-2 \
19 | -selector k8s:container-name:auth-helper
20 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend-2/frontend-2-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frontend-2
5 | labels:
6 | app: frontend-2
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: frontend-2
11 | template:
12 | metadata:
13 | labels:
14 | app: frontend-2
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: Always
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml", "--base-id", "2"]
21 | volumeMounts:
22 | - name: envoy-config
23 | mountPath: "/run/envoy"
24 | readOnly: true
25 | - name: spire-agent-socket
26 | mountPath: /run/spire/sockets
27 | readOnly: true
28 | - name: auth-helper
29 | image: envoy-jwt-auth-helper:latest
30 | imagePullPolicy: IfNotPresent
31 | args: ["-config", "/run/envoy-jwt-auth-helper/config/envoy-jwt-auth-helper.conf"]
32 | ports:
33 | - containerPort: 9012
34 | volumeMounts:
35 | - name: envoy-jwt-auth-helper-config
36 | mountPath: "/run/envoy-jwt-auth-helper/config"
37 | readOnly: true
38 | - name: spire-agent-socket
39 | mountPath: /run/spire/sockets
40 | readOnly: true
41 | - name: frontend-2
42 | imagePullPolicy: IfNotPresent
43 | image: us.gcr.io/scytale-registry/symbank-webapp@sha256:a1c9b1d14e14bd1a4e75698a4f153680d2a08e6f8d1f2d7110bff63d39228a75
44 | command: ["/opt/symbank-webapp/symbank-webapp", "-config", "/run/symbank-webapp/config/symbank-webapp-2.conf"]
45 | ports:
46 | - containerPort: 3002
47 | volumeMounts:
48 | - name: symbank-webapp-2-config
49 | mountPath: /run/symbank-webapp/config
50 | volumes:
51 | - name: envoy-config
52 | configMap:
53 | name: frontend-2-envoy
54 | - name: spire-agent-socket
55 | hostPath:
56 | path: /run/spire/sockets
57 | type: DirectoryOrCreate
58 | - name: envoy-jwt-auth-helper-config
59 | configMap:
60 | name: fe-2-envoy-jwt-auth-helper-config
61 | - name: symbank-webapp-2-config
62 | configMap:
63 | name: symbank-webapp-2-config
64 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend-2/kustomization.yaml:
--------------------------------------------------------------------------------
1 | configMapGenerator:
2 | - name: frontend-2-envoy
3 | files:
4 | - config/envoy.yaml
5 | - name: fe-2-envoy-jwt-auth-helper-config
6 | files:
7 | - config/envoy-jwt-auth-helper.conf
8 |
9 | generatorOptions:
10 | disableNameSuffixHash: true
11 |
12 | resources:
13 | - frontend-2-deployment.yaml
14 |
15 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend/config/envoy-jwt-auth-helper.conf:
--------------------------------------------------------------------------------
1 | socket_path = "unix:///run/spire/sockets/agent.sock"
2 | host = "0.0.0.0"
3 | port = 9011
4 | jwt_mode = "jwt_injection"
5 | audience = "spiffe://example.org/ns/default/sa/default/backend"
6 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "frontend"
3 | cluster: "demo-cluster-spire"
4 | admin:
5 | access_log:
6 | - name: envoy.access_loggers.file
7 | typed_config:
8 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
9 | path: "/tmp/admin_access0.log"
10 | address:
11 | socket_address:
12 | protocol: TCP
13 | address: 127.0.0.1
14 | port_value: 8100
15 | static_resources:
16 | listeners:
17 | - name: outbound_proxy
18 | address:
19 | socket_address:
20 | address: 127.0.0.1
21 | port_value: 3001
22 | filter_chains:
23 | - filters:
24 | - name: envoy.filters.network.http_connection_manager
25 | typed_config:
26 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
27 | common_http_protocol_options:
28 | idle_timeout: 1s
29 | codec_type: auto
30 | access_log:
31 | - name: envoy.access_loggers.file
32 | typed_config:
33 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
34 | path: "/tmp/outbound-proxy.log"
35 | stat_prefix: ingress_http
36 | route_config:
37 | name: service_route
38 | virtual_hosts:
39 | - name: outbound_proxy
40 | domains: ["*"]
41 | routes:
42 | - match:
43 | prefix: "/"
44 | route:
45 | cluster: backend
46 | http_filters:
47 | - name: envoy.filters.http.ext_authz
48 | typed_config:
49 | "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
50 | grpc_service:
51 | envoy_grpc:
52 | cluster_name: ext-authz
53 | timeout: 0.5s
54 | transport_api_version: V3
55 | - name: envoy.filters.http.router
56 | typed_config:
57 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
58 |
59 | clusters:
60 | - name: spire_agent
61 | connect_timeout: 0.25s
62 | http2_protocol_options: {}
63 | load_assignment:
64 | cluster_name: spire_agent
65 | endpoints:
66 | - lb_endpoints:
67 | - endpoint:
68 | address:
69 | pipe:
70 | path: /run/spire/sockets/agent.sock
71 | - name: ext-authz
72 | connect_timeout: 1s
73 | type: strict_dns
74 | http2_protocol_options: {}
75 | load_assignment:
76 | cluster_name: ext-authz
77 | endpoints:
78 | - lb_endpoints:
79 | - endpoint:
80 | address:
81 | socket_address:
82 | address: 127.0.0.1
83 | port_value: 9011
84 | - name: backend
85 | connect_timeout: 0.25s
86 | type: strict_dns
87 | lb_policy: ROUND_ROBIN
88 | load_assignment:
89 | cluster_name: backend
90 | endpoints:
91 | - lb_endpoints:
92 | - endpoint:
93 | address:
94 | socket_address:
95 | address: backend-envoy
96 | port_value: 9001
97 | transport_socket:
98 | name: envoy.transport_sockets.tls
99 | typed_config:
100 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
101 | common_tls_context:
102 | tls_certificate_sds_secret_configs:
103 | - name: "spiffe://example.org/ns/default/sa/default/frontend"
104 | sds_config:
105 | resource_api_version: V3
106 | api_config_source:
107 | api_type: GRPC
108 | transport_api_version: V3
109 | grpc_services:
110 | envoy_grpc:
111 | cluster_name: spire_agent
112 | combined_validation_context:
113 | # validate the SPIFFE ID of the server (recommended)
114 | default_validation_context:
115 | match_typed_subject_alt_names:
116 | - san_type: URI
117 | matcher:
118 | exact: "spiffe://example.org/ns/default/sa/default/backend"
119 | validation_context_sds_secret_config:
120 | name: "spiffe://example.org"
121 | sds_config:
122 | resource_api_version: V3
123 | api_config_source:
124 | api_type: GRPC
125 | transport_api_version: V3
126 | grpc_services:
127 | envoy_grpc:
128 | cluster_name: spire_agent
129 | tls_params:
130 | ecdh_curves:
131 | - X25519:P-256:P-521:P-384
132 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/frontend/frontend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frontend
5 | labels:
6 | app: frontend
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: frontend
11 | template:
12 | metadata:
13 | labels:
14 | app: frontend
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: Always
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml", "--base-id", "1"]
21 | volumeMounts:
22 | - name: envoy-config
23 | mountPath: "/run/envoy"
24 | readOnly: true
25 | - name: spire-agent-socket
26 | mountPath: /run/spire/sockets
27 | readOnly: true
28 | - name: auth-helper
29 | image: envoy-jwt-auth-helper:latest
30 | imagePullPolicy: IfNotPresent
31 | args: ["-config", "/run/envoy-jwt-auth-helper/config/envoy-jwt-auth-helper.conf"]
32 | ports:
33 | - containerPort: 9011
34 | volumeMounts:
35 | - name: envoy-jwt-auth-helper-config
36 | mountPath: "/run/envoy-jwt-auth-helper/config"
37 | readOnly: true
38 | - name: spire-agent-socket
39 | mountPath: /run/spire/sockets
40 | readOnly: true
41 | - name: frontend
42 | imagePullPolicy: IfNotPresent
43 | image: us.gcr.io/scytale-registry/symbank-webapp@sha256:a1c9b1d14e14bd1a4e75698a4f153680d2a08e6f8d1f2d7110bff63d39228a75
44 | command: ["/opt/symbank-webapp/symbank-webapp", "-config", "/run/symbank-webapp/config/symbank-webapp.conf"]
45 | ports:
46 | - containerPort: 3000
47 | volumeMounts:
48 | - name: symbank-webapp-config
49 | mountPath: /run/symbank-webapp/config
50 | volumes:
51 | - name: envoy-config
52 | configMap:
53 | name: frontend-envoy
54 | - name: spire-agent-socket
55 | hostPath:
56 | path: /run/spire/sockets
57 | type: DirectoryOrCreate
58 | - name: envoy-jwt-auth-helper-config
59 | configMap:
60 | name: "fe-envoy-jwt-auth-helper-config"
61 | - name: symbank-webapp-config
62 | configMap:
63 | name: symbank-webapp-config
64 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/k8s/kustomization.yaml:
--------------------------------------------------------------------------------
1 | configMapGenerator:
2 | - name: backend-envoy
3 | files:
4 | - backend/config/envoy.yaml
5 | - name: be-envoy-jwt-auth-helper-config
6 | files:
7 | - backend/config/envoy-jwt-auth-helper.conf
8 | - name: frontend-envoy
9 | files:
10 | - frontend/config/envoy.yaml
11 | - name: fe-envoy-jwt-auth-helper-config
12 | files:
13 | - frontend/config/envoy-jwt-auth-helper.conf
14 | generatorOptions:
15 | disableNameSuffixHash: true
16 |
17 | resources:
18 | - backend/backend-deployment.yaml
19 | - frontend/frontend-deployment.yaml
20 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/scripts/build-helper.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | #/bin/bash
4 |
5 | set -e
6 |
7 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
8 | EXAMPLEDIR="$(dirname "$DIR")"
9 | K8SDIR="$(dirname "$EXAMPLEDIR")"
10 |
11 | DOCKER_IMAGE="envoy-jwt-auth-helper"
12 | SERVICE_VERSION="1.0.0"
13 |
14 | echo "Building ${DOCKER_IMAGE}"
15 | (cd $K8SDIR/envoy-jwt-auth-helper; docker build --no-cache --tag ${DOCKER_IMAGE} .)
16 |
17 | case $1 in
18 | "minikube")
19 | echo "Loading image into minikube"
20 | minikube image load $DOCKER_IMAGE:latest;;
21 | "kind")
22 | echo "Load image into kind"
23 | kind load docker-image $DOCKER_IMAGE:latest;;
24 | *)
25 | echo "Image builded successfully";;
26 | esac
27 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | green=$(tput setaf 2) || true
12 |
13 | echo "${bb}Deleting backend and frontends resources...${nn}"
14 | kubectl delete -k "${EXAMPLEDIR}"/k8s/. --ignore-not-found
15 | kubectl delete -k "${EXAMPLEDIR}"/k8s/frontend-2/. --ignore-not-found
16 |
17 | echo "${bb}Deleting resources from X.509 tutorial...${nn}"
18 | bash "${K8SDIR}"/envoy-x509/scripts/clean-env.sh > /dev/null
19 |
20 | echo "${green}Cleaning completed.${nn}"
21 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/scripts/pre-set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | # Creates Envoy-X509 scenario
10 | bash "${K8SDIR}"/envoy-x509/scripts/set-env.sh
11 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | red=$(tput setaf 1) || true
12 |
13 | restart_deployments() {
14 | # Delete pods so they are re run using the new configurations
15 | kubectl scale deployment backend --replicas=0
16 | kubectl scale deployment frontend --replicas=0
17 | kubectl scale deployment frontend-2 --replicas=0
18 |
19 | # Let's be sure that there is no pod running before starting the new pods
20 | for ((i=0;i<30;i++)); do
21 | if ! kubectl get pods 2>&1 | grep -qe "No resources found in default namespace." ; then
22 | sleep 5
23 | echo "Waiting until pods are terminated..."
24 | continue
25 | fi
26 | echo "Pods are terminated. Let's re-start them."
27 | POD_TERMINATED=1
28 | break
29 | done
30 | if [ -z "${POD_TERMINATED}" ]; then
31 | echo "${red}Timed out waiting for pods to be terminated.${nn}"
32 | exit 1
33 | fi
34 |
35 | # Restart all pods
36 | kubectl scale deployment backend --replicas=1
37 | kubectl scale deployment frontend --replicas=1
38 | kubectl scale deployment frontend-2 --replicas=1
39 | }
40 |
41 |
42 | wait_for_envoy() {
43 | # wait until deployments are completed
44 | kubectl rollout status deployment/backend --timeout=60s
45 | kubectl rollout status deployment/frontend --timeout=60s
46 | kubectl rollout status deployment/frontend-2 --timeout=60s
47 |
48 | # wait until Envoy is ready
49 | LOGLINE="all dependencies initialized. starting workers"
50 | LOGLINE2="DNS hosts have changed for backend-envoy"
51 | for ((i=0;i<30;i++)); do
52 | if ! kubectl logs --tail=1000 --selector=app=frontend -c envoy | grep -qe "${LOGLINE}" ; then
53 | sleep 5
54 | echo "Waiting until Envoy is ready..."
55 | continue
56 | fi
57 | if ! kubectl logs --tail=100 --selector=app=frontend -c envoy | grep -qe "${LOGLINE2}" ; then
58 | sleep 5
59 | echo "Waiting until Envoy is ready..."
60 | continue
61 | fi
62 | echo "Workloads ready."
63 | WK_READY=1
64 | break
65 | done
66 | if [ -z "${WK_READY}" ]; then
67 | echo "${red}Timed out waiting for workloads to be ready.${nn}"
68 | exit 1
69 | fi
70 | }
71 |
72 | echo "${bb}Creating Envoy-x509 scenario...${nn}"
73 | bash "${EXAMPLEDIR}"/scripts/pre-set-env.sh > /dev/null
74 |
75 | echo "${bb}Applying SPIRE Envoy JWT configuration...${nn}"
76 | kubectl apply -k "${EXAMPLEDIR}"/k8s/. > /dev/null
77 | bash "${EXAMPLEDIR}"/create-registration-entries.sh > /dev/null
78 |
79 | # Updates resources for frontend-2
80 | kubectl apply -k "${EXAMPLEDIR}"/k8s/frontend-2/. > /dev/null
81 | bash "${EXAMPLEDIR}"/k8s/frontend-2/create-registration-entry.sh > /dev/null
82 |
83 | # Restarts all deployments to pickup the new configurations
84 | restart_deployments > /dev/null
85 |
86 | echo "${bb}Waiting until deployments and Envoy are ready...${nn}"
87 | wait_for_envoy > /dev/null
88 |
89 | echo "${bb}Envoy JWT Environment creation completed.${nn}"
90 |
--------------------------------------------------------------------------------
/k8s/envoy-jwt/test.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | bb=$(tput bold) || true
8 | nn=$(tput sgr0) || true
9 | red=$(tput setaf 1) || true
10 | green=$(tput setaf 2) || true
11 |
12 | clean-env() {
13 | echo "${bb}Cleaning up...${nn}"
14 | bash "${DIR}"/scripts/clean-env.sh > /dev/null
15 | }
16 |
17 | trap clean-env EXIT
18 |
19 | echo "${bb}Preparing environment...${nm}"
20 | clean-env
21 |
22 | # Build helper image
23 | bash "${DIR}"/scripts/build-helper.sh minikube
24 |
25 | # Creates Envoy JWT scenario
26 | bash "${DIR}"/scripts/set-env.sh
27 |
28 | echo "${bb}Running test...${nm}"
29 | # If balance is part of the response, then the request was accepted by the backend and token was valid.
30 | BALANCE_LINE="Your current balance is 10.95"
31 | if curl -s $(minikube service frontend --url) | grep -qe "$BALANCE_LINE"; then
32 | echo "${green}Success${nn}"
33 | exit 0
34 | fi
35 |
36 | echo "${red}Failed! Request did not make it through the proxies.${nn}"
37 | exit 1
38 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/images/SPIRE_Envoy_OPA_X509_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-opa/images/SPIRE_Envoy_OPA_X509_diagram.png
--------------------------------------------------------------------------------
/k8s/envoy-opa/images/frontend-2_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-opa/images/frontend-2_view.png
--------------------------------------------------------------------------------
/k8s/envoy-opa/images/frontend-2_view_no_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-opa/images/frontend-2_view_no_details.png
--------------------------------------------------------------------------------
/k8s/envoy-opa/images/frontend_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-opa/images/frontend_view.png
--------------------------------------------------------------------------------
/k8s/envoy-opa/k8s/backend/backend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: backend
5 | labels:
6 | app: backend
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: backend
11 | template:
12 | metadata:
13 | labels:
14 | app: backend
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: Always
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml"]
21 | ports:
22 | - containerPort: 9001
23 | volumeMounts:
24 | - name: envoy-config
25 | mountPath: "/run/envoy"
26 | readOnly: true
27 | - name: spire-agent-socket
28 | mountPath: /run/spire/sockets
29 | readOnly: true
30 | - name: backend
31 | image: nginx
32 | ports:
33 | - containerPort: 80
34 | volumeMounts:
35 | - name: backend-balance-json-data
36 | mountPath: "/usr/share/nginx/html/balances"
37 | readOnly: true
38 | - name: backend-profile-json-data
39 | mountPath: "/usr/share/nginx/html/profiles"
40 | readOnly: true
41 | - name: backend-transactions-json-data
42 | mountPath: "/usr/share/nginx/html/transactions"
43 | readOnly: true
44 | - name: opa
45 | image: openpolicyagent/opa:0.50.2-envoy
46 | imagePullPolicy: IfNotPresent
47 | ports:
48 | - name: opa-envoy
49 | containerPort: 8182
50 | protocol: TCP
51 | - name: opa-api-port
52 | containerPort: 8181
53 | protocol: TCP
54 | args:
55 | - "run"
56 | - "--server"
57 | - "--config-file=/run/opa/opa-config.yaml"
58 | - "/run/opa/opa-policy.rego"
59 | volumeMounts:
60 | - name: backend-opa-policy
61 | mountPath: /run/opa
62 | readOnly: true
63 | volumes:
64 | - name: envoy-config
65 | configMap:
66 | name: backend-envoy
67 | - name: backend-opa-policy
68 | configMap:
69 | name: backend-opa-policy-config
70 | - name: spire-agent-socket
71 | hostPath:
72 | path: /run/spire/sockets
73 | type: Directory
74 | - name: backend-balance-json-data
75 | configMap:
76 | name: backend-balance-json-data
77 | - name: backend-profile-json-data
78 | configMap:
79 | name: backend-profile-json-data
80 | - name: backend-transactions-json-data
81 | configMap:
82 | name: backend-transactions-json-data
83 |
84 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/k8s/backend/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "backend"
3 | cluster: "demo-cluster-spire"
4 | static_resources:
5 | listeners:
6 | - name: local_service
7 | address:
8 | socket_address:
9 | address: 0.0.0.0
10 | port_value: 9001
11 | filter_chains:
12 | - filters:
13 | - name: envoy.filters.network.http_connection_manager
14 | typed_config:
15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
16 | common_http_protocol_options:
17 | idle_timeout: 1s
18 | forward_client_cert_details: sanitize_set
19 | set_current_client_cert_details:
20 | uri: true
21 | codec_type: auto
22 | access_log:
23 | - name: envoy.access_loggers.file
24 | typed_config:
25 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
26 | path: "/tmp/inbound-proxy.log"
27 | stat_prefix: ingress_http
28 | route_config:
29 | name: local_route
30 | virtual_hosts:
31 | - name: local_service
32 | domains: ["*"]
33 | routes:
34 | - match:
35 | prefix: "/"
36 | route:
37 | cluster: local_service
38 | http_filters:
39 | - name: envoy.filters.http.ext_authz
40 | typed_config:
41 | "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
42 | transport_api_version: V3
43 | with_request_body:
44 | max_request_bytes: 8192
45 | allow_partial_message: true
46 | failure_mode_allow: false
47 | grpc_service:
48 | google_grpc:
49 | target_uri: 127.0.0.1:8182
50 | stat_prefix: ext_authz
51 | timeout: 0.5s
52 | - name: envoy.filters.http.router
53 | typed_config:
54 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
55 | transport_socket:
56 | name: envoy.transport_sockets.tls
57 | typed_config:
58 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
59 | common_tls_context:
60 | tls_certificate_sds_secret_configs:
61 | - name: "spiffe://example.org/ns/default/sa/default/backend"
62 | sds_config:
63 | resource_api_version: V3
64 | api_config_source:
65 | api_type: GRPC
66 | transport_api_version: V3
67 | grpc_services:
68 | envoy_grpc:
69 | cluster_name: spire_agent
70 | combined_validation_context:
71 | # validate the SPIFFE ID of incoming clients (optionally)
72 | default_validation_context:
73 | match_typed_subject_alt_names:
74 | - san_type: URI
75 | matcher:
76 | exact: "spiffe://example.org/ns/default/sa/default/frontend"
77 | - san_type: URI
78 | matcher:
79 | exact: "spiffe://example.org/ns/default/sa/default/frontend-2"
80 | # obtain the trust bundle from SDS
81 | validation_context_sds_secret_config:
82 | name: "spiffe://example.org"
83 | sds_config:
84 | resource_api_version: V3
85 | api_config_source:
86 | api_type: GRPC
87 | transport_api_version: V3
88 | grpc_services:
89 | envoy_grpc:
90 | cluster_name: spire_agent
91 | tls_params:
92 | ecdh_curves:
93 | - X25519:P-256:P-521:P-384
94 | clusters:
95 | - name: spire_agent
96 | connect_timeout: 0.25s
97 | http2_protocol_options: {}
98 | load_assignment:
99 | cluster_name: spire_agent
100 | endpoints:
101 | - lb_endpoints:
102 | - endpoint:
103 | address:
104 | pipe:
105 | path: /run/spire/sockets/agent.sock
106 | - name: local_service
107 | connect_timeout: 1s
108 | type: strict_dns
109 | load_assignment:
110 | cluster_name: ext-authz
111 | endpoints:
112 | - lb_endpoints:
113 | - endpoint:
114 | address:
115 | socket_address:
116 | address: 127.0.0.1
117 | port_value: 80
118 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/k8s/backend/config/opa-config.yaml:
--------------------------------------------------------------------------------
1 | decision_logs:
2 | console: true
3 | plugins:
4 | envoy_ext_authz_grpc:
5 | addr: :8182
6 | query: data.envoy.authz.allow
7 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/k8s/backend/config/opa-policy.rego:
--------------------------------------------------------------------------------
1 | package envoy.authz
2 |
3 | import input.attributes.request.http as http_request
4 |
5 | default allow = false
6 |
7 | # allow Frontend service to access Backend service
8 | allow {
9 | valid_path
10 | http_request.method == "GET"
11 | svc_spiffe_id == "spiffe://example.org/ns/default/sa/default/frontend"
12 | }
13 |
14 | svc_spiffe_id = spiffe_id {
15 | [_, _, uri_type_san] := split(http_request.headers["x-forwarded-client-cert"], ";")
16 | [_, spiffe_id] := split(uri_type_san, "=")
17 | }
18 |
19 | valid_path {
20 | glob.match("/balances/*", [], http_request.path)
21 | }
22 |
23 | valid_path {
24 | glob.match("/profiles/*", [], http_request.path)
25 | }
26 |
27 | valid_path {
28 | glob.match("/transactions/*", [], http_request.path)
29 | }
30 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/k8s/kustomization.yaml:
--------------------------------------------------------------------------------
1 | configMapGenerator:
2 | - name: backend-envoy
3 | files:
4 | - backend/config/envoy.yaml
5 | - name: backend-opa-policy-config
6 | files:
7 | - backend/config/opa-policy.rego
8 | - backend/config/opa-config.yaml
9 |
10 | generatorOptions:
11 | disableNameSuffixHash: true
12 |
13 | resources:
14 | - backend/backend-deployment.yaml
15 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/scripts/backend-opa-logs.sh:
--------------------------------------------------------------------------------
1 | POD=$(kubectl get pod -l app=backend -o jsonpath="{.items[0].metadata.name}")
2 | kubectl logs $POD -c opa | jq .
3 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/scripts/backend-update-policy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | kubectl edit configmap backend-opa-policy-config
4 |
5 | # Restart pod
6 | kubectl scale deployment backend --replicas=0
7 | kubectl scale deployment backend --replicas=1
8 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | green=$(tput setaf 2) || true
12 |
13 | echo "${bb}Deleting tutorial resources...${nn}"
14 | kubectl delete -k "${EXAMPLEDIR}"/k8s/. --ignore-not-found
15 |
16 | echo "${bb}Deleting resources from X.509 Tutorial...${nn}"
17 | bash "${K8SDIR}"/envoy-x509/scripts/clean-env.sh > /dev/null
18 |
19 | echo "${green}Cleaning completed.${nn}"
20 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/scripts/pre-set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | # Creates Envoy-X509 scenario
10 | bash "${K8SDIR}"/envoy-x509/scripts/set-env.sh
11 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | red=$(tput setaf 1) || true
12 |
13 | restart_deployment() {
14 | kubectl scale deployment backend --replicas=0
15 |
16 | # Let's be sure that there is no pod running before starting the new pod
17 | for ((i=0;i<30;i++)); do
18 | if ! kubectl get pod --selector=app=backend 2>&1 | grep -qe "No resources found in default namespace." ; then
19 | sleep 5
20 | echo "Waiting until backend pod is terminated..."
21 | continue
22 | fi
23 | echo "Backend pod is terminated. Let's re-start it."
24 | POD_TERMINATED=1
25 | break
26 | done
27 | if [ -z "${POD_TERMINATED}" ]; then
28 | echo "${red}Timed out waiting for pods to be terminated.${nn}"
29 | exit 1
30 | fi
31 |
32 | kubectl scale deployment backend --replicas=1
33 | }
34 |
35 | wait_for_envoy() {
36 | # waits until deployments are completed and Envoy ready
37 | kubectl rollout status deployment/backend --timeout=60s
38 |
39 | LOGLINE="all dependencies initialized. starting workers"
40 | LOGLINE2="membership update for TLS cluster backend added 1 removed 1"
41 | for ((i=0;i<30;i++)); do
42 | if ! kubectl logs --tail=1000 --selector=app=backend -c envoy | grep -qe "${LOGLINE}" ; then
43 | sleep 5
44 | echo "Waiting until backend envoy instance is ready..."
45 | continue
46 | fi
47 | if ! kubectl logs --tail=1000 --selector=app=frontend -c envoy | grep -qe "${LOGLINE2}" ; then
48 | sleep 5
49 | echo "Waiting until frontend envoy instance is in sync with the backend envoy..."
50 | continue
51 | fi
52 | echo "${bb}Workloads ready.${nn}"
53 | WK_READY=1
54 | break
55 | done
56 | if [ -z "${WK_READY}" ]; then
57 | echo "${red}Timed out waiting for workloads to be ready.${nn}"
58 | exit 1
59 | fi
60 | }
61 |
62 | # Creating Envoy-x509 scenario
63 | bash "${EXAMPLEDIR}"/scripts/pre-set-env.sh > /dev/null
64 |
65 | echo "${bb}Applying SPIRE Envoy OPA configuration...${nn}"
66 | kubectl apply -k "${EXAMPLEDIR}"/k8s/. > /dev/null
67 |
68 | echo "${bb}Restarting backend pod...${nn}"
69 | restart_deployment > /dev/null
70 |
71 | echo "${bb}Waiting until deployments and Envoy are ready...${nn}"
72 | wait_for_envoy > /dev/null
73 |
74 | echo "${bb}Envoy OPA Environment creation completed.${nn}"
75 |
--------------------------------------------------------------------------------
/k8s/envoy-opa/test.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | bb=$(tput bold) || true
8 | nn=$(tput sgr0) || true
9 | red=$(tput setaf 1) || true
10 | green=$(tput setaf 2) || true
11 |
12 | clean-env() {
13 | echo "${bb}Cleaning up...${nn}"
14 | bash "${DIR}"/scripts/clean-env.sh > /dev/null
15 | }
16 |
17 | trap clean-env EXIT
18 |
19 | echo "${bb}Preparing environment...${nm}"
20 | clean-env
21 |
22 | # Creates Envoy OPA scenario
23 | bash "${DIR}"/scripts/set-env.sh
24 |
25 | echo "${bb}Let's run the test... ${nn}"
26 | # If balance is part of the response, then the request was authorized by OPA and reached the backend.
27 | BALANCE_LINE="Your current balance is 10.95"
28 | if curl -s $(minikube service frontend --url) | grep -qe "$BALANCE_LINE"; then
29 | echo "${green}Success${nn}"
30 | exit 0
31 | fi
32 |
33 | echo "${red}Failed! Request did not make it through the proxies.${nn}"
34 | exit 1
35 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/backend-envoy-configmap-update.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: backend-envoy
5 | data:
6 | envoy.yaml: |
7 | node:
8 | id: "backend"
9 | cluster: "demo-cluster-spire"
10 | static_resources:
11 | listeners:
12 | - name: local_service
13 | address:
14 | socket_address:
15 | address: 0.0.0.0
16 | port_value: 9001
17 | filter_chains:
18 | - filters:
19 | - name: envoy.filters.network.http_connection_manager
20 | typed_config:
21 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
22 | common_http_protocol_options:
23 | idle_timeout: 1s
24 | forward_client_cert_details: sanitize_set
25 | set_current_client_cert_details:
26 | uri: true
27 | codec_type: auto
28 | access_log:
29 | - name: envoy.access_loggers.file
30 | typed_config:
31 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
32 | path: "/tmp/inbound-proxy.log"
33 | stat_prefix: ingress_http
34 | route_config:
35 | name: local_route
36 | virtual_hosts:
37 | - name: local_service
38 | domains: ["*"]
39 | routes:
40 | - match:
41 | prefix: "/"
42 | route:
43 | cluster: local_service
44 | http_filters:
45 | - name: envoy.filters.http.router
46 | typed_config:
47 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
48 | transport_socket:
49 | name: envoy.transport_sockets.tls
50 | typed_config:
51 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
52 | common_tls_context:
53 | tls_certificate_sds_secret_configs:
54 | - name: "spiffe://example.org/ns/default/sa/default/backend"
55 | sds_config:
56 | resource_api_version: V3
57 | api_config_source:
58 | api_type: GRPC
59 | transport_api_version: V3
60 | grpc_services:
61 | envoy_grpc:
62 | cluster_name: spire_agent
63 | combined_validation_context:
64 | # validate the SPIFFE ID of incoming clients (optionally)
65 | default_validation_context:
66 | match_typed_subject_alt_names:
67 | - san_type: URI
68 | matcher:
69 | exact: "spiffe://example.org/ns/default/sa/default/frontend"
70 | # obtain the trust bundle from SDS
71 | validation_context_sds_secret_config:
72 | name: "spiffe://example.org"
73 | sds_config:
74 | resource_api_version: V3
75 | api_config_source:
76 | api_type: GRPC
77 | transport_api_version: V3
78 | grpc_services:
79 | envoy_grpc:
80 | cluster_name: spire_agent
81 | tls_params:
82 | ecdh_curves:
83 | - X25519:P-256:P-521:P-384
84 | clusters:
85 | - name: spire_agent
86 | connect_timeout: 0.25s
87 | http2_protocol_options: {}
88 | load_assignment:
89 | cluster_name: spire_agent
90 | endpoints:
91 | - lb_endpoints:
92 | - endpoint:
93 | address:
94 | pipe:
95 | path: /run/spire/sockets/agent.sock
96 | - name: local_service
97 | connect_timeout: 1s
98 | type: strict_dns
99 | load_assignment:
100 | cluster_name: local_service
101 | endpoints:
102 | - lb_endpoints:
103 | - endpoint:
104 | address:
105 | socket_address:
106 | address: 127.0.0.1
107 | port_value: 80
108 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/create-registration-entries.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 | register() {
9 | kubectl exec -n spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry create $@
10 | }
11 |
12 | echo "${bb}Creating registration entry for the backend - envoy...${nn}"
13 | register \
14 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \
15 | -spiffeID spiffe://example.org/ns/default/sa/default/backend \
16 | -selector k8s:ns:default \
17 | -selector k8s:sa:default \
18 | -selector k8s:pod-label:app:backend \
19 | -selector k8s:container-name:envoy
20 |
21 | echo "${bb}Creating registration entry for the frontend - envoy...${nn}"
22 | register \
23 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \
24 | -spiffeID spiffe://example.org/ns/default/sa/default/frontend \
25 | -selector k8s:ns:default \
26 | -selector k8s:sa:default \
27 | -selector k8s:pod-label:app:frontend \
28 | -selector k8s:container-name:envoy
29 |
30 | echo "${bb}Creating registration entry for the frontend - envoy...${nn}"
31 | register \
32 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \
33 | -spiffeID spiffe://example.org/ns/default/sa/default/frontend-2 \
34 | -selector k8s:ns:default \
35 | -selector k8s:sa:default \
36 | -selector k8s:pod-label:app:frontend-2 \
37 | -selector k8s:container-name:envoy
38 |
39 | echo "${bb}Listing created registration entries...${nn}"
40 | kubectl exec -n spire spire-server-0 -- /opt/spire/bin/spire-server entry show
--------------------------------------------------------------------------------
/k8s/envoy-x509/images/SPIRE_Envoy_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-x509/images/SPIRE_Envoy_diagram.png
--------------------------------------------------------------------------------
/k8s/envoy-x509/images/frontend-2_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-x509/images/frontend-2_view.png
--------------------------------------------------------------------------------
/k8s/envoy-x509/images/frontend-2_view_no_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-x509/images/frontend-2_view_no_details.png
--------------------------------------------------------------------------------
/k8s/envoy-x509/images/frontend_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spiffe/spire-tutorials/c345f877d5b6a489877357057aaf9a6ed751cebc/k8s/envoy-x509/images/frontend_view.png
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/backend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: backend
5 | labels:
6 | app: backend
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: backend
11 | template:
12 | metadata:
13 | labels:
14 | app: backend
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: Always
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml"]
21 | ports:
22 | - containerPort: 9001
23 | volumeMounts:
24 | - name: envoy-config
25 | mountPath: "/run/envoy"
26 | readOnly: true
27 | - name: spire-agent-socket
28 | mountPath: /run/spire/sockets
29 | readOnly: true
30 | - name: backend
31 | image: nginx
32 | ports:
33 | - containerPort: 80
34 | volumeMounts:
35 | - name: backend-balance-json-data
36 | mountPath: "/usr/share/nginx/html/balances"
37 | readOnly: true
38 | - name: backend-profile-json-data
39 | mountPath: "/usr/share/nginx/html/profiles"
40 | readOnly: true
41 | - name: backend-transactions-json-data
42 | mountPath: "/usr/share/nginx/html/transactions"
43 | readOnly: true
44 | volumes:
45 | - name: envoy-config
46 | configMap:
47 | name: backend-envoy
48 | - name: spire-agent-socket
49 | hostPath:
50 | path: /run/spire/sockets
51 | type: Directory
52 | - name: backend-balance-json-data
53 | configMap:
54 | name: backend-balance-json-data
55 | - name: backend-profile-json-data
56 | configMap:
57 | name: backend-profile-json-data
58 | - name: backend-transactions-json-data
59 | configMap:
60 | name: backend-transactions-json-data
61 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/backend-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: backend-envoy
5 | spec:
6 | clusterIP: None
7 | ports:
8 | - port: 9001
9 | protocol: TCP
10 | targetPort: 9001
11 | selector:
12 | app: backend
13 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "backend"
3 | cluster: "demo-cluster-spire"
4 | static_resources:
5 | listeners:
6 | - name: local_service
7 | address:
8 | socket_address:
9 | address: 0.0.0.0
10 | port_value: 9001
11 | filter_chains:
12 | - filters:
13 | - name: envoy.filters.network.http_connection_manager
14 | typed_config:
15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
16 | common_http_protocol_options:
17 | idle_timeout: 1s
18 | forward_client_cert_details: sanitize_set
19 | set_current_client_cert_details:
20 | uri: true
21 | codec_type: auto
22 | access_log:
23 | - name: envoy.access_loggers.file
24 | typed_config:
25 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
26 | path: "/tmp/inbound-proxy.log"
27 | stat_prefix: ingress_http
28 | route_config:
29 | name: local_route
30 | virtual_hosts:
31 | - name: local_service
32 | domains: ["*"]
33 | routes:
34 | - match:
35 | prefix: "/"
36 | route:
37 | cluster: local_service
38 | http_filters:
39 | - name: envoy.filters.http.router
40 | typed_config:
41 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
42 | transport_socket:
43 | name: envoy.transport_sockets.tls
44 | typed_config:
45 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
46 | common_tls_context:
47 | tls_certificate_sds_secret_configs:
48 | - name: "spiffe://example.org/ns/default/sa/default/backend"
49 | sds_config:
50 | resource_api_version: V3
51 | api_config_source:
52 | api_type: GRPC
53 | transport_api_version: V3
54 | grpc_services:
55 | envoy_grpc:
56 | cluster_name: spire_agent
57 | combined_validation_context:
58 | # validate the SPIFFE ID of incoming clients (optionally)
59 | default_validation_context:
60 | match_typed_subject_alt_names:
61 | - san_type: URI
62 | matcher:
63 | exact: "spiffe://example.org/ns/default/sa/default/frontend"
64 | - san_type: URI
65 | matcher:
66 | exact: "spiffe://example.org/ns/default/sa/default/frontend-2"
67 | # obtain the trust bundle from SDS
68 | validation_context_sds_secret_config:
69 | name: "spiffe://example.org"
70 | sds_config:
71 | resource_api_version: V3
72 | api_config_source:
73 | api_type: GRPC
74 | transport_api_version: V3
75 | grpc_services:
76 | envoy_grpc:
77 | cluster_name: spire_agent
78 | tls_params:
79 | ecdh_curves:
80 | - X25519:P-256:P-521:P-384
81 | clusters:
82 | - name: spire_agent
83 | connect_timeout: 0.25s
84 | http2_protocol_options: {}
85 | load_assignment:
86 | cluster_name: spire_agent
87 | endpoints:
88 | - lb_endpoints:
89 | - endpoint:
90 | address:
91 | pipe:
92 | path: /run/spire/sockets/agent.sock
93 | - name: local_service
94 | connect_timeout: 1s
95 | type: strict_dns
96 | load_assignment:
97 | cluster_name: local_service
98 | endpoints:
99 | - lb_endpoints:
100 | - endpoint:
101 | address:
102 | socket_address:
103 | address: 127.0.0.1
104 | port_value: 80
105 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/balancedata.json:
--------------------------------------------------------------------------------
1 | {
2 | "balance": 10.95
3 | }
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/balances/balance_1:
--------------------------------------------------------------------------------
1 | {
2 | "balance": 10.95
3 | }
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/balances/balance_2:
--------------------------------------------------------------------------------
1 | {
2 | "balance": 310
3 | }
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/profiledata.json:
--------------------------------------------------------------------------------
1 | {
2 | "Name": "Jacob Marley",
3 | "Address": "48-49 Doughty ST, Holborn - London WC1N 2LX.UK"
4 | }
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/profiles/profile_1:
--------------------------------------------------------------------------------
1 | {
2 | "Name": "Jacob Marley",
3 | "Address": "48-49 Doughty ST, Holborn - London WC1N 2LX.UK"
4 | }
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/profiles/profile_2:
--------------------------------------------------------------------------------
1 | {
2 | "Name": "Alex Fergus",
3 | "Address": "Sir Matt Busby Way, Trafford Park, Stretford, Manchester M16 0RA, Reino Unido"
4 | }
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/transactions/transaction_1:
--------------------------------------------------------------------------------
1 | {
2 | "transactions": [
3 | {
4 | "description": "Kohls: Crhistmas decorations",
5 | "debit": 20
6 | },
7 | {
8 | "description": "Alms from the collections",
9 | "credit": 450
10 | },
11 | {
12 | "description": "Khols: Christmas cards",
13 | "debit": 20
14 | },
15 | {
16 | "description": "Cash withdrawl",
17 | "debit": 600
18 | },
19 | {
20 | "description": "Pay from employer",
21 | "credit": 6000
22 | },
23 | {
24 | "description": "Health Insurance: Tiny Tim",
25 | "debit": 5000
26 | },
27 | {
28 | "description": "Opening Balance",
29 | "debit": 10000
30 | }
31 |
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/transactions/transaction_2:
--------------------------------------------------------------------------------
1 | {
2 | "transactions": [
3 | {
4 | "description": "Kohls: Crhistmas decorations",
5 | "debit": 20
6 | },
7 | {
8 | "description": "Alms from the collections",
9 | "credit": 450
10 | },
11 | {
12 | "description": "Khols: Christmas cards",
13 | "debit": 20
14 | },
15 | {
16 | "description": "Cash withdrawl",
17 | "debit": 300
18 | },
19 | {
20 | "description": "Pay from employer",
21 | "credit": 600
22 | },
23 | {
24 | "description": "Health Insurance: Tiny Tim",
25 | "debit": 500
26 | },
27 | {
28 | "description": "Opening Balance",
29 | "debit": 1000
30 | }
31 |
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/backend/json/transactionsdata.json:
--------------------------------------------------------------------------------
1 | {
2 | "transactions": [
3 | {
4 | "description": "Kohls: Crhistmas decorations",
5 | "debit": 20
6 | },
7 | {
8 | "description": "Alms from the collections",
9 | "credit": 450
10 | },
11 | {
12 | "description": "Khols: Christmas cards",
13 | "debit": 20
14 | },
15 | {
16 | "description": "Cash withdrawl",
17 | "debit": 600
18 | },
19 | {
20 | "description": "Pay from employer",
21 | "credit": 6000
22 | },
23 | {
24 | "description": "Health Insurance: Tiny Tim",
25 | "debit": 5000
26 | },
27 | {
28 | "description": "Opening Balance",
29 | "debit": 10000
30 | }
31 |
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend-2/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "frontend-2"
3 | cluster: "demo-cluster-spire"
4 | admin:
5 | access_log:
6 | - name: envoy.access_loggers.file
7 | typed_config:
8 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
9 | path: "/tmp/admin_access0.log"
10 | address:
11 | socket_address:
12 | protocol: TCP
13 | address: 127.0.0.1
14 | port_value: 8102
15 | static_resources:
16 | listeners:
17 | - name: outbound_proxy
18 | address:
19 | socket_address:
20 | address: 127.0.0.1
21 | port_value: 3003
22 | filter_chains:
23 | - filters:
24 | - name: envoy.filters.network.http_connection_manager
25 | typed_config:
26 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
27 | common_http_protocol_options:
28 | idle_timeout: 1s
29 | codec_type: auto
30 | access_log:
31 | - name: envoy.access_loggers.file
32 | typed_config:
33 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
34 | path: "/tmp/outbound-proxy.log"
35 | stat_prefix: ingress_http
36 | route_config:
37 | name: service_route
38 | virtual_hosts:
39 | - name: outbound_proxy
40 | domains: ["*"]
41 | routes:
42 | - match:
43 | prefix: "/"
44 | route:
45 | cluster: backend
46 | http_filters:
47 | - name: envoy.filters.http.router
48 | typed_config:
49 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
50 | clusters:
51 | - name: spire_agent
52 | connect_timeout: 0.25s
53 | http2_protocol_options: {}
54 | load_assignment:
55 | cluster_name: spire_agent
56 | endpoints:
57 | - lb_endpoints:
58 | - endpoint:
59 | address:
60 | pipe:
61 | path: /run/spire/sockets/agent.sock
62 | - name: backend
63 | connect_timeout: 0.25s
64 | type: strict_dns
65 | lb_policy: ROUND_ROBIN
66 | load_assignment:
67 | cluster_name: backend
68 | endpoints:
69 | - lb_endpoints:
70 | - endpoint:
71 | address:
72 | socket_address:
73 | address: backend-envoy
74 | port_value: 9001
75 | transport_socket:
76 | name: envoy.transport_sockets.tls
77 | typed_config:
78 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
79 | common_tls_context:
80 | tls_certificate_sds_secret_configs:
81 | - name: "spiffe://example.org/ns/default/sa/default/frontend-2"
82 | sds_config:
83 | resource_api_version: V3
84 | api_config_source:
85 | api_type: GRPC
86 | transport_api_version: V3
87 | grpc_services:
88 | envoy_grpc:
89 | cluster_name: spire_agent
90 | combined_validation_context:
91 | # validate the SPIFFE ID of the server (recommended)
92 | default_validation_context:
93 | match_typed_subject_alt_names:
94 | - san_type: URI
95 | matcher:
96 | exact: "spiffe://example.org/ns/default/sa/default/backend"
97 | validation_context_sds_secret_config:
98 | name: "spiffe://example.org"
99 | sds_config:
100 | resource_api_version: V3
101 | api_config_source:
102 | api_type: GRPC
103 | transport_api_version: V3
104 | grpc_services:
105 | envoy_grpc:
106 | cluster_name: spire_agent
107 | tls_params:
108 | ecdh_curves:
109 | - X25519:P-256:P-521:P-384
110 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend-2/config/symbank-webapp-2.conf:
--------------------------------------------------------------------------------
1 | port = 3002
2 | address = ""
3 | balanceDataPath = "http://localhost:3003/balances/balance_2"
4 | profileDataPath = "http://localhost:3003/profiles/profile_2"
5 | transactionDataPath = "http://localhost:3003/transactions/transaction_2"
6 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend-2/frontend-2-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frontend-2
5 | labels:
6 | app: frontend-2
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: frontend-2
11 | template:
12 | metadata:
13 | labels:
14 | app: frontend-2
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: Always
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml", "--base-id", "2"]
21 | volumeMounts:
22 | - name: envoy-config
23 | mountPath: "/run/envoy"
24 | readOnly: true
25 | - name: spire-agent-socket
26 | mountPath: /run/spire/sockets
27 | readOnly: true
28 | - name: frontend-2
29 | imagePullPolicy: IfNotPresent
30 | image: us.gcr.io/scytale-registry/symbank-webapp@sha256:a1c9b1d14e14bd1a4e75698a4f153680d2a08e6f8d1f2d7110bff63d39228a75
31 | command: ["/opt/symbank-webapp/symbank-webapp", "-config", "/run/symbank-webapp/config/symbank-webapp-2.conf"]
32 | ports:
33 | - containerPort: 3002
34 | volumeMounts:
35 | - name: symbank-webapp-2-config
36 | mountPath: /run/symbank-webapp/config
37 | volumes:
38 | - name: envoy-config
39 | configMap:
40 | name: frontend-2-envoy
41 | - name: spire-agent-socket
42 | hostPath:
43 | path: /run/spire/sockets
44 | type: DirectoryOrCreate
45 | - name: symbank-webapp-2-config
46 | configMap:
47 | name: symbank-webapp-2-config
48 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend-2/frontend-2-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: frontend-2
5 | spec:
6 | ports:
7 | - port: 3002
8 | name: http
9 | protocol: TCP
10 | targetPort: 3002
11 | selector:
12 | app: frontend-2
13 | type: LoadBalancer
14 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend/config/envoy.yaml:
--------------------------------------------------------------------------------
1 | node:
2 | id: "frontend"
3 | cluster: "demo-cluster-spire"
4 | admin:
5 | access_log:
6 | - name: envoy.access_loggers.file
7 | typed_config:
8 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
9 | path: "/tmp/admin_access0.log"
10 | address:
11 | socket_address:
12 | protocol: TCP
13 | address: 127.0.0.1
14 | port_value: 8100
15 | static_resources:
16 | listeners:
17 | - name: outbound_proxy
18 | address:
19 | socket_address:
20 | address: 127.0.0.1
21 | port_value: 3001
22 | filter_chains:
23 | - filters:
24 | - name: envoy.filters.network.http_connection_manager
25 | typed_config:
26 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
27 | common_http_protocol_options:
28 | idle_timeout: 1s
29 | codec_type: auto
30 | access_log:
31 | - name: envoy.access_loggers.file
32 | typed_config:
33 | "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
34 | path: "/tmp/outbound-proxy.log"
35 | stat_prefix: ingress_http
36 | route_config:
37 | name: service_route
38 | virtual_hosts:
39 | - name: outbound_proxy
40 | domains: ["*"]
41 | routes:
42 | - match:
43 | prefix: "/"
44 | route:
45 | cluster: backend
46 | http_filters:
47 | - name: envoy.filters.http.router
48 | typed_config:
49 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
50 | clusters:
51 | - name: spire_agent
52 | connect_timeout: 0.25s
53 | http2_protocol_options: {}
54 | load_assignment:
55 | cluster_name: spire_agent
56 | endpoints:
57 | - lb_endpoints:
58 | - endpoint:
59 | address:
60 | pipe:
61 | path: /run/spire/sockets/agent.sock
62 | - name: backend
63 | connect_timeout: 0.25s
64 | type: strict_dns
65 | lb_policy: ROUND_ROBIN
66 | load_assignment:
67 | cluster_name: ext-authz
68 | endpoints:
69 | - lb_endpoints:
70 | - endpoint:
71 | address:
72 | socket_address:
73 | address: backend-envoy
74 | port_value: 9001
75 | transport_socket:
76 | name: envoy.transport_sockets.tls
77 | typed_config:
78 | "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
79 | common_tls_context:
80 | tls_certificate_sds_secret_configs:
81 | - name: "spiffe://example.org/ns/default/sa/default/frontend"
82 | sds_config:
83 | resource_api_version: V3
84 | api_config_source:
85 | api_type: GRPC
86 | transport_api_version: V3
87 | grpc_services:
88 | envoy_grpc:
89 | cluster_name: spire_agent
90 | combined_validation_context:
91 | # validate the SPIFFE ID of the server (recommended)
92 | default_validation_context:
93 | match_typed_subject_alt_names:
94 | - san_type: URI
95 | matcher:
96 | exact: "spiffe://example.org/ns/default/sa/default/backend"
97 | validation_context_sds_secret_config:
98 | name: "spiffe://example.org"
99 | sds_config:
100 | resource_api_version: V3
101 | api_config_source:
102 | api_type: GRPC
103 | transport_api_version: V3
104 | grpc_services:
105 | envoy_grpc:
106 | cluster_name: spire_agent
107 | tls_params:
108 | ecdh_curves:
109 | - X25519:P-256:P-521:P-384
110 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend/config/symbank-webapp.conf:
--------------------------------------------------------------------------------
1 | port = 3000
2 | address = ""
3 | balanceDataPath = "http://localhost:3001/balances/balance_1"
4 | profileDataPath = "http://localhost:3001/profiles/profile_1"
5 | transactionDataPath = "http://localhost:3001/transactions/transaction_1"
6 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend/frontend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frontend
5 | labels:
6 | app: frontend
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: frontend
11 | template:
12 | metadata:
13 | labels:
14 | app: frontend
15 | spec:
16 | containers:
17 | - name: envoy
18 | image: envoyproxy/envoy:v1.25.1
19 | imagePullPolicy: Always
20 | args: ["-l", "debug", "--local-address-ip-version", "v4", "-c", "/run/envoy/envoy.yaml", "--base-id", "1"]
21 | volumeMounts:
22 | - name: envoy-config
23 | mountPath: "/run/envoy"
24 | readOnly: true
25 | - name: spire-agent-socket
26 | mountPath: /run/spire/sockets
27 | readOnly: true
28 | - name: frontend
29 | imagePullPolicy: IfNotPresent
30 | image: us.gcr.io/scytale-registry/symbank-webapp@sha256:a1c9b1d14e14bd1a4e75698a4f153680d2a08e6f8d1f2d7110bff63d39228a75
31 | command: ["/opt/symbank-webapp/symbank-webapp", "-config", "/run/symbank-webapp/config/symbank-webapp.conf"]
32 | ports:
33 | - containerPort: 3000
34 | volumeMounts:
35 | - name: symbank-webapp-config
36 | mountPath: /run/symbank-webapp/config
37 | volumes:
38 | - name: envoy-config
39 | configMap:
40 | name: frontend-envoy
41 | - name: spire-agent-socket
42 | hostPath:
43 | path: /run/spire/sockets
44 | type: DirectoryOrCreate
45 | - name: symbank-webapp-config
46 | configMap:
47 | name: symbank-webapp-config
48 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/frontend/frontend-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: frontend
5 | spec:
6 | ports:
7 | - port: 3000
8 | name: http
9 | protocol: TCP
10 | targetPort: 3000
11 | selector:
12 | app: frontend
13 | type: LoadBalancer
14 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/kustomization.yaml:
--------------------------------------------------------------------------------
1 | configMapGenerator:
2 | - name: backend-envoy
3 | files:
4 | - backend/config/envoy.yaml
5 | - name: backend-balance-json-data
6 | files:
7 | - backend/json/balances/balance_1
8 | - backend/json/balances/balance_2
9 | - name: backend-profile-json-data
10 | files:
11 | - backend/json/profiles/profile_1
12 | - backend/json/profiles/profile_2
13 | - name: backend-transactions-json-data
14 | files:
15 | - backend/json/transactions/transaction_1
16 | - backend/json/transactions/transaction_2
17 | - name: frontend-envoy
18 | files:
19 | - frontend/config/envoy.yaml
20 | - name: symbank-webapp-config
21 | files:
22 | - frontend/config/symbank-webapp.conf
23 | - name: symbank-webapp-2-config
24 | files:
25 | - frontend-2/config/symbank-webapp-2.conf
26 | - name: frontend-2-envoy
27 | files:
28 | - frontend-2/config/envoy.yaml
29 | generatorOptions:
30 | disableNameSuffixHash: true
31 |
32 | resources:
33 | - backend/backend-service.yaml
34 | - backend/backend-deployment.yaml
35 | - frontend/frontend-service.yaml
36 | - frontend/frontend-deployment.yaml
37 | - frontend-2/frontend-2-service.yaml
38 | - frontend-2/frontend-2-deployment.yaml
39 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/k8s/metallb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: metallb.io/v1beta1
2 | kind: IPAddressPool
3 | metadata:
4 | name: sandbox
5 | namespace: metallb-system
6 | spec:
7 | addresses:
8 | # On the other hand, the sandbox environment uses private IP space,
9 | # which is free and plentiful. We give this address pool a ton of IPs,
10 | # so that developers can spin up as many sandboxes as they need.
11 | - 192.168.144.0/20
12 |
13 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/metallb-config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: metallb.io/v1beta1
2 | kind: IPAddressPool
3 | metadata:
4 | name: example
5 | namespace: metallb-system
6 | spec:
7 | addresses:
8 | - 172.18.255.200-172.18.255.250
9 | ---
10 | apiVersion: metallb.io/v1beta1
11 | kind: L2Advertisement
12 | metadata:
13 | name: empty
14 | namespace: metallb-system
15 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/scripts/clean-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 |
10 | bb=$(tput bold) || true
11 | nn=$(tput sgr0) || true
12 | green=$(tput setaf 2) || true
13 |
14 | echo "${bb}Deleting backend and frontends resources...${nn}"
15 | kubectl delete -k ${EXAMPLEDIR}/k8s/. --ignore-not-found
16 |
17 | echo "${bb}Deleting all the SPIRE resources available in the cluster...${nn}"
18 | kubectl delete -k ${K8SDIR}/quickstart/. --ignore-not-found
19 |
20 | echo "${green}Cleaning completed.${nn}"
21 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/scripts/pre-set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | red=$(tput setaf 1) || true
12 | green=$(tput setaf 2) || true
13 |
14 | LOGLINE="Agent attestation request completed"
15 | wait_for_agent() {
16 | for ((i=0;i<120;i++)); do
17 | if ! kubectl -nspire rollout status statefulset/spire-server; then
18 | sleep 1
19 | continue
20 | fi
21 | if ! kubectl -nspire rollout status daemonset/spire-agent; then
22 | sleep 1
23 | continue
24 | fi
25 | if ! kubectl -nspire logs statefulset/spire-server -c spire-server | grep -e "$LOGLINE" ; then
26 | sleep 1
27 | continue
28 | fi
29 | echo "${bold}SPIRE Agent ready.${nn}"
30 | RUNNING=1
31 | break
32 | done
33 | if [ ! -n "${RUNNING}" ]; then
34 | echo "${red}Timed out waiting for SPIRE Agent to be running.${nn}"
35 | exit 1
36 | fi
37 | }
38 |
39 | echo "${bb}Creates all the resources needed to the SPIRE Server and SPIRE Agent to be available in the cluster.${nn}"
40 | kubectl apply -k ${K8SDIR}/quickstart/.
41 |
42 | echo "${bb}Waiting until SPIRE Agent is running${nn}"
43 | wait_for_agent
44 |
45 | echo "${bb}Creates registration entries.${nn}"
46 | bash ${K8SDIR}/quickstart/create-node-registration-entry.sh > /dev/null
47 |
48 | echo "${green}SPIRE resources creation completed.${nn}"
49 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/scripts/set-env.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | EXAMPLEDIR="$(dirname "$DIR")"
7 | K8SDIR="$(dirname "$EXAMPLEDIR")"
8 |
9 | bb=$(tput bold) || true
10 | nn=$(tput sgr0) || true
11 | red=$(tput setaf 1) || true
12 |
13 | restart_deployments() {
14 | kubectl scale deployment backend --replicas=0
15 | kubectl scale deployment backend --replicas=1
16 |
17 | kubectl scale deployment frontend --replicas=0
18 | kubectl scale deployment frontend --replicas=1
19 |
20 | kubectl scale deployment frontend-2 --replicas=0
21 | kubectl scale deployment frontend-2 --replicas=1
22 | }
23 |
24 | wait_for_envoy() {
25 | # wait until deployments are completed and Envoy is ready
26 | LOGLINE="DNS hosts have changed for backend-envoy"
27 |
28 | for ((i=0;i<30;i++)); do
29 | if ! kubectl rollout status deployment/backend; then
30 | sleep 1
31 | continue
32 | fi
33 | if ! kubectl rollout status deployment/frontend; then
34 | sleep 1
35 | continue
36 | fi
37 | if ! kubectl rollout status deployment/frontend-2; then
38 | sleep 1
39 | continue
40 | fi
41 | if ! kubectl logs --tail=300 --selector=app=frontend -c envoy | grep -qe "${LOGLINE}" ; then
42 | sleep 5
43 | echo "Waiting until Envoy is ready..."
44 | continue
45 | fi
46 | echo "Workloads ready."
47 | WK_READY=1
48 | break
49 | done
50 | if [ -z "${WK_READY}" ]; then
51 | echo "${red}Timed out waiting for workloads to be ready.${nn}"
52 | exit 1
53 | fi
54 | }
55 |
56 | #Creates Envoy-x509 scenario
57 | bash "${EXAMPLEDIR}"/scripts/pre-set-env.sh > /dev/null
58 |
59 | echo "${bb}Applying SPIRE Envoy X509 configuration...${nn}"
60 | # Updates resources for the backend and frontend
61 | kubectl apply -k "${EXAMPLEDIR}"/k8s/. > /dev/null
62 | bash "${EXAMPLEDIR}"/create-registration-entries.sh > /dev/null
63 |
64 | #Restarts all deployments to pickup the new configurations
65 | restart_deployments > /dev/null
66 |
67 | echo "${bb}Waiting until deployments and Envoy are ready...${nn}"
68 | wait_for_envoy > /dev/null
69 |
70 | echo "${bb}X.509 Environment creation completed.${nn}"
71 |
--------------------------------------------------------------------------------
/k8s/envoy-x509/test.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | bb=$(tput bold) || true
8 | nn=$(tput sgr0) || true
9 | red=$(tput setaf 1) || true
10 | green=$(tput setaf 2) || true
11 |
12 | clean-env() {
13 | echo "${bb}Cleaning up...${nn}"
14 | bash "${DIR}"/scripts/clean-env.sh > /dev/null
15 | }
16 |
17 | trap clean-env EXIT
18 |
19 | echo "${bb}Preparing environment...${nm}"
20 | clean-env
21 |
22 | # Creates Envoy X509 scenario
23 | bash "${DIR}"/scripts/set-env.sh
24 |
25 | # If balance is part of the response, then the request reached the backend service.
26 | BALANCE_LINE="Your current balance is 10.95"
27 | if curl -s $(minikube service frontend --url) | grep -qe "$BALANCE_LINE"; then
28 | echo "${green}Success${nn}"
29 | exit 0
30 | fi
31 |
32 | echo "${red}Failed! Request did not make it through the proxies.${nn}".
33 | exit 1
34 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/agent-daemonset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: spire-agent
5 | namespace: spire
6 | labels:
7 | app: spire-agent
8 | spec:
9 | selector:
10 | matchLabels:
11 | app: spire-agent
12 | template:
13 | metadata:
14 | namespace: spire
15 | labels:
16 | app: spire-agent
17 | spec:
18 | hostPID: true
19 | hostNetwork: true
20 | dnsPolicy: ClusterFirstWithHostNet
21 | serviceAccountName: spire-agent
22 | initContainers:
23 | - name: init
24 | # This is a small image with wait-for-it, choose whatever image
25 | # you prefer that waits for a service to be up. This image is built
26 | # from https://github.com/lqhl/wait-for-it
27 | image: cgr.dev/chainguard/wait-for-it
28 | args: ["-t", "30", "spire-server:8081"]
29 | containers:
30 | - name: spire-agent
31 | image: ghcr.io/spiffe/spire-agent:1.5.1
32 | args: ["-config", "/run/spire/config/agent.conf"]
33 | volumeMounts:
34 | - name: spire-config
35 | mountPath: /run/spire/config
36 | readOnly: true
37 | - name: spire-bundle
38 | mountPath: /run/spire/bundle
39 | - name: spire-agent-socket
40 | mountPath: /run/spire/sockets
41 | readOnly: false
42 | livenessProbe:
43 | httpGet:
44 | path: /live
45 | port: 8080
46 | failureThreshold: 2
47 | initialDelaySeconds: 15
48 | periodSeconds: 60
49 | timeoutSeconds: 3
50 | readinessProbe:
51 | httpGet:
52 | path: /ready
53 | port: 8080
54 | initialDelaySeconds: 5
55 | periodSeconds: 5
56 | volumes:
57 | - name: spire-config
58 | configMap:
59 | name: spire-agent
60 | - name: spire-bundle
61 | configMap:
62 | name: spire-bundle
63 | - name: spire-agent-socket
64 | hostPath:
65 | path: /run/spire/sockets
66 | type: DirectoryOrCreate
67 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/client-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: client
5 | labels:
6 | app: client
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: client
11 | template:
12 | metadata:
13 | labels:
14 | app: client
15 | spec:
16 | hostPID: true
17 | hostNetwork: true
18 | dnsPolicy: ClusterFirstWithHostNet
19 | containers:
20 | - name: client
21 | image: us.gcr.io/scytale-registry/aws-cli:latest
22 | command: ["sleep"]
23 | args: ["1000000000"]
24 | volumeMounts:
25 | - name: spire-agent-socket
26 | mountPath: /run/spire/sockets
27 | readOnly: true
28 | volumes:
29 | - name: spire-agent-socket
30 | hostPath:
31 | path: /run/spire/sockets
32 | type: Directory
33 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: spire-ingress
5 | namespace: spire
6 | spec:
7 | tls:
8 | - hosts:
9 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
10 | - MY_DISCOVERY_DOMAIN
11 | secretName: oidc-secret
12 | rules:
13 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
14 | - host: MY_DISCOVERY_DOMAIN
15 | http:
16 | paths:
17 | - path: /.well-known/openid-configuration
18 | pathType: Prefix
19 | backend:
20 | service:
21 | name: spire-oidc
22 | port:
23 | number: 443
24 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/oidc-dp-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: oidc-discovery-provider
5 | namespace: spire
6 | data:
7 | oidc-discovery-provider.conf: |
8 | log_level = "INFO"
9 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
10 | domains = ["MY_DISCOVERY_DOMAIN"]
11 | acme {
12 | directory_url = "https://acme-v02.api.letsencrypt.org/directory"
13 | cache_dir = "/run/spire"
14 | tos_accepted = true
15 | # TODO: Change MY_EMAIL_ADDRESS with your email
16 | email = "MY_EMAIL_ADDRESS"
17 | }
18 | server_api {
19 | address = "unix:///tmp/spire-server/private/api.sock"
20 | }
21 | health_checks {}
22 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/server-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | data:
7 | server.conf: |
8 | server {
9 | bind_address = "0.0.0.0"
10 | bind_port = "8081"
11 | socket_path = "/tmp/spire-server/private/api.sock"
12 | trust_domain = "example.org"
13 | data_dir = "/run/spire/data"
14 | log_level = "DEBUG"
15 | federation {
16 | bundle_endpoint {
17 | address = "0.0.0.0"
18 | port = 8443
19 | }
20 | }
21 | #AWS requires the use of RSA. EC cryptography is not supported
22 | ca_key_type = "rsa-2048"
23 |
24 | # Creates the iss claim in JWT-SVIDs.
25 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
26 | jwt_issuer = "https://MY_DISCOVERY_DOMAIN"
27 |
28 | ca_subject = {
29 | country = ["US"],
30 | organization = ["SPIFFE"],
31 | common_name = "",
32 | }
33 | }
34 |
35 | plugins {
36 | DataStore "sql" {
37 | plugin_data {
38 | database_type = "sqlite3"
39 | connection_string = "/run/spire/data/datastore.sqlite3"
40 | }
41 | }
42 |
43 | NodeAttestor "k8s_sat" {
44 | plugin_data {
45 | clusters = {
46 | # TODO: Change this to your cluster name
47 | "demo-cluster" = {
48 | use_token_review_api_validation = true
49 | service_account_allow_list = ["spire:spire-agent"]
50 | }
51 | }
52 | }
53 | }
54 |
55 | KeyManager "disk" {
56 | plugin_data {
57 | keys_path = "/run/spire/data/keys.json"
58 | }
59 | }
60 |
61 | Notifier "k8sbundle" {
62 | plugin_data {
63 | }
64 | }
65 | }
66 |
67 | health_checks {
68 | listener_enabled = true
69 | bind_address = "0.0.0.0"
70 | bind_port = "8080"
71 | live_path = "/live"
72 | ready_path = "/ready"
73 | }
74 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/server-oidc-service.yaml:
--------------------------------------------------------------------------------
1 | # Service definition for the admission webhook
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: spire-oidc
6 | namespace: spire
7 | spec:
8 | type: LoadBalancer
9 | selector:
10 | app: spire-server
11 | ports:
12 | - name: https
13 | port: 443
14 | targetPort: spire-oidc-port
15 |
--------------------------------------------------------------------------------
/k8s/oidc-aws/server-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | labels:
7 | app: spire-server
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: spire-server
13 | serviceName: spire-server
14 | template:
15 | metadata:
16 | namespace: spire
17 | labels:
18 | app: spire-server
19 | spec:
20 | serviceAccountName: spire-server
21 | shareProcessNamespace: true
22 | containers:
23 | - name: spire-server
24 | image: ghcr.io/spiffe/spire-server:1.5.1
25 | args:
26 | - -config
27 | - /run/spire/config/server.conf
28 | ports:
29 | - containerPort: 8081
30 | volumeMounts:
31 | - name: spire-config
32 | mountPath: /run/spire/config
33 | readOnly: true
34 | - name: spire-data
35 | mountPath: /run/spire/data
36 | readOnly: false
37 | - name: spire-server-socket
38 | mountPath: /tmp/spire-server/private
39 | readOnly: false
40 | livenessProbe:
41 | httpGet:
42 | path: /live
43 | port: 8080
44 | failureThreshold: 2
45 | initialDelaySeconds: 15
46 | periodSeconds: 60
47 | timeoutSeconds: 3
48 | readinessProbe:
49 | httpGet:
50 | path: /ready
51 | port: 8080
52 | initialDelaySeconds: 5
53 | periodSeconds: 5
54 | - name: spire-oidc
55 | image: ghcr.io/spiffe/oidc-discovery-provider:1.5.1
56 | args:
57 | - -config
58 | - /run/spire/oidc/config/oidc-discovery-provider.conf
59 | ports:
60 | - containerPort: 443
61 | name: spire-oidc-port
62 | volumeMounts:
63 | - name: spire-server-socket
64 | mountPath: /tmp/spire-server/private
65 | readOnly: true
66 | - name: spire-oidc-config
67 | mountPath: /run/spire/oidc/config/
68 | readOnly: true
69 | - name: spire-data
70 | mountPath: /run/spire/data
71 | readOnly: false
72 | readinessProbe:
73 | httpGet:
74 | path: /keys # TODO: Change this to /ready when using 1.5.2+
75 | port: 8008
76 | failureThreshold: 5
77 | initialDelaySeconds: 5
78 | periodSeconds: 5
79 | timeoutSeconds: 3
80 | volumes:
81 | - name: spire-config
82 | configMap:
83 | name: spire-server
84 | - name: spire-server-socket
85 | hostPath:
86 | path: /run/spire/sockets/server
87 | type: DirectoryOrCreate
88 | - name: spire-oidc-config
89 | configMap:
90 | name: oidc-discovery-provider
91 | volumeClaimTemplates:
92 | - metadata:
93 | name: spire-data
94 | namespace: spire
95 | spec:
96 | accessModes:
97 | - ReadWriteOnce
98 | resources:
99 | requests:
100 | storage: 1Gi
101 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/k8s/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: spire-ingress
5 | namespace: spire
6 | spec:
7 | tls:
8 | - hosts:
9 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
10 | - MY_DISCOVERY_DOMAIN
11 | secretName: oidc-secret
12 | rules:
13 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
14 | - host: MY_DISCOVERY_DOMAIN
15 | http:
16 | paths:
17 | - path: /.well-known/openid-configuration
18 | pathType: Prefix
19 | backend:
20 | service:
21 | name: spire-oidc
22 | port:
23 | number: 443
24 | - path: /keys
25 | pathType: Prefix
26 | backend:
27 | service:
28 | name: spire-oidc
29 | port:
30 | number: 443
31 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/k8s/oidc-dp-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: oidc-discovery-provider
5 | namespace: spire
6 | data:
7 | oidc-discovery-provider.conf: |
8 | log_level = "INFO"
9 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
10 | domain = "MY_DISCOVERY_DOMAIN"
11 | acme {
12 | directory_url = "https://acme-v02.api.letsencrypt.org/directory"
13 | cache_dir = "/run/spire"
14 | tos_accepted = true
15 | # TODO: Change MY_EMAIL_ADDRESS with your email
16 | email = "MY_EMAIL_ADDRESS"
17 | }
18 | server_api {
19 | address = "unix:///tmp/spire-server/private/api.sock"
20 | }
21 | health_checks {}
22 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/k8s/server-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | data:
7 | server.conf: |
8 | server {
9 | bind_address = "0.0.0.0"
10 | bind_port = "8081"
11 | socket_path = "/tmp/spire-server/private/api.sock"
12 | trust_domain = "example.org"
13 | data_dir = "/run/spire/data"
14 | log_level = "DEBUG"
15 | federation {
16 | bundle_endpoint {
17 | address = "0.0.0.0"
18 | port = 8443
19 | }
20 | }
21 | ca_key_type = "rsa-2048"
22 |
23 | # Creates the iss claim in JWT-SVIDs.
24 | # TODO: Replace MY_DISCOVERY_DOMAIN with the FQDN of the Discovery Provider that you will configure in DNS
25 | jwt_issuer = "MY_DISCOVERY_DOMAIN"
26 |
27 | ca_subject = {
28 | country = ["US"],
29 | organization = ["SPIFFE"],
30 | common_name = "",
31 | }
32 | }
33 |
34 | plugins {
35 | DataStore "sql" {
36 | plugin_data {
37 | database_type = "sqlite3"
38 | connection_string = "/run/spire/data/datastore.sqlite3"
39 | }
40 | }
41 |
42 | NodeAttestor "k8s_sat" {
43 | plugin_data {
44 | clusters = {
45 | # TODO: Change this to your cluster name
46 | "MY_CLUSTER_NAME" = {
47 | use_token_review_api_validation = true
48 | service_account_allow_list = ["spire:spire-agent"]
49 | service_account_allow_list = ["spire:spire-oidc"]
50 | }
51 | }
52 | }
53 | }
54 |
55 | KeyManager "disk" {
56 | plugin_data {
57 | keys_path = "/run/spire/data/keys.json"
58 | }
59 | }
60 |
61 | Notifier "k8sbundle" {
62 | plugin_data {
63 | }
64 | }
65 | }
66 |
67 | health_checks {
68 | listener_enabled = true
69 | bind_address = "0.0.0.0"
70 | bind_port = "8080"
71 | live_path = "/live"
72 | ready_path = "/ready"
73 | }
74 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/k8s/server-oidc-service.yaml:
--------------------------------------------------------------------------------
1 | # Service definition for the admission webhook
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: spire-oidc
6 | namespace: spire
7 | spec:
8 | type: LoadBalancer
9 | selector:
10 | app: spire-server
11 | ports:
12 | - name: https
13 | port: 443
14 | targetPort: spire-oidc-port
15 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/k8s/server-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | labels:
7 | app: spire-server
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: spire-server
13 | serviceName: spire-server
14 | template:
15 | metadata:
16 | namespace: spire
17 | labels:
18 | app: spire-server
19 | spec:
20 | serviceAccountName: spire-server
21 | shareProcessNamespace: true
22 | containers:
23 | - name: spire-server
24 | image: ghcr.io/spiffe/spire-server:1.5.1
25 | args:
26 | - -config
27 | - /run/spire/config/server.conf
28 | ports:
29 | - containerPort: 8081
30 | volumeMounts:
31 | - name: spire-config
32 | mountPath: /run/spire/config
33 | readOnly: true
34 | - name: spire-data
35 | mountPath: /run/spire/data
36 | readOnly: false
37 | - name: spire-server-socket
38 | mountPath: /tmp/spire-server/private
39 | readOnly: false
40 | livenessProbe:
41 | httpGet:
42 | path: /live
43 | port: 8080
44 | failureThreshold: 2
45 | initialDelaySeconds: 15
46 | periodSeconds: 60
47 | timeoutSeconds: 3
48 | readinessProbe:
49 | httpGet:
50 | path: /ready
51 | port: 8080
52 | initialDelaySeconds: 5
53 | periodSeconds: 5
54 | - name: spire-oidc
55 | image: ghcr.io/spiffe/oidc-discovery-provider:1.5.1
56 | args:
57 | - -config
58 | - /run/spire/oidc/config/oidc-discovery-provider.conf
59 | ports:
60 | - containerPort: 443
61 | name: spire-oidc-port
62 | volumeMounts:
63 | - name: spire-server-socket
64 | mountPath: /tmp/spire-server/private
65 | readOnly: true
66 | - name: spire-oidc-config
67 | mountPath: /run/spire/oidc/config/
68 | readOnly: true
69 | - name: spire-data
70 | mountPath: /run/spire/data
71 | readOnly: false
72 | readinessProbe:
73 | httpGet:
74 | path: /keys # TODO: Change this to /ready when using 1.5.2+
75 | port: 8008
76 | failureThreshold: 5
77 | initialDelaySeconds: 5
78 | periodSeconds: 5
79 | timeoutSeconds: 3
80 | volumes:
81 | - name: spire-config
82 | configMap:
83 | name: spire-server
84 | - name: spire-server-socket
85 | hostPath:
86 | path: /run/spire/sockets/server
87 | type: DirectoryOrCreate
88 | - name: spire-oidc-config
89 | configMap:
90 | name: oidc-discovery-provider
91 | volumeClaimTemplates:
92 | - metadata:
93 | name: spire-data
94 | namespace: spire
95 | spec:
96 | accessModes:
97 | - ReadWriteOnce
98 | resources:
99 | requests:
100 | storage: 1Gi
101 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/vault/config.hcl:
--------------------------------------------------------------------------------
1 | listener "tcp" {
2 | address = "127.0.0.1:8200"
3 | tls_disable = 1
4 | }
5 |
6 | storage "file" {
7 | path = "vault-storage"
8 | }
9 |
--------------------------------------------------------------------------------
/k8s/oidc-vault/vault/vault-policy.hcl:
--------------------------------------------------------------------------------
1 | path "secret/my-super-secret" {
2 | capabilities = ["read"]
3 | }
4 |
--------------------------------------------------------------------------------
/k8s/quickstart/agent-account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: spire-agent
5 | namespace: spire
6 |
--------------------------------------------------------------------------------
/k8s/quickstart/agent-cluster-role.yaml:
--------------------------------------------------------------------------------
1 | # Required cluster role to allow spire-agent to query k8s API server
2 | kind: ClusterRole
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | metadata:
5 | name: spire-agent-cluster-role
6 | rules:
7 | - apiGroups: [""]
8 | resources: ["pods","nodes","nodes/proxy"]
9 | verbs: ["get"]
10 |
11 | ---
12 | # Binds above cluster role to spire-agent service account
13 | kind: ClusterRoleBinding
14 | apiVersion: rbac.authorization.k8s.io/v1
15 | metadata:
16 | name: spire-agent-cluster-role-binding
17 | subjects:
18 | - kind: ServiceAccount
19 | name: spire-agent
20 | namespace: spire
21 | roleRef:
22 | kind: ClusterRole
23 | name: spire-agent-cluster-role
24 | apiGroup: rbac.authorization.k8s.io
--------------------------------------------------------------------------------
/k8s/quickstart/agent-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: spire-agent
5 | namespace: spire
6 | data:
7 | agent.conf: |
8 | agent {
9 | data_dir = "/run/spire"
10 | log_level = "DEBUG"
11 | server_address = "spire-server"
12 | server_port = "8081"
13 | socket_path = "/run/spire/sockets/agent.sock"
14 | trust_bundle_path = "/run/spire/bundle/bundle.crt"
15 | trust_domain = "example.org"
16 | }
17 |
18 | plugins {
19 | NodeAttestor "k8s_psat" {
20 | plugin_data {
21 | # NOTE: Change this to your cluster name
22 | cluster = "demo-cluster"
23 | }
24 | }
25 |
26 | KeyManager "memory" {
27 | plugin_data {
28 | }
29 | }
30 |
31 | WorkloadAttestor "k8s" {
32 | plugin_data {
33 | # Defaults to the secure kubelet port by default.
34 | # Minikube does not have a cert in the cluster CA bundle that
35 | # can authenticate the kubelet cert, so skip validation.
36 | skip_kubelet_verification = true
37 | node_name_env = "MY_NODE_NAME"
38 | }
39 | }
40 |
41 | WorkloadAttestor "unix" {
42 | plugin_data {
43 | }
44 | }
45 | }
46 |
47 | health_checks {
48 | listener_enabled = true
49 | bind_address = "0.0.0.0"
50 | bind_port = "8080"
51 | live_path = "/live"
52 | ready_path = "/ready"
53 | }
54 |
--------------------------------------------------------------------------------
/k8s/quickstart/agent-daemonset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: spire-agent
5 | namespace: spire
6 | labels:
7 | app: spire-agent
8 | spec:
9 | selector:
10 | matchLabels:
11 | app: spire-agent
12 | template:
13 | metadata:
14 | namespace: spire
15 | labels:
16 | app: spire-agent
17 | spec:
18 | hostPID: true
19 | hostNetwork: true
20 | dnsPolicy: ClusterFirstWithHostNet
21 | serviceAccountName: spire-agent
22 | initContainers:
23 | - name: init
24 | # This is a small image with wait-for-it, choose whatever image
25 | # you prefer that waits for a service to be up. This image is built
26 | # from https://github.com/lqhl/wait-for-it
27 | image: cgr.dev/chainguard/wait-for-it
28 | args: ["-t", "30", "spire-server:8081"]
29 | containers:
30 | - name: spire-agent
31 | image: ghcr.io/spiffe/spire-agent:1.11.2
32 | args: ["-config", "/run/spire/config/agent.conf"]
33 | env:
34 | - name: MY_NODE_NAME
35 | valueFrom:
36 | fieldRef:
37 | fieldPath: status.podIP
38 | volumeMounts:
39 | - name: spire-config
40 | mountPath: /run/spire/config
41 | readOnly: true
42 | - name: spire-bundle
43 | mountPath: /run/spire/bundle
44 | - name: spire-agent-socket
45 | mountPath: /run/spire/sockets
46 | readOnly: false
47 | - name: spire-token
48 | mountPath: /var/run/secrets/tokens
49 | livenessProbe:
50 | httpGet:
51 | path: /live
52 | port: 8080
53 | failureThreshold: 2
54 | initialDelaySeconds: 15
55 | periodSeconds: 60
56 | timeoutSeconds: 3
57 | readinessProbe:
58 | httpGet:
59 | path: /ready
60 | port: 8080
61 | initialDelaySeconds: 5
62 | periodSeconds: 5
63 | volumes:
64 | - name: spire-config
65 | configMap:
66 | name: spire-agent
67 | - name: spire-bundle
68 | configMap:
69 | name: spire-bundle
70 | - name: spire-agent-socket
71 | hostPath:
72 | path: /run/spire/sockets
73 | type: DirectoryOrCreate
74 | - name: spire-token
75 | projected:
76 | sources:
77 | - serviceAccountToken:
78 | path: spire-agent
79 | expirationSeconds: 7200
80 | audience: spire-server
81 |
--------------------------------------------------------------------------------
/k8s/quickstart/client-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: client
5 | labels:
6 | app: client
7 | spec:
8 | selector:
9 | matchLabels:
10 | app: client
11 | template:
12 | metadata:
13 | labels:
14 | app: client
15 | spec:
16 | hostPID: true
17 | hostNetwork: true
18 | dnsPolicy: ClusterFirstWithHostNet
19 | containers:
20 | - name: client
21 | image: ghcr.io/spiffe/spire-agent:1.5.1
22 | command: ["/opt/spire/bin/spire-agent"]
23 | args: [ "api", "watch", "-socketPath", "/run/spire/sockets/agent.sock" ]
24 | volumeMounts:
25 | - name: spire-agent-socket
26 | mountPath: /run/spire/sockets
27 | readOnly: true
28 | volumes:
29 | - name: spire-agent-socket
30 | hostPath:
31 | path: /run/spire/sockets
32 | type: Directory
33 |
--------------------------------------------------------------------------------
/k8s/quickstart/create-node-registration-entry.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -e
4 |
5 | bb=$(tput bold)
6 | nn=$(tput sgr0)
7 |
8 |
9 | echo "${bb}Creating registration entry for the node...${nn}"
10 | kubectl exec -n spire spire-server-0 -- \
11 | /opt/spire/bin/spire-server entry create \
12 | -node \
13 | -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \
14 | -selector k8s_psat:cluster:demo-cluster \
15 | -selector k8s_psat:agent_ns:spire \
16 | -selector k8s_psat:agent_sa:spire-agent
17 |
--------------------------------------------------------------------------------
/k8s/quickstart/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 |
4 | namespace: spire
5 |
6 | resources:
7 | - spire-namespace.yaml
8 | - agent-account.yaml
9 | - agent-cluster-role.yaml
10 | - agent-configmap.yaml
11 | - agent-daemonset.yaml
12 | - server-account.yaml
13 | - server-cluster-role.yaml
14 | - server-configmap.yaml
15 | - server-service.yaml
16 | - server-statefulset.yaml
17 | - spire-bundle-configmap.yaml
18 |
19 |
--------------------------------------------------------------------------------
/k8s/quickstart/server-account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 |
--------------------------------------------------------------------------------
/k8s/quickstart/server-cluster-role.yaml:
--------------------------------------------------------------------------------
1 | # Role (namespace scoped) to be able to push certificate bundles to a configmap
2 | kind: Role
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | metadata:
5 | name: spire-server-configmap-role
6 | namespace: spire
7 | rules:
8 | - apiGroups: [""]
9 | resources: ["configmaps"]
10 | verbs: ["patch", "get", "list"]
11 | ---
12 | # Binds above role to spire-server service account
13 | kind: RoleBinding
14 | apiVersion: rbac.authorization.k8s.io/v1
15 | metadata:
16 | name: spire-server-configmap-role-binding
17 | namespace: spire
18 | subjects:
19 | - kind: ServiceAccount
20 | name: spire-server
21 | namespace: spire
22 | roleRef:
23 | apiGroup: rbac.authorization.k8s.io
24 | kind: Role
25 | name: spire-server-configmap-role
26 | ---
27 | # ClusterRole to allow spire-server node attestor to read pods and nodes, and query Token Review API
28 | kind: ClusterRole
29 | apiVersion: rbac.authorization.k8s.io/v1
30 | metadata:
31 | name: spire-server-trust-role
32 | rules:
33 | - apiGroups: [""]
34 | resources: ["pods", "nodes"]
35 | verbs: ["get"]
36 | - apiGroups: ["authentication.k8s.io"]
37 | resources: ["tokenreviews"]
38 | verbs: ["create"]
39 | ---
40 | # Binds above cluster role to spire-server service account
41 | kind: ClusterRoleBinding
42 | apiVersion: rbac.authorization.k8s.io/v1
43 | metadata:
44 | name: spire-server-trust-role-binding
45 | subjects:
46 | - kind: ServiceAccount
47 | name: spire-server
48 | namespace: spire
49 | roleRef:
50 | kind: ClusterRole
51 | name: spire-server-trust-role
52 | apiGroup: rbac.authorization.k8s.io
53 |
--------------------------------------------------------------------------------
/k8s/quickstart/server-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | data:
7 | server.conf: |
8 | server {
9 | bind_address = "0.0.0.0"
10 | bind_port = "8081"
11 | socket_path = "/tmp/spire-server/private/api.sock"
12 | trust_domain = "example.org"
13 | data_dir = "/run/spire/data"
14 | log_level = "DEBUG"
15 | #AWS requires the use of RSA. EC cryptography is not supported
16 | ca_key_type = "rsa-2048"
17 |
18 | ca_subject = {
19 | country = ["US"],
20 | organization = ["SPIFFE"],
21 | common_name = "",
22 | }
23 | }
24 |
25 | plugins {
26 | DataStore "sql" {
27 | plugin_data {
28 | database_type = "sqlite3"
29 | connection_string = "/run/spire/data/datastore.sqlite3"
30 | }
31 | }
32 |
33 | NodeAttestor "k8s_psat" {
34 | plugin_data {
35 | clusters = {
36 | # NOTE: Change this to your cluster name
37 | "demo-cluster" = {
38 | service_account_allow_list = ["spire:spire-agent"]
39 | }
40 | }
41 | }
42 | }
43 |
44 | KeyManager "disk" {
45 | plugin_data {
46 | keys_path = "/run/spire/data/keys.json"
47 | }
48 | }
49 |
50 | Notifier "k8sbundle" {
51 | plugin_data {
52 | }
53 | }
54 | }
55 |
56 | health_checks {
57 | listener_enabled = true
58 | bind_address = "0.0.0.0"
59 | bind_port = "8080"
60 | live_path = "/live"
61 | ready_path = "/ready"
62 | }
63 |
--------------------------------------------------------------------------------
/k8s/quickstart/server-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | spec:
7 | type: NodePort
8 | ports:
9 | - name: grpc
10 | port: 8081
11 | targetPort: 8081
12 | protocol: TCP
13 | selector:
14 | app: spire-server
15 |
--------------------------------------------------------------------------------
/k8s/quickstart/server-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: spire-server
5 | namespace: spire
6 | labels:
7 | app: spire-server
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: spire-server
13 | serviceName: spire-server
14 | template:
15 | metadata:
16 | namespace: spire
17 | labels:
18 | app: spire-server
19 | spec:
20 | serviceAccountName: spire-server
21 | securityContext:
22 | fsGroup: 1000
23 | containers:
24 | - name: spire-server
25 | image: ghcr.io/spiffe/spire-server:1.11.2
26 | args:
27 | - -config
28 | - /run/spire/config/server.conf
29 | ports:
30 | - containerPort: 8081
31 | volumeMounts:
32 | - name: spire-config
33 | mountPath: /run/spire/config
34 | readOnly: true
35 | - name: spire-data
36 | mountPath: /run/spire/data
37 | readOnly: false
38 | livenessProbe:
39 | httpGet:
40 | path: /live
41 | port: 8080
42 | failureThreshold: 2
43 | initialDelaySeconds: 15
44 | periodSeconds: 60
45 | timeoutSeconds: 3
46 | readinessProbe:
47 | httpGet:
48 | path: /ready
49 | port: 8080
50 | initialDelaySeconds: 5
51 | periodSeconds: 5
52 | volumes:
53 | - name: spire-config
54 | configMap:
55 | name: spire-server
56 | volumeClaimTemplates:
57 | - metadata:
58 | name: spire-data
59 | namespace: spire
60 | spec:
61 | accessModes:
62 | - ReadWriteOnce
63 | resources:
64 | requests:
65 | storage: 1Gi
66 |
--------------------------------------------------------------------------------
/k8s/quickstart/spire-bundle-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: spire-bundle
5 | namespace: spire
--------------------------------------------------------------------------------
/k8s/quickstart/spire-namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: spire
5 |
--------------------------------------------------------------------------------
/k8s/quickstart/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | bold=$(tput bold)
6 | norm=$(tput sgr0)
7 | red=$(tput setaf 1)
8 | green=$(tput setaf 2)
9 | yellow=$(tput setaf 3)
10 |
11 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
12 |
13 | MINIKUBEPROFILE="SPIRE-SYSTEMS-TEST"
14 | MINIKUBECMD="minikube -p ${MINIKUBEPROFILE}"
15 | CHECKINTERVAL=1
16 | if [ -n "${GITHUB_WORKFLOW}" ]; then
17 | CHECKINTERVAL=5
18 | fi
19 | TMPDIR=$(mktemp -d)
20 | SERVERLOGS=${TMPDIR}/spire-server-logs.log
21 |
22 | start_minikube() {
23 | # GH actions will start up minikube
24 | if [ -z "${GITHUB_WORKFLOW}" ]; then
25 | echo "${bold}Starting minikube... ${norm}"
26 | ${MINIKUBECMD} start
27 | eval $(${MINIKUBECMD} docker-env --shell=bash)
28 | fi
29 | }
30 |
31 | tear_down_config() {
32 | kubectl delete namespace spire > /dev/null || true
33 | }
34 |
35 | stop_minikube() {
36 | # Don't stop the minikube inside of GH actions
37 | if [ -z "${GITHUB_WORKFLOW}" ]; then
38 | ${MINIKUBECMD} stop > /dev/null || true
39 | fi
40 | }
41 |
42 | cleanup() {
43 | echo -n "${bold}Cleaning up... ${norm}"
44 | if [ ! -z "${SUCCESS}" ]; then
45 | # success. remove the tmp dir.
46 | rm -rf ${TMPDIR}
47 | fi
48 | tear_down_config
49 | stop_minikube
50 | echo "${green}ok${norm}."
51 | }
52 |
53 | # apply the k8s configuration
54 | apply_server_config() {
55 | echo -n "${bold}Applying SPIRE server k8s configuration... ${norm}"
56 | kubectl apply -f ${DIR}/spire-namespace.yaml > /dev/null
57 | kubectl apply -f ${DIR}/server-account.yaml > /dev/null
58 | kubectl apply -f ${DIR}/server-cluster-role.yaml > /dev/null
59 | kubectl apply -f ${DIR}/server-configmap.yaml > /dev/null
60 | kubectl apply -f ${DIR}/spire-bundle-configmap.yaml > /dev/null
61 | kubectl apply -f ${DIR}/server-statefulset.yaml > /dev/null
62 | kubectl apply -f ${DIR}/server-service.yaml > /dev/null
63 | echo "${green}ok.${norm}"
64 | }
65 |
66 | apply_agent_config() {
67 | echo -n "${bold}Applying SPIRE agent k8s configuration... ${norm}"
68 | kubectl apply -f ${DIR}/agent-account.yaml > /dev/null
69 | kubectl apply -f ${DIR}/agent-cluster-role.yaml > /dev/null
70 | kubectl apply -f ${DIR}/agent-configmap.yaml > /dev/null
71 | kubectl apply -f ${DIR}/agent-daemonset.yaml > /dev/null
72 | echo "${green}ok.${norm}"
73 | }
74 |
75 | wait_for_pod() {
76 | local prefix=$1
77 | local outvar=$2
78 | for i in $(seq 60); do
79 | echo -n "${bold}Checking ${prefix} pod status... ${norm}"
80 | local getpods=$(kubectl -n spire get pods 2>/dev/null | grep ${prefix} || true)
81 | if [ -z "${getpods}" ]; then
82 | echo "${yellow}NotFound${norm}."
83 | sleep ${CHECKINTERVAL}
84 | continue
85 | fi
86 | local podname=$(echo ${getpods} | awk '{print $1}')
87 | local podstatus=$(echo ${getpods} | awk '{print $3}')
88 | if [ "${podstatus}" != "Running" ]; then
89 | echo "${yellow}${podstatus}${norm}."
90 | sleep ${CHECKINTERVAL}
91 | continue
92 | fi
93 | echo "${green}Running (${podname})${norm}."
94 | # I'd rather use name binding, but macOS ships with Bash 3. Silly macOS.
95 | eval $outvar=\${podname}
96 | return
97 | done
98 |
99 | echo "${red}failed${norm}."
100 | echo "${red}FAILED: ${prefix} pod not running in time${norm}"
101 | exit -1
102 | }
103 |
104 | wait_for_server() {
105 | wait_for_pod spire-server SPIRE_SERVER_POD_NAME
106 | }
107 |
108 | wait_for_agent() {
109 | wait_for_pod spire-agent SPIRE_AGENT_POD_NAME
110 | }
111 |
112 | check_for_node_attestation() {
113 | # spin for 60 seconds, checking to see if the agent attests
114 | for i in $(seq 60); do
115 | sleep ${CHECKINTERVAL}
116 | echo -n "${bold}Checking for node attestation... ${norm}"
117 | kubectl -n spire logs ${SPIRE_SERVER_POD_NAME} > ${SERVERLOGS} || true
118 | if grep -sxq -e ".*Agent attestation request completed.*k8s_psat.*" ${SERVERLOGS}; then
119 | echo "${green}ok${norm}."
120 | return
121 | fi
122 | echo "${yellow}nope${norm}."
123 | done
124 |
125 | echo "${red}FAILED: node attestation did not succeed in time.${norm}" >&2
126 | echo "${yellow}Log at ${SERVERLOGS}${norm}" >&2
127 | exit -1
128 | }
129 |
130 | trap cleanup EXIT
131 | start_minikube
132 | apply_server_config
133 | wait_for_server
134 | apply_agent_config
135 | wait_for_agent
136 | check_for_node_attestation
137 |
138 | echo "${bold}Success.${norm}"
139 |
--------------------------------------------------------------------------------
/k8s/test-all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This script runs the test.sh script in each subdirectory to test if each
3 | # tutorial is working properly. It is run by the Travis CI tool when a PR
4 | # is submitted or merged on GitHub, but you can also run it interactively.
5 |
6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
7 |
8 | bold=$(tput bold) || true
9 | norm=$(tput sgr0) || true
10 | red=$(tput setaf 1) || true
11 | green=$(tput setaf 2) || true
12 |
13 | fail() {
14 | echo "${red}$*${norm}."
15 | exit 1
16 | }
17 |
18 | echo "${bold}Checking for kubectl...${norm}"
19 | command -v kubectl > /dev/null || fail "kubectl is required."
20 |
21 | echo "${bold}Checking minikube status...${norm}"
22 | minikube status || fail "minikube isn't running"
23 |
24 | echo "${bold}Running all tests...${norm}"
25 | for testdir in "${DIR}"/*; do
26 | if [[ -x "${testdir}/test.sh" ]]; then
27 | testname=$(basename "$testdir")
28 | echo "${bold}Running \"$testname\" test...${norm}"
29 | if ${testdir}/test.sh; then
30 | echo "${green}\"$testname\" test succeeded${norm}"
31 | else
32 | echo "${red}\"$testname\" test failed${norm}"
33 | FAILED=true
34 | fi
35 | fi
36 | done
37 |
38 | if [ -n "${FAILED}" ]; then
39 | fail "There were test failures"
40 | fi
41 | echo "${green}Done. All test passed!${norm}"
42 |
43 |
--------------------------------------------------------------------------------