Skip to content

Commit

Permalink
Add KeyManager vault integration test (spiffe#5058)
Browse files Browse the repository at this point in the history
Signed-off-by: Matteo Kamm <[email protected]>
  • Loading branch information
InverseIntegral committed Nov 2, 2024
1 parent f57fd03 commit 7113b7b
Show file tree
Hide file tree
Showing 22 changed files with 929 additions and 3 deletions.
6 changes: 3 additions & 3 deletions test/integration/common
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ download-kind() {
elif [ "${ARCH}" = "aarch64" ]; then
ARCH=arm64
fi
echo "Ensuring kind version $KINDVERSION is available..."
echo "Ensuring kind version $KINDVERSION is available..."
KINDURL="https://github.com/kubernetes-sigs/kind/releases/download/$KINDVERSION/kind-$UNAME-$ARCH"

local kind_link_or_path=$1
Expand All @@ -247,7 +247,7 @@ download-helm() {
ARCH=arm64
fi

echo "Ensuring helm version $HELMVERSION is available..."
echo "Ensuring helm version $HELMVERSION is available..."
HELMURL="https://get.helm.sh/helm-${HELMVERSION}-${UNAME}-${ARCH}.tar.gz"

local helm_link_or_path=$1
Expand All @@ -274,7 +274,7 @@ download-kubectl() {
ARCH=arm64
fi

KUBECTLURL="https://storage.googleapis.com/kubernetes-release/release/$WANTVERSION/bin/$UNAME/$ARCH/kubectl"
KUBECTLURL="https://dl.k8s.io/release//$WANTVERSION/bin/$UNAME/$ARCH/kubectl"

HAVEVERSION=""
if [ -x "${KUBECTLPATH}" ]; then
Expand Down
33 changes: 33 additions & 0 deletions test/integration/suites/key-manager-vault/00-setup-kind
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash

# Create a temporary path that will be added to the PATH to avoid picking up
# binaries from the environment that aren't a version match.
mkdir -p ./bin

KINDVERSION=v0.24.0
KUBECTLVERSION=v1.31.2
K8SIMAGE=kindest/node:v1.31.0
HELMVERSION=v3.16.2

KIND_PATH=./bin/kind
KUBECTL_PATH=./bin/kubectl
HELM_PATH=./bin/helm

# Download kind at the expected version at the given path.
download-kind "${KIND_PATH}"

# Download kubectl at the expected version.
download-kubectl "${KUBECTL_PATH}"

# Download helm at the expected version.
download-helm "${HELM_PATH}"

# Start the kind cluster.
start-kind-cluster "${KIND_PATH}" vault-test

# Load the given images in the cluster.
container_images=("spire-server:latest-local")
load-images "${KIND_PATH}" vault-test "${container_images[@]}"

# Set the kubectl context.
set-kubectl-context "${KUBECTL_PATH}" kind-vault-test
102 changes: 102 additions & 0 deletions test/integration/suites/key-manager-vault/01-setup-vault
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/bin/bash

set -e -o pipefail

source init-kubectl

CHARTVERSION=0.28.1

log-info "installing hashicorp vault..."

kubectl-exec-vault() {
./bin/kubectl exec -n vault vault-0 -- $@
}

log-info "preparing certificates..."
# Prepare CSR for Vault instance
openssl ecparam -name prime256v1 -genkey -noout -out vault_key.pem
openssl req -new \
-key vault_key.pem \
-out vault_csr.pem \
-subj "/C=US/O=system:nodes/CN=system:node:vault" \
-reqexts v3 \
-config <(cat /etc/ssl/openssl.cnf ; printf "\n[v3]\nsubjectAltName=@alt_names\n[alt_names]\nDNS.1=vault\nDNS.2=vault.vault.svc\nIP.1=127.0.0.1")
cat > csr.yaml <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: vault.svc
namespace: vault
spec:
signerName: kubernetes.io/kubelet-serving
expirationSeconds: 8640000
request: $(cat vault_csr.pem|base64|tr -d '\n')
usages:
- digital signature
- key encipherment
- server auth
EOF

# Prepare certificates for Vault Cert Auth
openssl ecparam -name prime256v1 -genkey -noout -out cert_auth_ca_key.pem
openssl req -days 30 -x509 -new \
-key cert_auth_ca_key.pem \
-out cert_auth_ca.pem \
-subj "/C=US/O=SPIFFE" \
-extensions v3 \
-config <(cat /etc/ssl/openssl.cnf ; printf "\n[v3]\nsubjectAltName=URI:spiffe://cert-auth-ca\nbasicConstraints=CA:true")
openssl ecparam -name prime256v1 -genkey -noout -out client_key.pem
openssl req -new \
-key client_key.pem \
-out client_csr.pem \
-subj "/C=US/O=SPIFFE/CN=Cert Auth Client" \
-config <(cat /etc/ssl/openssl.cnf ; printf "\n[v3]\nsubjectAltName=URI:spiffe://cert-auth-client")
openssl x509 -days 10 -req \
-CAcreateserial \
-CA cert_auth_ca.pem \
-CAkey cert_auth_ca_key.pem \
-in client_csr.pem \
-out client.pem \
-sha256 \
-extensions v3 \
-extfile <(cat /etc/ssl/openssl.cnf ; printf "\n[v3]\nsubjectAltName=URI:spiffe://cert-auth-client")

# Install Vault
log-info "installing hashicorp vault..."
./bin/kubectl create namespace vault
./bin/kubectl create -f csr.yaml
./bin/kubectl certificate -n vault approve vault.svc
log-debug "waiting for certificate to be issued by k8s..."
timeout 30s bash -c 'while [[ -z $(./bin/kubectl get csr -n vault vault.svc -o jsonpath="{.status.certificate}" 2>/dev/null) ]]; do sleep 1; done'
./bin/kubectl get csr -n vault vault.svc -o jsonpath='{.status.certificate}' | openssl base64 -d -A -out vault.pem
./bin/kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' \
| base64 -d > vault_ca.pem
./bin/kubectl create secret generic vault-tls -n vault \
--from-file=vault_key.pem=vault_key.pem \
--from-file=vault.pem=vault.pem \
--from-file=vault_ca.pem=vault_ca.pem

./bin/helm repo add hashicorp https://helm.releases.hashicorp.com
./bin/helm install vault hashicorp/vault --namespace vault --version $CHARTVERSION -f conf/helm-values.yaml
./bin/kubectl wait -n kube-system --for=condition=available deployment --all --timeout=90s
./bin/kubectl wait pods -n vault --for=jsonpath='{.status.phase}'=Running vault-0 --timeout=90s

# Initialize and unseal
log-info "initializing hashicorp vault..."
kubectl-exec-vault vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
kubectl-exec-vault vault operator unseal $VAULT_UNSEAL_KEY
./bin/kubectl wait pods -n vault --for=condition=Ready vault-0 --timeout=60s
VAULT_ROOT_TOKEN=$(cat cluster-keys.json | jq -r ".root_token")
kubectl-exec-vault vault login $VAULT_ROOT_TOKEN > /dev/null

./bin/kubectl cp -n vault cert_auth_ca.pem vault-0:/tmp/.
./bin/kubectl cp -n vault conf/configure-transit-secret-engine.sh vault-0:/tmp/.
./bin/kubectl cp -n vault conf/spire.hcl vault-0:tmp/.
./bin/kubectl cp -n vault conf/configure-auth-method.sh vault-0:/tmp/.

# Configure Vault
log-info "configuring transit secret engine..."
kubectl-exec-vault /tmp/configure-transit-secret-engine.sh
log-info "configuring auth methods..."
kubectl-exec-vault /tmp/configure-auth-method.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash

set -e -o pipefail

./bin/kubectl create namespace spire
./bin/kubectl create secret -n spire generic vault-tls \
--from-file=vault_ca.pem=vault_ca.pem

# Verify AppRole Auth
log-info "verifying approle auth..."
APPROLE_ID=$(./bin/kubectl exec -n vault vault-0 -- vault read --format json auth/approle/role/spire/role-id | jq -r .data.role_id)
SECRET_ID=$(./bin/kubectl exec -n vault vault-0 -- vault write --format json -f auth/approle/role/spire/secret-id | jq -r .data.secret_id)
./bin/kubectl create secret -n spire generic vault-credential \
--from-literal=approle_id=$APPROLE_ID \
--from-literal=secret_id=$SECRET_ID
./bin/kubectl apply -k ./conf/server/approle-auth
./bin/kubectl wait pods -n spire -l app=spire-server --for condition=Ready --timeout=60s
./bin/kubectl delete -k ./conf/server/approle-auth
./bin/kubectl delete secret -n spire vault-credential
./bin/kubectl wait pods -n spire -l app=spire-server --for=delete --timeout=60s

# Verify K8s Auth
log-info "verifying k8s auth..."
./bin/kubectl apply -k ./conf/server/k8s-auth
./bin/kubectl wait pods -n spire -l app=spire-server --for condition=Ready --timeout=60s
./bin/kubectl delete -k ./conf/server/k8s-auth
./bin/kubectl wait pods -n spire -l app=spire-server --for=delete --timeout=60s

# Verify Cert Auth
log-info "verifying cert auth..."
./bin/kubectl create secret -n spire generic vault-credential \
--from-file=client.pem=client.pem \
--from-file=client_key.pem=client_key.pem
./bin/kubectl apply -k ./conf/server/cert-auth
./bin/kubectl wait pods -n spire -l app=spire-server --for condition=Ready --timeout=60s
./bin/kubectl delete -k ./conf/server/cert-auth
./bin/kubectl delete secret -n spire vault-credential
./bin/kubectl wait pods -n spire -l app=spire-server --for=delete --timeout=60s

# Verify Token Auth
log-info "verifying token auth..."
TOKEN=$(./bin/kubectl exec -n vault vault-0 -- vault token create -policy=spire -ttl=1m -field=token)
./bin/kubectl create secret -n spire generic vault-credential \
--from-literal=token=$TOKEN
./bin/kubectl apply -k ./conf/server/token-auth
./bin/kubectl wait pods -n spire -l app=spire-server --for condition=Ready --timeout=60s
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

set -eo pipefail

log-debug "verifying token renewal..."

timeout=$(date -ud "1 minute 30 second" +%s)
count=0

while [ $(date -u +%s) -lt $timeout ]; do
count=`./bin/kubectl logs -n spire $(./bin/kubectl get pod -n spire -o name) | echo "$(grep "Successfully renew auth token" || [[ $? == 1 ]])" | wc -l`
if [ $count -ge 2 ]; then
log-info "token renewal is verified"
exit 0
fi
sleep 10
done

fail-now "expected number of token renewal log not found"
9 changes: 9 additions & 0 deletions test/integration/suites/key-manager-vault/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# KeyManager HashiCorp Vault plugin suite

## Description

This suite sets up a Kubernetes cluster using [Kind](https://kind.sigs.k8s.io),
installs HashiCorp Vault. It then asserts the following:

* SPIRE server successfully requests a key from the referenced Vault Transit Secret Engine
* Verifies that Auth Methods are configured successfully
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh

set -e -o pipefail

# Create Policy
vault policy write spire /tmp/spire.hcl

# Configure Vault Auth Method
vault auth enable approle
vault write auth/approle/role/spire \
secret_id_ttl=120m \
token_ttl=1m \
policies="spire"

# Configure K8s Auth Method
vault auth enable kubernetes
vault write auth/kubernetes/config kubernetes_host=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT_HTTPS
vault write auth/kubernetes/role/my-role \
bound_service_account_names=spire-server \
bound_service_account_namespaces=spire \
token_ttl=1m \
policies=spire

# Configure Cert Auth Method
vault auth enable cert
vault write auth/cert/certs/my-role \
display_name=spire \
token_ttl=1m \
policies=spire \
certificate=@/tmp/cert_auth_ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

set -e -o pipefail

# Configure Root CA
vault secrets enable transit
vault secrets tune -max-lease-ttl=8760h transit
33 changes: 33 additions & 0 deletions test/integration/suites/key-manager-vault/conf/helm-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
global:
enabled: true
tlsDisable: false
server:
extraEnvironmentVars:
VAULT_CACERT: /vault/userconfig/vault-tls/vault_ca.pem
VAULT_TLSCERT: /vault/userconfig/vault-tls/vault.pem
VAULT_TLSKEY: /vault/userconfig/vault-tls/vault_key.pem
volumes:
- name: userconfig-vault-tls
secret:
defaultMode: 420
secretName: vault-tls
volumeMounts:
- mountPath: /vault/userconfig/vault-tls
name: userconfig-vault-tls
readOnly: true
standalone:
enabled: "-"
config: |
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_cert_file = "/vault/userconfig/vault-tls/vault.pem"
tls_key_file = "/vault/userconfig/vault-tls/vault_key.pem"
tls_disable_client_certs = false
}
storage "file" {
path = "/vault/data"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../base
patchesStrategicMerge:
- spire-server.yaml
Loading

0 comments on commit 7113b7b

Please sign in to comment.