diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f016204..bcd4963 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2025-06-02T14:41:29Z by kres 9f64b0d-dirty. +# Generated on 2025-07-02T13:16:29Z by kres 43deb91-dirty. name: default concurrency: @@ -22,6 +22,7 @@ jobs: permissions: actions: read contents: write + id-token: write issues: read packages: write pull-requests: read @@ -112,6 +113,13 @@ jobs: PUSH: "true" run: | make image-kres IMAGE_TAG=latest + - name: Install Cosign + if: startsWith(github.ref, 'refs/tags/') + uses: sigstore/cosign-installer@v3 + - name: Sign artifacts + if: startsWith(github.ref, 'refs/tags/') + run: | + find _out -type f -name _out/kres-* -exec cosign sign-blob --yes --output {}.sig {} \; - name: Generate Checksums if: startsWith(github.ref, 'refs/tags/') run: | @@ -131,3 +139,4 @@ jobs: files: |- _out/kres-* _out/sha*.txt + _out/*.sig diff --git a/.kres.yaml b/.kres.yaml index 0870a49..c6ed630 100644 --- a/.kres.yaml +++ b/.kres.yaml @@ -19,6 +19,10 @@ spec: PLATFORM: linux/amd64,linux/arm64 entrypointArgs: ['gen'] --- +kind: common.Release +spec: + generateSignatures: true +--- kind: golang.Build spec: outputs: diff --git a/internal/output/ghworkflow/gh_workflow.go b/internal/output/ghworkflow/gh_workflow.go index 293aa0f..dcb4c26 100644 --- a/internal/output/ghworkflow/gh_workflow.go +++ b/internal/output/ghworkflow/gh_workflow.go @@ -256,6 +256,11 @@ func (o *Output) AddStep(jobName string, steps ...*JobStep) { o.workflows[ciWorkflow].Jobs[jobName].Steps = append(o.workflows[ciWorkflow].Jobs[jobName].Steps, steps...) } +// AddJobPermissions adds permissions to the job. +func (o *Output) AddJobPermissions(jobName, permission, value string) { + o.workflows[ciWorkflow].Jobs[jobName].Permissions[permission] = value +} + // AddStepBefore adds step before another step in the job. func (o *Output) AddStepBefore(jobName, beforeStepID string, steps ...*JobStep) { job := o.workflows[ciWorkflow].Jobs[jobName] diff --git a/internal/project/common/gh_workflow.go b/internal/project/common/gh_workflow.go index e09fbed..0f891cd 100644 --- a/internal/project/common/gh_workflow.go +++ b/internal/project/common/gh_workflow.go @@ -79,10 +79,11 @@ type CoverageStep struct { // ReleaseStep defines options for release steps. type ReleaseStep struct { - BaseDirectory string `yaml:"baseDirectory"` - ReleaseNotes string `yaml:"releaseNotes"` - Artifacts []string `yaml:"artifacts"` - GenerateChecksums bool `yaml:"generateChecksums"` + BaseDirectory string `yaml:"baseDirectory"` + ReleaseNotes string `yaml:"releaseNotes"` + Artifacts []string `yaml:"artifacts"` + GenerateChecksums bool `yaml:"generateChecksums"` + GenerateSignatures bool `yaml:"generateSignatures"` } // RegistryLoginStep defines options for registry login steps. @@ -281,7 +282,29 @@ func (gh *GHWorkflow) CompileGitHubWorkflow(o *ghworkflow.Output) error { SetWith("draft", "true"). SetWith("files", strings.Join(artifacts, "\n")) + if step.ReleaseStep.GenerateSignatures { + jobDef.Permissions["id-token"] = "write" + + cosignStep := ghworkflow.Step("Install Cosign"). + SetUses("sigstore/cosign-installer@" + config.CosignInstallActionVerson) + + jobDef.Steps = append(jobDef.Steps, cosignStep) + + signCommands := xslices.Map(artifacts, func(artifact string) string { + return fmt.Sprintf("cosign sign-blob --output %s.sig --yes %s", artifact, artifact) + }) + + signStep := ghworkflow.Step("Sign artifacts"). + SetCommand(strings.Join(signCommands, "\n")) + + jobDef.Steps = append(jobDef.Steps, signStep) + + releaseStep.SetWith("files", strings.Join(artifacts, "\n")+"\n"+filepath.Join(step.ReleaseStep.BaseDirectory, "*.sig")) + } + if step.ReleaseStep.GenerateChecksums { + jobDef.Permissions["id-token"] = "write" + checkSumCommands := []string{ fmt.Sprintf("cd %s", step.ReleaseStep.BaseDirectory), fmt.Sprintf("sha256sum %s > %s", strings.Join(step.ReleaseStep.Artifacts, " "), "sha256sum.txt"), @@ -291,10 +314,29 @@ func (gh *GHWorkflow) CompileGitHubWorkflow(o *ghworkflow.Output) error { checkSumStep := ghworkflow.Step("Generate Checksums"). SetCommand(strings.Join(checkSumCommands, "\n")) + jobDef.Steps = append(jobDef.Steps, checkSumStep) + releaseStep. SetWith("files", strings.Join(artifacts, "\n")+"\n"+filepath.Join(step.ReleaseStep.BaseDirectory, "sha*.txt")) - jobDef.Steps = append(jobDef.Steps, checkSumStep) + if step.ReleaseStep.GenerateSignatures { + checkSumSignCommands := []string{ + "cosign sign-blob --output sha256sum.txt.sig --yes sha256sum.txt", + "cosign sign-blob --output sha512sum.txt.sig --yes sha512sum.txt", + } + + signStep := ghworkflow.Step("Sign checksums"). + SetCommand(strings.Join(checkSumSignCommands, "\n")) + + jobDef.Steps = append(jobDef.Steps, signStep) + + releaseStep.SetWith("files", + strings.Join(artifacts, "\n")+ + "\n"+ + filepath.Join(step.ReleaseStep.BaseDirectory, "sha*.txt")+"\n"+ + filepath.Join(step.ReleaseStep.BaseDirectory, "*.sig"), + ) + } } jobDef.Steps = append(jobDef.Steps, releaseStep) diff --git a/internal/project/common/release.go b/internal/project/common/release.go index 8d34635..2bc1bca 100644 --- a/internal/project/common/release.go +++ b/internal/project/common/release.go @@ -27,7 +27,8 @@ type Release struct { // List of file patterns relative to the ArtifactsPath to include in the release. // // If not specified, defaults to the auto-detected commands. - Artifacts []string `yaml:"artifacts"` + Artifacts []string `yaml:"artifacts"` + GenerateSignatures bool `yaml:"generateSignatures,omitempty"` } // NewRelease initializes Release. @@ -88,7 +89,35 @@ func (release *Release) CompileGitHubWorkflow(output *ghworkflow.Output) error { checkSumStep := ghworkflow.Step("Generate Checksums"). SetCommand(strings.Join(checkSumCommands, "\n")) - releaseStep.SetWith("files", strings.Join(artifacts, "\n")+"\n"+filepath.Join(release.meta.ArtifactsPath, "sha*.txt")) + artifactsToUpload := strings.Join(artifacts, "\n") + "\n" + filepath.Join(release.meta.ArtifactsPath, "sha*.txt") + + if release.GenerateSignatures { + output.AddJobPermissions("default", "id-token", "write") + + cosignStep := ghworkflow.Step("Install Cosign"). + SetUses("sigstore/cosign-installer@" + config.CosignInstallActionVerson) + + if err := cosignStep.SetConditions("only-on-tag"); err != nil { + return err + } + + signCommands := xslices.Map(artifacts, func(artifact string) string { + return fmt.Sprintf("find %s -type f -name %s -exec cosign sign-blob --yes --output {}.sig {} \\;", release.meta.ArtifactsPath, artifact) + }) + + signStep := ghworkflow.Step("Sign artifacts"). + SetCommand(strings.Join(signCommands, "\n")) + + if err := signStep.SetConditions("only-on-tag"); err != nil { + return err + } + + steps = append(steps, cosignStep, signStep) + + artifactsToUpload += "\n" + filepath.Join(release.meta.ArtifactsPath, "*.sig") + } + + releaseStep.SetWith("files", artifactsToUpload) if err := checkSumStep.SetConditions("only-on-tag"); err != nil { return err