Appendix: full scripts
Full script
This script aggregates the commands from the entire guide. It is not idempotent, so if you encounter errors, you should view the script logs and only run the following commands to avoid creating too many resources. To clean up, see Cleanup script below, which uses the environment variables set by this script.
By default, the script expects the TLS secrets,
named tls-key.pem
and tls-cert.pem
,
to be in the local directory.
To use a self-signed certificate,
see Generate a self-signed certificate below, for instructions.
To use a fully-qualified domain name that you control,
see TLS configuration
in a previous section.
Copy-paste the following script into a file, generate-env-vars.sh
.
Edit the required environment variables, at the top.
Then, run this script with source generate-env-vars.sh
,
so that environment variables will be persisted for teardown.
#!/bin/bash
#=======================================#
# Set environment variables (mandatory) #
#=======================================#
export PREFIX="anjuna" # Update this prefix - must be alphanumeric, and unique for each execution
export GCP_PROJECT="<your-project-name>" # Update this to your project name
mkdir -p apm-on-gcp/{server,client}
# You MUST copy anjuna-gcp-installer.release-1.12.0007.bin into apm-on-gcp/server,
# because it is required in the server Docker build context
# Uncomment the following line if you have a certificate.
# Otherwise, the APM’s internal DNS name will be used.
# export CERT_FQDN="<fully-qualified domain name>"
#=======================================#
# Set environment variables (generated) #
#=======================================#
export GCP_REGION="us-central1"
export GCP_ZONE="us-central1-a"
export NETWORK_NAME="${PREFIX}-apm-network"
export SUBNET_NAME="${NETWORK_NAME}-subnet"
export SERVICE_ACCOUNT_NAME="${PREFIX}-apm-sa"
export SERVICE_ACCOUNT_DESCR="Anjuna Policy Manager Server"
export SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${GCP_PROJECT}.iam.gserviceaccount.com"
export ATTESTATION_ROLE_NAME="${PREFIX}_attestation_role"
export KMS_LOCATION="global"
export KMS_KEYRING="${PREFIX}-apm-keyring"
export KMS_KEY="${PREFIX}-apm-key"
export APM_SERVER_BUCKET="${PREFIX}-apm-server"
export APM_SERVER_IMAGE="${PREFIX}-apm-server-image"
export APM_SERVER_INSTANCE="${PREFIX}-apm-server-instance"
export APM_SERVER_STORAGE="${PREFIX}-apm-storage"
export APM_SERVER_TLS_KEY_SECRET="${PREFIX}-apm-tls-key"
export APM_SERVER_TLS_CERT_SECRET="${PREFIX}-apm-tls-cert"
export INTERNAL_DNS_NAME="${APM_SERVER_INSTANCE}.${GCP_ZONE}.c.${GCP_PROJECT}.internal"
if [ -z "$CERT_FQDN" ]; then
export APM_SERVER_HOST="${INTERNAL_DNS_NAME}"
else
export APM_SERVER_HOST="${CERT_FQDN}"
fi
export CLIENT_PREFIX="${PREFIX}-apm-client"
export APM_CLIENT_BUCKET="${CLIENT_PREFIX}"
export APM_CLIENT_IMAGE="${CLIENT_PREFIX}-image"
export APM_CLIENT_INSTANCE="${CLIENT_PREFIX}-instance"
Then, copy the following script into a file, deploy-apm.sh
.
Run this script with bash deploy-apm.sh
.
The script will check for the presence of required files.
#==========================#
# Check for required files #
#==========================#
# If you do not have a certificate, see the next code sample
# for an `openssl` script to generate a self-signed certificate
if [[ ! -f tls-key.pem || ! -f tls-cert.pem || ! -f apm-on-gcp/server/anjuna-gcp-installer.release-1.12.0007.bin ]]
then
echo "./tls-key.pem and ./tls-cert.pem are required in the current working directory. Please create them."
exit 1
fi
# You MUST copy anjuna-gcp-installer.release-1.12.0007.bin into apm-on-gcp/server,
# because it is required in the server Docker build context
if [[ ! -f apm-on-gcp/server/anjuna-gcp-installer.release-1.12.0007.bin ]]
then
echo "anjuna-gcp-installer.release-1.12.0007.bin is required in apm-on-gcp/server. Please copy it."
exit 1
fi
#===============================================#
# Configure shell script settings #
#===============================================#
# These are optional settings which may improve your script experience
set -e # exit if any command fails
set -u # exit if any environment variables are unset
set -o pipefail # exit if any piped command fails
set -x # print each command before running it
#===============================================#
# Create a new VPC, subnets, and firewall rules #
#===============================================#
gcloud compute networks create \
"${NETWORK_NAME}" \
--subnet-mode "custom"
gcloud compute networks subnets create \
"${SUBNET_NAME}" \
--network ${NETWORK_NAME} \
--range "10.128.0.0/20" \
--region ${GCP_REGION}
# Allow any TCP and ICMP within the VPC
gcloud compute firewall-rules create \
"${NETWORK_NAME}-allow-internal" \
--network "${NETWORK_NAME}" \
--allow tcp,icmp \
--source-ranges "10.128.0.0/20"
# Allow TCP port 8200 from anywhere
gcloud compute firewall-rules create \
"${NETWORK_NAME}-firewall-apm" \
--network "${NETWORK_NAME}" \
--allow tcp:8200 \
--source-ranges "0.0.0.0/0"
#================================================================================#
# Create a service account with permissions needed for the Anjuna Policy Manager #
#================================================================================#
gcloud iam service-accounts create \
"${SERVICE_ACCOUNT_NAME}" \
--description "${SERVICE_ACCOUNT_DESCR}"
# Grant permission needed for logging
gcloud projects add-iam-policy-binding \
"${GCP_PROJECT}" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role="roles/logging.logWriter"
# Grant permission needed for verifying attestation
gcloud iam roles create \
"${ATTESTATION_ROLE_NAME}" \
--project "${GCP_PROJECT}" \
--title "Anjuna Attestation Role" \
--permissions "compute.instances.getShieldedInstanceIdentity"
gcloud projects add-iam-policy-binding \
"${GCP_PROJECT}" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role "projects/${GCP_PROJECT}/roles/${ATTESTATION_ROLE_NAME}"
# Create a Google Cloud storage bucket for persistent storage
# and grant permission to the service account
gcloud storage buckets create \
"gs://${APM_SERVER_STORAGE}" \
--location "${GCP_REGION}"
gcloud storage buckets add-iam-policy-binding \
"gs://${APM_SERVER_STORAGE}" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role=roles/storage.objectAdmin
# Upload TLS secrets to Google Cloud Secret Manager
# and grant permission to the service account
gcloud secrets create \
"${APM_SERVER_TLS_KEY_SECRET}" \
--data-file "tls-key.pem"
gcloud secrets create \
"${APM_SERVER_TLS_CERT_SECRET}" \
--data-file "tls-cert.pem"
gcloud secrets add-iam-policy-binding \
"${APM_SERVER_TLS_KEY_SECRET}" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role "roles/secretmanager.secretAccessor"
gcloud secrets add-iam-policy-binding \
"${APM_SERVER_TLS_CERT_SECRET}" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role="roles/secretmanager.secretAccessor"
# Create a KMS keyring and key for auto-unsealing the Anjuna Policy Manager
# and grant permission to the service account
gcloud kms keyrings create \
${KMS_KEYRING} \
--location "${KMS_LOCATION}"
gcloud kms keys create \
${KMS_KEY} \
--keyring "${KMS_KEYRING}" \
--location "${KMS_LOCATION}" \
--purpose "encryption"
gcloud kms keys add-iam-policy-binding \
${KMS_KEY} \
--keyring "${KMS_KEYRING}" \
--location "${KMS_LOCATION}" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role "roles/cloudkms.cryptoKeyEncrypterDecrypter"
#========================================#
# Build the Anjuna Policy Manager Server #
#========================================#
mkdir -p apm-on-gcp/{server,client}
cd apm-on-gcp
# Create script to fetch secrets and start the Anjuna Policy Manager
cat << EOF >server/apm-start.sh
#!/bin/bash
# Exit on failure
set -ex
# Access TLS key and cert from Google Cloud Secret Manager and store to files
gcloud secrets versions access latest \
--secret "${APM_SERVER_TLS_KEY_SECRET}" \
>/opt/anjuna/policy-manager/tls-key.pem
gcloud secrets versions access latest \
--secret "${APM_SERVER_TLS_CERT_SECRET}" \
>/opt/anjuna/policy-manager/tls-cert.pem
source /opt/anjuna/gcp/env.sh
anjuna-policy-manager-server server \
-config /opt/anjuna/policy-manager/config.hcl \
2>&1 >&/opt/anjuna/policy-manager/apm.log
EOF
# Create configuration file for Anjuna Policy Manager
cat << EOF >server/config.hcl
api_addr = "https://${APM_SERVER_HOST}:8200"
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/anjuna/policy-manager/tls-cert.pem"
tls_key_file = "/opt/anjuna/policy-manager/tls-key.pem"
}
storage "gcs" {
bucket = "${APM_SERVER_STORAGE}"
}
seal "gcpckms" {
project = "${GCP_PROJECT}"
region = "${KMS_LOCATION}"
key_ring = "${KMS_KEYRING}"
crypto_key = "${KMS_KEY}"
}
EOF
# Create Dockerfile to include previous two files and run the start script
cat << EOF >server/Dockerfile
# Ubuntu 20.04
FROM ubuntu@sha256:8eb87f3d6c9f2feee114ff0eff93ea9dfd20b294df0a0353bd6a4abf403336fe
# Install Anjuna GCP
COPY anjuna-gcp-installer.release-1.12.0007.bin /tmp/
RUN /bin/bash /tmp/anjuna-gcp-installer.release-1.12.0007.bin --extract
RUN rm /tmp/anjuna-gcp-installer.release-1.12.0007.bin
FROM ubuntu@sha256:8eb87f3d6c9f2feee114ff0eff93ea9dfd20b294df0a0353bd6a4abf403336fe
# Install gcloud-cli
RUN apt update
RUN apt -y install gnupg wget
RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
RUN wget -q -O- https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
RUN apt update && apt -y install google-cloud-cli=413.0.0-0
# Copy the APM artifacts from the previous stage
COPY --from=0 /opt/anjuna/gcp/env.sh /opt/anjuna/gcp/
COPY --from=0 /opt/anjuna/gcp/bin/anjuna-policy-manager /opt/anjuna/gcp/bin/
COPY --from=0 /opt/anjuna/gcp/bin/anjuna-policy-manager-server /opt/anjuna/gcp/bin/
# Copy APM config file and start script
COPY config.hcl /opt/anjuna/policy-manager/
COPY apm-start.sh /opt/anjuna/policy-manager/
RUN chmod +x /opt/anjuna/policy-manager/apm-start.sh
CMD /opt/anjuna/policy-manager/apm-start.sh
EOF
# Build the server Docker image
docker build -t apm-on-gcp-server ./server
# Build and upload the Anjuna custom image for the server
anjuna-gcp-cli disk create --disk-size 4GB --disk server-disk.raw --docker-uri apm-on-gcp-server
anjuna-gcp-cli disk upload \
--disk server-disk.raw \
--bucket "${APM_SERVER_BUCKET}" \
--image "${APM_SERVER_IMAGE}" \
--project "${GCP_PROJECT}"
#============================================================================#
# Deploy the Anjuna Policy Manager Server, initialize it, and create secrets #
#============================================================================#
anjuna-gcp-cli instance create "${APM_SERVER_INSTANCE}" \
--image "${APM_SERVER_IMAGE}" \
--machine "n2d-standard-2" \
--network "${NETWORK_NAME}" \
--subnet "${SUBNET_NAME}" \
--zone "${GCP_ZONE}" \
--service-account "${SERVICE_ACCOUNT_EMAIL}" \
--scopes https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/devstorage.read_write,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/cloudkms
sleep 180
export APM_SERVER_IP=$(gcloud compute instances describe "${APM_SERVER_INSTANCE}" \
--format='get(networkInterfaces[0].accessConfigs[0].natIP)')
# If using the private DNS name, add it to the local hosts file
# so that TLS verification works with the correct hostname
if [ -z "${CERT_FQDN:-}" ]; then
echo "${APM_SERVER_IP} ${APM_SERVER_HOST}" | sudo tee /etc/hosts
fi
export VAULT_ADDR="https://${APM_SERVER_HOST}:8200"
export VAULT_CACERT="$(pwd)/../tls-cert.pem"
# Initialize the APM server
# Note that the root token here is sensitive and allows admin access
# such as reading or writing any secret
anjuna-policy-manager-server operator init | tee init-output.txt
export VAULT_TOKEN=$(cat init-output.txt | grep "Initial Root Token: " | awk '{print $4}')
rm init-output.txt
anjuna-policy-manager-server auth enable apm
# Enable the key-value storage engine
export ANJUNA_ADDR="https://${APM_SERVER_HOST}:8200"
export ANJUNA_CACERT="$(pwd)/../tls-cert.pem"
export ANJUNA_TOKEN="${VAULT_TOKEN}"
anjuna-policy-manager-server secrets enable --path anjuna kv
anjuna-policy-manager secret create \
apm-path/to/secret-env \
--value=SecretEnvValue
anjuna-policy-manager secret create \
apm-path/to/secret-file \
--value=SecretFileValue
#=================================================================#
# Build the client enclave - this is an insecure example app #
# and should not be used in production as all secrets are printed #
#=================================================================#
cat << EOF >client/Dockerfile
# Ubuntu 20.04
FROM ubuntu@sha256:8eb87f3d6c9f2feee114ff0eff93ea9dfd20b294df0a0353bd6a4abf403336fe
CMD export;cat /secret_file.txt;sleep infinity
EOF
docker build -t apm-on-gcp-test-client ./client
# Define the Anjuna Confidential Container configuration,
# which fetches secrets from the Anjuna Policy Manager
cat << EOF >client-config.yaml
version: 1.7
apmConfig:
url: https://${APM_SERVER_HOST}:8200
envs:
- name: SECRET_ENV
engine: anjuna
apmPath: apm-path/to/secret-env
files:
- path: /secret_file.txt
engine: anjuna
apmPath: apm-path/to/secret-file
caCert: |
$(cat ../tls-cert.pem | sed 's/^/ /g')
EOF
# Build and upload the enclave image
anjuna-gcp-cli disk create \
--config client-config.yaml \
--disk-size 2GB \
--disk client-disk.raw \
--docker-uri apm-on-gcp-test-client \
--save-measurements measurements.json
anjuna-gcp-cli disk upload \
--disk client-disk.raw \
--bucket "${APM_CLIENT_BUCKET}" \
--image "${APM_CLIENT_IMAGE}" \
--project "${GCP_PROJECT}"
#=================================================================================#
# Configure the Anjuna Policy Manager to allow the enclave to access the secrets, #
# based on the enclave’s Enclave ID and Signer ID (PCR16) #
#=================================================================================#
# Parse the PCRs from `anjuna-gcp-cli` disk create saved measurements
export SIGNER_ID="$(jq -r .SignerID measurements.json)"
export ENCLAVE_ID="$(jq -r .EnclaveID measurements.json)"
anjuna-policy-manager authorize enclave \
--enclave "${ENCLAVE_ID#0x}" \
--signer "${SIGNER_ID#0x}" \
apm-path/to/secret-env
anjuna-policy-manager authorize enclave \
--enclave "${ENCLAVE_ID#0x}" \
--signer "${SIGNER_ID#0x}" \
apm-path/to/secret-file
#===============================================#
# Deploy the client enclave and view its output #
#===============================================#
anjuna-gcp-cli instance create "${APM_CLIENT_INSTANCE}" \
--image "${APM_CLIENT_IMAGE}" \
--machine "n2d-standard-2" \
--network "${NETWORK_NAME}" \
--subnet "${SUBNET_NAME}" \
--zone "${GCP_ZONE}"
anjuna-gcp-cli instance describe "${APM_CLIENT_INSTANCE}" \
--serial --tail
Generate a self-signed certificate
For development purposes, it may be convenient to generate and use a self-signed certificate. This is not secure for production use cases.
If you need to generate a self-signed cert, copy-paste the following script into create_cert.sh
.
Then you can run it like bash create_cert.sh $APM_SERVER_HOST
.
openssl
is a requirement. You can install it by running sudo apt install openssl
.
#! /bin/bash
if [ "$#" -ne 1 ]
then
echo "Error: No domain name argument provided"
echo "Usage: Provide a domain name as an argument"
exit 1
fi
DOMAIN=$1
# Create root CA & Private key
openssl req -x509 \
-sha256 -days 356 \
-nodes \
-newkey rsa:2048 \
-subj "/CN=dev.anjuna.io/C=US/L=Palo Alto" \
-keyout rootCA.key -out rootCA.crt
# Generate Private key
openssl genrsa -out tls-key.pem 2048
# Create csf conf
cat > csr.conf <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C = US
ST = California
L = Palo Alto
O = Anjuna Security
OU = Anjuna Security Dev
CN = dev.anjuna.io
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = ${DOMAIN}
EOF
# create CSR request using private key
openssl req -new -key tls-key.pem -out server.csr -config csr.conf
# Create a external config file for the certificate
cat > cert.conf <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${DOMAIN}
EOF
# Create SSl with self signed CA
openssl x509 -req \
-in server.csr \
-CA rootCA.crt -CAkey rootCA.key \
-CAcreateserial -out tls-cert.pem \
-days 365 \
-sha256 -extfile cert.conf
Cleanup script
This is all of the commands from Deleting resources, in a single script. Note that this relies on the environment variables from Full script, above, and that deleting cloud resources is a destructive action, which may be impossible to undo.
#!/bin/bash
cat <<EOF
The following resources will be deleted:
- Compute instance ${APM_CLIENT_INSTANCE}
- Storage bucket gs://${APM_CLIENT_BUCKET}/
- Storage bucket gs://${APM_SERVER_BUCKET}/
- Secret ${APM_SERVER_TLS_KEY_SECRET}
- Secret ${APM_SERVER_TLS_CERT_SECRET}
- Storage bucket gs://${APM_SERVER_STORAGE}/
- KMS key ${KMS_KEY} in keyring ${KMS_KEYRING}
- IAM role ${ATTESTATION_ROLE_NAME}
- IAM service account ${SERVICE_ACCOUNT_EMAIL}
- Firewall rule ${NETWORK_NAME}-allow-internal
- Firewall rule ${NETWORK_NAME}-firewall-apm
- Subnet ${SUBNET_NAME}
- Network ${NETWORK_NAME}
EOF
read -p "Are you sure you would like to delete these resources? [y/N]" CONT
if [ "$CONT" != "y" ]; then
echo "Received non-'y' response, exiting"
exit
fi
set -euo pipefail # fail the script if any command fails
set -x # print each command as it is executed
anjuna-gcp-cli instance delete "${APM_CLIENT_INSTANCE}"
gcloud storage rm -r "gs://${APM_CLIENT_BUCKET}/"
anjuna-gcp-cli instance delete "${APM_SERVER_INSTANCE}"
gcloud storage rm -r "gs://${APM_SERVER_BUCKET}/"
gcloud secrets delete "${APM_SERVER_TLS_KEY_SECRET}" \
--quiet
gcloud secrets delete "${APM_SERVER_TLS_CERT_SECRET}" \
--quiet
gcloud storage rm -r "gs://${APM_SERVER_STORAGE}/"
# Assume there is only one version
gcloud kms keys versions destroy \
"1" \
--key "${KMS_KEY}" \
--keyring "${KMS_KEYRING}" \
--location "${KMS_LOCATION}"
gcloud projects remove-iam-policy-binding "${GCP_PROJECT}" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role "projects/${GCP_PROJECT}/roles/${ATTESTATION_ROLE_NAME}"
gcloud iam roles delete \
"${ATTESTATION_ROLE_NAME}" \
--project "${GCP_PROJECT}"
gcloud iam service-accounts delete \
"${SERVICE_ACCOUNT_EMAIL}" \
--quiet
gcloud compute firewall-rules delete \
"${NETWORK_NAME}-allow-internal" \
"${NETWORK_NAME}-firewall-apm" \
--quiet
gcloud compute networks subnets delete \
"${SUBNET_NAME}" \
--region ${GCP_REGION} \
--quiet
gcloud compute networks delete "${NETWORK_NAME}"
--quiet