Skip to content
Snippets Groups Projects
Commit 6749eab3 authored by Avielle Wolfe's avatar Avielle Wolfe Committed by Jan Provaznik
Browse files

Add API fuzzing config GQL types and models

New GraphQL types follow the structure of the SAST configuration types:
* CiConfiguration::ApiFuzzing::ScanModeEnum
* CiConfiguration::ApiFuzzing::ScanProfileType
* CiConfiguration::ApiFuzzing::Type

New models:
* Security::ApiFuzzing::CiConfiguration
* Security::ApiFuzzing::ScanProfile
parent d9730360
No related branches found
No related tags found
No related merge requests found
Showing
with 563 additions and 2 deletions
Loading
Loading
@@ -1077,6 +1077,56 @@ Identifier of Analytics::DevopsAdoption::Segment.
"""
scalar AnalyticsDevopsAdoptionSegmentID
 
"""
Data associated with configuring API fuzzing scans in GitLab CI
"""
type ApiFuzzingCiConfiguration {
"""
All available scan modes.
"""
scanModes: [ApiFuzzingScanMode!]
"""
All default scan profiles.
"""
scanProfiles: [ApiFuzzingScanProfile!]
}
"""
All possible ways to specify the API surface for an API fuzzing scan
"""
enum ApiFuzzingScanMode {
"""
The API surface is specified by a HAR file.
"""
HAR
"""
The API surface is specified by a OPENAPI file.
"""
OPENAPI
}
"""
An API Fuzzing scan profile.
"""
type ApiFuzzingScanProfile {
"""
A short description of the profile.
"""
description: String
"""
The unique name of the profile.
"""
name: String
"""
A syntax highlit HTML representation of the YAML.
"""
yaml: String
}
"""
User availability status
"""
Loading
Loading
@@ -18419,6 +18469,11 @@ type Project {
"""
allowMergeOnSkippedPipeline: Boolean
 
"""
API fuzzing configuration for the project. Available only when feature flag `api_fuzzing_configuration_ui` is enabled.
"""
apiFuzzingCiConfiguration: ApiFuzzingCiConfiguration
"""
Indicates the archived status of the project.
"""
Loading
Loading
Loading
Loading
@@ -2678,6 +2678,141 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ApiFuzzingCiConfiguration",
"description": "Data associated with configuring API fuzzing scans in GitLab CI",
"fields": [
{
"name": "scanModes",
"description": "All available scan modes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "ApiFuzzingScanMode",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "scanProfiles",
"description": "All default scan profiles.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ApiFuzzingScanProfile",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "ApiFuzzingScanMode",
"description": "All possible ways to specify the API surface for an API fuzzing scan",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "HAR",
"description": "The API surface is specified by a HAR file.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "OPENAPI",
"description": "The API surface is specified by a OPENAPI file.",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ApiFuzzingScanProfile",
"description": "An API Fuzzing scan profile.",
"fields": [
{
"name": "description",
"description": "A short description of the profile.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "The unique name of the profile.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "yaml",
"description": "A syntax highlit HTML representation of the YAML.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "AvailabilityEnum",
Loading
Loading
@@ -54303,6 +54438,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "apiFuzzingCiConfiguration",
"description": "API fuzzing configuration for the project. Available only when feature flag `api_fuzzing_configuration_ui` is enabled.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "ApiFuzzingCiConfiguration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "archived",
"description": "Indicates the archived status of the project.",
Loading
Loading
@@ -180,6 +180,25 @@ Autogenerated return type of AlertTodoCreate.
| `issue` | Issue | The issue created after mutation. |
| `todo` | Todo | The to-do item after mutation. |
 
### ApiFuzzingCiConfiguration
Data associated with configuring API fuzzing scans in GitLab CI.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `scanModes` | ApiFuzzingScanMode! => Array | All available scan modes. |
| `scanProfiles` | ApiFuzzingScanProfile! => Array | All default scan profiles. |
### ApiFuzzingScanProfile
An API Fuzzing scan profile..
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String | A short description of the profile. |
| `name` | String | The unique name of the profile. |
| `yaml` | String | A syntax highlit HTML representation of the YAML. |
### AwardEmoji
 
An emoji awarded by a user.
Loading
Loading
@@ -2790,6 +2809,7 @@ Autogenerated return type of PipelineRetry.
| `alertManagementIntegrations` | AlertManagementIntegrationConnection | Integrations which can receive alerts for the project. |
| `alertManagementPayloadFields` | AlertManagementPayloadAlertField! => Array | Extract alert fields from payload for custom mapping |
| `allowMergeOnSkippedPipeline` | Boolean | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs. |
| `apiFuzzingCiConfiguration` | ApiFuzzingCiConfiguration | API fuzzing configuration for the project. Available only when feature flag `api_fuzzing_configuration_ui` is enabled. |
| `archived` | Boolean | Indicates the archived status of the project. |
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically. |
| `avatarUrl` | String | URL to avatar image file of the project. |
Loading
Loading
@@ -4565,6 +4585,15 @@ Alert status values.
| `RESOLVED` | Resolved status |
| `TRIGGERED` | Triggered status |
 
### ApiFuzzingScanMode
All possible ways to specify the API surface for an API fuzzing scan.
| Value | Description |
| ----- | ----------- |
| `HAR` | The API surface is specified by a HAR file. |
| `OPENAPI` | The API surface is specified by a OPENAPI file. |
### AvailabilityEnum
 
User availability status.
Loading
Loading
Loading
Loading
@@ -127,6 +127,23 @@ module ProjectType
null: true,
description: 'Incident Management On-call schedules of the project',
resolver: ::Resolvers::IncidentManagement::OncallScheduleResolver
field :api_fuzzing_ci_configuration,
::Types::CiConfiguration::ApiFuzzingType,
null: true,
description: 'API fuzzing configuration for the project.',
feature_flag: :api_fuzzing_configuration_ui
end
def api_fuzzing_ci_configuration
return unless Ability.allowed?(current_user, :read_vulnerability, object)
configuration = ::Security::ApiFuzzing::CiConfiguration.new(project: object)
{
scan_modes: ::Security::ApiFuzzing::CiConfiguration::SCAN_MODES,
scan_profiles: configuration.scan_profiles
}
end
 
def dast_profiles
Loading
Loading
# frozen_string_literal: true
module Types
module CiConfiguration
module ApiFuzzing
class ScanModeEnum < BaseEnum
graphql_name 'ApiFuzzingScanMode'
description 'All possible ways to specify the API surface for an API fuzzing scan'
::Security::ApiFuzzing::CiConfiguration::SCAN_MODES.each do |mode|
value mode.upcase, value: mode, description: "The API surface is specified by a #{mode.upcase} file."
end
end
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
module ApiFuzzing
# rubocop: disable Graphql/AuthorizeTypes
class ScanProfileType < BaseObject
graphql_name 'ApiFuzzingScanProfile'
description 'An API Fuzzing scan profile.'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'The unique name of the profile.'
field :description, GraphQL::STRING_TYPE, null: true,
description: 'A short description of the profile.'
field :yaml, GraphQL::STRING_TYPE, null: true,
description: 'A syntax highlit HTML representation of the YAML.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
# rubocop: disable Graphql/AuthorizeTypes
class ApiFuzzingType < BaseObject
graphql_name 'ApiFuzzingCiConfiguration'
description 'Data associated with configuring API fuzzing scans in GitLab CI'
field :scan_modes, [ApiFuzzing::ScanModeEnum], null: true,
description: 'All available scan modes.'
field :scan_profiles, [ApiFuzzing::ScanProfileType], null: true,
description: 'All default scan profiles.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
# frozen_string_literal: true
module Security
module ApiFuzzing
class CiConfiguration
PROFILES_DEFINITION_FILE = 'https://gitlab.com/gitlab-org/security-products/analyzers' \
'/api-fuzzing/-/raw/master/gitlab-api-fuzzing-config.yml'
SCAN_MODES = [:har, :openapi].freeze
def initialize(project:)
@project = project
end
def scan_profiles
fetch_scan_profiles.map do |profile|
next unless ScanProfile::NAMES.include?(profile[:Name])
ScanProfile.new(
name: profile[:Name],
project: project,
yaml: profile.deep_stringify_keys.to_yaml
)
end.compact
end
private
attr_reader :project
def fetch_scan_profiles
response = Gitlab::HTTP.try_get(PROFILES_DEFINITION_FILE)
if response && response.code.to_i < 300
content = Gitlab::Config::Loader::Yaml.new(response.to_s).load!
content.fetch(:Profiles, [])
else
[]
end
end
end
end
end
# frozen_string_literal: true
module Security
module ApiFuzzing
class ScanProfile
NAMES = %w(Quick-10 Medium-20 Medium-50 Long-100).freeze
DESCRIPTIONS = {
'Quick-10' => 'Fuzzing 10 times per parameter',
'Medium-20' => 'Fuzzing 20 times per parameter',
'Medium-50' => 'Fuzzing 50 times per parameter',
'Long-100' => 'Fuzzing 100 times per parameter'
}.freeze
attr_reader :description, :name, :project, :yaml
def initialize(name:, project:, yaml:)
@description = DESCRIPTIONS[name]
@name = name
@project = project
@yaml = yaml
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ApiFuzzingScanMode'] do
it 'exposes all API fuzzing scan modes' do
expect(described_class.values.keys).to match_array(%w[HAR OPENAPI])
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ApiFuzzingScanProfile'] do
let_it_be(:fields) { %i[name description yaml] }
it { expect(described_class).to have_graphql_fields(fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ApiFuzzingCiConfiguration'] do
let_it_be(:fields) { %i[scanModes scanProfiles] }
it { expect(described_class).to have_graphql_fields(fields) }
end
Loading
Loading
@@ -20,7 +20,7 @@
vulnerabilities vulnerability_scanners requirement_states_count
vulnerability_severities_count packages compliance_frameworks vulnerabilities_count_by_day
security_dashboard_path iterations cluster_agents repository_size_excess actual_repository_size_limit
code_coverage_summary
code_coverage_summary api_fuzzing_ci_configuration
]
 
expect(described_class).to include_graphql_fields(*expected_fields)
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::ApiFuzzing::CiConfiguration do
include StubRequests
describe '#scan_profiles' do
context 'when the request finishes successfully' do
it 'returns all scan profiles' do
profiles_yaml = YAML.dump(Profiles: [{ Name: 'Quick-10' }])
stub_full_request(
::Security::ApiFuzzing::CiConfiguration::PROFILES_DEFINITION_FILE
).to_return(body: profiles_yaml)
profiles = described_class.new(project: double(Project)).scan_profiles
expect(profiles.first.name).to eq('Quick-10')
end
context 'when the response includes unknown scan profiles' do
it 'excludes them from the returned profiles' do
profiles_yaml = YAML.dump(Profiles: [{ Name: 'UNKNOWN!' }])
stub_full_request(
::Security::ApiFuzzing::CiConfiguration::PROFILES_DEFINITION_FILE
).to_return(body: profiles_yaml)
profiles = described_class.new(project: double(Project)).scan_profiles
expect(profiles).to be_empty
end
end
end
context 'when the request errors' do
it 'returns an empty array' do
allow(Gitlab::HTTP).to receive(:try_get)
profiles = described_class.new(project: double(Project)).scan_profiles
expect(profiles).to be_empty
end
end
context 'when the request returns an unsuccessful status code' do
it 'returns an empty array' do
stub_full_request(
::Security::ApiFuzzing::CiConfiguration::PROFILES_DEFINITION_FILE
).to_return(status: [500, 'everything is broken'])
profiles = described_class.new(project: double(Project)).scan_profiles
expect(profiles).to be_empty
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project(fullPath).apiFuzzingCiConfiguration' do
include GraphqlHelpers
include StubRequests
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
apiFuzzingCiConfiguration {
scanModes
scanProfiles {
name
description
yaml
}
}
}
}
)
end
let_it_be(:profiles_yaml) do
YAML.dump(
Profiles: [
{ Name: 'Quick-10' }
]
)
end
before do
project.add_developer(user)
stub_full_request(
::Security::ApiFuzzing::CiConfiguration::PROFILES_DEFINITION_FILE
).to_return(body: profiles_yaml)
end
context 'when the api_fuzzing_configuration_ui feature flag is enabled' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: true)
end
context 'when the user can read vulnerabilities for the project' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'returns scan modes and scan profiles' do
post_graphql(query, current_user: user)
expect(response).to have_gitlab_http_status(:ok)
fuzzing_config = graphql_data.dig('project', 'apiFuzzingCiConfiguration')
modes = fuzzing_config['scanModes']
profiles = fuzzing_config['scanProfiles']
expect(modes).to contain_exactly('HAR', 'OPENAPI')
expect(profiles).to contain_exactly({
'name' => 'Quick-10',
'description' => 'Fuzzing 10 times per parameter',
'yaml' => "---\nName: Quick-10\n"
})
end
end
context 'when the user cannot read vulnerabilities for the project' do
before do
stub_licensed_features(security_dashboard: false)
end
it 'returns null' do
post_graphql(query, current_user: user)
expect(response).to have_gitlab_http_status(:ok)
fuzzing_config = graphql_data.dig('project', 'apiFuzzingCiConfiguration')
expect(fuzzing_config).to be_nil
end
end
end
context 'when the api_fuzzing_configuration_ui feature flag is disabled' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: false)
end
it 'errors' do
post_graphql(query, current_user: user)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_errors.first['message']).to eq(
"Field 'apiFuzzingCiConfiguration' doesn't exist on type 'Project'"
)
end
end
end
Loading
Loading
@@ -15,7 +15,7 @@
end
 
let(:depth) { 3 }
let(:excluded) { ['metadata'] }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration] }
 
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment