added SLSA provenance

This commit is contained in:
Mick Grove 2026-05-02 00:14:31 -07:00
commit 44d67cea1b
6 changed files with 73 additions and 41 deletions

View file

@ -347,13 +347,47 @@ jobs:
run: |
awk '
BEGIN { grabbing = 0 }
/^## \[/ {
/^## \[/ {
if (grabbing) exit; # already grabbed latest entry
grabbing = 1
}
grabbing { print }
' CHANGELOG.md > .latest_changelog.md
# ── Sign every release artifact with a SLSA v1 build-provenance attestation.
# actions/attest-build-provenance produces a multi-subject Sigstore Bundle
# in JSONL format and writes it to bundle-path. We ship that file alongside
# the binaries as `multiple.intoto.jsonl` so users can verify offline with
# `gh attestation verify`, `cosign`, or `slsa-verifier` — no GitHub API call
# required at verify time. This also satisfies the OSSF Scorecard
# `Signed-Releases` check, which scans for *.intoto.jsonl in release assets.
- name: Attest build provenance
id: attest
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
# Match the actual artifact files under target/release/ (download-artifact
# places them in a subdirectory, so we use ** to recurse).
subject-path: |
target/release/**/kingfisher-*.tgz
target/release/**/kingfisher-*.zip
target/release/**/kingfisher-*.deb
target/release/**/kingfisher-*.rpm
- name: Stage attestation bundle as a release asset
shell: bash
run: |
set -euo pipefail
BUNDLE_PATH='${{ steps.attest.outputs.bundle-path }}'
if [[ -z "${BUNDLE_PATH}" || ! -f "${BUNDLE_PATH}" ]]; then
echo "::error::attest-build-provenance did not produce a bundle at '${BUNDLE_PATH}'"
exit 1
fi
# Use the slsa-verifier-recognized filename for multi-subject bundles.
mkdir -p target/release
cp "${BUNDLE_PATH}" target/release/multiple.intoto.jsonl
echo "Bundle line count (one DSSE-wrapped attestation per subject):"
wc -l target/release/multiple.intoto.jsonl
# ── create the release using just that snippet ─────────────────────
- name: Create release & upload assets
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
@ -365,11 +399,6 @@ jobs:
generateReleaseNotes: false
artifacts: target/release/**
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: 'target/release/*'
# ──────────────── Publish Docker image ────────────────
publish-docker:
needs: [release]

View file

@ -318,19 +318,40 @@ Kingfisher supports multiple installation methods:
## Verifying Releases
Every Kingfisher release includes GitHub build attestations so you can verify that artifacts were built by our CI pipeline and haven't been tampered with.
Every release ships [SLSA v1 build-provenance attestations](https://github.com/actions/attest-build-provenance) (Sigstore keyless OIDC) proving the artifact was built by our CI workflow at a known commit and hasn't been tampered with. Attestations are available via the GitHub attestation store or as the `multiple.intoto.jsonl` release asset.
### GitHub attestations
**Option 1 — `gh attestation verify`** (simplest; requires [GitHub CLI](https://cli.github.com/))
Release artifacts have GitHub build attestations, verifiable with the GitHub CLI:
```bash
gh release download <version> --repo mongodb/kingfisher --pattern 'kingfisher-linux-x64.tgz'
gh attestation verify kingfisher-linux-x64.tgz --repo mongodb/kingfisher
```
**Option 2 — `cosign`** (offline-friendly; requires [cosign](https://docs.sigstore.dev/system_config/installation/) ≥ 2.x)
```bash
gh release download <version> --repo mongodb/kingfisher \
--pattern 'kingfisher-linux-x64.tgz'
--pattern 'kingfisher-linux-x64.tgz' --pattern 'multiple.intoto.jsonl'
gh attestation verify kingfisher-linux-x64.tgz --repo mongodb/kingfisher
cosign verify-blob-attestation \
--bundle multiple.intoto.jsonl \
--new-bundle-format \
--certificate-identity-regexp '^https://github.com/mongodb/kingfisher/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
kingfisher-linux-x64.tgz
```
**Option 3 — `slsa-verifier`** (requires [slsa-verifier](https://github.com/slsa-framework/slsa-verifier))
```bash
slsa-verifier verify-artifact kingfisher-linux-x64.tgz \
--provenance-path multiple.intoto.jsonl \
--source-uri github.com/mongodb/kingfisher \
--source-tag <version>
```
A successful verification prints `Verified OK`. The attestation proves the artifact's SHA-256, the signing identity (the release workflow at a specific tag), and the source commit — all recorded in the public [Rekor transparency log](https://search.sigstore.dev/).
## Report Viewer (local and hosted)

View file

@ -317,10 +317,7 @@ impl PermissionSummary {
}
pub fn total(&self) -> usize {
self.admin.len()
+ self.privilege_escalation.len()
+ self.risky.len()
+ self.read_only.len()
self.admin.len() + self.privilege_escalation.len() + self.risky.len() + self.read_only.len()
}
}

View file

@ -138,10 +138,8 @@ pub async fn map_access_from_token(token: &str) -> Result<AccessMapResult> {
for index in &indexes {
let name = index.name.clone().unwrap_or_else(|| "unknown".to_string());
let metric = index.metric.as_deref().unwrap_or("unknown");
let dimension = index
.dimension
.map(|d| d.to_string())
.unwrap_or_else(|| "unknown".to_string());
let dimension =
index.dimension.map(|d| d.to_string()).unwrap_or_else(|| "unknown".to_string());
let ready = index.status.as_ref().and_then(|s| s.ready).unwrap_or(false);
let state = index
.status
@ -175,13 +173,9 @@ pub async fn map_access_from_token(token: &str) -> Result<AccessMapResult> {
}
}
let host_suffix =
index.host.as_ref().map(|h| format!(" ({h})")).unwrap_or_default();
let location_suffix = if location.is_empty() {
String::new()
} else {
format!("{location}")
};
let host_suffix = index.host.as_ref().map(|h| format!(" ({h})")).unwrap_or_default();
let location_suffix =
if location.is_empty() { String::new() } else { format!("{location}") };
let ready_marker = if ready { "ready" } else { "not ready" };
resources.push(ResourceExposure {
@ -316,16 +310,12 @@ async fn fetch_collections(client: &Client, token: &str) -> Result<Vec<PineconeC
Ok(list.collections)
}
fn derive_severity(
indexes: &[PineconeIndex],
collections: &[PineconeCollection],
) -> Severity {
fn derive_severity(indexes: &[PineconeIndex], collections: &[PineconeCollection]) -> Severity {
let index_count = indexes.len();
let collection_count = collections.len();
let total = index_count + collection_count;
let any_unprotected = indexes
.iter()
.any(|i| i.deletion_protection.as_deref().unwrap_or("disabled") != "enabled");
let any_unprotected =
indexes.iter().any(|i| i.deletion_protection.as_deref().unwrap_or("disabled") != "enabled");
if index_count > 10 {
return Severity::High;

View file

@ -1256,11 +1256,8 @@ impl DetailsReporter {
groups.sort_by(|a, b| a.resources.cmp(&b.resources));
let permissions_by_severity = if result.permissions.is_empty() {
None
} else {
Some(result.permissions.clone())
};
let permissions_by_severity =
if result.permissions.is_empty() { None } else { Some(result.permissions.clone()) };
let context = AccessIdentityContext::from_summary(&result.identity);
entries.push(AccessMapEntry {

View file

@ -328,9 +328,7 @@ pub fn rewrite_argv_for_reexec(argv: impl IntoIterator<Item = OsString>) -> Vec<
#[cfg(not(any(unix, windows)))]
{
// Fallback for unknown targets: best-effort UTF-8 conversion.
tok.to_str()
.map(|s| s.as_bytes().starts_with(prefix))
.unwrap_or(false)
tok.to_str().map(|s| s.as_bytes().starts_with(prefix)).unwrap_or(false)
}
}