Commit a24b3c09 authored by o's avatar o
Browse files

record security relevant events

the events are recorded in the user transaction log and the user is
informed by email.

For translating event strings we use a new i18n repo that is shared with
the user interface.
parent 07e1ebf9
Pipeline #9224 failed with stage
in 2 minutes and 48 seconds
[submodule "i18n"]
path = i18n
url = https://code.immerda.ch/immerda/apps/i18n_i5a.git
Subproject commit b3a9493dc03becc57bb8f9243726e2443da8c11d
......@@ -7,6 +7,7 @@ require 'thin'
require 'singleton'
require 'active_record'
require 'bcrypt'
require 'i18n'
require 'iapi/utils/logger'
require 'iapi/utils/mail_crypt'
......@@ -46,6 +47,7 @@ require 'iapi/managers/jabber_manager'
require 'iapi/managers/mail_manager'
require 'iapi/managers/saml_manager'
require 'iapi/managers/user_manager'
require 'iapi/managers/transaction_manager'
require 'iapi/delayed_jobs'
require 'iapi/mailer'
......@@ -96,6 +98,7 @@ class IApi < Sinatra::Base
set :server, "thin"
set :logger, IApiLog.logger
set :logging, IApiLog.level
I18n.load_path = Dir["#{File.dirname(__FILE__)}/../i18n/*.yml"]
end
def self.run!
......
......@@ -2,3 +2,4 @@ require 'iapi/mailers/base_mailer'
require 'iapi/mailers/welcome_mail'
require 'iapi/mailers/recovery_token_mail'
require 'iapi/mailers/nudge_mail'
require 'iapi/mailers/audit_mail'
class AuditMail < BaseMailer
SUPPORTED_LANG = []
def notify(recipient, event, msg, language = nil)
language = if SUPPORTED_LANG.include? language then language else 'multi' end
mail(
to: recipient,
subject: "Sicherheitsinfo / Security Event - immerda.ch",
) do |format|
# https://github.com/rails/rails/issues/22045
self.action_name = language
# The msg is not rendered on purpose. It has some malicious potential,
# as it allows an attacker that compromised the account to embed content.
format.text {
render locals: { recipient: recipient, event: event }
}
end
end
end
# encoding: utf-8
Ping <%= recipient %> !
[de]
Soeben wurde folgende Kontoeinstellungen vorgenommen:
<%= t(event, scope: :audit_event, locale: :de) %>
Sollte diese Änderung nicht von dir stammen, kontaktiere uns so schnell wie möglich via admin@immerda.ch.
Um deine Kontoeinstellungen zu überprüfen, gehe auf https://www.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und dann auf Einstellungen.
[en]
The following settings have been changed on your account:
<%= t(event, scope: :audit_event, locale: :en) %>
If you did not make these changes, please contact us as quickly as possible via admin@immerda.ch.
To configure your account and audit changes visit https://immerda.ch, click on the butterfly at the bottom and then Einstellungen.
immerda admin team
class TransactionManager
class << self
def get_user_transactions(scope, user, user_secret_key, from=nil, to=nil)
from = DateTime.parse(from) if from.present?
to = DateTime.parse(to) if to.present?
entries = UserTransaction.where(email_user: user, scope: scope)
entries = entries.order(created_at: 'DESC')
entries = entries.where('created_at >= ?', from) if from
entries = entries.where('created_at < ?', to) if to
entries = entries.pluck(:encrypted, :value, :created_at)
entries.map do |e|
encrypted, value, created_at = e
value = Base64.decode64(value)
if encrypted
value = Sodium::Box.open(
IApiConf.transaction_log_key.pub,
user_secret_key,
value)
end
[value.force_encoding("utf-8"), created_at]
end
end
def add_user_transaction(scope, user, value, ttl=nil)
key = user.iapi_public_key
ttl = ttl.to_i if ttl.present?
data = {email_user: user, scope: scope, value: value}
if ttl.present?
data[:remove_at] = Time.now + ttl.days
end
if key
data[:value] = Sodium::Box.close(
key,
IApiConf.transaction_log_key.sec, data[:value])
data[:encrypted] = true
end
data[:value] = Base64.encode64(data[:value])
UserTransaction.create!(data)
end
def add_audit_log(user, event, msg = "")
add_user_transaction('audit_log', user, {event: event, msg: msg}.to_json, 90)
AuditMail.notify(user.email, event, msg).deliver
end
end
end
......@@ -90,7 +90,7 @@ class UserManager
email, pass, keep_recovery_token: keep_token)
if res[:state] == 'success'
if options[:email_recovery_token]
generate_recovery_token_and_send_by_mail(email, pass, recovery_email)
generate_recovery_token_and_send_by_mail(email, pass, recovery_email, true)
end
return_token = options[:return_recovery_token]
......@@ -99,7 +99,7 @@ class UserManager
return { state: 'success' }
end
mail_crypt_token = UserManager.generate_recovery_token(email, pass)
mail_crypt_token = UserManager.generate_recovery_token(email, pass, true)
if mail_crypt_token[:state] == 'success'
welcome_mail(email_user, options[:language])
return {state: 'success',
......@@ -122,6 +122,7 @@ class UserManager
user.set_password(new_pwd)
end
user.save!
TransactionManager.add_audit_log(user, "pwchange")
return { state: 'success' }
end
end
......@@ -156,6 +157,7 @@ class UserManager
secret_box: app_pw[:secret_box],
})
if success
TransactionManager.add_audit_log(user, "apppw-add", pw_name)
return {state: 'success',
new_pw: app_pw[:pw]}
end
......@@ -172,14 +174,15 @@ class UserManager
with_active_mailbox(email) do |user|
if user.mail_crypt_enabled?
if user.mail_crypt_extra_boxes.where(name: pw_name).destroy_all
return {state: 'success'}
TransactionManager.add_audit_log(user, "apppw-del", pw_name)
return {state: 'success'}
end
end
end
return { state: 'fail'}
end
def generate_recovery_token(email, pwd)
def generate_recovery_token(email, pwd, no_audit=false)
return { state: 'fail', msg: 'No issuer key found!' } unless IApiConf.mail_crypt_issuer_key
with_active_mailbox(email) do |user|
if res = AuthManager.mail_crypt_authenticate(pwd, user.mail_crypt_secret_box)
......@@ -191,6 +194,9 @@ class UserManager
user.mail_crypt_recovery_token = nil
user.save!
end
unless no_audit
TransactionManager.add_audit_log(user, "recoverytoken-gen")
end
return {
state: 'success',
mail_crypt_recovery_token: token,
......@@ -202,11 +208,11 @@ class UserManager
return { state: 'fail'}
end
def generate_recovery_token_and_send_by_mail(email, pass, receiver)
def generate_recovery_token_and_send_by_mail(email, pass, receiver, no_audit=false)
unless allowed_to_receive_recovery_token(receiver)
return {state: 'fail', msg: "invalid_recovery_token_receiver"}
end
res = UserManager.generate_recovery_token(email, pass)
res = UserManager.generate_recovery_token(email, pass, no_audit)
if res[:state] == 'success'
recovery_token_mail(
email, receiver, res[:mail_crypt_recovery_token])
......@@ -266,6 +272,7 @@ class UserManager
totp: secret,
totp_last: current_totp,
)
TransactionManager.add_audit_log(user, "totp-add", name)
return { state: 'success' }
else
IApiLog.error "Error while verifying initial TOTP for #{email}"
......@@ -279,6 +286,7 @@ class UserManager
if (u = EmailUser.active_by_email(email)) && (t = u.totps.find_by(name: name))
unless u.totps.count == 1 && u.web_authn_credentials.present?
t.destroy!
TransactionManager.add_audit_log(u, "totp-del", name)
return { state: 'success' }
else
IApiLog.error "Refuse to delete last TOTP for '#{email}' - User still has WebAuthnCredentials"
......@@ -292,6 +300,7 @@ class UserManager
def set_recovery_email(email, recovery_email)
with_active_mailbox(email) do |user|
user.set_recovery_email!(recovery_email.downcase)
TransactionManager.add_audit_log(user, "recoveryemail")
return { state: 'success' }
end
end
......@@ -341,6 +350,7 @@ class UserManager
credential.update!(
name: name,
public_key: WebAuthn.standard_encoder.encode(webauthn_credential.credential.public_key))
TransactionManager.add_audit_log(user, "webauthn-add", name)
return { state: 'success' }
end
end
......@@ -351,6 +361,16 @@ class UserManager
{ state: 'webauthn verification failed' }
end
def delete_webauthn_credential(email, name)
if user = EmailUser.active_by_email(email)
if user && (c = user.web_authn_credentials.find_by(name: name))
c.destroy!
TransactionManager.add_audit_log(user, "webauthn-del", name)
return true
end
end
end
############################
# Admin API
###########################
......
......@@ -19,6 +19,10 @@ class IApi < Sinatra::Base
end
end
def security_critical_resource?(kind)
['mail_alias'].include?(kind)
end
namespace '/resource' do
get '/available' do
return json result: 'success', available:
......@@ -97,6 +101,10 @@ class IApi < Sinatra::Base
self_create: true,
allow_protected_alias:
IApiConf.acl.has_access_to_admin_api?(authenticated_user.email))
# Log creation of security critical resources
if security_critical_resource?(kind)
TransactionManager.add_audit_log(owner, "#{kind}-add", res.to_api[:email])
end
return json result: 'success', uid: res.uid
end
end
......@@ -123,7 +131,14 @@ class IApi < Sinatra::Base
post '/delete' do
if (_, res = authorize_access)
owners = res.owners.to_a
kind = res.class.resource_name
if res.destroy
if security_critical_resource?(kind)
owners.each do |o|
TransactionManager.add_audit_log(o, "#{kind}-del", res.to_api[:email])
end
end
return json result: 'success'
end
end
......
......@@ -90,27 +90,12 @@ class IApi < Sinatra::Base
namespace '/user_transaction' do
get '/get' do
scope = params['scope']
unless (verify_acl(scope, ['bill']))
unless (verify_acl(scope, ['bill','audit_log']))
return client_error('invalid_scope')
end
from = DateTime.parse(params['from']) if params['from']
to = DateTime.parse(params['to']) if params['to']
entries = UserTransaction.where(email_user: authenticated_user, scope: scope)
entries = entries.where('created_at >= ?', from) if from
entries = entries.where('created_at < ?', to) if to
entries = entries.pluck(:encrypted, :value, :created_at)
entries = entries.map do |e|
encrypted, value, created_at = e
value = Base64.decode64(value)
if encrypted
value = Sodium::Box.open(
IApiConf.transaction_log_key.pub,
api_user_secret_key,
value)
end
[value.force_encoding("utf-8"), created_at]
end
entries = TransactionManager.get_user_transactions(
scope, authenticated_user, api_user_secret_key, params['from'], params['to'])
json(result: 'success', values: entries)
end
......@@ -128,22 +113,8 @@ class IApi < Sinatra::Base
return client_error('invalid_target_user')
end
end
key = user.iapi_public_key
value = parsed_body['value']
ttl = parsed_body['ttl']
ttl = ttl.to_i if ttl.present?
data = {email_user: user, scope: scope, value: value}
if ttl.present?
data[:remove_at] = Time.now + ttl.days
end
if key
data[:value] = Sodium::Box.close(
key,
IApiConf.transaction_log_key.sec, data[:value])
data[:encrypted] = true
end
data[:value] = Base64.encode64(data[:value])
UserTransaction.create!(data)
TransactionManager.add_user_transaction(
scope, user, parsed_body['value'], parsed_body['ttl'])
json result: 'success'
end
end
......
......@@ -351,9 +351,7 @@ class IApi < Sinatra::Base
'deleting webauthn'
)
if name = parsed_body['webauthn_name']
u = authenticated_user
if u && (c = u.web_authn_credentials.find_by(name: name))
c.destroy!
if UserManager.delete_webauthn_credential(authenticated_user.email, name)
return json result: 'success'
end
end
......
This diff is collapsed.
Date: Sat, 23 Oct 2021 14:16:32 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user2@example.com
Message-ID: <6174194074495_481060042771@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user2@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D
TOTP device added=0D
=0D
If you did not make these changes, please contact us as quickly as possib=
le via admin@immerda.ch.=0D
=0D
To configure your account and audit changes visit https://immerda.ch, cli=
ck on the butterfly at the bottom and then Einstellungen.=0D
=0D
immerda admin team=0D
Date: Sat, 23 Oct 2021 14:23:15 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user2@example.com
Message-ID: <61741ad3a8074_4a085fc79995@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user2@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D
TOTP device added=0D
=0D
If you did not make these changes, please contact us as quickly as possib=
le via admin@immerda.ch.=0D
=0D
To configure your account and audit changes visit https://immerda.ch, cli=
ck on the butterfly at the bottom and then Einstellungen.=0D
=0D
immerda admin team=0D
Date: Sat, 23 Oct 2021 14:28:22 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user2@example.com
Message-ID: <61741c06d62a2_411ae5f496553@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user2@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D
TOTP device added=0D
=0D
If you did not make these changes, please contact us as quickly as possib=
le via admin@immerda.ch.=0D
=0D
To configure your account and audit changes visit https://immerda.ch, cli=
ck on the butterfly at the bottom and then Einstellungen.=0D
=0D
immerda admin team=0D
Date: Sat, 23 Oct 2021 14:32:17 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user2@example.com
Message-ID: <61741cf1a8b02_4d7c5f832724@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user2@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D
TOTP device added=0D
=0D
If you did not make these changes, please contact us as quickly as possib=
le via admin@immerda.ch.=0D
=0D
To configure your account and audit changes visit https://immerda.ch, cli=
ck on the butterfly at the bottom and then Einstellungen.=0D
=0D
immerda admin team=0D
Date: Sat, 23 Oct 2021 14:16:38 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user3@example.com
Message-ID: <6174194613804_48106004289b@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user3@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D
TOTP device added=0D
=0D
If you did not make these changes, please contact us as quickly as possib=
le via admin@immerda.ch.=0D
=0D
To configure your account and audit changes visit https://immerda.ch, cli=
ck on the butterfly at the bottom and then Einstellungen.=0D
=0D
immerda admin team=0D
Date: Sat, 23 Oct 2021 14:23:09 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user3@example.com
Message-ID: <61741acdc3710_4a085fc798b2@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user3@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D
TOTP device added=0D
=0D
If you did not make these changes, please contact us as quickly as possib=
le via admin@immerda.ch.=0D
=0D
To configure your account and audit changes visit https://immerda.ch, cli=
ck on the butterfly at the bottom and then Einstellungen.=0D
=0D
immerda admin team=0D
Date: Sat, 23 Oct 2021 14:29:02 +0000
From: Immerda Admin Team <admin@immerda.ch>
To: user3@example.com
Message-ID: <61741c2e2416a_411ae5f4996b7@immer.mail>
Subject: Sicherheitsinfo / Security Event - immerda.ch
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
=0D
Ping user3@example.com !=0D
=0D
[de]=0D
=0D
Soeben wurde folgende Kontoeinstellungen vorgenommen:=0D
=0D
TOTP Ger=C3=A4t hinzugef=C3=BCgt=0D
=0D
Sollte diese =C3=84nderung nicht von dir stammen, kontaktiere uns so schn=
ell wie m=C3=B6glich via admin@immerda.ch.=0D
=0D
Um deine Kontoeinstellungen zu =C3=BCberpr=C3=BCfen, gehe auf https://www=
.immerda.ch, klicke auf den Schmetterling am unteren Ende der seite und d=
ann auf Einstellungen.=0D
=0D
[en]=0D
=0D
The following settings have been changed on your account:=0D
=0D