cleanup-cert-manager-secrets.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
#!/bin/bash
usage() {
cat << EOF
This script detects TLS secrets which refer to certificates that don't exist (anymore).
This is the case when error "unable to fetch certificate that owns the secret" occurs in cert-manager (cainjector) logs.
The reason is that a certificate has been removed without the secret being deleted.
The solution is to clean up by deleting any secret which belonged to a certificate that no longer exists.
Usage: $0 [-n namespace] [-v] $0
-n | --namespace <namespace> (optional, default="all")
-d | --dry-run <true/false> (optional, default=true)
-v | --verbose (optional)
-h | --help (displays help and exits)
Examples:
Check the TLS secrets and matching certificates in all namespaces, print only the end result
$0
Check the TLS secrets and matching certificates in the "mynamespace" namespace, print only the end result
$0 -n mynamespace
Check the TLS secrets and matching certificates in all namespaces, print info about all TLS secrets and certificates
$0 -v
Check the TLS secrets and matching certificates in all namespaces, delete all "safe" secrets
$0 -d false
EOF
} namespace="all"
dry_run=true
printfLog='silentPrintf'
while [ "$1" != "" ] do
case $1 in
-n | --namespace )
shift
namespace=$1
;;
-d | --dry-run )
shift
if [ "$1" == "true" ]; then
dry_run=true
elif [ "$1" == "false" ]; then
dry_run=false
else
echo "Invalid value for -d|--dry-run. Use 'true' or 'false'."
exit 1
fi
;;
-v | --verbose )
shift
printfLog='printf'
;;
-h | --help )
usage
exit
;;
* )
usage
exit 1
esac
shift
done
silentPrintf() {
:
}
check_tls_cert_expired() {
local secret_name=$1
local namespace=$2
cert_data=$(kubectl get secret "$secret_name" -n "$namespace" -o jsonpath='{.data.tls\.crt}' | base64 --decode)
if [ -z "$cert_data" ]; then
echo "Secret $secret_name in namespace $namespace does not contain a valid TLS certificate."
return 1
fi
if ! echo "$cert_data" | openssl x509 -noout -text > /dev/null 2>&1; then
echo "Secret $secret_name in namespace $namespace contains an invalid TLS certificate."
return 1
fi
expiration_date=$(echo "$cert_data" | openssl x509 -noout -enddate | cut -d= -f2)
if [[ "$OSTYPE" == "darwin"* ]]; then
expiration_epoch=$(date -j -f "%b %d %T %Y %Z" "$expiration_date" "+%s")
else
expiration_epoch=$(date -d "$expiration_date" "+%s")
fi
current_epoch=$(date "+%s")
if [ -z "$expiration_epoch" ]; then
echo "Failed to parse expiration date for secret $secret_name."
return 1
fi
[ "$current_epoch" -gt "$expiration_epoch" ]
return $?
}
last_namespace=""
last_namespace_resources=""
check_tls_cert_referenced() {
local secret_name=$1
local namespace=$2
if [[ "$namespace" != "$last_namespace" ]]; then
printf "Fetching resources for namespace %s ...\n" "$namespace"
last_namespace_resources=$(kubectl get all -n "$namespace" -o json)
last_namespace="$namespace"
fi
printf "Searching for secret %s in namespace %s ...\n" "$secret_name" "$namespace"
resources="$last_namespace_resources"
if echo "$resources" | grep -q "$secret_name"; then
printf "Secret %s is used in the namespace %s\n" "$secret_name" "$namespace"
return 0
else
printf "Secret %s is not used in the namespace %s\n" "$secret_name" "$namespace"
return 1
fi
}
check_tls_secrets_and_certs() {
local namespace=$1
printf "\nProcessing namespace: %s\n" "$namespace"
secrets_with_certificate_name=$(kubectl get secret -n "$namespace" -o jsonpath="{range .items[?(.type=='kubernetes.io/tls')]}{.metadata.name},{.metadata.annotations.cert-manager\.io/certificate-name} {end}")
$printfLog "Listing secrets (of type kubernetes.io/tls) with their annotated certificate:\n"
for i in ${secrets_with_certificate_name[@]}; do
$printfLog "$i \n"
done
$printfLog "\nListing certificates:\n"
$printfLog "$(kubectl get certificate -n $namespace|awk '{print $1}'|grep -v "NAME")\n"
secrets_without_certs=()
$printfLog "\nLooking for certificates matching the secrets:\n"
for secret_with_cert_name in ${secrets_with_certificate_name[@]}; do
IFS="," read -r -a details <<< "$secret_with_cert_name"
secret_name=${details[0]}
cert=${details[1]}
$printfLog "Secret $secret_name is made using cert $cert. Looking for that cert...\n"
certmatch=$(kubectl get certificate -n "$namespace" -o jsonpath="{range .items[?(.metadata.name=='$cert')]}{.metadata.name} {end}")
if [[ $certmatch == "" ]]; then
secrets_without_certs+=("$secret_name")
$printfLog "\n##### WARNING: Secret $secret_name has no matching certificate ($cert). #####\n\n"
else
$printfLog "Certificate found.\n"
fi
done
expired_secrets_without_certs=()
secrets_seemingly_unused=()
secrets_in_use=()
$printfLog "\nChecking secrets in namespace $namespace that have no matching certificates:\n"
for secret in ${secrets_without_certs[@]}; do
if check_tls_cert_expired "$secret" "$namespace"; then
expired_secrets_without_certs+=("$secret")
elif ! check_tls_cert_referenced "$secret" "$namespace"; then
secrets_seemingly_unused+=("$secret")
else
secrets_in_use+=("$secret")
fi
done
if [ ${#secrets_in_use[@]} -ne 0 ]; then
printf "\nSecrets still in use with no matching certificates (should not be deleted):\n"
for secret in ${secrets_in_use[@]}; do
printf "%s \n" "$secret"
done
fi
if [ ${#secrets_seemingly_unused[@]} -ne 0 ]; then
printf "\nSecrets probably not in use with no matching certificates (possibly could be deleted):\n"
if [ "$dry_run" = true ]; then
for secret in ${secrets_seemingly_unused[@]}; do
printf "%s \n" "$secret"
done
else
for secret in ${secrets_seemingly_unused[@]}; do
kubectl delete secret -n "$namespace" "$secret"
done
fi
fi
if [ ${#expired_secrets_without_certs[@]} -ne 0 ]; then
printf "\nExpired Secrets with no matching certificates (probably can be deleted):\n"
if [ "$dry_run" = true ]; then
for secret in ${expired_secrets_without_certs[@]}; do
printf "%s \n" "$secret"
done
else
for secret in ${expired_secrets_without_certs[@]}; do
kubectl delete secret -n "$namespace" "$secret"
done
fi
fi
}
if [ "$namespace" == "all" ]; then
namespaces=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}')
for ns in $namespaces; do
check_tls_secrets_and_certs "$ns"
done
else
check_tls_secrets_and_certs "$namespace"
fi