diff --git a/app/actions/buildpack_create.rb b/app/actions/buildpack_create.rb index 387b3344fc0..4dbf2120b1c 100644 --- a/app/actions/buildpack_create.rb +++ b/app/actions/buildpack_create.rb @@ -1,3 +1,5 @@ +require 'repositories/buildpack_event_repository' + module VCAP::CloudController class BuildpackCreate class Error < ::StandardError @@ -7,6 +9,10 @@ class Error < ::StandardError DEFAULT_ENABLED = true DEFAULT_LOCKED = false + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def create(message) Buildpack.db.transaction do Locking[name: 'buildpacks'].lock! @@ -22,6 +28,10 @@ def create(message) MetadataUpdate.update(buildpack, message) buildpack.move_to(message.position || DEFAULT_POSITION) + + Repositories::BuildpackEventRepository.new.record_buildpack_create(buildpack, @user_audit_info, message.audit_hash) + + buildpack end rescue Sequel::ValidationFailed => e validation_error!(e, message) diff --git a/app/actions/buildpack_delete.rb b/app/actions/buildpack_delete.rb index c449feb9b42..e67c1c888e9 100644 --- a/app/actions/buildpack_delete.rb +++ b/app/actions/buildpack_delete.rb @@ -1,9 +1,16 @@ +require 'repositories/buildpack_event_repository' + module VCAP::CloudController class BuildpackDelete + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def delete(buildpacks) buildpacks.each do |buildpack| Buildpack.db.transaction do Locking[name: 'buildpacks'].lock! + Repositories::BuildpackEventRepository.new.record_buildpack_delete(buildpack, @user_audit_info) buildpack.destroy end if buildpack.key diff --git a/app/actions/buildpack_update.rb b/app/actions/buildpack_update.rb index b0b9b8674c6..e668ecd712b 100644 --- a/app/actions/buildpack_update.rb +++ b/app/actions/buildpack_update.rb @@ -1,8 +1,14 @@ +require 'repositories/buildpack_event_repository' + module VCAP::CloudController class BuildpackUpdate class Error < ::StandardError end + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def update(buildpack, message) Buildpack.db.transaction do Locking[name: 'buildpacks'].lock! @@ -15,6 +21,8 @@ def update(buildpack, message) buildpack.locked = message.locked if message.requested?(:locked) buildpack.name = message.name if message.requested?(:name) buildpack.save + + Repositories::BuildpackEventRepository.new.record_buildpack_update(buildpack, @user_audit_info, message.audit_hash) end buildpack rescue Sequel::ValidationFailed => e diff --git a/app/controllers/v3/buildpacks_controller.rb b/app/controllers/v3/buildpacks_controller.rb index 2916970fa59..87ef45097d9 100644 --- a/app/controllers/v3/buildpacks_controller.rb +++ b/app/controllers/v3/buildpacks_controller.rb @@ -35,7 +35,7 @@ def create message = BuildpackCreateMessage.new(hashed_params[:body]) unprocessable!(message.errors.full_messages) unless message.valid? - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) render status: :created, json: Presenters::V3::BuildpackPresenter.new(buildpack) rescue BuildpackCreate::Error => e @@ -51,7 +51,7 @@ def update message = BuildpackUpdateMessage.new(hashed_params[:body]) unprocessable!(message.errors.full_messages) unless message.valid? - buildpack = VCAP::CloudController::BuildpackUpdate.new.update(buildpack, message) + buildpack = VCAP::CloudController::BuildpackUpdate.new(user_audit_info).update(buildpack, message) render status: :ok, json: Presenters::V3::BuildpackPresenter.new(buildpack) rescue BuildpackUpdate::Error => e @@ -64,7 +64,7 @@ def destroy unauthorized! unless permission_queryer.can_write_globally? - delete_action = BuildpackDelete.new + delete_action = BuildpackDelete.new(user_audit_info) deletion_job = VCAP::CloudController::Jobs::DeleteActionJob.new(Buildpack, buildpack.guid, delete_action) pollable_job = Jobs::Enqueuer.new(queue: Jobs::Queues.generic).enqueue_pollable(deletion_job) diff --git a/app/repositories/buildpack_event_repository.rb b/app/repositories/buildpack_event_repository.rb new file mode 100644 index 00000000000..ade759b718a --- /dev/null +++ b/app/repositories/buildpack_event_repository.rb @@ -0,0 +1,62 @@ +require 'repositories/event_types' + +module VCAP::CloudController + module Repositories + class BuildpackEventRepository + def record_buildpack_create(buildpack, user_audit_info, request_attrs) + Event.create( + type: EventTypes::BUILDPACK_CREATE, + actee: buildpack.guid, + actee_type: 'buildpack', + actee_name: buildpack.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: '', + metadata: { + request: request_attrs + } + ) + end + + def record_buildpack_update(buildpack, user_audit_info, request_attrs) + Event.create( + type: EventTypes::BUILDPACK_UPDATE, + actee: buildpack.guid, + actee_type: 'buildpack', + actee_name: buildpack.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: '', + metadata: { + request: request_attrs + } + ) + end + + def record_buildpack_delete(buildpack, user_audit_info) + Event.create( + type: EventTypes::BUILDPACK_DELETE, + actee: buildpack.guid, + actee_type: 'buildpack', + actee_name: buildpack.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: '', + metadata: {} + ) + end + end + end +end diff --git a/app/repositories/event_types.rb b/app/repositories/event_types.rb index a71cbe878ee..e4442f4b7d5 100644 --- a/app/repositories/event_types.rb +++ b/app/repositories/event_types.rb @@ -54,6 +54,10 @@ class EventTypesError < StandardError APP_SSH_AUTHORIZED = 'audit.app.ssh-authorized'.freeze, APP_SSH_UNAUTHORIZED = 'audit.app.ssh-unauthorized'.freeze, + BUILDPACK_CREATE = 'audit.buildpack.create'.freeze, + BUILDPACK_UPDATE = 'audit.buildpack.update'.freeze, + BUILDPACK_DELETE = 'audit.buildpack.delete'.freeze, + SERVICE_CREATE = 'audit.service.create'.freeze, SERVICE_UPDATE = 'audit.service.update'.freeze, SERVICE_DELETE = 'audit.service.delete'.freeze, diff --git a/docs/v3/source/includes/resources/audit_events/_header.md.erb b/docs/v3/source/includes/resources/audit_events/_header.md.erb index c6671ec423a..b12e7275468 100644 --- a/docs/v3/source/includes/resources/audit_events/_header.md.erb +++ b/docs/v3/source/includes/resources/audit_events/_header.md.erb @@ -50,6 +50,11 @@ For more information, see the [Cloud Foundry docs](https://docs.cloudfoundry.org - `audit.app.update` - `audit.app.upload-bits` +##### Buildpack lifecycle +- `audit.buildpack.create` +- `audit.buildpack.delete` +- `audit.buildpack.update` + ##### Organization lifecycle - `audit.organization.create` - `audit.organization.delete-request` diff --git a/spec/unit/actions/buildpack_create_spec.rb b/spec/unit/actions/buildpack_create_spec.rb index c9f01d6471c..15e0fa28f8d 100644 --- a/spec/unit/actions/buildpack_create_spec.rb +++ b/spec/unit/actions/buildpack_create_spec.rb @@ -5,6 +5,11 @@ module VCAP::CloudController RSpec.describe BuildpackCreate do describe 'create' do + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + let!(:buildpack1) { Buildpack.create(name: 'take-up-position-1', position: 1) } let!(:buildpack2) { Buildpack.create(name: 'take-up-position-2', position: 2) } let!(:buildpack3) { Buildpack.create(name: 'take-up-position-3', position: 3) } @@ -22,7 +27,7 @@ module VCAP::CloudController locked: true, lifecycle: Lifecycles::BUILDPACK ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.name).to eq('the-name') expect(buildpack.stack).to eq('the-stack') @@ -31,6 +36,33 @@ module VCAP::CloudController expect(buildpack.locked).to be(true) expect(buildpack.lifecycle).to eq(Lifecycles::BUILDPACK) end + + it 'creates an audit event' do + message = BuildpackCreateMessage.new( + name: 'the-name', + stack: 'the-stack', + enabled: false, + locked: true + ) + buildpack = BuildpackCreate.new(user_audit_info).create(message) + + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.buildpack.create', + actee: buildpack.guid, + actee_type: 'buildpack', + actee_name: buildpack.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: '' + ) + expect(event.metadata).to eq({ 'request' => message.audit_hash }) + expect(event.timestamp).to be + end end context 'when metadata is provided' do @@ -49,7 +81,7 @@ module VCAP::CloudController } } ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.name).to eq('the-name') expect(buildpack.stack).to eq('the-stack') @@ -68,7 +100,7 @@ module VCAP::CloudController name: 'the-name', position: 2 ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.position).to eq(2) expect(buildpack1.reload.position).to eq(1) @@ -83,7 +115,7 @@ module VCAP::CloudController name: 'the-name', position: 42 ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.position).to eq(4) end @@ -97,7 +129,7 @@ module VCAP::CloudController stack: 'the-stack', locked: true ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.enabled).to be(true) end @@ -110,7 +142,7 @@ module VCAP::CloudController stack: 'the-stack', enabled: true ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.locked).to be(false) end @@ -123,7 +155,7 @@ module VCAP::CloudController stack: 'the-stack', lifecycle: Lifecycles::CNB ) - buildpack = BuildpackCreate.new.create(message) + buildpack = BuildpackCreate.new(user_audit_info).create(message) expect(buildpack.lifecycle).to eq(Lifecycles::CNB) end @@ -138,7 +170,7 @@ module VCAP::CloudController message = BuildpackCreateMessage.new(name: 'foobar') expect do - BuildpackCreate.new.create(message) + BuildpackCreate.new(user_audit_info).create(message) end.to raise_error(BuildpackCreate::Error, 'blork is busted') end end @@ -148,7 +180,7 @@ module VCAP::CloudController message = BuildpackCreateMessage.new(name: 'the-name', stack: 'does-not-exist') expect do - BuildpackCreate.new.create(message) + BuildpackCreate.new(user_audit_info).create(message) end.to raise_error(BuildpackCreate::Error, "Stack 'does-not-exist' does not exist") end end @@ -164,7 +196,7 @@ module VCAP::CloudController it 'raises a human-friendly error' do message = BuildpackCreateMessage.new(name:) expect do - BuildpackCreate.new.create(message) + BuildpackCreate.new(user_audit_info).create(message) end.to raise_error(BuildpackCreate::Error, "Buildpack with name 'the-name' and an unassigned stack already exists") end end @@ -177,7 +209,7 @@ module VCAP::CloudController it 'raises a human-friendly error' do message = BuildpackCreateMessage.new(name: name, stack: 'the-stack') expect do - BuildpackCreate.new.create(message) + BuildpackCreate.new(user_audit_info).create(message) end.to raise_error(BuildpackCreate::Error, "Buildpack with name 'the-name', stack 'the-stack' and lifecycle 'buildpack' already exists") end end diff --git a/spec/unit/actions/buildpack_delete_spec.rb b/spec/unit/actions/buildpack_delete_spec.rb index a1e3d6c4a4d..4fde34b6cc2 100644 --- a/spec/unit/actions/buildpack_delete_spec.rb +++ b/spec/unit/actions/buildpack_delete_spec.rb @@ -3,7 +3,12 @@ module VCAP::CloudController RSpec.describe BuildpackDelete do - subject(:buildpack_delete) { BuildpackDelete.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject(:buildpack_delete) { BuildpackDelete.new(user_audit_info) } describe '#delete' do let!(:buildpack) { Buildpack.make } @@ -15,6 +20,30 @@ module VCAP::CloudController expect { buildpack.refresh }.to raise_error Sequel::Error, 'Record not found' end + it 'creates an audit event' do + buildpack_guid = buildpack.guid + buildpack_name = buildpack.name + + buildpack_delete.delete([buildpack]) + + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.buildpack.delete', + actee: buildpack_guid, + actee_type: 'buildpack', + actee_name: buildpack_name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: '' + ) + expect(event.metadata).to eq({}) + expect(event.timestamp).to be + end + context 'when the buildpack has associated bits in the blobstore' do before do buildpack.update(key: 'the-key') diff --git a/spec/unit/actions/buildpack_update_spec.rb b/spec/unit/actions/buildpack_update_spec.rb index 5958e3bcfda..848f485ca69 100644 --- a/spec/unit/actions/buildpack_update_spec.rb +++ b/spec/unit/actions/buildpack_update_spec.rb @@ -5,6 +5,11 @@ module VCAP::CloudController RSpec.describe BuildpackUpdate do describe 'update' do + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + let!(:buildpack1) { Buildpack.make(position: 1) } let!(:buildpack2) { Buildpack.make(position: 2) } let!(:buildpack3) { Buildpack.make(position: 3) } @@ -16,7 +21,7 @@ module VCAP::CloudController position: 2, name: 'new-name' ) - BuildpackUpdate.new.update(buildpack1, message) + BuildpackUpdate.new(user_audit_info).update(buildpack1, message) expect(buildpack1.reload.position).to eq(2) expect(buildpack1.name).to eq('new-name') @@ -30,7 +35,7 @@ module VCAP::CloudController position: 2, stack: 'invalid-stack' ) - expect { BuildpackUpdate.new.update(buildpack1, message) }.to raise_error(BuildpackUpdate::Error) + expect { BuildpackUpdate.new(user_audit_info).update(buildpack1, message) }.to raise_error(BuildpackUpdate::Error) expect(buildpack1.reload.position).to eq(1) end @@ -41,10 +46,34 @@ module VCAP::CloudController message = BuildpackUpdateMessage.new( enabled: false ) - buildpack = BuildpackUpdate.new.update(buildpack1, message) + buildpack = BuildpackUpdate.new(user_audit_info).update(buildpack1, message) expect(buildpack.enabled).to be(false) end + + it 'creates an audit event' do + message = BuildpackUpdateMessage.new( + enabled: false + ) + BuildpackUpdate.new(user_audit_info).update(buildpack1, message) + + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.buildpack.update', + actee: buildpack1.guid, + actee_type: 'buildpack', + actee_name: buildpack1.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: '' + ) + expect(event.metadata).to eq({ 'request' => message.audit_hash }) + expect(event.timestamp).to be + end end context 'when locked is not provided' do @@ -52,7 +81,7 @@ module VCAP::CloudController message = BuildpackUpdateMessage.new( locked: true ) - buildpack = BuildpackUpdate.new.update(buildpack1, message) + buildpack = BuildpackUpdate.new(user_audit_info).update(buildpack1, message) expect(buildpack.locked).to be(true) end @@ -63,7 +92,7 @@ module VCAP::CloudController message = BuildpackUpdateMessage.new( name: 'new-name' ) - buildpack = BuildpackUpdate.new.update(buildpack1, message) + buildpack = BuildpackUpdate.new(user_audit_info).update(buildpack1, message) expect(buildpack.name).to eq('new-name') end @@ -81,7 +110,7 @@ module VCAP::CloudController } } ) - buildpack = BuildpackUpdate.new.update(buildpack1, message) + buildpack = BuildpackUpdate.new(user_audit_info).update(buildpack1, message) expect(buildpack.labels[0].key_name).to eq('fruit') expect(buildpack.annotations[0].value).to eq('adora') @@ -94,7 +123,7 @@ module VCAP::CloudController message = BuildpackUpdateMessage.new(stack: 'does-not-exist') expect do - BuildpackUpdate.new.update(buildpack1, message) + BuildpackUpdate.new(user_audit_info).update(buildpack1, message) end.to raise_error(BuildpackUpdate::Error, "Stack 'does-not-exist' does not exist") end end @@ -106,7 +135,7 @@ module VCAP::CloudController it 'raises a human-friendly error' do message = BuildpackUpdateMessage.new(name: buildpack1.name) expect do - BuildpackUpdate.new.update(buildpack2, message) + BuildpackUpdate.new(user_audit_info).update(buildpack2, message) end.to raise_error(BuildpackUpdate::Error, "Buildpack with name '#{buildpack1.name}' and an unassigned stack already exists") end end @@ -115,7 +144,7 @@ module VCAP::CloudController it 'raises a human-friendly error' do message = BuildpackUpdateMessage.new(stack: nil) expect do - BuildpackUpdate.new.update(buildpack1, message) + BuildpackUpdate.new(user_audit_info).update(buildpack1, message) end.to raise_error(BuildpackUpdate::Error, 'Buildpack stack cannot be changed') end end @@ -125,7 +154,7 @@ module VCAP::CloudController message = BuildpackUpdateMessage.new(name: buildpack1.name) expect do - BuildpackUpdate.new.update(buildpack2, message) + BuildpackUpdate.new(user_audit_info).update(buildpack2, message) end.to raise_error(BuildpackUpdate::Error, "Buildpack with name '#{buildpack1.name}', stack '#{buildpack1.stack}' and lifecycle '#{buildpack1.lifecycle}' already exists") end @@ -135,7 +164,7 @@ module VCAP::CloudController allow(buildpack1).to receive(:save).and_raise(Sequel::ValidationFailed.new(buildpack1)) expect do - BuildpackUpdate.new.update(buildpack1, message) + BuildpackUpdate.new(user_audit_info).update(buildpack1, message) end.to raise_error(BuildpackUpdate::Error, /unknown error/) end end diff --git a/spec/unit/repositories/event_types_spec.rb b/spec/unit/repositories/event_types_spec.rb index 6c6b74bb5c8..593303eecbf 100644 --- a/spec/unit/repositories/event_types_spec.rb +++ b/spec/unit/repositories/event_types_spec.rb @@ -64,6 +64,9 @@ module Repositories 'audit.app.apply_manifest', 'audit.app.ssh-authorized', 'audit.app.ssh-unauthorized', + 'audit.buildpack.create', + 'audit.buildpack.update', + 'audit.buildpack.delete', 'audit.service.create', 'audit.service.update', 'audit.service.delete',