Commit e2fe6aec authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'sast-ci-config' into 'master'

Implement GraphQL query to generate JSON for SAST config UI

See merge request gitlab-org/gitlab!35397
parents de430fa1 f1b7c692
# frozen_string_literal: true
require "json"
module Resolvers
module CiConfiguration
class SastResolver < BaseResolver
SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json'
type ::Types::CiConfiguration::Sast::Type, null: true
def resolve(**args)
Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH)))
end
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class AnalyzersEntityType < BaseObject
graphql_name 'SastCiConfigurationAnalyzersEntity'
description 'Represents an analyzer entity in SAST CI configuration'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the analyzer.'
field :label, GraphQL::STRING_TYPE, null: true,
description: 'Analyzer label used in the config UI.'
field :enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates whether an analyzer is enabled.'
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Analyzer description that is displayed on the form.'
end
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class EntityType < BaseObject
graphql_name 'SastCiConfigurationEntity'
description 'Represents an entity in SAST CI configuration'
field :field, GraphQL::STRING_TYPE, null: true,
description: 'CI keyword of entity.'
field :label, GraphQL::STRING_TYPE, null: true,
description: 'Label for entity used in the form.'
field :type, GraphQL::STRING_TYPE, null: true,
description: 'Type of the field value.'
field :options, ::Types::CiConfiguration::Sast::OptionsEntityType.connection_type, null: true,
description: 'Different possible values of the field.'
field :default_value, GraphQL::STRING_TYPE, null: true,
description: 'Default value that is used if value is empty.'
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Entity description that is displayed on the form.'
field :value, GraphQL::STRING_TYPE, null: true,
description: 'Current value of the entity.'
end
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class OptionsEntityType < BaseObject
graphql_name 'SastCiConfigurationOptionsEntity'
description 'Represents an entity for options in SAST CI configuration'
field :label, GraphQL::STRING_TYPE, null: true,
description: 'Label of option entity.'
field :value, GraphQL::STRING_TYPE, null: true,
description: 'Value of option entity.'
end
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class Type < BaseObject
graphql_name 'SastCiConfiguration'
description 'Represents a CI configuration of SAST'
field :global, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
description: 'List of global entities related to SAST configuration.'
field :pipeline, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true,
description: 'List of pipeline entities related to SAST configuration.'
field :analyzers, ::Types::CiConfiguration::Sast::AnalyzersEntityType.connection_type, null: true,
description: 'List of analyzers entities attached to SAST configuration.'
end
end
end
end
...@@ -153,6 +153,10 @@ module Types ...@@ -153,6 +153,10 @@ module Types
description: 'Environments of the project', description: 'Environments of the project',
resolver: Resolvers::EnvironmentsResolver resolver: Resolvers::EnvironmentsResolver
field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true,
description: 'SAST CI configuration for the project',
resolver: ::Resolvers::CiConfiguration::SastResolver
field :issue, field :issue,
Types::IssueType, Types::IssueType,
null: true, null: true,
......
{
"global": [
{
"field" : "SECURE_ANALYZERS_PREFIX",
"label" : "Image prefix",
"type": "string",
"default_value": "registry.gitlab.com/gitlab-org/security-products/analyzers",
"value": ""
},
{
"field" : "SAST_EXCLUDED_PATHS",
"label" : "Excluded Paths",
"type": "string",
"default_value": "spec, test, tests, tmp",
"value": ""
},
{
"field" : "SECURE_ANALYZER_IMAGE_TAG",
"label" : "Image tag",
"type": "string",
"options": [],
"default_value": "2",
"value": ""
},
{
"field" : "SAST_DISABLED",
"label" : "Disable SAST",
"type": "options",
"options": [
{
"value" :"true",
"label" : "true (disables SAST)"
},
{
"value":"false",
"label":"false (enables SAST)"
}
],
"default_value": "false",
"value": ""
}
],
"pipeline": [
{
"field" : "stage",
"label" : "Stage",
"type": "dropdown",
"options": [
{
"value" :"test",
"label" : "test"
},
{
"value":"build",
"label":"build"
}
],
"default_value": "test",
"value": ""
},
{
"field" : "allow_failure",
"label" : "Allow Failure",
"type": "options",
"options": [
{
"value" :"true",
"label" : "Allows pipeline failure"
},
{
"value": "false",
"label": "Does not allow pipeline failure"
}
],
"default_value": "true",
"value": ""
},
{
"field" : "rules",
"label" : "Rules",
"type": "multiline",
"default_value": "",
"value": ""
}
],
"analyzers": [
{
"name": "brakeman",
"label": "Brakeman",
"enabled" : true
},
{
"name": "bandit",
"label": "Bandit",
"enabled" : true
},
{
"name": "eslint",
"label": "ESLint",
"enabled" : true
},
{
"name": "flawfinder",
"label": "Flawfinder",
"enabled" : true
},
{
"name": "kubesec",
"label": "kubesec",
"enabled" : true
},
{
"name": "nodejsscan",
"label": "Node.js Scan",
"enabled" : true
},
{
"name": "gosec",
"label": "Golang Security Checker",
"enabled" : true
},
{
"name": "phpcs-security-audit",
"label": "PHP Security Audit",
"enabled" : true
},
{
"name": "pmd-apex",
"label": "PMD APEX",
"enabled" : true
},
{
"name": "security-code-scan",
"label": "Security Code Scan",
"enabled" : true
},
{
"name": "sobelow",
"label": "Sobelow",
"enabled" : true
},
{
"name": "spotbugs",
"label": "Spotbugs",
"enabled" : true
},
{
"name": "tslint",
"label": "TSLint",
"enabled" : true
},
{
"name": "secrets",
"label": "Secrets",
"enabled" : true
}
]
}
---
title: Implement GraphQL query to generate JSON for SAST config UI
merge_request: 35397
author:
type: added
...@@ -9636,6 +9636,11 @@ type Project { ...@@ -9636,6 +9636,11 @@ type Project {
state: RequirementState state: RequirementState
): RequirementConnection ): RequirementConnection
"""
SAST CI configuration for the project
"""
sastCiConfiguration: SastCiConfiguration
""" """
Detailed version of a Sentry error on the project Detailed version of a Sentry error on the project
""" """
...@@ -11390,6 +11395,291 @@ type RunDASTScanPayload { ...@@ -11390,6 +11395,291 @@ type RunDASTScanPayload {
pipelineUrl: String pipelineUrl: String
} }
"""
Represents a CI configuration of SAST
"""
type SastCiConfiguration {
"""
List of analyzers entities attached to SAST configuration.
"""
analyzers(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): SastCiConfigurationAnalyzersEntityConnection
"""
List of global entities related to SAST configuration.
"""
global(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): SastCiConfigurationEntityConnection
"""
List of pipeline entities related to SAST configuration.
"""
pipeline(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): SastCiConfigurationEntityConnection
}
"""
Represents an analyzer entity in SAST CI configuration
"""
type SastCiConfigurationAnalyzersEntity {
"""
Analyzer description that is displayed on the form.
"""
description: String
"""
Indicates whether an analyzer is enabled.
"""
enabled: Boolean
"""
Analyzer label used in the config UI.
"""
label: String
"""
Name of the analyzer.
"""
name: String
}
"""
The connection type for SastCiConfigurationAnalyzersEntity.
"""
type SastCiConfigurationAnalyzersEntityConnection {
"""
A list of edges.
"""
edges: [SastCiConfigurationAnalyzersEntityEdge]
"""
A list of nodes.
"""
nodes: [SastCiConfigurationAnalyzersEntity]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type SastCiConfigurationAnalyzersEntityEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: SastCiConfigurationAnalyzersEntity
}
"""
Represents an entity in SAST CI configuration
"""
type SastCiConfigurationEntity {
"""
Default value that is used if value is empty.
"""
defaultValue: String
"""
Entity description that is displayed on the form.
"""
description: String
"""
CI keyword of entity.
"""
field: String
"""
Label for entity used in the form.
"""
label: String
"""
Different possible values of the field.
"""
options(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): SastCiConfigurationOptionsEntityConnection
"""
Type of the field value.
"""
type: String
"""
Current value of the entity.
"""
value: String
}
"""
The connection type for SastCiConfigurationEntity.
"""
type SastCiConfigurationEntityConnection {
"""
A list of edges.
"""
edges: [SastCiConfigurationEntityEdge]
"""
A list of nodes.
"""
nodes: [SastCiConfigurationEntity]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type SastCiConfigurationEntityEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: SastCiConfigurationEntity
}
"""
Represents an entity for options in SAST CI configuration
"""
type SastCiConfigurationOptionsEntity {
"""
Label of option entity.
"""
label: String
"""
Value of option entity.
"""
value: String
}
"""
The connection type for SastCiConfigurationOptionsEntity.
"""
type SastCiConfigurationOptionsEntityConnection {
"""
A list of edges.
"""
edges: [SastCiConfigurationOptionsEntityEdge]
"""
A list of nodes.
"""
nodes: [SastCiConfigurationOptionsEntity]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type SastCiConfigurationOptionsEntityEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: SastCiConfigurationOptionsEntity
}
""" """
Represents a resource scanned by a security scan Represents a resource scanned by a security scan
""" """
......
...@@ -1391,6 +1391,7 @@ Information about pagination in a connection. ...@@ -1391,6 +1391,7 @@ Information about pagination in a connection.
| `requestAccessEnabled` | Boolean | Indicates if users can request member access to the project | | `requestAccessEnabled` | Boolean | Indicates if users can request member access to the project |
| `requirement` | Requirement | Find a single requirement. Available only when feature flag `requirements_management` is enabled. | | `requirement` | Requirement | Find a single requirement. Available only when feature flag `requirements_management` is enabled. |
| `requirementStatesCount` | RequirementStatesCount | Number of requirements for the project by their state | | `requirementStatesCount` | RequirementStatesCount | Number of requirements for the project by their state |
| `sastCiConfiguration` | SastCiConfiguration | SAST CI configuration for the project |
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project | | `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `sentryErrors` | SentryErrorCollection | Paginated collection of Sentry errors on the project | | `sentryErrors` | SentryErrorCollection | Paginated collection of Sentry errors on the project |
| `serviceDeskAddress` | String | E-mail address of the service desk. | | `serviceDeskAddress` | String | E-mail address of the service desk. |
...@@ -1638,6 +1639,39 @@ Autogenerated return type of RunDASTScan ...@@ -1638,6 +1639,39 @@ Autogenerated return type of RunDASTScan
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `pipelineUrl` | String | URL of the pipeline that was created. | | `pipelineUrl` | String | URL of the pipeline that was created. |
## SastCiConfigurationAnalyzersEntity
Represents an analyzer entity in SAST CI configuration
| Name | Type | Description |
| --- | ---- | ---------- |
| `description` | String | Analyzer description that is displayed on the form. |
| `enabled` | Boolean | Indicates whether an analyzer is enabled. |
| `label` | String | Analyzer label used in the config UI. |
| `name` | String | Name of the analyzer. |
## SastCiConfigurationEntity
Represents an entity in SAST CI configuration
| Name | Type | Description |
| --- | ---- | ---------- |
| `defaultValue` | String | Default value that is used if value is empty. |
| `description` | String | Entity description that is displayed on the form. |
| `field` | String | CI keyword of entity. |
| `label` | String | Label for entity used in the form. |
| `type` | String | Type of the field value. |
| `value` | String | Current value of the entity. |
## SastCiConfigurationOptionsEntity
Represents an entity for options in SAST CI configuration
| Name | Type | Description |
| --- | ---- | ---------- |
| `label` | String | Label of option entity. |
| `value` | String | Value of option entity. |
## ScannedResource ## ScannedResource
Represents a resource scanned by a security scan Represents a resource scanned by a security scan
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::CiConfiguration::SastResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
describe '#resolve' do
subject(:sast_config) { resolve(described_class, ctx: { current_user: user }, obj: project) }
it 'returns global variable informations related to SAST' do
expect(sast_config['global'].first['field']).to eql("SECURE_ANALYZERS_PREFIX")
expect(sast_config['global'].first['label']).to eql("Image prefix")
expect(sast_config['global'].first['type']).to eql("string")
expect(sast_config['pipeline'].first['field']).to eql("stage")
expect(sast_config['pipeline'].first['label']).to eql("Stage")
expect(sast_config['pipeline'].first['type']).to eql("dropdown")
expect(sast_config['analyzers'].first['name']).to eql("brakeman")
expect(sast_config['analyzers'].first['label']).to eql("Brakeman")
expect(sast_config['analyzers'].first['enabled']).to be true
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationAnalyzersEntity'] do
let(:fields) { %i[name label enabled description] }
it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntity') }
it { expect(described_class).to have_graphql_fields(fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationEntity'] do
let(:fields) { %i[field label description type options default_value value] }
it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntity') }
it { expect(described_class).to have_graphql_fields(fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationOptionsEntity'] do
let(:fields) { %i[label value] }
it { expect(described_class.graphql_name).to eq('SastCiConfigurationOptionsEntity') }
it { expect(described_class).to have_graphql_fields(fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfiguration'] do
let(:fields) { %i[global pipeline analyzers] }
it { expect(described_class.graphql_name).to eq('SastCiConfiguration') }
it { expect(described_class).to have_graphql_fields(fields) }
end
...@@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy container_expiration_policy sast_ci_configuration
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
...@@ -140,5 +140,93 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -140,5 +140,93 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) } it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end end
describe 'sast_ci_configuration' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
sastCiConfiguration {
global {
nodes {
type
options {
nodes {
label
value
}
}
field
label
defaultValue
value
}
}
pipeline {
nodes {
type
options {
nodes {
label
value
}
}
field
label
defaultValue
value
}
}
analyzers {
nodes {
name
label
enabled
}
}
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before do
project.add_developer(user)
end
it "returns the project's sast configuration for global variables" do
query_result = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes')
first_config = query_result.first
fourth_config = query_result[3]
expect(first_config['type']).to eq('string')
expect(first_config['field']).to eq('SECURE_ANALYZERS_PREFIX')
expect(first_config['label']).to eq('Image prefix')
expect(first_config['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(first_config['value']).to eq('')
expect(first_config['options']).to be_nil
expect(fourth_config['options']['nodes']).to match([{ "value" => "true", "label" => "true (disables SAST)" },
{ "value" => "false", "label" => "false (enables SAST)" }])
end
it "returns the project's sast configuration for pipeline variables" do
configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first
expect(configuration['type']).to eq('dropdown')
expect(configuration['field']).to eq('stage')
expect(configuration['label']).to eq('Stage')
expect(configuration['defaultValue']).to eq('test')
expect(configuration['value']).to eq('')
end
it "returns the project's sast configuration for analyzer variables" do
configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first
expect(configuration['name']).to eq('brakeman')
expect(configuration['label']).to eq('Brakeman')
expect(configuration['enabled']).to eq(true)
end
end
it_behaves_like 'a GraphQL type with labels' it_behaves_like 'a GraphQL type with labels'
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment