-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathvault-helper.sh
executable file
·301 lines (238 loc) · 9.12 KB
/
vault-helper.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
#!/usr/bin/env bash
# A script that is meant to be used with the private Vault cluster examples to:
#
# 1. Wait for the Vault server cluster to come up.
# 2. Print out the IP addresses of the Vault servers.
# 3. Print out some example commands you can run against your Vault servers.
# From https://github.com/hashicorp/terraform-aws-vault/blob/master/examples/vault-examples-helper/vault-examples-helper.sh
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly MAX_RETRIES=30
readonly SLEEP_BETWEEN_RETRIES_SEC=10
TERRAFORM="${TERRAFORM:-terraform}"
function log {
local readonly level="$1"
local readonly message="$2"
local readonly timestamp=$(date +"%Y-%m-%d %H:%M:%S")
>&2 echo -e "${timestamp} [${level}] [$SCRIPT_NAME] ${message}"
}
function log_info {
local readonly message="$1"
log "INFO" "$message"
}
function log_warn {
local readonly message="$1"
log "WARN" "$message"
}
function log_error {
local readonly message="$1"
log "ERROR" "$message"
}
function assert_is_installed {
local readonly name="$1"
if [[ ! $(command -v ${name}) ]]; then
log_error "The binary '$name' is required by this script but is not installed or in the system's PATH."
exit 1
fi
}
function get_required_terraform_data_source_output {
local readonly data_source_name="$1"
local readonly output_name="$2"
local output_value
output_value=$("${TERRAFORM}" show -json | jq -r "
.values.root_module.resources |
.[] |
select(.address==\"data.terraform_remote_state.$data_source_name\") |
.values.outputs.$output_name")
if [[ "$output_value" == "null" ]]; then
log_error "Unable to find Terraform data source $data_source_name with output $output_name"
exit 1
fi
echo "$output_value"
}
function get_optional_terraform_output {
local readonly output_name="$1"
local output_value
output_value=$("${TERRAFORM}" show -json | jq -r "
.values.outputs.$output_name.value")
if [[ "$output_value" == "null" ]]; then
output_value=""
fi
echo "$output_value"
}
function get_required_terraform_output {
local readonly output_name="$1"
local output_value
output_value=$(get_optional_terraform_output "$output_name")
if [[ -z "$output_value" ]]; then
log_error "Unable to find a value for Terraform output $output_name"
exit 1
fi
echo "$output_value"
}
#
# Usage: join SEPARATOR ARRAY
#
# Joins the elements of ARRAY with the SEPARATOR character between them.
#
# Examples:
#
# join ", " ("A" "B" "C")
# Returns: "A, B, C"
#
function join {
local readonly separator="$1"
shift
local readonly values=("$@")
printf "%s$separator" "${values[@]}" | sed "s/$separator$//"
}
function get_all_vault_server_ips {
local expected_num_vault_servers
expected_num_vault_servers=$(get_required_terraform_output "vault_cluster_size")
log_info "Looking up private IP addresses for $expected_num_vault_servers Vault server EC2 Instances."
local ips
local i
for (( i=1; i<="$MAX_RETRIES"; i++ )); do
ips=($(get_vault_server_ips))
if [[ "${#ips[@]}" -eq "$expected_num_vault_servers" ]]; then
log_info "Found all $expected_num_vault_servers private IP addresses!"
echo "${ips[@]}"
return
else
log_warn "Found ${#ips[@]} of $expected_num_vault_servers private IP addresses. Will sleep for $SLEEP_BETWEEN_RETRIES_SEC seconds and try again."
sleep "$SLEEP_BETWEEN_RETRIES_SEC"
fi
done
log_error "Failed to find the IP addresses for $expected_num_vault_servers Vault server EC2 Instances after $MAX_RETRIES retries."
exit 1
}
function wait_for_all_vault_servers_to_come_up {
local readonly server_ips=($@)
local expected_num_vault_servers
expected_num_vault_servers=$(get_required_terraform_output "vault_cluster_size")
log_info "Waiting for $expected_num_vault_servers Vault servers to come up"
local server_ip
for server_ip in "${server_ips[@]}"; do
wait_for_vault_server_to_come_up "$server_ip"
done
}
function wait_for_vault_server_to_come_up {
local readonly server_ip="$1"
for (( i=1; i<="$MAX_RETRIES"; i++ )); do
local readonly vault_health_url="https://$server_ip:8200/v1/sys/health"
log_info "Checking health of Vault server via URL $vault_health_url"
local response
local status
local body
response=$(curl --show-error --location --insecure --silent --write-out "HTTPSTATUS:%{http_code}" "$vault_health_url" || true)
status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g')
log_info "Got a $status response from Vault server $server_ip with body:\n$body"
# Response code for the health check endpoint are defined here: https://www.vaultproject.io/api/system/health.html
if [[ "$status" -eq 200 ]]; then
log_info "Vault server $server_ip is initialized, unsealed, and active."
return
elif [[ "$status" -eq 429 ]]; then
log_info "Vault server $server_ip is unsealed and in standby mode."
return
elif [[ "$status" -eq 501 ]]; then
log_info "Vault server $server_ip is uninitialized."
return
elif [[ "$status" -eq 503 ]]; then
log_info "Vault server $server_ip is sealed."
return
else
log_info "Vault server $server_ip returned unexpected status code $status. Will sleep for $SLEEP_BETWEEN_RETRIES_SEC seconds and check again."
sleep "$SLEEP_BETWEEN_RETRIES_SEC"
fi
done
log_error "Did not get a successful response code from Vault server $server_ip after $MAX_RETRIES retries."
exit 1
}
function get_vault_server_ips {
local aws_region
local cluster_tag_key
local cluster_tag_value
local instances
aws_region=$(get_required_terraform_data_source_output "vpc" "vpc_region")
cluster_tag_key=$(get_required_terraform_output "vault_servers_cluster_tag_key")
cluster_tag_value=$(get_required_terraform_output "vault_servers_cluster_tag_value")
log_info "Fetching private IP addresses for EC2 Instances in $aws_region with tag $cluster_tag_key=$cluster_tag_value"
instances=$(aws ec2 describe-instances \
--region "$aws_region" \
--filter "Name=tag:$cluster_tag_key,Values=$cluster_tag_value" "Name=instance-state-name,Values=running")
echo "$instances" | jq -r '.Reservations[].Instances[].PrivateIpAddress'
}
function print_instructions {
local readonly server_ips=($@)
local server_ip="${server_ips[0]}"
local ssh_key_name
ssh_key_name=$(get_required_terraform_output "ssh_key_name")
ssh_key_name="$ssh_key_name.pem"
local instructions=()
instructions+=("\nYour Vault servers are running at the following IP addresses:\n\n${server_ips[@]/#/ }\n")
instructions+=("To initialize your Vault cluster, SSH to one of the servers and run the init command:\n")
instructions+=(" ssh -i $ssh_key_name ubuntu@$server_ip")
instructions+=(" vault init")
instructions+=("\nTo unseal your Vault cluster, SSH to each of the servers and run the unseal command with 3 of the 5 unseal keys:\n")
for server_ip in "${server_ips[@]}"; do
instructions+=(" ssh -i $ssh_key_name ubuntu@$server_ip")
instructions+=(" vault unseal (run this 3 times)\n")
done
local vault_elb_domain_name
vault_elb_domain_name=$(get_optional_terraform_output "vault_api_address" || true)
if [[ -z "$vault_elb_domain_name" ]]; then
instructions+=("\nOnce your cluster is unsealed, you can read and write secrets by SSHing to any of the servers:\n")
instructions+=(" ssh -i $ssh_key_name ubuntu@$server_ip")
instructions+=(" vault auth")
instructions+=(" vault write secret/example value=secret")
instructions+=(" vault read secret/example")
else
instructions+=("\nOnce your cluster is unsealed, you can read and write secrets via the ELB:\n")
instructions+=(" vault auth -address=https://$vault_elb_domain_name")
instructions+=(" vault write -address=https://$vault_elb_domain_name secret/example value=secret")
instructions+=(" vault read -address=https://$vault_elb_domain_name secret/example")
fi
instructions+=("\nPass the '-i' flag to this script to only print the private IP Addresses.")
local instructions_str
instructions_str=$(join "\n" "${instructions[@]}")
echo -e "$instructions_str\n"
}
function run {
# Argument parsing: https://stackoverflow.com/a/14203146
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-i|--inventory)
INVENTORY="YES"
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
assert_is_installed "aws"
assert_is_installed "jq"
assert_is_installed "terraform"
assert_is_installed "curl"
local server_ips
server_ips=$(get_all_vault_server_ips)
wait_for_all_vault_servers_to_come_up "$server_ips"
if [ "${INVENTORY:-}" == "YES" ]; then
local joined_ip
local ip_list
ip_list=($server_ips)
joined_ip=$(join "\n" "${ip_list[@]}")
log_info "Printing out server IPs to STDOUT"
echo -e "$joined_ip"
else
print_instructions "$server_ips"
fi
}
run "$@"