Add option to ban user by ip instead of username
parent
c13e0e12ea
commit
e9a383be0c
|
@ -58,6 +58,15 @@ Wenn die Konfiguration geändert wurde, müssen die Keys zur Validierung wieder
|
|||
go run ./cmd/authelia-gen code keys
|
||||
```
|
||||
|
||||
## Mocks abgeändert
|
||||
|
||||
Wenn interfaces von den Mocks geändert werden, muss folgendes wieder ausgeführt werden:
|
||||
|
||||
```
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## Bauen
|
||||
|
||||
Um ein Docker Image für authelia zu bauen, müssen die folgenden Befehle ausgeführt werden.
|
||||
|
|
|
@ -85,6 +85,8 @@ server:
|
|||
# Even if TLS is configured in the server setting (under server.tls), the grcp server won't use TLS
|
||||
disableTLS: false
|
||||
|
||||
# By default the ban is issued for the user. With this options the IP instead of the user will be banned
|
||||
use_ip_for_ban: true
|
||||
|
||||
## Server headers configuration/customization.
|
||||
headers:
|
||||
|
|
|
@ -265,6 +265,7 @@ var Keys = []string{
|
|||
"server.asset_path",
|
||||
"server.disable_healthcheck",
|
||||
"server.disable_autho_https_redirect",
|
||||
"server.use_ip_for_ban",
|
||||
"server.tls.certificate",
|
||||
"server.tls.key",
|
||||
"server.tls.client_certificates",
|
||||
|
|
|
@ -11,6 +11,7 @@ type ServerConfiguration struct {
|
|||
AssetPath string `koanf:"asset_path"`
|
||||
DisableHealthcheck bool `koanf:"disable_healthcheck"`
|
||||
DisableAutoHttpsRedirect bool `koanf:"disable_autho_https_redirect"`
|
||||
UseIPInsteadOfUserForBan bool `koanf:"use_ip_for_ban"`
|
||||
|
||||
TLS ServerTLS `koanf:"tls"`
|
||||
Headers ServerHeaders `koanf:"headers"`
|
||||
|
|
|
@ -33,7 +33,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
|
|||
return
|
||||
}
|
||||
|
||||
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username); err != nil {
|
||||
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username, ctx.RemoteIP().String()); err != nil {
|
||||
if errors.Is(err, regulation.ErrUserIsBanned) {
|
||||
_ = markAuthenticationAttempt(ctx, false, &bannedUntil, bodyJSON.Username, regulation.AuthType1FA, nil)
|
||||
|
||||
|
|
|
@ -277,13 +277,18 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba
|
|||
if successful {
|
||||
ctx.Logger.Debugf("Successful %s authentication attempt made by user '%s'", authType, username)
|
||||
} else {
|
||||
reasonPhrase := "by user '" + username + "'"
|
||||
if ctx.Configuration.Server.UseIPInsteadOfUserForBan {
|
||||
reasonPhrase = fmt.Sprintf("by ip %q (user %q)", ctx.RemoteIP().String(), username)
|
||||
}
|
||||
|
||||
switch {
|
||||
case errAuth != nil:
|
||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s': %+v", authType, username, errAuth)
|
||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s: %+v", authType, reasonPhrase, errAuth)
|
||||
case bannedUntil != nil:
|
||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s' and they are banned until %s", authType, username, bannedUntil)
|
||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s and they are banned until %s", authType, reasonPhrase, bannedUntil)
|
||||
default:
|
||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s'", authType, username)
|
||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s", authType, reasonPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -210,18 +210,18 @@ func (mr *MockStorageMockRecorder) FindIdentityVerification(arg0, arg1 interface
|
|||
}
|
||||
|
||||
// LoadAuthenticationLogs mocks base method.
|
||||
func (m *MockStorage) LoadAuthenticationLogs(arg0 context.Context, arg1 string, arg2 time.Time, arg3, arg4 int) ([]model.AuthenticationAttempt, error) {
|
||||
func (m *MockStorage) LoadAuthenticationLogs(arg0 context.Context, arg1, arg2 string, arg3 time.Time, arg4, arg5 int) ([]model.AuthenticationAttempt, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LoadAuthenticationLogs", arg0, arg1, arg2, arg3, arg4)
|
||||
ret := m.ctrl.Call(m, "LoadAuthenticationLogs", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret0, _ := ret[0].([]model.AuthenticationAttempt)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// LoadAuthenticationLogs indicates an expected call of LoadAuthenticationLogs.
|
||||
func (mr *MockStorageMockRecorder) LoadAuthenticationLogs(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||
func (mr *MockStorageMockRecorder) LoadAuthenticationLogs(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadAuthenticationLogs", reflect.TypeOf((*MockStorage)(nil).LoadAuthenticationLogs), arg0, arg1, arg2, arg3, arg4)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadAuthenticationLogs", reflect.TypeOf((*MockStorage)(nil).LoadAuthenticationLogs), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// LoadOAuth2BlacklistedJTI mocks base method.
|
||||
|
|
|
@ -40,13 +40,13 @@ func (r *Regulator) Mark(ctx Context, successful, banned bool, username, request
|
|||
|
||||
// Regulate the authentication attempts for a given user.
|
||||
// This method returns ErrUserIsBanned if the user is banned along with the time until when the user is banned.
|
||||
func (r *Regulator) Regulate(ctx context.Context, username string) (time.Time, error) {
|
||||
func (r *Regulator) Regulate(ctx context.Context, username string, remoteIp string) (time.Time, error) {
|
||||
// If there is regulation configuration, no regulation applies.
|
||||
if !r.enabled {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
attempts, err := r.store.LoadAuthenticationLogs(ctx, username, r.clock.Now().Add(-r.config.BanTime), 10, 0)
|
||||
attempts, err := r.store.LoadAuthenticationLogs(ctx, username, remoteIp, r.clock.Now().Add(-r.config.BanTime), 10, 0)
|
||||
if err != nil {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ type RegulatorSuite struct {
|
|||
mock *mocks.MockAutheliaCtx
|
||||
}
|
||||
|
||||
// @TODO
|
||||
// Extend this test for IP ban :)
|
||||
func (s *RegulatorSuite) SetupTest() {
|
||||
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
||||
s.mock.Ctx.Configuration.Regulation = schema.RegulationConfiguration{
|
||||
|
@ -58,9 +60,9 @@ func (s *RegulatorSuite) TestShouldMark() {
|
|||
func (s *RegulatorSuite) TestShouldHandleRegulateError() {
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
s.mock.StorageMock.EXPECT().LoadAuthenticationLogs(s.mock.Ctx, "john", s.mock.Clock.Now().Add(-s.mock.Ctx.Configuration.Regulation.BanTime), 10, 0).Return(nil, fmt.Errorf("failed"))
|
||||
s.mock.StorageMock.EXPECT().LoadAuthenticationLogs(s.mock.Ctx, "john", "127.0.0.1", s.mock.Clock.Now().Add(-s.mock.Ctx.Configuration.Regulation.BanTime), 10, 0).Return(nil, fmt.Errorf("failed"))
|
||||
|
||||
until, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
until, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
|
||||
s.NoError(err)
|
||||
s.Equal(time.Time{}, until)
|
||||
|
@ -76,12 +78,12 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
|
@ -107,12 +109,12 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
|
@ -143,12 +145,12 @@ func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||
}
|
||||
|
||||
|
@ -176,12 +178,12 @@ func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||
}
|
||||
|
||||
|
@ -200,12 +202,12 @@ func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
|
@ -232,12 +234,12 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
|
@ -268,12 +270,12 @@ func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttemp
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
|
@ -303,7 +305,7 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
|||
}
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
// Check Disabled Functionality.
|
||||
|
@ -314,7 +316,7 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
|||
}
|
||||
|
||||
regulator := regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.NoError(s.T(), err)
|
||||
|
||||
// Check Enabled Functionality.
|
||||
|
@ -325,6 +327,6 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
|||
}
|
||||
|
||||
regulator = regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
|
||||
_, err = regulator.Regulate(s.mock.Ctx, "john")
|
||||
_, err = regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
|
||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||
}
|
||||
|
|
|
@ -90,5 +90,5 @@ type Provider interface {
|
|||
// RegulatorProvider is an interface providing storage capabilities for persisting any kind of data related to the regulator.
|
||||
type RegulatorProvider interface {
|
||||
AppendAuthenticationLog(ctx context.Context, attempt model.AuthenticationAttempt) (err error)
|
||||
LoadAuthenticationLogs(ctx context.Context, username string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error)
|
||||
LoadAuthenticationLogs(ctx context.Context, username string, ip string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
|
|||
|
||||
sqlInsertAuthenticationAttempt: fmt.Sprintf(queryFmtInsertAuthenticationLogEntry, tableAuthenticationLogs),
|
||||
sqlSelectAuthenticationAttemptsByUsername: fmt.Sprintf(queryFmtSelect1FAAuthenticationLogEntryByUsername, tableAuthenticationLogs),
|
||||
sqlSelectAuthenticationAttemptyByIP: fmt.Sprintf(queryFmtSelect1FAAuthenticationLogEntryByIP, tableAuthenticationLogs),
|
||||
|
||||
sqlInsertIdentityVerification: fmt.Sprintf(queryFmtInsertIdentityVerification, tableIdentityVerification),
|
||||
sqlConsumeIdentityVerification: fmt.Sprintf(queryFmtConsumeIdentityVerification, tableIdentityVerification),
|
||||
|
@ -149,6 +150,7 @@ type SQLProvider struct {
|
|||
// Table: authentication_logs.
|
||||
sqlInsertAuthenticationAttempt string
|
||||
sqlSelectAuthenticationAttemptsByUsername string
|
||||
sqlSelectAuthenticationAttemptyByIP string
|
||||
|
||||
// Table: identity_verification.
|
||||
sqlInsertIdentityVerification string
|
||||
|
@ -1021,10 +1023,18 @@ func (p *SQLProvider) AppendAuthenticationLog(ctx context.Context, attempt model
|
|||
}
|
||||
|
||||
// LoadAuthenticationLogs retrieve the latest failed authentications from the authentication log.
|
||||
func (p *SQLProvider) LoadAuthenticationLogs(ctx context.Context, username string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error) {
|
||||
func (p *SQLProvider) LoadAuthenticationLogs(ctx context.Context, username string, ip string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error) {
|
||||
attempts = make([]model.AuthenticationAttempt, 0, limit)
|
||||
|
||||
if err = p.db.SelectContext(ctx, &attempts, p.sqlSelectAuthenticationAttemptsByUsername, fromDate, username, limit, limit*page); err != nil {
|
||||
// Dynmaic values based on ip / username ban
|
||||
query := p.sqlSelectAuthenticationAttemptsByUsername
|
||||
placeholder := username
|
||||
if p.config.Server.UseIPInsteadOfUserForBan {
|
||||
query = p.sqlSelectAuthenticationAttemptyByIP
|
||||
placeholder = ip
|
||||
}
|
||||
|
||||
if err = p.db.SelectContext(ctx, &attempts, query, fromDate, placeholder, limit, limit*page); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNoAuthenticationLogs
|
||||
}
|
||||
|
@ -1033,4 +1043,5 @@ func (p *SQLProvider) LoadAuthenticationLogs(ctx context.Context, username strin
|
|||
}
|
||||
|
||||
return attempts, nil
|
||||
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
|
|||
|
||||
provider.sqlInsertAuthenticationAttempt = provider.db.Rebind(provider.sqlInsertAuthenticationAttempt)
|
||||
provider.sqlSelectAuthenticationAttemptsByUsername = provider.db.Rebind(provider.sqlSelectAuthenticationAttemptsByUsername)
|
||||
provider.sqlSelectAuthenticationAttemptyByIP = provider.db.Rebind(provider.sqlSelectAuthenticationAttemptyByIP)
|
||||
|
||||
provider.sqlInsertMigration = provider.db.Rebind(provider.sqlInsertMigration)
|
||||
provider.sqlSelectMigrations = provider.db.Rebind(provider.sqlSelectMigrations)
|
||||
|
|
|
@ -211,6 +211,14 @@ const (
|
|||
ORDER BY time DESC
|
||||
LIMIT ?
|
||||
OFFSET ?;`
|
||||
|
||||
queryFmtSelect1FAAuthenticationLogEntryByIP = `
|
||||
SELECT time, successful, username
|
||||
FROM %s
|
||||
WHERE time > ? AND remote_ip = ? AND auth_type = '1FA' AND banned = FALSE
|
||||
ORDER BY time DESC
|
||||
LIMIT ?
|
||||
OFFSET ?;`
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
Loading…
Reference in New Issue