Skip to content

Instantly share code, notes, and snippets.

@cclloyd
Created January 26, 2025 22:05
Show Gist options
  • Save cclloyd/873cd1cb96260b8ce4152b6ae918db7c to your computer and use it in GitHub Desktop.
Save cclloyd/873cd1cb96260b8ce4152b6ae918db7c to your computer and use it in GitHub Desktop.
Copy LetsEncrypt ACME Certificates from OPNSense to Kubernetes
#!/usr/local/bin/bash
#
# This script takes any certificates found from the ACME client plugin in OPNSense and copies them to one or more namespaces in kubernetes.
# Useful for setting as an automation script in the ACME plugin.
#
# Required env variables: KUBE_TOKEN, KUBE_HOST
# Optional variables: KUBE_PORT, NAMESPACES (space separated string of namespaces)
#
# NOTE: This script requires you to install base64 and bash. Run `pkg install base64 bash`
#
BASE_DIR="/var/etc/acme-client"
KUBE_API="https://${KUBE_HOST}:${KUBE_PORT:-6443}"
NAMESPACES=($(echo "${KUBE_NAMESPACES:-default}"))
[ -z "${KUBE_HOST}" ] && echo "Error: KUBE_HOST is not set." && exit 1
[ -z "${KUBE_TOKEN}" ] && echo "Error: KUBE_TOKEN is not set." && exit 1
# Iterate over each folder in the certificates directory
for CERT_DIR in "$BASE_DIR/certs/"*; do
# Check if it's a directory
[[ -d "$CERT_DIR" ]] || continue
FOLDER_NAME=$(basename "$CERT_DIR")
FULLCHAIN_FILE="$BASE_DIR/certs/$FOLDER_NAME/fullchain.pem"
PRIVATE_KEY_FILE="$BASE_DIR/keys/$FOLDER_NAME/private.key"
# Check if the required files exist
[[ -f "$FULLCHAIN_FILE" ]] || { echo "Fullchain file not found in $BASE_DIR/certs/$FOLDER_NAME. Skipping..."; continue; }
[[ -f "$PRIVATE_KEY_FILE" ]] || { echo "Private key file not found in $BASE_DIR/keys/$FOLDER_NAME. Skipping..."; continue; }
COMMON_NAME=$(openssl x509 -noout -subject -in "$FULLCHAIN_FILE" | sed -n 's/.*CN = \(.*\)/\1/p')
echo "Found cert with common name: $COMMON_NAME"
# Base64 encode the files
FULLCHAIN_CONTENT=$(cat "$FULLCHAIN_FILE" | base64 | tr -d '\n')
PRIVATE_KEY_CONTENT=$(cat "$PRIVATE_KEY_FILE" | base64 | tr -d '\n')
SECRET_NAME="tls-$COMMON_NAME"
# Create the JSON payload for the secret
SECRET_JSON=$(cat <<EOF
{
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "$SECRET_NAME",
"namespace": "$KUBE_NAMESPACE"
},
"type": "kubernetes.io/tls",
"data": {
"tls.crt": "$FULLCHAIN_CONTENT",
"tls.key": "$PRIVATE_KEY_CONTENT"
}
}
EOF
)
# Iterate over each namespace provided
for KUBE_NAMESPACE in "${NAMESPACES[@]}"; do
echo "Processing namespace: $KUBE_NAMESPACE"
# Check if the secret already exists
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -k -H "Authorization: Bearer $KUBE_TOKEN" "$KUBE_API/api/v1/namespaces/$KUBE_NAMESPACE/secrets/$SECRET_NAME")
if [[ "$RESPONSE" == "200" ]]; then
echo "Updating existing secret: $SECRET_NAME in namespace $KUBE_NAMESPACE"
UPDATE_RESPONSE=$(curl -s -o /tmp/update_output -w "%{http_code}" -k -X PUT \
-H "Authorization: Bearer $KUBE_TOKEN" \
-H "Content-Type: application/json" \
-d "$SECRET_JSON" \
"$KUBE_API/api/v1/namespaces/$KUBE_NAMESPACE/secrets/$SECRET_NAME")
if [[ "$UPDATE_RESPONSE" != "200" ]]; then
echo "Failed to update secret: $SECRET_NAME in namespace $KUBE_NAMESPACE"
echo "Status Code: $UPDATE_RESPONSE"
echo "Error Response: $(cat /tmp/update_output)"
else
echo "Secret $SECRET_NAME updated successfully in namespace $KUBE_NAMESPACE."
fi
else
echo "Creating new secret: $SECRET_NAME in namespace $KUBE_NAMESPACE"
CREATE_RESPONSE=$(curl -s -o /tmp/create_output -w "%{http_code}" -k -X POST \
-H "Authorization: Bearer $KUBE_TOKEN" \
-H "Content-Type: application/json" \
-d "$SECRET_JSON" \
"$KUBE_API/api/v1/namespaces/$KUBE_NAMESPACE/secrets")
if [[ "$CREATE_RESPONSE" != "201" ]]; then
echo "Failed to create secret: $SECRET_NAME in namespace $KUBE_NAMESPACE"
echo "Status Code: $CREATE_RESPONSE"
echo "Error Response: $(cat /tmp/create_output)"
else
echo "Secret $SECRET_NAME created successfully in namespace $KUBE_NAMESPACE."
fi
fi
done
echo "Processed secret for $COMMON_NAME in all namespaces"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment