Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,10 @@ gh attestation verify CodeIndex-linux-x64.tar.gz -R Widthdom/CodeIndex
```

The GitHub attestation verifies that the artifact was generated by the
repository workflow identity.
repository workflow identity. The installer runs this verification
automatically when the `gh` command is available and the public GitHub release
host is used. Set `CDIDX_REQUIRE_ATTESTATION=1` to make the installer fail
closed when provenance verification cannot be completed.

### Option A: One-liner install (no .NET required)

Expand Down Expand Up @@ -2487,6 +2490,11 @@ signature に対する GitHub build provenance attestation も出力します。
gh attestation verify CodeIndex-linux-x64.tar.gz -R Widthdom/CodeIndex
```

`gh` command が利用可能で public GitHub release host を使っている場合、
installer はこの provenance verification を自動実行します。
`CDIDX_REQUIRE_ATTESTATION=1` を設定すると、provenance verification を完了
できない場合に installer は fail closed します。

GitHub attestation は、その artifact が repository workflow identity により
生成されたことを検証します。

Expand Down
17 changes: 17 additions & 0 deletions changelog.d/unreleased/1678.security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: security
issues:
- 1678
affected:
- install.sh
- USER_GUIDE.md
- tests/CodeIndex.Tests/InstallScriptTests.cs
---

## English

- **Installer downloads can now verify GitHub provenance attestations (#1678)** - `install.sh` now verifies release archives and `sha256sums.txt` through GitHub provenance attestations when `gh` is available, and `CDIDX_REQUIRE_ATTESTATION=1` makes installs fail closed if that second-channel verification cannot complete.

## 日本語

- **installer download が GitHub provenance attestation を検証できるようになりました (#1678)** - `install.sh` は `gh` が利用可能な場合に release archive と `sha256sums.txt` を GitHub provenance attestation で検証し、`CDIDX_REQUIRE_ATTESTATION=1` を設定すると second-channel verification を完了できない場合に install を fail closed します。
47 changes: 47 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Optional env vars / 任意環境変数:
# CDIDX_GITHUB_BASE_URL Release download base URL override
# CDIDX_GITHUB_API_BASE_URL API base URL override for latest-release lookup
# CDIDX_REQUIRE_ATTESTATION=1 Require GitHub provenance verification via gh
# CDIDX_LOCAL_MIRROR_PORT Local self-test HTTP server port (default: 18765)
# HTTPS_PROXY / HTTP_PROXY Proxy used by curl for release and API probes
# NO_PROXY Hosts that should bypass the proxy
Expand Down Expand Up @@ -76,6 +77,7 @@ BINARY_NAME="cdidx"
MANIFEST_REQUIRED_VERSION="1.24.6"
GITHUB_BASE_URL="${CDIDX_GITHUB_BASE_URL:-https://github.com}"
GITHUB_API_BASE_URL="${CDIDX_GITHUB_API_BASE_URL:-https://api.github.com}"
REQUIRE_ATTESTATION="${CDIDX_REQUIRE_ATTESTATION:-0}"
# Normalize optional base URL overrides by removing a trailing slash.
# 末尾スラッシュ付きでも URL 連結が壊れないようにする。
GITHUB_BASE_URL="${GITHUB_BASE_URL%/}"
Expand Down Expand Up @@ -182,6 +184,49 @@ need_cmd() {
fi
}

has_cmd() {
command -v "$1" > /dev/null 2>&1
}

release_attestation_supported() {
if [ "${CDIDX_INSTALL_SH_LIB_ONLY:-0}" = "1" ] && [ "${CDIDX_TEST_ENABLE_ATTESTATION:-0}" != "1" ]; then
return 1
fi

[ "$GITHUB_BASE_URL" = "https://github.com" ] && [ "${SELF_TEST_LOCAL_MIRROR:-0}" != "1" ]
}

verify_release_attestation() {
local artifact_path="$1"
local artifact_name="$2"

if ! release_attestation_supported; then
if [ "$REQUIRE_ATTESTATION" = "1" ]; then
error "GitHub provenance attestation verification is required, but the release host is not github.com. Unset CDIDX_REQUIRE_ATTESTATION or install from the public GitHub release."
fi
return 0
fi

if ! has_cmd gh; then
if [ "$REQUIRE_ATTESTATION" = "1" ]; then
error "GitHub provenance attestation verification is required, but the 'gh' command was not found. Install GitHub CLI or unset CDIDX_REQUIRE_ATTESTATION."
fi
warn "Skipping GitHub provenance attestation for ${artifact_name}: 'gh' command not found. Set CDIDX_REQUIRE_ATTESTATION=1 to require this verification."
return 0
fi

info "Verifying GitHub provenance attestation for ${artifact_name}..."
if gh attestation verify "$artifact_path" -R "$REPO" > /dev/null; then
return 0
fi

if [ "$REQUIRE_ATTESTATION" = "1" ]; then
error "GitHub provenance attestation verification failed for ${artifact_name}."
fi

warn "GitHub provenance attestation verification failed for ${artifact_name}; continuing with checksum verification. Set CDIDX_REQUIRE_ATTESTATION=1 to fail closed."
}

temp_root() {
printf '%s' "${TMPDIR:-/tmp}"
}
Expand Down Expand Up @@ -989,9 +1034,11 @@ download_and_install() {

info "Downloading ${archive_name}..."
download_release_file "$archive_url" "${tmpdir}/${archive_name}" "${archive_name}"
verify_release_attestation "${tmpdir}/${archive_name}" "$archive_name"

info "Downloading checksums..."
download_release_file "$checksums_url" "${tmpdir}/sha256sums.txt" "sha256sums.txt"
verify_release_attestation "${tmpdir}/sha256sums.txt" "sha256sums.txt"

# Verify checksum / チェックサム検証
info "Verifying checksum..."
Expand Down
60 changes: 60 additions & 0 deletions tests/CodeIndex.Tests/InstallScriptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,66 @@ shift 2
Assert.Contains("allow-list at least one artifact host path", stderr);
}

[Fact]
public void VerifyReleaseAttestation_GhAvailable_VerifiesArtifactWithRepository()
{
if (OperatingSystem.IsWindows())
return;

var logPath = Path.Combine(_tempRoot, "gh_attestation.log");
var artifactPath = Path.Combine(_tempRoot, "CodeIndex-linux-x64.tar.gz");
File.WriteAllText(artifactPath, "archive");

var (exitCode, stdout, stderr) = RunInstallerSnippet(
$$"""
gh() {
printf '%s\n' "$*" >> "{{logPath}}"
return 0
}

verify_release_attestation "{{artifactPath}}" "CodeIndex-linux-x64.tar.gz"
""",
new Dictionary<string, string?>
{
["CDIDX_TEST_ENABLE_ATTESTATION"] = "1",
});

Assert.Equal(0, exitCode);
Assert.Empty(stderr);
Assert.Contains("Verifying GitHub provenance attestation for CodeIndex-linux-x64.tar.gz", stdout);
Assert.Equal($"attestation verify {artifactPath} -R Widthdom/CodeIndex{Environment.NewLine}", File.ReadAllText(logPath));
}

[Fact]
public void VerifyReleaseAttestation_RequiredAndGhFails_Aborts()
{
if (OperatingSystem.IsWindows())
return;

var artifactPath = Path.Combine(_tempRoot, "sha256sums.txt");
File.WriteAllText(artifactPath, "checksums");

var (exitCode, stdout, stderr) = RunInstallerSnippet(
$$"""
gh() {
return 1
}

verify_release_attestation "{{artifactPath}}" "sha256sums.txt"
echo "UNREACHABLE"
""",
new Dictionary<string, string?>
{
["CDIDX_REQUIRE_ATTESTATION"] = "1",
["CDIDX_TEST_ENABLE_ATTESTATION"] = "1",
});

Assert.Equal(1, exitCode);
Assert.Contains("Verifying GitHub provenance attestation for sha256sums.txt", stdout);
Assert.DoesNotContain("UNREACHABLE", stdout);
Assert.Contains("GitHub provenance attestation verification failed for sha256sums.txt", stderr);
}

[Theory]
[InlineData("curl: (56) CONNECT tunnel failed, response 403")]
[InlineData("curl: (56) Received HTTP code 403 from proxy after CONNECT")]
Expand Down
Loading