-
Notifications
You must be signed in to change notification settings - Fork 3
/
rotate-encryption-keys.sh
executable file
·339 lines (282 loc) · 11.5 KB
/
rotate-encryption-keys.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#! /usr/bin/env bash
#
# Copyright contributors to the Galasa project
#
# SPDX-License-Identifier: EPL-2.0
#
#-----------------------------------------------------------------------------------------
#
# Objective: Rotates the encryption key currently being used to encrypt secrets in
# the Galasa Ecosystem and re-encrypts secrets using the new encryption key
#
# Environment variable overrides:
# None
#
#-----------------------------------------------------------------------------------------
# Where is this script executing from?
BASEDIR=$(dirname "$0");pushd $BASEDIR 2>&1 >> /dev/null ;BASEDIR=$(pwd);popd 2>&1 >> /dev/null
cd "${BASEDIR}/.."
WORKSPACE_DIR=$(pwd)
#-----------------------------------------------------------------------------------------
#
# Set Colors
#
#-----------------------------------------------------------------------------------------
bold=$(tput bold)
underline=$(tput sgr 0 1)
reset=$(tput sgr0)
red=$(tput setaf 1)
green=$(tput setaf 76)
white=$(tput setaf 7)
tan=$(tput setaf 202)
blue=$(tput setaf 25)
#-----------------------------------------------------------------------------------------
#
# Headers and Logging
#
#-----------------------------------------------------------------------------------------
underline() { printf "${underline}${bold}%s${reset}\n" "$@" ;}
h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@" ;}
h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@" ;}
debug() { printf "${white}%s${reset}\n" "$@" ;}
info() { printf "${white}➜ %s${reset}\n" "$@" ;}
success() { printf "${green}✔ %s${reset}\n" "$@" ;}
error() { printf "${red}✖ %s${reset}\n" "$@" ;}
warn() { printf "${tan}➜ %s${reset}\n" "$@" ;}
bold() { printf "${bold}%s${reset}\n" "$@" ;}
note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" ;}
#-----------------------------------------------------------------------------------------
# Functions
#-----------------------------------------------------------------------------------------
function usage {
h1 "A helper script to rotate Galasa ecosystem encryption keys."
info "The following command-line tools must be installed before running this script:"
cat << EOF
kubectl (v1.30.3 or later)
openssl (3.3.2 or later)
galasactl (0.38.0 or later)
EOF
info "Syntax: rotate-encryption-keys.sh [OPTIONS]"
cat << EOF
Options are:
--release-name <name> : Required. The Helm release name provided when the Galasa ecosystem Helm chart was installed.
-b | --bootstrap <bootstrap-url> : Optional. The bootstrap URL of the Galasa ecosystem that is being serviced. Not required if the GALASA_BOOTSTRAP environment variable is set and is pointing to the correct bootstrap URL.
-n | --namespace <namespace> : Optional. The Kubernetes namespace in which your Galasa ecosystem is running. By default, the namespace pointed to by the current Kubernetes context will be used.
EOF
}
function check_exit_code {
# This function takes 2 parameters in the form:
# $1 an integer value of the returned exit code
# $2 an error message to display if $1 is not equal to 0
return_code=$1
error_msg=$2
if [[ "${return_code}" != "0" ]]; then
error "${error_msg}. Log file is at ${LOG_FILE}"
exit 1
fi
}
function check_tool_installed {
# This function takes 1 parameter:
# $1 the name of the tool to look for
tool_name=$1
h2 "Checking ${tool_name} is installed..."
which ${tool_name} 2>&1 > /dev/null
rc=$?
check_exit_code ${rc} "${tool_name} is not installed. Install it and try again. rc=${rc}"
success "${tool_name} is installed OK"
}
function check_required_tools_installed {
h1 "Checking required CLI tools are installed"
check_tool_installed "kubectl"
check_tool_installed "openssl"
check_tool_installed "galasactl"
success "All required tools are already installed OK"
}
function check_galasactl_auth {
h1 "Logging into the Galasa ecosystem"
info "Bootstrap URL is ${GALASA_BOOTSTRAP}"
cmd="galasactl auth login"
info "Running command: ${cmd}"
${cmd} 2>&1 | tee -a "${LOG_FILE}"
rc=$?
check_exit_code ${rc} "Failed to log in to the Galasa ecosystem secret. Check that a personal access token has been set and try again. rc=${rc}"
success "Logged in to the Galasa ecosystem OK"
}
function get_existing_encryption_keys {
h1 "Retrieving encryption keys"
# Get the current secret data
decoded_secret_data=$(kubectl get secret ${ENCRYPTION_SECRET_NAME} \
${KUBECTL_NAMESPACE_FLAG} \
--output jsonpath="{ .data.encryption-keys\.yaml }" | base64 --decode)
rc=$?
check_exit_code ${rc} "Failed to get encryption keys from ${ENCRYPTION_SECRET_NAME} secret. Check that the value for the --release-name flag matches an existing Helm release and try again. rc=${rc}"
# Extract the current encryption key
export CURRENT_KEY=$(echo "${decoded_secret_data}" | grep "encryptionKey:" | awk '{print $2}')
rc=$?
check_exit_code ${rc} "Failed to get current encryption key from ${ENCRYPTION_SECRET_NAME} secret. rc=${rc}"
# Extract the fallback decryption keys list
export FALLBACK_KEYS=$(echo "${decoded_secret_data}" | awk '/^fallbackDecryptionKeys:/,/^$/' | tail -n +2)
rc=$?
check_exit_code ${rc} "Failed to get fallback keys from ${ENCRYPTION_SECRET_NAME} secret. rc=${rc}"
success "Existing keys retrieved OK"
}
function rotate_encryption_keys {
h1 "Rotating encryption keys"
# Create a new AES256 encryption key - this must be 32 characters long for 256-bit keys
info "Creating new encryption key..."
cmd="openssl rand -base64 32"
info "Running command: ${cmd}"
new_key=$(${cmd})
rc=$?
check_exit_code ${rc} "Failed to create a new encryption key. rc=${rc}"
success "Created new encryption key OK"
# Add the existing encryption key to the start of the fallback keys list
updated_fallback_keys_yaml=$(cat << EOF
fallbackDecryptionKeys:
- ${CURRENT_KEY}
${FALLBACK_KEYS}
EOF
)
# Update the Kubernetes Secret with the rotated encryption keys
patch_encryption_keys "${new_key}" "${updated_fallback_keys_yaml}"
success "Successfully rotated encryption keys"
}
function restart_deployment {
# This function takes 1 parameter:
# $1 the deployment name to wait for
deployment_name=$1
kubectl rollout restart deployment ${deployment_name} ${KUBECTL_NAMESPACE_FLAG} 2>&1 | tee -a "${LOG_FILE}"
rc=$?
check_exit_code ${rc} "Failed to issue command to restart the ${deployment_name} deployment. Check that the value for the --release-name flag matches an existing Helm release and try again. rc=${rc}"
# Wait for the rollout to complete
kubectl rollout status deployment "${deployment_name}" ${KUBECTL_NAMESPACE_FLAG} --timeout=3m 2>&1 | tee -a "${LOG_FILE}"
rc=$?
check_exit_code ${rc} "Failed to wait for ${deployment_name} to be restarted. rc=${rc}"
}
function restart_deployments {
h1 "Restarting Galasa ecosystem deployments to use new encryption keys"
info "Restarting API server..."
restart_deployment "${API_DEPLOYMENT_NAME}"
success "API server restarted OK"
info "Restarting engine controller..."
restart_deployment "${ENGINE_CONTROLLER_DEPLOYMENT_NAME}"
success "Engine controller restarted OK"
success "Restarted ecosystem deployments OK"
}
function get_existing_secrets {
h1 "Getting existing Galasa Secrets"
cmd="galasactl secrets get --format yaml"
info "Running command: ${cmd}"
${cmd} > ${SECRETS_FILE}
rc=$?
check_exit_code ${rc} "Failed to get secrets from the Galasa ecosystem. rc=${rc}"
success "Existing secrets stored at ${SECRETS_FILE}"
}
function migrate_secrets {
h1 "Re-encrypting existing Galasa Secrets with new encryption keys"
if [[ ! -s "${SECRETS_FILE}" ]]; then
info "No secrets found to re-encrypt"
success "OK"
else
info "Re-applying secrets"
cmd="galasactl resources apply -f ${SECRETS_FILE}"
info "Running command: ${cmd}"
${cmd} 2>&1 | tee -a "${LOG_FILE}"
rc=$?
check_exit_code ${rc} "Failed to re-apply secrets to the Galasa ecosystem. rc=${rc}"
success "Successfully re-encrypted existing Galasa Secrets"
fi
}
function patch_encryption_keys {
# This function takes 2 parameters:
# $1 a base64-encoded encryption key string to be the primary encryption key
# $2 a string representing the fallbackDecryptionKeys key-value pair in YAML format
encryption_key=$1
fallback_decryption_keys_yaml=$2
# Convert the keys into the expected YAML structure to be placed inside the secret
yaml=$(cat << EOF
encryptionKey: ${encryption_key}
${fallback_decryption_keys_yaml}
EOF
)
encoded_yaml=$(echo -n "${yaml}" | base64)
patch=$(cat << EOF
data:
encryption-keys.yaml: ${encoded_yaml}
EOF
)
# Update the Kubernetes Secret
echo "${patch}" | kubectl patch secret "${ENCRYPTION_SECRET_NAME}" ${KUBECTL_NAMESPACE_FLAG} --patch-file /dev/stdin 2>&1 | tee -a "${LOG_FILE}"
rc=$?
check_exit_code ${rc} "Failed to patch the encryption keys secret. rc=${rc}"
}
function clear_fallback_keys {
h1 "Clearing fallback decryption keys"
get_existing_encryption_keys
# Reset the fallback decryption keys list to be an empty list
updated_fallback_keys_yaml=$(cat << EOF
fallbackDecryptionKeys: []
EOF
)
patch_encryption_keys "${CURRENT_KEY}" "${updated_fallback_keys_yaml}"
success "Successfully cleared fallback decryption keys"
}
#-----------------------------------------------------------------------------------------
# Process parameters
#-----------------------------------------------------------------------------------------
NAMESPACE=""
RELEASE_NAME=""
while [ "$1" != "" ]; do
case $1 in
-n | --namespace ) shift
export NAMESPACE=$1
;;
--release-name ) shift
export RELEASE_NAME=$1
;;
-b | --bootstrap ) shift
export GALASA_BOOTSTRAP=$1
;;
-h | --help ) usage
exit
;;
* ) error "Unexpected argument $1"
usage
exit 1
esac
shift
done
if [[ -z "${RELEASE_NAME}" ]]; then
error "A release name must be provided using the --release-name flag."
usage
exit 1
fi
#-----------------------------------------------------------------------------------------
# Main program logic
#-----------------------------------------------------------------------------------------
set -o pipefail
ENCRYPTION_SECRET_NAME="${RELEASE_NAME}-encryption-secret"
API_DEPLOYMENT_NAME="${RELEASE_NAME}-api"
ENGINE_CONTROLLER_DEPLOYMENT_NAME="${RELEASE_NAME}-engine-controller"
KUBECTL_NAMESPACE_FLAG=""
if [[ -n "${NAMESPACE}" ]]; then
KUBECTL_NAMESPACE_FLAG="--namespace ${NAMESPACE}"
fi
# Create temp directory and log file
TEMP_DIR="${BASEDIR}/temp"
mkdir -p "${TEMP_DIR}"
LOG_FILE="${TEMP_DIR}/rotate-${RELEASE_NAME}-keys.txt"
> "${LOG_FILE}"
SECRETS_FILE="${TEMP_DIR}/secrets.yaml"
# Before starting the rotation process, check that we have all the tools required
# and that we can authenticate with the Galasa ecosystem
check_required_tools_installed
check_galasactl_auth
get_existing_secrets
get_existing_encryption_keys
rotate_encryption_keys
restart_deployments
migrate_secrets
clear_fallback_keys
restart_deployments
success "Successfully rotated encryption keys and re-encrypted secrets!"