From 448133b76f8a06aa48e8a2882e1e6c999da3ee1e Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 15:21:15 +0100 Subject: [PATCH 01/12] push attestation on gate fail Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 12 ++++++++++ app/cli/cmd/attestation_push.go | 1 + app/cli/pkg/action/attestation_add.go | 21 ++++++++++++++++ app/cli/pkg/action/attestation_push.go | 33 +++++++++++++++----------- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index ca7334d30..e4ba179e0 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -38,6 +38,7 @@ func newAttestationAddCmd() *cobra.Command { var name, value, kind string var artifactCASConn *grpc.ClientConn var annotationsFlag []string + var skipPushOnGateFailure bool // OCI registry credentials can be passed as flags or environment variables var registryServer, registryUsername, registryPassword string @@ -129,6 +130,16 @@ func newAttestationAddCmd() *cobra.Command { for _, evaluations := range policies { for _, eval := range evaluations { if len(eval.Violations) > 0 && eval.Gate { + if !skipPushOnGateFailure { + // Auto-push incomplete attestation + logger.Info().Msgf("Gated policy %q failed during material add, pushing attesstation", eval.Name) + + if err := a.PushIncompleteAttestation(cmd.Context(), attestationID); err != nil { + return fmt.Errorf("failed to auto-push: %w", err) + } + + logger.Info().Msg("push completed") + } return NewGateError(eval.Name) } } @@ -155,6 +166,7 @@ func newAttestationAddCmd() *cobra.Command { cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value") flagAttestationID(cmd) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("kind of the material to be recorded: %q", schemaapi.ListAvailableMaterialKind())) + cmd.Flags().BoolVar(&skipPushOnGateFailure, "skip-push-on-gate-failure", false, "do not push attestation when a gated policy fails") // Optional OCI registry credentials cmd.Flags().StringVar(®istryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName)) diff --git a/app/cli/cmd/attestation_push.go b/app/cli/cmd/attestation_push.go index a5954a9b8..311dbd61c 100644 --- a/app/cli/cmd/attestation_push.go +++ b/app/cli/cmd/attestation_push.go @@ -76,6 +76,7 @@ func newAttestationPushCmd() *cobra.Command { AuthClientCertPath: signServerAuthCertPath, AuthClientCertPass: signServerAuthCertPass, }, + ValidateAttestation: true, // Always validate on push }) if err != nil { return fmt.Errorf("failed to load action: %w", err) diff --git a/app/cli/pkg/action/attestation_add.go b/app/cli/pkg/action/attestation_add.go index 22b7e76d3..c0fdb6a0c 100644 --- a/app/cli/pkg/action/attestation_add.go +++ b/app/cli/pkg/action/attestation_add.go @@ -177,3 +177,24 @@ func (action *AttestationAdd) GetPolicyEvaluations(ctx context.Context, attestat return policyEvaluations, nil } + +// PushIncompleteAttestation pushes an incomplete attestation to the control plane +func (action *AttestationAdd) PushIncompleteAttestation(ctx context.Context, attestationID string) error { + pushAction, err := NewAttestationPush(&AttestationPushOpts{ + ActionsOpts: action.ActionsOpts, + KeyPath: "", // Keyless mode only + LocalStatePath: action.localStatePath, + ValidateAttestation: false, // Skip validation, attestation might be incomplete + }) + if err != nil { + return fmt.Errorf("failed to create push action: %w", err) + } + + // Execute push with bypass enabled to skip policy checks + _, err = pushAction.Run(ctx, attestationID, nil, true) + if err != nil { + return fmt.Errorf("failed to auto-push incomplete attestation: %w", err) + } + + return nil +} diff --git a/app/cli/pkg/action/attestation_push.go b/app/cli/pkg/action/attestation_push.go index 30c597c77..7a83dbbc3 100644 --- a/app/cli/pkg/action/attestation_push.go +++ b/app/cli/pkg/action/attestation_push.go @@ -38,8 +38,9 @@ type AttestationPushOpts struct { *ActionsOpts KeyPath, CLIVersion, CLIDigest, BundlePath string - LocalStatePath string - SignServerOpts *SignServerOpts + LocalStatePath string + SignServerOpts *SignServerOpts + ValidateAttestation bool } // SignServerOpts holds SignServer integration options @@ -60,6 +61,7 @@ type AttestationPush struct { *ActionsOpts keyPath, cliVersion, cliDigest, bundlePath string localStatePath string + validateAttestation bool signServerOpts *SignServerOpts *newCrafterOpts } @@ -67,14 +69,15 @@ type AttestationPush struct { func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) { opts := []crafter.NewOpt{crafter.WithLogger(&cfg.Logger), crafter.WithAuthRawToken(cfg.AuthTokenRaw)} return &AttestationPush{ - ActionsOpts: cfg.ActionsOpts, - keyPath: cfg.KeyPath, - cliVersion: cfg.CLIVersion, - cliDigest: cfg.CLIDigest, - bundlePath: cfg.BundlePath, - signServerOpts: cfg.SignServerOpts, - localStatePath: cfg.LocalStatePath, - newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts}, + ActionsOpts: cfg.ActionsOpts, + keyPath: cfg.KeyPath, + cliVersion: cfg.CLIVersion, + cliDigest: cfg.CLIDigest, + bundlePath: cfg.BundlePath, + signServerOpts: cfg.SignServerOpts, + localStatePath: cfg.LocalStatePath, + validateAttestation: cfg.ValidateAttestation, + newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts}, }, nil } @@ -145,11 +148,13 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru // Set the annotations crafter.CraftingState.Attestation.Annotations = craftedAnnotations - if err := crafter.ValidateAttestation(); err != nil { - return nil, err - } + if action.validateAttestation { + if err := crafter.ValidateAttestation(); err != nil { + return nil, err + } - action.Logger.Debug().Msg("validation completed") + action.Logger.Debug().Msg("validation completed") + } // Update status annotations finalAnnotations := make([]*Annotation, 0, len(craftedAnnotations)) From f4b97923360afb27eba4a606d014b8abcd11c14d Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 15:29:38 +0100 Subject: [PATCH 02/12] remove duplicated info log Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index e4ba179e0..e79562698 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -137,8 +137,6 @@ func newAttestationAddCmd() *cobra.Command { if err := a.PushIncompleteAttestation(cmd.Context(), attestationID); err != nil { return fmt.Errorf("failed to auto-push: %w", err) } - - logger.Info().Msg("push completed") } return NewGateError(eval.Name) } From c64d7f499c0383fc101b356c8d8f312e09149439 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 15:30:37 +0100 Subject: [PATCH 03/12] log msg fix Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index e79562698..54822f7ed 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -132,7 +132,7 @@ func newAttestationAddCmd() *cobra.Command { if len(eval.Violations) > 0 && eval.Gate { if !skipPushOnGateFailure { // Auto-push incomplete attestation - logger.Info().Msgf("Gated policy %q failed during material add, pushing attesstation", eval.Name) + logger.Info().Msgf("Gated policy %q failed during material add, pushing attestation", eval.Name) if err := a.PushIncompleteAttestation(cmd.Context(), attestationID); err != nil { return fmt.Errorf("failed to auto-push: %w", err) From 97921afd6d6ec2e0138d8d9ab8862da4509cc257 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 15:38:50 +0100 Subject: [PATCH 04/12] disable annotation validation Signed-off-by: Sylwester Piskozub --- app/cli/pkg/action/attestation_push.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/cli/pkg/action/attestation_push.go b/app/cli/pkg/action/attestation_push.go index 7a83dbbc3..e191f6992 100644 --- a/app/cli/pkg/action/attestation_push.go +++ b/app/cli/pkg/action/attestation_push.go @@ -135,14 +135,16 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru // Make sure all the annotation values are now set // This is in fact validated below but by manually checking we can provide a better error message - for k, v := range craftedAnnotations { - var missingAnnotations []string - if v == "" { - missingAnnotations = append(missingAnnotations, k) - } - - if len(missingAnnotations) > 0 { - return nil, fmt.Errorf("annotations %q required", missingAnnotations) + if action.validateAttestation { + for k, v := range craftedAnnotations { + var missingAnnotations []string + if v == "" { + missingAnnotations = append(missingAnnotations, k) + } + + if len(missingAnnotations) > 0 { + return nil, fmt.Errorf("annotations %q required", missingAnnotations) + } } } // Set the annotations From 9bac3cb2ef56b7631028bc8c5044e5c1ae9ea933 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 15:42:55 +0100 Subject: [PATCH 05/12] add cli ref Signed-off-by: Sylwester Piskozub --- app/cli/documentation/cli-reference.mdx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 21c14c386..bc91c1001 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -197,15 +197,16 @@ chainloop attestation add --value https://example.com/sbom.json Options ``` ---annotation strings additional annotation in the format of key=value ---attestation-id string Unique identifier of the in-progress attestation --h, --help help for add ---kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] ---name string name of the material as shown in the contract ---registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) ---registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) ---registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) ---value string value to be recorded +--annotation strings additional annotation in the format of key=value +--attestation-id string Unique identifier of the in-progress attestation +-h, --help help for add +--kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] +--name string name of the material as shown in the contract +--registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) +--registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) +--registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) +--skip-push-on-gate-failure do not push attestation when a gated policy fails +--value string value to be recorded ``` Options inherited from parent commands From d1258f636643fdcbf18e39ec40bef93a3d3c019d Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 16:54:31 +0100 Subject: [PATCH 06/12] rename flag Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 54822f7ed..0deee3168 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -38,7 +38,7 @@ func newAttestationAddCmd() *cobra.Command { var name, value, kind string var artifactCASConn *grpc.ClientConn var annotationsFlag []string - var skipPushOnGateFailure bool + var bypassPolicyCheck bool // OCI registry credentials can be passed as flags or environment variables var registryServer, registryUsername, registryPassword string @@ -130,7 +130,7 @@ func newAttestationAddCmd() *cobra.Command { for _, evaluations := range policies { for _, eval := range evaluations { if len(eval.Violations) > 0 && eval.Gate { - if !skipPushOnGateFailure { + if !bypassPolicyCheck { // Auto-push incomplete attestation logger.Info().Msgf("Gated policy %q failed during material add, pushing attestation", eval.Name) @@ -164,7 +164,7 @@ func newAttestationAddCmd() *cobra.Command { cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value") flagAttestationID(cmd) cmd.Flags().StringVar(&kind, "kind", "", fmt.Sprintf("kind of the material to be recorded: %q", schemaapi.ListAvailableMaterialKind())) - cmd.Flags().BoolVar(&skipPushOnGateFailure, "skip-push-on-gate-failure", false, "do not push attestation when a gated policy fails") + cmd.Flags().BoolVar(&bypassPolicyCheck, exceptionFlagName, false, "do not push attestation when a gated policy fails") // Optional OCI registry credentials cmd.Flags().StringVar(®istryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName)) From 065e59f0c394c3217045f4c8634bbd723ecf0d7c Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Wed, 17 Dec 2025 17:05:13 +0100 Subject: [PATCH 07/12] remove validation check Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_push.go | 1 - app/cli/pkg/action/attestation_add.go | 15 ++++---- app/cli/pkg/action/attestation_push.go | 51 +++++++++++--------------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/app/cli/cmd/attestation_push.go b/app/cli/cmd/attestation_push.go index 311dbd61c..a5954a9b8 100644 --- a/app/cli/cmd/attestation_push.go +++ b/app/cli/cmd/attestation_push.go @@ -76,7 +76,6 @@ func newAttestationPushCmd() *cobra.Command { AuthClientCertPath: signServerAuthCertPath, AuthClientCertPass: signServerAuthCertPass, }, - ValidateAttestation: true, // Always validate on push }) if err != nil { return fmt.Errorf("failed to load action: %w", err) diff --git a/app/cli/pkg/action/attestation_add.go b/app/cli/pkg/action/attestation_add.go index c0fdb6a0c..df63c12dd 100644 --- a/app/cli/pkg/action/attestation_add.go +++ b/app/cli/pkg/action/attestation_add.go @@ -178,22 +178,21 @@ func (action *AttestationAdd) GetPolicyEvaluations(ctx context.Context, attestat return policyEvaluations, nil } -// PushIncompleteAttestation pushes an incomplete attestation to the control plane +// PushIncompleteAttestation pushes an attestation to the control plane +// Note: All required materials must be present for the push to succeed. func (action *AttestationAdd) PushIncompleteAttestation(ctx context.Context, attestationID string) error { pushAction, err := NewAttestationPush(&AttestationPushOpts{ - ActionsOpts: action.ActionsOpts, - KeyPath: "", // Keyless mode only - LocalStatePath: action.localStatePath, - ValidateAttestation: false, // Skip validation, attestation might be incomplete + ActionsOpts: action.ActionsOpts, + KeyPath: "", // Keyless mode only + LocalStatePath: action.localStatePath, }) if err != nil { return fmt.Errorf("failed to create push action: %w", err) } - // Execute push with bypass enabled to skip policy checks - _, err = pushAction.Run(ctx, attestationID, nil, true) + _, err = pushAction.Run(ctx, attestationID, nil, false) if err != nil { - return fmt.Errorf("failed to auto-push incomplete attestation: %w", err) + return fmt.Errorf("failed to auto-push attestation: %w", err) } return nil diff --git a/app/cli/pkg/action/attestation_push.go b/app/cli/pkg/action/attestation_push.go index e191f6992..30c597c77 100644 --- a/app/cli/pkg/action/attestation_push.go +++ b/app/cli/pkg/action/attestation_push.go @@ -38,9 +38,8 @@ type AttestationPushOpts struct { *ActionsOpts KeyPath, CLIVersion, CLIDigest, BundlePath string - LocalStatePath string - SignServerOpts *SignServerOpts - ValidateAttestation bool + LocalStatePath string + SignServerOpts *SignServerOpts } // SignServerOpts holds SignServer integration options @@ -61,7 +60,6 @@ type AttestationPush struct { *ActionsOpts keyPath, cliVersion, cliDigest, bundlePath string localStatePath string - validateAttestation bool signServerOpts *SignServerOpts *newCrafterOpts } @@ -69,15 +67,14 @@ type AttestationPush struct { func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) { opts := []crafter.NewOpt{crafter.WithLogger(&cfg.Logger), crafter.WithAuthRawToken(cfg.AuthTokenRaw)} return &AttestationPush{ - ActionsOpts: cfg.ActionsOpts, - keyPath: cfg.KeyPath, - cliVersion: cfg.CLIVersion, - cliDigest: cfg.CLIDigest, - bundlePath: cfg.BundlePath, - signServerOpts: cfg.SignServerOpts, - localStatePath: cfg.LocalStatePath, - validateAttestation: cfg.ValidateAttestation, - newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts}, + ActionsOpts: cfg.ActionsOpts, + keyPath: cfg.KeyPath, + cliVersion: cfg.CLIVersion, + cliDigest: cfg.CLIDigest, + bundlePath: cfg.BundlePath, + signServerOpts: cfg.SignServerOpts, + localStatePath: cfg.LocalStatePath, + newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts}, }, nil } @@ -135,29 +132,25 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru // Make sure all the annotation values are now set // This is in fact validated below but by manually checking we can provide a better error message - if action.validateAttestation { - for k, v := range craftedAnnotations { - var missingAnnotations []string - if v == "" { - missingAnnotations = append(missingAnnotations, k) - } - - if len(missingAnnotations) > 0 { - return nil, fmt.Errorf("annotations %q required", missingAnnotations) - } + for k, v := range craftedAnnotations { + var missingAnnotations []string + if v == "" { + missingAnnotations = append(missingAnnotations, k) + } + + if len(missingAnnotations) > 0 { + return nil, fmt.Errorf("annotations %q required", missingAnnotations) } } // Set the annotations crafter.CraftingState.Attestation.Annotations = craftedAnnotations - if action.validateAttestation { - if err := crafter.ValidateAttestation(); err != nil { - return nil, err - } - - action.Logger.Debug().Msg("validation completed") + if err := crafter.ValidateAttestation(); err != nil { + return nil, err } + action.Logger.Debug().Msg("validation completed") + // Update status annotations finalAnnotations := make([]*Annotation, 0, len(craftedAnnotations)) for name, value := range craftedAnnotations { From e6d8bf5530c498d08fb4dd4dab9ab2db1a34db8a Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Thu, 18 Dec 2025 10:41:54 +0100 Subject: [PATCH 08/12] return gate exit code Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- app/cli/pkg/action/attestation_add.go | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 0deee3168..c90b8d635 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -135,7 +135,7 @@ func newAttestationAddCmd() *cobra.Command { logger.Info().Msgf("Gated policy %q failed during material add, pushing attestation", eval.Name) if err := a.PushIncompleteAttestation(cmd.Context(), attestationID); err != nil { - return fmt.Errorf("failed to auto-push: %w", err) + logger.Warn().Msgf("failed to push attestation: %v", err) } } return NewGateError(eval.Name) diff --git a/app/cli/pkg/action/attestation_add.go b/app/cli/pkg/action/attestation_add.go index df63c12dd..aca83ec3b 100644 --- a/app/cli/pkg/action/attestation_add.go +++ b/app/cli/pkg/action/attestation_add.go @@ -181,18 +181,32 @@ func (action *AttestationAdd) GetPolicyEvaluations(ctx context.Context, attestat // PushIncompleteAttestation pushes an attestation to the control plane // Note: All required materials must be present for the push to succeed. func (action *AttestationAdd) PushIncompleteAttestation(ctx context.Context, attestationID string) error { + // Check if keyless signing is available + crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: (attestationID != ""), localStatePath: action.localStatePath}, action.CPConnection, action.opts...) + if err != nil { + return fmt.Errorf("loading crafter: %w", err) + } + + if err := crafter.LoadCraftingState(ctx, attestationID); err != nil { + return fmt.Errorf("loading existing attestation: %w", err) + } + + if crafter.CraftingState.GetAttestation().GetSigningOptions().GetSigningCa() == "" { + return fmt.Errorf("keyless signing not configured") + } + pushAction, err := NewAttestationPush(&AttestationPushOpts{ ActionsOpts: action.ActionsOpts, KeyPath: "", // Keyless mode only LocalStatePath: action.localStatePath, }) if err != nil { - return fmt.Errorf("failed to create push action: %w", err) + return fmt.Errorf("creating push action: %w", err) } _, err = pushAction.Run(ctx, attestationID, nil, false) if err != nil { - return fmt.Errorf("failed to auto-push attestation: %w", err) + return fmt.Errorf("pushing attestation: %w", err) } return nil From b1df03eb4405211ca044859d33c17a26d6562217 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Thu, 18 Dec 2025 10:43:38 +0100 Subject: [PATCH 09/12] fix info msg Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index c90b8d635..415df4516 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -132,7 +132,7 @@ func newAttestationAddCmd() *cobra.Command { if len(eval.Violations) > 0 && eval.Gate { if !bypassPolicyCheck { // Auto-push incomplete attestation - logger.Info().Msgf("Gated policy %q failed during material add, pushing attestation", eval.Name) + logger.Info().Msgf("gated policy %q failed during material add, pushing attestation", eval.Name) if err := a.PushIncompleteAttestation(cmd.Context(), attestationID); err != nil { logger.Warn().Msgf("failed to push attestation: %v", err) From a9e39fe10a42c31bed1bf3b17859e77f0b5e5831 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Thu, 18 Dec 2025 10:56:33 +0100 Subject: [PATCH 10/12] cli ref Signed-off-by: Sylwester Piskozub --- app/cli/documentation/cli-reference.mdx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index bc91c1001..98099a828 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -197,16 +197,16 @@ chainloop attestation add --value https://example.com/sbom.json Options ``` ---annotation strings additional annotation in the format of key=value ---attestation-id string Unique identifier of the in-progress attestation --h, --help help for add ---kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] ---name string name of the material as shown in the contract ---registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) ---registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) ---registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) ---skip-push-on-gate-failure do not push attestation when a gated policy fails ---value string value to be recorded +--annotation strings additional annotation in the format of key=value +--attestation-id string Unique identifier of the in-progress attestation +--exception-bypass-policy-check do not push attestation when a gated policy fails +-h, --help help for add +--kind string kind of the material to be recorded: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENVEX" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "TWISTCLI_SCAN_JSON" "ZAP_DAST_ZIP"] +--name string name of the material as shown in the contract +--registry-password string registry password, ($CHAINLOOP_REGISTRY_PASSWORD) +--registry-server string OCI repository server, ($CHAINLOOP_REGISTRY_SERVER) +--registry-username string registry username, ($CHAINLOOP_REGISTRY_USERNAME) +--value string value to be recorded ``` Options inherited from parent commands From 0ba2218574f856319dd7be10baec1285021662fc Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Sun, 21 Dec 2025 21:06:58 +0100 Subject: [PATCH 11/12] move thrown gate error Signed-off-by: Sylwester Piskozub --- app/cli/cmd/attestation_add.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 415df4516..981716e7b 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -137,8 +137,8 @@ func newAttestationAddCmd() *cobra.Command { if err := a.PushIncompleteAttestation(cmd.Context(), attestationID); err != nil { logger.Warn().Msgf("failed to push attestation: %v", err) } + return NewGateError(eval.Name) } - return NewGateError(eval.Name) } } } From 3451851c15a73db91040697534616d43f4bcede7 Mon Sep 17 00:00:00 2001 From: Sylwester Piskozub Date: Sun, 21 Dec 2025 21:07:48 +0100 Subject: [PATCH 12/12] remove unnecessary push arg; add only keyless comment Signed-off-by: Sylwester Piskozub --- app/cli/pkg/action/attestation_add.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/cli/pkg/action/attestation_add.go b/app/cli/pkg/action/attestation_add.go index aca83ec3b..f6b42fd33 100644 --- a/app/cli/pkg/action/attestation_add.go +++ b/app/cli/pkg/action/attestation_add.go @@ -179,7 +179,8 @@ func (action *AttestationAdd) GetPolicyEvaluations(ctx context.Context, attestat } // PushIncompleteAttestation pushes an attestation to the control plane -// Note: All required materials must be present for the push to succeed. +// Note: All required materials must be present for the push to succeed and only keyless signing is supported + func (action *AttestationAdd) PushIncompleteAttestation(ctx context.Context, attestationID string) error { // Check if keyless signing is available crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: (attestationID != ""), localStatePath: action.localStatePath}, action.CPConnection, action.opts...) @@ -197,7 +198,6 @@ func (action *AttestationAdd) PushIncompleteAttestation(ctx context.Context, att pushAction, err := NewAttestationPush(&AttestationPushOpts{ ActionsOpts: action.ActionsOpts, - KeyPath: "", // Keyless mode only LocalStatePath: action.localStatePath, }) if err != nil {