From b76f30d1a45698895ab2bedc0ce9fc6d25eef154 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Tue, 9 Jun 2026 11:47:06 +0200 Subject: [PATCH] feat: org setting to skip storing runner-discovered env vars in attestations Add an organization-level skip_runner_env_vars setting that opts out of storing the environment variables automatically discovered by the CI runner in the attestation. The contract's env_allow_list is still honored, and runner detection and runner environment metadata are unaffected. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino Chainloop-Trace-Sessions: 9b4d83fb-68fa-40f1-b9da-4e67c2b6c6ce --- app/cli/cmd/organization_update.go | 6 ++ app/cli/documentation/cli-reference.mdx | 1 + app/cli/pkg/action/attestation_init.go | 3 + app/cli/pkg/action/membership_list.go | 2 + app/cli/pkg/action/org_update.go | 3 + .../api/controlplane/v1/organization.pb.go | 22 +++++-- .../api/controlplane/v1/organization.proto | 3 + .../controlplane/v1/response_messages.pb.go | 18 +++-- .../controlplane/v1/response_messages.proto | 2 + .../api/controlplane/v1/workflow_run.pb.go | 20 ++++-- .../api/controlplane/v1/workflow_run.proto | 2 + .../frontend/attestation/v1/crafting_state.ts | 19 ++++++ .../frontend/controlplane/v1/organization.ts | 20 +++++- .../controlplane/v1/response_messages.ts | 16 +++++ .../frontend/controlplane/v1/workflow_run.ts | 16 +++++ ...attestation.v1.Attestation.jsonschema.json | 8 +++ .../attestation.v1.Attestation.schema.json | 8 +++ ...ServiceInitResponse.Result.jsonschema.json | 8 +++ ...tionServiceInitResponse.Result.schema.json | 8 +++ .../controlplane.v1.OrgItem.jsonschema.json | 8 +++ .../controlplane.v1.OrgItem.schema.json | 8 +++ ...zationServiceUpdateRequest.jsonschema.json | 8 +++ ...ganizationServiceUpdateRequest.schema.json | 8 +++ .../internal/service/attestation.go | 1 + app/controlplane/internal/service/context.go | 1 + .../internal/service/organization.go | 1 + app/controlplane/pkg/biz/organization.go | 3 + .../pkg/biz/organization_integration_test.go | 14 ++++ .../ent/migrate/migrations/20260609111546.sql | 2 + .../pkg/data/ent/migrate/migrations/atlas.sum | 3 +- .../pkg/data/ent/migrate/schema.go | 1 + app/controlplane/pkg/data/ent/mutation.go | 56 +++++++++++++++- app/controlplane/pkg/data/ent/organization.go | 13 +++- .../pkg/data/ent/organization/organization.go | 10 +++ .../pkg/data/ent/organization/where.go | 15 +++++ .../pkg/data/ent/organization_create.go | 65 +++++++++++++++++++ .../pkg/data/ent/organization_update.go | 34 ++++++++++ app/controlplane/pkg/data/ent/runtime.go | 6 +- .../pkg/data/ent/schema/organization.go | 3 + app/controlplane/pkg/data/organization.go | 2 + .../api/attestation/v1/crafting_state.pb.go | 21 ++++-- .../api/attestation/v1/crafting_state.proto | 4 ++ pkg/attestation/crafter/crafter.go | 39 +++++++---- pkg/attestation/crafter/crafter_test.go | 32 +++++++++ 44 files changed, 506 insertions(+), 37 deletions(-) create mode 100644 app/controlplane/pkg/data/ent/migrate/migrations/20260609111546.sql diff --git a/app/cli/cmd/organization_update.go b/app/cli/cmd/organization_update.go index 6b128e757..179949ef7 100644 --- a/app/cli/cmd/organization_update.go +++ b/app/cli/cmd/organization_update.go @@ -33,6 +33,7 @@ func newOrganizationUpdateCmd() *cobra.Command { apiTokenMaxDaysInactive string enableAIAgentCollector bool blockAttestationsOnReleasedVersions bool + skipRunnerEnvVars bool ) cmd := &cobra.Command{ @@ -64,6 +65,10 @@ func newOrganizationUpdateCmd() *cobra.Command { opts.BlockAttestationsOnReleasedVersions = &blockAttestationsOnReleasedVersions } + if cmd.Flags().Changed("skip-runner-env-vars") { + opts.SkipRunnerEnvVars = &skipRunnerEnvVars + } + if cmd.Flags().Changed("api-token-max-days-inactive") { days, err := strconv.Atoi(apiTokenMaxDaysInactive) if err != nil { @@ -96,5 +101,6 @@ func newOrganizationUpdateCmd() *cobra.Command { cmd.Flags().StringVar(&apiTokenMaxDaysInactive, "api-token-max-days-inactive", "", "maximum days of inactivity before API tokens are auto-revoked (e.g. '90', '0' to disable)") cmd.Flags().BoolVar(&enableAIAgentCollector, "enable-ai-agent-collector", false, "enable automatic AI agent config collection during attestation init") cmd.Flags().BoolVar(&blockAttestationsOnReleasedVersions, "block-attestations-on-released-versions", false, "reject new attestations pushed to project versions that are already released") + cmd.Flags().BoolVar(&skipRunnerEnvVars, "skip-runner-env-vars", false, "opt out of storing the environment variables automatically discovered by the CI runner in the attestation") return cmd } diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index 625e00128..ea41b92a2 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -2917,6 +2917,7 @@ Options --policies-allowed-hostnames strings set the allowed hostnames for the policy engine --prevent-implicit-workflow-creation prevent workflows and projects from being created implicitly during attestation init --restrict-contract-creation restrict contract creation (org-level and project-level) to only organization admins (owner/admin roles) +--skip-runner-env-vars opt out of storing the environment variables automatically discovered by the CI runner in the attestation ``` Options inherited from parent commands diff --git a/app/cli/pkg/action/attestation_init.go b/app/cli/pkg/action/attestation_init.go index 89689421f..bfb9cc8d1 100644 --- a/app/cli/pkg/action/attestation_init.go +++ b/app/cli/pkg/action/attestation_init.go @@ -200,6 +200,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun blockOnPolicyViolation bool policiesAllowedHostnames []string enableAIAgentCollector bool + skipRunnerEnvVars bool // Timestamp Authority URL for new attestations timestampAuthorityURL, signingCAName string uiDashboardURL string @@ -233,6 +234,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun blockOnPolicyViolation = result.GetBlockOnPolicyViolation() policiesAllowedHostnames = result.GetPoliciesAllowedHostnames() enableAIAgentCollector = result.GetEnableAiAgentCollector() + skipRunnerEnvVars = result.GetSkipRunnerEnvVars() signingOpts := result.GetSigningOptions() timestampAuthorityURL = signingOpts.GetTimestampAuthorityUrl() signingCAName = signingOpts.GetSigningCa() @@ -291,6 +293,7 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun Runner: discoveredRunner, BlockOnPolicyViolation: blockOnPolicyViolation, PoliciesAllowedHostnames: policiesAllowedHostnames, + SkipRunnerEnvVars: skipRunnerEnvVars, SigningOptions: &crafter.SigningOpts{ TimestampAuthorityURL: timestampAuthorityURL, SigningCAName: signingCAName, diff --git a/app/cli/pkg/action/membership_list.go b/app/cli/pkg/action/membership_list.go index 07c737668..8f92dac00 100644 --- a/app/cli/pkg/action/membership_list.go +++ b/app/cli/pkg/action/membership_list.go @@ -38,6 +38,7 @@ type OrgItem struct { APITokenMaxDaysInactive *string `json:"apiTokenMaxDaysInactive,omitempty"` EnableAIAgentCollector bool `json:"enableAiAgentCollector"` BlockAttestationsOnReleasedVersions bool `json:"blockAttestationsOnReleasedVersions"` + SkipRunnerEnvVars bool `json:"skipRunnerEnvVars"` } type MembershipItem struct { @@ -142,6 +143,7 @@ func pbOrgItemToAction(in *pb.OrgItem) *OrgItem { PreventImplicitWorkflowCreation: in.PreventImplicitWorkflowCreation, EnableAIAgentCollector: in.EnableAiAgentCollector, BlockAttestationsOnReleasedVersions: in.BlockAttestationsOnReleasedVersions, + SkipRunnerEnvVars: in.SkipRunnerEnvVars, } if in.DefaultPolicyViolationStrategy == pb.OrgItem_POLICY_VIOLATION_BLOCKING_STRATEGY_BLOCK { diff --git a/app/cli/pkg/action/org_update.go b/app/cli/pkg/action/org_update.go index 02c60518b..480d85083 100644 --- a/app/cli/pkg/action/org_update.go +++ b/app/cli/pkg/action/org_update.go @@ -42,6 +42,8 @@ type NewOrgUpdateOpts struct { EnableAIAgentCollector *bool // BlockAttestationsOnReleasedVersions rejects new attestations pushed to project versions that are already released BlockAttestationsOnReleasedVersions *bool + // SkipRunnerEnvVars opts out of storing the environment variables automatically discovered by the CI runner in the attestation + SkipRunnerEnvVars *bool } func (action *OrgUpdate) Run(ctx context.Context, name string, opts *NewOrgUpdateOpts) (*OrgItem, error) { @@ -54,6 +56,7 @@ func (action *OrgUpdate) Run(ctx context.Context, name string, opts *NewOrgUpdat RestrictContractCreationToOrgAdmins: opts.RestrictContractCreation, EnableAiAgentCollector: opts.EnableAIAgentCollector, BlockAttestationsOnReleasedVersions: opts.BlockAttestationsOnReleasedVersions, + SkipRunnerEnvVars: opts.SkipRunnerEnvVars, } if opts.PoliciesAllowedHostnames != nil { diff --git a/app/controlplane/api/controlplane/v1/organization.pb.go b/app/controlplane/api/controlplane/v1/organization.pb.go index c79b11f81..ed94bc5a8 100644 --- a/app/controlplane/api/controlplane/v1/organization.pb.go +++ b/app/controlplane/api/controlplane/v1/organization.pb.go @@ -454,8 +454,10 @@ type OrganizationServiceUpdateRequest struct { EnableAiAgentCollector *bool `protobuf:"varint,8,opt,name=enable_ai_agent_collector,json=enableAiAgentCollector,proto3,oneof" json:"enable_ai_agent_collector,omitempty"` // Reject new attestations pushed to project versions that are already released (prerelease == false) BlockAttestationsOnReleasedVersions *bool `protobuf:"varint,9,opt,name=block_attestations_on_released_versions,json=blockAttestationsOnReleasedVersions,proto3,oneof" json:"block_attestations_on_released_versions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Opt out of storing the environment variables automatically discovered by the CI runner in the attestation + SkipRunnerEnvVars *bool `protobuf:"varint,10,opt,name=skip_runner_env_vars,json=skipRunnerEnvVars,proto3,oneof" json:"skip_runner_env_vars,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *OrganizationServiceUpdateRequest) Reset() { @@ -551,6 +553,13 @@ func (x *OrganizationServiceUpdateRequest) GetBlockAttestationsOnReleasedVersion return false } +func (x *OrganizationServiceUpdateRequest) GetSkipRunnerEnvVars() bool { + if x != nil && x.SkipRunnerEnvVars != nil { + return *x.SkipRunnerEnvVars + } + return false +} + type OrganizationServiceUpdateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Result *OrgItem `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` @@ -709,7 +718,7 @@ const file_controlplane_v1_organization_proto_rawDesc = "" + " OrganizationServiceCreateRequest\x12\x1b\n" + "\x04name\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04name\"U\n" + "!OrganizationServiceCreateResponse\x120\n" + - "\x06result\x18\x01 \x01(\v2\x18.controlplane.v1.OrgItemR\x06result\"\xf0\x06\n" + + "\x06result\x18\x01 \x01(\v2\x18.controlplane.v1.OrgItemR\x06result\"\xbf\a\n" + " OrganizationServiceUpdateRequest\x12\x1b\n" + "\x04name\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x04name\x12>\n" + "\x19block_on_policy_violation\x18\x02 \x01(\bH\x00R\x16blockOnPolicyViolation\x88\x01\x01\x12<\n" + @@ -719,13 +728,16 @@ const file_controlplane_v1_organization_proto_rawDesc = "" + "(restrict_contract_creation_to_org_admins\x18\x06 \x01(\bH\x02R#restrictContractCreationToOrgAdmins\x88\x01\x01\x12A\n" + "\x1bapi_token_max_days_inactive\x18\a \x01(\x05H\x03R\x17apiTokenMaxDaysInactive\x88\x01\x01\x12>\n" + "\x19enable_ai_agent_collector\x18\b \x01(\bH\x04R\x16enableAiAgentCollector\x88\x01\x01\x12Y\n" + - "'block_attestations_on_released_versions\x18\t \x01(\bH\x05R#blockAttestationsOnReleasedVersions\x88\x01\x01B\x1c\n" + + "'block_attestations_on_released_versions\x18\t \x01(\bH\x05R#blockAttestationsOnReleasedVersions\x88\x01\x01\x124\n" + + "\x14skip_runner_env_vars\x18\n" + + " \x01(\bH\x06R\x11skipRunnerEnvVars\x88\x01\x01B\x1c\n" + "\x1a_block_on_policy_violationB%\n" + "#_prevent_implicit_workflow_creationB+\n" + ")_restrict_contract_creation_to_org_adminsB\x1e\n" + "\x1c_api_token_max_days_inactiveB\x1c\n" + "\x1a_enable_ai_agent_collectorB*\n" + - "(_block_attestations_on_released_versions\"U\n" + + "(_block_attestations_on_released_versionsB\x17\n" + + "\x15_skip_runner_env_vars\"U\n" + "!OrganizationServiceUpdateResponse\x120\n" + "\x06result\x18\x01 \x01(\v2\x18.controlplane.v1.OrgItemR\x06result\"?\n" + " OrganizationServiceDeleteRequest\x12\x1b\n" + diff --git a/app/controlplane/api/controlplane/v1/organization.proto b/app/controlplane/api/controlplane/v1/organization.proto index d57b5bf5d..c6d6ef4f7 100644 --- a/app/controlplane/api/controlplane/v1/organization.proto +++ b/app/controlplane/api/controlplane/v1/organization.proto @@ -108,6 +108,9 @@ message OrganizationServiceUpdateRequest { // Reject new attestations pushed to project versions that are already released (prerelease == false) optional bool block_attestations_on_released_versions = 9; + + // Opt out of storing the environment variables automatically discovered by the CI runner in the attestation + optional bool skip_runner_env_vars = 10; } message OrganizationServiceUpdateResponse { diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index deb101414..98354281b 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -2305,8 +2305,10 @@ type OrgItem struct { EnableAiAgentCollector bool `protobuf:"varint,10,opt,name=enable_ai_agent_collector,json=enableAiAgentCollector,proto3" json:"enable_ai_agent_collector,omitempty"` // Whether new attestations are rejected on project versions that are already released (prerelease == false) BlockAttestationsOnReleasedVersions bool `protobuf:"varint,11,opt,name=block_attestations_on_released_versions,json=blockAttestationsOnReleasedVersions,proto3" json:"block_attestations_on_released_versions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Whether the environment variables automatically discovered by the CI runner are skipped from the attestation + SkipRunnerEnvVars bool `protobuf:"varint,12,opt,name=skip_runner_env_vars,json=skipRunnerEnvVars,proto3" json:"skip_runner_env_vars,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *OrgItem) Reset() { @@ -2416,6 +2418,13 @@ func (x *OrgItem) GetBlockAttestationsOnReleasedVersions() bool { return false } +func (x *OrgItem) GetSkipRunnerEnvVars() bool { + if x != nil { + return x.SkipRunnerEnvVars + } + return false +} + type CASBackendItem struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -3301,7 +3310,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + "\n" + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x123\n" + - "\x04role\x18\x06 \x01(\x0e2\x1f.controlplane.v1.MembershipRoleR\x04role\"\xb2\a\n" + + "\x04role\x18\x06 \x01(\x0e2\x1f.controlplane.v1.MembershipRoleR\x04role\"\xe3\a\n" + "\aOrgItem\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x129\n" + @@ -3316,7 +3325,8 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x1bapi_token_max_days_inactive\x18\t \x01(\x05H\x00R\x17apiTokenMaxDaysInactive\x88\x01\x01\x129\n" + "\x19enable_ai_agent_collector\x18\n" + " \x01(\bR\x16enableAiAgentCollector\x12T\n" + - "'block_attestations_on_released_versions\x18\v \x01(\bR#blockAttestationsOnReleasedVersions\"\xb4\x01\n" + + "'block_attestations_on_released_versions\x18\v \x01(\bR#blockAttestationsOnReleasedVersions\x12/\n" + + "\x14skip_runner_env_vars\x18\f \x01(\bR\x11skipRunnerEnvVars\"\xb4\x01\n" + "\x1fPolicyViolationBlockingStrategy\x122\n" + ".POLICY_VIOLATION_BLOCKING_STRATEGY_UNSPECIFIED\x10\x00\x12,\n" + "(POLICY_VIOLATION_BLOCKING_STRATEGY_BLOCK\x10\x01\x12/\n" + diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index 5dff8d273..e7d79f43d 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -393,6 +393,8 @@ message OrgItem { bool enable_ai_agent_collector = 10; // Whether new attestations are rejected on project versions that are already released (prerelease == false) bool block_attestations_on_released_versions = 11; + // Whether the environment variables automatically discovered by the CI runner are skipped from the attestation + bool skip_runner_env_vars = 12; enum PolicyViolationBlockingStrategy { POLICY_VIOLATION_BLOCKING_STRATEGY_UNSPECIFIED = 0; diff --git a/app/controlplane/api/controlplane/v1/workflow_run.pb.go b/app/controlplane/api/controlplane/v1/workflow_run.pb.go index 2ac61bf50..d87662b4c 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.pb.go +++ b/app/controlplane/api/controlplane/v1/workflow_run.pb.go @@ -1427,8 +1427,10 @@ type AttestationServiceInitResponse_Result struct { UiDashboardUrl string `protobuf:"bytes,7,opt,name=ui_dashboard_url,json=uiDashboardUrl,proto3" json:"ui_dashboard_url,omitempty"` // Whether AI agent config collection is enabled at the org level EnableAiAgentCollector bool `protobuf:"varint,8,opt,name=enable_ai_agent_collector,json=enableAiAgentCollector,proto3" json:"enable_ai_agent_collector,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation + SkipRunnerEnvVars bool `protobuf:"varint,9,opt,name=skip_runner_env_vars,json=skipRunnerEnvVars,proto3" json:"skip_runner_env_vars,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AttestationServiceInitResponse_Result) Reset() { @@ -1510,6 +1512,13 @@ func (x *AttestationServiceInitResponse_Result) GetEnableAiAgentCollector() bool return false } +func (x *AttestationServiceInitResponse_Result) GetSkipRunnerEnvVars() bool { + if x != nil { + return x.SkipRunnerEnvVars + } + return false +} + type AttestationServiceInitResponse_SigningOptions struct { state protoimpl.MessageState `protogen:"open.v1"` // TSA service to be used for signing @@ -1834,9 +1843,9 @@ const file_controlplane_v1_workflow_run_proto_rawDesc = "" + "\x18require_existing_version\x18\a \x01(\bR\x16requireExistingVersion\x12,\n" + "\x12use_latest_version\x18\b \x01(\bR\x10useLatestVersion\x12)\n" + "\x0emark_as_latest\x18\t \x01(\bH\x00R\fmarkAsLatest\x88\x01\x01B\x11\n" + - "\x0f_mark_as_latest\"\x94\x05\n" + + "\x0f_mark_as_latest\"\xc5\x05\n" + "\x1eAttestationServiceInitResponse\x12N\n" + - "\x06result\x18\x01 \x01(\v26.controlplane.v1.AttestationServiceInitResponse.ResultR\x06result\x1a\xb8\x03\n" + + "\x06result\x18\x01 \x01(\v26.controlplane.v1.AttestationServiceInitResponse.ResultR\x06result\x1a\xe9\x03\n" + "\x06Result\x12C\n" + "\fworkflow_run\x18\x02 \x01(\v2 .controlplane.v1.WorkflowRunItemR\vworkflowRun\x12\"\n" + "\forganization\x18\x03 \x01(\tR\forganization\x129\n" + @@ -1844,7 +1853,8 @@ const file_controlplane_v1_workflow_run_proto_rawDesc = "" + "\x0fsigning_options\x18\x05 \x01(\v2>.controlplane.v1.AttestationServiceInitResponse.SigningOptionsR\x0esigningOptions\x12<\n" + "\x1apolicies_allowed_hostnames\x18\x06 \x03(\tR\x18policiesAllowedHostnames\x12(\n" + "\x10ui_dashboard_url\x18\a \x01(\tR\x0euiDashboardUrl\x129\n" + - "\x19enable_ai_agent_collector\x18\b \x01(\bR\x16enableAiAgentCollector\x1ag\n" + + "\x19enable_ai_agent_collector\x18\b \x01(\bR\x16enableAiAgentCollector\x12/\n" + + "\x14skip_runner_env_vars\x18\t \x01(\bR\x11skipRunnerEnvVars\x1ag\n" + "\x0eSigningOptions\x126\n" + "\x17timestamp_authority_url\x18\x01 \x01(\tR\x15timestampAuthorityUrl\x12\x1d\n" + "\n" + diff --git a/app/controlplane/api/controlplane/v1/workflow_run.proto b/app/controlplane/api/controlplane/v1/workflow_run.proto index 30708fd04..f29492c05 100644 --- a/app/controlplane/api/controlplane/v1/workflow_run.proto +++ b/app/controlplane/api/controlplane/v1/workflow_run.proto @@ -152,6 +152,8 @@ message AttestationServiceInitResponse { string ui_dashboard_url = 7; // Whether AI agent config collection is enabled at the org level bool enable_ai_agent_collector = 8; + // Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation + bool skip_runner_env_vars = 9; } message SigningOptions { 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 1ef9ddba4..ac115b714 100644 --- a/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts +++ b/app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts @@ -45,6 +45,11 @@ export interface Attestation { policiesAllowedHostnames: string[]; /** CAS backend information used during attestation */ casBackend?: Attestation_CASBackend; + /** + * skip storing the environment variables automatically discovered by the CI runner. + * The contract's env_allow_list is not affected by this flag. + */ + skipRunnerEnvVars: boolean; } export interface Attestation_MaterialsEntry { @@ -634,6 +639,7 @@ function createBaseAttestation(): Attestation { auth: undefined, policiesAllowedHostnames: [], casBackend: undefined, + skipRunnerEnvVars: false, }; } @@ -690,6 +696,9 @@ export const Attestation = { if (message.casBackend !== undefined) { Attestation_CASBackend.encode(message.casBackend, writer.uint32(154).fork()).ldelim(); } + if (message.skipRunnerEnvVars === true) { + writer.uint32(160).bool(message.skipRunnerEnvVars); + } return writer; }, @@ -828,6 +837,13 @@ export const Attestation = { message.casBackend = Attestation_CASBackend.decode(reader, reader.uint32()); continue; + case 20: + if (tag !== 160) { + break; + } + + message.skipRunnerEnvVars = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -879,6 +895,7 @@ export const Attestation = { ? object.policiesAllowedHostnames.map((e: any) => String(e)) : [], casBackend: isSet(object.casBackend) ? Attestation_CASBackend.fromJSON(object.casBackend) : undefined, + skipRunnerEnvVars: isSet(object.skipRunnerEnvVars) ? Boolean(object.skipRunnerEnvVars) : false, }; }, @@ -930,6 +947,7 @@ export const Attestation = { } message.casBackend !== undefined && (obj.casBackend = message.casBackend ? Attestation_CASBackend.toJSON(message.casBackend) : undefined); + message.skipRunnerEnvVars !== undefined && (obj.skipRunnerEnvVars = message.skipRunnerEnvVars); return obj; }, @@ -987,6 +1005,7 @@ export const Attestation = { message.casBackend = (object.casBackend !== undefined && object.casBackend !== null) ? Attestation_CASBackend.fromPartial(object.casBackend) : undefined; + message.skipRunnerEnvVars = object.skipRunnerEnvVars ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/organization.ts b/app/controlplane/api/gen/frontend/controlplane/v1/organization.ts index f74d09e06..b77bb5874 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/organization.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/organization.ts @@ -93,7 +93,11 @@ export interface OrganizationServiceUpdateRequest { | boolean | undefined; /** Reject new attestations pushed to project versions that are already released (prerelease == false) */ - blockAttestationsOnReleasedVersions?: boolean | undefined; + blockAttestationsOnReleasedVersions?: + | boolean + | undefined; + /** Opt out of storing the environment variables automatically discovered by the CI runner in the attestation */ + skipRunnerEnvVars?: boolean | undefined; } export interface OrganizationServiceUpdateResponse { @@ -686,6 +690,7 @@ function createBaseOrganizationServiceUpdateRequest(): OrganizationServiceUpdate apiTokenMaxDaysInactive: undefined, enableAiAgentCollector: undefined, blockAttestationsOnReleasedVersions: undefined, + skipRunnerEnvVars: undefined, }; } @@ -718,6 +723,9 @@ export const OrganizationServiceUpdateRequest = { if (message.blockAttestationsOnReleasedVersions !== undefined) { writer.uint32(72).bool(message.blockAttestationsOnReleasedVersions); } + if (message.skipRunnerEnvVars !== undefined) { + writer.uint32(80).bool(message.skipRunnerEnvVars); + } return writer; }, @@ -791,6 +799,13 @@ export const OrganizationServiceUpdateRequest = { message.blockAttestationsOnReleasedVersions = reader.bool(); continue; + case 10: + if (tag !== 80) { + break; + } + + message.skipRunnerEnvVars = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -823,6 +838,7 @@ export const OrganizationServiceUpdateRequest = { blockAttestationsOnReleasedVersions: isSet(object.blockAttestationsOnReleasedVersions) ? Boolean(object.blockAttestationsOnReleasedVersions) : undefined, + skipRunnerEnvVars: isSet(object.skipRunnerEnvVars) ? Boolean(object.skipRunnerEnvVars) : undefined, }; }, @@ -846,6 +862,7 @@ export const OrganizationServiceUpdateRequest = { message.enableAiAgentCollector !== undefined && (obj.enableAiAgentCollector = message.enableAiAgentCollector); message.blockAttestationsOnReleasedVersions !== undefined && (obj.blockAttestationsOnReleasedVersions = message.blockAttestationsOnReleasedVersions); + message.skipRunnerEnvVars !== undefined && (obj.skipRunnerEnvVars = message.skipRunnerEnvVars); return obj; }, @@ -868,6 +885,7 @@ export const OrganizationServiceUpdateRequest = { message.apiTokenMaxDaysInactive = object.apiTokenMaxDaysInactive ?? undefined; message.enableAiAgentCollector = object.enableAiAgentCollector ?? undefined; message.blockAttestationsOnReleasedVersions = object.blockAttestationsOnReleasedVersions ?? undefined; + message.skipRunnerEnvVars = object.skipRunnerEnvVars ?? undefined; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index 58d3d7523..dea3ff5c2 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -896,6 +896,8 @@ export interface OrgItem { enableAiAgentCollector: boolean; /** Whether new attestations are rejected on project versions that are already released (prerelease == false) */ blockAttestationsOnReleasedVersions: boolean; + /** Whether the environment variables automatically discovered by the CI runner are skipped from the attestation */ + skipRunnerEnvVars: boolean; } export enum OrgItem_PolicyViolationBlockingStrategy { @@ -4406,6 +4408,7 @@ function createBaseOrgItem(): OrgItem { apiTokenMaxDaysInactive: undefined, enableAiAgentCollector: false, blockAttestationsOnReleasedVersions: false, + skipRunnerEnvVars: false, }; } @@ -4444,6 +4447,9 @@ export const OrgItem = { if (message.blockAttestationsOnReleasedVersions === true) { writer.uint32(88).bool(message.blockAttestationsOnReleasedVersions); } + if (message.skipRunnerEnvVars === true) { + writer.uint32(96).bool(message.skipRunnerEnvVars); + } return writer; }, @@ -4531,6 +4537,13 @@ export const OrgItem = { message.blockAttestationsOnReleasedVersions = reader.bool(); continue; + case 12: + if (tag !== 96) { + break; + } + + message.skipRunnerEnvVars = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -4565,6 +4578,7 @@ export const OrgItem = { blockAttestationsOnReleasedVersions: isSet(object.blockAttestationsOnReleasedVersions) ? Boolean(object.blockAttestationsOnReleasedVersions) : false, + skipRunnerEnvVars: isSet(object.skipRunnerEnvVars) ? Boolean(object.skipRunnerEnvVars) : false, }; }, @@ -4592,6 +4606,7 @@ export const OrgItem = { message.enableAiAgentCollector !== undefined && (obj.enableAiAgentCollector = message.enableAiAgentCollector); message.blockAttestationsOnReleasedVersions !== undefined && (obj.blockAttestationsOnReleasedVersions = message.blockAttestationsOnReleasedVersions); + message.skipRunnerEnvVars !== undefined && (obj.skipRunnerEnvVars = message.skipRunnerEnvVars); return obj; }, @@ -4612,6 +4627,7 @@ export const OrgItem = { message.apiTokenMaxDaysInactive = object.apiTokenMaxDaysInactive ?? undefined; message.enableAiAgentCollector = object.enableAiAgentCollector ?? false; message.blockAttestationsOnReleasedVersions = object.blockAttestationsOnReleasedVersions ?? false; + message.skipRunnerEnvVars = object.skipRunnerEnvVars ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts index 274f83ead..5e68be807 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts @@ -138,6 +138,8 @@ export interface AttestationServiceInitResponse_Result { uiDashboardUrl: string; /** Whether AI agent config collection is enabled at the org level */ enableAiAgentCollector: boolean; + /** Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation */ + skipRunnerEnvVars: boolean; } export interface AttestationServiceInitResponse_SigningOptions { @@ -1348,6 +1350,7 @@ function createBaseAttestationServiceInitResponse_Result(): AttestationServiceIn policiesAllowedHostnames: [], uiDashboardUrl: "", enableAiAgentCollector: false, + skipRunnerEnvVars: false, }; } @@ -1374,6 +1377,9 @@ export const AttestationServiceInitResponse_Result = { if (message.enableAiAgentCollector === true) { writer.uint32(64).bool(message.enableAiAgentCollector); } + if (message.skipRunnerEnvVars === true) { + writer.uint32(72).bool(message.skipRunnerEnvVars); + } return writer; }, @@ -1433,6 +1439,13 @@ export const AttestationServiceInitResponse_Result = { message.enableAiAgentCollector = reader.bool(); continue; + case 9: + if (tag !== 72) { + break; + } + + message.skipRunnerEnvVars = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1455,6 +1468,7 @@ export const AttestationServiceInitResponse_Result = { : [], uiDashboardUrl: isSet(object.uiDashboardUrl) ? String(object.uiDashboardUrl) : "", enableAiAgentCollector: isSet(object.enableAiAgentCollector) ? Boolean(object.enableAiAgentCollector) : false, + skipRunnerEnvVars: isSet(object.skipRunnerEnvVars) ? Boolean(object.skipRunnerEnvVars) : false, }; }, @@ -1474,6 +1488,7 @@ export const AttestationServiceInitResponse_Result = { } message.uiDashboardUrl !== undefined && (obj.uiDashboardUrl = message.uiDashboardUrl); message.enableAiAgentCollector !== undefined && (obj.enableAiAgentCollector = message.enableAiAgentCollector); + message.skipRunnerEnvVars !== undefined && (obj.skipRunnerEnvVars = message.skipRunnerEnvVars); return obj; }, @@ -1498,6 +1513,7 @@ export const AttestationServiceInitResponse_Result = { message.policiesAllowedHostnames = object.policiesAllowedHostnames?.map((e) => e) || []; message.uiDashboardUrl = object.uiDashboardUrl ?? ""; message.enableAiAgentCollector = object.enableAiAgentCollector ?? false; + message.skipRunnerEnvVars = object.skipRunnerEnvVars ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.jsonschema.json index b05ac4c34..95888e48b 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.jsonschema.json @@ -80,6 +80,10 @@ "^(signing_options)$": { "$ref": "attestation.v1.Attestation.SigningOptions.jsonschema.json", "description": "Signing options" + }, + "^(skip_runner_env_vars)$": { + "description": "skip storing the environment variables automatically discovered by the CI runner.\n The contract's env_allow_list is not affected by this flag.", + "type": "boolean" } }, "properties": { @@ -189,6 +193,10 @@ "$ref": "attestation.v1.Attestation.SigningOptions.jsonschema.json", "description": "Signing options" }, + "skipRunnerEnvVars": { + "description": "skip storing the environment variables automatically discovered by the CI runner.\n The contract's env_allow_list is not affected by this flag.", + "type": "boolean" + }, "workflow": { "$ref": "attestation.v1.WorkflowMetadata.jsonschema.json" } diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.schema.json index 5b4b85c8c..7d2d3331f 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.schema.json @@ -80,6 +80,10 @@ "^(signingOptions)$": { "$ref": "attestation.v1.Attestation.SigningOptions.schema.json", "description": "Signing options" + }, + "^(skipRunnerEnvVars)$": { + "description": "skip storing the environment variables automatically discovered by the CI runner.\n The contract's env_allow_list is not affected by this flag.", + "type": "boolean" } }, "properties": { @@ -189,6 +193,10 @@ "$ref": "attestation.v1.Attestation.SigningOptions.schema.json", "description": "Signing options" }, + "skip_runner_env_vars": { + "description": "skip storing the environment variables automatically discovered by the CI runner.\n The contract's env_allow_list is not affected by this flag.", + "type": "boolean" + }, "workflow": { "$ref": "attestation.v1.WorkflowMetadata.schema.json" } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json index c8b111c40..57bbda5b2 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.jsonschema.json @@ -22,6 +22,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.jsonschema.json", "description": "Signing options" }, + "^(skip_runner_env_vars)$": { + "description": "Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "^(ui_dashboard_url)$": { "description": "URL pointing to the web dashboard. It might be empty if not available", "type": "string" @@ -54,6 +58,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.jsonschema.json", "description": "Signing options" }, + "skipRunnerEnvVars": { + "description": "Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "uiDashboardUrl": { "description": "URL pointing to the web dashboard. It might be empty if not available", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json index 76901c13d..b6d27a74b 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationServiceInitResponse.Result.schema.json @@ -22,6 +22,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.schema.json", "description": "Signing options" }, + "^(skipRunnerEnvVars)$": { + "description": "Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "^(uiDashboardUrl)$": { "description": "URL pointing to the web dashboard. It might be empty if not available", "type": "string" @@ -54,6 +58,10 @@ "$ref": "controlplane.v1.AttestationServiceInitResponse.SigningOptions.schema.json", "description": "Signing options" }, + "skip_runner_env_vars": { + "description": "Whether to skip storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "ui_dashboard_url": { "description": "URL pointing to the web dashboard. It might be empty if not available", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.jsonschema.json index ffe2d4ae6..f4fff5c54 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.jsonschema.json @@ -52,6 +52,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "^(skip_runner_env_vars)$": { + "description": "Whether the environment variables automatically discovered by the CI runner are skipped from the attestation", + "type": "boolean" + }, "^(updated_at)$": { "$ref": "google.protobuf.Timestamp.jsonschema.json" } @@ -112,6 +116,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "skipRunnerEnvVars": { + "description": "Whether the environment variables automatically discovered by the CI runner are skipped from the attestation", + "type": "boolean" + }, "updatedAt": { "$ref": "google.protobuf.Timestamp.jsonschema.json" } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.schema.json index 0186ccd4f..f8966c68a 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrgItem.schema.json @@ -52,6 +52,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "^(skipRunnerEnvVars)$": { + "description": "Whether the environment variables automatically discovered by the CI runner are skipped from the attestation", + "type": "boolean" + }, "^(updatedAt)$": { "$ref": "google.protobuf.Timestamp.schema.json" } @@ -112,6 +116,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "skip_runner_env_vars": { + "description": "Whether the environment variables automatically discovered by the CI runner are skipped from the attestation", + "type": "boolean" + }, "updated_at": { "$ref": "google.protobuf.Timestamp.schema.json" } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.jsonschema.json index 523b1b814..bd8853a05 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.jsonschema.json @@ -36,6 +36,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "^(skip_runner_env_vars)$": { + "description": "Opt out of storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "^(update_policies_allowed_hostnames)$": { "description": "flag that allows us to detect if the value is explicitly set\n since repeated fields can not be optional", "type": "boolean" @@ -79,6 +83,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "skipRunnerEnvVars": { + "description": "Opt out of storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "updatePoliciesAllowedHostnames": { "description": "flag that allows us to detect if the value is explicitly set\n since repeated fields can not be optional", "type": "boolean" diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.schema.json index 0fb092c93..14da6f1f8 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.OrganizationServiceUpdateRequest.schema.json @@ -36,6 +36,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "^(skipRunnerEnvVars)$": { + "description": "Opt out of storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "^(updatePoliciesAllowedHostnames)$": { "description": "flag that allows us to detect if the value is explicitly set\n since repeated fields can not be optional", "type": "boolean" @@ -79,6 +83,10 @@ "description": "restrict_contract_creation_to_org_admins restricts contract creation (org-level and project-level) to only organization admins (owner/admin roles)", "type": "boolean" }, + "skip_runner_env_vars": { + "description": "Opt out of storing the environment variables automatically discovered by the CI runner in the attestation", + "type": "boolean" + }, "update_policies_allowed_hostnames": { "description": "flag that allows us to detect if the value is explicitly set\n since repeated fields can not be optional", "type": "boolean" diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index e76a5b10e..61815fbd2 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -223,6 +223,7 @@ func (s *AttestationService) Init(ctx context.Context, req *cpAPI.AttestationSer PoliciesAllowedHostnames: org.PoliciesAllowedHostnames, UiDashboardUrl: s.bootstrapConfig.UiDashboardUrl, EnableAiAgentCollector: org.EnableAIAgentCollector, + SkipRunnerEnvVars: org.SkipRunnerEnvVars, } resp.SigningOptions = &cpAPI.AttestationServiceInitResponse_SigningOptions{} diff --git a/app/controlplane/internal/service/context.go b/app/controlplane/internal/service/context.go index e98eed465..2874157ae 100644 --- a/app/controlplane/internal/service/context.go +++ b/app/controlplane/internal/service/context.go @@ -127,6 +127,7 @@ func bizOrgToPb(m *biz.Organization) *pb.OrgItem { RestrictContractCreationToOrgAdmins: m.RestrictContractCreationToOrgAdmins, EnableAiAgentCollector: m.EnableAIAgentCollector, BlockAttestationsOnReleasedVersions: m.BlockAttestationsOnReleasedVersions, + SkipRunnerEnvVars: m.SkipRunnerEnvVars, } if m.APITokenInactivityThresholdDays != nil { diff --git a/app/controlplane/internal/service/organization.go b/app/controlplane/internal/service/organization.go index a426143f8..f97abcb24 100644 --- a/app/controlplane/internal/service/organization.go +++ b/app/controlplane/internal/service/organization.go @@ -110,6 +110,7 @@ func (s *OrganizationService) Update(ctx context.Context, req *pb.OrganizationSe APITokenInactivityThresholdDays: apiTokenMaxDaysInactive, EnableAIAgentCollector: req.EnableAiAgentCollector, BlockAttestationsOnReleasedVersions: req.BlockAttestationsOnReleasedVersions, + SkipRunnerEnvVars: req.SkipRunnerEnvVars, }) if err != nil { return nil, handleUseCaseErr(err, s.log) diff --git a/app/controlplane/pkg/biz/organization.go b/app/controlplane/pkg/biz/organization.go index 4bfce6f6f..14f5a7201 100644 --- a/app/controlplane/pkg/biz/organization.go +++ b/app/controlplane/pkg/biz/organization.go @@ -53,6 +53,8 @@ type Organization struct { EnableAIAgentCollector bool // BlockAttestationsOnReleasedVersions rejects new attestations pushed to project versions that are already released (prerelease == false) BlockAttestationsOnReleasedVersions bool + // SkipRunnerEnvVars opts out of storing the environment variables automatically discovered by the CI runner in the attestation + SkipRunnerEnvVars bool // Suspended indicates whether the organization is suspended Suspended bool } @@ -68,6 +70,7 @@ type OrganizationUpdateOpts struct { APITokenInactivityThresholdDays *int EnableAIAgentCollector *bool BlockAttestationsOnReleasedVersions *bool + SkipRunnerEnvVars *bool } type OrganizationRepo interface { diff --git a/app/controlplane/pkg/biz/organization_integration_test.go b/app/controlplane/pkg/biz/organization_integration_test.go index 4be0eb82c..50041a20d 100644 --- a/app/controlplane/pkg/biz/organization_integration_test.go +++ b/app/controlplane/pkg/biz/organization_integration_test.go @@ -169,6 +169,20 @@ func (s *OrgIntegrationTestSuite) TestUpdate() { s.False(got.EnableAIAgentCollector) }) + s.Run("valid skip runner env vars update", func() { + got, err := s.Organization.Update(ctx, s.user.ID, s.org.Name, &biz.OrganizationUpdateOpts{ + SkipRunnerEnvVars: toPtrBool(true), + }) + s.NoError(err) + s.True(got.SkipRunnerEnvVars) + + got, err = s.Organization.Update(ctx, s.user.ID, s.org.Name, &biz.OrganizationUpdateOpts{ + SkipRunnerEnvVars: toPtrBool(false), + }) + s.NoError(err) + s.False(got.SkipRunnerEnvVars) + }) + s.Run("but not passing a value doesn't clear the hostnames value", func() { got, err := s.Organization.Update(ctx, s.user.ID, s.org.Name, &biz.OrganizationUpdateOpts{ PoliciesAllowedHostnames: []string{"foo.com", "bar.com"}, diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/20260609111546.sql b/app/controlplane/pkg/data/ent/migrate/migrations/20260609111546.sql new file mode 100644 index 000000000..3843270a5 --- /dev/null +++ b/app/controlplane/pkg/data/ent/migrate/migrations/20260609111546.sql @@ -0,0 +1,2 @@ +-- Modify "organizations" table +ALTER TABLE "organizations" ADD COLUMN "skip_runner_env_vars" boolean NOT NULL DEFAULT false; diff --git a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum index 14eff557b..ad4003e0c 100644 --- a/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/pkg/data/ent/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:cpizrs7Ih7KS6e/UHvHSXhaClLfSgzfxcSneduY74XU= +h1:hBVIjVioi1u2VnBzWIATzfZi0fpDZ189Lam4XUp83Zs= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -137,3 +137,4 @@ h1:cpizrs7Ih7KS6e/UHvHSXhaClLfSgzfxcSneduY74XU= 20260516210119.sql h1:rfBnXQwPnrhVYAp/OIiFPGcS+Tx1x9CAMSDPGs8HIT8= 20260527093110.sql h1:Jgq9xDyLakqIVMo3LZF4pPYAkBSc2G5qUK/IV9bzYc4= 20260608210839.sql h1:RfwH7Yf8FRzqPdJeNzfIVH5TwPEush04KMAv4K1c2zY= +20260609111546.sql h1:2NQIGvPRGNb0XeCbokCSZ8CyuiuIhgbXix9XUWJok2M= diff --git a/app/controlplane/pkg/data/ent/migrate/schema.go b/app/controlplane/pkg/data/ent/migrate/schema.go index 6fd24cf6f..920f1fb87 100644 --- a/app/controlplane/pkg/data/ent/migrate/schema.go +++ b/app/controlplane/pkg/data/ent/migrate/schema.go @@ -444,6 +444,7 @@ var ( {Name: "api_token_inactivity_threshold_days", Type: field.TypeInt, Nullable: true}, {Name: "enable_ai_agent_collector", Type: field.TypeBool, Default: false}, {Name: "block_attestations_on_released_versions", Type: field.TypeBool, Default: false}, + {Name: "skip_runner_env_vars", Type: field.TypeBool, Default: false}, {Name: "suspended", Type: field.TypeBool, Default: false}, } // OrganizationsTable holds the schema information for the "organizations" table. diff --git a/app/controlplane/pkg/data/ent/mutation.go b/app/controlplane/pkg/data/ent/mutation.go index 8a6f3d87e..072dac02f 100644 --- a/app/controlplane/pkg/data/ent/mutation.go +++ b/app/controlplane/pkg/data/ent/mutation.go @@ -9031,6 +9031,7 @@ type OrganizationMutation struct { addapi_token_inactivity_threshold_days *int enable_ai_agent_collector *bool block_attestations_on_released_versions *bool + skip_runner_env_vars *bool suspended *bool clearedFields map[string]struct{} memberships map[uuid.UUID]struct{} @@ -9641,6 +9642,42 @@ func (m *OrganizationMutation) ResetBlockAttestationsOnReleasedVersions() { m.block_attestations_on_released_versions = nil } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (m *OrganizationMutation) SetSkipRunnerEnvVars(b bool) { + m.skip_runner_env_vars = &b +} + +// SkipRunnerEnvVars returns the value of the "skip_runner_env_vars" field in the mutation. +func (m *OrganizationMutation) SkipRunnerEnvVars() (r bool, exists bool) { + v := m.skip_runner_env_vars + if v == nil { + return + } + return *v, true +} + +// OldSkipRunnerEnvVars returns the old "skip_runner_env_vars" field's value of the Organization entity. +// If the Organization object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *OrganizationMutation) OldSkipRunnerEnvVars(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSkipRunnerEnvVars is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSkipRunnerEnvVars requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSkipRunnerEnvVars: %w", err) + } + return oldValue.SkipRunnerEnvVars, nil +} + +// ResetSkipRunnerEnvVars resets all changes to the "skip_runner_env_vars" field. +func (m *OrganizationMutation) ResetSkipRunnerEnvVars() { + m.skip_runner_env_vars = nil +} + // SetSuspended sets the "suspended" field. func (m *OrganizationMutation) SetSuspended(b bool) { m.suspended = &b @@ -10197,7 +10234,7 @@ func (m *OrganizationMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *OrganizationMutation) Fields() []string { - fields := make([]string, 0, 12) + fields := make([]string, 0, 13) if m.name != nil { fields = append(fields, organization.FieldName) } @@ -10231,6 +10268,9 @@ func (m *OrganizationMutation) Fields() []string { if m.block_attestations_on_released_versions != nil { fields = append(fields, organization.FieldBlockAttestationsOnReleasedVersions) } + if m.skip_runner_env_vars != nil { + fields = append(fields, organization.FieldSkipRunnerEnvVars) + } if m.suspended != nil { fields = append(fields, organization.FieldSuspended) } @@ -10264,6 +10304,8 @@ func (m *OrganizationMutation) Field(name string) (ent.Value, bool) { return m.EnableAiAgentCollector() case organization.FieldBlockAttestationsOnReleasedVersions: return m.BlockAttestationsOnReleasedVersions() + case organization.FieldSkipRunnerEnvVars: + return m.SkipRunnerEnvVars() case organization.FieldSuspended: return m.Suspended() } @@ -10297,6 +10339,8 @@ func (m *OrganizationMutation) OldField(ctx context.Context, name string) (ent.V return m.OldEnableAiAgentCollector(ctx) case organization.FieldBlockAttestationsOnReleasedVersions: return m.OldBlockAttestationsOnReleasedVersions(ctx) + case organization.FieldSkipRunnerEnvVars: + return m.OldSkipRunnerEnvVars(ctx) case organization.FieldSuspended: return m.OldSuspended(ctx) } @@ -10385,6 +10429,13 @@ func (m *OrganizationMutation) SetField(name string, value ent.Value) error { } m.SetBlockAttestationsOnReleasedVersions(v) return nil + case organization.FieldSkipRunnerEnvVars: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSkipRunnerEnvVars(v) + return nil case organization.FieldSuspended: v, ok := value.(bool) if !ok { @@ -10510,6 +10561,9 @@ func (m *OrganizationMutation) ResetField(name string) error { case organization.FieldBlockAttestationsOnReleasedVersions: m.ResetBlockAttestationsOnReleasedVersions() return nil + case organization.FieldSkipRunnerEnvVars: + m.ResetSkipRunnerEnvVars() + return nil case organization.FieldSuspended: m.ResetSuspended() return nil diff --git a/app/controlplane/pkg/data/ent/organization.go b/app/controlplane/pkg/data/ent/organization.go index d2d493849..027adf356 100644 --- a/app/controlplane/pkg/data/ent/organization.go +++ b/app/controlplane/pkg/data/ent/organization.go @@ -41,6 +41,8 @@ type Organization struct { EnableAiAgentCollector bool `json:"enable_ai_agent_collector,omitempty"` // BlockAttestationsOnReleasedVersions holds the value of the "block_attestations_on_released_versions" field. BlockAttestationsOnReleasedVersions bool `json:"block_attestations_on_released_versions,omitempty"` + // SkipRunnerEnvVars holds the value of the "skip_runner_env_vars" field. + SkipRunnerEnvVars bool `json:"skip_runner_env_vars,omitempty"` // Suspended holds the value of the "suspended" field. Suspended bool `json:"suspended,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -162,7 +164,7 @@ func (*Organization) scanValues(columns []string) ([]any, error) { switch columns[i] { case organization.FieldPoliciesAllowedHostnames: values[i] = new([]byte) - case organization.FieldBlockOnPolicyViolation, organization.FieldPreventImplicitWorkflowCreation, organization.FieldRestrictContractCreationToOrgAdmins, organization.FieldEnableAiAgentCollector, organization.FieldBlockAttestationsOnReleasedVersions, organization.FieldSuspended: + case organization.FieldBlockOnPolicyViolation, organization.FieldPreventImplicitWorkflowCreation, organization.FieldRestrictContractCreationToOrgAdmins, organization.FieldEnableAiAgentCollector, organization.FieldBlockAttestationsOnReleasedVersions, organization.FieldSkipRunnerEnvVars, organization.FieldSuspended: values[i] = new(sql.NullBool) case organization.FieldAPITokenInactivityThresholdDays: values[i] = new(sql.NullInt64) @@ -262,6 +264,12 @@ func (_m *Organization) assignValues(columns []string, values []any) error { } else if value.Valid { _m.BlockAttestationsOnReleasedVersions = value.Bool } + case organization.FieldSkipRunnerEnvVars: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field skip_runner_env_vars", values[i]) + } else if value.Valid { + _m.SkipRunnerEnvVars = value.Bool + } case organization.FieldSuspended: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field suspended", values[i]) @@ -384,6 +392,9 @@ func (_m *Organization) String() string { builder.WriteString("block_attestations_on_released_versions=") builder.WriteString(fmt.Sprintf("%v", _m.BlockAttestationsOnReleasedVersions)) builder.WriteString(", ") + builder.WriteString("skip_runner_env_vars=") + builder.WriteString(fmt.Sprintf("%v", _m.SkipRunnerEnvVars)) + builder.WriteString(", ") builder.WriteString("suspended=") builder.WriteString(fmt.Sprintf("%v", _m.Suspended)) builder.WriteByte(')') diff --git a/app/controlplane/pkg/data/ent/organization/organization.go b/app/controlplane/pkg/data/ent/organization/organization.go index 067a2b6ed..440b518fc 100644 --- a/app/controlplane/pkg/data/ent/organization/organization.go +++ b/app/controlplane/pkg/data/ent/organization/organization.go @@ -37,6 +37,8 @@ const ( FieldEnableAiAgentCollector = "enable_ai_agent_collector" // FieldBlockAttestationsOnReleasedVersions holds the string denoting the block_attestations_on_released_versions field in the database. FieldBlockAttestationsOnReleasedVersions = "block_attestations_on_released_versions" + // FieldSkipRunnerEnvVars holds the string denoting the skip_runner_env_vars field in the database. + FieldSkipRunnerEnvVars = "skip_runner_env_vars" // FieldSuspended holds the string denoting the suspended field in the database. FieldSuspended = "suspended" // EdgeMemberships holds the string denoting the memberships edge name in mutations. @@ -138,6 +140,7 @@ var Columns = []string{ FieldAPITokenInactivityThresholdDays, FieldEnableAiAgentCollector, FieldBlockAttestationsOnReleasedVersions, + FieldSkipRunnerEnvVars, FieldSuspended, } @@ -166,6 +169,8 @@ var ( DefaultEnableAiAgentCollector bool // DefaultBlockAttestationsOnReleasedVersions holds the default value on creation for the "block_attestations_on_released_versions" field. DefaultBlockAttestationsOnReleasedVersions bool + // DefaultSkipRunnerEnvVars holds the default value on creation for the "skip_runner_env_vars" field. + DefaultSkipRunnerEnvVars bool // DefaultSuspended holds the default value on creation for the "suspended" field. DefaultSuspended bool // DefaultID holds the default value on creation for the "id" field. @@ -230,6 +235,11 @@ func ByBlockAttestationsOnReleasedVersions(opts ...sql.OrderTermOption) OrderOpt return sql.OrderByField(FieldBlockAttestationsOnReleasedVersions, opts...).ToFunc() } +// BySkipRunnerEnvVars orders the results by the skip_runner_env_vars field. +func BySkipRunnerEnvVars(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSkipRunnerEnvVars, opts...).ToFunc() +} + // BySuspended orders the results by the suspended field. func BySuspended(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSuspended, opts...).ToFunc() diff --git a/app/controlplane/pkg/data/ent/organization/where.go b/app/controlplane/pkg/data/ent/organization/where.go index 6d4f204bc..3c1a9060b 100644 --- a/app/controlplane/pkg/data/ent/organization/where.go +++ b/app/controlplane/pkg/data/ent/organization/where.go @@ -106,6 +106,11 @@ func BlockAttestationsOnReleasedVersions(v bool) predicate.Organization { return predicate.Organization(sql.FieldEQ(FieldBlockAttestationsOnReleasedVersions, v)) } +// SkipRunnerEnvVars applies equality check predicate on the "skip_runner_env_vars" field. It's identical to SkipRunnerEnvVarsEQ. +func SkipRunnerEnvVars(v bool) predicate.Organization { + return predicate.Organization(sql.FieldEQ(FieldSkipRunnerEnvVars, v)) +} + // Suspended applies equality check predicate on the "suspended" field. It's identical to SuspendedEQ. func Suspended(v bool) predicate.Organization { return predicate.Organization(sql.FieldEQ(FieldSuspended, v)) @@ -416,6 +421,16 @@ func BlockAttestationsOnReleasedVersionsNEQ(v bool) predicate.Organization { return predicate.Organization(sql.FieldNEQ(FieldBlockAttestationsOnReleasedVersions, v)) } +// SkipRunnerEnvVarsEQ applies the EQ predicate on the "skip_runner_env_vars" field. +func SkipRunnerEnvVarsEQ(v bool) predicate.Organization { + return predicate.Organization(sql.FieldEQ(FieldSkipRunnerEnvVars, v)) +} + +// SkipRunnerEnvVarsNEQ applies the NEQ predicate on the "skip_runner_env_vars" field. +func SkipRunnerEnvVarsNEQ(v bool) predicate.Organization { + return predicate.Organization(sql.FieldNEQ(FieldSkipRunnerEnvVars, v)) +} + // SuspendedEQ applies the EQ predicate on the "suspended" field. func SuspendedEQ(v bool) predicate.Organization { return predicate.Organization(sql.FieldEQ(FieldSuspended, v)) diff --git a/app/controlplane/pkg/data/ent/organization_create.go b/app/controlplane/pkg/data/ent/organization_create.go index 5f7aa0790..a499f434e 100644 --- a/app/controlplane/pkg/data/ent/organization_create.go +++ b/app/controlplane/pkg/data/ent/organization_create.go @@ -171,6 +171,20 @@ func (_c *OrganizationCreate) SetNillableBlockAttestationsOnReleasedVersions(v * return _c } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (_c *OrganizationCreate) SetSkipRunnerEnvVars(v bool) *OrganizationCreate { + _c.mutation.SetSkipRunnerEnvVars(v) + return _c +} + +// SetNillableSkipRunnerEnvVars sets the "skip_runner_env_vars" field if the given value is not nil. +func (_c *OrganizationCreate) SetNillableSkipRunnerEnvVars(v *bool) *OrganizationCreate { + if v != nil { + _c.SetSkipRunnerEnvVars(*v) + } + return _c +} + // SetSuspended sets the "suspended" field. func (_c *OrganizationCreate) SetSuspended(v bool) *OrganizationCreate { _c.mutation.SetSuspended(v) @@ -397,6 +411,10 @@ func (_c *OrganizationCreate) defaults() { v := organization.DefaultBlockAttestationsOnReleasedVersions _c.mutation.SetBlockAttestationsOnReleasedVersions(v) } + if _, ok := _c.mutation.SkipRunnerEnvVars(); !ok { + v := organization.DefaultSkipRunnerEnvVars + _c.mutation.SetSkipRunnerEnvVars(v) + } if _, ok := _c.mutation.Suspended(); !ok { v := organization.DefaultSuspended _c.mutation.SetSuspended(v) @@ -433,6 +451,9 @@ func (_c *OrganizationCreate) check() error { if _, ok := _c.mutation.BlockAttestationsOnReleasedVersions(); !ok { return &ValidationError{Name: "block_attestations_on_released_versions", err: errors.New(`ent: missing required field "Organization.block_attestations_on_released_versions"`)} } + if _, ok := _c.mutation.SkipRunnerEnvVars(); !ok { + return &ValidationError{Name: "skip_runner_env_vars", err: errors.New(`ent: missing required field "Organization.skip_runner_env_vars"`)} + } if _, ok := _c.mutation.Suspended(); !ok { return &ValidationError{Name: "suspended", err: errors.New(`ent: missing required field "Organization.suspended"`)} } @@ -516,6 +537,10 @@ func (_c *OrganizationCreate) createSpec() (*Organization, *sqlgraph.CreateSpec) _spec.SetField(organization.FieldBlockAttestationsOnReleasedVersions, field.TypeBool, value) _node.BlockAttestationsOnReleasedVersions = value } + if value, ok := _c.mutation.SkipRunnerEnvVars(); ok { + _spec.SetField(organization.FieldSkipRunnerEnvVars, field.TypeBool, value) + _node.SkipRunnerEnvVars = value + } if value, ok := _c.mutation.Suspended(); ok { _spec.SetField(organization.FieldSuspended, field.TypeBool, value) _node.Suspended = value @@ -860,6 +885,18 @@ func (u *OrganizationUpsert) UpdateBlockAttestationsOnReleasedVersions() *Organi return u } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (u *OrganizationUpsert) SetSkipRunnerEnvVars(v bool) *OrganizationUpsert { + u.Set(organization.FieldSkipRunnerEnvVars, v) + return u +} + +// UpdateSkipRunnerEnvVars sets the "skip_runner_env_vars" field to the value that was provided on create. +func (u *OrganizationUpsert) UpdateSkipRunnerEnvVars() *OrganizationUpsert { + u.SetExcluded(organization.FieldSkipRunnerEnvVars) + return u +} + // SetSuspended sets the "suspended" field. func (u *OrganizationUpsert) SetSuspended(v bool) *OrganizationUpsert { u.Set(organization.FieldSuspended, v) @@ -1091,6 +1128,20 @@ func (u *OrganizationUpsertOne) UpdateBlockAttestationsOnReleasedVersions() *Org }) } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (u *OrganizationUpsertOne) SetSkipRunnerEnvVars(v bool) *OrganizationUpsertOne { + return u.Update(func(s *OrganizationUpsert) { + s.SetSkipRunnerEnvVars(v) + }) +} + +// UpdateSkipRunnerEnvVars sets the "skip_runner_env_vars" field to the value that was provided on create. +func (u *OrganizationUpsertOne) UpdateSkipRunnerEnvVars() *OrganizationUpsertOne { + return u.Update(func(s *OrganizationUpsert) { + s.UpdateSkipRunnerEnvVars() + }) +} + // SetSuspended sets the "suspended" field. func (u *OrganizationUpsertOne) SetSuspended(v bool) *OrganizationUpsertOne { return u.Update(func(s *OrganizationUpsert) { @@ -1491,6 +1542,20 @@ func (u *OrganizationUpsertBulk) UpdateBlockAttestationsOnReleasedVersions() *Or }) } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (u *OrganizationUpsertBulk) SetSkipRunnerEnvVars(v bool) *OrganizationUpsertBulk { + return u.Update(func(s *OrganizationUpsert) { + s.SetSkipRunnerEnvVars(v) + }) +} + +// UpdateSkipRunnerEnvVars sets the "skip_runner_env_vars" field to the value that was provided on create. +func (u *OrganizationUpsertBulk) UpdateSkipRunnerEnvVars() *OrganizationUpsertBulk { + return u.Update(func(s *OrganizationUpsert) { + s.UpdateSkipRunnerEnvVars() + }) +} + // SetSuspended sets the "suspended" field. func (u *OrganizationUpsertBulk) SetSuspended(v bool) *OrganizationUpsertBulk { return u.Update(func(s *OrganizationUpsert) { diff --git a/app/controlplane/pkg/data/ent/organization_update.go b/app/controlplane/pkg/data/ent/organization_update.go index acf0951ed..da01eada9 100644 --- a/app/controlplane/pkg/data/ent/organization_update.go +++ b/app/controlplane/pkg/data/ent/organization_update.go @@ -203,6 +203,20 @@ func (_u *OrganizationUpdate) SetNillableBlockAttestationsOnReleasedVersions(v * return _u } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (_u *OrganizationUpdate) SetSkipRunnerEnvVars(v bool) *OrganizationUpdate { + _u.mutation.SetSkipRunnerEnvVars(v) + return _u +} + +// SetNillableSkipRunnerEnvVars sets the "skip_runner_env_vars" field if the given value is not nil. +func (_u *OrganizationUpdate) SetNillableSkipRunnerEnvVars(v *bool) *OrganizationUpdate { + if v != nil { + _u.SetSkipRunnerEnvVars(*v) + } + return _u +} + // SetSuspended sets the "suspended" field. func (_u *OrganizationUpdate) SetSuspended(v bool) *OrganizationUpdate { _u.mutation.SetSuspended(v) @@ -635,6 +649,9 @@ func (_u *OrganizationUpdate) sqlSave(ctx context.Context) (_node int, err error if value, ok := _u.mutation.BlockAttestationsOnReleasedVersions(); ok { _spec.SetField(organization.FieldBlockAttestationsOnReleasedVersions, field.TypeBool, value) } + if value, ok := _u.mutation.SkipRunnerEnvVars(); ok { + _spec.SetField(organization.FieldSkipRunnerEnvVars, field.TypeBool, value) + } if value, ok := _u.mutation.Suspended(); ok { _spec.SetField(organization.FieldSuspended, field.TypeBool, value) } @@ -1228,6 +1245,20 @@ func (_u *OrganizationUpdateOne) SetNillableBlockAttestationsOnReleasedVersions( return _u } +// SetSkipRunnerEnvVars sets the "skip_runner_env_vars" field. +func (_u *OrganizationUpdateOne) SetSkipRunnerEnvVars(v bool) *OrganizationUpdateOne { + _u.mutation.SetSkipRunnerEnvVars(v) + return _u +} + +// SetNillableSkipRunnerEnvVars sets the "skip_runner_env_vars" field if the given value is not nil. +func (_u *OrganizationUpdateOne) SetNillableSkipRunnerEnvVars(v *bool) *OrganizationUpdateOne { + if v != nil { + _u.SetSkipRunnerEnvVars(*v) + } + return _u +} + // SetSuspended sets the "suspended" field. func (_u *OrganizationUpdateOne) SetSuspended(v bool) *OrganizationUpdateOne { _u.mutation.SetSuspended(v) @@ -1690,6 +1721,9 @@ func (_u *OrganizationUpdateOne) sqlSave(ctx context.Context) (_node *Organizati if value, ok := _u.mutation.BlockAttestationsOnReleasedVersions(); ok { _spec.SetField(organization.FieldBlockAttestationsOnReleasedVersions, field.TypeBool, value) } + if value, ok := _u.mutation.SkipRunnerEnvVars(); ok { + _spec.SetField(organization.FieldSkipRunnerEnvVars, field.TypeBool, value) + } if value, ok := _u.mutation.Suspended(); ok { _spec.SetField(organization.FieldSuspended, field.TypeBool, value) } diff --git a/app/controlplane/pkg/data/ent/runtime.go b/app/controlplane/pkg/data/ent/runtime.go index 85d4e2b6f..6ba03a475 100644 --- a/app/controlplane/pkg/data/ent/runtime.go +++ b/app/controlplane/pkg/data/ent/runtime.go @@ -229,8 +229,12 @@ func init() { organizationDescBlockAttestationsOnReleasedVersions := organizationFields[11].Descriptor() // organization.DefaultBlockAttestationsOnReleasedVersions holds the default value on creation for the block_attestations_on_released_versions field. organization.DefaultBlockAttestationsOnReleasedVersions = organizationDescBlockAttestationsOnReleasedVersions.Default.(bool) + // organizationDescSkipRunnerEnvVars is the schema descriptor for skip_runner_env_vars field. + organizationDescSkipRunnerEnvVars := organizationFields[12].Descriptor() + // organization.DefaultSkipRunnerEnvVars holds the default value on creation for the skip_runner_env_vars field. + organization.DefaultSkipRunnerEnvVars = organizationDescSkipRunnerEnvVars.Default.(bool) // organizationDescSuspended is the schema descriptor for suspended field. - organizationDescSuspended := organizationFields[12].Descriptor() + organizationDescSuspended := organizationFields[13].Descriptor() // organization.DefaultSuspended holds the default value on creation for the suspended field. organization.DefaultSuspended = organizationDescSuspended.Default.(bool) // organizationDescID is the schema descriptor for id field. diff --git a/app/controlplane/pkg/data/ent/schema/organization.go b/app/controlplane/pkg/data/ent/schema/organization.go index be2cc386d..47b484421 100644 --- a/app/controlplane/pkg/data/ent/schema/organization.go +++ b/app/controlplane/pkg/data/ent/schema/organization.go @@ -62,6 +62,9 @@ func (Organization) Fields() []ent.Field { field.Bool("enable_ai_agent_collector").Default(false), // block_attestations_on_released_versions rejects new attestations pushed to project versions that are already released (prerelease == false) field.Bool("block_attestations_on_released_versions").Default(false), + // skip_runner_env_vars opts out of storing the environment variables automatically + // discovered by the CI runner in the attestation. The contract's env_allow_list is not affected. + field.Bool("skip_runner_env_vars").Default(false), // Suspended orgs are blocked from all operations. field.Bool("suspended").Default(false), } diff --git a/app/controlplane/pkg/data/organization.go b/app/controlplane/pkg/data/organization.go index 2919e8246..b3b54fe8c 100644 --- a/app/controlplane/pkg/data/organization.go +++ b/app/controlplane/pkg/data/organization.go @@ -104,6 +104,7 @@ func (r *OrganizationRepo) Update(ctx context.Context, id uuid.UUID, updateOpts SetNillableRestrictContractCreationToOrgAdmins(updateOpts.RestrictContractCreationToOrgAdmins). SetNillableEnableAiAgentCollector(updateOpts.EnableAIAgentCollector). SetNillableBlockAttestationsOnReleasedVersions(updateOpts.BlockAttestationsOnReleasedVersions). + SetNillableSkipRunnerEnvVars(updateOpts.SkipRunnerEnvVars). SetUpdatedAt(time.Now()) if updateOpts.PoliciesAllowedHostnames != nil { @@ -184,6 +185,7 @@ func entOrgToBizOrg(eu *ent.Organization) *biz.Organization { APITokenInactivityThresholdDays: eu.APITokenInactivityThresholdDays, EnableAIAgentCollector: eu.EnableAiAgentCollector, BlockAttestationsOnReleasedVersions: eu.BlockAttestationsOnReleasedVersions, + SkipRunnerEnvVars: eu.SkipRunnerEnvVars, Suspended: eu.Suspended, } } 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 f77109a76..c64e1dac5 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go @@ -184,9 +184,12 @@ type Attestation struct { // array of hostnames that are allowed to be used in the policies PoliciesAllowedHostnames []string `protobuf:"bytes,18,rep,name=policies_allowed_hostnames,json=policiesAllowedHostnames,proto3" json:"policies_allowed_hostnames,omitempty"` // CAS backend information used during attestation - CasBackend *Attestation_CASBackend `protobuf:"bytes,19,opt,name=cas_backend,json=casBackend,proto3" json:"cas_backend,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + CasBackend *Attestation_CASBackend `protobuf:"bytes,19,opt,name=cas_backend,json=casBackend,proto3" json:"cas_backend,omitempty"` + // skip storing the environment variables automatically discovered by the CI runner. + // The contract's env_allow_list is not affected by this flag. + SkipRunnerEnvVars bool `protobuf:"varint,20,opt,name=skip_runner_env_vars,json=skipRunnerEnvVars,proto3" json:"skip_runner_env_vars,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Attestation) Reset() { @@ -338,6 +341,13 @@ func (x *Attestation) GetCasBackend() *Attestation_CASBackend { return nil } +func (x *Attestation) GetSkipRunnerEnvVars() bool { + if x != nil { + return x.SkipRunnerEnvVars + } + return false +} + // The runner environment in which the attestation was crafted type RunnerEnvironment struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2789,7 +2799,7 @@ var File_attestation_v1_crafting_state_proto protoreflect.FileDescriptor const file_attestation_v1_crafting_state_proto_rawDesc = "" + "\n" + - "#attestation/v1/crafting_state.proto\x12\x0eattestation.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a)workflowcontract/v1/crafting_schema.proto\"\xde\x1b\n" + + "#attestation/v1/crafting_state.proto\x12\x0eattestation.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a)workflowcontract/v1/crafting_schema.proto\"\x8f\x1c\n" + "\vAttestation\x12I\n" + "\x0einitialized_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\rinitializedAt\x12;\n" + "\vfinished_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\n" + @@ -2812,7 +2822,8 @@ const file_attestation_v1_crafting_state_proto_rawDesc = "" + "\x04auth\x18\x11 \x01(\v2 .attestation.v1.Attestation.AuthR\x04auth\x12<\n" + "\x1apolicies_allowed_hostnames\x18\x12 \x03(\tR\x18policiesAllowedHostnames\x12G\n" + "\vcas_backend\x18\x13 \x01(\v2&.attestation.v1.Attestation.CASBackendR\n" + - "casBackend\x1ab\n" + + "casBackend\x12/\n" + + "\x14skip_runner_env_vars\x18\x14 \x01(\bR\x11skipRunnerEnvVars\x1ab\n" + "\x0eMaterialsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + "\x05value\x18\x02 \x01(\v2$.attestation.v1.Attestation.MaterialR\x05value:\x028\x01\x1a>\n" + diff --git a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto index 9fabe6466..702588cc9 100644 --- a/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto +++ b/pkg/attestation/crafter/api/attestation/v1/crafting_state.proto @@ -162,6 +162,10 @@ message Attestation { // CAS backend information used during attestation CASBackend cas_backend = 19; + // skip storing the environment variables automatically discovered by the CI runner. + // The contract's env_allow_list is not affected by this flag. + bool skip_runner_env_vars = 20; + message Auth { AuthType type = 1 [(buf.validate.field).enum = { not_in: [0] diff --git a/pkg/attestation/crafter/crafter.go b/pkg/attestation/crafter/crafter.go index ccf7435e5..4a3915377 100644 --- a/pkg/attestation/crafter/crafter.go +++ b/pkg/attestation/crafter/crafter.go @@ -204,6 +204,8 @@ type InitOpts struct { Auth *api.Attestation_Auth // array of hostnames that are allowed to be used in the policies PoliciesAllowedHostnames []string + // opt out of storing the environment variables automatically discovered by the CI runner + SkipRunnerEnvVars bool // CAS backend information CASBackend *api.Attestation_CASBackend // UIDashboardURL is the base URL to build the attestation view link @@ -451,6 +453,7 @@ func initialCraftingState(cwd string, opts *InitOpts) (*api.CraftingState, error }, Auth: opts.Auth, PoliciesAllowedHostnames: opts.PoliciesAllowedHostnames, + SkipRunnerEnvVars: opts.SkipRunnerEnvVars, CasBackend: opts.CASBackend, }, DryRun: opts.DryRun, @@ -485,20 +488,30 @@ func (c *Crafter) ResolveEnvVars(ctx context.Context, attestationID string) erro return fmt.Errorf("%s - %w", errorStr, ErrRunnerContextNotFound) } - // Workflow run environment variables - varNames := make([]string, len(c.Runner.ListEnvVars())) - for index, envVarDef := range c.Runner.ListEnvVars() { - varNames[index] = envVarDef.Name - } - c.Logger.Debug().Str("runnerType", c.Runner.ID().String()).Strs("variables", varNames).Msg("list of env variables to automatically extract") - - outputEnvVars, errors := c.Runner.ResolveEnvVars() - if len(errors) > 0 { - var combinedErrs string - for _, err := range errors { - combinedErrs += (*err).Error() + "\n" + // The org can opt out of storing the environment variables that the runner discovers + // automatically. The contract's env allow list below is still honored since it is an + // explicit, user-defined opt-in. + var outputEnvVars map[string]string + if c.CraftingState.GetAttestation().GetSkipRunnerEnvVars() { + c.Logger.Debug().Str("runnerType", c.Runner.ID().String()).Msg("skipping automatically discovered runner env variables") + outputEnvVars = make(map[string]string) + } else { + // Workflow run environment variables + varNames := make([]string, len(c.Runner.ListEnvVars())) + for index, envVarDef := range c.Runner.ListEnvVars() { + varNames[index] = envVarDef.Name + } + c.Logger.Debug().Str("runnerType", c.Runner.ID().String()).Strs("variables", varNames).Msg("list of env variables to automatically extract") + + var errs []*error + outputEnvVars, errs = c.Runner.ResolveEnvVars() + if len(errs) > 0 { + var combinedErrs string + for _, err := range errs { + combinedErrs += (*err).Error() + "\n" + } + return fmt.Errorf("error while resolving runner environment variables: %s", combinedErrs) } - return fmt.Errorf("error while resolving runner environment variables: %s", combinedErrs) } // User-defined environment vars diff --git a/pkg/attestation/crafter/crafter_test.go b/pkg/attestation/crafter/crafter_test.go index 6786c92c3..834fb9d42 100644 --- a/pkg/attestation/crafter/crafter_test.go +++ b/pkg/attestation/crafter/crafter_test.go @@ -355,6 +355,9 @@ func (s *crafterSuite) TestResolveEnvVars() { inGithubEnv bool inJenkinsEnv bool + // opt out of storing the auto-discovered runner env vars + skipRunnerEnvVars bool + expectedError string expectedEnvVars map[string]string }{ @@ -409,6 +412,33 @@ func (s *crafterSuite) TestResolveEnvVars() { "WORKSPACE": "/some/home/dir", "NODE_NAME": "some-node", }, + }, { + name: "skip runner env vars stores only the contract allow-list", + inGithubEnv: true, + skipRunnerEnvVars: true, + envVars: map[string]string{ + "CUSTOM_VAR_1": "custom_value_1", + "CUSTOM_VAR_2": "custom_value_2", + }, + // the auto-discovered GITHUB_* vars are not stored, only the explicit allow-list + expectedEnvVars: map[string]string{ + "CUSTOM_VAR_1": "custom_value_1", + "CUSTOM_VAR_2": "custom_value_2", + }, + }, { + name: "skip runner env vars does not fail on missing runner vars", + inGithubEnv: true, + skipRunnerEnvVars: true, + envVars: map[string]string{ + "CUSTOM_VAR_1": "custom_value_1", + "CUSTOM_VAR_2": "custom_value_2", + // A required runner var is missing, but it must not fail since runner vars are skipped + "GITHUB_ACTOR": "", + }, + expectedEnvVars: map[string]string{ + "CUSTOM_VAR_1": "custom_value_1", + "CUSTOM_VAR_2": "custom_value_2", + }, }, } @@ -441,6 +471,8 @@ func (s *crafterSuite) TestResolveEnvVars() { c, err := newInitializedCrafter(s.T(), contract, &v1.WorkflowMetadata{}, false, "", runner) require.NoError(s.T(), err) + c.CraftingState.Attestation.SkipRunnerEnvVars = tc.skipRunnerEnvVars + err = c.ResolveEnvVars(context.Background(), "") if tc.expectedError != "" {