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
6 changes: 6 additions & 0 deletions app/cli/cmd/attestation_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ func attestationStatusTableOutput(status *action.AttestationStatusResult, w io.W
gt.AppendRow(table.Row{"Policies", "------"})
policiesTable(evs, gt)
}

// Add the Attestation View URL if available
if status.AttestationViewURL != "" {
gt.AppendRow(table.Row{"Attestation View URL", status.AttestationViewURL})
}

gt.Render()

if err := materialsTable(status, w, full); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions app/cli/cmd/workflow_workflow_run_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error {
gt.AppendRow(table.Row{"Policies", "------"})
policiesTable(evs, gt)
}

if run.Attestation.AttestationViewURL != "" {
gt.AppendRow(table.Row{"Attestation View URL", run.Attestation.AttestationViewURL})
}

gt.Render()

predicateV1Table(att)
Expand Down
32 changes: 32 additions & 0 deletions app/cli/pkg/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"

pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
Expand Down Expand Up @@ -153,3 +154,34 @@ func getCASBackend(ctx context.Context, client pb.AttestationServiceClient, work
casBackend.Uploader = casclient.New(artifactCASConn, casclient.WithLogger(logger))
return casBackendInfo, artifactCASConn.Close, nil
}

// fetchUIDashboardURL retrieves the UI Dashboard URL from the control plane
// Returns empty string if not configured or if fetch fails
func fetchUIDashboardURL(ctx context.Context, cpConnection *grpc.ClientConn) string {
if cpConnection == nil {
return ""
}

tmoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

client := pb.NewStatusServiceClient(cpConnection)
resp, err := client.Infoz(tmoutCtx, &pb.InfozRequest{})
if err != nil {
return ""
}

return resp.UiDashboardUrl
}

// buildAttestationViewURL constructs the attestation view URL
// Returns empty string if platformURL is not configured
func buildAttestationViewURL(uiDashboardURL, digest string) string {
if uiDashboardURL == "" || digest == "" {
return ""
}

// Trim trailing slash from platform URL if present
uiDashboardURL = strings.TrimRight(uiDashboardURL, "/")
return fmt.Sprintf("%s/attestation/%s?tab=summary", uiDashboardURL, digest)
}
9 changes: 6 additions & 3 deletions app/cli/pkg/action/attestation_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
policiesAllowedHostnames []string
// Timestamp Authority URL for new attestations
timestampAuthorityURL, signingCAName string
uiDashboardURL string
)

// Init in the control plane if needed including the runner context
Expand Down Expand Up @@ -220,6 +221,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
signingOpts := result.GetSigningOptions()
timestampAuthorityURL = signingOpts.GetTimestampAuthorityUrl()
signingCAName = signingOpts.GetSigningCa()
uiDashboardURL = result.GetUiDashboardUrl()

if v := workflowMeta.Version; v != nil && workflowRun.GetVersion() != nil {
v.Prerelease = workflowRun.GetVersion().GetPrerelease()
Expand Down Expand Up @@ -277,9 +279,10 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
TimestampAuthorityURL: timestampAuthorityURL,
SigningCAName: signingCAName,
},
Auth: authInfo,
CASBackend: casBackendInfo,
Logger: &action.Logger,
Auth: authInfo,
CASBackend: casBackendInfo,
Logger: &action.Logger,
UIDashboardURL: uiDashboardURL,
}

if err := action.c.Init(ctx, initOpts); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions app/cli/pkg/action/attestation_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
return nil, fmt.Errorf("pushing to control plane: %w", err)
}

// Build attestation view URL
attestationResult.Status.AttestationViewURL = buildAttestationViewURL(crafter.CraftingState.UiDashboardUrl, attestationResult.Digest)

action.Logger.Info().Msg("push completed")

// Save bundle to disk
Expand Down
1 change: 1 addition & 0 deletions app/cli/pkg/action/attestation_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type AttestationStatusResult struct {
HasPolicyViolations bool `json:"has_policy_violations"`
MustBlockOnPolicyViolations bool `json:"must_block_on_policy_violations"`
TimestampAuthority string `json:"timestamp_authority"`
AttestationViewURL string `json:"attestation_view_url"`
// This might only be set if the attestation is pushed
Digest string `json:"digest"`
// This is the human readable output of the attestation status
Expand Down
9 changes: 9 additions & 0 deletions app/cli/pkg/action/workflow_run_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type WorkflowRunAttestationItem struct {
PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"`
// Policy evaluation status
PolicyEvaluationStatus *PolicyEvaluationStatus `json:"policy_evaluation_status,omitempty"`
// URL to view the attestation in the UI
AttestationViewURL string `json:"attestation_view_url"`
}

type PolicyEvaluationStatus struct {
Expand Down Expand Up @@ -227,6 +229,12 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes

policyEvaluationStatus := att.GetPolicyEvaluationStatus()

var attestationViewURL string
baseUIDashboardURL := fetchUIDashboardURL(ctx, action.cfg.CPConnection)
if baseUIDashboardURL != "" {
attestationViewURL = buildAttestationViewURL(baseUIDashboardURL, att.DigestInCasBackend)
}

item.Attestation = &WorkflowRunAttestationItem{
Envelope: envelope,
Bundle: att.GetBundle(),
Expand All @@ -243,6 +251,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes
HasViolations: policyEvaluationStatus.HasViolations,
HasGatedViolations: policyEvaluationStatus.HasGatedViolations,
},
AttestationViewURL: attestationViewURL,
}

return item, nil
Expand Down
18 changes: 14 additions & 4 deletions app/controlplane/api/controlplane/v1/status.pb.go

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

2 changes: 2 additions & 0 deletions app/controlplane/api/controlplane/v1/status.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ message InfozResponse {
string chart_version = 3;
// Whether organization creation is restricted to admins
bool restricted_org_creation = 4;
// Link to the platform UI, if available
string ui_dashboard_url = 5;
}

message StatuszResponse {}
20 changes: 15 additions & 5 deletions app/controlplane/api/controlplane/v1/workflow_run.pb.go

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

2 changes: 2 additions & 0 deletions app/controlplane/api/controlplane/v1/workflow_run.proto
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ message AttestationServiceInitResponse {
SigningOptions signing_options = 5;
// array of hostnames that are allowed to be used in the policies
repeated string policies_allowed_hostnames = 6;
// URL pointing to the web dashboard. It might be empty if not available
string ui_dashboard_url = 7;
}

message SigningOptions {
Expand Down

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

17 changes: 16 additions & 1 deletion app/controlplane/api/gen/frontend/controlplane/v1/status.ts

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

Loading
Loading