forked from mirrors/kingfisher
added SLSA provenance
This commit is contained in:
parent
b2287c99ee
commit
44d67cea1b
6 changed files with 73 additions and 41 deletions
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
|
|
@ -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]
|
||||
|
|
|
|||
31
README.md
31
README.md
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue