Add configurable profile create mutation
What does this MR do and why?
Introduces ScanProfiles::Create mutation and ScanProfiles::CreateScanProfileService, a GQL write path to create a Security::ScanProfile together with its triggers and per-trigger configuration in a single atomic call.
The service creates the profile, its triggers, and one ScanProfiles::Configuration row per configured trigger inside a transaction. Per-scan-type configuration is typed via a oneOf input union and each trigger's configuration is attached through the existing FK. Also adds a new create_security_scan_profiles ability (for Security Manager, Maintainer, Owner, Admin) enforced by the mutation, a per-namespace cap of 20 profiles, and an allowed-trigger matrix per scan type.
Changelog: added
EE: true
Related issue
[Backend] Add configuration write path (#605765 - closed) • Gal Katz
Query plan
Raw SQL
SELECT COUNT(*)
FROM "security_scan_profiles"
WHERE "security_scan_profiles"."namespace_id" = 9970Plan
See details here
Aggregate (cost=3.36..3.37 rows=1 width=8) (actual time=1.777..1.778 rows=1 loops=1)
Buffers: shared hit=3 read=6 dirtied=1
WAL: records=1 fpi=1 bytes=6445
I/O Timings: read=1.687 write=0.000
-> Index Only Scan using index_security_scan_profiles_namespace_scan_type_name on public.security_scan_profiles (cost=0.28..3.35 rows=4 width=0) (actual time=1.238..1.770 rows=4 loops=1)
Index Cond: (security_scan_profiles.namespace_id = 9970)
Heap Fetches: 3
Index Searches: 1
Buffers: shared hit=3 read=6 dirtied=1
WAL: records=1 fpi=1 bytes=6445
I/O Timings: read=1.687 write=0.000
Settings: effective_cache_size = '338688MB', jit = 'off', work_mem = '100MB', random_page_cost = '1.5', seq_page_cost = '4'
Query ID: -2720531705532250424How to set up and validate locally
-
Enable the
configurable_security_scan_profilesFF:Feature.enable(:configurable_security_scan_profiles) -
Choose a root group
g. -
Use this mutation to create a profile with a typed configuration attached to its trigger:
mutation {
securityScanProfileCreate(
input: {
namespaceId: "gid://gitlab/Group/<g.id>"
scanType: DEPENDENCY_SCANNING_POST_PROCESSING
name: "DS-PP"
triggers: [
{
triggerType: SBOM_INGESTED
configuration: {
dependencyScanningPostProcessing: {
autoRemediation: {
enabled: true
cooldown: 5
severityLevel: HIGH
upgradePolicy: MINOR
}
}
}
}
]
}
) {
scanProfile {
id
name
scanType
triggers
configuration
}
errors
}
}
Expect a persisted profile whose sbom_ingested trigger carries the configuration. Confirm:
Security::ScanProfile.last.scan_profile_triggers.map { |t| [t.trigger_type, t.configuration&.configuration] }-
Use these mutations to validate the error handling:
a) No triggers → "A scan profile must have at least one trigger."
mutation { securityScanProfileCreate( input: { namespaceId: "gid://gitlab/Group/<g.id>" scanType: SAST name: "x" triggers: [] } ) { errors } }b) Trigger not allowed for scan type → "Trigger type is not allowed for sast scan profiles"
mutation { securityScanProfileCreate( input: { namespaceId: "gid://gitlab/Group/1<g.id>" scanType: SAST name: "x" triggers: [{ triggerType: SBOM_INGESTED }] } ) { errors } }c) Config member does not match scan type → top-level ArgumentError
mutation { securityScanProfileCreate( input: { namespaceId: "gid://gitlab/Group/<g.id>" scanType: SAST name: "x" triggers: [ { triggerType: DEFAULT_BRANCH_PIPELINE configuration: { dependencyScanningPostProcessing: { autoRemediation: { enabled: true } } } } ] } ) { errors } }
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.