From a935c010282034711d6bace0f5675ebc75930860 Mon Sep 17 00:00:00 2001 From: huzaifaiftikhar1 Date: Mon, 20 Nov 2023 20:52:06 +0530 Subject: [PATCH] Add API for creating instance_amazon_s3_configuration for audit events Changelog: added EE: true --- .../graphql_shared/possible_types.json | 3 +- .../audit_event_types.md | 1 + doc/api/graphql/reference/index.md | 38 +++++ ee/app/graphql/ee/types/mutation_type.rb | 1 + .../instance/amazon_s3_configurations/base.rb | 34 +++++ .../amazon_s3_configurations/create.rb | 57 ++++++++ .../instance/amazon_s3_configuration_type.rb | 15 ++ .../amazon_s3_configuration_policy.rb | 9 ++ ...stance_amazon_s3_configuration_created.yml | 9 ++ .../amazon_s3_configurations/create_spec.rb | 130 ++++++++++++++++++ 10 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/base.rb create mode 100644 ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/create.rb create mode 100644 ee/app/graphql/types/audit_events/instance/amazon_s3_configuration_type.rb create mode 100644 ee/app/policies/audit_events/instance/amazon_s3_configuration_policy.rb create mode 100644 ee/config/audit_events/types/instance_amazon_s3_configuration_created.yml create mode 100644 ee/spec/requests/api/graphql/mutations/audit_events/instance/amazon_s3_configurations/create_spec.rb diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 4ef0d06703086e..dbdadf371ca24a 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -4,7 +4,8 @@ "AlertManagementPrometheusIntegration" ], "AmazonS3ConfigurationInterface": [ - "AmazonS3ConfigurationType" + "AmazonS3ConfigurationType", + "InstanceAmazonS3ConfigurationType" ], "BaseHeaderInterface": [ "AuditEventStreamingHeader", diff --git a/doc/administration/audit_event_streaming/audit_event_types.md b/doc/administration/audit_event_streaming/audit_event_types.md index e0caad66fcabce..fa89074ad53faf 100644 --- a/doc/administration/audit_event_streaming/audit_event_types.md +++ b/doc/administration/audit_event_streaming/audit_event_types.md @@ -55,6 +55,7 @@ Audit event types belong to the following product categories. | [`google_cloud_logging_configuration_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122025) | Triggered when Google Cloud Logging configuration is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) | | [`google_cloud_logging_configuration_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122025) | Triggered when Google Cloud Logging configuration is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) | | [`google_cloud_logging_configuration_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122025) | Triggered when Google Cloud Logging configuration is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) | +| [`instance_amazon_s3_configuration_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137651) | Triggered when instance Amazon S3 configuration for audit events streaming is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/423235) | | [`instance_google_cloud_logging_configuration_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130663) | Triggered when Instance level Google Cloud Logging configuration is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/423038) | | [`instance_google_cloud_logging_configuration_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131752) | Triggered when instance level Google Cloud Logging configuration is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/423040) | | [`instance_google_cloud_logging_configuration_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131790) | Triggered when instance level Google Cloud Logging configuration is updated.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/423039) | diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 714b9b8395f37b..c826994474326d 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1472,6 +1472,29 @@ Input type: `AuditEventsAmazonS3ConfigurationUpdateInput` | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.auditEventsInstanceAmazonS3ConfigurationCreate` + +Input type: `AuditEventsInstanceAmazonS3ConfigurationCreateInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. | +| `awsRegion` | [`String!`](#string) | AWS region where the bucket is created. | +| `bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `name` | [`String`](#string) | Destination name. | +| `secretAccessKey` | [`String!`](#string) | Secret access key of the Amazon S3 account. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| `instanceAmazonS3Configuration` | [`InstanceAmazonS3ConfigurationType`](#instanceamazons3configurationtype) | Created instance Amazon S3 configuration. | + ### `Mutation.auditEventsStreamingDestinationEventsAdd` Input type: `AuditEventsStreamingDestinationEventsAddInput` @@ -20317,6 +20340,20 @@ CI/CD variables a project inherites from its parent group and ancestors. | `raw` | [`Boolean`](#boolean) | Indicates whether the variable is raw. | | `variableType` | [`CiVariableType`](#civariabletype) | Type of the variable. | +### `InstanceAmazonS3ConfigurationType` + +Stores instance level Amazon S3 configurations for audit event streaming. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. | +| `awsRegion` | [`String!`](#string) | AWS region where the bucket is created. | +| `bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. | +| `id` | [`ID!`](#id) | ID of the configuration. | +| `name` | [`String!`](#string) | Name of the external destination to send audit events to. | + ### `InstanceExternalAuditEventDestination` Represents an external resource to send instance audit events to. @@ -32377,6 +32414,7 @@ Implementations: Implementations: - [`AmazonS3ConfigurationType`](#amazons3configurationtype) +- [`InstanceAmazonS3ConfigurationType`](#instanceamazons3configurationtype) ##### Fields diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb index 4ad78418af0d84..b5288431e0a272 100644 --- a/ee/app/graphql/ee/types/mutation_type.rb +++ b/ee/app/graphql/ee/types/mutation_type.rb @@ -130,6 +130,7 @@ module MutationType mount_mutation ::Mutations::AuditEvents::AmazonS3Configurations::Create mount_mutation ::Mutations::AuditEvents::AmazonS3Configurations::Delete mount_mutation ::Mutations::AuditEvents::AmazonS3Configurations::Update + mount_mutation ::Mutations::AuditEvents::Instance::AmazonS3Configurations::Create mount_mutation ::Mutations::AuditEvents::Instance::GoogleCloudLoggingConfigurations::Create mount_mutation ::Mutations::Forecasting::BuildForecast, alpha: { milestone: '16.0' } mount_mutation ::Mutations::AuditEvents::Streaming::InstanceHeaders::Create diff --git a/ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/base.rb b/ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/base.rb new file mode 100644 index 00000000000000..702aa485fd740b --- /dev/null +++ b/ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/base.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Mutations + module AuditEvents + module Instance + module AmazonS3Configurations + class Base < BaseMutation + authorize :admin_instance_external_audit_events + + def ready?(**args) + raise_resource_not_available_error! unless current_user&.can?(:admin_instance_external_audit_events) + + super + end + + private + + def audit(config, action:) + audit_context = { + name: "instance_amazon_s3_configuration_#{action}", + author: current_user, + scope: Gitlab::Audit::InstanceScope.new, + target: config, + message: "#{action.capitalize} Instance Amazon S3 configuration with name: #{config.name} " \ + "bucket: #{config.bucket_name} and AWS region: #{config.aws_region}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end + end + end +end diff --git a/ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/create.rb b/ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/create.rb new file mode 100644 index 00000000000000..2534ea8d69193a --- /dev/null +++ b/ee/app/graphql/mutations/audit_events/instance/amazon_s3_configurations/create.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Mutations + module AuditEvents + module Instance + module AmazonS3Configurations + class Create < Base + graphql_name 'AuditEventsInstanceAmazonS3ConfigurationCreate' + + argument :name, GraphQL::Types::String, + required: false, + description: 'Destination name.' + + argument :access_key_xid, GraphQL::Types::String, + required: true, + description: 'Access key ID of the Amazon S3 account.' + + argument :secret_access_key, GraphQL::Types::String, + required: true, + description: 'Secret access key of the Amazon S3 account.' + + argument :bucket_name, GraphQL::Types::String, + required: true, + description: 'Name of the bucket where the audit events would be logged.' + + argument :aws_region, GraphQL::Types::String, + required: true, + description: 'AWS region where the bucket is created.' + + field :instance_amazon_s3_configuration, ::Types::AuditEvents::Instance::AmazonS3ConfigurationType, + null: true, + description: 'Created instance Amazon S3 configuration.' + + def resolve(access_key_xid:, secret_access_key:, bucket_name:, aws_region:, name: nil) + config_attributes = { + access_key_xid: access_key_xid, + secret_access_key: secret_access_key, + bucket_name: bucket_name, + aws_region: aws_region, + name: name + } + + config = ::AuditEvents::Instance::AmazonS3Configuration.new(config_attributes) + + if config.save + audit(config, action: :created) + + { instance_amazon_s3_configuration: config, errors: [] } + else + { instance_amazon_s3_configuration: nil, errors: Array(config.errors) } + end + end + end + end + end + end +end diff --git a/ee/app/graphql/types/audit_events/instance/amazon_s3_configuration_type.rb b/ee/app/graphql/types/audit_events/instance/amazon_s3_configuration_type.rb new file mode 100644 index 00000000000000..517af4c482df0f --- /dev/null +++ b/ee/app/graphql/types/audit_events/instance/amazon_s3_configuration_type.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module AuditEvents + module Instance + class AmazonS3ConfigurationType < ::Types::BaseObject + graphql_name 'InstanceAmazonS3ConfigurationType' + description 'Stores instance level Amazon S3 configurations for audit event streaming.' + authorize :admin_instance_external_audit_events + + implements AmazonS3ConfigurationInterface + end + end + end +end diff --git a/ee/app/policies/audit_events/instance/amazon_s3_configuration_policy.rb b/ee/app/policies/audit_events/instance/amazon_s3_configuration_policy.rb new file mode 100644 index 00000000000000..c83a5997a29a82 --- /dev/null +++ b/ee/app/policies/audit_events/instance/amazon_s3_configuration_policy.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module AuditEvents + module Instance + class AmazonS3ConfigurationPolicy < BasePolicy + delegate { :global } + end + end +end diff --git a/ee/config/audit_events/types/instance_amazon_s3_configuration_created.yml b/ee/config/audit_events/types/instance_amazon_s3_configuration_created.yml new file mode 100644 index 00000000000000..4fc819c53ef962 --- /dev/null +++ b/ee/config/audit_events/types/instance_amazon_s3_configuration_created.yml @@ -0,0 +1,9 @@ +--- +name: instance_amazon_s3_configuration_created +description: Triggered when instance Amazon S3 configuration for audit events streaming is created +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/423235 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137651 +feature_category: audit_events +milestone: '16.7' +saved_to_database: true +streamed: true diff --git a/ee/spec/requests/api/graphql/mutations/audit_events/instance/amazon_s3_configurations/create_spec.rb b/ee/spec/requests/api/graphql/mutations/audit_events/instance/amazon_s3_configurations/create_spec.rb new file mode 100644 index 00000000000000..86746b760d6398 --- /dev/null +++ b/ee/spec/requests/api/graphql/mutations/audit_events/instance/amazon_s3_configurations/create_spec.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Create Instance level Amazon S3 configuration', feature_category: :audit_events do + include GraphqlHelpers + + let_it_be(:current_user) { create(:admin) } + let_it_be(:destination_name) { 'test_aws_s3_destination' } + let_it_be(:access_key_id) { 'AKIARANDOMID1234' } + let_it_be(:secret_access_key) { 'TEST/SECRET/XYZ' } + let_it_be(:bucket_name) { 'test-bucket' } + let_it_be(:aws_region) { 'us-east-1' } + + let(:mutation) { graphql_mutation(:audit_events_instance_amazon_s3_configuration_create, input) } + let(:mutation_response) { graphql_mutation_response(:audit_events_instance_amazon_s3_configuration_create) } + + let(:input) do + { + name: destination_name, + accessKeyXid: access_key_id, + secretAccessKey: secret_access_key, + bucketName: bucket_name, + awsRegion: aws_region + } + end + + subject(:mutate) { post_graphql_mutation(mutation, current_user: current_user) } + + shared_examples 'creates an audit event' do + before do + allow(Gitlab::Audit::Auditor).to receive(:audit) + end + + it 'audits the creation' do + subject + + config = AuditEvents::Instance::AmazonS3Configuration.last + + expect(Gitlab::Audit::Auditor).to have_received(:audit) do |args| + expect(args[:name]).to eq('instance_amazon_s3_configuration_created') + expect(args[:author]).to eq(current_user) + expect(args[:scope]).to be_an_instance_of(Gitlab::Audit::InstanceScope) + expect(args[:target]).to eq(config) + expect(args[:message]) + .to eq("Created Instance Amazon S3 configuration with name: #{destination_name} " \ + "bucket: #{bucket_name} and AWS region: #{aws_region}") + end + end + end + + shared_examples 'a mutation that does not create a configuration' do + it 'does not create the configuration' do + expect { mutate }.not_to change { AuditEvents::Instance::AmazonS3Configuration.count } + end + + it 'does not create audit event' do + expect { mutate }.not_to change { AuditEvent.count } + end + end + + shared_examples 'an unauthorized mutation that does not create a configuration' do + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it_behaves_like 'a mutation that does not create a configuration' + end + + context 'when feature is licensed' do + before do + stub_licensed_features(external_audit_events: true) + end + + context 'when current user is an admin' do + it 'creates the configuration', :aggregate_failures do + expect { mutate }.to change { AuditEvents::Instance::AmazonS3Configuration.count }.by(1) + + config = AuditEvents::Instance::AmazonS3Configuration.last + + expect(config.name).to eq(destination_name) + expect(config.access_key_xid).to eq(access_key_id) + expect(config.secret_access_key).to eq(secret_access_key) + expect(config.bucket_name).to eq(bucket_name) + expect(config.aws_region).to eq(aws_region) + + expect(mutation_response['errors']).to be_empty + expect(mutation_response['instanceAmazonS3Configuration']['accessKeyXid']).to eq(access_key_id) + expect(mutation_response['instanceAmazonS3Configuration']['id']).not_to be_empty + expect(mutation_response['instanceAmazonS3Configuration']['secretAccessKey']).to eq(nil) + expect(mutation_response['instanceAmazonS3Configuration']['bucketName']).to eq(bucket_name) + expect(mutation_response['instanceAmazonS3Configuration']['awsRegion']).to eq(aws_region) + end + + it_behaves_like 'creates an audit event', 'audit_events' + + context 'when there is error while saving' do + before do + allow_next_instance_of(AuditEvents::Instance::AmazonS3Configuration) do |s3_configuration| + allow(s3_configuration).to receive(:save).and_return(false) + errors = ActiveModel::Errors.new(s3_configuration).tap { |e| e.add(:bucket_name, 'invalid name') } + allow(s3_configuration).to receive(:errors).and_return(errors) + end + end + + it 'does not create the configuration and returns the error' do + expect { mutate }.not_to change { AuditEvents::Instance::AmazonS3Configuration.count } + + expect(mutation_response).to include( + 'instanceAmazonS3Configuration' => nil, + 'errors' => ["Bucket name invalid name"] + ) + end + end + end + + context 'when current user is not an admin' do + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'an unauthorized mutation that does not create a configuration' + end + end + + context 'when feature is unlicensed' do + before do + stub_licensed_features(external_audit_events: false) + end + + it_behaves_like 'an unauthorized mutation that does not create a configuration' + end +end -- GitLab