@@ -66,12 +66,20 @@ runs:
6666 run : |
6767 # Build a 20-char password that satisfies all known MATLAB / NLM regexes:
6868 # >=14 chars, includes upper, lower, digit, and special character.
69- UPPER=$(LC_ALL=C tr -dc 'A-Z' < /dev/urandom | head -c1)
70- LOWER=$(LC_ALL=C tr -dc 'a-z' < /dev/urandom | head -c1)
71- DIGIT=$(LC_ALL=C tr -dc '0-9' < /dev/urandom | head -c1)
72- SPECIAL=$(LC_ALL=C tr -dc '!@#%^_+=-' < /dev/urandom | head -c1)
73- REST=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c16)
69+ # Read a bounded block from /dev/urandom before filtering: piping an
70+ # unbounded stream straight into `head -c` makes `tr` die on SIGPIPE
71+ # when head closes the pipe, which aborts the step under pipefail.
72+ gen() { LC_ALL=C head -c 8192 /dev/urandom | LC_ALL=C tr -dc "$1" | head -c "$2"; }
73+ UPPER=$(gen 'A-Z' 1)
74+ LOWER=$(gen 'a-z' 1)
75+ DIGIT=$(gen '0-9' 1)
76+ SPECIAL=$(gen '!@#%^_+=-' 1)
77+ REST=$(gen 'A-Za-z0-9' 16)
7478 PASSWORD="${UPPER}${LOWER}${DIGIT}${SPECIAL}${REST}"
79+ if [ "${#PASSWORD}" -lt 14 ]; then
80+ echo "::error::Generated password too short (${#PASSWORD} chars); entropy source may be unavailable"
81+ exit 1
82+ fi
7583 echo "::add-mask::$PASSWORD"
7684 echo "password=$PASSWORD" >> "$GITHUB_OUTPUT"
7785
8694 CLIENT_IP : " ${{ steps.get-ip.outputs.ipv4 }}/32"
8795 STACK_PASSWORD : ${{ steps.gen-password.outputs.password }}
8896 CONFIG_FILE : " ${{ github.action_path }}/refarch-type-mappings.json"
97+ TEMPLATE_FILE : ${{ inputs.compiled_template_file_path }}
8998 run : |
9099 KEY_PARAM=$(jq -r ".\"$REFARCH_TYPE\".key_param" $CONFIG_FILE)
91100 VPC_PARAM=$(jq -r ".\"$REFARCH_TYPE\".vpc_param" $CONFIG_FILE)
@@ -99,12 +108,21 @@ runs:
99108 jq --arg k "$KEY_PARAM" --arg v "$KEY" '. += [{"ParameterKey": $k, "ParameterValue": $v}]' params.json > tmp.json && mv tmp.json params.json
100109 jq --arg k "$CLIENT_IP_PARAM" --arg v "$CLIENT_IP" '. += [{"ParameterKey": $k, "ParameterValue": $v}]' params.json > tmp.json && mv tmp.json params.json
101110
102- # Inject a random password for any template that asks for one. We
103- # set Password / ConfirmPassword (and never log them) so deployments
104- # do not rely on a hardcoded credential.
105- jq --arg pw "$STACK_PASSWORD" '
106- (.[] | select(.ParameterKey == "Password") | .ParameterValue) |= $pw
107- | (.[] | select(.ParameterKey == "ConfirmPassword") | .ParameterValue) |= $pw
111+ # Inject a random password for any template that asks for one. Only
112+ # Password / ConfirmPassword parameters that the compiled template
113+ # actually declares are set (added if absent, updated if present), so
114+ # we never pass an unknown parameter to CloudFormation. The values are
115+ # masked and never logged, so deployments do not rely on a hardcoded
116+ # credential.
117+ jq --arg pw "$STACK_PASSWORD" --slurpfile tmpl "$TEMPLATE_FILE" '
118+ ($tmpl[0].Parameters // {}) as $tp
119+ | reduce ["Password", "ConfirmPassword"][] as $name (.;
120+ if ($tp | has($name)) then
121+ if any(.[]; .ParameterKey == $name)
122+ then map(if .ParameterKey == $name then .ParameterValue = $pw else . end)
123+ else . + [{"ParameterKey": $name, "ParameterValue": $pw}]
124+ end
125+ else . end)
108126 ' params.json > tmp.json && mv tmp.json params.json
109127
110128 echo "Generated Parameters (passwords redacted by runner masking):"
0 commit comments