From 5227547448f1b9c75b9fe03289c6cce21aec6033 Mon Sep 17 00:00:00 2001 From: Marcos Yacob Date: Mon, 13 Sep 2021 15:34:50 -0300 Subject: [PATCH] Add envoy with federation example Signed-off-by: Marcos Yacob --- examples/envoy-federated/.gitignore | 3 + examples/envoy-federated/1-start-services.sh | 19 ++ .../envoy-federated/2-start-spire-agents.sh | 36 +++ .../3-create-registration-entries.sh | 55 ++++ examples/envoy-federated/README.md | 85 ++++++ examples/envoy-federated/build.sh | 11 + examples/envoy-federated/docker-compose.yml | 39 +++ .../envoy-federated/docker/echo/Dockerfile | 14 + .../docker/echo/conf/agent.crt.pem | 10 + .../docker/echo/conf/agent.key.pem | 13 + .../docker/echo/conf/envoy.yaml | 154 +++++++++++ .../docker/echo/conf/spire-agent.conf | 28 ++ .../docker/spire-server-domain/Dockerfile | 7 + .../spire-server-domain/conf/agent-cacert.pem | 9 + .../conf/spire-server.conf | 47 ++++ .../docker/spire-server-federated/Dockerfile | 7 + .../conf/agent-cacert.pem | 9 + .../conf/spire-server.conf | 47 ++++ .../docker/web-domain/Dockerfile | 15 ++ .../docker/web-domain/conf/agent.crt.pem | 10 + .../docker/web-domain/conf/agent.key.pem | 13 + .../docker/web-domain/conf/envoy.yaml | 161 ++++++++++++ .../docker/web-domain/conf/spire-agent.conf | 28 ++ .../docker/web-federated/Dockerfile | 15 ++ .../docker/web-federated/conf/agent.crt.pem | 10 + .../docker/web-federated/conf/agent.key.pem | 13 + .../docker/web-federated/conf/envoy.yaml | 161 ++++++++++++ .../web-federated/conf/spire-agent.conf | 28 ++ examples/envoy-federated/gencerts.go | 106 ++++++++ .../images/envoy-federated.png | Bin 0 -> 41484 bytes .../envoy-federated/src/echo-server/go.mod | 3 + .../envoy-federated/src/echo-server/main.go | 66 +++++ .../envoy-federated/src/web-server/css.go | 102 ++++++++ .../envoy-federated/src/web-server/go.mod | 5 + .../envoy-federated/src/web-server/go.sum | 2 + .../envoy-federated/src/web-server/main.go | 245 ++++++++++++++++++ examples/envoy-federated/test.sh | 127 +++++++++ examples/envoy-federated/wait-for-envoy.sh | 27 ++ 38 files changed, 1730 insertions(+) create mode 100644 examples/envoy-federated/.gitignore create mode 100755 examples/envoy-federated/1-start-services.sh create mode 100755 examples/envoy-federated/2-start-spire-agents.sh create mode 100755 examples/envoy-federated/3-create-registration-entries.sh create mode 100644 examples/envoy-federated/README.md create mode 100755 examples/envoy-federated/build.sh create mode 100644 examples/envoy-federated/docker-compose.yml create mode 100644 examples/envoy-federated/docker/echo/Dockerfile create mode 100644 examples/envoy-federated/docker/echo/conf/agent.crt.pem create mode 100644 examples/envoy-federated/docker/echo/conf/agent.key.pem create mode 100644 examples/envoy-federated/docker/echo/conf/envoy.yaml create mode 100644 examples/envoy-federated/docker/echo/conf/spire-agent.conf create mode 100644 examples/envoy-federated/docker/spire-server-domain/Dockerfile create mode 100644 examples/envoy-federated/docker/spire-server-domain/conf/agent-cacert.pem create mode 100644 examples/envoy-federated/docker/spire-server-domain/conf/spire-server.conf create mode 100644 examples/envoy-federated/docker/spire-server-federated/Dockerfile create mode 100644 examples/envoy-federated/docker/spire-server-federated/conf/agent-cacert.pem create mode 100644 examples/envoy-federated/docker/spire-server-federated/conf/spire-server.conf create mode 100644 examples/envoy-federated/docker/web-domain/Dockerfile create mode 100644 examples/envoy-federated/docker/web-domain/conf/agent.crt.pem create mode 100644 examples/envoy-federated/docker/web-domain/conf/agent.key.pem create mode 100644 examples/envoy-federated/docker/web-domain/conf/envoy.yaml create mode 100644 examples/envoy-federated/docker/web-domain/conf/spire-agent.conf create mode 100644 examples/envoy-federated/docker/web-federated/Dockerfile create mode 100644 examples/envoy-federated/docker/web-federated/conf/agent.crt.pem create mode 100644 examples/envoy-federated/docker/web-federated/conf/agent.key.pem create mode 100644 examples/envoy-federated/docker/web-federated/conf/envoy.yaml create mode 100644 examples/envoy-federated/docker/web-federated/conf/spire-agent.conf create mode 100644 examples/envoy-federated/gencerts.go create mode 100644 examples/envoy-federated/images/envoy-federated.png create mode 100644 examples/envoy-federated/src/echo-server/go.mod create mode 100644 examples/envoy-federated/src/echo-server/main.go create mode 100644 examples/envoy-federated/src/web-server/css.go create mode 100644 examples/envoy-federated/src/web-server/go.mod create mode 100644 examples/envoy-federated/src/web-server/go.sum create mode 100644 examples/envoy-federated/src/web-server/main.go create mode 100755 examples/envoy-federated/test.sh create mode 100644 examples/envoy-federated/wait-for-envoy.sh diff --git a/examples/envoy-federated/.gitignore b/examples/envoy-federated/.gitignore new file mode 100644 index 0000000..9ba5b5b --- /dev/null +++ b/examples/envoy-federated/.gitignore @@ -0,0 +1,3 @@ +docker/web-domain/web-server +docker/web-federated/web-server +docker/echo/echo-server diff --git a/examples/envoy-federated/1-start-services.sh b/examples/envoy-federated/1-start-services.sh new file mode 100755 index 0000000..9641aa4 --- /dev/null +++ b/examples/envoy-federated/1-start-services.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +bb=$(tput bold) +nn=$(tput sgr0) + +# Start up the web server +echo "${bb}Starting web server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -d web-domain web-server -log /tmp/web-server.log + +echo "${bb}Starting federated web server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -d web-federated web-server -log /tmp/web-server.log + +# Start up the echo server +echo "${bb}Starting echo server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -d echo echo-server -log /tmp/echo-server.log diff --git a/examples/envoy-federated/2-start-spire-agents.sh b/examples/envoy-federated/2-start-spire-agents.sh new file mode 100755 index 0000000..cfeca06 --- /dev/null +++ b/examples/envoy-federated/2-start-spire-agents.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +bb=$(tput bold) +nn=$(tput sgr0) + +# Bootstrap trust to the SPIRE server for each agent by copying over the +# trust bundle into each agent container. Alternatively, an upstream CA could +# be configured on the SPIRE server and each agent provided with the upstream +# trust bundle (see UpstreamAuthority under +# https://github.com/spiffe/spire/blob/master/doc/spire_server.md#plugin-types) +echo "${bb}Bootstrapping trust between SPIRE agents and federated SPIRE server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -T spire-server-federated bin/spire-server bundle show | + docker-compose -f "${DIR}"/docker-compose.yml exec -T web-federated tee conf/agent/bootstrap.crt > /dev/null + +echo "${bb}Bootstrapping trust between SPIRE agents and SPIRE server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -T spire-server-domain bin/spire-server bundle show | + docker-compose -f "${DIR}"/docker-compose.yml exec -T echo tee conf/agent/bootstrap.crt > /dev/null + +docker-compose -f "${DIR}"/docker-compose.yml exec -T spire-server-domain bin/spire-server bundle show | + docker-compose -f "${DIR}"/docker-compose.yml exec -T web-domain tee conf/agent/bootstrap.crt > /dev/null + +# Start up the web server SPIRE agent. +echo "${bb}Starting web server SPIRE agent...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -d web-domain bin/spire-agent run + +# Start up the web-federated server SPIRE agent. +echo "${bb}Starting web-federated server SPIRE agent...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -d web-federated bin/spire-agent run + +# Start up the echo server SPIRE agent. +echo "${bb}Starting echo server SPIRE agent...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec -d echo bin/spire-agent run diff --git a/examples/envoy-federated/3-create-registration-entries.sh b/examples/envoy-federated/3-create-registration-entries.sh new file mode 100755 index 0000000..e93b109 --- /dev/null +++ b/examples/envoy-federated/3-create-registration-entries.sh @@ -0,0 +1,55 @@ +#/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +bb=$(tput bold) +nn=$(tput sgr0) + +fingerprint() { + # calculate the SHA1 digest of the DER bytes of the certificate using the + # "coreutils" output format (`-r`) to provide uniform output from + # `openssl sha1` on macOS and linux. + cat $1 | openssl x509 -outform DER | openssl sha1 -r | awk '{print $1}' +} + +WEB_FEDERATED_AGENT_FINGERPRINT=$(fingerprint "${DIR}"/docker/web-federated/conf/agent.crt.pem) +WEB_AGENT_FINGERPRINT=$(fingerprint "${DIR}"/docker/web-domain/conf/agent.crt.pem) +ECHO_AGENT_FINGERPRINT=$(fingerprint "${DIR}"/docker/echo/conf/agent.crt.pem) + +echo "${bb}Storing bundles...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-federated bin/spire-server bundle show | + docker-compose -f "${DIR}"/docker-compose.yml exec -T spire-server-domain tee /opt/spire/federatedServerBundle.pem > /dev/null +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-domain bin/spire-server bundle show | + docker-compose -f "${DIR}"/docker-compose.yml exec -T spire-server-federated tee /opt/spire/domainServerBundle.pem > /dev/null + +echo "${bb}Creating federated bundle: domain...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-federated bin/spire-server bundle set \ + -id spiffe://domain.test \ + -path /opt/spire/domainServerBundle.pem + +echo "${bb}Creating federated bundle: federated...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-domain bin/spire-server bundle set \ + -id spiffe://federated.test \ + -path /opt/spire/federatedServerBundle.pem + +echo "${bb}Creating registration entry for the federated web server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-federated bin/spire-server entry create \ + -parentID spiffe://federated.test/spire/agent/x509pop/${WEB_FEDERATED_AGENT_FINGERPRINT} \ + -spiffeID spiffe://federated.test/web-server \ + -selector unix:user:envoy \ + -federatesWith spiffe://domain.test + +echo "${bb}Creating registration entry for the domain server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-domain bin/spire-server entry create \ + -parentID spiffe://domain.test/spire/agent/x509pop/${ECHO_AGENT_FINGERPRINT} \ + -spiffeID spiffe://domain.test/echo-server \ + -selector unix:user:envoy \ + -federatesWith spiffe://federated.test + +echo "${bb}Creating registration entry for the web server...${nn}" +docker-compose -f "${DIR}"/docker-compose.yml exec spire-server-domain bin/spire-server entry create \ + -parentID spiffe://domain.test/spire/agent/x509pop/${WEB_AGENT_FINGERPRINT} \ + -spiffeID spiffe://domain.test/web-server \ + -selector unix:user:envoy diff --git a/examples/envoy-federated/README.md b/examples/envoy-federated/README.md new file mode 100644 index 0000000..2b5c43e --- /dev/null +++ b/examples/envoy-federated/README.md @@ -0,0 +1,85 @@ +# Envoy 1.19.0 SDS Example with SPIRE 1.0.2 + +## Requirements + +- Go 1.14 +- docker-compose + +## Diagram + +![diagram](./images/envoy-federated.png) + +## Build + +``` +$ ./build.sh +``` + +## Run + +``` +$ docker-compose up -d +``` + +## Start Web and Echo Servers + +``` +$ ./1-start-services.sh +``` + +## Start SPIRE Agents + +``` +$ ./2-start-spire-agents.sh +``` + +## Create Workload Registration Entries + +``` +$ ./3-create-registration-entries.sh +``` + +## Interact with Web Server + +Open up a browser to http://localhost:8080 to test out: + +- Direct connection between Web and Echo servers +- mTLS connection between Web and Echo servers via Envoy +- TLS connection between Web and Echo servers via Envoy + +## Interact with Federated Web Server + +Open up a browser to http://localhost:8088 to test out: + +- Direct connection between Web and Echo servers +- mTLS connection between Web and Echo servers via Envoy +- TLS connection between Web and Echo servers via Envoy +**NOTE** It may take a 30 seconds or so for Envoy to reconnect to upstream +after the registration entries are created. + +## Clean up + +``` +$ docker-compose down +``` + +## Miscellaneous + +### Regenerating Agent Certificates for X509PoP attestation + +If you want to regenerate the agent certificates, you use +the gencerts.go Go script, supplying the names of all the services. + +``` +$ go run gencerts.go web echo +74613bb92549782b5d01ad5e6e93cedd78841683 web +14e6255ca2a1f32198656fc400f82d242ccf44f2 echo +``` + +This script creates a new CA keypair that is used to sign the service +certificates. It replaces the agent keypairs in the docker +configurations for each service, as well as the agent cacert in the +spire-server configuration. + +After running, you must rerun `build.sh` to rebuild the containers with the +changes. diff --git a/examples/envoy-federated/build.sh b/examples/envoy-federated/build.sh new file mode 100755 index 0000000..993e2f9 --- /dev/null +++ b/examples/envoy-federated/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +(cd "${DIR}"/src/web-server && GOOS=linux go build -v -o $DIR/docker/web-domain/web-server) +(cd "${DIR}"/src/web-server && GOOS=linux go build -v -o $DIR/docker/web-federated/web-server) +(cd "${DIR}"/src/echo-server && GOOS=linux go build -v -o $DIR/docker/echo/echo-server) + +docker-compose -f "${DIR}"/docker-compose.yml build diff --git a/examples/envoy-federated/docker-compose.yml b/examples/envoy-federated/docker-compose.yml new file mode 100644 index 0000000..fc1427f --- /dev/null +++ b/examples/envoy-federated/docker-compose.yml @@ -0,0 +1,39 @@ +version: '3' +services: + spire-server-federated: + build: ./docker/spire-server-federated + hostname: spire-server-federated + tty: true + privileged: true + spire-server-domain: + build: ./docker/spire-server-domain + hostname: spire-server-domain + tty: true + privileged: true + echo: + build: ./docker/echo + hostname: echo + tty: true + privileged: true + links: + - spire-server-domain + web-domain: + build: ./docker/web-domain + hostname: web-domain + tty: true + privileged: true + links: + - spire-server-domain + - echo + ports: + - 8080:8080 + web-federated: + build: ./docker/web-federated + hostname: web-federated + tty: true + privileged: true + links: + - spire-server-federated + - echo + ports: + - 8088:8080 diff --git a/examples/envoy-federated/docker/echo/Dockerfile b/examples/envoy-federated/docker/echo/Dockerfile new file mode 100644 index 0000000..f4247c9 --- /dev/null +++ b/examples/envoy-federated/docker/echo/Dockerfile @@ -0,0 +1,14 @@ +FROM gcr.io/spiffe-io/spire-agent:1.0.2 as spire + +FROM envoyproxy/envoy-alpine:v1.19.0 +RUN mkdir -p /opt/spire/conf/agent +RUN mkdir -p /opt/spire/data/agent +COPY --from=spire /opt/spire/bin/spire-agent /opt/spire/bin/spire-agent +COPY conf/envoy.yaml /etc/envoy/envoy.yaml +COPY conf/spire-agent.conf /opt/spire/conf/agent/agent.conf +COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem +COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem +COPY echo-server /usr/local/bin/echo-server + +WORKDIR /opt/spire +CMD /usr/local/bin/envoy -l debug -c /etc/envoy/envoy.yaml --log-path /tmp/envoy.log diff --git a/examples/envoy-federated/docker/echo/conf/agent.crt.pem b/examples/envoy-federated/docker/echo/conf/agent.crt.pem new file mode 100644 index 0000000..ae7d3f9 --- /dev/null +++ b/examples/envoy-federated/docker/echo/conf/agent.crt.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBZjCB8aADAgECAgEBMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAxMDEwMDAw +MDBaGA85OTk5MTIzMTIzNTk1OVowIDEeMBwGA1UEAxMVRWNobyBYNTA5UG9QIElk +ZW50aXR5MHwwDQYJKoZIhvcNAQEBBQADawAwaAJhAMyss5CqRpvL6jzXDggSV/+A +NBaYhAyWgRRkRKKcKE+1GfXutd4ewrrsC5DRncSH1HxTnwO9H7YmobKu7kVUZegB +EfeydefNQqvBnHqMooUWUgfiwAN01CKOxEURn+dtzQIDAQABoxIwEDAOBgNVHQ8B +Af8EBAMCB4AwDQYJKoZIhvcNAQELBQADYQAj+ukSTrT5aJFOtDkpA4dHGOgJNFQ+ +Lf03GEBuvNCzM/XKaxr+/j0PbtcE+SwtOn/rTV1aGcKHT+jOSQuLUW7wguB7Scvd +p/ecQJ2D1C98EEiJ0zEDuuCEXQhPmFjSOkk= +-----END CERTIFICATE----- diff --git a/examples/envoy-federated/docker/echo/conf/agent.key.pem b/examples/envoy-federated/docker/echo/conf/agent.key.pem new file mode 100644 index 0000000..124fcfb --- /dev/null +++ b/examples/envoy-federated/docker/echo/conf/agent.key.pem @@ -0,0 +1,13 @@ +-----BEGIN PRIVATE KEY----- +MIIB5gIBADANBgkqhkiG9w0BAQEFAASCAdAwggHMAgEAAmEAzKyzkKpGm8vqPNcO +CBJX/4A0FpiEDJaBFGREopwoT7UZ9e613h7CuuwLkNGdxIfUfFOfA70ftiahsq7u +RVRl6AER97J1581Cq8GceoyihRZSB+LAA3TUIo7ERRGf523NAgMBAAECYGKA94a3 +PHY8xIX1Zk5iLbHMzMTXMC4yvPrIavPrvcbmvRDMiwDUwxldAw/2ojx0teVHsdxM +AJmQ7WIfEgseRdZIalQREOscdapzOM/UA3VIJwZkaAWbnTt7A6E52xavrQIxAPMv +eqvzIKX+GTCXWYVH5/ITebDVByxkS7xXk/1T66W7MqtDbI+RV083KkxcqSnm9wIx +ANd1uA3AjsrMm2kyA9BR1LqSF+2OioNc8ivuXofpi2HfAwf03JAxi2TNe8KfjNdM +WwIxAMF5SLN8nsDxQ8Ew6HvtFU+2xt403Zwnv0l8s7ScC6KzKECxR7GBBGF1liVi +owM/SQIxALGTpjQ/ZfCjHdtfi2EgcJGsYug5uNN3wWiPt32Q8H2jjh36o+Z7zwUv +jjbjxPKVTQIxANEUsYQBcq1dvpwfmIzy73vrZTIdCyCzgm12KIQHxKvaRiGLFcxD +3iNplWkDRezhLg== +-----END PRIVATE KEY----- diff --git a/examples/envoy-federated/docker/echo/conf/envoy.yaml b/examples/envoy-federated/docker/echo/conf/envoy.yaml new file mode 100644 index 0000000..2137ce0 --- /dev/null +++ b/examples/envoy-federated/docker/echo/conf/envoy.yaml @@ -0,0 +1,154 @@ +node: + id: "id_01" + cluster: "cluster_01" +static_resources: + listeners: + - name: listener-sds-mtls + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + common_http_protocol_options: + idle_timeout: 1s + forward_client_cert_details: sanitize_set + set_current_client_cert_details: + uri: true + codec_type: auto + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/tmp/envoy-access-8001.log" + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: echo_server + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificate_sds_secret_configs: + - name: "spiffe://domain.test/echo-server" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + combined_validation_context: + # validate the SPIFFE ID of incoming clients (optionally) + default_validation_context: + match_subject_alt_names: + safe_regex: + google_re2: + max_program_size: 100 + regex: "spiffe://(domain|federated)\\.test/web-server" + # obtain the trust bundle from SDS + validation_context_sds_secret_config: + name: "ALL" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + tls_params: + ecdh_curves: + - X25519:P-256:P-521:P-384 + - name: listener-sds-tls + address: + socket_address: + address: 0.0.0.0 + port_value: 8002 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + common_http_protocol_options: + idle_timeout: 1s + forward_client_cert_details: sanitize_set + set_current_client_cert_details: + uri: true + codec_type: auto + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/tmp/envoy-access-8002.log" + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: echo_server + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificate_sds_secret_configs: + - name: "spiffe://domain.test/echo-server" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + tls_params: + ecdh_curves: + - X25519:P-256:P-521:P-384 + + clusters: + - name: spire_agent + connect_timeout: 0.25s + http2_protocol_options: {} + load_assignment: + cluster_name: spire_agent + endpoints: + - lb_endpoints: + - endpoint: + address: + pipe: + path: /tmp/agent.sock + - name: echo_server + connect_timeout: 0.25s + type: strict_dns + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: echo_server + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8081 diff --git a/examples/envoy-federated/docker/echo/conf/spire-agent.conf b/examples/envoy-federated/docker/echo/conf/spire-agent.conf new file mode 100644 index 0000000..80f4c1d --- /dev/null +++ b/examples/envoy-federated/docker/echo/conf/spire-agent.conf @@ -0,0 +1,28 @@ +agent { + data_dir = "/opt/spire/data/agent" + log_level = "DEBUG" + log_file = "/opt/spire/agent.log" + server_address = "spire-server-domain" + server_port = "9081" + socket_path ="/tmp/agent.sock" + trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt" + trust_domain = "domain.test" +} + +plugins { + NodeAttestor "x509pop" { + plugin_data { + private_key_path = "/opt/spire/conf/agent/agent.key.pem" + certificate_path = "/opt/spire/conf/agent/agent.crt.pem" + } + } + KeyManager "disk" { + plugin_data { + directory = "/opt/spire/data/agent" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } +} diff --git a/examples/envoy-federated/docker/spire-server-domain/Dockerfile b/examples/envoy-federated/docker/spire-server-domain/Dockerfile new file mode 100644 index 0000000..2b78560 --- /dev/null +++ b/examples/envoy-federated/docker/spire-server-domain/Dockerfile @@ -0,0 +1,7 @@ +FROM gcr.io/spiffe-io/spire-server:1.0.2 + +# Override spire configurations +COPY conf/spire-server.conf /opt/spire/conf/server/server.conf +COPY conf/agent-cacert.pem /opt/spire/conf/server/agent-cacert.pem + +WORKDIR /opt/spire diff --git a/examples/envoy-federated/docker/spire-server-domain/conf/agent-cacert.pem b/examples/envoy-federated/docker/spire-server-domain/conf/agent-cacert.pem new file mode 100644 index 0000000..15185a5 --- /dev/null +++ b/examples/envoy-federated/docker/spire-server-domain/conf/agent-cacert.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBRzCB0qADAgECAgEBMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAxMDEwMDAw +MDBaGA85OTk5MTIzMTIzNTk1OVowADB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQC0 +8d4KBYOCzOUR2mGBA7/HDI+cprZkABrCtjv0HMPR5FeNo1XuLzHoKYB5saDqNWOW +I7KzvDOIvwXUEnPsHNe8CX41NrjocMc3BvqIMTAqUTaQwN/2bPc3IqQ1WEywGzEC +AwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANhAHAKDl1a +u0pP0doHcYJcyKfYFbUPaPUvPZHfVQ2J7e6c1vrKVr2hAFQ2jFRKc6v3QEvGF7V4 +1PfudcS9A/GnXvU8uGjWiaXsz60OA9BskRWE7Gy1ZAc/70TvKp4/jKujrA== +-----END CERTIFICATE----- diff --git a/examples/envoy-federated/docker/spire-server-domain/conf/spire-server.conf b/examples/envoy-federated/docker/spire-server-domain/conf/spire-server.conf new file mode 100644 index 0000000..951897e --- /dev/null +++ b/examples/envoy-federated/docker/spire-server-domain/conf/spire-server.conf @@ -0,0 +1,47 @@ +server { + bind_address = "0.0.0.0" + bind_port = "9081" + socket_path = "/tmp/spire-server/private/api.sock" + trust_domain = "domain.test" + data_dir = "/opt/spire/data/server" + log_level = "DEBUG" + /* log_file = "/opt/spire/server.log" */ + default_svid_ttl = "1h" + ca_subject = { + country = ["US"], + organization = ["SPIFFE"], + common_name = "", + } + federation { + bundle_endpoint { + port = 8443 + } + + federates_with "federated.test" { + bundle_endpoint_url = "https://spire-server-federated:8443" + bundle_endpoint_profile "https_spiffe" { + endpoint_spiffe_id = "spiffe://federated.test/spire/server" + } + } + } +} + +plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/opt/spire/data/server/datastore.sqlite3" + } + } + NodeAttestor "x509pop" { + plugin_data { + ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem" + } + } + + KeyManager "disk" { + plugin_data = { + keys_path = "/opt/spire/data/server/keys.json" + } + } +} diff --git a/examples/envoy-federated/docker/spire-server-federated/Dockerfile b/examples/envoy-federated/docker/spire-server-federated/Dockerfile new file mode 100644 index 0000000..2b78560 --- /dev/null +++ b/examples/envoy-federated/docker/spire-server-federated/Dockerfile @@ -0,0 +1,7 @@ +FROM gcr.io/spiffe-io/spire-server:1.0.2 + +# Override spire configurations +COPY conf/spire-server.conf /opt/spire/conf/server/server.conf +COPY conf/agent-cacert.pem /opt/spire/conf/server/agent-cacert.pem + +WORKDIR /opt/spire diff --git a/examples/envoy-federated/docker/spire-server-federated/conf/agent-cacert.pem b/examples/envoy-federated/docker/spire-server-federated/conf/agent-cacert.pem new file mode 100644 index 0000000..15185a5 --- /dev/null +++ b/examples/envoy-federated/docker/spire-server-federated/conf/agent-cacert.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBRzCB0qADAgECAgEBMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAxMDEwMDAw +MDBaGA85OTk5MTIzMTIzNTk1OVowADB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQC0 +8d4KBYOCzOUR2mGBA7/HDI+cprZkABrCtjv0HMPR5FeNo1XuLzHoKYB5saDqNWOW +I7KzvDOIvwXUEnPsHNe8CX41NrjocMc3BvqIMTAqUTaQwN/2bPc3IqQ1WEywGzEC +AwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANhAHAKDl1a +u0pP0doHcYJcyKfYFbUPaPUvPZHfVQ2J7e6c1vrKVr2hAFQ2jFRKc6v3QEvGF7V4 +1PfudcS9A/GnXvU8uGjWiaXsz60OA9BskRWE7Gy1ZAc/70TvKp4/jKujrA== +-----END CERTIFICATE----- diff --git a/examples/envoy-federated/docker/spire-server-federated/conf/spire-server.conf b/examples/envoy-federated/docker/spire-server-federated/conf/spire-server.conf new file mode 100644 index 0000000..c415e3f --- /dev/null +++ b/examples/envoy-federated/docker/spire-server-federated/conf/spire-server.conf @@ -0,0 +1,47 @@ +server { + bind_address = "0.0.0.0" + bind_port = "9081" + socket_path = "/tmp/spire-server/private/api.sock" + trust_domain = "federated.test" + data_dir = "/opt/spire/data/server" + log_level = "DEBUG" + /* log_file = "/opt/spire/server.log" */ + default_svid_ttl = "1h" + ca_subject = { + country = ["US"], + organization = ["SPIFFE"], + common_name = "", + } + federation { + bundle_endpoint { + port = 8443 + } + + federates_with "domain.test" { + bundle_endpoint_url = "https://spire-server-domain:8443" + bundle_endpoint_profile "https_spiffe" { + endpoint_spiffe_id = "spiffe://domain.test/spire/server" + } + } + } +} + +plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/opt/spire/data/server/datastore.sqlite3" + } + } + NodeAttestor "x509pop" { + plugin_data { + ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem" + } + } + + KeyManager "disk" { + plugin_data = { + keys_path = "/opt/spire/data/server/keys.json" + } + } +} diff --git a/examples/envoy-federated/docker/web-domain/Dockerfile b/examples/envoy-federated/docker/web-domain/Dockerfile new file mode 100644 index 0000000..858ef6a --- /dev/null +++ b/examples/envoy-federated/docker/web-domain/Dockerfile @@ -0,0 +1,15 @@ +FROM gcr.io/spiffe-io/spire-agent:1.0.2 as spire + +FROM envoyproxy/envoy-alpine:v1.19.0 +RUN mkdir -p /opt/spire/conf/agent +RUN mkdir -p /opt/spire/data/agent +COPY --from=spire /opt/spire/bin/spire-agent /opt/spire/bin/spire-agent +COPY conf/envoy.yaml /etc/envoy/envoy.yaml +COPY conf/spire-agent.conf /opt/spire/conf/agent/agent.conf +COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem +COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem +COPY web-server /usr/local/bin/web-server + +WORKDIR /opt/spire +CMD /usr/local/bin/envoy -l debug -c /etc/envoy/envoy.yaml --log-path /tmp/envoy.log + diff --git a/examples/envoy-federated/docker/web-domain/conf/agent.crt.pem b/examples/envoy-federated/docker/web-domain/conf/agent.crt.pem new file mode 100644 index 0000000..b99e1b5 --- /dev/null +++ b/examples/envoy-federated/docker/web-domain/conf/agent.crt.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBZTCB8KADAgECAgEBMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAxMDEwMDAw +MDBaGA85OTk5MTIzMTIzNTk1OVowHzEdMBsGA1UEAxMUV2ViIFg1MDlQb3AgSWRl +bnRpdHkwfDANBgkqhkiG9w0BAQEFAANrADBoAmEAs1NjsJBicAi16HOTb7Qs0TbE +dGkknN+AeaeKyCBD1AeWszGIxKY0BGK35M7yT8xywfdA4iPVyoxniTRs2dbipJos +QE6MnC63lyroyP4fDT/IOhID19IkeNXp2oYt7JMLAgMBAAGjEjAQMA4GA1UdDwEB +/wQEAwIHgDANBgkqhkiG9w0BAQsFAANhAJtPzDYWP+ZqA+B0MNTb4yuhgm/8R5NL +cXO3+RHuJx2F6URezNRclubxZwarILTWgfeD/kwhSv+//4BfCIseSURNq/15/DnS +ns5ZoezRYSQhtYzQYsSKGeZWlrjLcYOj9Q== +-----END CERTIFICATE----- diff --git a/examples/envoy-federated/docker/web-domain/conf/agent.key.pem b/examples/envoy-federated/docker/web-domain/conf/agent.key.pem new file mode 100644 index 0000000..7f67a9f --- /dev/null +++ b/examples/envoy-federated/docker/web-domain/conf/agent.key.pem @@ -0,0 +1,13 @@ +-----BEGIN PRIVATE KEY----- +MIIB5QIBADANBgkqhkiG9w0BAQEFAASCAc8wggHLAgEAAmEAs1NjsJBicAi16HOT +b7Qs0TbEdGkknN+AeaeKyCBD1AeWszGIxKY0BGK35M7yT8xywfdA4iPVyoxniTRs +2dbipJosQE6MnC63lyroyP4fDT/IOhID19IkeNXp2oYt7JMLAgMBAAECYGQJkQNe +iIuXUGNslkMqcBYGR3HpddNNm45CONmLpksSMK+mYnXRghMwPwYVrHd3xV3mcURQ +9khZeYa/Xj9zB/FLr7jTiFo06BsF/Xi+USJyk9TbRSOMcLB70EnHMJBSgQIxANAA +Ul877XbRzW1FV2z0fUtQlzVjKDhsshAd4Bc+4eDol2jcTxmfwE/+S/ICf1lOrQIx +ANy1D5kvbHaS0QE1EhKTP1avajaz0ITwf/t398kxHAFnulgbMh5P06fqGOIwJLg3 +lwIxAKq2WOTIrpDCiLOSIbrOvra9ohyzESOeMlZ/7peliIWq3/m0N0d0kcYVlAdA +fXyy0QIwfPNo3FHT9yFRgHx6OdfxQPNYj5WnlMvPuR+HQfkyoL0H63+iFTUeX+8R +nWSiNUULAjEApWCigs9ycON1fOcwUdXgqm7PsfbtBslq512qA4AjfRnkwmwjeSYf +DZXZztuqcBW/ +-----END PRIVATE KEY----- diff --git a/examples/envoy-federated/docker/web-domain/conf/envoy.yaml b/examples/envoy-federated/docker/web-domain/conf/envoy.yaml new file mode 100644 index 0000000..e451824 --- /dev/null +++ b/examples/envoy-federated/docker/web-domain/conf/envoy.yaml @@ -0,0 +1,161 @@ +node: + id: "id_02" + cluster: "cluster_02" +static_resources: + listeners: + - name: web_to_echo_mtls_listener + address: + socket_address: + address: 127.0.0.1 + port_value: 8001 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + common_http_protocol_options: + idle_timeout: 1s + codec_type: auto + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/tmp/envoy-to-envoy-mtls.log" + stat_prefix: ingress_http + route_config: + name: service_route + virtual_hosts: + - name: web_to_echo_mtls + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: web_to_echo_mtls + http_filters: + - name: envoy.filters.http.router + - name: web_to_echo_tls_listener + address: + socket_address: + address: 127.0.0.1 + port_value: 8002 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + common_http_protocol_options: + idle_timeout: 1s + codec_type: auto + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/tmp/envoy-to-envoy-tls.log" + stat_prefix: ingress_http + route_config: + name: service_route + virtual_hosts: + - name: web_to_echo_tls + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: web_to_echo_tls + http_filters: + - name: envoy.filters.http.router + clusters: + - name: spire_agent + connect_timeout: 0.25s + http2_protocol_options: {} + load_assignment: + cluster_name: spire_agent + endpoints: + - lb_endpoints: + - endpoint: + address: + pipe: + path: /tmp/agent.sock + - name: web_to_echo_mtls + connect_timeout: 0.25s + type: strict_dns + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: web_to_echo_mtls + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: echo + port_value: 8001 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_certificate_sds_secret_configs: + - name: "spiffe://domain.test/web-server" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + combined_validation_context: + # validate the SPIFFE ID of the server (recommended) + default_validation_context: + match_subject_alt_names: + exact: "spiffe://domain.test/echo-server" + validation_context_sds_secret_config: + name: "ALL" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + tls_params: + ecdh_curves: + - X25519:P-256:P-521:P-384 + - name: web_to_echo_tls + connect_timeout: 0.25s + type: strict_dns + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: web_to_echo_tls + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: echo + port_value: 8002 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + combined_validation_context: + # validate the SPIFFE ID of the server (recommended) + default_validation_context: + match_subject_alt_names: + exact: "spiffe://domain.test/echo-server" + validation_context_sds_secret_config: + name: "ALL" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + tls_params: + ecdh_curves: + - X25519:P-256:P-521:P-384 diff --git a/examples/envoy-federated/docker/web-domain/conf/spire-agent.conf b/examples/envoy-federated/docker/web-domain/conf/spire-agent.conf new file mode 100644 index 0000000..80f4c1d --- /dev/null +++ b/examples/envoy-federated/docker/web-domain/conf/spire-agent.conf @@ -0,0 +1,28 @@ +agent { + data_dir = "/opt/spire/data/agent" + log_level = "DEBUG" + log_file = "/opt/spire/agent.log" + server_address = "spire-server-domain" + server_port = "9081" + socket_path ="/tmp/agent.sock" + trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt" + trust_domain = "domain.test" +} + +plugins { + NodeAttestor "x509pop" { + plugin_data { + private_key_path = "/opt/spire/conf/agent/agent.key.pem" + certificate_path = "/opt/spire/conf/agent/agent.crt.pem" + } + } + KeyManager "disk" { + plugin_data { + directory = "/opt/spire/data/agent" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } +} diff --git a/examples/envoy-federated/docker/web-federated/Dockerfile b/examples/envoy-federated/docker/web-federated/Dockerfile new file mode 100644 index 0000000..858ef6a --- /dev/null +++ b/examples/envoy-federated/docker/web-federated/Dockerfile @@ -0,0 +1,15 @@ +FROM gcr.io/spiffe-io/spire-agent:1.0.2 as spire + +FROM envoyproxy/envoy-alpine:v1.19.0 +RUN mkdir -p /opt/spire/conf/agent +RUN mkdir -p /opt/spire/data/agent +COPY --from=spire /opt/spire/bin/spire-agent /opt/spire/bin/spire-agent +COPY conf/envoy.yaml /etc/envoy/envoy.yaml +COPY conf/spire-agent.conf /opt/spire/conf/agent/agent.conf +COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem +COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem +COPY web-server /usr/local/bin/web-server + +WORKDIR /opt/spire +CMD /usr/local/bin/envoy -l debug -c /etc/envoy/envoy.yaml --log-path /tmp/envoy.log + diff --git a/examples/envoy-federated/docker/web-federated/conf/agent.crt.pem b/examples/envoy-federated/docker/web-federated/conf/agent.crt.pem new file mode 100644 index 0000000..b99e1b5 --- /dev/null +++ b/examples/envoy-federated/docker/web-federated/conf/agent.crt.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBZTCB8KADAgECAgEBMA0GCSqGSIb3DQEBCwUAMAAwIhgPMDAwMTAxMDEwMDAw +MDBaGA85OTk5MTIzMTIzNTk1OVowHzEdMBsGA1UEAxMUV2ViIFg1MDlQb3AgSWRl +bnRpdHkwfDANBgkqhkiG9w0BAQEFAANrADBoAmEAs1NjsJBicAi16HOTb7Qs0TbE +dGkknN+AeaeKyCBD1AeWszGIxKY0BGK35M7yT8xywfdA4iPVyoxniTRs2dbipJos +QE6MnC63lyroyP4fDT/IOhID19IkeNXp2oYt7JMLAgMBAAGjEjAQMA4GA1UdDwEB +/wQEAwIHgDANBgkqhkiG9w0BAQsFAANhAJtPzDYWP+ZqA+B0MNTb4yuhgm/8R5NL +cXO3+RHuJx2F6URezNRclubxZwarILTWgfeD/kwhSv+//4BfCIseSURNq/15/DnS +ns5ZoezRYSQhtYzQYsSKGeZWlrjLcYOj9Q== +-----END CERTIFICATE----- diff --git a/examples/envoy-federated/docker/web-federated/conf/agent.key.pem b/examples/envoy-federated/docker/web-federated/conf/agent.key.pem new file mode 100644 index 0000000..7f67a9f --- /dev/null +++ b/examples/envoy-federated/docker/web-federated/conf/agent.key.pem @@ -0,0 +1,13 @@ +-----BEGIN PRIVATE KEY----- +MIIB5QIBADANBgkqhkiG9w0BAQEFAASCAc8wggHLAgEAAmEAs1NjsJBicAi16HOT +b7Qs0TbEdGkknN+AeaeKyCBD1AeWszGIxKY0BGK35M7yT8xywfdA4iPVyoxniTRs +2dbipJosQE6MnC63lyroyP4fDT/IOhID19IkeNXp2oYt7JMLAgMBAAECYGQJkQNe +iIuXUGNslkMqcBYGR3HpddNNm45CONmLpksSMK+mYnXRghMwPwYVrHd3xV3mcURQ +9khZeYa/Xj9zB/FLr7jTiFo06BsF/Xi+USJyk9TbRSOMcLB70EnHMJBSgQIxANAA +Ul877XbRzW1FV2z0fUtQlzVjKDhsshAd4Bc+4eDol2jcTxmfwE/+S/ICf1lOrQIx +ANy1D5kvbHaS0QE1EhKTP1avajaz0ITwf/t398kxHAFnulgbMh5P06fqGOIwJLg3 +lwIxAKq2WOTIrpDCiLOSIbrOvra9ohyzESOeMlZ/7peliIWq3/m0N0d0kcYVlAdA +fXyy0QIwfPNo3FHT9yFRgHx6OdfxQPNYj5WnlMvPuR+HQfkyoL0H63+iFTUeX+8R +nWSiNUULAjEApWCigs9ycON1fOcwUdXgqm7PsfbtBslq512qA4AjfRnkwmwjeSYf +DZXZztuqcBW/ +-----END PRIVATE KEY----- diff --git a/examples/envoy-federated/docker/web-federated/conf/envoy.yaml b/examples/envoy-federated/docker/web-federated/conf/envoy.yaml new file mode 100644 index 0000000..ad6c894 --- /dev/null +++ b/examples/envoy-federated/docker/web-federated/conf/envoy.yaml @@ -0,0 +1,161 @@ +node: + id: "id_02" + cluster: "cluster_02" +static_resources: + listeners: + - name: web_to_echo_mtls_listener + address: + socket_address: + address: 127.0.0.1 + port_value: 8001 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + common_http_protocol_options: + idle_timeout: 1s + codec_type: auto + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/tmp/envoy-to-envoy-mtls.log" + stat_prefix: ingress_http + route_config: + name: service_route + virtual_hosts: + - name: web_to_echo_mtls + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: web_to_echo_mtls + http_filters: + - name: envoy.filters.http.router + - name: web_to_echo_tls_listener + address: + socket_address: + address: 127.0.0.1 + port_value: 8002 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + common_http_protocol_options: + idle_timeout: 1s + codec_type: auto + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/tmp/envoy-to-envoy-tls.log" + stat_prefix: ingress_http + route_config: + name: service_route + virtual_hosts: + - name: web_to_echo_tls + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: web_to_echo_tls + http_filters: + - name: envoy.filters.http.router + clusters: + - name: spire_agent + connect_timeout: 0.25s + http2_protocol_options: {} + load_assignment: + cluster_name: spire_agent + endpoints: + - lb_endpoints: + - endpoint: + address: + pipe: + path: /tmp/agent.sock + - name: web_to_echo_mtls + connect_timeout: 0.25s + type: strict_dns + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: web_to_echo_mtls + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: echo + port_value: 8001 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_certificate_sds_secret_configs: + - name: "spiffe://federated.test/web-server" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + combined_validation_context: + # validate the SPIFFE ID of the server (recommended) + default_validation_context: + match_subject_alt_names: + exact: "spiffe://domain.test/echo-server" + validation_context_sds_secret_config: + name: "ALL" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + tls_params: + ecdh_curves: + - X25519:P-256:P-521:P-384 + - name: web_to_echo_tls + connect_timeout: 0.25s + type: strict_dns + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: web_to_echo_tls + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: echo + port_value: 8002 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + combined_validation_context: + # validate the SPIFFE ID of the server (recommended) + default_validation_context: + match_subject_alt_names: + exact: "spiffe://domain.test/echo-server" + validation_context_sds_secret_config: + name: "ALL" + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: spire_agent + transport_api_version: V3 + resource_api_version: V3 + tls_params: + ecdh_curves: + - X25519:P-256:P-521:P-384 diff --git a/examples/envoy-federated/docker/web-federated/conf/spire-agent.conf b/examples/envoy-federated/docker/web-federated/conf/spire-agent.conf new file mode 100644 index 0000000..e20e5ef --- /dev/null +++ b/examples/envoy-federated/docker/web-federated/conf/spire-agent.conf @@ -0,0 +1,28 @@ +agent { + data_dir = "/opt/spire/data/agent" + log_level = "DEBUG" + log_file = "/opt/spire/agent.log" + server_address = "spire-server-federated" + server_port = "9081" + socket_path ="/tmp/agent.sock" + trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt" + trust_domain = "federated.test" +} + +plugins { + NodeAttestor "x509pop" { + plugin_data { + private_key_path = "/opt/spire/conf/agent/agent.key.pem" + certificate_path = "/opt/spire/conf/agent/agent.crt.pem" + } + } + KeyManager "disk" { + plugin_data { + directory = "/opt/spire/data/agent" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } +} diff --git a/examples/envoy-federated/gencerts.go b/examples/envoy-federated/gencerts.go new file mode 100644 index 0000000..1cf3e03 --- /dev/null +++ b/examples/envoy-federated/gencerts.go @@ -0,0 +1,106 @@ +package main + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "time" +) + +var ( + // The "never expires" timestamp from RFC5280. Probably not a good + // idea in practice. + neverExpires = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC) +) + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "usage: gencerts NAME1 [NAME2...]") + os.Exit(1) + } + + caKey := generateRSAKey() + caCert := createRootCertificate(caKey, &x509.Certificate{ + SerialNumber: big.NewInt(1), + BasicConstraintsValid: true, + IsCA: true, + NotAfter: neverExpires, + Subject: pkix.Name{CommonName: "agent CA"}, + }) + + writeCerts(filepath.Join("docker", "spire-server", "conf", "agent-cacert.pem"), caCert) + + for _, name := range os.Args[1:] { + agentKey := generateRSAKey() + agentCert := createCertificate(agentKey, &x509.Certificate{ + SerialNumber: big.NewInt(1), + KeyUsage: x509.KeyUsageDigitalSignature, + NotAfter: neverExpires, + Subject: pkix.Name{CommonName: fmt.Sprintf("%s agent", name)}, + }, caKey, caCert) + + dir := filepath.Join("docker", name, "conf") + writeKey(filepath.Join(dir, "agent.key.pem"), agentKey) + writeCerts(filepath.Join(dir, "agent.crt.pem"), agentCert) + + fingerprint := sha1.Sum(agentCert.Raw) + fmt.Printf("%x %s\n", fingerprint, name) + } +} + +func createRootCertificate(key *rsa.PrivateKey, tmpl *x509.Certificate) *x509.Certificate { + return createCertificate(key, tmpl, key, tmpl) +} + +func createCertificate(key *rsa.PrivateKey, tmpl *x509.Certificate, parentKey *rsa.PrivateKey, parent *x509.Certificate) *x509.Certificate { + certDER, err := x509.CreateCertificate(rand.Reader, tmpl, parent, &key.PublicKey, parentKey) + checkErr(err) + cert, err := x509.ParseCertificate(certDER) + checkErr(err) + return cert +} + +func generateRSAKey() *rsa.PrivateKey { + key, err := rsa.GenerateKey(rand.Reader, 768) + checkErr(err) + return key +} + +func writeKey(path string, key interface{}) { + keyBytes, err := x509.MarshalPKCS8PrivateKey(key) + checkErr(err) + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: keyBytes, + }) + err = ioutil.WriteFile(path, pemBytes, 0600) + checkErr(err) +} + +func writeCerts(path string, certs ...*x509.Certificate) { + data := new(bytes.Buffer) + for _, cert := range certs { + err := pem.Encode(data, &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }) + checkErr(err) + } + err := ioutil.WriteFile(path, data.Bytes(), 0644) + checkErr(err) +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/examples/envoy-federated/images/envoy-federated.png b/examples/envoy-federated/images/envoy-federated.png new file mode 100644 index 0000000000000000000000000000000000000000..2de2705cdbdf9afaa7a0d4b49d243736865f851a GIT binary patch literal 41484 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 ") + log.SetFlags(log.Ltime) + if *logFlag != "" { + logFile, err := os.OpenFile(*logFlag, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("unable to open log file: %v", err) + } + defer logFile.Close() + log.SetOutput(logFile) + } else { + log.SetOutput(os.Stdout) + } + log.Printf("starting echo server...") + + ln, err := net.Listen("tcp", *addrFlag) + if err != nil { + return fmt.Errorf("unable to listen: %v", err) + } + defer ln.Close() + + log.Printf("listening on %s...", ln.Addr()) + server := &http.Server{ + Handler: http.HandlerFunc(handler), + } + return server.Serve(ln) +} diff --git a/examples/envoy-federated/src/web-server/css.go b/examples/envoy-federated/src/web-server/css.go new file mode 100644 index 0000000..3fe4e92 --- /dev/null +++ b/examples/envoy-federated/src/web-server/css.go @@ -0,0 +1,102 @@ +package main + +import "net/http" + +func serveCSS(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/css") + w.Write([]byte(` +body {background-color: 999999;} +button { + -webkit-transition-duration: 0.4s; /* Safari */ + transition-duration: 0.4s; + background-color: #333333; + background-color: #02bdd9; + border-radius: 8px; + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + float: none; + font-size: 24px; + font-weight: bold; + text-shadow: 1px 1px 1px #000000; + width: 40%; +} +button:hover { + background-color: #029db9; + color: white; +} +.footer { + position: fixed; + left: 0; + bottom: 0; + height: 40px; + padding-left: 12px; + line-height: 40px; + width: 100%; + background-color: #333333; + color: white; + text-align: left; +} +.error-text{ + font-family: monospace; + font-size: 24px; + white-space: pre-wrap; + hyphens: auto; + word-break: break-word; + word-wrap: break-word; + overflow-wrap: break-word; +} +.header-title { + font-size: 24px; +} +.header-key { + font-family: monospace; + font-size: 24px; + white-space: pre; +} +.header-value { + font-family: monospace; + font-size: 24px; + white-space: pre-wrap; /* css-3 */ + hyphens: auto; + word-break: break-all; + word-wrap: break-word; + overflow-wrap: break-word; +} +.header-grid-container { + display: inline-grid; + grid-template-columns: min-content auto; +} +.button-grid-container { + width: 100%; + display: inline-grid; + grid-gap: 10px; +} +#success { + background-color: #bcd819; +} +#error { + background-color: #e32d31; + color: white; +} +.card { + /* Add shadows to create the "card" effect */ + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); + transition: 0.3s; + width: 100%; +} + +/* On mouse-over, add a deeper shadow */ +.card:hover { + box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); +} + +/* Add some padding inside the card container */ +.container { + padding: 2px 16px; +} +`)) +} diff --git a/examples/envoy-federated/src/web-server/go.mod b/examples/envoy-federated/src/web-server/go.mod new file mode 100644 index 0000000..7f032bd --- /dev/null +++ b/examples/envoy-federated/src/web-server/go.mod @@ -0,0 +1,5 @@ +module web-server + +require github.com/go-chi/chi v4.1.2+incompatible + +go 1.14 diff --git a/examples/envoy-federated/src/web-server/go.sum b/examples/envoy-federated/src/web-server/go.sum new file mode 100644 index 0000000..fa1444a --- /dev/null +++ b/examples/envoy-federated/src/web-server/go.sum @@ -0,0 +1,2 @@ +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= diff --git a/examples/envoy-federated/src/web-server/main.go b/examples/envoy-federated/src/web-server/main.go new file mode 100644 index 0000000..b41f7ae --- /dev/null +++ b/examples/envoy-federated/src/web-server/main.go @@ -0,0 +1,245 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "html/template" + "io" + "log" + "net" + "net/http" + "os" + "sync/atomic" + "time" + + "github.com/go-chi/chi" +) + +var ( + addrFlag = flag.String("addr", ":8080", "address to bind the web server to") + directFlag = flag.String("direct", "echo:8081", "address to the echo server") + envoyMTLSFlag = flag.String("envoy-to-envoy-mtls", "localhost:8001", "address to the envoy-to-envoy mTLS endpoint") + envoyTLSFlag = flag.String("envoy-to-envoy-tls", "localhost:8002", "address to the envoy-to-envoy TLS endpoint") + logFlag = flag.String("log", "", "path to log to (empty=stderr)") + + pageHTML = template.Must(template.New("").Parse(` + + + + +
+
+ +
+
+ +
+
+ +
+
+
+{{- if .WebHeader }} +
+
+
+

Headers Sent by Web Server

+
+{{- range $k, $v := .WebHeader }} +
+
{{ printf "%-30s: " $k }}
+
+
+
{{- range $i, $v := $v }}{{ if $i }},{{ end }}{{ $v }}{{ end }}
+
+{{- end }} +
+
+
+
+{{- end }} + +{{- if .EchoHeader }} +
+
+
+

Headers Received by Echo Server

+
+{{- range $k, $v := .EchoHeader }} +
+
{{ printf "%-30s: " $k }}
+
+
+
{{- range $i, $v := $v }}{{ if $i }},{{ end }}{{ $v }}{{ end }}
+
+{{- end }} +
+
+
+
+{{- end }} + +{{- if .Error }} +
+
+
+

Error:

+
{{ .Error }}
+
+
+
+{{- end }} +{{- if .Attempt }} + +{{- end }} + + +`)) + attempt int32 = 0 +) + +func serveRoot(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + + var host string + route := r.URL.Query().Get("route") + switch route { + case "": + pageHTML.Execute(w, successParams(0, nil, nil)) + return + case "direct": + host = *directFlag + case "envoy-to-envoy-mtls": + host = *envoyMTLSFlag + case "envoy-to-envoy-tls": + host = *envoyTLSFlag + default: + pageHTML.Execute(w, errorParams(attempt, nil, "invalid route value %q", route)) + return + } + + echoURL := fmt.Sprintf("http://%s", host) + + atomic.AddInt32(&attempt, 1) + w.Header().Set("Content-Type", "text/html") + + req, err := http.NewRequest("GET", echoURL, nil) + if err != nil { + log.Printf("[%s] failed to create request for echo server %q: %v", r.RemoteAddr, route, err) + pageHTML.Execute(w, errorParams(attempt, nil, "failed to create request: %v", err)) + return + } + req.Header.Set("Date", httpDate(time.Now())) + req.Header.Set("User-Agent", "") + req.Header.Set("X-Super-Secret-Password", "hunter2") + + log.Printf("[%s] Issuing GET %s", r.RemoteAddr, echoURL) + resp, err := http.DefaultClient.Do(req) + req.Header.Del("User-Agent") + if err != nil { + log.Printf("[%s] failed to send request to echo server %q: %v", r.RemoteAddr, route, err) + pageHTML.Execute(w, errorParams(attempt, req.Header, "failed to contact echo server: %v", err)) + return + } + defer resp.Body.Close() + log.Printf("[%s] GOT %s", r.RemoteAddr, echoURL) + + if resp.StatusCode != http.StatusOK { + body := tryRead(resp.Body) + log.Printf("[%s] unexpected echo server %q response: %d\n%s", r.RemoteAddr, route, resp.StatusCode, body) + pageHTML.Execute(w, errorParams(attempt, req.Header, "unexpected echo server response status: %d\n\n%s", resp.StatusCode, body)) + return + } + + var header http.Header + if err := json.NewDecoder(resp.Body).Decode(&header); err != nil { + log.Printf("[%s] failed to parse echo server %q response: %v", r.RemoteAddr, route, err) + pageHTML.Execute(w, errorParams(attempt, req.Header, "failed to parse echo server response: %v", err)) + return + } + + header.Del("Accept-Encoding") + + log.Printf("[%s] echo server %q response OK", r.RemoteAddr, route) + pageHTML.Execute(w, successParams(attempt, req.Header, header)) +} + +func successParams(attempt int32, webHeader, echoHeader http.Header) map[string]interface{} { + return map[string]interface{}{ + "Attempt": attempt, + "WebHeader": webHeader, + "EchoHeader": echoHeader, + } +} + +func errorParams(attempt int32, webHeader http.Header, format string, args ...interface{}) map[string]interface{} { + return map[string]interface{}{ + "Attempt": attempt, + "WebHeader": webHeader, + "Error": fmt.Sprintf(format, args...), + } +} + +func tryRead(r io.Reader) string { + b := make([]byte, 1024) + n, _ := r.Read(b) + return string(b[:n]) +} + +func main() { + if err := run(context.Background()); err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} + +func run(ctx context.Context) (err error) { + flag.Parse() + log.SetPrefix("web> ") + log.SetFlags(log.Ltime) + if *logFlag != "" { + logFile, err := os.OpenFile(*logFlag, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("unable to open log file: %v", err) + } + defer logFile.Close() + log.SetOutput(logFile) + } else { + log.SetOutput(os.Stdout) + } + log.Printf("starting web server...") + + ln, err := net.Listen("tcp", *addrFlag) + if err != nil { + return fmt.Errorf("unable to listen: %v", err) + } + defer ln.Close() + + r := chi.NewRouter() + r.Use(noCache) + r.Get("/styles.css", http.HandlerFunc(serveCSS)) + r.Get("/", http.HandlerFunc(serveRoot)) + + log.Printf("listening on %s...", ln.Addr()) + server := &http.Server{ + Handler: r, + } + return server.Serve(ln) +} + +func noCache(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Expires", "0") + h.ServeHTTP(w, r) + }) +} + +func httpDate(t time.Time) string { + return t.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") +} diff --git a/examples/envoy-federated/test.sh b/examples/envoy-federated/test.sh new file mode 100755 index 0000000..f19517b --- /dev/null +++ b/examples/envoy-federated/test.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +bold=$(tput bold) || true +norm=$(tput sgr0) || true +red=$(tput setaf 1) || true +green=$(tput setaf 2) || true + +search_occurrences() { + echo "$1" | grep -e "$2" +} + +cleanup() { + echo "${bold}Cleaning up...${norm}" + docker-compose -f "${DIR}"/docker-compose.yml down +} + +set_env() { + "${DIR}"/build.sh > /dev/null + docker-compose -f "${DIR}"/docker-compose.yml up -d + "${DIR}"/1-start-services.sh + "${DIR}"/2-start-spire-agents.sh + "${DIR}"/3-create-registration-entries.sh > /dev/null +} + +run_test() { + PORT=$1 + CLIENT_SPIFFE_ID=$2 + + SECRET_PASS_HEADER="X-Super-Secret-Password" + LOCAL_URL="http://localhost:${PORT}" + DIRECT_RESPONSE=$(curl -s ${LOCAL_URL}/?route=direct) + + if [[ "$( echo "$(search_occurrences "$DIRECT_RESPONSE" "$SECRET_PASS_HEADER")" | wc -l)" -eq 2 ]] ; then + echo "${green}Direct connection test succeded${norm}" + else + echo "${red}Direct connection test failed${norm}" + FAILED=true + fi + if [ -n "${FAILED}" ]; then + echo "${red}There were test failures${norm}" + exit 1 + fi + + EXPECTED_TIMEOUT_HEADER="X-Envoy-Expected-Rq-Timeout-Ms" + FORWARDED_PROTOCOL_HEADER="X-Forwarded-Proto" + REQUEST_ID_HEADER="X-Request-Id" + + for i in {1..5}; do + TLS_RESPONSE=$(curl -s ${LOCAL_URL}/?route=envoy-to-envoy-tls) + + if [[ $( echo "$(search_occurrences "$TLS_RESPONSE" "$SECRET_PASS_HEADER")" | wc -l) -eq 2 ]] && + [[ -n $(search_occurrences "$TLS_RESPONSE" "$EXPECTED_TIMEOUT_HEADER") ]] && + [[ -n $(search_occurrences "$TLS_RESPONSE" "$FORWARDED_PROTOCOL_HEADER") ]] && + [[ -n $(search_occurrences "$TLS_RESPONSE" "$REQUEST_ID_HEADER") ]]; then + echo "${green}Envoy to Envoy TLS connection test succeded${norm}" + FAILED= + break + else + echo "${red}Envoy to Envoy TLS connection test failed, retry...${norm}" + sleep 5 + FAILED=true + fi + done + if [ -n "${FAILED}" ]; then + echo "${red}There were test failures${norm}" + exit 1 + fi + + FORWARDED_CLIENT_CERT_HEADER="X-Forwarded-Client-Cert" + for i in {1..5}; do + MTLS_RESPONSE=$(curl -s ${LOCAL_URL}/?route=envoy-to-envoy-mtls) + + if [[ "$( echo "$(search_occurrences "$MTLS_RESPONSE" "$SECRET_PASS_HEADER")" | wc -l)" -eq 2 ]] && + [[ -n $(search_occurrences "$MTLS_RESPONSE" "$EXPECTED_TIMEOUT_HEADER") ]] && + [[ -n $(search_occurrences "$MTLS_RESPONSE" "$FORWARDED_PROTOCOL_HEADER") ]] && + [[ -n $(search_occurrences "$MTLS_RESPONSE" "$REQUEST_ID_HEADER") ]] && + [[ -n $(search_occurrences "$MTLS_RESPONSE" "$FORWARDED_CLIENT_HEADER") ]] && + [[ -n $(search_occurrences "$MTLS_RESPONSE" "$CLIENT_SPIFFE_ID") ]]; then + echo "${green}Envoy to Envoy mTLS connection test succeded${norm}" + FAILED= + break + else + echo "${red}Envoy to Envoy mTLS connection test failed${norm}" + sleep 5 + FAILED=true + fi + done + if [ -n "${FAILED}" ]; then + echo "${red}There were test failures${norm}" + exit 1 + fi +} + +trap cleanup EXIT + +echo "${bold}Preparing environment...${norm}" + +cleanup +set_env + +ECHO_CONTAINER_ID=$(docker container ls -qf "name=echo") +WEB_DOMAIN_CONTAINER_ID=$(docker container ls -qf "name=web-domain") +echo $WEB_DOMAIN_CONTAINER_ID +WEB_FEDERATED_CONTAINER_ID=$(docker container ls -qf "name=web-federated") +echo $WEB_FEDERATED_CONTAINER_ID + +docker cp "${DIR}"/wait-for-envoy.sh "$ECHO_CONTAINER_ID":/tmp +docker cp "${DIR}"/wait-for-envoy.sh "$WEB_DOMAIN_CONTAINER_ID":/tmp +docker cp "${DIR}"/wait-for-envoy.sh "$WEB_FEDERATED_CONTAINER_ID":/tmp + +echo "${bold}Waiting for envoy...${norm}" + +docker exec -w /tmp "$ECHO_CONTAINER_ID" sh wait-for-envoy.sh Echo +docker exec -w /tmp "$WEB_DOMAIN_CONTAINER_ID" sh wait-for-envoy.sh "Web domain" +docker exec -w /tmp "$WEB_FEDERATED_CONTAINER_ID" sh wait-for-envoy.sh "Web federated" + +echo "${bold}Running domain test...${norm}" +run_test "8080" "spiffe://domain.test/web-server" +sleep 10 + +echo "${bold}Running federated test...${norm}" +run_test "8088" "spiffe://federated.test/web-server" + +echo "${green}Test passed!${norm}" +exit 0 diff --git a/examples/envoy-federated/wait-for-envoy.sh b/examples/envoy-federated/wait-for-envoy.sh new file mode 100644 index 0000000..119fbe5 --- /dev/null +++ b/examples/envoy-federated/wait-for-envoy.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +bold='\033[1;37m' +norm='\033[0m' +red='\033[0;31m' + +i=0 +while [ "$i" -lt 30 ]; do + i=$(( i+1 )) + LOGLINE="all dependencies initialized. starting workers" + + if [ -z "$(grep "${LOGLINE}" envoy.log)" ]; then + echo "${1}'s envoy is not ready yet, sleeping for a while..." + sleep 5 + continue + fi + + echo -e "${bold}${1}'s envoy is ready!${norm}" + ENVOY_READY=true + break +done + +if [ -z "${ENVOY_READY}" ]; then + echo "${red}Timed out waiting for ${1}'s envoy${norm}" + exit 1 +fi +exit 0