diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 7f5e057758347ad88d1d0d2a29310335de2c6c3d..b8ee5e8a4789d49a850b03a32dcc46cb639c52f0 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -280,13 +280,37 @@ def valid_otp_attempt?(user) user.invalidate_otp_backup_code!(user_params[:otp_attempt]) end + def audit_event_name_for_authentication_method(method) + case method + when AuthenticationEvent::TWO_FACTOR + 'authenticated_with_two_factor' + when AuthenticationEvent::TWO_FACTOR_WEBAUTHN + 'authenticated_with_webauthn' + else + 'authenticated_with_password' + end + end + def log_audit_event(user, resource, options = {}) Gitlab::AppLogger.info( "Successful Login: username=#{resource.username} ip=#{request.remote_ip} " \ "method=#{options[:with]} admin=#{resource.admin?}" ) - AuditEventService.new(user, user, options) - .for_authentication.security_event + + event_name = audit_event_name_for_authentication_method(options[:with] || AuthenticationEvent::STANDARD) + audit_context = { + name: event_name, + author: user, + scope: user, + target: user, + message: "Signed in with #{options[:with]} authentication", + authentication_event: true, + authentication_provider: options[:with], + additional_details: { + with: options[:with] + } + } + ::Gitlab::Audit::Auditor.audit(audit_context) end def log_user_activity(user) diff --git a/config/audit_events/types/authenticated_with_password.yml b/config/audit_events/types/authenticated_with_password.yml new file mode 100644 index 0000000000000000000000000000000000000000..cacd71dedc792bc8827360aaa0a7d01f77a797dc --- /dev/null +++ b/config/audit_events/types/authenticated_with_password.yml @@ -0,0 +1,10 @@ +--- +name: authenticated_with_password +description: User successfully signed in with password +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/555101 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198216 +milestone: '18.3' +feature_category: system_access +saved_to_database: true +streamed: true +scope: [User] diff --git a/config/audit_events/types/authenticated_with_two_factor.yml b/config/audit_events/types/authenticated_with_two_factor.yml new file mode 100644 index 0000000000000000000000000000000000000000..bf31b6e3656c4241f3c3207f9b2ae3364a64286e --- /dev/null +++ b/config/audit_events/types/authenticated_with_two_factor.yml @@ -0,0 +1,10 @@ +--- +name: authenticated_with_two_factor +description: User successfully signed in with two-factor authentication +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/555101 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198216 +milestone: '18.3' +feature_category: system_access +saved_to_database: true +streamed: true +scope: [User] diff --git a/config/audit_events/types/authenticated_with_webauthn.yml b/config/audit_events/types/authenticated_with_webauthn.yml new file mode 100644 index 0000000000000000000000000000000000000000..e20440637644bc8016fb22a00894baf46f859d24 --- /dev/null +++ b/config/audit_events/types/authenticated_with_webauthn.yml @@ -0,0 +1,10 @@ +--- +name: authenticated_with_webauthn +description: User successfully signed in with WebAuthn device +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/555101 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198216 +milestone: '18.3' +feature_category: system_access +saved_to_database: true +streamed: true +scope: [User] diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 713d043f7ac1c12bb265b201dacc1a571f103007..9bbca195e4cc43ed07ebeab2d9971d85864dffbc 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -644,6 +644,9 @@ Audit event types belong to the following product categories. | [`user_enable_admin_mode`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104754) | Admin Mode enabled | {{< icon name="check-circle" >}} Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/362101) | User | | [`authenticated_with_ldap`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175763) | User successfully signed in with LDAP | {{< icon name="check-circle" >}} Yes | GitLab [17.11](https://gitlab.com/gitlab-org/gitlab/-/issues/509377) | User | | [`authenticated_with_oauth`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175763) | User successfully signed in with OAuth | {{< icon name="check-circle" >}} Yes | GitLab [17.11](https://gitlab.com/gitlab-org/gitlab/-/issues/509377) | User | +| [`authenticated_with_password`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198216) | User successfully signed in with password | {{< icon name="check-circle" >}} Yes | GitLab [18.3](https://gitlab.com/gitlab-org/gitlab/-/issues/555101) | User | +| [`authenticated_with_two_factor`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198216) | User successfully signed in with two-factor authentication | {{< icon name="check-circle" >}} Yes | GitLab [18.3](https://gitlab.com/gitlab-org/gitlab/-/issues/555101) | User | +| [`authenticated_with_webauthn`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198216) | User successfully signed in with WebAuthn device | {{< icon name="check-circle" >}} Yes | GitLab [18.3](https://gitlab.com/gitlab-org/gitlab/-/issues/555101) | User | ### Team planning diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 6349db704feca0f5dc4f2a56abc4566a4ab9fcd4..11d170ff25217ae381715be95bef4acf1086b6d6 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -261,9 +261,17 @@ end end - it 'creates an audit log record' do + it 'creates audit event records' do expect { post(:create, params: { user: user_params }) }.to change { AuditEvent.count }.by(1) - expect(AuditEvent.last.details[:with]).to eq('standard') + .and change { AuditEvents::UserAuditEvent.count }.by(1) + + audit_event = AuditEvent.last + expect(audit_event.details[:with]).to eq('standard') + expect(audit_event.details[:event_name]).to eq('authenticated_with_password') + + user_audit_event = AuditEvents::UserAuditEvent.last + expect(user_audit_event.event_name).to eq('authenticated_with_password') + expect(user_audit_event.details[:with]).to eq('standard') end it 'creates an authentication event record' do @@ -605,9 +613,17 @@ def authenticate_2fa(otp_user_id: user.id, **user_params) end end - it "creates an audit log record" do + it 'creates audit event records' do expect { authenticate_2fa(login: user.username, otp_attempt: user.current_otp) }.to change { AuditEvent.count }.by(1) - expect(AuditEvent.last.details[:with]).to eq("two-factor") + .and change { AuditEvents::UserAuditEvent.count }.by(1) + + audit_event = AuditEvent.last + expect(audit_event.details[:with]).to eq('two-factor') + expect(audit_event.details[:event_name]).to eq('authenticated_with_two_factor') + + user_audit_event = AuditEvents::UserAuditEvent.last + expect(user_audit_event.event_name).to eq('authenticated_with_two_factor') + expect(user_audit_event.details[:with]).to eq('two-factor') end it "creates an authentication event record" do @@ -661,12 +677,19 @@ def authenticate_2fa(user_params) end end - it "creates an audit log record" do + it 'creates audit event records' do allow_any_instance_of(Webauthn::AuthenticateService).to receive(:execute).and_return(true) - expect { authenticate_2fa(login: user.username, device_response: "{}") }.to( - change { AuditEvent.count }.by(1)) - expect(AuditEvent.last.details[:with]).to eq("two-factor-via-webauthn-device") + expect { authenticate_2fa(login: user.username, device_response: "{}") }.to change { AuditEvent.count }.by(1) + .and change { AuditEvents::UserAuditEvent.count }.by(1) + + audit_event = AuditEvent.last + expect(audit_event.details[:with]).to eq('two-factor-via-webauthn-device') + expect(audit_event.details[:event_name]).to eq('authenticated_with_webauthn') + + user_audit_event = AuditEvents::UserAuditEvent.last + expect(user_audit_event.event_name).to eq('authenticated_with_webauthn') + expect(user_audit_event.details[:with]).to eq('two-factor-via-webauthn-device') end it "creates an authentication event record" do @@ -719,6 +742,30 @@ def authenticate_2fa(user_params) end end + describe '#audit_event_name_for_authentication_method' do + let(:controller) { described_class.new } + + it 'returns correct event name for standard authentication' do + expect(controller.send(:audit_event_name_for_authentication_method, AuthenticationEvent::STANDARD)) + .to eq('authenticated_with_password') + end + + it 'returns correct event name for two-factor authentication' do + expect(controller.send(:audit_event_name_for_authentication_method, AuthenticationEvent::TWO_FACTOR)) + .to eq('authenticated_with_two_factor') + end + + it 'returns correct event name for WebAuthn authentication' do + expect(controller.send(:audit_event_name_for_authentication_method, AuthenticationEvent::TWO_FACTOR_WEBAUTHN)) + .to eq('authenticated_with_webauthn') + end + + it 'returns correct event name for unknown authentication method' do + expect(controller.send(:audit_event_name_for_authentication_method, 'unknown')) + .to eq('authenticated_with_password') + end + end + context 'when login fails' do before do @request.env["warden.options"] = { action: 'unauthenticated' }