diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8c37af87d0d..5ad3363b080 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0460209dab07b05be09be6fc3d050211fdf4ded5 +0da32707243b9c339f75cd5a1c03b00c88502f86 diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 8edc2e732e0..4b5ce02676c 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -17,6 +17,7 @@ class Projects::PipelinesController < Projects::ApplicationController push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, project, type: :development, default_enabled: :yaml) + push_frontend_feature_flag(:jira_for_vulnerabilities, project, type: :development, default_enabled: :yaml) end before_action :ensure_pipeline, only: [:show] before_action :push_experiment_to_gon, only: :index, if: :html_request? diff --git a/config/feature_flags/development/kubernetes_agent_on_gitlab_com.yml b/config/feature_flags/development/kubernetes_agent_on_gitlab_com.yml new file mode 100644 index 00000000000..fac509224e0 --- /dev/null +++ b/config/feature_flags/development/kubernetes_agent_on_gitlab_com.yml @@ -0,0 +1,8 @@ +--- +name: kubernetes_agent_on_gitlab_com +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53322 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300960 +milestone: '13.9' +type: development +group: group::configure +default_enabled: false diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 3f716767748..c3120d8417d 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -5675,6 +5675,71 @@ type DastProfileConnection { pageInfo: PageInfo! } +""" +Autogenerated input type of DastProfileCreate +""" +input DastProfileCreateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the scanner profile to be associated. + """ + dastScannerProfileId: DastScannerProfileID! + + """ + ID of the site profile to be associated. + """ + dastSiteProfileId: DastSiteProfileID! + + """ + The description of the profile. Defaults to an empty string. + """ + description: String = "" + + """ + The project the profile belongs to. + """ + fullPath: ID! + + """ + The name of the profile. + """ + name: String! + + """ + Run scan using profile after creation. Defaults to false. + """ + runAfterCreate: Boolean = false +} + +""" +Autogenerated return type of DastProfileCreate +""" +type DastProfileCreatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created profile. + """ + dastProfile: DastProfile + + """ + Errors encountered during execution of the mutation. + """ + errors: [String!]! + + """ + The URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`. + """ + pipelineUrl: String! +} + """ An edge in a connection. """ @@ -16139,6 +16204,7 @@ type Mutation { createSnippet(input: CreateSnippetInput!): CreateSnippetPayload createTestCase(input: CreateTestCaseInput!): CreateTestCasePayload dastOnDemandScanCreate(input: DastOnDemandScanCreateInput!): DastOnDemandScanCreatePayload + dastProfileCreate(input: DastProfileCreateInput!): DastProfileCreatePayload dastScannerProfileCreate(input: DastScannerProfileCreateInput!): DastScannerProfileCreatePayload dastScannerProfileDelete(input: DastScannerProfileDeleteInput!): DastScannerProfileDeletePayload dastScannerProfileUpdate(input: DastScannerProfileUpdateInput!): DastScannerProfileUpdatePayload diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 215ae78d5ff..47855e1cf98 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -15505,6 +15505,188 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "DastProfileCreateInput", + "description": "Autogenerated input type of DastProfileCreate", + "fields": null, + "inputFields": [ + { + "name": "fullPath", + "description": "The project the profile belongs to.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "name", + "description": "The name of the profile.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "description", + "description": "The description of the profile. Defaults to an empty string.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"\"" + }, + { + "name": "dastSiteProfileId", + "description": "ID of the site profile to be associated.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DastSiteProfileID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "dastScannerProfileId", + "description": "ID of the scanner profile to be associated.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DastScannerProfileID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "runAfterCreate", + "description": "Run scan using profile after creation. Defaults to false.", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DastProfileCreatePayload", + "description": "Autogenerated return type of DastProfileCreate", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dastProfile", + "description": "The created profile.", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "DastProfile", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "errors", + "description": "Errors encountered during execution of the mutation.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pipelineUrl", + "description": "The URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "DastProfileEdge", @@ -45170,6 +45352,33 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "dastProfileCreate", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "DastProfileCreateInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "DastProfileCreatePayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "dastScannerProfileCreate", "description": null, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index bb0baa2eeee..1cf8371f35c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -905,6 +905,17 @@ Represents a DAST Profile. | `id` | DastProfileID! | ID of the profile. | | `name` | String | The name of the profile. | +### DastProfileCreatePayload + +Autogenerated return type of DastProfileCreate. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `dastProfile` | DastProfile | The created profile. | +| `errors` | String! => Array | Errors encountered during execution of the mutation. | +| `pipelineUrl` | String! | The URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`. | + ### DastScannerProfile Represents a DAST scanner profile. diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 73723a96401..87ad79d601f 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -52,6 +52,8 @@ module API def check_agent_token forbidden! unless agent_token + + forbidden! unless Gitlab::Kas.included_in_gitlab_com_rollout?(agent.project) end end diff --git a/lib/gitlab/kas.rb b/lib/gitlab/kas.rb index 08dde98e965..329c0f221b5 100644 --- a/lib/gitlab/kas.rb +++ b/lib/gitlab/kas.rb @@ -23,6 +23,12 @@ module Gitlab write_secret end + + def included_in_gitlab_com_rollout?(project) + return true unless ::Gitlab.com? + + Feature.enabled?(:kubernetes_agent_on_gitlab_com, project) + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8277195c1d1..7d2fb3e0788 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6051,6 +6051,9 @@ msgstr "" msgid "ClusterAgent|This feature is only available for premium plans" msgstr "" +msgid "ClusterAgent|This project is not included in the GitLab.com rollout for Kubernetes agent" +msgstr "" + msgid "ClusterAgent|User has insufficient permissions to create a token for this project" msgstr "" @@ -25802,6 +25805,9 @@ msgstr "" msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'" msgstr "" +msgid "SecurityReports|Create Jira issue" +msgstr "" + msgid "SecurityReports|Create issue" msgstr "" diff --git a/spec/lib/gitlab/kas_spec.rb b/spec/lib/gitlab/kas_spec.rb index ce22f36e9fd..01ced407883 100644 --- a/spec/lib/gitlab/kas_spec.rb +++ b/spec/lib/gitlab/kas_spec.rb @@ -58,4 +58,48 @@ RSpec.describe Gitlab::Kas do end end end + + describe '.included_in_gitlab_com_rollout?' do + let_it_be(:project) { create(:project) } + + context 'not GitLab.com' do + before do + allow(Gitlab).to receive(:com?).and_return(false) + end + + it 'returns true' do + expect(described_class.included_in_gitlab_com_rollout?(project)).to be_truthy + end + end + + context 'GitLab.com' do + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + context 'kubernetes_agent_on_gitlab_com feature flag disabled' do + before do + stub_feature_flags(kubernetes_agent_on_gitlab_com: false) + end + + it 'returns false' do + expect(described_class.included_in_gitlab_com_rollout?(project)).to be_falsey + end + end + + context 'kubernetes_agent_on_gitlab_com feature flag enabled' do + before do + stub_feature_flags(kubernetes_agent_on_gitlab_com: project) + end + + it 'returns true' do + expect(described_class.included_in_gitlab_com_rollout?(project)).to be_truthy + end + + it 'returns false for another project' do + expect(described_class.included_in_gitlab_com_rollout?(create(:project))).to be_falsey + end + end + end + end end diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index afff3647b91..2e13016a0a6 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -62,25 +62,25 @@ RSpec.describe API::Internal::Kubernetes do let!(:agent_token) { create(:cluster_agent_token) } it 'returns no_content for valid gitops_sync_count' do - send_request(params: { gitops_sync_count: 10 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + send_request(params: { gitops_sync_count: 10 }) expect(response).to have_gitlab_http_status(:no_content) end it 'returns no_content 0 gitops_sync_count' do - send_request(params: { gitops_sync_count: 0 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + send_request(params: { gitops_sync_count: 0 }) expect(response).to have_gitlab_http_status(:no_content) end it 'returns 400 for non number' do - send_request(params: { gitops_sync_count: 'string' }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + send_request(params: { gitops_sync_count: 'string' }) expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 for negative number' do - send_request(params: { gitops_sync_count: '-1' }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + send_request(params: { gitops_sync_count: '-1' }) expect(response).to have_gitlab_http_status(:bad_request) end @@ -125,6 +125,36 @@ RSpec.describe API::Internal::Kubernetes do ) ) end + + context 'on GitLab.com' do + before do + allow(::Gitlab).to receive(:com?).and_return(true) + end + + context 'kubernetes_agent_on_gitlab_com feature flag disabled' do + before do + stub_feature_flags(kubernetes_agent_on_gitlab_com: false) + end + + it 'returns 403' do + send_request(headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'kubernetes_agent_on_gitlab_com feature flag enabled' do + before do + stub_feature_flags(kubernetes_agent_on_gitlab_com: agent_token.agent.project) + end + + it 'returns success' do + send_request(headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + + expect(response).to have_gitlab_http_status(:success) + end + end + end end end @@ -174,6 +204,36 @@ RSpec.describe API::Internal::Kubernetes do expect(response).to have_gitlab_http_status(:not_found) end end + + context 'on GitLab.com' do + before do + allow(::Gitlab).to receive(:com?).and_return(true) + end + + context 'kubernetes_agent_on_gitlab_com feature flag disabled' do + before do + stub_feature_flags(kubernetes_agent_on_gitlab_com: false) + end + + it 'returns 403' do + send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'kubernetes_agent_on_gitlab_com feature flag enabled' do + before do + stub_feature_flags(kubernetes_agent_on_gitlab_com: agent_token.agent.project) + end + + it 'returns success' do + send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + + expect(response).to have_gitlab_http_status(:success) + end + end + end end context 'project is private' do