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
33 changes: 25 additions & 8 deletions app/cli/cmd/attestation_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"fmt"

schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand All @@ -37,6 +38,7 @@ func newAttestationPushCmd() *cobra.Command {
signServerAuthCertPath string
signServerAuthCertPass string
bypassPolicyCheck bool
deactivateCIReport bool
)

cmd := &cobra.Command{
Expand Down Expand Up @@ -106,15 +108,29 @@ func newAttestationPushCmd() *cobra.Command {

res.Status.Digest = res.Digest

// If we are returning the json format, we also want to render the attestation table as one property so it can also be consumed
if flagOutputFormat == output.FormatJSON {
// Render the attestation status to a string
buf := &bytes.Buffer{}
if err := fullStatusTableWithWriter(res.Status, buf); err != nil {
return fmt.Errorf("failed to render output: %w", err)
}
// Render the attestation status to a string
buf := &bytes.Buffer{}
if err := fullStatusTableWithWriter(res.Status, buf); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could receive a boolean for ANSI colors, and defer the table rendering to the last step. Then, from the runner, the flag would be set to false, deactivating colors.

return fmt.Errorf("failed to render output: %w", err)
}

res.Status.TerminalOutput = buf.Bytes()
res.Status.TerminalOutput = buf.Bytes()

// Report to CI/CD platform if supported and not disabled
if !deactivateCIReport && !res.Status.DryRun && res.Status.RunnerContext != nil {
// Clean up possible ANSI characters from the output
sanitizedOutput := removeAnsiCharactersFromBytes(res.Status.TerminalOutput)
if err := res.Status.RunnerContext.RawRunner.Report(sanitizedOutput); err != nil {
logger.Warn().Err(err).Msg("failed to write CI/CD platform report")
} else {
// Log success message based on runner type
switch res.Status.RunnerContext.RawRunner.ID() {
case schemaapi.CraftingSchema_Runner_GITHUB_ACTION:
logger.Info().Msg("attestation report written to GitHub step summary")
case schemaapi.CraftingSchema_Runner_GITLAB_PIPELINE:
logger.Info().Msg("attestation report written to chainloop-attestation-report.txt artifact")
}
}
}

// In TABLE format, we render the attestation status
Expand Down Expand Up @@ -160,6 +176,7 @@ func newAttestationPushCmd() *cobra.Command {
cmd.Flags().StringVar(&signServerAuthCertPath, "signserver-client-cert", "", "path to client certificate in PEM format for authenticated SignServer TLS connection")
cmd.Flags().StringVar(&signServerAuthCertPass, "signserver-client-pass", "", "certificate passphrase for authenticated SignServer TLS connection")
cmd.Flags().BoolVar(&bypassPolicyCheck, exceptionFlagName, false, "do not fail this command on policy violations enforcement")
cmd.Flags().BoolVar(&deactivateCIReport, "deactivate-ci-report", false, "deactivate automatic attestation report to CI/CD platform")

return cmd
}
Expand Down
9 changes: 9 additions & 0 deletions app/cli/cmd/attestation_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"io"
"os"
"regexp"
"slices"
"strings"
"time"
Expand Down Expand Up @@ -302,3 +303,11 @@ func versionStringAttFinal(p *action.ProjectVersion) string {

return p.Version
}

// removeAnsiCharactersFromBytes removes ANSI escape codes from bytes slices.
// Credits to: https://github.com/acarl005/stripansi
func removeAnsiCharactersFromBytes(input []byte) []byte {
const ansiPattern = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
re := regexp.MustCompile(ansiPattern)
return re.ReplaceAll(input, []byte(""))
}
1 change: 1 addition & 0 deletions app/cli/documentation/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ Options
--annotation strings additional annotation in the format of key=value
--attestation-id string Unique identifier of the in-progress attestation
--bundle string output a Sigstore bundle to the provided path
--deactivate-ci-report deactivate automatic attestation report to CI/CD platform
--exception-bypass-policy-check do not fail this command on policy violations enforcement
-h, --help help for push
-k, --key string reference (path or env variable name) to the cosign or KMS key that will be used to sign the attestation
Expand Down
2 changes: 1 addition & 1 deletion app/cli/pkg/action/attestation_push.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2024 The Chainloop Authors.
// Copyright 2024-2025 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 2 additions & 0 deletions app/cli/pkg/action/attestation_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type AttestationStatusResult struct {
type AttestationResultRunnerContext struct {
EnvVars map[string]string
JobURL, RunnerType string
RawRunner crafter.SupportedRunner
}

type AttestationStatusWorkflowMeta struct {
Expand Down Expand Up @@ -202,6 +203,7 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
EnvVars: runnerEnvVars,
RunnerType: att.RunnerType.String(),
JobURL: att.RunnerUrl,
RawRunner: c.Runner,
}

return res, nil
Expand Down
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ type SupportedRunner interface {
// Returns nil if verification is not supported or not applicable for this runner.
// Non-blocking: errors are logged and returned as unavailable status.
VerifyCommitSignature(ctx context.Context, commitHash string) *commitverification.CommitVerification

// Report writes attestation table output to platform-specific location.
// Returns nil if platform doesn't support reporting.
Report(tableOutput []byte) error
}

type RunnerM map[schemaapi.CraftingSchema_Runner_RunnerType]SupportedRunner
Expand Down
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runners/azurepipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,7 @@ func (r *AzurePipeline) Environment() RunnerEnvironment {
func (r *AzurePipeline) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification {
return nil // Not supported for this runner
}

func (r *AzurePipeline) Report(_ []byte) error {
return nil
}
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runners/circleci_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@ func (r *CircleCIBuild) Environment() RunnerEnvironment {
func (r *CircleCIBuild) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification {
return nil // Not supported for this runner
}

func (r *CircleCIBuild) Report(_ []byte) error {
return nil
}
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runners/daggerpipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,7 @@ func (r *DaggerPipeline) verifyCommitViaGitLab(ctx context.Context, commitHash s
// Call GitLab API to verify commit
return commitverification.VerifyGitLabCommit(ctx, baseURL, projectPath, commitHash, token, r.logger)
}

func (r *DaggerPipeline) Report(_ []byte) error {
return nil
}
6 changes: 5 additions & 1 deletion pkg/attestation/crafter/runners/generic.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2023 The Chainloop Authors.
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,3 +65,7 @@ func (r *Generic) Environment() RunnerEnvironment {
func (r *Generic) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification {
return nil // Not supported for this runner
}

func (r *Generic) Report(_ []byte) error {
return nil
}
28 changes: 28 additions & 0 deletions pkg/attestation/crafter/runners/githubaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,31 @@ func (r *GitHubAction) VerifyCommitSignature(ctx context.Context, commitHash str
// Call GitHub API to verify commit
return commitverification.VerifyGitHubCommit(ctx, parts[0], parts[1], commitHash, token, r.logger)
}

// Report writes attestation table output to GitHub Step Summary
func (r *GitHubAction) Report(tableOutput []byte) error {
summaryFile := os.Getenv("GITHUB_STEP_SUMMARY")
if summaryFile == "" {
return fmt.Errorf("GITHUB_STEP_SUMMARY environment variable not set")
}

// Wrap table output in markdown code block
var content strings.Builder
content.WriteString("## Chainloop Attestation Report\n\n")
content.WriteString("```\n")
content.Write(tableOutput)
content.WriteString("```\n")

// Append to GITHUB_STEP_SUMMARY file
f, err := os.OpenFile(summaryFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to open GITHUB_STEP_SUMMARY: %w", err)
}
defer f.Close()

if _, err := f.WriteString(content.String()); err != nil {
return fmt.Errorf("failed to write to GITHUB_STEP_SUMMARY: %w", err)
}

return nil
}
20 changes: 20 additions & 0 deletions pkg/attestation/crafter/runners/gitlabpipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package runners

import (
"context"
"fmt"
"os"

schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
Expand Down Expand Up @@ -138,3 +139,22 @@ func (r *GitlabPipeline) VerifyCommitSignature(ctx context.Context, commitHash s
// Call GitLab API to verify commit
return commitverification.VerifyGitLabCommit(ctx, baseURL, projectPath, commitHash, token, r.logger)
}

// Report writes attestation table output as text artifact
func (r *GitlabPipeline) Report(tableOutput []byte) error {
artifactFile := "chainloop-attestation-report.txt"

if err := os.WriteFile(artifactFile, tableOutput, 0600); err != nil {
return fmt.Errorf("failed to write attestation report: %w", err)
}

// Log instruction for GitLab CI configuration
r.logger.Info().Msgf("Attestation report written to %s", artifactFile)
r.logger.Info().Msg("To view in GitLab CI, add this to your job configuration:")
r.logger.Info().Msg("artifacts:")
r.logger.Info().Msg(" paths:")
r.logger.Info().Msgf(" - %s", artifactFile)
r.logger.Info().Msg(" expire_in: 30 days")

return nil
}
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runners/jenkinsjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ func (r *JenkinsJob) Environment() RunnerEnvironment {
func (r *JenkinsJob) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification {
return nil // Not supported for this runner
}

func (r *JenkinsJob) Report(_ []byte) error {
return nil
}
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runners/teamcitypipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ func (r *TeamCityPipeline) Environment() RunnerEnvironment {
func (r *TeamCityPipeline) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification {
return nil // Not supported for this runner
}

func (r *TeamCityPipeline) Report(_ []byte) error {
return nil
}
4 changes: 4 additions & 0 deletions pkg/attestation/crafter/runners/tektonpipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ func (r *TektonPipeline) Environment() RunnerEnvironment {
func (r *TektonPipeline) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification {
return nil // Not supported for this runner
}

func (r *TektonPipeline) Report(_ []byte) error {
return nil
}
Loading