diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 038b599ff..b9d6c57bb 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -266,12 +266,6 @@ on: required: false default: "" - # Feature toggle: Artifacts attestation for build provenance - attestation: - description: Create a verifiable attestation for the plugin using Github OIDC. - type: boolean - required: false - # User inputs plugin-version-suffix: description: | @@ -588,7 +582,6 @@ concurrency: permissions: contents: write id-token: write - attestations: write pull-requests: read env: @@ -849,70 +842,6 @@ jobs: dist-artifacts-retention-days: ${{ inputs.dist-artifacts-retention-days }} DO-NOT-USE-allow-pinned-commit-hashes: ${{ inputs.DO-NOT-USE-allow-pinned-commit-hashes }} - build-attestation: - name: Build attestation - if: ${{ inputs.attestation }} - needs: - - ci - runs-on: ubuntu-x64-small - - outputs: - attestation-id: ${{ steps.attestation.outputs.attestation-id }} - attestation-url: ${{ steps.attestation.outputs.attestation-url }} - bundle-path: ${{ steps.attestation.outputs.bundle-path }} - provenance-attestation: ${{ steps.provenance-attestation.outputs.provenance-attestation }} - - steps: - - name: Download GitHub artifact - id: download-dist-artifacts - continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: ${{ inputs.dist-artifacts-prefix }}dist-artifacts - path: /tmp/dist-artifacts - - - name: Log unavailable dist-artifacts error - if: steps.download-dist-artifacts.outcome == 'failure' - run: | - echo "::error::The dist-artifacts artifact could not be downloaded. It may have expired (retention period: ${RETENTION_DAYS} days). Please re-run the entire workflow from the beginning to rebuild the plugin." - exit 1 - shell: bash - env: - RETENTION_DAYS: ${{ inputs.dist-artifacts-retention-days }} - - - name: Generate artifact attestation - if: ${{ inputs.attestation }} - id: attestation - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 - with: - subject-path: /tmp/dist-artifacts/*.zip - - - name: Generate provenance attestation reference - if: ${{ inputs.attestation }} - id: provenance-attestation - run: | - shopt -s nullglob - - universal_zip="" - for zip_path in "${DIST_ARTIFACTS_PATH}"/*.zip; do - zip_file="$(basename "${zip_path}")" - if [[ ! "${zip_file}" =~ \.[[:alnum:]]+_[[:alnum:]]+\.zip$ ]]; then - universal_zip="${zip_path}" - break - fi - done - - if [ -z "${universal_zip}" ]; then - echo "::error::Could not find a universal plugin ZIP to reference in the provenance attestation." - exit 1 - fi - - sha256="$(sha256sum "${universal_zip}" | cut -d ' ' -f1)" - echo "provenance-attestation=github#${GITHUB_REPOSITORY_OWNER}#sha256:${sha256}" >> "${GITHUB_OUTPUT}" - shell: bash - env: - DIST_ARTIFACTS_PATH: /tmp/dist-artifacts - publish-to-catalog: name: Publish to catalog (${{ matrix.environment }}) if: >- @@ -923,14 +852,12 @@ jobs: && needs.setup.result == 'success' && needs.upload-to-gcs-release.result == 'success' && needs.ci.result == 'success' - && (!inputs.attestation || needs.build-attestation.result == 'success') && needs.setup.outputs.environments != '[]' }} needs: - setup - upload-to-gcs-release - ci - - build-attestation strategy: # Allow each stage to be deployed independently, even if others fails fail-fast: false @@ -1036,7 +963,6 @@ jobs: gcloud-auth-token: ${{ steps.gcloud.outputs.id_token }} ignore-conflicts: ${{ steps.determine-continue.outputs.ignore_conflicts }} publish-as-pending: ${{ matrix.environment == 'prod-canary' || inputs.publish-to-catalog-as-pending }} - provenance-attestation: ${{ inputs.attestation && needs.build-attestation.outputs.provenance-attestation || '' }} - name: Print publish summary run: | diff --git a/actions/plugins/publish/publish/action.yml b/actions/plugins/publish/publish/action.yml index 8728f8de4..432b1b5fc 100644 --- a/actions/plugins/publish/publish/action.yml +++ b/actions/plugins/publish/publish/action.yml @@ -46,12 +46,6 @@ inputs: Default is "false". required: false default: "false" - provenance-attestation: - description: | - Optional provenance attestation reference to include when publishing the plugin. - Expected format: github##sha256:. - required: false - default: "" gcom-api-url: description: | FOR INTERNAL TESTING ONLY. @@ -81,10 +75,6 @@ runs: publish_args+=(--publish-as-pending) fi - if [ -n "${PROVENANCE_ATTESTATION}" ]; then - publish_args+=(--provenance-attestation "${PROVENANCE_ATTESTATION}") - fi - ${{ github.action_path }}/publish.sh \ "${publish_args[@]}" \ "${args[@]}" @@ -100,5 +90,4 @@ runs: IGNORE_CONFLICTS: ${{ inputs.ignore-conflicts }} PUBLISH_AS_PENDING: ${{ inputs.publish-as-pending }} - PROVENANCE_ATTESTATION: ${{ inputs.provenance-attestation }} shell: bash diff --git a/actions/plugins/publish/publish/publish.sh b/actions/plugins/publish/publish/publish.sh index 7de418b4a..4523c738b 100755 --- a/actions/plugins/publish/publish/publish.sh +++ b/actions/plugins/publish/publish/publish.sh @@ -5,7 +5,7 @@ if [[ "$RUNNER_DEBUG" == "1" ]]; then fi usage() { - echo "Usage: $0 --environment [--scopes ] [--publish-as-pending] [--provenance-attestation ] [--dry-run] " + echo "Usage: $0 --environment [--scopes ] [--publish-as-pending] [--dry-run] " } json_obj() { @@ -16,14 +16,12 @@ gcs_zip_urls=() scopes='' dry_run=false publish_as_pending=false -provenance_attestation='' while [[ "$#" -gt 0 ]]; do case $1 in --environment) gcom_env=$2; shift 2;; --scopes) scopes=$(echo $2 | jq -Rc 'split(",")'); shift 2;; --dry-run) dry_run=true; shift;; --publish-as-pending) publish_as_pending=true; shift;; - --provenance-attestation) provenance_attestation=$2; shift 2;; --help) usage exit 0 @@ -139,8 +137,7 @@ json_payload=$(jq -c -n \ --arg commit "$sha" \ --argjson scopes "$scopes" \ --argjson pending "$pending_param" \ - --arg provenanceAttestation "$provenance_attestation" \ - '$ARGS.named | if .provenanceAttestation == "" then del(.provenanceAttestation) else . end' + '$ARGS.named' ) echo $json_payload | jq if [ "$dry_run" = true ]; then diff --git a/examples/base/provisioned-plugin-auto-cd/publish.yaml b/examples/base/provisioned-plugin-auto-cd/publish.yaml index 6e72c5a43..50382ba6d 100644 --- a/examples/base/provisioned-plugin-auto-cd/publish.yaml +++ b/examples/base/provisioned-plugin-auto-cd/publish.yaml @@ -37,7 +37,6 @@ jobs: contents: write pull-requests: read id-token: write - attestations: write with: branch: ${{ github.event.inputs.branch }} environment: ${{ github.event.inputs.environment }} diff --git a/examples/base/provisioned-plugin-auto-cd/push.yaml b/examples/base/provisioned-plugin-auto-cd/push.yaml index 8c93eb760..5039aeb9a 100644 --- a/examples/base/provisioned-plugin-auto-cd/push.yaml +++ b/examples/base/provisioned-plugin-auto-cd/push.yaml @@ -22,7 +22,6 @@ jobs: contents: write pull-requests: read id-token: write - attestations: write with: # Checkout/build PR or main branch, depending on event branch: ${{ github.event_name == 'push' && github.ref_name || github.ref }} diff --git a/examples/base/provisioned-plugin-manual-deployment/publish.yaml b/examples/base/provisioned-plugin-manual-deployment/publish.yaml index 8f2064a96..33ab96f0f 100644 --- a/examples/base/provisioned-plugin-manual-deployment/publish.yaml +++ b/examples/base/provisioned-plugin-manual-deployment/publish.yaml @@ -37,7 +37,6 @@ jobs: contents: write pull-requests: read id-token: write - attestations: write with: branch: ${{ github.event.inputs.branch }} environment: ${{ github.event.inputs.environment }} diff --git a/examples/base/simple/publish.yaml b/examples/base/simple/publish.yaml index a23218e63..fc349fbd0 100644 --- a/examples/base/simple/publish.yaml +++ b/examples/base/simple/publish.yaml @@ -37,7 +37,6 @@ jobs: contents: write pull-requests: read id-token: write - attestations: write with: branch: ${{ github.event.inputs.branch }} environment: ${{ github.event.inputs.environment }} diff --git a/tests/act/internal/workflow/cd/cd.go b/tests/act/internal/workflow/cd/cd.go index f557a6415..1559f9ce9 100644 --- a/tests/act/internal/workflow/cd/cd.go +++ b/tests/act/internal/workflow/cd/cd.go @@ -46,7 +46,6 @@ func NewWorkflow(opts ...WorkflowOption) (Workflow, error) { Permissions: workflow.Permissions{ "contents": "write", "id-token": "write", - "attestations": "write", "pull-requests": "read", }, With: map[string]any{ @@ -134,7 +133,6 @@ type WorkflowInputs struct { DocsOnly *bool AllowPublishingPRsToProd *bool UploadGCSLatest *bool - Attestation *bool } // WithWorkflowInputs sets the inputs for the CD workflow. @@ -160,7 +158,6 @@ func WithWorkflowInputs(inputs WorkflowInputs) WorkflowOption { workflow.SetJobInput(job, "docs-only", inputs.DocsOnly) workflow.SetJobInput(job, "allow-publishing-prs-to-prod", inputs.AllowPublishingPRsToProd) workflow.SetJobInput(job, "upload-gcs-latest", inputs.UploadGCSLatest) - workflow.SetJobInput(job, "attestation", inputs.Attestation) } } diff --git a/tests/act/main_cd_test.go b/tests/act/main_cd_test.go index 22718828a..6edd75e1e 100644 --- a/tests/act/main_cd_test.go +++ b/tests/act/main_cd_test.go @@ -1,11 +1,8 @@ package main import ( - "crypto/sha256" "encoding/json" - "fmt" "net/http" - "os" "path/filepath" "testing" @@ -262,142 +259,6 @@ func TestCD(t *testing.T) { } } -func TestCD_PublishIncludesProvenanceAttestation(t *testing.T) { - t.Parallel() - - gitSha, err := getGitCommitSHA() - require.NoError(t, err) - - const ( - pluginVersion = "1.0.0" - pluginFolder = "simple-frontend" - pluginSlug = "grafana-simplefrontend-panel" - fakeGcomTokenDev = "dummy-gcom-api-key-dev" - fakeIapToken = "dummy-iap-token" - ) - - zipBytes, err := os.ReadFile(workflow.LocalMockdataPath( - "dist-artifacts-unsigned", - pluginFolder, - anyZipFileName(pluginSlug, pluginVersion), - )) - require.NoError(t, err) - zipSHA256 := sha256.Sum256(zipBytes) - expProvenanceAttestation := fmt.Sprintf("github#grafana#sha256:%x", zipSHA256) - - mockVault := workflow.VaultSecrets{ - DefaultValue: newPointer(""), - CommonSecrets: map[string]string{ - "plugins/gcom-publish-token:dev": fakeGcomTokenDev, - }, - } - - var publishCalls int - runner, err := act.NewRunner(t) - require.NoError(t, err) - - runner.GCOM.HandleFunc("POST /api/plugins", func(w http.ResponseWriter, r *http.Request) { - publishCalls++ - - require.Subset(t, r.Header, http.Header{ - "Accept": []string{"application/json"}, - "User-Agent": []string{"github-actions-shared-workflows:/plugins/publish"}, - "Authorization": []string{"Bearer " + fakeGcomTokenDev}, - }) - - var body map[string]any - require.NoError(t, json.NewDecoder(r.Body).Decode(&body), "should be able to decode json body") - require.Equal(t, expProvenanceAttestation, body["provenanceAttestation"]) - - w.WriteHeader(http.StatusOK) - require.NoError(t, json.NewEncoder(w).Encode(map[string]any{ - "plugin": map[string]any{"id": 1337}, - })) - }) - - gcsZipURLs, err := json.Marshal([]string{gcsPublishURL(pluginSlug, pluginVersion, "any")}) - require.NoError(t, err) - pluginOutput, err := json.Marshal(map[string]string{ - "id": pluginSlug, - "version": pluginVersion, - }) - require.NoError(t, err) - - wf, err := cd.NewWorkflow( - cd.WithWorkflowInputs(cd.WorkflowInputs{ - CI: ci.WorkflowInputs{ - PluginDirectory: workflow.Input(filepath.Join("tests", pluginFolder)), - DistArtifactsPrefix: workflow.Input(pluginFolder + "-"), - Testing: workflow.Input(false), - }, - Attestation: workflow.Input(true), - DisableDocsPublishing: workflow.Input(true), - DisableGitHubRelease: workflow.Input(true), - Environment: workflow.Input("dev"), - Scopes: workflow.Input("universal"), - }), - cd.WithMockedGCOM(t, runner.GCOM), - cd.MutateAllWorkflows().With( - workflow.WithMockedVault(t, mockVault), - ), - cd.MutateCDWorkflow().With( - workflow.WithOnlyOneJob(t, "publish-to-catalog", false), - workflow.WithNoOpJobWithOutputs(t, "setup", map[string]string{ - "commit-sha": gitSha, - "environments": `["dev"]`, - "publish-docs": "false", - "plugin-version-suffix": "", - "platforms": `["any"]`, - "is-release-reference": "true", - }), - workflow.WithNoOpJobWithOutputs(t, "ci", map[string]string{ - "plugin": string(pluginOutput), - }), - workflow.WithNoOpJobWithOutputs(t, "upload-to-gcs-release", map[string]string{ - "gcs-zip-urls": string(gcsZipURLs), - }), - workflow.WithReplacedStep( - t, - "build-attestation", - "download-dist-artifacts", - workflow.CopyMockFilesStep("dist-artifacts-unsigned/"+pluginFolder, "/tmp/dist-artifacts"), - ), - workflow.WithReplacedStep( - t, - "build-attestation", - "attestation", - workflow.MockOutputsStep(map[string]string{ - "attestation-id": "attestation-id", - "attestation-url": "https://github.com/grafana/plugin-ci-workflows/attestations/attestation-id", - "bundle-path": "/tmp/attestation.jsonl", - }), - ), - workflow.WithEnvironment(t, "build-attestation", "provenance-attestation", map[string]string{ - "GITHUB_REPOSITORY_OWNER": "grafana", - }), - workflow.WithNoOpStep(t, "publish-to-catalog", "check-and-create-stub"), - workflow.WithNoOpStep(t, "publish-to-catalog", "check-artifact-zips"), - workflow.WithReplacedStep( - t, - "publish-to-catalog", - "gcloud", - workflow.MockOutputsStep(map[string]string{ - "id_token": fakeIapToken, - }), - ), - workflow.WithMatrix("publish-to-catalog", map[string][]string{ - "environment": {"dev"}, - }), - ), - ) - require.NoError(t, err) - - r, err := runner.Run(wf, act.NewPushEventPayload("main")) - require.NoError(t, err) - require.True(t, r.Success, "workflow should succeed") - require.Equal(t, 1, publishCalls, "GCOM API POST /plugins should be called exactly once") -} - func TestCD_GCSLatest(t *testing.T) { // Tests that "latest" GCS release artifacts are only uploaded for release references, // and can be forced via the upload-gcs-latest input.