diff --git a/test/integration/suites/force-rotation-upstream-authority/00-setup b/test/integration/suites/force-rotation-upstream-authority/00-setup new file mode 100755 index 0000000000..d6cdd65b94 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/00-setup @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e + +# Function to generate a new EC key and self-signed certificate +generate_cert() { + local key_path=$1 + local crt_path=$2 + + openssl ecparam -name secp384r1 -genkey -noout -out "${key_path}" + openssl req -new -x509 -key "${key_path}" -out "${crt_path}" -days 1825 -subj "/C=US/ST=/L=/O=SPIFFE/OU=/CN=/" -config <( +cat <<-EOF +[req] +default_bits = 2048 +default_md = sha512 +distinguished_name = dn +[ dn ] +[alt_names] +URI.1 = spiffe://local +[v3_req] +subjectKeyIdentifier=hash +basicConstraints=critical,CA:TRUE +keyUsage=critical,keyCertSign,cRLSign +subjectAltName = @alt_names +EOF + ) -extensions 'v3_req' +} + +"${ROOTDIR}/setup/x509pop/setup.sh" conf/server conf/agent + +# Generate dummy upstream CA +generate_cert "conf/server/dummy_upstream_ca.key" "conf/server/dummy_upstream_ca.crt" + +# Generate new upstream CA +generate_cert "conf/server/new_upstream_ca.key" "conf/server/new_upstream_ca.crt" diff --git a/test/integration/suites/force-rotation-upstream-authority/01-start-server b/test/integration/suites/force-rotation-upstream-authority/01-start-server new file mode 100755 index 0000000000..a3e999b264 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/01-start-server @@ -0,0 +1,3 @@ +#!/bin/bash + +docker-up spire-server diff --git a/test/integration/suites/force-rotation-upstream-authority/02-bootstrap-agent b/test/integration/suites/force-rotation-upstream-authority/02-bootstrap-agent new file mode 100755 index 0000000000..8ee7d32c26 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/02-bootstrap-agent @@ -0,0 +1,5 @@ +#!/bin/bash + +log-debug "bootstrapping agent..." +docker compose exec -T spire-server \ + /opt/spire/bin/spire-server bundle show > conf/agent/bootstrap.crt diff --git a/test/integration/suites/force-rotation-upstream-authority/03-start-agent b/test/integration/suites/force-rotation-upstream-authority/03-start-agent new file mode 100755 index 0000000000..ac36d05f0d --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/03-start-agent @@ -0,0 +1,3 @@ +#!/bin/bash + +docker-up spire-agent diff --git a/test/integration/suites/force-rotation-upstream-authority/04-create-workload-entry b/test/integration/suites/force-rotation-upstream-authority/04-create-workload-entry new file mode 100755 index 0000000000..f0d56b7e66 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/04-create-workload-entry @@ -0,0 +1,28 @@ +#!/bin/bash + +log-debug "creating registration entry..." +docker compose exec -T spire-server \ + /opt/spire/bin/spire-server entry create \ + -parentID "spiffe://domain.test/spire/agent/x509pop/$(fingerprint conf/agent/agent.crt.pem)" \ + -spiffeID "spiffe://domain.test/workload" \ + -selector "unix:uid:0" \ + -x509SVIDTTL 0 + +# Check at most 30 times (with one second in between) that the agent has +# successfully synced down the workload entry. +MAXCHECKS=30 +CHECKINTERVAL=1 +for ((i=1;i<=MAXCHECKS;i++)); do + log-info "checking for synced workload entry ($i of $MAXCHECKS max)..." + docker compose logs spire-agent + if docker compose logs spire-agent | grep "spiffe://domain.test/workload"; then + exit 0 + fi + sleep "${CHECKINTERVAL}" +done + +fail-now "timed out waiting for agent to sync down entry" + +log-info "checking X509-SVID" +docker compose exec -T spire-agent \ + /opt/spire/bin/spire-agent api fetch x509 || fail-now "SVID check failed" \ No newline at end of file diff --git a/test/integration/suites/force-rotation-upstream-authority/05-update-upstream-authority b/test/integration/suites/force-rotation-upstream-authority/05-update-upstream-authority new file mode 100755 index 0000000000..fa5021e0b2 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/05-update-upstream-authority @@ -0,0 +1,6 @@ +#!/bin/bash + +# Update upstream authority +cp conf/server/new_upstream_ca.crt conf/server/dummy_upstream_ca.crt +cp conf/server/new_upstream_ca.key conf/server/dummy_upstream_ca.key + diff --git a/test/integration/suites/force-rotation-upstream-authority/06-prepare-x509-authority b/test/integration/suites/force-rotation-upstream-authority/06-prepare-x509-authority new file mode 100755 index 0000000000..b5f1bbf8cb --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/06-prepare-x509-authority @@ -0,0 +1,46 @@ +#!/bin/bash + +# Constants +MAXCHECKS=30 +RETRY_DELAY=1 + +# Initial check for x509 authorities in spire-server +x509_authorities=$(docker compose exec -T spire-server \ + /opt/spire/bin/spire-server bundle show -output json | jq '.x509_authorities' -c) + +amount_bundles=$(echo "$x509_authorities" | jq length) + +# Ensure only one bundle is present at the start +if [[ $amount_bundles -ne 1 ]]; then + fail-now "Only one bundle expected at start" +fi + +# Prepare authority +prepared_authority_id=$(docker compose exec -T -e SPIRE_SERVER_FFLAGS=forced_rotation spire-server \ + /opt/spire/bin/spire-server localauthority x509 prepare -output json | jq -r .prepared_authority.authority_id) + +# Verify that the prepared authority is logged +searching="X509 CA prepared.|local_authority_id=${prepared_authority_id}" +check-log-line spire-server "$searching" + +# Check for updated x509 authorities in spire-server +x509_authorities=$(docker compose exec -T spire-server \ + /opt/spire/bin/spire-server bundle show -output json | jq '.x509_authorities' -c) +amount_bundles=$(echo "$x509_authorities" | jq length) + +# Ensure two bundles are present after preparation +if [[ $amount_bundles -ne 2 ]]; then + fail-now "Two bundles expected after prepare" +fi + +new_dummy_ca_skid=$(openssl x509 -in conf/server/new_upstream_ca.crt -text | grep \ + -A 1 'Subject Key Identifier' | tail -n 1 | tr -d ' ' | tr -d ':' | tr '[:upper:]' '[:lower:]') + +upstream_authority_id=$(docker compose exec -T -e SPIRE_SERVER_FFLAGS=forced_rotation spire-server \ + /opt/spire/bin/spire-server \ + localauthority x509 show -output json | jq .prepared.upstream_authority_subject_key_id -r) + +if [ "$new_dummy_ca_skid" == "$upstream_authority_id" ]; then + log-debug "Prepared X.509 authority is using new upstream authorityh" +else + fail-now "Subject Key Identifier does not match. Found: $upstream_authority_id Expected: $new_dummy_ca_skid" diff --git a/test/integration/suites/force-rotation-upstream-authority/07-activate-x509-authority b/test/integration/suites/force-rotation-upstream-authority/07-activate-x509-authority new file mode 100755 index 0000000000..1b103c2cfa --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/07-activate-x509-authority @@ -0,0 +1,22 @@ +#!/bin/bash + +# Fetch the prepared authority ID +prepared_authority=$(docker compose exec -t -e SPIRE_SERVER_FFLAGS=forced_rotation spire-server \ + /opt/spire/bin/spire-server \ + localauthority x509 show -output json | jq -r .prepared.authority_id) || fail-now "Failed to fetch prepared authority ID" +upstream_authority=$(docker compose exec -t -e SPIRE_SERVER_FFLAGS=forced_rotation spire-server \ + /opt/spire/bin/spire-server \ + localauthority x509 show -output json | jq -r .prepared.upstream_authority_subject_key_id) || fail-now "Failed to fetch prepared authority ID" + +# Activate the authority +activated_authority=$(docker compose exec -t -e SPIRE_SERVER_FFLAGS=forced_rotation spire-server \ + /opt/spire/bin/spire-server \ + localauthority x509 activate -authorityID "${prepared_authority}" \ + -output json | jq -r .activated_authority.authority_id) || fail-now "Failed to activate authority" + +log-info "Activated authority: ${activated_authority}" + +# Check logs for specific lines +check-log-line spire-server "X509 CA activated|local_authority_id=${prepared_authority}|upstream_authority_id=${upstream_authority}" +check-log-line spire-server "Successfully rotated X\.509 CA" + diff --git a/test/integration/suites/force-rotation-upstream-authority/README.md b/test/integration/suites/force-rotation-upstream-authority/README.md new file mode 100644 index 0000000000..b3533581ee --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/README.md @@ -0,0 +1,6 @@ +# Force rotation using upstream authority Suite + +## Description + +This suite sets a very low TTLs and ensures that workload SVIDs are valid +across many SVID and SPIRE server CA rotation periods. diff --git a/test/integration/suites/force-rotation-upstream-authority/conf/agent/agent.conf b/test/integration/suites/force-rotation-upstream-authority/conf/agent/agent.conf new file mode 100644 index 0000000000..f79c4e9b06 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/conf/agent/agent.conf @@ -0,0 +1,26 @@ +agent { + data_dir = "/opt/spire/data/agent" + log_level = "DEBUG" + server_address = "spire-server" + server_port = "8081" + 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/test/integration/suites/force-rotation-upstream-authority/conf/server/server.conf b/test/integration/suites/force-rotation-upstream-authority/conf/server/server.conf new file mode 100644 index 0000000000..74b4f1cc38 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/conf/server/server.conf @@ -0,0 +1,35 @@ +server { + bind_address = "0.0.0.0" + bind_port = "8081" + trust_domain = "domain.test" + data_dir = "/opt/spire/data/server" + log_level = "DEBUG" + ca_ttl = "24h" + default_x509_svid_ttl = "8h" + experimental { + feature_flags = ["forced_rotation"] + } +} + +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 "memory" { + plugin_data = {} + } + UpstreamAuthority "disk" { + plugin_data { + key_file_path = "./conf/server/dummy_upstream_ca.key" + cert_file_path = "./conf/server/dummy_upstream_ca.crt" + } + } +} diff --git a/test/integration/suites/force-rotation-upstream-authority/docker-compose.yaml b/test/integration/suites/force-rotation-upstream-authority/docker-compose.yaml new file mode 100644 index 0000000000..288be5fd27 --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/docker-compose.yaml @@ -0,0 +1,14 @@ +services: + spire-server: + image: spire-server:latest-local + hostname: spire-server + volumes: + - ./conf/server:/opt/spire/conf/server + command: ["-config", "/opt/spire/conf/server/server.conf"] + spire-agent: + image: spire-agent:latest-local + hostname: spire-agent + depends_on: ["spire-server"] + volumes: + - ./conf/agent:/opt/spire/conf/agent + command: ["-config", "/opt/spire/conf/agent/agent.conf"] diff --git a/test/integration/suites/force-rotation-upstream-authority/teardown b/test/integration/suites/force-rotation-upstream-authority/teardown new file mode 100755 index 0000000000..fabbf145ae --- /dev/null +++ b/test/integration/suites/force-rotation-upstream-authority/teardown @@ -0,0 +1,6 @@ +#!/bin/bash + +if [ -z "$SUCCESS" ]; then + docker compose logs +fi +docker-down