├── .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 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {{range .Data}} 56 | 57 | 58 | {{if .Time}} 59 | 60 | 61 | 62 | 63 | 64 | 65 | {{else}} 66 | 67 | 68 | 69 | 70 | 71 | 72 | {{end}} 73 | 74 | {{end}} 75 | 76 |
Last Updated: {{.LastUpdated.Format "Jan 2 15:04:05"}}
SymbolPriceOpenLowHighCloseTime
{{.Symbol}}{{.Price | printf "%.2f"}}{{.Open | printf "%.2f"}}{{.Low | printf "%.2f"}}{{.High | printf "%.2f"}}{{.Close | printf "%.2f"}}{{.Time.Format "15:04:05"}}------
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 | --------------------------------------------------------------------------------