From e5825719eb313cab9d8eed6b8f4ab862da8563bc Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 9 Jan 2026 12:48:08 +0100 Subject: [PATCH 1/8] feat(user-verification): Introduce user verification on attestation init Signed-off-by: Javier Rodriguez --- app/cli/pkg/action/attestation_init.go | 1 + .../frontend/attestation/v1/crafting_state.ts | 229 ++++++++++- ....Commit.CommitVerification.jsonschema.json | 60 +++ ...n.v1.Commit.CommitVerification.schema.json | 60 +++ .../attestation.v1.Commit.jsonschema.json | 8 + .../attestation.v1.Commit.schema.json | 8 + extras/dagger/main.go | 20 +- .../api/attestation/v1/crafting_state.pb.go | 372 +++++++++++++----- .../api/attestation/v1/crafting_state.proto | 29 ++ pkg/attestation/crafter/crafter.go | 38 +- pkg/attestation/crafter/runner.go | 20 +- .../crafter/runners/azurepipeline.go | 8 +- .../crafter/runners/circleci_build.go | 9 +- .../runners/commitverification/github.go | 198 ++++++++++ .../runners/commitverification/gitlab.go | 168 ++++++++ .../crafter/runners/daggerpipeline.go | 106 ++++- .../crafter/runners/daggerpipeline_test.go | 4 +- pkg/attestation/crafter/runners/generic.go | 7 + .../crafter/runners/githubaction.go | 35 +- .../crafter/runners/gitlabpipeline.go | 28 +- pkg/attestation/crafter/runners/jenkinsjob.go | 7 + .../crafter/runners/teamcitypipeline.go | 7 + .../crafter/runners/tektonpipeline.go | 7 + 23 files changed, 1308 insertions(+), 121 deletions(-) create mode 100644 app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json create mode 100644 app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json create mode 100644 pkg/attestation/crafter/runners/commitverification/github.go create mode 100644 pkg/attestation/crafter/runners/commitverification/gitlab.go diff --git a/app/cli/pkg/action/attestation_init.go b/app/cli/pkg/action/attestation_init.go index e98e92f51..602776ade 100644 --- a/app/cli/pkg/action/attestation_init.go +++ b/app/cli/pkg/action/attestation_init.go @@ -279,6 +279,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun }, Auth: authInfo, CASBackend: casBackendInfo, + Logger: &action.Logger, } if err := action.c.Init(ctx, initOpts); err != nil { diff --git a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts index 7af46004d..08653b41d 100644 --- a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts +++ b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts @@ -336,6 +336,8 @@ export interface Commit { date?: Date; remotes: Commit_Remote[]; signature: string; + /** Platform verification information (GitHub/GitLab signature verification) */ + platformVerification?: Commit_CommitVerification | undefined; } export interface Commit_Remote { @@ -343,6 +345,80 @@ export interface Commit_Remote { url: string; } +export interface Commit_CommitVerification { + /** Whether verification was attempted */ + attempted: boolean; + /** Verification status */ + status: Commit_CommitVerification_VerificationStatus; + /** Human-readable reason for the status */ + reason: string; + /** Platform that performed the verification (e.g., "github", "gitlab") */ + platform: string; + /** Optional: The signing key ID if verified */ + keyId: string; + /** Optional: The signature algorithm used */ + signatureAlgorithm: string; +} + +export enum Commit_CommitVerification_VerificationStatus { + VERIFICATION_STATUS_UNSPECIFIED = 0, + /** VERIFICATION_STATUS_VERIFIED - Successfully verified by platform */ + VERIFICATION_STATUS_VERIFIED = 1, + /** VERIFICATION_STATUS_UNVERIFIED - Platform checked but signature is invalid/unverified */ + VERIFICATION_STATUS_UNVERIFIED = 2, + /** VERIFICATION_STATUS_UNAVAILABLE - Verification could not be performed (no API access, network error, etc.) */ + VERIFICATION_STATUS_UNAVAILABLE = 3, + /** VERIFICATION_STATUS_NOT_APPLICABLE - Platform doesn't support verification or no commit signature present */ + VERIFICATION_STATUS_NOT_APPLICABLE = 4, + UNRECOGNIZED = -1, +} + +export function commit_CommitVerification_VerificationStatusFromJSON( + object: any, +): Commit_CommitVerification_VerificationStatus { + switch (object) { + case 0: + case "VERIFICATION_STATUS_UNSPECIFIED": + return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED; + case 1: + case "VERIFICATION_STATUS_VERIFIED": + return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_VERIFIED; + case 2: + case "VERIFICATION_STATUS_UNVERIFIED": + return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNVERIFIED; + case 3: + case "VERIFICATION_STATUS_UNAVAILABLE": + return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNAVAILABLE; + case 4: + case "VERIFICATION_STATUS_NOT_APPLICABLE": + return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_NOT_APPLICABLE; + case -1: + case "UNRECOGNIZED": + default: + return Commit_CommitVerification_VerificationStatus.UNRECOGNIZED; + } +} + +export function commit_CommitVerification_VerificationStatusToJSON( + object: Commit_CommitVerification_VerificationStatus, +): string { + switch (object) { + case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED: + return "VERIFICATION_STATUS_UNSPECIFIED"; + case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_VERIFIED: + return "VERIFICATION_STATUS_VERIFIED"; + case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNVERIFIED: + return "VERIFICATION_STATUS_UNVERIFIED"; + case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNAVAILABLE: + return "VERIFICATION_STATUS_UNAVAILABLE"; + case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_NOT_APPLICABLE: + return "VERIFICATION_STATUS_NOT_APPLICABLE"; + case Commit_CommitVerification_VerificationStatus.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + /** Intermediate information that will get stored in the system while the run is being executed */ export interface CraftingState { inputSchema?: CraftingSchema | undefined; @@ -2960,7 +3036,16 @@ export const PolicyEvaluation_RawResult = { }; function createBaseCommit(): Commit { - return { hash: "", authorEmail: "", authorName: "", message: "", date: undefined, remotes: [], signature: "" }; + return { + hash: "", + authorEmail: "", + authorName: "", + message: "", + date: undefined, + remotes: [], + signature: "", + platformVerification: undefined, + }; } export const Commit = { @@ -2986,6 +3071,9 @@ export const Commit = { if (message.signature !== "") { writer.uint32(58).string(message.signature); } + if (message.platformVerification !== undefined) { + Commit_CommitVerification.encode(message.platformVerification, writer.uint32(66).fork()).ldelim(); + } return writer; }, @@ -3045,6 +3133,13 @@ export const Commit = { message.signature = reader.string(); continue; + case 8: + if (tag !== 66) { + break; + } + + message.platformVerification = Commit_CommitVerification.decode(reader, reader.uint32()); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -3063,6 +3158,9 @@ export const Commit = { date: isSet(object.date) ? fromJsonTimestamp(object.date) : undefined, remotes: Array.isArray(object?.remotes) ? object.remotes.map((e: any) => Commit_Remote.fromJSON(e)) : [], signature: isSet(object.signature) ? String(object.signature) : "", + platformVerification: isSet(object.platformVerification) + ? Commit_CommitVerification.fromJSON(object.platformVerification) + : undefined, }; }, @@ -3079,6 +3177,9 @@ export const Commit = { obj.remotes = []; } message.signature !== undefined && (obj.signature = message.signature); + message.platformVerification !== undefined && (obj.platformVerification = message.platformVerification + ? Commit_CommitVerification.toJSON(message.platformVerification) + : undefined); return obj; }, @@ -3095,6 +3196,9 @@ export const Commit = { message.date = object.date ?? undefined; message.remotes = object.remotes?.map((e) => Commit_Remote.fromPartial(e)) || []; message.signature = object.signature ?? ""; + message.platformVerification = (object.platformVerification !== undefined && object.platformVerification !== null) + ? Commit_CommitVerification.fromPartial(object.platformVerification) + : undefined; return message; }, }; @@ -3167,6 +3271,129 @@ export const Commit_Remote = { }, }; +function createBaseCommit_CommitVerification(): Commit_CommitVerification { + return { attempted: false, status: 0, reason: "", platform: "", keyId: "", signatureAlgorithm: "" }; +} + +export const Commit_CommitVerification = { + encode(message: Commit_CommitVerification, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.attempted === true) { + writer.uint32(8).bool(message.attempted); + } + if (message.status !== 0) { + writer.uint32(16).int32(message.status); + } + if (message.reason !== "") { + writer.uint32(26).string(message.reason); + } + if (message.platform !== "") { + writer.uint32(34).string(message.platform); + } + if (message.keyId !== "") { + writer.uint32(42).string(message.keyId); + } + if (message.signatureAlgorithm !== "") { + writer.uint32(50).string(message.signatureAlgorithm); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Commit_CommitVerification { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCommit_CommitVerification(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.attempted = reader.bool(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.status = reader.int32() as any; + continue; + case 3: + if (tag !== 26) { + break; + } + + message.reason = reader.string(); + continue; + case 4: + if (tag !== 34) { + break; + } + + message.platform = reader.string(); + continue; + case 5: + if (tag !== 42) { + break; + } + + message.keyId = reader.string(); + continue; + case 6: + if (tag !== 50) { + break; + } + + message.signatureAlgorithm = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Commit_CommitVerification { + return { + attempted: isSet(object.attempted) ? Boolean(object.attempted) : false, + status: isSet(object.status) ? commit_CommitVerification_VerificationStatusFromJSON(object.status) : 0, + reason: isSet(object.reason) ? String(object.reason) : "", + platform: isSet(object.platform) ? String(object.platform) : "", + keyId: isSet(object.keyId) ? String(object.keyId) : "", + signatureAlgorithm: isSet(object.signatureAlgorithm) ? String(object.signatureAlgorithm) : "", + }; + }, + + toJSON(message: Commit_CommitVerification): unknown { + const obj: any = {}; + message.attempted !== undefined && (obj.attempted = message.attempted); + message.status !== undefined && (obj.status = commit_CommitVerification_VerificationStatusToJSON(message.status)); + message.reason !== undefined && (obj.reason = message.reason); + message.platform !== undefined && (obj.platform = message.platform); + message.keyId !== undefined && (obj.keyId = message.keyId); + message.signatureAlgorithm !== undefined && (obj.signatureAlgorithm = message.signatureAlgorithm); + return obj; + }, + + create, I>>(base?: I): Commit_CommitVerification { + return Commit_CommitVerification.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Commit_CommitVerification { + const message = createBaseCommit_CommitVerification(); + message.attempted = object.attempted ?? false; + message.status = object.status ?? 0; + message.reason = object.reason ?? ""; + message.platform = object.platform ?? ""; + message.keyId = object.keyId ?? ""; + message.signatureAlgorithm = object.signatureAlgorithm ?? ""; + return message; + }, +}; + function createBaseCraftingState(): CraftingState { return { inputSchema: undefined, schemaV2: undefined, attestation: undefined, dryRun: false }; } diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json new file mode 100644 index 000000000..929d7c47c --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json @@ -0,0 +1,60 @@ +{ + "$id": "attestation.v1.Commit.CommitVerification.jsonschema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "patternProperties": { + "^(key_id)$": { + "description": "Optional: The signing key ID if verified", + "type": "string" + }, + "^(signature_algorithm)$": { + "description": "Optional: The signature algorithm used", + "type": "string" + } + }, + "properties": { + "attempted": { + "description": "Whether verification was attempted", + "type": "boolean" + }, + "keyId": { + "description": "Optional: The signing key ID if verified", + "type": "string" + }, + "platform": { + "description": "Platform that performed the verification (e.g., \"github\", \"gitlab\")", + "type": "string" + }, + "reason": { + "description": "Human-readable reason for the status", + "type": "string" + }, + "signatureAlgorithm": { + "description": "Optional: The signature algorithm used", + "type": "string" + }, + "status": { + "anyOf": [ + { + "enum": [ + "VERIFICATION_STATUS_UNSPECIFIED", + "VERIFICATION_STATUS_VERIFIED", + "VERIFICATION_STATUS_UNVERIFIED", + "VERIFICATION_STATUS_UNAVAILABLE", + "VERIFICATION_STATUS_NOT_APPLICABLE" + ], + "title": "Verification Status", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "Verification status" + } + }, + "title": "Commit Verification", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json new file mode 100644 index 000000000..301688ced --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json @@ -0,0 +1,60 @@ +{ + "$id": "attestation.v1.Commit.CommitVerification.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "patternProperties": { + "^(keyId)$": { + "description": "Optional: The signing key ID if verified", + "type": "string" + }, + "^(signatureAlgorithm)$": { + "description": "Optional: The signature algorithm used", + "type": "string" + } + }, + "properties": { + "attempted": { + "description": "Whether verification was attempted", + "type": "boolean" + }, + "key_id": { + "description": "Optional: The signing key ID if verified", + "type": "string" + }, + "platform": { + "description": "Platform that performed the verification (e.g., \"github\", \"gitlab\")", + "type": "string" + }, + "reason": { + "description": "Human-readable reason for the status", + "type": "string" + }, + "signature_algorithm": { + "description": "Optional: The signature algorithm used", + "type": "string" + }, + "status": { + "anyOf": [ + { + "enum": [ + "VERIFICATION_STATUS_UNSPECIFIED", + "VERIFICATION_STATUS_VERIFIED", + "VERIFICATION_STATUS_UNVERIFIED", + "VERIFICATION_STATUS_UNAVAILABLE", + "VERIFICATION_STATUS_NOT_APPLICABLE" + ], + "title": "Verification Status", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ], + "description": "Verification status" + } + }, + "title": "Commit Verification", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.jsonschema.json index 912e3703f..f364c5fb1 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.jsonschema.json @@ -10,6 +10,10 @@ "^(author_name)$": { "minLength": 1, "type": "string" + }, + "^(platform_verification)$": { + "$ref": "attestation.v1.Commit.CommitVerification.jsonschema.json", + "description": "Platform verification information (GitHub/GitLab signature verification)" } }, "properties": { @@ -32,6 +36,10 @@ "minLength": 1, "type": "string" }, + "platformVerification": { + "$ref": "attestation.v1.Commit.CommitVerification.jsonschema.json", + "description": "Platform verification information (GitHub/GitLab signature verification)" + }, "remotes": { "items": { "$ref": "attestation.v1.Commit.Remote.jsonschema.json" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.schema.json index ccb9c3d43..8dd8b2566 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.schema.json @@ -10,6 +10,10 @@ "^(authorName)$": { "minLength": 1, "type": "string" + }, + "^(platformVerification)$": { + "$ref": "attestation.v1.Commit.CommitVerification.schema.json", + "description": "Platform verification information (GitHub/GitLab signature verification)" } }, "properties": { @@ -32,6 +36,10 @@ "minLength": 1, "type": "string" }, + "platform_verification": { + "$ref": "attestation.v1.Commit.CommitVerification.schema.json", + "description": "Platform verification information (GitHub/GitLab signature verification)" + }, "remotes": { "items": { "$ref": "attestation.v1.Commit.Remote.schema.json" diff --git a/extras/dagger/main.go b/extras/dagger/main.go index b2b67f0c5..8c9b86249 100644 --- a/extras/dagger/main.go +++ b/extras/dagger/main.go @@ -68,7 +68,7 @@ type InstanceInfo struct { } // ParentCIContext holds environment variables from a parent CI system (Github Actions, Gitlab CI) -// to enable PR/MR auto-detection when running Chainloop via Dagger inside those CI systems +// to enable PR/MR auto-detection and commit verification when running Chainloop via Dagger inside those CI systems type ParentCIContext struct { // Github Actions PR context // Event name (e.g., "pull_request", "pull_request_target") @@ -77,6 +77,8 @@ type ParentCIContext struct { GithubHeadRef string // Target branch name GithubBaseRef string + // Github token for API access and commit verification + GithubToken *dagger.Secret // Gitlab CI MR context // Pipeline source (should be "merge_request_event" for MRs) @@ -95,6 +97,8 @@ type ParentCIContext struct { GitlabMRProjectURL string // User login GitlabUserLogin string + // Gitlab job token for API access and commit verification + GitlabJobToken *dagger.Secret } // Initialize a new attestation @@ -133,6 +137,9 @@ func (m *Chainloop) Init( // Github target branch name // +optional githubBaseRef string, + // Github token for API access and commit verification (when running in Github Actions) + // +optional + githubToken *dagger.Secret, // Gitlab pipeline source (should be "merge_request_event" for MRs) // +optional gitlabPipelineSource string, @@ -157,6 +164,9 @@ func (m *Chainloop) Init( // Gitlab user login // +optional gitlabUserLogin string, + // Gitlab job token for API access and commit verification (when running in Gitlab CI) + // +optional + gitlabJobToken *dagger.Secret, ) (*Attestation, error) { // Construct ParentCIContext from individual parameters var parentCIContext *ParentCIContext @@ -168,6 +178,7 @@ func (m *Chainloop) Init( GithubEventName: githubEventName, GithubHeadRef: githubHeadRef, GithubBaseRef: githubBaseRef, + GithubToken: githubToken, GitlabPipelineSource: gitlabPipelineSource, GitlabMRIID: gitlabMRIID, GitlabMRTitle: gitlabMRTitle, @@ -176,6 +187,7 @@ func (m *Chainloop) Init( GitlabMRTargetBranch: gitlabMRTargetBranch, GitlabMRProjectURL: gitlabMRProjectURL, GitlabUserLogin: gitlabUserLogin, + GitlabJobToken: gitlabJobToken, } } @@ -458,6 +470,9 @@ func cliContainer(ttl int, token *dagger.Secret, instance InstanceInfo, parentCI if parentCI.GithubBaseRef != "" { ctr = ctr.WithEnvVariable("GITHUB_BASE_REF", parentCI.GithubBaseRef) } + if parentCI.GithubToken != nil { + ctr = ctr.WithSecretVariable("GITHUB_TOKEN", parentCI.GithubToken) + } // Handle Github event file (passed as separate parameter for CLI convenience) if githubEventFile != nil { @@ -490,6 +505,9 @@ func cliContainer(ttl int, token *dagger.Secret, instance InstanceInfo, parentCI if parentCI.GitlabUserLogin != "" { ctr = ctr.WithEnvVariable("GITLAB_USER_LOGIN", parentCI.GitlabUserLogin) } + if parentCI.GitlabJobToken != nil { + ctr = ctr.WithSecretVariable("CI_JOB_TOKEN", parentCI.GitlabJobToken) + } } if token != nil { diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go index 9a09e4128..18cc0bd11 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go @@ -93,6 +93,65 @@ func (Attestation_Auth_AuthType) EnumDescriptor() ([]byte, []int) { return file_attestation_v1_crafting_state_proto_rawDescGZIP(), []int{0, 4, 0} } +type Commit_CommitVerification_VerificationStatus int32 + +const ( + Commit_CommitVerification_VERIFICATION_STATUS_UNSPECIFIED Commit_CommitVerification_VerificationStatus = 0 + // Successfully verified by platform + Commit_CommitVerification_VERIFICATION_STATUS_VERIFIED Commit_CommitVerification_VerificationStatus = 1 + // Platform checked but signature is invalid/unverified + Commit_CommitVerification_VERIFICATION_STATUS_UNVERIFIED Commit_CommitVerification_VerificationStatus = 2 + // Verification could not be performed (no API access, network error, etc.) + Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE Commit_CommitVerification_VerificationStatus = 3 + // Platform doesn't support verification or no commit signature present + Commit_CommitVerification_VERIFICATION_STATUS_NOT_APPLICABLE Commit_CommitVerification_VerificationStatus = 4 +) + +// Enum value maps for Commit_CommitVerification_VerificationStatus. +var ( + Commit_CommitVerification_VerificationStatus_name = map[int32]string{ + 0: "VERIFICATION_STATUS_UNSPECIFIED", + 1: "VERIFICATION_STATUS_VERIFIED", + 2: "VERIFICATION_STATUS_UNVERIFIED", + 3: "VERIFICATION_STATUS_UNAVAILABLE", + 4: "VERIFICATION_STATUS_NOT_APPLICABLE", + } + Commit_CommitVerification_VerificationStatus_value = map[string]int32{ + "VERIFICATION_STATUS_UNSPECIFIED": 0, + "VERIFICATION_STATUS_VERIFIED": 1, + "VERIFICATION_STATUS_UNVERIFIED": 2, + "VERIFICATION_STATUS_UNAVAILABLE": 3, + "VERIFICATION_STATUS_NOT_APPLICABLE": 4, + } +) + +func (x Commit_CommitVerification_VerificationStatus) Enum() *Commit_CommitVerification_VerificationStatus { + p := new(Commit_CommitVerification_VerificationStatus) + *p = x + return p +} + +func (x Commit_CommitVerification_VerificationStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Commit_CommitVerification_VerificationStatus) Descriptor() protoreflect.EnumDescriptor { + return file_attestation_v1_crafting_state_proto_enumTypes[1].Descriptor() +} + +func (Commit_CommitVerification_VerificationStatus) Type() protoreflect.EnumType { + return &file_attestation_v1_crafting_state_proto_enumTypes[1] +} + +func (x Commit_CommitVerification_VerificationStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Commit_CommitVerification_VerificationStatus.Descriptor instead. +func (Commit_CommitVerification_VerificationStatus) EnumDescriptor() ([]byte, []int) { + return file_attestation_v1_crafting_state_proto_rawDescGZIP(), []int{3, 1, 0} +} + type Attestation struct { state protoimpl.MessageState `protogen:"open.v1"` InitializedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=initialized_at,json=initializedAt,proto3" json:"initialized_at,omitempty"` @@ -570,14 +629,16 @@ type Commit struct { state protoimpl.MessageState `protogen:"open.v1"` Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // Commit authors might not include email i.e "Flux <>" - AuthorEmail string `protobuf:"bytes,2,opt,name=author_email,json=authorEmail,proto3" json:"author_email,omitempty"` - AuthorName string `protobuf:"bytes,3,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"` - Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` - Date *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=date,proto3" json:"date,omitempty"` - Remotes []*Commit_Remote `protobuf:"bytes,6,rep,name=remotes,proto3" json:"remotes,omitempty"` - Signature string `protobuf:"bytes,7,opt,name=signature,proto3" json:"signature,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AuthorEmail string `protobuf:"bytes,2,opt,name=author_email,json=authorEmail,proto3" json:"author_email,omitempty"` + AuthorName string `protobuf:"bytes,3,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + Date *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=date,proto3" json:"date,omitempty"` + Remotes []*Commit_Remote `protobuf:"bytes,6,rep,name=remotes,proto3" json:"remotes,omitempty"` + Signature string `protobuf:"bytes,7,opt,name=signature,proto3" json:"signature,omitempty"` + // Platform verification information (GitHub/GitLab signature verification) + PlatformVerification *Commit_CommitVerification `protobuf:"bytes,8,opt,name=platform_verification,json=platformVerification,proto3,oneof" json:"platform_verification,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Commit) Reset() { @@ -659,6 +720,13 @@ func (x *Commit) GetSignature() string { return "" } +func (x *Commit) GetPlatformVerification() *Commit_CommitVerification { + if x != nil { + return x.PlatformVerification + } + return nil +} + // Intermediate information that will get stored in the system while the run is being executed type CraftingState struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2010,6 +2078,96 @@ func (x *Commit_Remote) GetUrl() string { return "" } +type Commit_CommitVerification struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Whether verification was attempted + Attempted bool `protobuf:"varint,1,opt,name=attempted,proto3" json:"attempted,omitempty"` + // Verification status + Status Commit_CommitVerification_VerificationStatus `protobuf:"varint,2,opt,name=status,proto3,enum=attestation.v1.Commit_CommitVerification_VerificationStatus" json:"status,omitempty"` + // Human-readable reason for the status + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + // Platform that performed the verification (e.g., "github", "gitlab") + Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` + // Optional: The signing key ID if verified + KeyId string `protobuf:"bytes,5,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` + // Optional: The signature algorithm used + SignatureAlgorithm string `protobuf:"bytes,6,opt,name=signature_algorithm,json=signatureAlgorithm,proto3" json:"signature_algorithm,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Commit_CommitVerification) Reset() { + *x = Commit_CommitVerification{} + mi := &file_attestation_v1_crafting_state_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Commit_CommitVerification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Commit_CommitVerification) ProtoMessage() {} + +func (x *Commit_CommitVerification) ProtoReflect() protoreflect.Message { + mi := &file_attestation_v1_crafting_state_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Commit_CommitVerification.ProtoReflect.Descriptor instead. +func (*Commit_CommitVerification) Descriptor() ([]byte, []int) { + return file_attestation_v1_crafting_state_proto_rawDescGZIP(), []int{3, 1} +} + +func (x *Commit_CommitVerification) GetAttempted() bool { + if x != nil { + return x.Attempted + } + return false +} + +func (x *Commit_CommitVerification) GetStatus() Commit_CommitVerification_VerificationStatus { + if x != nil { + return x.Status + } + return Commit_CommitVerification_VERIFICATION_STATUS_UNSPECIFIED +} + +func (x *Commit_CommitVerification) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *Commit_CommitVerification) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + +func (x *Commit_CommitVerification) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +func (x *Commit_CommitVerification) GetSignatureAlgorithm() string { + if x != nil { + return x.SignatureAlgorithm + } + return "" +} + var File_attestation_v1_crafting_state_proto protoreflect.FileDescriptor const file_attestation_v1_crafting_state_proto_rawDesc = "" + @@ -2159,7 +2317,7 @@ const file_attestation_v1_crafting_state_proto_rawDesc = "" + "\borg_name\x18\x04 \x01(\tR\aorgName\x1a9\n" + "\tRawResult\x12\x14\n" + "\x05input\x18\x01 \x01(\fR\x05input\x12\x16\n" + - "\x06output\x18\x02 \x01(\fR\x06output\"\xde\x02\n" + + "\x06output\x18\x02 \x01(\fR\x06output\"\xb3\a\n" + "\x06Commit\x12\x1b\n" + "\x04hash\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04hash\x12!\n" + "\fauthor_email\x18\x02 \x01(\tR\vauthorEmail\x12(\n" + @@ -2168,10 +2326,25 @@ const file_attestation_v1_crafting_state_proto_rawDesc = "" + "\amessage\x18\x04 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\amessage\x12.\n" + "\x04date\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x04date\x127\n" + "\aremotes\x18\x06 \x03(\v2\x1d.attestation.v1.Commit.RemoteR\aremotes\x12\x1c\n" + - "\tsignature\x18\a \x01(\tR\tsignature\x1a@\n" + + "\tsignature\x18\a \x01(\tR\tsignature\x12c\n" + + "\x15platform_verification\x18\b \x01(\v2).attestation.v1.Commit.CommitVerificationH\x00R\x14platformVerification\x88\x01\x01\x1a@\n" + "\x06Remote\x12\x1b\n" + "\x04name\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04name\x12\x19\n" + - "\x03url\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x03url\"\x81\x02\n" + + "\x03url\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x03url\x1a\xd3\x03\n" + + "\x12CommitVerification\x12\x1c\n" + + "\tattempted\x18\x01 \x01(\bR\tattempted\x12T\n" + + "\x06status\x18\x02 \x01(\x0e2<.attestation.v1.Commit.CommitVerification.VerificationStatusR\x06status\x12\x16\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\x12\x1a\n" + + "\bplatform\x18\x04 \x01(\tR\bplatform\x12\x15\n" + + "\x06key_id\x18\x05 \x01(\tR\x05keyId\x12/\n" + + "\x13signature_algorithm\x18\x06 \x01(\tR\x12signatureAlgorithm\"\xcc\x01\n" + + "\x12VerificationStatus\x12#\n" + + "\x1fVERIFICATION_STATUS_UNSPECIFIED\x10\x00\x12 \n" + + "\x1cVERIFICATION_STATUS_VERIFIED\x10\x01\x12\"\n" + + "\x1eVERIFICATION_STATUS_UNVERIFIED\x10\x02\x12#\n" + + "\x1fVERIFICATION_STATUS_UNAVAILABLE\x10\x03\x12&\n" + + "\"VERIFICATION_STATUS_NOT_APPLICABLE\x10\x04B\x18\n" + + "\x16_platform_verification\"\x81\x02\n" + "\rCraftingState\x12H\n" + "\finput_schema\x18\x01 \x01(\v2#.workflowcontract.v1.CraftingSchemaH\x00R\vinputSchema\x12D\n" + "\tschema_v2\x18\x04 \x01(\v2%.workflowcontract.v1.CraftingSchemaV2H\x00R\bschemaV2\x12=\n" + @@ -2222,93 +2395,97 @@ func file_attestation_v1_crafting_state_proto_rawDescGZIP() []byte { return file_attestation_v1_crafting_state_proto_rawDescData } -var file_attestation_v1_crafting_state_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_attestation_v1_crafting_state_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_attestation_v1_crafting_state_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_attestation_v1_crafting_state_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_attestation_v1_crafting_state_proto_goTypes = []any{ - (Attestation_Auth_AuthType)(0), // 0: attestation.v1.Attestation.Auth.AuthType - (*Attestation)(nil), // 1: attestation.v1.Attestation - (*RunnerEnvironment)(nil), // 2: attestation.v1.RunnerEnvironment - (*PolicyEvaluation)(nil), // 3: attestation.v1.PolicyEvaluation - (*Commit)(nil), // 4: attestation.v1.Commit - (*CraftingState)(nil), // 5: attestation.v1.CraftingState - (*WorkflowMetadata)(nil), // 6: attestation.v1.WorkflowMetadata - (*ProjectVersion)(nil), // 7: attestation.v1.ProjectVersion - (*ResourceDescriptor)(nil), // 8: attestation.v1.ResourceDescriptor - nil, // 9: attestation.v1.Attestation.MaterialsEntry - nil, // 10: attestation.v1.Attestation.AnnotationsEntry - (*Attestation_Material)(nil), // 11: attestation.v1.Attestation.Material - nil, // 12: attestation.v1.Attestation.EnvVarsEntry - (*Attestation_Auth)(nil), // 13: attestation.v1.Attestation.Auth - (*Attestation_CASBackend)(nil), // 14: attestation.v1.Attestation.CASBackend - (*Attestation_SigningOptions)(nil), // 15: attestation.v1.Attestation.SigningOptions - nil, // 16: attestation.v1.Attestation.Material.AnnotationsEntry - (*Attestation_Material_KeyVal)(nil), // 17: attestation.v1.Attestation.Material.KeyVal - (*Attestation_Material_ContainerImage)(nil), // 18: attestation.v1.Attestation.Material.ContainerImage - (*Attestation_Material_Artifact)(nil), // 19: attestation.v1.Attestation.Material.Artifact - (*Attestation_Material_SBOMArtifact)(nil), // 20: attestation.v1.Attestation.Material.SBOMArtifact - (*Attestation_Material_SBOMArtifact_MainComponent)(nil), // 21: attestation.v1.Attestation.Material.SBOMArtifact.MainComponent - nil, // 22: attestation.v1.PolicyEvaluation.AnnotationsEntry - nil, // 23: attestation.v1.PolicyEvaluation.WithEntry - (*PolicyEvaluation_Violation)(nil), // 24: attestation.v1.PolicyEvaluation.Violation - (*PolicyEvaluation_Reference)(nil), // 25: attestation.v1.PolicyEvaluation.Reference - (*PolicyEvaluation_RawResult)(nil), // 26: attestation.v1.PolicyEvaluation.RawResult - (*Commit_Remote)(nil), // 27: attestation.v1.Commit.Remote - nil, // 28: attestation.v1.ResourceDescriptor.DigestEntry - (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp - (v1.CraftingSchema_Runner_RunnerType)(0), // 30: workflowcontract.v1.CraftingSchema.Runner.RunnerType - (v1.CraftingSchema_Material_MaterialType)(0), // 31: workflowcontract.v1.CraftingSchema.Material.MaterialType - (*v1.CraftingSchema)(nil), // 32: workflowcontract.v1.CraftingSchema - (*v1.CraftingSchemaV2)(nil), // 33: workflowcontract.v1.CraftingSchemaV2 - (*structpb.Struct)(nil), // 34: google.protobuf.Struct - (*wrapperspb.BoolValue)(nil), // 35: google.protobuf.BoolValue + (Attestation_Auth_AuthType)(0), // 0: attestation.v1.Attestation.Auth.AuthType + (Commit_CommitVerification_VerificationStatus)(0), // 1: attestation.v1.Commit.CommitVerification.VerificationStatus + (*Attestation)(nil), // 2: attestation.v1.Attestation + (*RunnerEnvironment)(nil), // 3: attestation.v1.RunnerEnvironment + (*PolicyEvaluation)(nil), // 4: attestation.v1.PolicyEvaluation + (*Commit)(nil), // 5: attestation.v1.Commit + (*CraftingState)(nil), // 6: attestation.v1.CraftingState + (*WorkflowMetadata)(nil), // 7: attestation.v1.WorkflowMetadata + (*ProjectVersion)(nil), // 8: attestation.v1.ProjectVersion + (*ResourceDescriptor)(nil), // 9: attestation.v1.ResourceDescriptor + nil, // 10: attestation.v1.Attestation.MaterialsEntry + nil, // 11: attestation.v1.Attestation.AnnotationsEntry + (*Attestation_Material)(nil), // 12: attestation.v1.Attestation.Material + nil, // 13: attestation.v1.Attestation.EnvVarsEntry + (*Attestation_Auth)(nil), // 14: attestation.v1.Attestation.Auth + (*Attestation_CASBackend)(nil), // 15: attestation.v1.Attestation.CASBackend + (*Attestation_SigningOptions)(nil), // 16: attestation.v1.Attestation.SigningOptions + nil, // 17: attestation.v1.Attestation.Material.AnnotationsEntry + (*Attestation_Material_KeyVal)(nil), // 18: attestation.v1.Attestation.Material.KeyVal + (*Attestation_Material_ContainerImage)(nil), // 19: attestation.v1.Attestation.Material.ContainerImage + (*Attestation_Material_Artifact)(nil), // 20: attestation.v1.Attestation.Material.Artifact + (*Attestation_Material_SBOMArtifact)(nil), // 21: attestation.v1.Attestation.Material.SBOMArtifact + (*Attestation_Material_SBOMArtifact_MainComponent)(nil), // 22: attestation.v1.Attestation.Material.SBOMArtifact.MainComponent + nil, // 23: attestation.v1.PolicyEvaluation.AnnotationsEntry + nil, // 24: attestation.v1.PolicyEvaluation.WithEntry + (*PolicyEvaluation_Violation)(nil), // 25: attestation.v1.PolicyEvaluation.Violation + (*PolicyEvaluation_Reference)(nil), // 26: attestation.v1.PolicyEvaluation.Reference + (*PolicyEvaluation_RawResult)(nil), // 27: attestation.v1.PolicyEvaluation.RawResult + (*Commit_Remote)(nil), // 28: attestation.v1.Commit.Remote + (*Commit_CommitVerification)(nil), // 29: attestation.v1.Commit.CommitVerification + nil, // 30: attestation.v1.ResourceDescriptor.DigestEntry + (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp + (v1.CraftingSchema_Runner_RunnerType)(0), // 32: workflowcontract.v1.CraftingSchema.Runner.RunnerType + (v1.CraftingSchema_Material_MaterialType)(0), // 33: workflowcontract.v1.CraftingSchema.Material.MaterialType + (*v1.CraftingSchema)(nil), // 34: workflowcontract.v1.CraftingSchema + (*v1.CraftingSchemaV2)(nil), // 35: workflowcontract.v1.CraftingSchemaV2 + (*structpb.Struct)(nil), // 36: google.protobuf.Struct + (*wrapperspb.BoolValue)(nil), // 37: google.protobuf.BoolValue } var file_attestation_v1_crafting_state_proto_depIdxs = []int32{ - 29, // 0: attestation.v1.Attestation.initialized_at:type_name -> google.protobuf.Timestamp - 29, // 1: attestation.v1.Attestation.finished_at:type_name -> google.protobuf.Timestamp - 6, // 2: attestation.v1.Attestation.workflow:type_name -> attestation.v1.WorkflowMetadata - 9, // 3: attestation.v1.Attestation.materials:type_name -> attestation.v1.Attestation.MaterialsEntry - 10, // 4: attestation.v1.Attestation.annotations:type_name -> attestation.v1.Attestation.AnnotationsEntry - 12, // 5: attestation.v1.Attestation.env_vars:type_name -> attestation.v1.Attestation.EnvVarsEntry - 30, // 6: attestation.v1.Attestation.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType - 4, // 7: attestation.v1.Attestation.head:type_name -> attestation.v1.Commit - 3, // 8: attestation.v1.Attestation.policy_evaluations:type_name -> attestation.v1.PolicyEvaluation - 15, // 9: attestation.v1.Attestation.signing_options:type_name -> attestation.v1.Attestation.SigningOptions - 2, // 10: attestation.v1.Attestation.runner_environment:type_name -> attestation.v1.RunnerEnvironment - 13, // 11: attestation.v1.Attestation.auth:type_name -> attestation.v1.Attestation.Auth - 14, // 12: attestation.v1.Attestation.cas_backend:type_name -> attestation.v1.Attestation.CASBackend - 30, // 13: attestation.v1.RunnerEnvironment.type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType - 22, // 14: attestation.v1.PolicyEvaluation.annotations:type_name -> attestation.v1.PolicyEvaluation.AnnotationsEntry - 24, // 15: attestation.v1.PolicyEvaluation.violations:type_name -> attestation.v1.PolicyEvaluation.Violation - 23, // 16: attestation.v1.PolicyEvaluation.with:type_name -> attestation.v1.PolicyEvaluation.WithEntry - 31, // 17: attestation.v1.PolicyEvaluation.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType - 25, // 18: attestation.v1.PolicyEvaluation.policy_reference:type_name -> attestation.v1.PolicyEvaluation.Reference - 25, // 19: attestation.v1.PolicyEvaluation.group_reference:type_name -> attestation.v1.PolicyEvaluation.Reference - 26, // 20: attestation.v1.PolicyEvaluation.raw_results:type_name -> attestation.v1.PolicyEvaluation.RawResult - 29, // 21: attestation.v1.Commit.date:type_name -> google.protobuf.Timestamp - 27, // 22: attestation.v1.Commit.remotes:type_name -> attestation.v1.Commit.Remote - 32, // 23: attestation.v1.CraftingState.input_schema:type_name -> workflowcontract.v1.CraftingSchema - 33, // 24: attestation.v1.CraftingState.schema_v2:type_name -> workflowcontract.v1.CraftingSchemaV2 - 1, // 25: attestation.v1.CraftingState.attestation:type_name -> attestation.v1.Attestation - 7, // 26: attestation.v1.WorkflowMetadata.version:type_name -> attestation.v1.ProjectVersion - 28, // 27: attestation.v1.ResourceDescriptor.digest:type_name -> attestation.v1.ResourceDescriptor.DigestEntry - 34, // 28: attestation.v1.ResourceDescriptor.annotations:type_name -> google.protobuf.Struct - 11, // 29: attestation.v1.Attestation.MaterialsEntry.value:type_name -> attestation.v1.Attestation.Material - 17, // 30: attestation.v1.Attestation.Material.string:type_name -> attestation.v1.Attestation.Material.KeyVal - 18, // 31: attestation.v1.Attestation.Material.container_image:type_name -> attestation.v1.Attestation.Material.ContainerImage - 19, // 32: attestation.v1.Attestation.Material.artifact:type_name -> attestation.v1.Attestation.Material.Artifact - 20, // 33: attestation.v1.Attestation.Material.sbom_artifact:type_name -> attestation.v1.Attestation.Material.SBOMArtifact - 29, // 34: attestation.v1.Attestation.Material.added_at:type_name -> google.protobuf.Timestamp - 31, // 35: attestation.v1.Attestation.Material.material_type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType - 16, // 36: attestation.v1.Attestation.Material.annotations:type_name -> attestation.v1.Attestation.Material.AnnotationsEntry - 0, // 37: attestation.v1.Attestation.Auth.type:type_name -> attestation.v1.Attestation.Auth.AuthType - 35, // 38: attestation.v1.Attestation.Material.ContainerImage.has_latest_tag:type_name -> google.protobuf.BoolValue - 19, // 39: attestation.v1.Attestation.Material.SBOMArtifact.artifact:type_name -> attestation.v1.Attestation.Material.Artifact - 21, // 40: attestation.v1.Attestation.Material.SBOMArtifact.main_component:type_name -> attestation.v1.Attestation.Material.SBOMArtifact.MainComponent - 41, // [41:41] is the sub-list for method output_type - 41, // [41:41] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 31, // 0: attestation.v1.Attestation.initialized_at:type_name -> google.protobuf.Timestamp + 31, // 1: attestation.v1.Attestation.finished_at:type_name -> google.protobuf.Timestamp + 7, // 2: attestation.v1.Attestation.workflow:type_name -> attestation.v1.WorkflowMetadata + 10, // 3: attestation.v1.Attestation.materials:type_name -> attestation.v1.Attestation.MaterialsEntry + 11, // 4: attestation.v1.Attestation.annotations:type_name -> attestation.v1.Attestation.AnnotationsEntry + 13, // 5: attestation.v1.Attestation.env_vars:type_name -> attestation.v1.Attestation.EnvVarsEntry + 32, // 6: attestation.v1.Attestation.runner_type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType + 5, // 7: attestation.v1.Attestation.head:type_name -> attestation.v1.Commit + 4, // 8: attestation.v1.Attestation.policy_evaluations:type_name -> attestation.v1.PolicyEvaluation + 16, // 9: attestation.v1.Attestation.signing_options:type_name -> attestation.v1.Attestation.SigningOptions + 3, // 10: attestation.v1.Attestation.runner_environment:type_name -> attestation.v1.RunnerEnvironment + 14, // 11: attestation.v1.Attestation.auth:type_name -> attestation.v1.Attestation.Auth + 15, // 12: attestation.v1.Attestation.cas_backend:type_name -> attestation.v1.Attestation.CASBackend + 32, // 13: attestation.v1.RunnerEnvironment.type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType + 23, // 14: attestation.v1.PolicyEvaluation.annotations:type_name -> attestation.v1.PolicyEvaluation.AnnotationsEntry + 25, // 15: attestation.v1.PolicyEvaluation.violations:type_name -> attestation.v1.PolicyEvaluation.Violation + 24, // 16: attestation.v1.PolicyEvaluation.with:type_name -> attestation.v1.PolicyEvaluation.WithEntry + 33, // 17: attestation.v1.PolicyEvaluation.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType + 26, // 18: attestation.v1.PolicyEvaluation.policy_reference:type_name -> attestation.v1.PolicyEvaluation.Reference + 26, // 19: attestation.v1.PolicyEvaluation.group_reference:type_name -> attestation.v1.PolicyEvaluation.Reference + 27, // 20: attestation.v1.PolicyEvaluation.raw_results:type_name -> attestation.v1.PolicyEvaluation.RawResult + 31, // 21: attestation.v1.Commit.date:type_name -> google.protobuf.Timestamp + 28, // 22: attestation.v1.Commit.remotes:type_name -> attestation.v1.Commit.Remote + 29, // 23: attestation.v1.Commit.platform_verification:type_name -> attestation.v1.Commit.CommitVerification + 34, // 24: attestation.v1.CraftingState.input_schema:type_name -> workflowcontract.v1.CraftingSchema + 35, // 25: attestation.v1.CraftingState.schema_v2:type_name -> workflowcontract.v1.CraftingSchemaV2 + 2, // 26: attestation.v1.CraftingState.attestation:type_name -> attestation.v1.Attestation + 8, // 27: attestation.v1.WorkflowMetadata.version:type_name -> attestation.v1.ProjectVersion + 30, // 28: attestation.v1.ResourceDescriptor.digest:type_name -> attestation.v1.ResourceDescriptor.DigestEntry + 36, // 29: attestation.v1.ResourceDescriptor.annotations:type_name -> google.protobuf.Struct + 12, // 30: attestation.v1.Attestation.MaterialsEntry.value:type_name -> attestation.v1.Attestation.Material + 18, // 31: attestation.v1.Attestation.Material.string:type_name -> attestation.v1.Attestation.Material.KeyVal + 19, // 32: attestation.v1.Attestation.Material.container_image:type_name -> attestation.v1.Attestation.Material.ContainerImage + 20, // 33: attestation.v1.Attestation.Material.artifact:type_name -> attestation.v1.Attestation.Material.Artifact + 21, // 34: attestation.v1.Attestation.Material.sbom_artifact:type_name -> attestation.v1.Attestation.Material.SBOMArtifact + 31, // 35: attestation.v1.Attestation.Material.added_at:type_name -> google.protobuf.Timestamp + 33, // 36: attestation.v1.Attestation.Material.material_type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType + 17, // 37: attestation.v1.Attestation.Material.annotations:type_name -> attestation.v1.Attestation.Material.AnnotationsEntry + 0, // 38: attestation.v1.Attestation.Auth.type:type_name -> attestation.v1.Attestation.Auth.AuthType + 37, // 39: attestation.v1.Attestation.Material.ContainerImage.has_latest_tag:type_name -> google.protobuf.BoolValue + 20, // 40: attestation.v1.Attestation.Material.SBOMArtifact.artifact:type_name -> attestation.v1.Attestation.Material.Artifact + 22, // 41: attestation.v1.Attestation.Material.SBOMArtifact.main_component:type_name -> attestation.v1.Attestation.Material.SBOMArtifact.MainComponent + 1, // 42: attestation.v1.Commit.CommitVerification.status:type_name -> attestation.v1.Commit.CommitVerification.VerificationStatus + 43, // [43:43] is the sub-list for method output_type + 43, // [43:43] is the sub-list for method input_type + 43, // [43:43] is the sub-list for extension type_name + 43, // [43:43] is the sub-list for extension extendee + 0, // [0:43] is the sub-list for field type_name } func init() { file_attestation_v1_crafting_state_proto_init() } @@ -2316,6 +2493,7 @@ func file_attestation_v1_crafting_state_proto_init() { if File_attestation_v1_crafting_state_proto != nil { return } + file_attestation_v1_crafting_state_proto_msgTypes[3].OneofWrappers = []any{} file_attestation_v1_crafting_state_proto_msgTypes[4].OneofWrappers = []any{ (*CraftingState_InputSchema)(nil), (*CraftingState_SchemaV2)(nil), @@ -2331,8 +2509,8 @@ func file_attestation_v1_crafting_state_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_attestation_v1_crafting_state_proto_rawDesc), len(file_attestation_v1_crafting_state_proto_rawDesc)), - NumEnums: 1, - NumMessages: 28, + NumEnums: 2, + NumMessages: 29, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto index df9077cc6..2188b6a65 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto @@ -307,11 +307,40 @@ message Commit { google.protobuf.Timestamp date = 5; repeated Remote remotes = 6; string signature = 7; + // Platform verification information (GitHub/GitLab signature verification) + optional CommitVerification platform_verification = 8; message Remote { string name = 1 [(buf.validate.field).string.min_len = 1]; string url = 2 [(buf.validate.field).string.min_len = 1]; } + + message CommitVerification { + // Whether verification was attempted + bool attempted = 1; + // Verification status + VerificationStatus status = 2; + // Human-readable reason for the status + string reason = 3; + // Platform that performed the verification (e.g., "github", "gitlab") + string platform = 4; + // Optional: The signing key ID if verified + string key_id = 5; + // Optional: The signature algorithm used + string signature_algorithm = 6; + + enum VerificationStatus { + VERIFICATION_STATUS_UNSPECIFIED = 0; + // Successfully verified by platform + VERIFICATION_STATUS_VERIFIED = 1; + // Platform checked but signature is invalid/unverified + VERIFICATION_STATUS_UNVERIFIED = 2; + // Verification could not be performed (no API access, network error, etc.) + VERIFICATION_STATUS_UNAVAILABLE = 3; + // Platform doesn't support verification or no commit signature present + VERIFICATION_STATUS_NOT_APPLICABLE = 4; + } + } } // Intermediate information that will get stored in the system while the run is being executed diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index 5f2e6896d..35f4a4268 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-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. @@ -168,6 +168,8 @@ type InitOpts struct { PoliciesAllowedHostnames []string // CAS backend information CASBackend *api.Attestation_CASBackend + // Logger for verification logging + Logger *zerolog.Logger } type SigningOpts struct { @@ -248,6 +250,8 @@ type HeadCommit struct { Message string Remotes []*CommitRemote Signature string + // Platform verification (if available) + PlatformVerification *api.Commit_CommitVerification } type CommitRemote struct { @@ -357,13 +361,22 @@ func initialCraftingState(cwd string, opts *InitOpts) (*api.CraftingState, error var headCommitP *api.Commit if headCommit != nil { + // Attempt platform verification + if opts.Runner != nil { + headCommit.PlatformVerification = verifyCommitWithPlatform( + headCommit, + opts.Runner, + ) + } + headCommitP = &api.Commit{ - Hash: headCommit.Hash, - AuthorEmail: headCommit.AuthorEmail, - AuthorName: headCommit.AuthorName, - Date: timestamppb.New(headCommit.Date), - Message: headCommit.Message, - Signature: headCommit.Signature, + Hash: headCommit.Hash, + AuthorEmail: headCommit.AuthorEmail, + AuthorName: headCommit.AuthorName, + Date: timestamppb.New(headCommit.Date), + Message: headCommit.Message, + Signature: headCommit.Signature, + PlatformVerification: headCommit.PlatformVerification, } for _, r := range headCommit.Remotes { @@ -806,3 +819,14 @@ func (c *Crafter) requireStateLoaded() error { return nil } + +// verifyCommitWithPlatform attempts to verify commit signature using platform APIs +// Returns nil if verification is not available or not applicable +func verifyCommitWithPlatform(commit *HeadCommit, runner SupportedRunner) *api.Commit_CommitVerification { + // Create context with timeout + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Call runner's verification method directly + return runner.VerifyCommitSignature(ctx, commit.Hash) +} diff --git a/pkg/attestation/crafter/runner.go b/pkg/attestation/crafter/runner.go index cab1c1d91..d0fe53cc6 100644 --- a/pkg/attestation/crafter/runner.go +++ b/pkg/attestation/crafter/runner.go @@ -22,6 +22,7 @@ import ( "time" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners" "github.com/rs/zerolog" ) @@ -29,16 +30,16 @@ import ( var ErrRunnerContextNotFound = errors.New("the runner environment doesn't match the required runner type") type SupportedRunner interface { - // Whether the attestation is happening in this environment + // CheckEnv whether the attestation is happening in this environment CheckEnv() bool - // List the env variables registered + // ListEnvVars lists the env variables registered ListEnvVars() []*runners.EnvVarDefinition - // Return the list of env vars associated with this runner already resolved + // ResolveEnvVars return the list of env vars associated with this runner already resolved ResolveEnvVars() (map[string]string, []*error) - // uri to the running job/workload + // RunURI to the running job/workload RunURI() string // ID returns the runner type @@ -50,8 +51,13 @@ type SupportedRunner interface { // IsAuthenticated returns whether the runner is authenticated or not IsAuthenticated() bool - // RunnerEnvironment returns the runner environment + // Environment returns the runner environment Environment() runners.RunnerEnvironment + + // VerifyCommitSignature checks if a commit's signature is verified by the platform. + // 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) *api.Commit_CommitVerification } type RunnerM map[schemaapi.CraftingSchema_Runner_RunnerType]SupportedRunner @@ -79,8 +85,8 @@ var RunnerFactories = map[schemaapi.CraftingSchema_Runner_RunnerType]RunnerFacto schemaapi.CraftingSchema_Runner_CIRCLECI_BUILD: func(_ string, _ *zerolog.Logger) SupportedRunner { return runners.NewCircleCIBuild() }, - schemaapi.CraftingSchema_Runner_DAGGER_PIPELINE: func(_ string, _ *zerolog.Logger) SupportedRunner { - return runners.NewDaggerPipeline() + schemaapi.CraftingSchema_Runner_DAGGER_PIPELINE: func(authToken string, logger *zerolog.Logger) SupportedRunner { + return runners.NewDaggerPipeline(authToken, logger) }, schemaapi.CraftingSchema_Runner_TEAMCITY_PIPELINE: func(_ string, _ *zerolog.Logger) SupportedRunner { return runners.NewTeamCityPipeline() diff --git a/pkg/attestation/crafter/runners/azurepipeline.go b/pkg/attestation/crafter/runners/azurepipeline.go index fc2b77ba4..97b45b5db 100644 --- a/pkg/attestation/crafter/runners/azurepipeline.go +++ b/pkg/attestation/crafter/runners/azurepipeline.go @@ -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. @@ -16,11 +16,13 @@ package runners import ( + "context" neturl "net/url" "os" "path" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" ) type AzurePipeline struct{} @@ -95,3 +97,7 @@ func (r *AzurePipeline) IsAuthenticated() bool { func (r *AzurePipeline) Environment() RunnerEnvironment { return Unknown } + +func (r *AzurePipeline) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { + return nil // Not supported for this runner +} diff --git a/pkg/attestation/crafter/runners/circleci_build.go b/pkg/attestation/crafter/runners/circleci_build.go index df5e73198..4951da835 100644 --- a/pkg/attestation/crafter/runners/circleci_build.go +++ b/pkg/attestation/crafter/runners/circleci_build.go @@ -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. @@ -16,8 +16,11 @@ package runners import ( + "context" "os" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" ) @@ -75,3 +78,7 @@ func (r *CircleCIBuild) IsAuthenticated() bool { func (r *CircleCIBuild) Environment() RunnerEnvironment { return Unknown } + +func (r *CircleCIBuild) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { + return nil // Not supported for this runner +} diff --git a/pkg/attestation/crafter/runners/commitverification/github.go b/pkg/attestation/crafter/runners/commitverification/github.go new file mode 100644 index 000000000..a43acd214 --- /dev/null +++ b/pkg/attestation/crafter/runners/commitverification/github.go @@ -0,0 +1,198 @@ +// +// Copyright 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commitverification + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/rs/zerolog" +) + +// VerifyGitHubCommit verifies a commit signature using the GitHub API +func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token string, logger *zerolog.Logger) *api.Commit_CommitVerification { + // Build API URL + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits/%s", owner, repo, commitHash) + + // Create HTTP client with timeout + client := &http.Client{Timeout: 10 * time.Second} + + // Create request + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + if logger != nil { + logger.Debug().Err(err).Msg("failed to create GitHub API request") + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: fmt.Sprintf("Failed to create request: %v", err), + Platform: "github", + } + } + + // Set headers + req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("X-GitHub-Api-Version", "2022-11-28") + if token != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + } + + // Make request + resp, err := client.Do(req) + if err != nil { + if logger != nil { + logger.Debug().Err(err).Str("commit", commitHash).Msg("failed to fetch commit from GitHub") + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: fmt.Sprintf("GitHub API error: %v", err), + Platform: "github", + } + } + defer resp.Body.Close() + + // Check HTTP status + if resp.StatusCode != http.StatusOK { + if logger != nil { + logger.Debug().Int("status", resp.StatusCode).Str("commit", commitHash).Msg("GitHub API returned non-OK status") + } + var reason string + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + reason = "GitHub API authentication failed" + } else if resp.StatusCode == http.StatusNotFound { + reason = "Commit not found" + } else { + reason = fmt.Sprintf("GitHub API error: HTTP %d", resp.StatusCode) + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: reason, + Platform: "github", + } + } + + // Parse response + var commitResponse githubCommitResponse + if err := json.NewDecoder(resp.Body).Decode(&commitResponse); err != nil { + if logger != nil { + logger.Debug().Err(err).Msg("failed to decode GitHub API response") + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: fmt.Sprintf("Failed to parse response: %v", err), + Platform: "github", + } + } + + // Check if verification info is available + if commitResponse.Commit.Verification == nil { + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_NOT_APPLICABLE, + Reason: "No signature verification data available", + Platform: "github", + } + } + + verification := commitResponse.Commit.Verification + + // Parse GitHub verification status + var status api.Commit_CommitVerification_VerificationStatus + if verification.Verified { + status = api.Commit_CommitVerification_VERIFICATION_STATUS_VERIFIED + } else { + status = api.Commit_CommitVerification_VERIFICATION_STATUS_UNVERIFIED + } + + // Detect signature type from the signature content + signatureAlgorithm := detectSignatureType(verification.Signature) + + if logger != nil { + logger.Debug().Str("status", status.String()).Str("reason", verification.Reason).Bool("verified", verification.Verified).Str("signature_type", signatureAlgorithm).Msg("GitHub commit verification completed") + } + + return &api.Commit_CommitVerification{ + Attempted: true, + Status: status, + Reason: verification.Reason, + Platform: "github", + SignatureAlgorithm: signatureAlgorithm, + } +} + +// detectSignatureType inspects the signature content to determine its type +// GitHub supports GPG, SSH, and S/MIME signatures +// Format references: +// - Git documentation: https://git-scm.com/docs/gitformat-signature +// - SSH format: https://blog.gitbutler.com/signing-commits-in-git-explained +func detectSignatureType(signature string) string { + if signature == "" { + return "" + } + + // Trim whitespace for consistent detection + sig := strings.TrimSpace(signature) + + // GPG/PGP signatures + // Format: -----BEGIN PGP SIGNATURE----- + if strings.HasPrefix(sig, "-----BEGIN PGP SIGNATURE-----") { + return "PGP" + } + + // SSH signatures + // Format: -----BEGIN SSH SIGNATURE----- + if strings.HasPrefix(sig, "-----BEGIN SSH SIGNATURE-----") { + return "SSH" + } + + // X.509/S/MIME signatures + // Format: -----BEGIN SIGNED MESSAGE----- + if strings.HasPrefix(sig, "-----BEGIN SIGNED MESSAGE-----") { + return "X509" + } + + // RFC1991 PGP format (legacy) + if strings.HasPrefix(sig, "-----BEGIN PGP MESSAGE-----") { + return "PGP" + } + + // Unknown signature format + return "UNKNOWN" +} + +// githubCommitResponse represents the GitHub API response for commit details +type githubCommitResponse struct { + Commit struct { + Verification *githubVerification `json:"verification"` + } `json:"commit"` +} + +// githubVerification represents GitHub's verification information +type githubVerification struct { + Verified bool `json:"verified"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Payload string `json:"payload"` +} diff --git a/pkg/attestation/crafter/runners/commitverification/gitlab.go b/pkg/attestation/crafter/runners/commitverification/gitlab.go new file mode 100644 index 000000000..9e0d6678f --- /dev/null +++ b/pkg/attestation/crafter/runners/commitverification/gitlab.go @@ -0,0 +1,168 @@ +// +// Copyright 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commitverification + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/rs/zerolog" +) + +// VerifyGitLabCommit verifies a commit signature using the GitLab API +func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, token string, logger *zerolog.Logger) *api.Commit_CommitVerification { + // URL encode the project path (e.g., "group/project" -> "group%2Fproject") + encodedProject := url.PathEscape(projectPath) + + // Build API URL - use the dedicated signature endpoint + apiURL := fmt.Sprintf("%s/api/v4/projects/%s/repository/commits/%s/signature", baseURL, encodedProject, commitHash) + + // Create HTTP client with timeout + client := &http.Client{Timeout: 10 * time.Second} + + // Create request + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + if err != nil { + if logger != nil { + logger.Debug().Err(err).Msg("failed to create GitLab API request") + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: fmt.Sprintf("Failed to create request: %v", err), + Platform: "gitlab", + } + } + + // Set headers + if token != "" { + req.Header.Set("JOB-TOKEN", token) + } + + // Make request + resp, err := client.Do(req) + if err != nil { + if logger != nil { + logger.Debug().Err(err).Str("commit", commitHash).Msg("failed to fetch commit from GitLab") + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: fmt.Sprintf("GitLab API error: %v", err), + Platform: "gitlab", + } + } + defer resp.Body.Close() + + // Check HTTP status + if resp.StatusCode != http.StatusOK { + if logger != nil { + logger.Debug().Int("status", resp.StatusCode).Str("commit", commitHash).Msg("GitLab API returned non-OK status") + } + + // 404 means the commit is unsigned (no signature data available) + if resp.StatusCode == http.StatusNotFound { + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_NOT_APPLICABLE, + Reason: "Commit is not signed", + Platform: "gitlab", + } + } + + var reason string + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + reason = "GitLab API authentication failed" + } else { + reason = fmt.Sprintf("GitLab API error: HTTP %d", resp.StatusCode) + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: reason, + Platform: "gitlab", + } + } + + // Parse response + var signatureResponse gitlabCommitResponse + if err := json.NewDecoder(resp.Body).Decode(&signatureResponse); err != nil { + if logger != nil { + logger.Debug().Err(err).Msg("failed to decode GitLab API response") + } + return &api.Commit_CommitVerification{ + Attempted: true, + Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Reason: fmt.Sprintf("Failed to parse response: %v", err), + Platform: "gitlab", + } + } + + // Parse GitLab verification status + var status api.Commit_CommitVerification_VerificationStatus + var reason string + var keyID string + var signatureAlgorithm string + + if signatureResponse.VerificationStatus == "verified" { + status = api.Commit_CommitVerification_VERIFICATION_STATUS_VERIFIED + reason = "Commit signed and verified" + if signatureResponse.GPGKeyID != 0 { + keyID = fmt.Sprintf("%d", signatureResponse.GPGKeyID) + } else if signatureResponse.GPGKeyPrimaryKeyID != "" { + keyID = signatureResponse.GPGKeyPrimaryKeyID + } + signatureAlgorithm = signatureResponse.SignatureType + } else { + status = api.Commit_CommitVerification_VERIFICATION_STATUS_UNVERIFIED + reason = fmt.Sprintf("Signature not verified: %s", signatureResponse.VerificationStatus) + if signatureResponse.GPGKeyID != 0 { + keyID = fmt.Sprintf("%d", signatureResponse.GPGKeyID) + } + signatureAlgorithm = signatureResponse.SignatureType + } + + if logger != nil { + logger.Debug().Str("status", status.String()).Str("reason", reason).Str("verification_status", signatureResponse.VerificationStatus).Msg("GitLab commit verification completed") + } + + return &api.Commit_CommitVerification{ + Attempted: true, + Status: status, + Reason: reason, + Platform: "gitlab", + KeyId: keyID, + SignatureAlgorithm: signatureAlgorithm, + } +} + +// gitlabCommitResponse represents the GitLab API response for commit signature +// from the /signature endpoint (not the general commits endpoint) +type gitlabCommitResponse struct { + SignatureType string `json:"signature_type"` + VerificationStatus string `json:"verification_status"` + GPGKeyID int `json:"gpg_key_id"` + GPGKeyPrimaryKeyID string `json:"gpg_key_primary_keyid"` + GPGKeyUserName string `json:"gpg_key_user_name"` + GPGKeyUserEmail string `json:"gpg_key_user_email"` + GPGKeySubkeyID string `json:"gpg_key_subkey_id"` + CommitSource string `json:"commit_source"` +} diff --git a/pkg/attestation/crafter/runners/daggerpipeline.go b/pkg/attestation/crafter/runners/daggerpipeline.go index ae77d65ab..ffd47289a 100644 --- a/pkg/attestation/crafter/runners/daggerpipeline.go +++ b/pkg/attestation/crafter/runners/daggerpipeline.go @@ -16,15 +16,25 @@ package runners import ( + "context" "os" + "strings" + + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" + "github.com/rs/zerolog" ) -type DaggerPipeline struct{} +type DaggerPipeline struct { + logger *zerolog.Logger +} -func NewDaggerPipeline() *DaggerPipeline { - return &DaggerPipeline{} +func NewDaggerPipeline(_ string, logger *zerolog.Logger) *DaggerPipeline { + return &DaggerPipeline{ + logger: logger, + } } func (r *DaggerPipeline) ID() schemaapi.CraftingSchema_Runner_RunnerType { @@ -82,3 +92,93 @@ func (r *DaggerPipeline) IsAuthenticated() bool { func (r *DaggerPipeline) Environment() RunnerEnvironment { return Unknown } + +func (r *DaggerPipeline) VerifyCommitSignature(ctx context.Context, commitHash string) *api.Commit_CommitVerification { + // Dagger can run in different CI environments. Detect which one we're in. + + // Check if running in GitHub Actions + if r.isGitHubActionsEnvironment() { + if r.logger != nil { + r.logger.Debug().Msg("Dagger running in GitHub Actions, delegating verification") + } + return r.verifyCommitViaGitHub(ctx, commitHash) + } + + // Check if running in GitLab CI + if r.isGitLabCIEnvironment() { + if r.logger != nil { + r.logger.Debug().Msg("Dagger running in GitLab CI, delegating verification") + } + return r.verifyCommitViaGitLab(ctx, commitHash) + } + + // Not running in a supported environment + if r.logger != nil { + r.logger.Debug().Msg("Dagger not running in GitHub Actions or GitLab CI, skipping verification") + } + return nil +} + +// isGitHubActionsEnvironment checks if Dagger is running in GitHub Actions +func (r *DaggerPipeline) isGitHubActionsEnvironment() bool { + // Check for GitHub Actions-specific environment variables + return os.Getenv("GITHUB_REPOSITORY") != "" && os.Getenv("GITHUB_RUN_ID") != "" +} + +// isGitLabCIEnvironment checks if Dagger is running in GitLab CI +func (r *DaggerPipeline) isGitLabCIEnvironment() bool { + // Check for GitLab CI-specific environment variables + return os.Getenv("GITLAB_CI") != "" && os.Getenv("CI_JOB_URL") != "" +} + +// verifyCommitViaGitHub performs GitHub commit verification +func (r *DaggerPipeline) verifyCommitViaGitHub(ctx context.Context, commitHash string) *api.Commit_CommitVerification { + // Extract owner/repo from GITHUB_REPOSITORY env var + repo := os.Getenv("GITHUB_REPOSITORY") + if repo == "" { + if r.logger != nil { + r.logger.Debug().Msg("GITHUB_REPOSITORY not set, cannot verify commit") + } + return nil + } + + parts := strings.Split(repo, "/") + if len(parts) != 2 { + if r.logger != nil { + r.logger.Debug().Str("repo", repo).Msg("invalid GITHUB_REPOSITORY format") + } + return nil + } + + // Get GITHUB_TOKEN for API access + token := os.Getenv("GITHUB_TOKEN") + if token == "" && r.logger != nil { + r.logger.Debug().Msg("GITHUB_TOKEN not set, API calls may be rate limited") + } + + // Call GitHub API to verify commit + return commitverification.VerifyGitHubCommit(ctx, parts[0], parts[1], commitHash, token, r.logger) +} + +// verifyCommitViaGitLab performs GitLab commit verification +func (r *DaggerPipeline) verifyCommitViaGitLab(ctx context.Context, commitHash string) *api.Commit_CommitVerification { + // Extract base URL and project path from env vars + baseURL := os.Getenv("CI_SERVER_URL") + projectPath := os.Getenv("CI_PROJECT_PATH") + + if baseURL == "" || projectPath == "" { + if r.logger != nil { + r.logger.Debug().Msg("CI_SERVER_URL or CI_PROJECT_PATH not set, cannot verify commit") + } + return nil + } + + // Get CI_JOB_TOKEN for API access + token := os.Getenv("CI_JOB_TOKEN") + if token == "" && r.logger != nil { + r.logger.Debug().Msg("CI_JOB_TOKEN not set, using unauthenticated requests") + } + + // Call GitLab API to verify commit + return commitverification.VerifyGitLabCommit(ctx, baseURL, projectPath, commitHash, token, r.logger) +} diff --git a/pkg/attestation/crafter/runners/daggerpipeline_test.go b/pkg/attestation/crafter/runners/daggerpipeline_test.go index 4c63640dc..a812440d6 100644 --- a/pkg/attestation/crafter/runners/daggerpipeline_test.go +++ b/pkg/attestation/crafter/runners/daggerpipeline_test.go @@ -19,6 +19,7 @@ import ( "os" "testing" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -98,7 +99,8 @@ func (s *daggerPipelineSuite) TestRunnerName() { // Run before each test func (s *daggerPipelineSuite) SetupTest() { - s.runner = NewDaggerPipeline() + logger := zerolog.Nop() + s.runner = NewDaggerPipeline("", &logger) t := s.T() t.Setenv("CHAINLOOP_DAGGER_CLIENT", "v0.6.0") } diff --git a/pkg/attestation/crafter/runners/generic.go b/pkg/attestation/crafter/runners/generic.go index f4d34ecc0..984c73fe2 100644 --- a/pkg/attestation/crafter/runners/generic.go +++ b/pkg/attestation/crafter/runners/generic.go @@ -16,7 +16,10 @@ package runners import ( + "context" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" ) type Generic struct{} @@ -58,3 +61,7 @@ func (r *Generic) IsAuthenticated() bool { func (r *Generic) Environment() RunnerEnvironment { return Unknown } + +func (r *Generic) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { + return nil // Not supported for this runner +} diff --git a/pkg/attestation/crafter/runners/githubaction.go b/pkg/attestation/crafter/runners/githubaction.go index 4bd998253..3600d97c5 100644 --- a/pkg/attestation/crafter/runners/githubaction.go +++ b/pkg/attestation/crafter/runners/githubaction.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-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. @@ -19,14 +19,18 @@ import ( "context" "fmt" "os" + "strings" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/oidc" "github.com/rs/zerolog" ) type GitHubAction struct { githubToken *oidc.Token + logger *zerolog.Logger } func NewGithubAction(ctx context.Context, logger *zerolog.Logger) *GitHubAction { @@ -40,6 +44,7 @@ func NewGithubAction(ctx context.Context, logger *zerolog.Logger) *GitHubAction logger.Debug().Err(err).Msg("failed creating GitHub OIDC client") return &GitHubAction{ githubToken: nil, + logger: logger, } } @@ -48,6 +53,7 @@ func NewGithubAction(ctx context.Context, logger *zerolog.Logger) *GitHubAction logger.Debug().Err(err).Msg("failed to get github token") return &GitHubAction{ githubToken: nil, + logger: logger, } } @@ -56,11 +62,13 @@ func NewGithubAction(ctx context.Context, logger *zerolog.Logger) *GitHubAction logger.Debug().Err(err).Msg("failed casting to OIDC token") return &GitHubAction{ githubToken: nil, + logger: logger, } } return &GitHubAction{ githubToken: ghToken, + logger: logger, } } @@ -134,3 +142,28 @@ func (r *GitHubAction) WorkflowFilePath() string { func (r *GitHubAction) IsAuthenticated() bool { return r.githubToken != nil } + +// VerifyCommitSignature checks if a commit's signature is verified by GitHub +func (r *GitHubAction) VerifyCommitSignature(ctx context.Context, commitHash string) *api.Commit_CommitVerification { + // Extract owner/repo from GITHUB_REPOSITORY env var + repo := os.Getenv("GITHUB_REPOSITORY") // e.g., "owner/repo" + if repo == "" { + r.logger.Debug().Msg("GITHUB_REPOSITORY not set, cannot verify commit") + return nil + } + + parts := strings.Split(repo, "/") + if len(parts) != 2 { + r.logger.Debug().Str("repo", repo).Msg("invalid GITHUB_REPOSITORY format") + return nil + } + + // Get GITHUB_TOKEN for API access + token := os.Getenv("GITHUB_TOKEN") + if token == "" { + r.logger.Debug().Msg("GITHUB_TOKEN not set, API calls may be rate limited") + } + + // Call GitHub API to verify commit + return commitverification.VerifyGitHubCommit(ctx, parts[0], parts[1], commitHash, token, r.logger) +} diff --git a/pkg/attestation/crafter/runners/gitlabpipeline.go b/pkg/attestation/crafter/runners/gitlabpipeline.go index fcc7205b7..318cd61e9 100644 --- a/pkg/attestation/crafter/runners/gitlabpipeline.go +++ b/pkg/attestation/crafter/runners/gitlabpipeline.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-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. @@ -20,12 +20,15 @@ import ( "os" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/oidc" "github.com/rs/zerolog" ) type GitlabPipeline struct { gitlabToken *oidc.GitlabToken + logger *zerolog.Logger } // authtoken is a possible oidc token that could be used to authenticate the runner @@ -35,11 +38,13 @@ func NewGitlabPipeline(ctx context.Context, authToken string, logger *zerolog.Lo logger.Debug().Err(err).Msgf("failed to create Gitlab OIDC client: %v", err) return &GitlabPipeline{ gitlabToken: nil, + logger: logger, } } return &GitlabPipeline{ gitlabToken: client.Token, + logger: logger, } } @@ -113,3 +118,24 @@ func (r *GitlabPipeline) Environment() RunnerEnvironment { } return Unknown } + +// VerifyCommitSignature checks if a commit's signature is verified by GitLab +func (r *GitlabPipeline) VerifyCommitSignature(ctx context.Context, commitHash string) *api.Commit_CommitVerification { + // Extract base URL and project path from env vars + baseURL := os.Getenv("CI_SERVER_URL") + projectPath := os.Getenv("CI_PROJECT_PATH") + + if baseURL == "" || projectPath == "" { + r.logger.Debug().Msg("CI_SERVER_URL or CI_PROJECT_PATH not set, cannot verify commit") + return nil + } + + // Get CI_JOB_TOKEN for API access + token := os.Getenv("CI_JOB_TOKEN") + if token == "" { + r.logger.Debug().Msg("CI_JOB_TOKEN not set, using unauthenticated requests") + } + + // Call GitLab API to verify commit + return commitverification.VerifyGitLabCommit(ctx, baseURL, projectPath, commitHash, token, r.logger) +} diff --git a/pkg/attestation/crafter/runners/jenkinsjob.go b/pkg/attestation/crafter/runners/jenkinsjob.go index 8020f2ecf..8043eb013 100644 --- a/pkg/attestation/crafter/runners/jenkinsjob.go +++ b/pkg/attestation/crafter/runners/jenkinsjob.go @@ -16,8 +16,11 @@ package runners import ( + "context" "os" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" ) @@ -82,3 +85,7 @@ func (r *JenkinsJob) IsAuthenticated() bool { func (r *JenkinsJob) Environment() RunnerEnvironment { return Unknown } + +func (r *JenkinsJob) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { + return nil // Not supported for this runner +} diff --git a/pkg/attestation/crafter/runners/teamcitypipeline.go b/pkg/attestation/crafter/runners/teamcitypipeline.go index 1a83ea78b..2af9fbf12 100644 --- a/pkg/attestation/crafter/runners/teamcitypipeline.go +++ b/pkg/attestation/crafter/runners/teamcitypipeline.go @@ -16,8 +16,11 @@ package runners import ( + "context" "os" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" ) @@ -68,3 +71,7 @@ func (r *TeamCityPipeline) IsAuthenticated() bool { func (r *TeamCityPipeline) Environment() RunnerEnvironment { return Unknown } + +func (r *TeamCityPipeline) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { + return nil // Not supported for this runner +} diff --git a/pkg/attestation/crafter/runners/tektonpipeline.go b/pkg/attestation/crafter/runners/tektonpipeline.go index 7b869cf06..2dde528e4 100644 --- a/pkg/attestation/crafter/runners/tektonpipeline.go +++ b/pkg/attestation/crafter/runners/tektonpipeline.go @@ -16,8 +16,11 @@ package runners import ( + "context" "os" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" ) @@ -64,3 +67,7 @@ func (r *TektonPipeline) IsAuthenticated() bool { func (r *TektonPipeline) Environment() RunnerEnvironment { return Unknown } + +func (r *TektonPipeline) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { + return nil // Not supported for this runner +} From ab58ce096c8c4562ae44743b474fce1108627d84 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 9 Jan 2026 12:58:23 +0100 Subject: [PATCH 2/8] fix linter Signed-off-by: Javier Rodriguez --- .../crafter/runners/commitverification/github.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/attestation/crafter/runners/commitverification/github.go b/pkg/attestation/crafter/runners/commitverification/github.go index a43acd214..94613696e 100644 --- a/pkg/attestation/crafter/runners/commitverification/github.go +++ b/pkg/attestation/crafter/runners/commitverification/github.go @@ -77,11 +77,12 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri logger.Debug().Int("status", resp.StatusCode).Str("commit", commitHash).Msg("GitHub API returned non-OK status") } var reason string - if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + switch resp.StatusCode { + case http.StatusUnauthorized, http.StatusForbidden: reason = "GitHub API authentication failed" - } else if resp.StatusCode == http.StatusNotFound { + case http.StatusNotFound: reason = "Commit not found" - } else { + default: reason = fmt.Sprintf("GitHub API error: HTTP %d", resp.StatusCode) } return &api.Commit_CommitVerification{ From ff30ce82fca2ab4723aba297b2601b8c75a73db0 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 9 Jan 2026 13:36:45 +0100 Subject: [PATCH 3/8] include author verification on attestation Signed-off-by: Javier Rodriguez --- pkg/attestation/renderer/chainloop/chainloop.go | 15 ++++++++------- pkg/attestation/renderer/chainloop/v02.go | 6 ++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index 06b0d1e67..f85a8b836 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -334,11 +334,12 @@ func (p *ProvenancePredicateCommon) GetMetadata() *Metadata { const ( // Subject names - SubjectGitHead = "git.head" - subjectGitAnnotationAuthorEmail = "author.email" - subjectGitAnnotationAuthorName = "author.name" - subjectGitAnnotationWhen = "date" - subjectGitAnnotationMessage = "message" - subjectGitAnnotationRemotes = "remotes" - subjectGitAnnotationSignature = "signature" + SubjectGitHead = "git.head" + subjectGitAnnotationAuthorEmail = "author.email" + subjectGitAnnotationAuthorName = "author.name" + subjectGitAnnotationAuthorVerified = "author.verified" + subjectGitAnnotationWhen = "date" + subjectGitAnnotationMessage = "message" + subjectGitAnnotationRemotes = "remotes" + subjectGitAnnotationSignature = "signature" ) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index 9c88949a0..28b75cdc2 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -144,6 +144,12 @@ func commitAnnotations(c *v1.Commit) (*structpb.Struct, error) { annotationsRaw[subjectGitAnnotationSignature] = c.GetSignature() } + // add author verification only if exists + if c.GetPlatformVerification() != nil { + pv := c.GetPlatformVerification() + annotationsRaw[subjectGitAnnotationAuthorVerified] = pv.GetStatus() + } + if remotes := c.GetRemotes(); len(remotes) > 0 { remotesRaw := []interface{}{} for _, r := range remotes { From 1f603275a0ba33047a12c689bfb7bb6dbafcdf56 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Fri, 9 Jan 2026 14:00:35 +0100 Subject: [PATCH 4/8] add signature algorithm annotation Signed-off-by: Javier Rodriguez --- pkg/attestation/renderer/chainloop/chainloop.go | 17 +++++++++-------- pkg/attestation/renderer/chainloop/v02.go | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index f85a8b836..c0a0a2560 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -334,12 +334,13 @@ func (p *ProvenancePredicateCommon) GetMetadata() *Metadata { const ( // Subject names - SubjectGitHead = "git.head" - subjectGitAnnotationAuthorEmail = "author.email" - subjectGitAnnotationAuthorName = "author.name" - subjectGitAnnotationAuthorVerified = "author.verified" - subjectGitAnnotationWhen = "date" - subjectGitAnnotationMessage = "message" - subjectGitAnnotationRemotes = "remotes" - subjectGitAnnotationSignature = "signature" + SubjectGitHead = "git.head" + subjectGitAnnotationAuthorEmail = "author.email" + subjectGitAnnotationAuthorName = "author.name" + subjectGitAnnotationAuthorVerified = "author.verified" + subjectGitAnnotationWhen = "date" + subjectGitAnnotationMessage = "message" + subjectGitAnnotationRemotes = "remotes" + subjectGitAnnotationSignature = "signature" + subjectGitAnnotationSignatureAlgorithm = "signature_algorithm" ) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index 28b75cdc2..fcd0ddccf 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -148,6 +148,7 @@ func commitAnnotations(c *v1.Commit) (*structpb.Struct, error) { if c.GetPlatformVerification() != nil { pv := c.GetPlatformVerification() annotationsRaw[subjectGitAnnotationAuthorVerified] = pv.GetStatus() + annotationsRaw[subjectGitAnnotationSignatureAlgorithm] = pv.GetSignatureAlgorithm() } if remotes := c.GetRemotes(); len(remotes) > 0 { From 6df5ad27e7630e29ca487de009c43a59813512a2 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Mon, 12 Jan 2026 09:26:25 +0100 Subject: [PATCH 5/8] fix annotation rendering Signed-off-by: Javier Rodriguez --- pkg/attestation/renderer/chainloop/v02.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index fcd0ddccf..ff4368d91 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -147,7 +147,7 @@ func commitAnnotations(c *v1.Commit) (*structpb.Struct, error) { // add author verification only if exists if c.GetPlatformVerification() != nil { pv := c.GetPlatformVerification() - annotationsRaw[subjectGitAnnotationAuthorVerified] = pv.GetStatus() + annotationsRaw[subjectGitAnnotationAuthorVerified] = pv.GetStatus().String() annotationsRaw[subjectGitAnnotationSignatureAlgorithm] = pv.GetSignatureAlgorithm() } From 8ea4d66d17224ce06106f55bcc2b12a412f9d206 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Mon, 12 Jan 2026 09:35:55 +0100 Subject: [PATCH 6/8] fix typo on annotation Signed-off-by: Javier Rodriguez --- pkg/attestation/crafter/crafter.go | 5 +---- pkg/attestation/renderer/chainloop/chainloop.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index 35f4a4268..fd866e13c 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -363,10 +363,7 @@ func initialCraftingState(cwd string, opts *InitOpts) (*api.CraftingState, error if headCommit != nil { // Attempt platform verification if opts.Runner != nil { - headCommit.PlatformVerification = verifyCommitWithPlatform( - headCommit, - opts.Runner, - ) + headCommit.PlatformVerification = verifyCommitWithPlatform(headCommit, opts.Runner) } headCommitP = &api.Commit{ diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index c0a0a2560..45c944c09 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -342,5 +342,5 @@ const ( subjectGitAnnotationMessage = "message" subjectGitAnnotationRemotes = "remotes" subjectGitAnnotationSignature = "signature" - subjectGitAnnotationSignatureAlgorithm = "signature_algorithm" + subjectGitAnnotationSignatureAlgorithm = "signature.algorithm" ) From 6b3b6e3972351400e119c84bb2056a40b87f0930 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Mon, 12 Jan 2026 13:43:55 +0100 Subject: [PATCH 7/8] tackle feedback Signed-off-by: Javier Rodriguez --- .../frontend/attestation/v1/crafting_state.ts | 63 ++++++++++--------- ....Commit.CommitVerification.jsonschema.json | 10 +-- ...n.v1.Commit.CommitVerification.schema.json | 10 +-- .../api/attestation/v1/crafting_state.pb.go | 54 ++++++++-------- .../api/attestation/v1/crafting_state.proto | 13 ++-- pkg/attestation/crafter/crafter.go | 40 +++++++++++- pkg/attestation/crafter/runner.go | 4 +- .../crafter/runners/azurepipeline.go | 4 +- .../crafter/runners/circleci_build.go | 5 +- .../commitverification/commitverification.go | 48 ++++++++++++++ .../runners/commitverification/github.go | 33 +++++----- .../runners/commitverification/gitlab.go | 35 +++++------ .../crafter/runners/daggerpipeline.go | 7 +-- pkg/attestation/crafter/runners/generic.go | 4 +- .../crafter/runners/githubaction.go | 3 +- .../crafter/runners/gitlabpipeline.go | 3 +- pkg/attestation/crafter/runners/jenkinsjob.go | 5 +- .../crafter/runners/teamcitypipeline.go | 5 +- .../crafter/runners/tektonpipeline.go | 5 +- .../renderer/chainloop/chainloop.go | 18 +++--- pkg/attestation/renderer/chainloop/v02.go | 2 +- 21 files changed, 230 insertions(+), 141 deletions(-) create mode 100644 pkg/attestation/crafter/runners/commitverification/commitverification.go diff --git a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts index 08653b41d..7a58c760f 100644 --- a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts +++ b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts @@ -360,16 +360,21 @@ export interface Commit_CommitVerification { signatureAlgorithm: string; } +/** + * buf:lint:ignore ENUM_VALUE_UPPER_SNAKE_CASE + * buf:lint:ignore ENUM_VALUE_PREFIX + * buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX + */ export enum Commit_CommitVerification_VerificationStatus { - VERIFICATION_STATUS_UNSPECIFIED = 0, - /** VERIFICATION_STATUS_VERIFIED - Successfully verified by platform */ - VERIFICATION_STATUS_VERIFIED = 1, - /** VERIFICATION_STATUS_UNVERIFIED - Platform checked but signature is invalid/unverified */ - VERIFICATION_STATUS_UNVERIFIED = 2, - /** VERIFICATION_STATUS_UNAVAILABLE - Verification could not be performed (no API access, network error, etc.) */ - VERIFICATION_STATUS_UNAVAILABLE = 3, - /** VERIFICATION_STATUS_NOT_APPLICABLE - Platform doesn't support verification or no commit signature present */ - VERIFICATION_STATUS_NOT_APPLICABLE = 4, + unspecified = 0, + /** verified - Successfully verified by platform */ + verified = 1, + /** unverified - Platform checked but signature is invalid/unverified */ + unverified = 2, + /** unavailable - Verification could not be performed (no API access, network error, etc.) */ + unavailable = 3, + /** not_applicable - Platform doesn't support verification or no commit signature present */ + not_applicable = 4, UNRECOGNIZED = -1, } @@ -378,20 +383,20 @@ export function commit_CommitVerification_VerificationStatusFromJSON( ): Commit_CommitVerification_VerificationStatus { switch (object) { case 0: - case "VERIFICATION_STATUS_UNSPECIFIED": - return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED; + case "unspecified": + return Commit_CommitVerification_VerificationStatus.unspecified; case 1: - case "VERIFICATION_STATUS_VERIFIED": - return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_VERIFIED; + case "verified": + return Commit_CommitVerification_VerificationStatus.verified; case 2: - case "VERIFICATION_STATUS_UNVERIFIED": - return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNVERIFIED; + case "unverified": + return Commit_CommitVerification_VerificationStatus.unverified; case 3: - case "VERIFICATION_STATUS_UNAVAILABLE": - return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNAVAILABLE; + case "unavailable": + return Commit_CommitVerification_VerificationStatus.unavailable; case 4: - case "VERIFICATION_STATUS_NOT_APPLICABLE": - return Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_NOT_APPLICABLE; + case "not_applicable": + return Commit_CommitVerification_VerificationStatus.not_applicable; case -1: case "UNRECOGNIZED": default: @@ -403,16 +408,16 @@ export function commit_CommitVerification_VerificationStatusToJSON( object: Commit_CommitVerification_VerificationStatus, ): string { switch (object) { - case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED: - return "VERIFICATION_STATUS_UNSPECIFIED"; - case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_VERIFIED: - return "VERIFICATION_STATUS_VERIFIED"; - case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNVERIFIED: - return "VERIFICATION_STATUS_UNVERIFIED"; - case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_UNAVAILABLE: - return "VERIFICATION_STATUS_UNAVAILABLE"; - case Commit_CommitVerification_VerificationStatus.VERIFICATION_STATUS_NOT_APPLICABLE: - return "VERIFICATION_STATUS_NOT_APPLICABLE"; + case Commit_CommitVerification_VerificationStatus.unspecified: + return "unspecified"; + case Commit_CommitVerification_VerificationStatus.verified: + return "verified"; + case Commit_CommitVerification_VerificationStatus.unverified: + return "unverified"; + case Commit_CommitVerification_VerificationStatus.unavailable: + return "unavailable"; + case Commit_CommitVerification_VerificationStatus.not_applicable: + return "not_applicable"; case Commit_CommitVerification_VerificationStatus.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json index 929d7c47c..bd26a234a 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.jsonschema.json @@ -37,11 +37,11 @@ "anyOf": [ { "enum": [ - "VERIFICATION_STATUS_UNSPECIFIED", - "VERIFICATION_STATUS_VERIFIED", - "VERIFICATION_STATUS_UNVERIFIED", - "VERIFICATION_STATUS_UNAVAILABLE", - "VERIFICATION_STATUS_NOT_APPLICABLE" + "unspecified", + "verified", + "unverified", + "unavailable", + "not_applicable" ], "title": "Verification Status", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json index 301688ced..e5a968c20 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Commit.CommitVerification.schema.json @@ -37,11 +37,11 @@ "anyOf": [ { "enum": [ - "VERIFICATION_STATUS_UNSPECIFIED", - "VERIFICATION_STATUS_VERIFIED", - "VERIFICATION_STATUS_UNVERIFIED", - "VERIFICATION_STATUS_UNAVAILABLE", - "VERIFICATION_STATUS_NOT_APPLICABLE" + "unspecified", + "verified", + "unverified", + "unavailable", + "not_applicable" ], "title": "Verification Status", "type": "string" diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go index 18cc0bd11..c2a4fd6da 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go @@ -93,35 +93,38 @@ func (Attestation_Auth_AuthType) EnumDescriptor() ([]byte, []int) { return file_attestation_v1_crafting_state_proto_rawDescGZIP(), []int{0, 4, 0} } +// buf:lint:ignore ENUM_VALUE_UPPER_SNAKE_CASE +// buf:lint:ignore ENUM_VALUE_PREFIX +// buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX type Commit_CommitVerification_VerificationStatus int32 const ( - Commit_CommitVerification_VERIFICATION_STATUS_UNSPECIFIED Commit_CommitVerification_VerificationStatus = 0 + Commit_CommitVerification_unspecified Commit_CommitVerification_VerificationStatus = 0 // Successfully verified by platform - Commit_CommitVerification_VERIFICATION_STATUS_VERIFIED Commit_CommitVerification_VerificationStatus = 1 + Commit_CommitVerification_verified Commit_CommitVerification_VerificationStatus = 1 // Platform checked but signature is invalid/unverified - Commit_CommitVerification_VERIFICATION_STATUS_UNVERIFIED Commit_CommitVerification_VerificationStatus = 2 + Commit_CommitVerification_unverified Commit_CommitVerification_VerificationStatus = 2 // Verification could not be performed (no API access, network error, etc.) - Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE Commit_CommitVerification_VerificationStatus = 3 + Commit_CommitVerification_unavailable Commit_CommitVerification_VerificationStatus = 3 // Platform doesn't support verification or no commit signature present - Commit_CommitVerification_VERIFICATION_STATUS_NOT_APPLICABLE Commit_CommitVerification_VerificationStatus = 4 + Commit_CommitVerification_not_applicable Commit_CommitVerification_VerificationStatus = 4 ) // Enum value maps for Commit_CommitVerification_VerificationStatus. var ( Commit_CommitVerification_VerificationStatus_name = map[int32]string{ - 0: "VERIFICATION_STATUS_UNSPECIFIED", - 1: "VERIFICATION_STATUS_VERIFIED", - 2: "VERIFICATION_STATUS_UNVERIFIED", - 3: "VERIFICATION_STATUS_UNAVAILABLE", - 4: "VERIFICATION_STATUS_NOT_APPLICABLE", + 0: "unspecified", + 1: "verified", + 2: "unverified", + 3: "unavailable", + 4: "not_applicable", } Commit_CommitVerification_VerificationStatus_value = map[string]int32{ - "VERIFICATION_STATUS_UNSPECIFIED": 0, - "VERIFICATION_STATUS_VERIFIED": 1, - "VERIFICATION_STATUS_UNVERIFIED": 2, - "VERIFICATION_STATUS_UNAVAILABLE": 3, - "VERIFICATION_STATUS_NOT_APPLICABLE": 4, + "unspecified": 0, + "verified": 1, + "unverified": 2, + "unavailable": 3, + "not_applicable": 4, } ) @@ -2137,7 +2140,7 @@ func (x *Commit_CommitVerification) GetStatus() Commit_CommitVerification_Verifi if x != nil { return x.Status } - return Commit_CommitVerification_VERIFICATION_STATUS_UNSPECIFIED + return Commit_CommitVerification_unspecified } func (x *Commit_CommitVerification) GetReason() string { @@ -2317,7 +2320,7 @@ const file_attestation_v1_crafting_state_proto_rawDesc = "" + "\borg_name\x18\x04 \x01(\tR\aorgName\x1a9\n" + "\tRawResult\x12\x14\n" + "\x05input\x18\x01 \x01(\fR\x05input\x12\x16\n" + - "\x06output\x18\x02 \x01(\fR\x06output\"\xb3\a\n" + + "\x06output\x18\x02 \x01(\fR\x06output\"\xce\x06\n" + "\x06Commit\x12\x1b\n" + "\x04hash\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04hash\x12!\n" + "\fauthor_email\x18\x02 \x01(\tR\vauthorEmail\x12(\n" + @@ -2330,20 +2333,21 @@ const file_attestation_v1_crafting_state_proto_rawDesc = "" + "\x15platform_verification\x18\b \x01(\v2).attestation.v1.Commit.CommitVerificationH\x00R\x14platformVerification\x88\x01\x01\x1a@\n" + "\x06Remote\x12\x1b\n" + "\x04name\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04name\x12\x19\n" + - "\x03url\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x03url\x1a\xd3\x03\n" + + "\x03url\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x03url\x1a\xee\x02\n" + "\x12CommitVerification\x12\x1c\n" + "\tattempted\x18\x01 \x01(\bR\tattempted\x12T\n" + "\x06status\x18\x02 \x01(\x0e2<.attestation.v1.Commit.CommitVerification.VerificationStatusR\x06status\x12\x16\n" + "\x06reason\x18\x03 \x01(\tR\x06reason\x12\x1a\n" + "\bplatform\x18\x04 \x01(\tR\bplatform\x12\x15\n" + "\x06key_id\x18\x05 \x01(\tR\x05keyId\x12/\n" + - "\x13signature_algorithm\x18\x06 \x01(\tR\x12signatureAlgorithm\"\xcc\x01\n" + - "\x12VerificationStatus\x12#\n" + - "\x1fVERIFICATION_STATUS_UNSPECIFIED\x10\x00\x12 \n" + - "\x1cVERIFICATION_STATUS_VERIFIED\x10\x01\x12\"\n" + - "\x1eVERIFICATION_STATUS_UNVERIFIED\x10\x02\x12#\n" + - "\x1fVERIFICATION_STATUS_UNAVAILABLE\x10\x03\x12&\n" + - "\"VERIFICATION_STATUS_NOT_APPLICABLE\x10\x04B\x18\n" + + "\x13signature_algorithm\x18\x06 \x01(\tR\x12signatureAlgorithm\"h\n" + + "\x12VerificationStatus\x12\x0f\n" + + "\vunspecified\x10\x00\x12\f\n" + + "\bverified\x10\x01\x12\x0e\n" + + "\n" + + "unverified\x10\x02\x12\x0f\n" + + "\vunavailable\x10\x03\x12\x12\n" + + "\x0enot_applicable\x10\x04B\x18\n" + "\x16_platform_verification\"\x81\x02\n" + "\rCraftingState\x12H\n" + "\finput_schema\x18\x01 \x01(\v2#.workflowcontract.v1.CraftingSchemaH\x00R\vinputSchema\x12D\n" + diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto index 2188b6a65..ea24063ad 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto @@ -329,16 +329,19 @@ message Commit { // Optional: The signature algorithm used string signature_algorithm = 6; + // buf:lint:ignore ENUM_VALUE_UPPER_SNAKE_CASE + // buf:lint:ignore ENUM_VALUE_PREFIX + // buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX enum VerificationStatus { - VERIFICATION_STATUS_UNSPECIFIED = 0; + unspecified = 0; // Successfully verified by platform - VERIFICATION_STATUS_VERIFIED = 1; + verified = 1; // Platform checked but signature is invalid/unverified - VERIFICATION_STATUS_UNVERIFIED = 2; + unverified = 2; // Verification could not be performed (no API access, network error, etc.) - VERIFICATION_STATUS_UNAVAILABLE = 3; + unavailable = 3; // Platform doesn't support verification or no commit signature present - VERIFICATION_STATUS_NOT_APPLICABLE = 4; + not_applicable = 4; } } } diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index fd866e13c..d2c3277c7 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -34,6 +34,7 @@ import ( "github.com/chainloop-dev/chainloop/internal/prinfo" api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" "github.com/chainloop-dev/chainloop/pkg/casclient" "github.com/chainloop-dev/chainloop/pkg/policies" "github.com/go-git/go-git/v5" @@ -825,5 +826,42 @@ func verifyCommitWithPlatform(commit *HeadCommit, runner SupportedRunner) *api.C defer cancel() // Call runner's verification method directly - return runner.VerifyCommitSignature(ctx, commit.Hash) + verification := runner.VerifyCommitSignature(ctx, commit.Hash) + if verification == nil { + return nil + } + + // Convert from commitverification type to protobuf type + return convertCommitVerification(verification) +} + +// convertCommitVerification converts from commitverification.CommitVerification to protobuf type +func convertCommitVerification(v *commitverification.CommitVerification) *api.Commit_CommitVerification { + if v == nil { + return nil + } + + // Convert status enum + var status api.Commit_CommitVerification_VerificationStatus + switch v.Status { + case commitverification.VerificationStatusVerified: + status = api.Commit_CommitVerification_verified + case commitverification.VerificationStatusUnverified: + status = api.Commit_CommitVerification_unverified + case commitverification.VerificationStatusUnavailable: + status = api.Commit_CommitVerification_unavailable + case commitverification.VerificationStatusNotApplicable: + status = api.Commit_CommitVerification_not_applicable + default: + status = api.Commit_CommitVerification_unspecified + } + + return &api.Commit_CommitVerification{ + Attempted: v.Attempted, + Status: status, + Reason: v.Reason, + Platform: v.Platform, + KeyId: v.KeyID, + SignatureAlgorithm: v.SignatureAlgorithm, + } } diff --git a/pkg/attestation/crafter/runner.go b/pkg/attestation/crafter/runner.go index d0fe53cc6..b47fa11db 100644 --- a/pkg/attestation/crafter/runner.go +++ b/pkg/attestation/crafter/runner.go @@ -22,8 +22,8 @@ import ( "time" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" "github.com/rs/zerolog" ) @@ -57,7 +57,7 @@ type SupportedRunner interface { // VerifyCommitSignature checks if a commit's signature is verified by the platform. // 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) *api.Commit_CommitVerification + VerifyCommitSignature(ctx context.Context, commitHash string) *commitverification.CommitVerification } type RunnerM map[schemaapi.CraftingSchema_Runner_RunnerType]SupportedRunner diff --git a/pkg/attestation/crafter/runners/azurepipeline.go b/pkg/attestation/crafter/runners/azurepipeline.go index 97b45b5db..cf07010d5 100644 --- a/pkg/attestation/crafter/runners/azurepipeline.go +++ b/pkg/attestation/crafter/runners/azurepipeline.go @@ -22,7 +22,7 @@ import ( "path" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" ) type AzurePipeline struct{} @@ -98,6 +98,6 @@ func (r *AzurePipeline) Environment() RunnerEnvironment { return Unknown } -func (r *AzurePipeline) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { +func (r *AzurePipeline) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification { return nil // Not supported for this runner } diff --git a/pkg/attestation/crafter/runners/circleci_build.go b/pkg/attestation/crafter/runners/circleci_build.go index 4951da835..4a6ea5518 100644 --- a/pkg/attestation/crafter/runners/circleci_build.go +++ b/pkg/attestation/crafter/runners/circleci_build.go @@ -19,9 +19,8 @@ import ( "context" "os" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" - schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" ) type CircleCIBuild struct{} @@ -79,6 +78,6 @@ func (r *CircleCIBuild) Environment() RunnerEnvironment { return Unknown } -func (r *CircleCIBuild) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { +func (r *CircleCIBuild) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification { return nil // Not supported for this runner } diff --git a/pkg/attestation/crafter/runners/commitverification/commitverification.go b/pkg/attestation/crafter/runners/commitverification/commitverification.go new file mode 100644 index 000000000..773cf64f7 --- /dev/null +++ b/pkg/attestation/crafter/runners/commitverification/commitverification.go @@ -0,0 +1,48 @@ +// +// Copyright 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commitverification + +// VerificationStatus represents the status of a commit signature verification +type VerificationStatus int + +const ( + // VerificationStatusUnspecified indicates an unspecified status + VerificationStatusUnspecified VerificationStatus = iota + // VerificationStatusVerified indicates the signature was successfully verified + VerificationStatusVerified + // VerificationStatusUnverified indicates the signature check failed or is invalid + VerificationStatusUnverified + // VerificationStatusUnavailable indicates verification could not be performed + VerificationStatusUnavailable + // VerificationStatusNotApplicable indicates no signature present or platform doesn't support it + VerificationStatusNotApplicable +) + +// CommitVerification represents the result of a commit signature verification +type CommitVerification struct { + // Whether verification was attempted + Attempted bool + // Verification status + Status VerificationStatus + // Human-readable reason for the status + Reason string + // Platform that performed the verification (e.g., "github", "gitlab") + Platform string + // Optional: The signing key ID if verified + KeyID string + // Optional: The signature algorithm used + SignatureAlgorithm string +} diff --git a/pkg/attestation/crafter/runners/commitverification/github.go b/pkg/attestation/crafter/runners/commitverification/github.go index 94613696e..f3068a7f2 100644 --- a/pkg/attestation/crafter/runners/commitverification/github.go +++ b/pkg/attestation/crafter/runners/commitverification/github.go @@ -23,12 +23,11 @@ import ( "strings" "time" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/rs/zerolog" ) // VerifyGitHubCommit verifies a commit signature using the GitHub API -func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token string, logger *zerolog.Logger) *api.Commit_CommitVerification { +func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token string, logger *zerolog.Logger) *CommitVerification { // Build API URL url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits/%s", owner, repo, commitHash) @@ -41,9 +40,9 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri if logger != nil { logger.Debug().Err(err).Msg("failed to create GitHub API request") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: fmt.Sprintf("Failed to create request: %v", err), Platform: "github", } @@ -62,9 +61,9 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri if logger != nil { logger.Debug().Err(err).Str("commit", commitHash).Msg("failed to fetch commit from GitHub") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: fmt.Sprintf("GitHub API error: %v", err), Platform: "github", } @@ -85,9 +84,9 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri default: reason = fmt.Sprintf("GitHub API error: HTTP %d", resp.StatusCode) } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: reason, Platform: "github", } @@ -99,9 +98,9 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri if logger != nil { logger.Debug().Err(err).Msg("failed to decode GitHub API response") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: fmt.Sprintf("Failed to parse response: %v", err), Platform: "github", } @@ -109,9 +108,9 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri // Check if verification info is available if commitResponse.Commit.Verification == nil { - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_NOT_APPLICABLE, + Status: VerificationStatusNotApplicable, Reason: "No signature verification data available", Platform: "github", } @@ -120,21 +119,21 @@ func VerifyGitHubCommit(ctx context.Context, owner, repo, commitHash, token stri verification := commitResponse.Commit.Verification // Parse GitHub verification status - var status api.Commit_CommitVerification_VerificationStatus + var status VerificationStatus if verification.Verified { - status = api.Commit_CommitVerification_VERIFICATION_STATUS_VERIFIED + status = VerificationStatusVerified } else { - status = api.Commit_CommitVerification_VERIFICATION_STATUS_UNVERIFIED + status = VerificationStatusUnverified } // Detect signature type from the signature content signatureAlgorithm := detectSignatureType(verification.Signature) if logger != nil { - logger.Debug().Str("status", status.String()).Str("reason", verification.Reason).Bool("verified", verification.Verified).Str("signature_type", signatureAlgorithm).Msg("GitHub commit verification completed") + logger.Debug().Int("status", int(status)).Str("reason", verification.Reason).Bool("verified", verification.Verified).Str("signature_type", signatureAlgorithm).Msg("GitHub commit verification completed") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, Status: status, Reason: verification.Reason, diff --git a/pkg/attestation/crafter/runners/commitverification/gitlab.go b/pkg/attestation/crafter/runners/commitverification/gitlab.go index 9e0d6678f..bbb0b6b08 100644 --- a/pkg/attestation/crafter/runners/commitverification/gitlab.go +++ b/pkg/attestation/crafter/runners/commitverification/gitlab.go @@ -23,12 +23,11 @@ import ( "net/url" "time" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/rs/zerolog" ) // VerifyGitLabCommit verifies a commit signature using the GitLab API -func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, token string, logger *zerolog.Logger) *api.Commit_CommitVerification { +func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, token string, logger *zerolog.Logger) *CommitVerification { // URL encode the project path (e.g., "group/project" -> "group%2Fproject") encodedProject := url.PathEscape(projectPath) @@ -44,9 +43,9 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t if logger != nil { logger.Debug().Err(err).Msg("failed to create GitLab API request") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: fmt.Sprintf("Failed to create request: %v", err), Platform: "gitlab", } @@ -63,9 +62,9 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t if logger != nil { logger.Debug().Err(err).Str("commit", commitHash).Msg("failed to fetch commit from GitLab") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: fmt.Sprintf("GitLab API error: %v", err), Platform: "gitlab", } @@ -80,9 +79,9 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t // 404 means the commit is unsigned (no signature data available) if resp.StatusCode == http.StatusNotFound { - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_NOT_APPLICABLE, + Status: VerificationStatusNotApplicable, Reason: "Commit is not signed", Platform: "gitlab", } @@ -94,9 +93,9 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t } else { reason = fmt.Sprintf("GitLab API error: HTTP %d", resp.StatusCode) } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: reason, Platform: "gitlab", } @@ -108,22 +107,22 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t if logger != nil { logger.Debug().Err(err).Msg("failed to decode GitLab API response") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, - Status: api.Commit_CommitVerification_VERIFICATION_STATUS_UNAVAILABLE, + Status: VerificationStatusUnavailable, Reason: fmt.Sprintf("Failed to parse response: %v", err), Platform: "gitlab", } } // Parse GitLab verification status - var status api.Commit_CommitVerification_VerificationStatus + var status VerificationStatus var reason string var keyID string var signatureAlgorithm string if signatureResponse.VerificationStatus == "verified" { - status = api.Commit_CommitVerification_VERIFICATION_STATUS_VERIFIED + status = VerificationStatusVerified reason = "Commit signed and verified" if signatureResponse.GPGKeyID != 0 { keyID = fmt.Sprintf("%d", signatureResponse.GPGKeyID) @@ -132,7 +131,7 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t } signatureAlgorithm = signatureResponse.SignatureType } else { - status = api.Commit_CommitVerification_VERIFICATION_STATUS_UNVERIFIED + status = VerificationStatusUnverified reason = fmt.Sprintf("Signature not verified: %s", signatureResponse.VerificationStatus) if signatureResponse.GPGKeyID != 0 { keyID = fmt.Sprintf("%d", signatureResponse.GPGKeyID) @@ -141,15 +140,15 @@ func VerifyGitLabCommit(ctx context.Context, baseURL, projectPath, commitHash, t } if logger != nil { - logger.Debug().Str("status", status.String()).Str("reason", reason).Str("verification_status", signatureResponse.VerificationStatus).Msg("GitLab commit verification completed") + logger.Debug().Int("status", int(status)).Str("reason", reason).Str("verification_status", signatureResponse.VerificationStatus).Msg("GitLab commit verification completed") } - return &api.Commit_CommitVerification{ + return &CommitVerification{ Attempted: true, Status: status, Reason: reason, Platform: "gitlab", - KeyId: keyID, + KeyID: keyID, SignatureAlgorithm: signatureAlgorithm, } } diff --git a/pkg/attestation/crafter/runners/daggerpipeline.go b/pkg/attestation/crafter/runners/daggerpipeline.go index ffd47289a..7ece73d89 100644 --- a/pkg/attestation/crafter/runners/daggerpipeline.go +++ b/pkg/attestation/crafter/runners/daggerpipeline.go @@ -20,7 +20,6 @@ import ( "os" "strings" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" @@ -93,7 +92,7 @@ func (r *DaggerPipeline) Environment() RunnerEnvironment { return Unknown } -func (r *DaggerPipeline) VerifyCommitSignature(ctx context.Context, commitHash string) *api.Commit_CommitVerification { +func (r *DaggerPipeline) VerifyCommitSignature(ctx context.Context, commitHash string) *commitverification.CommitVerification { // Dagger can run in different CI environments. Detect which one we're in. // Check if running in GitHub Actions @@ -132,7 +131,7 @@ func (r *DaggerPipeline) isGitLabCIEnvironment() bool { } // verifyCommitViaGitHub performs GitHub commit verification -func (r *DaggerPipeline) verifyCommitViaGitHub(ctx context.Context, commitHash string) *api.Commit_CommitVerification { +func (r *DaggerPipeline) verifyCommitViaGitHub(ctx context.Context, commitHash string) *commitverification.CommitVerification { // Extract owner/repo from GITHUB_REPOSITORY env var repo := os.Getenv("GITHUB_REPOSITORY") if repo == "" { @@ -161,7 +160,7 @@ func (r *DaggerPipeline) verifyCommitViaGitHub(ctx context.Context, commitHash s } // verifyCommitViaGitLab performs GitLab commit verification -func (r *DaggerPipeline) verifyCommitViaGitLab(ctx context.Context, commitHash string) *api.Commit_CommitVerification { +func (r *DaggerPipeline) verifyCommitViaGitLab(ctx context.Context, commitHash string) *commitverification.CommitVerification { // Extract base URL and project path from env vars baseURL := os.Getenv("CI_SERVER_URL") projectPath := os.Getenv("CI_PROJECT_PATH") diff --git a/pkg/attestation/crafter/runners/generic.go b/pkg/attestation/crafter/runners/generic.go index 984c73fe2..ec99cf79c 100644 --- a/pkg/attestation/crafter/runners/generic.go +++ b/pkg/attestation/crafter/runners/generic.go @@ -19,7 +19,7 @@ import ( "context" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" ) type Generic struct{} @@ -62,6 +62,6 @@ func (r *Generic) Environment() RunnerEnvironment { return Unknown } -func (r *Generic) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { +func (r *Generic) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification { return nil // Not supported for this runner } diff --git a/pkg/attestation/crafter/runners/githubaction.go b/pkg/attestation/crafter/runners/githubaction.go index 3600d97c5..0bfd30c3a 100644 --- a/pkg/attestation/crafter/runners/githubaction.go +++ b/pkg/attestation/crafter/runners/githubaction.go @@ -22,7 +22,6 @@ import ( "strings" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/oidc" "github.com/rs/zerolog" @@ -144,7 +143,7 @@ func (r *GitHubAction) IsAuthenticated() bool { } // VerifyCommitSignature checks if a commit's signature is verified by GitHub -func (r *GitHubAction) VerifyCommitSignature(ctx context.Context, commitHash string) *api.Commit_CommitVerification { +func (r *GitHubAction) VerifyCommitSignature(ctx context.Context, commitHash string) *commitverification.CommitVerification { // Extract owner/repo from GITHUB_REPOSITORY env var repo := os.Getenv("GITHUB_REPOSITORY") // e.g., "owner/repo" if repo == "" { diff --git a/pkg/attestation/crafter/runners/gitlabpipeline.go b/pkg/attestation/crafter/runners/gitlabpipeline.go index 318cd61e9..ab2cbec1f 100644 --- a/pkg/attestation/crafter/runners/gitlabpipeline.go +++ b/pkg/attestation/crafter/runners/gitlabpipeline.go @@ -20,7 +20,6 @@ import ( "os" schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/oidc" "github.com/rs/zerolog" @@ -120,7 +119,7 @@ func (r *GitlabPipeline) Environment() RunnerEnvironment { } // VerifyCommitSignature checks if a commit's signature is verified by GitLab -func (r *GitlabPipeline) VerifyCommitSignature(ctx context.Context, commitHash string) *api.Commit_CommitVerification { +func (r *GitlabPipeline) VerifyCommitSignature(ctx context.Context, commitHash string) *commitverification.CommitVerification { // Extract base URL and project path from env vars baseURL := os.Getenv("CI_SERVER_URL") projectPath := os.Getenv("CI_PROJECT_PATH") diff --git a/pkg/attestation/crafter/runners/jenkinsjob.go b/pkg/attestation/crafter/runners/jenkinsjob.go index 8043eb013..66df53b5c 100644 --- a/pkg/attestation/crafter/runners/jenkinsjob.go +++ b/pkg/attestation/crafter/runners/jenkinsjob.go @@ -19,9 +19,8 @@ import ( "context" "os" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" - schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" ) type JenkinsJob struct{} @@ -86,6 +85,6 @@ func (r *JenkinsJob) Environment() RunnerEnvironment { return Unknown } -func (r *JenkinsJob) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { +func (r *JenkinsJob) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification { return nil // Not supported for this runner } diff --git a/pkg/attestation/crafter/runners/teamcitypipeline.go b/pkg/attestation/crafter/runners/teamcitypipeline.go index 2af9fbf12..3ba4996ee 100644 --- a/pkg/attestation/crafter/runners/teamcitypipeline.go +++ b/pkg/attestation/crafter/runners/teamcitypipeline.go @@ -19,9 +19,8 @@ import ( "context" "os" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" - schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" ) type TeamCityPipeline struct{} @@ -72,6 +71,6 @@ func (r *TeamCityPipeline) Environment() RunnerEnvironment { return Unknown } -func (r *TeamCityPipeline) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { +func (r *TeamCityPipeline) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification { return nil // Not supported for this runner } diff --git a/pkg/attestation/crafter/runners/tektonpipeline.go b/pkg/attestation/crafter/runners/tektonpipeline.go index 2dde528e4..9509f0b5b 100644 --- a/pkg/attestation/crafter/runners/tektonpipeline.go +++ b/pkg/attestation/crafter/runners/tektonpipeline.go @@ -19,9 +19,8 @@ import ( "context" "os" - api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" - schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/runners/commitverification" ) type TektonPipeline struct{} @@ -68,6 +67,6 @@ func (r *TektonPipeline) Environment() RunnerEnvironment { return Unknown } -func (r *TektonPipeline) VerifyCommitSignature(_ context.Context, _ string) *api.Commit_CommitVerification { +func (r *TektonPipeline) VerifyCommitSignature(_ context.Context, _ string) *commitverification.CommitVerification { return nil // Not supported for this runner } diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index 45c944c09..82167b475 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -334,13 +334,13 @@ func (p *ProvenancePredicateCommon) GetMetadata() *Metadata { const ( // Subject names - SubjectGitHead = "git.head" - subjectGitAnnotationAuthorEmail = "author.email" - subjectGitAnnotationAuthorName = "author.name" - subjectGitAnnotationAuthorVerified = "author.verified" - subjectGitAnnotationWhen = "date" - subjectGitAnnotationMessage = "message" - subjectGitAnnotationRemotes = "remotes" - subjectGitAnnotationSignature = "signature" - subjectGitAnnotationSignatureAlgorithm = "signature.algorithm" + SubjectGitHead = "git.head" + subjectGitAnnotationAuthorEmail = "author.email" + subjectGitAnnotationAuthorName = "author.name" + subjectGitAnnotationAuthorVerificationStatus = "author.verification_status" + subjectGitAnnotationWhen = "date" + subjectGitAnnotationMessage = "message" + subjectGitAnnotationRemotes = "remotes" + subjectGitAnnotationSignature = "signature" + subjectGitAnnotationSignatureAlgorithm = "signature.algorithm" ) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index ff4368d91..372bff3dd 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -147,7 +147,7 @@ func commitAnnotations(c *v1.Commit) (*structpb.Struct, error) { // add author verification only if exists if c.GetPlatformVerification() != nil { pv := c.GetPlatformVerification() - annotationsRaw[subjectGitAnnotationAuthorVerified] = pv.GetStatus().String() + annotationsRaw[subjectGitAnnotationAuthorVerificationStatus] = pv.GetStatus().String() annotationsRaw[subjectGitAnnotationSignatureAlgorithm] = pv.GetSignatureAlgorithm() } From 054d8f37ee2c674b6ada0a0a1fe8cbf43ab23912 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Mon, 12 Jan 2026 13:51:20 +0100 Subject: [PATCH 8/8] ignore linting problem properly Signed-off-by: Javier Rodriguez --- buf.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buf.yaml b/buf.yaml index 766d1a0e9..4358d0cfc 100644 --- a/buf.yaml +++ b/buf.yaml @@ -106,6 +106,9 @@ modules: except: - FIELD_NOT_REQUIRED - PACKAGE_NO_IMPORT_CYCLE + - ENUM_VALUE_UPPER_SNAKE_CASE + - ENUM_VALUE_PREFIX + - ENUM_ZERO_VALUE_SUFFIX disallow_comment_ignores: true breaking: use: