Skip to content

Instantly share code, notes, and snippets.

@hiyosi
Created September 20, 2025 12:53
Show Gist options
  • Select an option

  • Save hiyosi/0bbe68c2f3a5de37ab36e5a86f7887a2 to your computer and use it in GitHub Desktop.

Select an option

Save hiyosi/0bbe68c2f3a5de37ab36e5a86f7887a2 to your computer and use it in GitHub Desktop.
PodCertificateRequest Issuer
#!/bin/bash
set -euo pipefail
# Configuration
NAMESPACE="${NAMESPACE:-default}"
PCR_NAME="${PCR_NAME:-}"
CA_KEY="${CA_KEY:-ca.key}"
CA_CRT="${CA_CRT:-ca.crt}"
POD_NAME="${POD_NAME:-pod-cert-test}"
CREATE_POD="${CREATE_POD:-false}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
}
error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
exit 1
}
# Show usage
show_usage() {
cat <<EOF
Pod Certificate Issuer Script
Usage: $0 [OPTIONS]
Options:
-h, --help Show this help message
-c, --create-pod Create a Pod with podCertificate volume
-p, --pod-name NAME Pod name (default: pod-cert-test)
-n, --namespace NAME Namespace (default: default)
--pcr-name NAME PodCertificateRequest name (auto-detect if not specified)
--ca-key FILE CA private key file (default: ca.key)
--ca-crt FILE CA certificate file (default: ca.crt)
Environment Variables:
NAMESPACE Target namespace (default: default)
PCR_NAME PodCertificateRequest name
POD_NAME Pod name (default: pod-cert-test)
CREATE_POD Create pod if true (default: false)
CA_KEY CA private key file (default: ca.key)
CA_CRT CA certificate file (default: ca.crt)
Examples:
# Issue certificate for existing PodCertificateRequest
$0
# Create Pod and issue certificate
$0 --create-pod
# Create Pod with custom name and issue certificate
$0 --create-pod --pod-name my-test-pod
# Specify namespace and PCR name
$0 --namespace kube-system --pcr-name req-12345
EOF
}
# Check prerequisites
check_prerequisites() {
log "Checking prerequisites..."
# Check required commands
for cmd in kubectl openssl jq gdate; do
if ! command -v $cmd &> /dev/null; then
error "$cmd is required but not installed"
fi
done
# Check CA files
if [[ ! -f "$CA_KEY" ]]; then
error "CA private key not found: $CA_KEY"
fi
if [[ ! -f "$CA_CRT" ]]; then
error "CA certificate not found: $CA_CRT"
fi
# Check OpenSSL version for -force_pubkey support
if ! openssl x509 -help 2>&1 | grep -q "force_pubkey"; then
error "OpenSSL version does not support -force_pubkey option"
fi
log "Prerequisites check passed"
}
# Create Pod if requested
create_pod_if_needed() {
if [[ "$CREATE_POD" == "true" ]]; then
log "Creating Pod with podCertificate volume..."
# Check if pod already exists
if kubectl get pod "$POD_NAME" -n "$NAMESPACE" &>/dev/null; then
log "Pod $POD_NAME already exists in namespace $NAMESPACE"
return 0
fi
# Create pod YAML
cat > pod-with-cert.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: $POD_NAME
namespace: $NAMESPACE
spec:
containers:
- name: test-container
image: alpine:3.18
command: ["/bin/sh"]
args: ["-c", "while true; do echo 'Waiting for certificates...'; ls -la /etc/certs/; sleep 30; done"]
volumeMounts:
- name: pod-certs
mountPath: /etc/certs
readOnly: true
volumes:
- name: pod-certs
projected:
sources:
- podCertificate:
signerName: signer-hiyosi
keyType: ECDSAP256
maxExpirationSeconds: 86400
credentialBundlePath: credential-bundle.pem
EOF
# Apply the pod
kubectl apply -f pod-with-cert.yaml
log "Pod $POD_NAME created successfully"
fi
}
# Find PodCertificateRequest
find_pcr() {
if [[ -z "$PCR_NAME" ]]; then
log "Finding PodCertificateRequest in namespace $NAMESPACE..."
# Wait for PodCertificateRequest to be created (max 60 seconds)
local max_wait=60
local wait_time=0
while [[ $wait_time -lt $max_wait ]]; do
PCR_NAME=$(kubectl get podcertificaterequest -n "$NAMESPACE" --no-headers -o custom-columns=":metadata.name" 2>/dev/null | head -1)
if [[ -n "$PCR_NAME" ]]; then
log "Found PodCertificateRequest: $PCR_NAME"
return 0
fi
if [[ $wait_time -eq 0 ]]; then
log "Waiting for PodCertificateRequest to be created..."
fi
sleep 5
wait_time=$((wait_time + 5))
echo -n "."
done
echo # New line after dots
error "No PodCertificateRequest found in namespace $NAMESPACE after ${max_wait}s"
fi
}
# Extract PCR information
extract_pcr_info() {
log "Extracting PodCertificateRequest information..."
kubectl get podcertificaterequest "$PCR_NAME" -n "$NAMESPACE" -o json > pcr.json
PUBLIC_KEY_B64=$(jq -r '.spec.pkixPublicKey' pcr.json)
POD_NAME=$(jq -r '.spec.podName' pcr.json)
POD_UID=$(jq -r '.spec.podUID' pcr.json)
NODE_NAME=$(jq -r '.spec.nodeName' pcr.json)
NODE_UID=$(jq -r '.spec.nodeUID' pcr.json)
SERVICE_ACCOUNT=$(jq -r '.spec.serviceAccountName' pcr.json)
SERVICE_ACCOUNT_UID=$(jq -r '.spec.serviceAccountUID' pcr.json)
MAX_EXPIRATION_SECONDS=$(jq -r '.spec.maxExpirationSeconds // 86400' pcr.json)
NAMESPACE_UID=$(kubectl get namespace "$NAMESPACE" -o jsonpath='{.metadata.uid}')
log "Pod: $POD_NAME, Service Account: $SERVICE_ACCOUNT, Max Expiration: ${MAX_EXPIRATION_SECONDS}s"
}
# Convert public key
convert_public_key() {
log "Converting public key..."
echo "$PUBLIC_KEY_B64" | base64 -d > pod-public.der
openssl ec -pubin -inform DER -in pod-public.der -outform PEM -out pod-public.pem
log "Public key converted to PEM format"
}
# Create certificate configuration
create_cert_config() {
log "Creating certificate configuration..."
# Certificate subject configuration
cat > cert.cnf <<EOF
[req]
distinguished_name = req_dn
prompt = no
[req_dn]
CN = system:pod:${NAMESPACE}:${POD_NAME}
O = system:pods
EOF
# Certificate extensions configuration
cat > ext.cnf <<EOF
basicConstraints = CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
# Subject Alternative Names
subjectAltName = @alt_names
# Pod Identity Extension - Official Kubernetes OID (KEP-4317)
1.3.6.1.4.1.57683.1 = critical,ASN1:SEQUENCE:pod_identity_section
[alt_names]
DNS.1 = ${POD_NAME}
DNS.2 = ${POD_NAME}.${NAMESPACE}
DNS.3 = ${POD_NAME}.${NAMESPACE}.pod.cluster.local
[pod_identity_section]
# Fields with implicit tags as per KEP-4317 specification
field0 = IMP:0,UTF8:${NAMESPACE}
field1 = IMP:1,UTF8:${NAMESPACE_UID}
field2 = IMP:2,UTF8:${SERVICE_ACCOUNT}
field3 = IMP:3,UTF8:${SERVICE_ACCOUNT_UID}
field4 = IMP:4,UTF8:${POD_NAME}
field5 = IMP:5,UTF8:${POD_UID}
field6 = IMP:6,UTF8:${NODE_NAME}
field7 = IMP:7,UTF8:${NODE_UID}
EOF
log "Certificate configuration created"
}
# Issue certificate
issue_certificate() {
log "Issuing certificate..."
# Create temporary private key for CSR (will be replaced)
openssl ecparam -genkey -name prime256v1 -out temp-private.pem
# Create CSR with temporary key
openssl req -new -key temp-private.pem -out temp.csr -config cert.cnf
# Calculate certificate validity
DAYS=$((MAX_EXPIRATION_SECONDS / 86400))
if [[ $DAYS -eq 0 ]]; then
DAYS=1
fi
# Issue certificate with actual public key
openssl x509 -req -in temp.csr -CA "$CA_CRT" -CAkey "$CA_KEY" -CAcreateserial \
-out pod-cert.crt -days $DAYS -extfile ext.cnf \
-force_pubkey pod-public.pem
log "Certificate issued successfully"
}
# Create credential bundle
create_credential_bundle() {
log "Creating credential bundle..."
cat pod-cert.crt ca.crt > credential-bundle.pem
log "Credential bundle created"
}
# Update PodCertificateRequest status
update_pcr_status() {
log "Updating PodCertificateRequest status..."
# Extract certificate dates
NOT_BEFORE=$(openssl x509 -in pod-cert.crt -noout -startdate | cut -d= -f2)
NOT_BEFORE=$(gdate -u -d "$NOT_BEFORE" +"%Y-%m-%dT%H:%M:%SZ")
# Calculate notAfter
NOT_BEFORE_EPOCH=$(gdate -u -d "$NOT_BEFORE" +%s)
NOT_AFTER=$(gdate -u -d "@$((NOT_BEFORE_EPOCH + MAX_EXPIRATION_SECONDS))" +"%Y-%m-%dT%H:%M:%SZ")
# Calculate refresh time (80% of validity period)
REFRESH_SECONDS=$((MAX_EXPIRATION_SECONDS * 80 / 100))
BEGIN_REFRESH_AT=$(gdate -u -d "@$((NOT_BEFORE_EPOCH + REFRESH_SECONDS))" +"%Y-%m-%dT%H:%M:%SZ")
# Convert certificate chain to JSON string
CREDENTIAL_BUNDLE=$(cat credential-bundle.pem | jq -Rs '.')
# Create patch JSON
cat > patch.json <<EOF
{
"status": {
"certificateChain": $CREDENTIAL_BUNDLE,
"notBefore": "$NOT_BEFORE",
"notAfter": "$NOT_AFTER",
"beginRefreshAt": "$BEGIN_REFRESH_AT",
"conditions": [
{
"type": "Issued",
"status": "True",
"reason": "Issued",
"message": "Certificate issued with KEP-4317 Pod Identity extension",
"lastTransitionTime": "$NOT_BEFORE"
}
]
}
}
EOF
# Apply patch
kubectl patch podcertificaterequest "$PCR_NAME" -n "$NAMESPACE" \
--type=merge --subresource=status -p "$(cat patch.json)"
log "PodCertificateRequest status updated successfully"
}
# Verify certificate
verify_certificate() {
log "Verifying certificate..."
# Check certificate validity
openssl x509 -in pod-cert.crt -noout -text | grep -A 5 "Validity"
# Verify public key match
PCR_PUBKEY=$(openssl ec -pubin -in pod-public.pem -text -noout | grep -A 10 "pub:" | grep -E "^\s*[0-9a-f]{2}:" | tr -d ' :' | tr -d '\n')
CERT_PUBKEY=$(openssl x509 -in pod-cert.crt -pubkey -noout | openssl ec -pubin -text -noout | grep -A 10 "pub:" | grep -E "^\s*[0-9a-f]{2}:" | tr -d ' :' | tr -d '\n')
if [[ "$PCR_PUBKEY" == "$CERT_PUBKEY" ]]; then
log "Public key verification: PASSED"
else
error "Public key verification: FAILED"
fi
# Check Pod Identity extension
if openssl x509 -in pod-cert.crt -text -noout | grep -q "1.3.6.1.4.1.57683.1"; then
log "Pod Identity extension: PRESENT"
else
warn "Pod Identity extension: NOT FOUND"
fi
}
# Cleanup temporary files
cleanup() {
log "Cleaning up temporary files..."
rm -f temp-private.pem temp.csr pod-public.der cert.cnf ext.cnf pcr.json
log "Cleanup completed"
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-c|--create-pod)
CREATE_POD="true"
shift
;;
-p|--pod-name)
POD_NAME="$2"
shift 2
;;
-n|--namespace)
NAMESPACE="$2"
shift 2
;;
--pcr-name)
PCR_NAME="$2"
shift 2
;;
--ca-key)
CA_KEY="$2"
shift 2
;;
--ca-crt)
CA_CRT="$2"
shift 2
;;
*)
error "Unknown option: $1"
;;
esac
done
}
# Main execution
main() {
parse_args "$@"
log "Starting Pod Certificate Issuer"
check_prerequisites
create_pod_if_needed
find_pcr
extract_pcr_info
convert_public_key
create_cert_config
issue_certificate
create_credential_bundle
update_pcr_status
verify_certificate
cleanup
log "Certificate issuance completed successfully!"
log "PodCertificateRequest: $PCR_NAME"
log "Certificate files created: pod-cert.crt, credential-bundle.pem"
# Show final status
kubectl get podcertificaterequest "$PCR_NAME" -n "$NAMESPACE"
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment