Skip to content

Commit 83cb8c2

Browse files
Update gen_secrets.sh
1 parent 73bf661 commit 83cb8c2

1 file changed

Lines changed: 238 additions & 0 deletions

File tree

gen_secrets.sh

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
#!/usr/bin/env bash
2+
# This file is managed by Terraform in github-control repository
3+
# Do not edit this file, all changes will be overwritten
4+
# If you need to change this file, create a pull request in
5+
# https://github.com/tinyfish-io/github-control
6+
#
7+
# gen_secrets.sh
8+
#
9+
# Generates a .env file by resolving secret references in a env.config file.
10+
#
11+
# env.config format:
12+
# aws_profile=<profile> # consumed by this script, not written to dest
13+
# KEY=@aws_secret_name # fetched via ih-secrets at run time
14+
# KEY=@aws_secret_name:key # fetched and extracted from JSON response
15+
# KEY=~hex:32 # locally generated via openssl rand -hex <n>
16+
# KEY=~base64:32 # locally generated via openssl rand -base64 <n>
17+
# KEY=value # written verbatim
18+
# # comment / blank lines # written verbatim
19+
#
20+
# Default behavior (fill mode): if the dest file already exists, keys that
21+
# already have a non-empty value are kept as-is. Only absent or empty keys
22+
# are fetched/generated. Keys present only in the dest file are also preserved.
23+
# Use --regen to overwrite everything.
24+
#
25+
# Usage:
26+
# ./gen_secrets.sh [--config env.config] [--dest .env.local] [--regen] [--force]
27+
28+
set -euo pipefail
29+
30+
CONFIG="env.config"
31+
DEST=".env.local"
32+
FORCE=0
33+
REGEN=0
34+
35+
while [[ $# -gt 0 ]]; do
36+
case "$1" in
37+
--config)
38+
[[ $# -gt 1 ]] || { echo "ERROR: --config requires an argument" >&2; exit 1; }
39+
CONFIG="$2"; shift 2 ;;
40+
--dest)
41+
[[ $# -gt 1 ]] || { echo "ERROR: --dest requires an argument" >&2; exit 1; }
42+
DEST="$2"; shift 2 ;;
43+
--force) FORCE=1; shift ;;
44+
--regen) REGEN=1; shift ;;
45+
-h|--help)
46+
sed -n '/^#/p' "$0" | sed 's/^# \{0,1\}//'
47+
exit 0
48+
;;
49+
*) echo "Unknown option: $1" >&2; exit 1 ;;
50+
esac
51+
done
52+
53+
if [[ ! -f "$CONFIG" ]]; then
54+
echo "ERROR: Config file '$CONFIG' not found." >&2
55+
exit 1
56+
fi
57+
58+
# First pass: extract aws_profile
59+
AWS_PROFILE=""
60+
while IFS= read -r line || [[ -n "$line" ]]; do
61+
if [[ "$line" =~ ^aws_profile=(.+)$ ]]; then
62+
AWS_PROFILE="${BASH_REMATCH[1]}"
63+
break
64+
fi
65+
done < "$CONFIG"
66+
67+
if [[ -z "$AWS_PROFILE" ]]; then
68+
echo "ERROR: 'aws_profile' not set in $CONFIG" >&2
69+
exit 1
70+
fi
71+
72+
# In fill mode, check if dest exists to decide whether we're filling or generating fresh
73+
FILL=0
74+
if [[ -f "$DEST" && "$REGEN" -eq 0 ]]; then
75+
FILL=1
76+
fi
77+
78+
# In regen mode, prompt before overwriting an existing dest
79+
if [[ -f "$DEST" && "$REGEN" -eq 1 && "$FORCE" -eq 0 ]]; then
80+
printf "'%s' already exists. Overwrite? [y/N] " "$DEST"
81+
reply=""
82+
read -r reply || true
83+
[[ "$reply" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
84+
fi
85+
86+
# Return the raw KEY=value line from the existing dest file, or empty if absent.
87+
# Uses awk for exact key matching (avoids treating k as a regex).
88+
lookup_raw_line() {
89+
local k="$1"
90+
awk -v k="$k" -F= '$1==k {print; exit}' "$DEST" 2>/dev/null || true
91+
}
92+
93+
# Return true if the key has a non-empty value in the existing dest file.
94+
has_existing_value() {
95+
local raw
96+
raw=$(lookup_raw_line "$1")
97+
[[ -n "$raw" ]] && [[ "${raw#*=}" != "" ]]
98+
}
99+
100+
# Return true if the key is in the PROCESSED_KEYS array.
101+
_key_processed() {
102+
local k="$1" pk
103+
for pk in "${PROCESSED_KEYS[@]+"${PROCESSED_KEYS[@]}"}"; do
104+
[[ "$pk" == "$k" ]] && return 0
105+
done
106+
return 1
107+
}
108+
109+
TMPFILE=$(mktemp)
110+
trap 'rm -f "$TMPFILE"' EXIT
111+
112+
FETCHED=0
113+
FAILED=0
114+
PASSED=0
115+
KEPT=0
116+
FAILED_KEYS=()
117+
PROCESSED_KEYS=()
118+
119+
if [[ "$FILL" -eq 1 ]]; then
120+
echo "Filling $DEST from $CONFIG (profile: $AWS_PROFILE) ..."
121+
else
122+
echo "Generating $DEST from $CONFIG (profile: $AWS_PROFILE) ..."
123+
fi
124+
echo ""
125+
126+
while IFS= read -r line || [[ -n "$line" ]]; do
127+
# Skip the aws_profile directive — it's for this script only
128+
if [[ "$line" =~ ^aws_profile= ]]; then
129+
continue
130+
fi
131+
132+
# Blank lines and comments pass through verbatim
133+
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
134+
echo "$line" >> "$TMPFILE"
135+
continue
136+
fi
137+
138+
# Determine the key for any KEY=... line
139+
current_key="${line%%=*}"
140+
PROCESSED_KEYS+=("$current_key")
141+
142+
# In fill mode, preserve the raw existing line if the key already has a value
143+
if [[ "$FILL" -eq 1 ]] && has_existing_value "$current_key"; then
144+
raw_line=$(lookup_raw_line "$current_key")
145+
printf " [KEEP] %s\n" "$current_key"
146+
echo "$raw_line" >> "$TMPFILE"
147+
KEPT=$((KEPT + 1))
148+
continue
149+
fi
150+
151+
# Secret reference: KEY=@aws_secret_name or KEY=@aws_secret_name:json_key
152+
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=@([^[:space:]:]+)(:([^[:space:]]+))?$ ]]; then
153+
key="${BASH_REMATCH[1]}"
154+
secret="${BASH_REMATCH[2]}"
155+
json_key="${BASH_REMATCH[4]}"
156+
printf " [FETCH] %-45s ... " "$key"
157+
if raw=$(ih-secrets --aws-profile "$AWS_PROFILE" get "$secret" 2>&1); then
158+
if [[ -n "$json_key" ]]; then
159+
if ! value=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])[sys.argv[2]])" "$raw" "$json_key" 2>&1); then
160+
printf '%s=\n' "$key" >> "$TMPFILE"
161+
echo "FAILED (key '$json_key' not found in JSON)"
162+
FAILED=$((FAILED + 1))
163+
FAILED_KEYS+=("$key ($secret:$json_key)")
164+
continue
165+
fi
166+
else
167+
value="$raw"
168+
fi
169+
printf '%s=%s\n' "$key" "$value" >> "$TMPFILE"
170+
echo "OK"
171+
FETCHED=$((FETCHED + 1))
172+
else
173+
printf '%s=\n' "$key" >> "$TMPFILE"
174+
echo "FAILED"
175+
FAILED=$((FAILED + 1))
176+
FAILED_KEYS+=("$key ($secret)")
177+
fi
178+
continue
179+
fi
180+
181+
# Generated value: KEY=~hex:32 or KEY=~base64:32
182+
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=~(hex|base64):([0-9]+)$ ]]; then
183+
key="${BASH_REMATCH[1]}"
184+
encoding="${BASH_REMATCH[2]}"
185+
length="${BASH_REMATCH[3]}"
186+
printf " [GEN] %-45s ... " "$key"
187+
if value=$(openssl rand -"$encoding" "$length" 2>&1); then
188+
printf '%s=%s\n' "$key" "$value" >> "$TMPFILE"
189+
echo "OK"
190+
PASSED=$((PASSED + 1))
191+
else
192+
printf '%s=\n' "$key" >> "$TMPFILE"
193+
echo "FAILED"
194+
FAILED=$((FAILED + 1))
195+
FAILED_KEYS+=("$key (generate)")
196+
fi
197+
continue
198+
fi
199+
200+
# Static value — pass through verbatim
201+
key="${line%%=*}"
202+
printf " [PASS] %s\n" "$key"
203+
echo "$line" >> "$TMPFILE"
204+
PASSED=$((PASSED + 1))
205+
done < "$CONFIG"
206+
207+
# In fill mode, preserve any keys from DEST that weren't present in CONFIG
208+
if [[ "$FILL" -eq 1 ]]; then
209+
while IFS= read -r dest_line || [[ -n "$dest_line" ]]; do
210+
if [[ "$dest_line" =~ ^([A-Za-z_][A-Za-z0-9_]*)= ]]; then
211+
dest_key="${BASH_REMATCH[1]}"
212+
if ! _key_processed "$dest_key"; then
213+
echo "$dest_line" >> "$TMPFILE"
214+
fi
215+
fi
216+
done < "$DEST"
217+
fi
218+
219+
mv "$TMPFILE" "$DEST"
220+
trap - EXIT
221+
222+
echo ""
223+
if [[ "$FILL" -eq 1 ]]; then
224+
echo "Done: $FETCHED fetched, $FAILED failed, $PASSED passed through, $KEPT kept from existing."
225+
else
226+
echo "Done: $FETCHED fetched, $FAILED failed, $PASSED passed through."
227+
fi
228+
echo "Written to $DEST"
229+
230+
if [[ "$FAILED" -gt 0 ]]; then
231+
echo ""
232+
echo "WARNING: $FAILED secret(s) could not be fetched (written as empty):"
233+
for k in "${FAILED_KEYS[@]}"; do
234+
echo " - $k"
235+
done
236+
echo "Check your access to AWS profile '$AWS_PROFILE'."
237+
exit 1
238+
fi

0 commit comments

Comments
 (0)