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
14 changes: 9 additions & 5 deletions app/cli/cmd/attestation_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/chainloop-dev/chainloop/app/cli/cmd/output"
"github.com/chainloop-dev/chainloop/app/cli/pkg/action"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func newAttestationInitCmd() *cobra.Command {
Expand Down Expand Up @@ -77,11 +78,14 @@ func newAttestationInitCmd() *cobra.Command {
RunE: func(cmd *cobra.Command, _ []string) error {
a, err := action.NewAttestationInit(
&action.AttestationInitOpts{
ActionsOpts: ActionOpts,
DryRun: attestationDryRun,
Force: force,
UseRemoteState: useAttestationRemoteState,
LocalStatePath: attestationLocalStatePath,
ActionsOpts: ActionOpts,
DryRun: attestationDryRun,
Force: force,
UseRemoteState: useAttestationRemoteState,
LocalStatePath: attestationLocalStatePath,
CASURI: viper.GetString(confOptions.CASAPI.viperKey),
CASCAPath: viper.GetString(confOptions.CASCA.viperKey),
ConnectionInsecure: apiInsecure(),
},
)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions app/cli/documentation/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ 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_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"]
--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)
Expand Down Expand Up @@ -2871,7 +2871,7 @@ Options
--annotation strings Key-value pairs of material annotations (key=value)
-h, --help help for eval
--input stringArray Key-value pairs of policy inputs (key=value)
--kind string Kind of the material: ["ARTIFACT" "ATTESTATION" "BLACKDUCK_SCA_JSON" "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"]
--kind string Kind of the material: ["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"]
--material string Path to material or attestation file
-p, --policy string Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy) (default "policy.yaml")
```
Expand Down
52 changes: 52 additions & 0 deletions app/cli/pkg/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package action

import (
"context"
"fmt"
"os"
"path/filepath"
Expand All @@ -25,6 +26,9 @@ import (
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/statemanager/filesystem"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter/statemanager/remote"
"github.com/chainloop-dev/chainloop/pkg/casclient"
"github.com/chainloop-dev/chainloop/pkg/grpcconn"

"github.com/rs/zerolog"
"google.golang.org/grpc"
)
Expand Down Expand Up @@ -94,3 +98,51 @@ func newCrafter(stateOpts *newCrafterStateOpts, conn *grpc.ClientConn, opts ...c

return crafter.NewCrafter(stateManager, attClient, opts...)
}

// getCASBackend tries to get CAS upload credentials and set up a CAS client
func getCASBackend(ctx context.Context, client pb.AttestationServiceClient, workflowRunID, casCAPath, casURI string, casConnectionInsecure bool, logger zerolog.Logger, casBackend *casclient.CASBackend) (func() error, error) {
Copy link
Member Author

Choose a reason for hiding this comment

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

@jiparis the refactor

credsResp, err := client.GetUploadCreds(ctx, &pb.AttestationServiceGetUploadCredsRequest{
WorkflowRunId: workflowRunID,
})
if err != nil {
// Log warning but don't fail - will fall back to inline storage
logger.Warn().Err(err).Msg("failed to get CAS credentials for PR metadata, will store inline")
return nil, fmt.Errorf("getting upload creds: %w", err)
}

if credsResp == nil || credsResp.GetResult() == nil {
logger.Debug().Msg("no upload creds result, will store inline")
return nil, fmt.Errorf("getting upload creds: %w", err)
}

result := credsResp.GetResult()
backend := result.GetBackend()
if backend == nil {
logger.Debug().Msg("no backend info in upload creds, will store inline")
return nil, fmt.Errorf("no backend found in upload creds")
}

casBackend.Name = backend.Provider
if backend.GetLimits() != nil {
casBackend.MaxSize = backend.GetLimits().MaxBytes
}

// Only attempt to create a CAS connection when not inline and token is present
if backend.IsInline || result.Token == "" {
return nil, nil
}

opts := []grpcconn.Option{grpcconn.WithInsecure(casConnectionInsecure)}
if casCAPath != "" {
opts = append(opts, grpcconn.WithCAFile(casCAPath))
}

artifactCASConn, err := grpcconn.New(casURI, result.Token, opts...)
if err != nil {
logger.Warn().Err(err).Msg("failed to create CAS connection, will store inline")
return nil, fmt.Errorf("creating CAS connection: %w", err)
}

casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(logger))
return artifactCASConn.Close, nil
}
40 changes: 7 additions & 33 deletions app/cli/pkg/action/attestation_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
"github.com/chainloop-dev/chainloop/pkg/casclient"
"github.com/chainloop-dev/chainloop/pkg/grpcconn"
"google.golang.org/grpc"
)

Expand Down Expand Up @@ -98,40 +97,15 @@ func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialNa

// Define CASbackend information based on the API response
if !crafter.CraftingState.GetDryRun() {
// Get upload creds and CASbackend for the current attestation and set up CAS client
client := pb.NewAttestationServiceClient(action.CPConnection)
creds, err := client.GetUploadCreds(ctx,
&pb.AttestationServiceGetUploadCredsRequest{
WorkflowRunId: crafter.CraftingState.GetAttestation().GetWorkflow().GetWorkflowRunId(),
},
)
if err != nil {
return nil, fmt.Errorf("getting upload creds: %w", err)
}
b := creds.GetResult().GetBackend()
if b == nil {
return nil, fmt.Errorf("no backend found in upload creds")
workflowRunID := crafter.CraftingState.GetAttestation().GetWorkflow().GetWorkflowRunId()
connectionCloserFn, getCASBackendErr := getCASBackend(ctx, client, workflowRunID, action.casCAPath, action.casURI, action.connectionInsecure, action.Logger, casBackend)
if getCASBackendErr != nil {
return nil, fmt.Errorf("failed to get CAS backend: %w", getCASBackendErr)
}
casBackend.Name = b.Provider
casBackend.MaxSize = b.GetLimits().MaxBytes
// Some CASBackends will actually upload information to the CAS server
// in such case we need to set up a connection
if !b.IsInline && creds.Result.Token != "" {
var opts = []grpcconn.Option{
grpcconn.WithInsecure(action.connectionInsecure),
}

if action.casCAPath != "" {
opts = append(opts, grpcconn.WithCAFile(action.casCAPath))
}

artifactCASConn, err := grpcconn.New(action.casURI, creds.Result.Token, opts...)
if err != nil {
return nil, fmt.Errorf("creating CAS connection: %w", err)
}
defer artifactCASConn.Close()

casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(action.Logger))
if connectionCloserFn != nil {
// nolint: errcheck
defer connectionCloserFn()
}
}

Expand Down
52 changes: 41 additions & 11 deletions app/cli/pkg/action/attestation_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
clientAPI "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
"github.com/chainloop-dev/chainloop/pkg/casclient"
"github.com/chainloop-dev/chainloop/pkg/policies"
"github.com/rs/zerolog"
)
Expand All @@ -37,16 +38,22 @@ type AttestationInitOpts struct {
// Force the initialization and override any existing, in-progress ones.
// Note that this is only useful when local-based attestation state is configured
// since it's a protection to make sure you don't override the state by mistake
Force bool
UseRemoteState bool
LocalStatePath string
Force bool
UseRemoteState bool
LocalStatePath string
CASURI string
CASCAPath string // optional CA certificate for the CAS connection
ConnectionInsecure bool
}

type AttestationInit struct {
*ActionsOpts
dryRun, force bool
c *crafter.Crafter
useRemoteState bool
dryRun, force bool
c *crafter.Crafter
useRemoteState bool
casURI string
casCAPath string
connectionInsecure bool
}

// ErrAttestationAlreadyExist means that there is an attestation in progress
Expand All @@ -67,11 +74,14 @@ func NewAttestationInit(cfg *AttestationInitOpts) (*AttestationInit, error) {
}

return &AttestationInit{
ActionsOpts: cfg.ActionsOpts,
c: c,
dryRun: cfg.DryRun,
force: cfg.Force,
useRemoteState: cfg.UseRemoteState,
ActionsOpts: cfg.ActionsOpts,
c: c,
dryRun: cfg.DryRun,
force: cfg.Force,
useRemoteState: cfg.UseRemoteState,
casURI: cfg.CASURI,
casCAPath: cfg.CASCAPath,
connectionInsecure: cfg.ConnectionInsecure,
}, nil
}

Expand Down Expand Up @@ -219,6 +229,20 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
attestationID = workflowRun.GetId()
}

// Get CAS credentials for PR metadata upload
var casBackend = &casclient.CASBackend{Name: "not-set"}
if !action.dryRun && attestationID != "" {
connectionCloserFn, err := getCASBackend(ctx, client, attestationID, action.casCAPath, action.casURI, action.connectionInsecure, action.Logger, casBackend)
if err != nil {
// We don't want to fail the attestation initialization if CAS setup fails, it's a best-effort feature for PR/MR metadata
action.Logger.Warn().Err(err).Msg("unexpected error getting CAS backend")
}
if connectionCloserFn != nil {
// nolint: errcheck
defer connectionCloserFn()
}
}

var authInfo *clientAPI.Attestation_Auth
if action.AuthTokenRaw != "" {
authInfo, err = extractAuthInfo(action.AuthTokenRaw)
Expand Down Expand Up @@ -268,6 +292,12 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
return "", err
}

// Auto-collect PR/MR metadata if in PR/MR context
if err := action.c.AutoCollectPRMetadata(ctx, attestationID, discoveredRunner, casBackend); err != nil {
action.Logger.Warn().Err(err).Msg("failed to auto-collect PR/MR metadata")
// Don't fail the init - this is best-effort
}

return attestationID, nil
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading