Skip to content

Commit 6db136c

Browse files
authored
Merge pull request #6 from gruntwork-io/feat/adding-signature-verficiation
feat: Adding signature verification
2 parents a1738f3 + 4c04fce commit 6db136c

File tree

2 files changed

+239
-2
lines changed

2 files changed

+239
-2
lines changed

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,38 @@ asdf plugin add terragrunt https://github.com/ohmer/asdf-terragrunt
1717
automatically detected value. Useful, for example, for allowing users on M1 Macs to install `amd64` binaries when
1818
there's no `arm64` binary available.
1919

20-
- `ASDF_TERRAGRUNT_SKIP_CHECKSUM`: Skip the checksum verification when downloading the binary. This is saves some time,
20+
- `ASDF_TERRAGRUNT_SKIP_CHECKSUM`: Skip the checksum verification when downloading the binary. This saves some time,
2121
but is less secure.
2222

23+
- `ASDF_TERRAGRUNT_SKIP_SIGNATURE`: Skip cryptographic signature verification of release assets. Set to `true` to
24+
disable verification (not recommended). Signature verification requires either `gpg` or `cosign` to be installed.
25+
Default: `false` (verification enabled).
26+
27+
- `ASDF_TERRAGRUNT_SIGNATURE_METHOD`: Choose the signature verification method. Options are:
28+
- `auto` (default): Tries GPG first, then Cosign if GPG is unavailable or fails.
29+
- `gpg`: Use GPG signature verification only. Requires `gpg` to be installed.
30+
- `cosign`: Use Cosign signature verification only. Requires `cosign` to be installed.
31+
32+
### Signature Verification
33+
34+
Terragrunt releases are cryptographically signed by Gruntwork. Signature verification is **enabled by default** to ensure
35+
the binary you download is authentic and hasn't been tampered with.
36+
37+
**Note:** Signature verification is only available for Terragrunt versions `v0.98.0` and newer. Older versions will
38+
skip signature verification automatically.
39+
40+
**Requirements:**
41+
- For GPG verification: Install `gpg` (GnuPG). The plugin will automatically import Gruntwork's public key.
42+
- For Cosign verification: Install `cosign` from [Sigstore](https://docs.sigstore.dev/cosign/installation/).
43+
44+
If neither tool is installed, the installation will fail. You can either:
45+
1. Install `gpg` or `cosign` (recommended)
46+
2. Disable signature verification (not recommended):
47+
```bash
48+
export ASDF_TERRAGRUNT_SKIP_SIGNATURE=true
49+
asdf install terragrunt 0.50.0
50+
```
51+
2352
### Special Thanks
2453

2554
This plugin was started by the community, and is now maintained by Gruntwork.

bin/install

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,23 @@ install() {
5151
exit 1
5252
fi
5353

54+
_install_tmp_dir="$(mktemp -d)"
55+
trap 'rm -rf "$_install_tmp_dir"' EXIT
56+
local sums_file="${_install_tmp_dir}/SHA256SUMS"
57+
58+
local sums_url="https://github.com/gruntwork-io/terragrunt/releases/download/v${version}/SHA256SUMS"
59+
if ! curl -sL -o "${sums_file}" "${sums_url}"; then
60+
echo "Warning: Failed to download SHA256SUMS"
61+
rm -f "${bin_path}"
62+
exit 1
63+
fi
64+
65+
# Checksum verification
5466
if [[ "${ASDF_TERRAGRUNT_SKIP_CHECKSUM:-"false"}" != "true" ]]; then
5567
local expected_checksum
5668
expected_checksum=$(_sha256 "${bin_path}" | awk '{print $1}')
5769
local checksum
58-
checksum="$(curl -sL "https://github.com/gruntwork-io/terragrunt/releases/download/v${version}/SHA256SUMS" | grep "terragrunt_${platform}_${arch}$" | awk '{print $1}')"
70+
checksum="$(grep "terragrunt_${platform}_${arch}$" "${sums_file}" | awk '{print $1}')"
5971

6072
if [[ "${expected_checksum}" != "${checksum}" ]]; then
6173
echo "Checksum for terragrunt did not match"
@@ -66,6 +78,27 @@ install() {
6678
fi
6779
fi
6880

81+
# Signature verification
82+
if [[ "${ASDF_TERRAGRUNT_SKIP_SIGNATURE:-"false"}" == "true" ]]; then
83+
echo "Skipping signature verification due to ASDF_TERRAGRUNT_SKIP_SIGNATURE=true"
84+
85+
chmod +x "${bin_path}"
86+
return
87+
fi
88+
89+
if ! _supports_signature_verification "${version}"; then
90+
echo "Skipping signature verification: not available for versions older than v${MIN_SIGNED_VERSION}"
91+
92+
chmod +x "${bin_path}"
93+
return
94+
fi
95+
96+
if ! verify_signature "${version}" "${sums_file}"; then
97+
echo "Signature verification failed. Set ASDF_TERRAGRUNT_SKIP_SIGNATURE=true to skip."
98+
rm -f "${bin_path}"
99+
exit 1
100+
fi
101+
69102
chmod +x "${bin_path}"
70103
}
71104

@@ -80,4 +113,179 @@ _sha256() {
80113
fi
81114
}
82115

116+
# Compare two semantic versions. Returns 0 if $1 >= $2, 1 otherwise.
117+
_version_gte() {
118+
local version=$1
119+
local min_version=$2
120+
121+
# Use sort -V if available, otherwise fall back to manual comparison
122+
if echo | sort -V >/dev/null 2>&1; then
123+
local sorted_first
124+
sorted_first=$(printf '%s\n%s' "$min_version" "$version" | sort -V | head -n1)
125+
[[ "$sorted_first" == "$min_version" ]]
126+
else
127+
# Manual version comparison for systems without sort -V (e.g., macOS)
128+
local i
129+
130+
IFS='.' read -ra v1 <<<"$version"
131+
IFS='.' read -ra v2 <<<"$min_version"
132+
133+
for ((i = 0; i < ${#v2[@]}; i++)); do
134+
local n1=${v1[i]:-0}
135+
local n2=${v2[i]:-0}
136+
if ((n1 > n2)); then
137+
return 0
138+
elif ((n1 < n2)); then
139+
return 1
140+
fi
141+
done
142+
143+
return 0
144+
fi
145+
}
146+
147+
# Minimum version that has signed release assets
148+
MIN_SIGNED_VERSION="0.98.0"
149+
150+
_supports_signature_verification() {
151+
local version=$1
152+
_version_gte "$version" "$MIN_SIGNED_VERSION"
153+
}
154+
155+
_gpg_available() {
156+
command -v gpg >/dev/null 2>&1
157+
}
158+
159+
_cosign_available() {
160+
command -v cosign >/dev/null 2>&1
161+
}
162+
163+
_import_gpg_key() {
164+
local key_url="https://gruntwork.io/.well-known/pgp-key.txt"
165+
166+
# Check if we already have the Gruntwork key imported
167+
if gpg --list-keys "Gruntwork" >/dev/null 2>&1; then
168+
return 0
169+
fi
170+
171+
echo "Importing Gruntwork GPG public key..."
172+
if ! curl -sL "${key_url}" | gpg --import 2>/dev/null; then
173+
echo "Warning: Failed to import Gruntwork GPG key from ${key_url}"
174+
return 1
175+
fi
176+
return 0
177+
}
178+
179+
_verify_gpg_signature() {
180+
local version=$1
181+
local sums_file=$2
182+
183+
local sig_url="https://github.com/gruntwork-io/terragrunt/releases/download/v${version}/SHA256SUMS.gpgsig"
184+
local sig_file="${sums_file}.gpgsig"
185+
186+
echo "Downloading GPG signature file..."
187+
188+
if ! curl -sL -o "${sig_file}" "${sig_url}"; then
189+
echo "Warning: Failed to download SHA256SUMS.gpgsig"
190+
return 1
191+
fi
192+
193+
if ! _import_gpg_key; then
194+
return 1
195+
fi
196+
197+
echo "Verifying GPG signature..."
198+
if gpg --verify "${sig_file}" "${sums_file}" 2>/dev/null; then
199+
echo "GPG signature verification successful"
200+
return 0
201+
else
202+
echo "GPG signature verification failed!"
203+
return 1
204+
fi
205+
}
206+
207+
_verify_cosign_signature() {
208+
local version=$1
209+
local sums_file=$2
210+
211+
local sig_url="https://github.com/gruntwork-io/terragrunt/releases/download/v${version}/SHA256SUMS.sig"
212+
local cert_url="https://github.com/gruntwork-io/terragrunt/releases/download/v${version}/SHA256SUMS.pem"
213+
214+
local sig_file="${sums_file}.sig"
215+
local cert_file="${sums_file}.pem"
216+
217+
echo "Downloading Cosign signature files..."
218+
219+
if ! curl -sL -o "${sig_file}" "${sig_url}"; then
220+
echo "Warning: Failed to download SHA256SUMS.sig"
221+
return 1
222+
fi
223+
224+
if ! curl -sL -o "${cert_file}" "${cert_url}"; then
225+
echo "Warning: Failed to download SHA256SUMS.pem"
226+
return 1
227+
fi
228+
229+
echo "Verifying Cosign signature..."
230+
if cosign verify-blob "${sums_file}" \
231+
--signature "${sig_file}" \
232+
--certificate "${cert_file}" \
233+
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
234+
--certificate-identity-regexp "github.com/gruntwork-io/terragrunt" 2>/dev/null; then
235+
echo "Cosign signature verification successful"
236+
return 0
237+
else
238+
echo "Cosign signature verification failed!"
239+
return 1
240+
fi
241+
}
242+
243+
verify_signature() {
244+
local version=$1
245+
local sums_file=$2
246+
247+
local method="${ASDF_TERRAGRUNT_SIGNATURE_METHOD:-auto}"
248+
249+
case "${method}" in
250+
gpg)
251+
if ! _gpg_available; then
252+
echo "Error: GPG verification requested but gpg is not installed"
253+
return 1
254+
fi
255+
_verify_gpg_signature "${version}" "${sums_file}"
256+
return $?
257+
;;
258+
cosign)
259+
if ! _cosign_available; then
260+
echo "Error: Cosign verification requested but cosign is not installed"
261+
return 1
262+
fi
263+
_verify_cosign_signature "${version}" "${sums_file}"
264+
return $?
265+
;;
266+
auto)
267+
# Try GPG first, then Cosign
268+
if _gpg_available; then
269+
echo "Using GPG for signature verification"
270+
_verify_gpg_signature "${version}" "${sums_file}"
271+
return $?
272+
fi
273+
274+
if _cosign_available; then
275+
echo "Using Cosign for signature verification"
276+
_verify_cosign_signature "${version}" "${sums_file}"
277+
return $?
278+
fi
279+
280+
echo "Warning: Neither gpg nor cosign is available for signature verification"
281+
echo "Install gpg or cosign to enable signature verification, or set ASDF_TERRAGRUNT_SKIP_SIGNATURE=true to skip"
282+
return 1
283+
;;
284+
*)
285+
echo "Error: Unknown signature method '${method}'. Use 'gpg', 'cosign', or 'auto'"
286+
return 1
287+
;;
288+
esac
289+
}
290+
83291
install "${ASDF_INSTALL_TYPE}" "${ASDF_INSTALL_VERSION}" "${ASDF_INSTALL_PATH}"

0 commit comments

Comments
 (0)