Kubernetes secrets are frequently used to securely store sensitive data, including passwords, API credentials, and certificates. In some cases, it may be necessary to compare secrets across several namespaces, clusters, or even two sets deployed by different systems.
In this article, we will look at easy and effective methods for analyzing two sets of Kubernetes secrets.
I can't claim that comparing Kubernetes secrets is part of the usual routine for DevOps or cloud engineers; however, it may be necessary for the following reasons:
To deal with any of these scenarios, a structural (keys and formats) or combination (structure and values) comparison of Kubernetes secrets may be required. Let's see how to do it.
To compare Kubernetes secrets, we need to perform the steps listed below, which we will implement as a script further:
kvendingoldo1
) using the Kubernetes API (kubectl).alextest
) using the Kubernetes API (kubectl).
The basic implementation of the algorithm is something like this:
#!/bin/bash
#
# inputs
#
SECRET1_PREFIX="${1}"
SECRET1_NS="${2}"
SECRET2_PREFIX="${3}"
SECRET2_NS="${4}"
#
# ANSI color codes
#
COLOR_GREEN="\033[0;32m"
COLOR_YELLOW="\033[1;33m"
COLOR_RED="\033[0;31m"
COLOR_RESET="\033[0m"
function get_secrets_with_prefix() {
local prefix="${1}"
kubectl get secrets -n="${SECRET1_NS}" -o json | jq -r ".items[] | select(.metadata.name | startswith(\"${prefix}\")) | .metadata.name"
}
function mask_value() {
local value=${1}
local length=${#value}
local mask_length=$((length / 2))
local visible_part=${value:2:mask_length}
echo "${visible_part}******"
}
function compare_secrets() {
local secret1=${1}
local secret2=${2}
# Get keys (structure) for both secrets
keys1=$(kubectl get secret -n="${SECRET1_NS}" "${secret1}" -o json | jq -r '.data | keys[]')
keys2=$(kubectl get secret -n="${SECRET2_NS}" "${secret2}" -o json | jq -r '.data | keys[]')
# Find common keys between the two secrets
common_keys=$(echo -e "${keys1}\n${keys2}" | sort | uniq -d)
if [ -z "${common_keys}" ]; then
echo -e "No matching keys between ${secret1} and ${secret2}"
return
fi
# Compare values of each common key
for key in ${common_keys}; do
value1=$(kubectl get secret -n="${SECRET1_NS}" "${secret1}" -o json | jq -r ".data[\"${key}\"]" | base64 --decode)
value2=$(kubectl get secret -n="${SECRET2_NS}" "${secret2}" -o json | jq -r ".data[\"${key}\"]" | base64 --decode)
if [ "${value1}" != "${value2}" ]; then
echo -e "${COLOR_YELLOW}Difference found in key '${key}':${COLOR_RESET}"
echo -e " - ${secret1}: $(mask_value ${value1})"
echo -e " - ${secret2}: $(mask_value ${value2})"
else
echo -e "${COLOR_GREEN}Key '${key}' is identical in both secrets.${COLOR_RESET}"
fi
done
# Check for keys that are missing in one of the secrets
for key in ${keys1}; do
if ! echo -e "${keys2}" | grep -q "${key}"; then
echo -e "${COLOR_RED}Key '${key}' is missing in ${secret2}.${COLOR_RESET}"
fi
done
for key in ${keys2}; do
if ! echo -e "${keys1}" | grep -q "${key}"; then
echo -e "${COLOR_RED}Key '${key}' is missing in ${secret1}.${COLOR_RESET}"
fi
done
}
function main() {
secrets1=$(get_secrets_with_prefix "${SECRET1_PREFIX}")
secrets2=$(get_secrets_with_prefix "${SECRET2_PREFIX}")
for s1 in ${secrets1}; do
suffix=${s1#$SECRET1_PREFIX} # Get the suffix by removing the prefix
s2="${SECRET2_PREFIX}${suffix}" # Construct the corresponding secret in the second namespace
# Check if the corresponding secret exists
if echo -e "${secrets2}" | grep -q "${s2}"; then
echo -e "Comparing ${s1} and ${s2}:"
compare_secrets "${s1}" "${s2}"
echo -e
else
echo -e "No corresponding secret for ${s1} in ${SECRET2_PREFIX}."
fi
done
}
main "${@}"
As you can see, the basic script supports different Kubernetes clusters, but it’s not difficult to implement.Let's do it with easy modifications:
Add KUBECONFIG variables for each secret set
KUBECONFIG1="${1}"
SECRET1_PREFIX="${2}"
SECRET1_NS="${3}"
KUBECONFIG2="${4}"
SECRET2_PREFIX="${5}"
SECRET2_NS="${6}"
Add kubeconfig variable into get_secrets_with_prefix
function
function get_secrets_with_prefix() {
local kubeconfig=${1}
local prefix=${2}
local namespace=${3}
KUBECONFIG=${kubeconfig} kubectl get secrets -n=${namespace} -o json | jq -r ".items[] | select(.metadata.name | startswith(\"${prefix}\")) | .metadata.name"
}
Add kubeconfig1
and kubeconfig2
variables to compare_secrets
As a result, you will get the following script that can work with different clusters:
#!/bin/bash
#
# inputs
#
KUBECONFIG1="${1}"
SECRET1_PREFIX="${2}"
SECRET1_NS="${3}"
KUBECONFIG2="${4}"
SECRET2_PREFIX="${5}"
SECRET2_NS="${6}"
#
# ANSI color codes
#
COLOR_GREEN="\033[0;32m"
COLOR_YELLOW="\033[1;33m"
COLOR_RED="\033[0;31m"
COLOR_RESET="\033[0m"
function mask_value() {
local value=${1}
local length=${#value}
local mask_length=$((length / 2))
local visible_part=${value:2:mask_length}
echo "${visible_part}******"
}
function get_secrets_with_prefix() {
local kubeconfig="${1}"
local prefix="${2}"
local namespace="${3}"
KUBECONFIG=${kubeconfig} kubectl get secrets -n=${namespace} -o json | jq -r ".items[] | select(.metadata.name | startswith(\"${prefix}\")) | .metadata.name"
}
function compare_secrets() {
local kubeconfig1="${1}"
local kubeconfig2="${2}"
local secret1=""${3}"
local secret2="${4}"
# Get keys (structure) for both secrets
keys1=$(KUBECONFIG=${kubeconfig1} kubectl get secret -n="${SECRET1_NS}" "${secret1}" -o json | jq -r '.data | keys[]')
keys2=$(KUBECONFIG=${kubeconfig2} kubectl get secret -n="${SECRET2_NS}" "${secret2}" -o json | jq -r '.data | keys[]')
# Find common keys between the two secrets
common_keys=$(echo -e "${keys1}\n${keys2}" | sort | uniq -d)
if [ -z "${common_keys}" ]; then
echo -e "No matching keys between ${secret1} and ${secret2}"
return
fi
# Compare values of each common key
for key in ${common_keys}; do
value1=$(KUBECONFIG=${kubeconfig1} kubectl get secret -n="${SECRET1_NS}" "${secret1}" -o json | jq -r ".data[\"${key}\"]" | base64 --decode)
value2=$(KUBECONFIG=${kubeconfig2} kubectl get secret -n="${SECRET2_NS}" "${secret2}" -o json | jq -r ".data[\"${key}\"]" | base64 --decode)
if [ "${value1}" != "${value2}" ]; then
echo -e "${COLOR_YELLOW}Difference found in key '${key}':${COLOR_RESET}"
echo -e " - ${secret1}: $(mask_value ${value1})"
echo -e " - ${secret2}: $(mask_value ${value2})"
else
echo -e "${COLOR_GREEN}Key '${key}' is identical in both secrets.${COLOR_RESET}"
fi
done
# Check for keys that are missing in one of the secrets
for key in ${keys1}; do
if ! echo -e "${keys2}" | grep -q "${key}"; then
echo -e "${COLOR_RED}Key '${key}' is missing in ${secret2}.${COLOR_RESET}"
fi
done
for key in ${keys2}; do
if ! echo -e "${keys1}" | grep -q "${key}"; then
echo -e "${COLOR_RED}Key '${key}' is missing in ${secret1}.${COLOR_RESET}"
fi
done
}
function main() {
secrets1=$(get_secrets_with_prefix "${KUBECONFIG1}" "${SECRET1_PREFIX}" "${SECRET1_NS}")
secrets2=$(get_secrets_with_prefix "${KUBECONFIG2}" "${SECRET2_PREFIX}" "${SECRET2_NS}")
for s1 in ${secrets1}; do
suffix=${s1#$SECRET1_PREFIX} # Get the suffix by removing the prefix
s2="${SECRET2_PREFIX}${suffix}" # Construct the corresponding secret in the second namespace
# Check if the corresponding secret exists
if echo -e "${secrets2}" | grep -q "${s2}"; then
echo -e "Comparing ${s1} in Cluster1 and ${s2} in Cluster2:"
compare_secrets "${KUBECONFIG1}" "${KUBECONFIG2}" "${s1}" "${s2}"
echo -e
else
echo -e "No corresponding secret for ${s1} in Cluster2 (${SECRET2_PREFIX})."
fi
done
}
main "${@}"
To test the script, let's generate test data using another bash script. The script source code:
#!/bin/bash
#
# inputs
#
SECRET1_PREFIX="${1}"
SECRET1_NS="${2}"
SECRET2_PREFIX="${3}"
SECRET2_NS="${4}"
NUM_SECRETS=5 # Number of secrets to generate
NUM_KEYS=4 # Number of keys per secret
function generate_random_value() {
head -c 8 /dev/urandom | base64
}
function create_test_secrets() {
local prefix=${1}
local namespace=${2}
for i in $(seq 1 $NUM_SECRETS); do
secret_name="${prefix}test-secret-${i}"
data=""
if (( i % 3 == 1 )); then
# Generate identical values
data=" key1: $(echo -n 'fixedValue' | base64)\n key2: $(echo -n 'fixedValue' | base64)\n key3: $(echo -n 'fixedValue' | base64)"
elif (( i % 3 == 2 )); then
# Generate different values
data=" key1: $(generate_random_value)\n key2: $(generate_random_value)\n key3: $(generate_random_value)"
else
# Generate different keys
data=" keyA: $(generate_random_value)\n keyB: $(generate_random_value)"
fi
echo "Creating secret ${secret_name} in namespace ${namespace}"
echo -e "apiVersion: v1\nkind: Secret\nmetadata:\n name: ${secret_name}\n namespace: ${namespace}\ntype: Opaque\ndata:\n${data}" | kubectl apply -f -
done
}
function main() {
create_test_secrets "${SECRET1_PREFIX}" "${SECRET1_NS}"
create_test_secrets "${SECRET2_PREFIX}" "${SECRET2_NS}"
}
main "${@}"
To run it, do the following commands:
$ bash generate_test_data.sh kvendingoldo test-secrets-demo alextest test-secrets-demo
$ bash compare_secrets.sh kvendingoldo test-secrets-demo alextest test-secrets-demo
Following that, you will see the output log, which shows that some secrets are identical while others have different values:
Comparing Kubernetes secrets is essential to maintaining consistency, security, and versioning in your application secrets. The approach described in this post is an excellent solution for rapid manual activities, which CloudOps/DevOps teams may need. You may need to modify the script a little bit for your task, but this should be easy thanks to the Bash language.
Feel free to experiment, and keep your secrets safeguarded!