Deprecate mongo and add mariadb as storage backend option.

pull/447/head
Clement Michaud 2019-11-16 11:38:21 +01:00 committed by Clément Michaud
parent fc099221f7
commit bd19ee48fd
41 changed files with 514 additions and 1084 deletions

View File

@ -8,7 +8,7 @@ that the system is more reliable overall.
Majors changes:
* The configuration mostly remained the same, only one major key has been added: `jwt_secret` and one key removed: `secure` from the
SMTP notifier as the Go SMTP library default to TLS if available.
* The local storage previously used as a replacement of mongo for dev purpose was a `nedb` database which was implementing the same interface
* The local storage used for dev purpose was a `nedb` database which was implementing the same interface
as mongo but was not really standard. It has been replaced by a good old sqlite3 database.
* The model of the database is not compatible with v3. This has been decided to better fit with Golang libraries.
* Some features have been upgraded such as U2F in order to use the latest security features available like allowing device cloning detection.
@ -32,9 +32,9 @@ for operations requiring identity validation.
* Make sure users and groups filter in the LDAP configuration have outer parenthesis. The standard format of LDAP filters always include outer
parenthesis. You can find some examples in the "Examples" section of the following document: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
#### If using the local storage
#### If you were using the local storage
* Remove the directory of the storage (beware you will lose your previous configuration: U2F, TOTP devices). Replace the path with a path to a sqlite3 database,
it is the new standard way of storing data in Authelia.
#### If using the mongo storage
#### If you were using the mongo storage
* Flush your collections (beware you will lose your previous configuration: U2F, TOTP devices). New collections will be created by Authelia.

View File

@ -8,9 +8,9 @@ Docker image but pick a version instead and check this file before upgrading. Th
Authelia has been rewritten in Go for better performance and reliability.
### Model of U2F devices in MongoDB
### Model of U2F devices
The model of U2F devices stored in MongoDB has been updated to better fit with the Go library handling U2F keys.
The model of U2F devices has been updated to better fit with the Go library handling U2F keys.
### Removal of flag secure for SMTP notifier

View File

@ -7,7 +7,7 @@ import (
)
const dockerPullCommandLine = "docker-compose -f docker-compose.yml " +
"-f example/compose/mongo/docker-compose.yml " +
"-f example/compose/mariadb/docker-compose.yml " +
"-f example/compose/redis/docker-compose.yml " +
"-f example/compose/nginx/portal/docker-compose.yml " +
"-f example/compose/smtp/docker-compose.yml " +

View File

@ -6,17 +6,15 @@ import (
"log"
"os"
"github.com/clems4ever/authelia/regulation"
"github.com/clems4ever/authelia/session"
"github.com/clems4ever/authelia/authentication"
"github.com/clems4ever/authelia/authorization"
"github.com/clems4ever/authelia/configuration"
"github.com/clems4ever/authelia/logging"
"github.com/clems4ever/authelia/middlewares"
"github.com/clems4ever/authelia/notification"
"github.com/clems4ever/authelia/regulation"
"github.com/clems4ever/authelia/server"
"github.com/clems4ever/authelia/session"
"github.com/clems4ever/authelia/storage"
"github.com/sirupsen/logrus"
)
@ -70,8 +68,8 @@ func main() {
}
var storageProvider storage.Provider
if config.Storage.Mongo != nil {
storageProvider = storage.NewMongoProvider(*config.Storage.Mongo)
if config.Storage.SQL != nil {
storageProvider = storage.NewSQLProvider(*config.Storage.SQL)
} else if config.Storage.Local != nil {
storageProvider = storage.NewSQLiteProvider(config.Storage.Local.Path)
} else {

View File

@ -243,19 +243,19 @@ regulation:
# Configuration of the storage backend used to store data and secrets.
#
# You must use only an available configuration: local, mongo
# You must use only an available configuration: local, sql
storage:
# The directory where the DB files will be saved
## local:
## path: /var/lib/authelia/db.sqlite3
# Settings to connect to mongo server
mongo:
url: mongodb://127.0.0.1
# Settings to connect to SQL server
sql:
host: 127.0.0.1
port: 3306
database: authelia
auth:
username: authelia
password: authelia
password: mypassword
# Configuration of the notification system.
#

View File

@ -1,22 +1,21 @@
package schema
// MongoStorageConfiguration represents the configuration related to mongo connection.
type MongoStorageConfiguration struct {
URL string `yaml:"url"`
Database string `yaml:"database"`
Auth struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
} `yaml:"auth"`
}
// LocalStorageConfiguration represents the configuration when using local storage.
type LocalStorageConfiguration struct {
Path string `yaml:"path"`
}
// SQLStorageConfiguration represents the configuration of the SQL database
type SQLStorageConfiguration struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Database string `yaml:"database"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
// StorageConfiguration represents the configuration of the storage backend.
type StorageConfiguration struct {
Mongo *MongoStorageConfiguration `yaml:"mongo"`
Local *LocalStorageConfiguration `yaml:"local"`
SQL *SQLStorageConfiguration `yaml:"sql"`
}

View File

@ -0,0 +1,36 @@
package validator
import (
"errors"
"github.com/clems4ever/authelia/configuration/schema"
)
// ValidateSQLStorage validates storage configuration.
func ValidateSQLStorage(configuration *schema.StorageConfiguration, validator *schema.StructValidator) {
if configuration.Local == nil && configuration.SQL == nil {
validator.Push(errors.New("A storage configuration must be provided. It could be 'local' or 'sql'"))
}
if configuration.SQL != nil {
validateSQLConfiguration(configuration.SQL, validator)
} else if configuration.Local != nil {
validateLocalStorageConfiguration(configuration.Local, validator)
}
}
func validateSQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator) {
if configuration.Password != "" && configuration.Username == "" {
validator.Push(errors.New("Username and password must be provided"))
}
if configuration.Database == "" {
validator.Push(errors.New("A database must be provided"))
}
}
func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) {
if configuration.Path == "" {
validator.Push(errors.New("A file path must be provided with key 'path'"))
}
}

View File

@ -44,8 +44,8 @@ To run the unit tests written, run:
### Integration tests
Integration tests run with Mocha and are based on Selenium. They generally
require a complete environment made of several components like redis, mongo and a LDAP
to run. That's why [suites] have been created. At this point, the *basic* suite should
require a complete environment made of several components like redis, a SQL server and a
LDAP to run. That's why [suites] have been created. At this point, the *basic* suite should
already be running and you can run the tests related to this suite with the following
command:

View File

@ -4,7 +4,7 @@
2. [Deploy With Docker](#deploy-with-docker)
3. [Deploy nginx](#deploy-nginx)
4. [Discard components](#discard-components)
1. [Discard MongoDB](#discard-mongodb)
1. [Discard SQL Server](#discard-sql-server)
2. [Discard Redis](#discard-redis)
3. [Discard LDAP](#discard-ldap)
5. [FAQ](#faq)
@ -46,12 +46,11 @@ TODO
## Discard components
### Discard MongoDB
### Discard SQL server
There is an option in the configuration file to discard MongoDB and use
your local filesystem to store data in a sqlite3 database. This option will
therefore prevent you from running multiple instances of **Authelia** in
parallel.
There is an option in the configuration file to avoid using a SQL server and use
a local sqlite3 database instead. This option will therefore prevent you from running
multiple instances of **Authelia** in parallel.
Consequently, this option is not meant to be used in production or at least
not one that should scale out.

View File

@ -15,10 +15,10 @@ authentication and authorization requests for your entire infrastructure.
As **Authelia** will be key to your architecture, it requires several
components to make it highly-available. Deploying it in production means having
an LDAP server for storing the information about the users, a Redis cache to
store the user sessions in a distributed manner, a MongoDB to persist user
configurations and one or more nginx reverse proxies configured to be used with
Authelia. With such a setup **Authelia** can easily be scaled to multiple instances
to evenly handle the traffic.
store the user sessions in a distributed manner, a SQL server like MariaDB to
persist user configurations and one or more nginx reverse proxies configured to
be used with Authelia. With such a setup **Authelia** can easily be scaled to
multiple instances to evenly handle the traffic.
**NOTE:** If you don't have all those components, don't worry, there is a way to
deploy **Authelia** with only nginx. This is described in [Deployment for Devs].

View File

@ -3,18 +3,18 @@
Authelia is a single component in interaction with many others. Consequently, testing the features
is not as easy as we might think. In order to solve this problem, Authelia came up with the concept of
suite which is a kind of virtual environment for Authelia, it allows to create an environment made of
components such as nginx, redis or mongo in which Authelia can run and be tested.
components such as nginx, redis or mariadb in which Authelia can run and be tested.
This abstraction allows to prepare an environment for manual testing during development and also to
craft and run integration tests efficiently.
## Start a suite.
Starting a suite called *basic* is done with the following command:
Starting a suite called *Standalone* is done with the following command:
authelia-scripts suites start basic
authelia-scripts suites setup Standalone
It will start the suite and block until you hit ctrl-c to stop the suite.
It will deploy the environment of the suite and block until you hit ctrl-c to stop the suite.
## Run tests of a suite
@ -28,12 +28,12 @@ and this will run the tests related to the running suite.
### Run tests of non-running suite
However, if no suite is running and you still want to test a particular suite like *high-availability*.
However, if no suite is running and you still want to test a particular suite like *HighAvailability*.
You can do so with the next command:
authelia-scripts suites test high-availability
authelia-scripts suites test HighAvailability
This command will run the tests for the *high-availability* suite. Beware that running tests of a
This command will run the tests for the *HighAvailability* suite. Beware that running tests of a
non-running suite implies the tests run against the distributable version of Authelia instead of
the current development version. If you made some patches, you must build the distributable version
before running the test command:

View File

@ -1,5 +1,7 @@
#!/bin/bash
set -e
# Build the binary
go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go

View File

@ -0,0 +1,11 @@
version: "3"
services:
mariadb:
image: mariadb:10.4.10
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_USER=admin
- MYSQL_PASSWORD=password
- MYSQL_DATABASE=authelia
networks:
- authelianet

View File

@ -51,7 +51,7 @@ manual intervention.
## What do I need to know to deploy it in my cluster?
Given your cluster already runs a LDAP server, a Redis, a Mongo database,
Given your cluster already runs a LDAP server, a Redis, a SQL database,
a SMTP server and a nginx ingress-controller, you can deploy **Authelia**
and update your ingress configurations. An example is provided
[here](./authelia).

View File

@ -2,88 +2,25 @@
# Authelia configuration #
###############################################################
# The port to listen on
port: 80
# Log level
#
# Level of verbosity for logs
logs_level: debug
jwt_secret: an_unsecure_secret
# Default redirection URL
#
# If user tries to authenticate without any referer, Authelia
# does not know where to redirect the user to at the end of the
# authentication process.
# This parameter allows you to specify the default redirection
# URL Authelia will use in such a case.
#
# Note: this parameter is optional. If not provided, user won't
# be redirected upon successful authentication.
default_redirection_url: https://home.example.com:8080
# The authentication backend to use for verifying user passwords
# and retrieve information such as email address and groups
# users belong to.
#
# There are two supported backends: `ldap` and `file`.
authentication_backend:
# LDAP backend configuration.
#
# This backend allows Authelia to be scaled to more
# than one instance and therefore is recommended for
# production.
ldap:
# The url of the ldap server
url: ldap-service:389
# The base dn for every entries
base_dn: dc=example,dc=com
# An additional dn to define the scope to all users
additional_users_dn: ou=users
# The users filter used to find the user DN
# {0} is a matcher replaced by username.
# 'cn={0}' by default.
users_filter: (cn={0})
# An additional dn to define the scope of groups
additional_groups_dn: ou=groups
# The groups filter used for retrieving groups of a given user.
# {0} is a matcher replaced by username.
# {dn} is a matcher replaced by user DN.
# {uid} is a matcher replaced by user uid.
# 'member={dn}' by default.
groups_filter: (&(member={dn})(objectclass=groupOfNames))
# The attribute holding the name of the group
group_name_attribute: cn
# The attribute holding the mail address of the user
mail_attribute: mail
# The username and password of the admin user.
user: cn=admin,dc=example,dc=com
password: password
# File backend configuration.
#
# With this backend, the users database is stored in a file
# which is updated when users reset their passwords.
# Therefore, this backend is meant to be used in a dev environment
# and not in production since it prevents Authelia to be scaled to
# more than one instance.
#
## file:
## path: ./users_database.yml
# Access Control
#
# For more details about the configuration see config.template.yml at the root of the repo.
access_control:
default_policy: deny
@ -137,68 +74,27 @@ access_control:
policy: two_factor
# Configuration of session cookies
#
# The session cookies identify the user once logged in.
session:
# The secret to encrypt the session cookie.
secret: unsecure_password
# The time in ms before the cookie expires and session is reset.
expiration: 3600000 # 1 hour
# The inactivity time in ms before the session is reset.
inactivity: 300000 # 5 minutes
# The domain to protect.
# Note: the authenticator must also be in that domain. If empty, the cookie
# is restricted to the subdomain of the issuer.
domain: example.com
# The redis connection details
redis:
host: redis-service
port: 6379
# Configuration of the authentication regulation mechanism.
#
# This mechanism prevents attackers from brute forcing the first factor.
# It bans the user if too many attempts are done in a short period of
# time.
regulation:
# The number of failed login attempts before user is banned.
# Set it to 0 for disabling regulation.
max_retries: 3
# The length of time between login attempts before user is banned.
find_time: 120
# The length of time before a banned user can login again.
ban_time: 300
# Configuration of the storage backend used to store data and secrets.
#
# You must use only an available configuration: local, mongo
storage:
# The directory where the DB files will be saved
# local: /var/lib/authelia/store
# Settings to connect to mongo server
mongo:
url: mongodb://mongo-service
sql:
host: mariadb-service
port: 3306
database: authelia
# Configuration of the notification system.
#
# Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration.
# Use only an available configuration: filesystem, gmail
notifier:
# For testing purpose, notifications can be sent in a file
# filesystem:
# filename: /tmp/authelia/notification.txt
# Use a SMTP server for sending notifications
smtp:
host: 'mailcatcher-service'
port: 1025

View File

@ -20,7 +20,7 @@ start_dashboard() {
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')
}
# Spawn Redis and Mongo as backend for Authelia
# Spawn Redis and storage backend
# Please note they are not configured to be distributed on several machines
start_storage() {
kubectl apply -f storage

View File

@ -0,0 +1,54 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mariadb
namespace: authelia
labels:
app: mariadb
spec:
replicas: 1
selector:
matchLabels:
app: mariadb
template:
metadata:
labels:
app: mariadb
spec:
containers:
- name: mariadb
image: mariadb:10.4.10
ports:
- containerPort: 3306
env:
- name: SLAPD_ORGANISATION
value: MyCompany
- name: MYSQL_ROOT_PASSWORD
value: rootpassword
- name: MYSQL_USER
value: admin
- name: MYSQL_PASSWORD
value: password
- name: MYSQL_DATABASE
value: authelia
volumeMounts:
- name: data-volume
mountPath: /var/lib/mysql
volumes:
- name: data-volume
hostPath:
path: /data/storage/mysql
---
apiVersion: v1
kind: Service
metadata:
name: mariadb-service
namespace: authelia
spec:
selector:
app: mariadb
ports:
- protocol: TCP
port: 3306

View File

@ -1,48 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
namespace: authelia
labels:
app: mongo
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:3.4
ports:
- containerPort: 27017
volumeMounts:
- name: data-volume
mountPath: /data/db
- name: config-volume
mountPath: /data/configdb
volumes:
- name: data-volume
hostPath:
path: /data/storage/mongo/data
- name: config-volume
hostPath:
path: /data/storage/mongo/config
---
apiVersion: v1
kind: Service
metadata:
name: mongo-service
namespace: authelia
spec:
selector:
app: mongo
ports:
- protocol: TCP
port: 27017

14
go.mod
View File

@ -5,19 +5,15 @@ go 1.13
require (
github.com/Workiva/go-datastructures v1.0.50
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/cespare/reflex v0.2.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74
github.com/fasthttp/router v0.5.2
github.com/fasthttp/session v1.1.3
github.com/go-stack/stack v1.8.0 // indirect
github.com/go-sql-driver/mysql v1.4.1
github.com/golang/mock v1.3.1
github.com/golang/snappy v0.0.1 // indirect
github.com/google/martian v2.1.0+incompatible
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/pty v1.1.8 // indirect
github.com/mattn/go-sqlite3 v1.11.0
github.com/ogier/pflag v0.0.1 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
github.com/otiai10/copy v1.0.2
github.com/pquerna/otp v1.2.0
github.com/simia-tech/crypt v0.2.0
@ -27,9 +23,7 @@ require (
github.com/tebeka/selenium v0.9.9
github.com/tstranex/u2f v1.0.0
github.com/valyala/fasthttp v1.6.0
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
github.com/xdg/stringprep v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.1.2
google.golang.org/appengine v1.6.5 // indirect
gopkg.in/ldap.v3 v3.1.0
gopkg.in/yaml.v2 v2.2.4
)

42
go.sum
View File

@ -19,15 +19,11 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/cespare/reflex v0.2.0 h1:6d9WpWJseKjJvZEevKP7Pk42nPx2+BUTqmhNk8wZPwM=
github.com/cespare/reflex v0.2.0/go.mod h1:ooqOLJ4algvHP/oYvKWfWJ9tFUzCLDk5qkIJduMYrgI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -43,9 +39,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -53,9 +48,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -73,18 +67,17 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -95,11 +88,16 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -149,13 +147,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -175,6 +167,7 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -195,6 +188,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116161606-93218def8b18/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -229,7 +223,10 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -241,9 +238,14 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE=
gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,379 +0,0 @@
package storage
import (
"context"
"time"
"github.com/clems4ever/authelia/configuration/schema"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/clems4ever/authelia/models"
)
const (
prefered2FAMethodCollection = "prefered_2fa_method"
identityValidationTokensCollection = "identity_validation_tokens"
authenticationLogsCollection = "authentication_logs"
u2fRegistrationsCollection = "u2f_devices"
totpSecretsCollection = "totp_secrets"
)
// MongoProvider is a storage provider persisting data in a SQLite database.
type MongoProvider struct {
configuration schema.MongoStorageConfiguration
}
// NewMongoProvider construct a mongo provider.
func NewMongoProvider(configuration schema.MongoStorageConfiguration) *MongoProvider {
return &MongoProvider{configuration}
}
func (p *MongoProvider) connect() (*mongo.Client, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
clientOptions := options.Client().ApplyURI(p.configuration.URL)
if p.configuration.Auth.Username != "" && p.configuration.Auth.Password != "" {
credentials := options.Credential{
Username: p.configuration.Auth.Username,
Password: p.configuration.Auth.Password,
}
clientOptions.SetAuth(credentials)
}
return mongo.Connect(ctx, clientOptions)
}
type prefered2FAMethodDocument struct {
UserID string `bson:"userId"`
Method string `bson:"method"`
}
// LoadPrefered2FAMethod load the prefered method for 2FA from sqlite db.
func (p *MongoProvider) LoadPrefered2FAMethod(username string) (string, error) {
client, err := p.connect()
if err != nil {
return "", nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(prefered2FAMethodCollection)
res := prefered2FAMethodDocument{}
err = collection.FindOne(context.Background(),
bson.M{"userId": username}).
Decode(&res)
if err != nil {
if err == mongo.ErrNoDocuments {
return "", nil
}
return "", err
}
return res.Method, nil
}
// SavePrefered2FAMethod save the prefered method for 2FA in sqlite db.
func (p *MongoProvider) SavePrefered2FAMethod(username string, method string) error {
client, err := p.connect()
if err != nil {
return nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(prefered2FAMethodCollection)
updateOptions := options.ReplaceOptions{}
updateOptions.SetUpsert(true)
_, err = collection.ReplaceOne(context.Background(),
bson.M{"userId": username},
bson.M{"userId": username, "method": method},
&updateOptions)
if err != nil {
return err
}
return nil
}
// IdentityTokenDocument model for the identiy token documents.
type IdentityTokenDocument struct {
Token string `bson:"token"`
}
// FindIdentityVerificationToken look for an identity verification token in DB.
func (p *MongoProvider) FindIdentityVerificationToken(token string) (bool, error) {
client, err := p.connect()
if err != nil {
return false, nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(identityValidationTokensCollection)
res := IdentityTokenDocument{}
err = collection.FindOne(context.Background(),
bson.M{"token": token}).Decode(&res)
if err != nil {
if err == mongo.ErrNoDocuments {
return false, nil
}
return false, err
}
return true, nil
}
// SaveIdentityVerificationToken save an identity verification token in DB.
func (p *MongoProvider) SaveIdentityVerificationToken(token string) error {
client, err := p.connect()
if err != nil {
return nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(identityValidationTokensCollection)
options := options.InsertOneOptions{}
_, err = collection.InsertOne(context.Background(),
bson.M{"token": token},
&options)
if err != nil {
return err
}
return nil
}
// RemoveIdentityVerificationToken remove an identity verification token from the DB.
func (p *MongoProvider) RemoveIdentityVerificationToken(token string) error {
client, err := p.connect()
if err != nil {
return nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(identityValidationTokensCollection)
options := options.DeleteOptions{}
_, err = collection.DeleteOne(context.Background(),
bson.M{"token": token},
&options)
if err != nil {
return err
}
return nil
}
// TOTPSecretDocument model of document storing TOTP secrets
type TOTPSecretDocument struct {
UserID string `bson:"userId"`
Secret string `bson:"secret"`
}
// SaveTOTPSecret save a TOTP secret of a given user.
func (p *MongoProvider) SaveTOTPSecret(username string, secret string) error {
client, err := p.connect()
if err != nil {
return nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(totpSecretsCollection)
options := options.ReplaceOptions{}
options.SetUpsert(true)
_, err = collection.ReplaceOne(context.Background(),
bson.M{"userId": username},
bson.M{"userId": username, "secret": secret},
&options)
if err != nil {
return err
}
return nil
}
// LoadTOTPSecret load a TOTP secret given a username.
func (p *MongoProvider) LoadTOTPSecret(username string) (string, error) {
client, err := p.connect()
if err != nil {
return "", nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(totpSecretsCollection)
res := TOTPSecretDocument{}
err = collection.FindOne(context.Background(),
bson.M{"userId": username}).Decode(&res)
if err != nil {
if err == mongo.ErrNoDocuments {
return "", nil
}
return "", err
}
return res.Secret, nil
}
// U2FDeviceDocument model of document storing U2F device
type U2FDeviceDocument struct {
UserID string `bson:"userId"`
DeviceHandle []byte `bson:"deviceHandle"`
}
// SaveU2FDeviceHandle save a registered U2F device registration blob.
func (p *MongoProvider) SaveU2FDeviceHandle(username string, deviceBytes []byte) error {
client, err := p.connect()
if err != nil {
return nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(u2fRegistrationsCollection)
options := options.ReplaceOptions{}
options.SetUpsert(true)
_, err = collection.ReplaceOne(context.Background(),
bson.M{"userId": username},
bson.M{"userId": username, "deviceHandle": deviceBytes},
&options)
if err != nil {
return err
}
return nil
}
// LoadU2FDeviceHandle load a U2F device registration blob for a given username.
func (p *MongoProvider) LoadU2FDeviceHandle(username string) ([]byte, error) {
client, err := p.connect()
if err != nil {
return nil, nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(u2fRegistrationsCollection)
res := U2FDeviceDocument{}
err = collection.FindOne(context.Background(),
bson.M{"userId": username}).Decode(&res)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, ErrNoU2FDeviceHandle
}
return nil, err
}
return res.DeviceHandle, nil
}
// AuthenticationLogDocument model of document storing authentication logs
type AuthenticationLogDocument struct {
UserID string `bson:"userId"`
Time time.Time `bson:"time"`
Success bool `bson:"success"`
}
// AppendAuthenticationLog append a mark to the authentication log.
func (p *MongoProvider) AppendAuthenticationLog(attempt models.AuthenticationAttempt) error {
client, err := p.connect()
if err != nil {
return nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(authenticationLogsCollection)
options := options.InsertOneOptions{}
_, err = collection.InsertOne(context.Background(),
bson.M{
"userId": attempt.Username,
"time": attempt.Time,
"success": attempt.Successful,
},
&options)
if err != nil {
return err
}
return nil
}
// LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log.
func (p *MongoProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) {
client, err := p.connect()
if err != nil {
return nil, nil
}
defer client.Disconnect(context.Background())
collection := client.
Database(p.configuration.Database).
Collection(authenticationLogsCollection)
options := options.FindOptions{}
options.SetSort(bson.M{"time": -1})
cursor, err := collection.Find(context.Background(),
bson.M{
"$and": bson.M{
"userId": username,
"time": bson.M{"$gt": fromDate},
},
})
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, nil
}
return nil, err
}
res := []AuthenticationLogDocument{}
cursor.All(context.Background(), &res)
attempts := []models.AuthenticationAttempt{}
for _, r := range res {
attempt := models.AuthenticationAttempt{
Username: r.UserID,
Time: r.Time,
Successful: r.Success,
}
attempts = append(attempts, attempt)
}
return attempts, nil
}

View File

@ -0,0 +1,66 @@
package storage
import (
"database/sql"
"fmt"
"time"
"github.com/clems4ever/authelia/configuration/schema"
"github.com/clems4ever/authelia/logging"
_ "github.com/go-sql-driver/mysql" // Load the MySQL Driver used in the connection string.
)
// MySQLProvider is a MySQL provider
type MySQLProvider struct {
SQLProvider
}
// NewSQLProvider a SQL provider
func NewSQLProvider(configuration schema.SQLStorageConfiguration) *MySQLProvider {
connectionString := configuration.Username
if configuration.Password != "" {
connectionString += fmt.Sprintf(":%s", configuration.Password)
}
if connectionString != "" {
connectionString += "@"
}
address := configuration.Host
if configuration.Port > 0 {
address += fmt.Sprintf(":%d", configuration.Port)
}
connectionString += fmt.Sprintf("tcp(%s)", address)
if configuration.Database != "" {
connectionString += fmt.Sprintf("/%s", configuration.Database)
}
fmt.Println(connectionString)
db, err := sql.Open("mysql", connectionString)
if err != nil {
logging.Logger().Fatalf("Unable to connect to SQL database: %v", err)
}
for i := 0; i < 3; i++ {
if err = db.Ping(); err == nil {
logging.Logger().Debug("Connection to the database is established")
break
}
if i == 2 {
logging.Logger().Fatal("Aborting because connection to database failed")
}
logging.Logger().Errorf("Unable to ping database retrying in 10 seconds. error: %v", err)
time.Sleep(10 * time.Second)
}
provider := MySQLProvider{}
if err := provider.initialize(db); err != nil {
logging.Logger().Fatalf("Unable to initialize SQL database: %v", err)
}
return &provider
}

View File

@ -0,0 +1,212 @@
package storage
import (
"database/sql"
"time"
"github.com/clems4ever/authelia/models"
)
// SQLProvider is a storage provider persisting data in a SQL database.
type SQLProvider struct {
db *sql.DB
}
func (p *SQLProvider) initialize(db *sql.DB) error {
p.db = db
_, err := db.Exec("CREATE TABLE IF NOT EXISTS SecondFactorPreferences (username VARCHAR(100) PRIMARY KEY, method VARCHAR(10))")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS IdentityVerificationTokens (token VARCHAR(512))")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS TOTPSecrets (username VARCHAR(100) PRIMARY KEY, secret VARCHAR(64))")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS U2FDeviceHandles (username VARCHAR(100) PRIMARY KEY, deviceHandle BLOB)")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS AuthenticationLogs (username VARCHAR(100), successful BOOL, time INTEGER)")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS time ON AuthenticationLogs (time);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS username ON AuthenticationLogs (username);")
if err != nil {
return err
}
return nil
}
// LoadPrefered2FAMethod load the prefered method for 2FA from sqlite db.
func (p *SQLProvider) LoadPrefered2FAMethod(username string) (string, error) {
stmt, err := p.db.Prepare("SELECT method FROM SecondFactorPreferences WHERE username=?")
if err != nil {
return "", err
}
rows, err := stmt.Query(username)
defer rows.Close()
if err != nil {
return "", err
}
if rows.Next() {
var method string
err = rows.Scan(&method)
if err != nil {
return "", err
}
return method, nil
}
return "", nil
}
// SavePrefered2FAMethod save the prefered method for 2FA in sqlite db.
func (p *SQLProvider) SavePrefered2FAMethod(username string, method string) error {
stmt, err := p.db.Prepare("REPLACE INTO SecondFactorPreferences (username, method) VALUES (?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(username, method)
return err
}
// FindIdentityVerificationToken look for an identity verification token in DB.
func (p *SQLProvider) FindIdentityVerificationToken(token string) (bool, error) {
stmt, err := p.db.Prepare("SELECT token FROM IdentityVerificationTokens WHERE token=?")
if err != nil {
return false, err
}
var found string
err = stmt.QueryRow(token).Scan(&found)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, err
}
return true, nil
}
// SaveIdentityVerificationToken save an identity verification token in DB.
func (p *SQLProvider) SaveIdentityVerificationToken(token string) error {
stmt, err := p.db.Prepare("INSERT INTO IdentityVerificationTokens (token) VALUES (?)")
if err != nil {
return err
}
_, err = stmt.Exec(token)
return err
}
// RemoveIdentityVerificationToken remove an identity verification token from the DB.
func (p *SQLProvider) RemoveIdentityVerificationToken(token string) error {
stmt, err := p.db.Prepare("DELETE FROM IdentityVerificationTokens WHERE token=?")
if err != nil {
return err
}
_, err = stmt.Exec(token)
return err
}
// SaveTOTPSecret save a TOTP secret of a given user.
func (p *SQLProvider) SaveTOTPSecret(username string, secret string) error {
stmt, err := p.db.Prepare("REPLACE INTO TOTPSecrets (username, secret) VALUES (?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(username, secret)
return err
}
// LoadTOTPSecret load a TOTP secret given a username.
func (p *SQLProvider) LoadTOTPSecret(username string) (string, error) {
stmt, err := p.db.Prepare("SELECT secret FROM TOTPSecrets WHERE username=?")
if err != nil {
return "", err
}
var secret string
err = stmt.QueryRow(username).Scan(&secret)
if err != nil {
if err == sql.ErrNoRows {
return "", nil
}
return "", err
}
return secret, nil
}
// SaveU2FDeviceHandle save a registered U2F device registration blob.
func (p *SQLProvider) SaveU2FDeviceHandle(username string, keyHandle []byte) error {
stmt, err := p.db.Prepare("REPLACE INTO U2FDeviceHandles (username, deviceHandle) VALUES (?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(username, keyHandle)
return err
}
// LoadU2FDeviceHandle load a U2F device registration blob for a given username.
func (p *SQLProvider) LoadU2FDeviceHandle(username string) ([]byte, error) {
stmt, err := p.db.Prepare("SELECT deviceHandle FROM U2FDeviceHandles WHERE username=?")
if err != nil {
return nil, err
}
var deviceHandle []byte
err = stmt.QueryRow(username).Scan(&deviceHandle)
if err != nil {
if err == sql.ErrNoRows {
return nil, ErrNoU2FDeviceHandle
}
return nil, err
}
return deviceHandle, nil
}
// AppendAuthenticationLog append a mark to the authentication log.
func (p *SQLProvider) AppendAuthenticationLog(attempt models.AuthenticationAttempt) error {
stmt, err := p.db.Prepare("INSERT INTO AuthenticationLogs (username, successful, time) VALUES (?, ?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(attempt.Username, attempt.Successful, attempt.Time.Unix())
return err
}
// LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log.
func (p *SQLProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) {
rows, err := p.db.Query("SELECT successful, time FROM AuthenticationLogs WHERE time>? AND username=? ORDER BY time DESC",
fromDate.Unix(), username)
if err != nil {
return nil, err
}
attempts := make([]models.AuthenticationAttempt, 0, 10)
for rows.Next() {
attempt := models.AuthenticationAttempt{
Username: username,
}
var t int64
err = rows.Scan(&attempt.Successful, &t)
attempt.Time = time.Unix(t, 0)
if err != nil {
return nil, err
}
attempts = append(attempts, attempt)
}
return attempts, nil
}

View File

@ -2,228 +2,26 @@ package storage
import (
"database/sql"
"time"
"github.com/clems4ever/authelia/models"
"github.com/clems4ever/authelia/logging"
_ "github.com/mattn/go-sqlite3" // Load the SQLite Driver used in the connection string.
)
// SQLiteProvider is a storage provider persisting data in a SQLite database.
// SQLiteProvider is a sqlite3 provider
type SQLiteProvider struct {
db *sql.DB
SQLProvider
}
// NewSQLiteProvider construct a sqlite provider.
func NewSQLiteProvider(path string) *SQLiteProvider {
provider := SQLiteProvider{}
err := provider.initialize(path)
db, err := sql.Open("sqlite3", path)
if err != nil {
logging.Logger().Fatalf("Unable to create SQLite database %s: %s", path, err)
}
provider := SQLiteProvider{}
if err := provider.initialize(db); err != nil {
logging.Logger().Fatalf("Unable to initialize SQLite database %s: %s", path, err)
}
return &provider
}
func (p *SQLiteProvider) initialize(path string) error {
db, err := sql.Open("sqlite3", path)
if err != nil {
return err
}
p.db = db
_, err = db.Exec("CREATE TABLE IF NOT EXISTS SecondFactorPreferences (username VARCHAR(100) PRIMARY KEY, method VARCHAR(10))")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS IdentityVerificationTokens (token VARCHAR(512))")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS TOTPSecrets (username VARCHAR(100) PRIMARY KEY, secret VARCHAR(64))")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS U2FDeviceHandles (username VARCHAR(100) PRIMARY KEY, deviceHandle BLOB)")
if err != nil {
return err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS AuthenticationLogs (username VARCHAR(100), successful BOOL, time INTEGER)")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS time ON AuthenticationLogs (time);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS username ON AuthenticationLogs (username);")
if err != nil {
return err
}
return nil
}
// LoadPrefered2FAMethod load the prefered method for 2FA from sqlite db.
func (p *SQLiteProvider) LoadPrefered2FAMethod(username string) (string, error) {
stmt, err := p.db.Prepare("SELECT method FROM SecondFactorPreferences WHERE username=?")
if err != nil {
return "", err
}
rows, err := stmt.Query(username)
defer rows.Close()
if err != nil {
return "", err
}
if rows.Next() {
var method string
err = rows.Scan(&method)
if err != nil {
return "", err
}
return method, nil
}
return "", nil
}
// SavePrefered2FAMethod save the prefered method for 2FA in sqlite db.
func (p *SQLiteProvider) SavePrefered2FAMethod(username string, method string) error {
stmt, err := p.db.Prepare("INSERT OR REPLACE INTO SecondFactorPreferences (username, method) VALUES (?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(username, method)
return err
}
// FindIdentityVerificationToken look for an identity verification token in DB.
func (p *SQLiteProvider) FindIdentityVerificationToken(token string) (bool, error) {
stmt, err := p.db.Prepare("SELECT token FROM IdentityVerificationTokens WHERE token=?")
if err != nil {
return false, err
}
var found string
err = stmt.QueryRow(token).Scan(&found)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, err
}
return true, nil
}
// SaveIdentityVerificationToken save an identity verification token in DB.
func (p *SQLiteProvider) SaveIdentityVerificationToken(token string) error {
stmt, err := p.db.Prepare("INSERT INTO IdentityVerificationTokens (token) VALUES (?)")
if err != nil {
return err
}
_, err = stmt.Exec(token)
return err
}
// RemoveIdentityVerificationToken remove an identity verification token from the DB.
func (p *SQLiteProvider) RemoveIdentityVerificationToken(token string) error {
stmt, err := p.db.Prepare("DELETE FROM IdentityVerificationTokens WHERE token=?")
if err != nil {
return err
}
_, err = stmt.Exec(token)
return err
}
// SaveTOTPSecret save a TOTP secret of a given user.
func (p *SQLiteProvider) SaveTOTPSecret(username string, secret string) error {
stmt, err := p.db.Prepare("INSERT OR REPLACE INTO TOTPSecrets (username, secret) VALUES (?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(username, secret)
return err
}
// LoadTOTPSecret load a TOTP secret given a username.
func (p *SQLiteProvider) LoadTOTPSecret(username string) (string, error) {
stmt, err := p.db.Prepare("SELECT secret FROM TOTPSecrets WHERE username=?")
if err != nil {
return "", err
}
var secret string
err = stmt.QueryRow(username).Scan(&secret)
if err != nil {
if err == sql.ErrNoRows {
return "", nil
}
return "", err
}
return secret, nil
}
// SaveU2FDeviceHandle save a registered U2F device registration blob.
func (p *SQLiteProvider) SaveU2FDeviceHandle(username string, keyHandle []byte) error {
stmt, err := p.db.Prepare("INSERT OR REPLACE INTO U2FDeviceHandles (username, deviceHandle) VALUES (?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(username, keyHandle)
return err
}
// LoadU2FDeviceHandle load a U2F device registration blob for a given username.
func (p *SQLiteProvider) LoadU2FDeviceHandle(username string) ([]byte, error) {
stmt, err := p.db.Prepare("SELECT deviceHandle FROM U2FDeviceHandles WHERE username=?")
if err != nil {
return nil, err
}
var deviceHandle []byte
err = stmt.QueryRow(username).Scan(&deviceHandle)
if err != nil {
if err == sql.ErrNoRows {
return nil, ErrNoU2FDeviceHandle
}
return nil, err
}
return deviceHandle, nil
}
// AppendAuthenticationLog append a mark to the authentication log.
func (p *SQLiteProvider) AppendAuthenticationLog(attempt models.AuthenticationAttempt) error {
stmt, err := p.db.Prepare("INSERT INTO AuthenticationLogs (username, successful, time) VALUES (?, ?, ?)")
if err != nil {
return err
}
_, err = stmt.Exec(attempt.Username, attempt.Successful, attempt.Time.Unix())
return err
}
// LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log.
func (p *SQLiteProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) {
rows, err := p.db.Query("SELECT successful, time FROM AuthenticationLogs WHERE time>? AND username=? ORDER BY time DESC",
fromDate.Unix(), username)
if err != nil {
return nil, err
}
attempts := make([]models.AuthenticationAttempt, 0, 10)
for rows.Next() {
attempt := models.AuthenticationAttempt{
Username: username,
}
var t int64
err = rows.Scan(&attempt.Successful, &t)
attempt.Time = time.Unix(t, 0)
if err != nil {
return nil, err
}
attempts = append(attempts, attempt)
}
return attempts, nil
}

View File

@ -220,19 +220,15 @@ regulation:
# Configuration of the storage backend used to store data and secrets.
#
# You must use only an available configuration: local, mongo
# You must use only an available configuration: local, sql
storage:
# The directory where the DB files will be saved
## local:
## path: /var/lib/authelia/store
# Settings to connect to mongo server
mongo:
url: mongodb://mongo
# Settings to connect to mariadb server
sql:
host: mariadb
port: 3306
database: authelia
auth:
username: authelia
password: authelia
username: admin
password: password
# Configuration of the notification system.
#

View File

@ -22,12 +22,12 @@ session:
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
mongo:
url: mongodb://mongo
sql:
host: mariadb
port: 3306
database: authelia
auth:
username: authelia
password: authelia
username: admin
password: password
# TOTP Issuer Name
#

View File

@ -1,6 +1,7 @@
package suites
import (
"fmt"
"os"
"os/exec"
"strings"
@ -38,6 +39,13 @@ func (de *DockerEnvironment) Up(suitePath string) error {
return cmd.Run()
}
// Restart restarts a service
func (de *DockerEnvironment) Restart(suitePath, service string) error {
cmd := de.createCommandWithStdout(fmt.Sprintf("restart %s", service))
cmd.Env = append(os.Environ(), "SUITE_PATH="+suitePath)
return cmd.Run()
}
// Down spawn a docker environment
func (de *DockerEnvironment) Down(suitePath string) error {
cmd := de.createCommandWithStdout("down -v")

View File

@ -1,39 +0,0 @@
package suites
import (
"time"
)
var basicSuiteName = "Basic"
func init() {
dockerEnvironment := NewDockerEnvironment([]string{
"docker-compose.yml",
"example/compose/authelia/docker-compose.backend.yml",
"example/compose/authelia/docker-compose.frontend.yml",
"example/compose/nginx/backend/docker-compose.yml",
"example/compose/nginx/portal/docker-compose.yml",
"example/compose/smtp/docker-compose.yml",
})
setup := func(suitePath string) error {
if err := dockerEnvironment.Up(suitePath); err != nil {
return err
}
return waitUntilAutheliaIsReady(dockerEnvironment)
}
teardown := func(suitePath string) error {
return dockerEnvironment.Down(suitePath)
}
GlobalRegistry.Register(basicSuiteName, Suite{
TestTimeout: 1 * time.Minute,
SetUp: setup,
SetUpTimeout: 5 * time.Minute,
TearDown: teardown,
TearDownTimeout: 1 * time.Minute,
Description: `This suite is used to test Authelia in a standalone
configuration with in-memory sessions and a local sqlite db stored on disk`,
})
}

View File

@ -1,17 +0,0 @@
package suites
import (
"testing"
)
type BasicSuite struct {
*SeleniumSuite
}
func NewBasicSuite() *BasicSuite {
return &BasicSuite{SeleniumSuite: new(SeleniumSuite)}
}
func TestBasicSuite(t *testing.T) {
RunTypescriptSuite(t, basicSuiteName)
}

View File

@ -11,7 +11,7 @@ func init() {
"docker-compose.yml",
"example/compose/authelia/docker-compose.backend.yml",
"example/compose/authelia/docker-compose.frontend.yml",
"example/compose/mongo/docker-compose.yml",
"example/compose/mariadb/docker-compose.yml",
"example/compose/redis/docker-compose.yml",
"example/compose/nginx/backend/docker-compose.yml",
"example/compose/nginx/portal/docker-compose.yml",

View File

@ -14,6 +14,5 @@ func NewHighAvailabilitySuite() *HighAvailabilitySuite {
func TestHighAvailabilitySuite(t *testing.T) {
RunTypescriptSuite(t, highAvailabilitySuiteName)
TestRunOneFactor(t)
}

View File

@ -4,7 +4,7 @@ import (
"time"
)
var mongoSuiteName = "Mongo"
var mariadbSuiteName = "Mariadb"
func init() {
dockerEnvironment := NewDockerEnvironment([]string{
@ -14,14 +14,12 @@ func init() {
"example/compose/nginx/backend/docker-compose.yml",
"example/compose/nginx/portal/docker-compose.yml",
"example/compose/smtp/docker-compose.yml",
"example/compose/mongo/docker-compose.yml",
"example/compose/mariadb/docker-compose.yml",
"example/compose/ldap/docker-compose.yml",
})
setup := func(suitePath string) error {
err := dockerEnvironment.Up(suitePath)
if err != nil {
if err := dockerEnvironment.Up(suitePath); err != nil {
return err
}
@ -33,9 +31,9 @@ func init() {
return err
}
GlobalRegistry.Register(mongoSuiteName, Suite{
GlobalRegistry.Register(mariadbSuiteName, Suite{
SetUp: setup,
SetUpTimeout: 5 * time.Minute,
SetUpTimeout: 3 * time.Minute,
TearDown: teardown,
TearDownTimeout: 2 * time.Minute,
})

View File

@ -6,15 +6,15 @@ import (
"github.com/stretchr/testify/suite"
)
type MongoSuite struct {
type MariadbSuite struct {
*SeleniumSuite
}
func NewMongoSuite() *MongoSuite {
return &MongoSuite{SeleniumSuite: new(SeleniumSuite)}
func NewMariadbSuite() *MariadbSuite {
return &MariadbSuite{SeleniumSuite: new(SeleniumSuite)}
}
func TestMongoSuite(t *testing.T) {
func TestMariadbSuite(t *testing.T) {
suite.Run(t, NewOneFactorSuite())
suite.Run(t, NewTwoFactorSuite())
}

View File

@ -36,5 +36,7 @@ func init() {
SetUpTimeout: 5 * time.Minute,
TearDown: teardown,
TearDownTimeout: 5 * time.Minute,
Description: `This suite is used to test Authelia in a standalone
configuration with in-memory sessions and a local sqlite db stored on disk`,
})
}

View File

@ -17,4 +17,6 @@ func NewStandaloneSuite() *StandaloneSuite {
func TestStandaloneSuite(t *testing.T) {
suite.Run(t, NewOneFactorSuite())
suite.Run(t, NewTwoFactorSuite())
RunTypescriptSuite(t, standaloneSuiteName)
}

View File

@ -2,140 +2,34 @@
# Authelia configuration #
###############################################################
# The port to listen on
port: 9091
# Log level
#
# Level of verbosity for logs
logs_level: debug
jwt_secret: a_secret
# Default redirection URL
#
# If user tries to authenticate without any referer, Authelia
# does not know where to redirect the user to at the end of the
# authentication process.
# This parameter allows you to specify the default redirection
# URL Authelia will use in such a case.
#
# Note: this parameter is optional. If not provided, user won't
# be redirected upon successful authentication.
default_redirection_url: https://home.example.com:8080/
# TOTP Issuer Name
#
# This will be the issuer name displayed in Google Authenticator
# See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names
totp:
issuer: authelia.com
# Duo Push API
#
# Parameters used to contact the Duo API. Those are generated when you protect an application
# of type "Partner Auth API" in the management panel.
duo_api:
hostname: api-123456789.example.com
integration_key: ABCDEF
secret_key: 1234567890abcdefghifjkl
# The authentication backend to use for verifying user passwords
# and retrieve information such as email address and groups
# users belong to.
#
# There are two supported backends: `ldap` and `file`.
authentication_backend:
# LDAP backend configuration.
#
# This backend allows Authelia to be scaled to more
# than one instance and therefore is recommended for
# production.
ldap:
# The url of the ldap server
url: ldap://127.0.0.1
# The base dn for every entries
base_dn: dc=example,dc=com
# An additional dn to define the scope to all users
additional_users_dn: ou=users
# The users filter used to find the user DN
# {0} is a matcher replaced by username.
# 'cn={0}' by default.
users_filter: cn={0}
# An additional dn to define the scope of groups
users_filter: (cn={0})
additional_groups_dn: ou=groups
# The groups filter used for retrieving groups of a given user.
# {0} is a matcher replaced by username.
# {dn} is a matcher replaced by user DN.
# {uid} is a matcher replaced by user uid.
# 'member={dn}' by default.
groups_filter: (&(member={dn})(objectclass=groupOfNames))
# The attribute holding the name of the group
group_name_attribute: cn
# The attribute holding the mail address of the user
mail_attribute: mail
# The username and password of the admin user.
user: cn=admin,dc=example,dc=com
password: password
# File backend configuration.
#
# With this backend, the users database is stored in a file
# which is updated when users reset their passwords.
# Therefore, this backend is meant to be used in a dev environment
# and not in production since it prevents Authelia to be scaled to
# more than one instance.
#
## file:
## path: ./users_database.yml
# Access Control
#
# Access control is a list of rules defining the authorizations applied for one
# resource to users or group of users.
#
# If 'access_control' is not defined, ACL rules are disabled and the `bypass`
# rule is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
# the rules defined.
#
# Note: One can use the wildcard * to match any subdomain.
# It must stand at the beginning of the pattern. (example: *.mydomain.com)
#
# Note: You must put patterns containing wildcards between simple quotes for the YAML
# to be syntaxically correct.
#
# Definition: A `rule` is an object with the following keys: `domain`, `subject`,
# `policy` and `resources`.
#
# - `domain` defines which domain or set of domains the rule applies to.
#
# - `subject` defines the subject to apply authorizations to. This parameter is
# optional and matching any user if not provided. If provided, the parameter
# represents either a user or a group. It should be of the form 'user:<username>'
# or 'group:<groupname>'.
#
# - `policy` is the policy to apply to resources. It must be either `bypass`,
# `one_factor`, `two_factor` or `deny`.
#
# - `resources` is a list of regular expressions that matches a set of resources to
# apply the policy to. This parameter is optional and matches any resource if not
# provided.
#
# Note: the order of the rules is important. The first policy matching
# (domain, resource, subject) applies.
access_control:
# Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`.
# It is the policy applied to any resource if there is no policy to be applied
# to the user.
default_policy: deny
rules:
@ -194,86 +88,31 @@ access_control:
subject: 'user:bob'
policy: two_factor
# Configuration of session cookies
#
# The session cookies identify the user once logged in.
session:
# The name of the session cookie. (default: authelia_session).
name: authelia_session
# The secret to encrypt the session cookie.
secret: unsecure_session_secret
# The time in ms before the cookie expires and session is reset.
expiration: 3600000 # 1 hour
# The inactivity time in ms before the session is reset.
inactivity: 300000 # 5 minutes
# The domain to protect.
# Note: the authenticator must also be in that domain. If empty, the cookie
# is restricted to the subdomain of the issuer.
domain: example.com
# The redis connection details
redis:
host: 127.0.0.1
port: 6379
password: authelia
# Configuration of the authentication regulation mechanism.
#
# This mechanism prevents attackers from brute forcing the first factor.
# It bans the user if too many attempts are done in a short period of
# time.
regulation:
# The number of failed login attempts before user is banned.
# Set it to 0 to disable regulation.
max_retries: 3
# The time range during which the user can attempt login before being banned.
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
find_time: 120
# The length of time before a banned user can login again.
ban_time: 300
# Configuration of the storage backend used to store data and secrets.
#
# You must use only an available configuration: local, mongo
storage:
# The directory where the DB files will be saved
## local:
## path: /var/lib/authelia/store
# Settings to connect to mongo server
mongo:
url: mongodb://127.0.0.1
sql:
host: 127.0.0.1
port: 3306
database: authelia
auth:
username: authelia
password: authelia
# Configuration of the notification system.
#
# Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration.
# Use only an available configuration: filesystem, gmail
notifier:
# For testing purpose, notifications can be sent in a file
## filesystem:
## filename: /tmp/authelia/notification.txt
# Use your email account to send the notifications. You can use an app password.
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
## email:
## username: user@example.com
## password: yourpassword
## sender: admin@example.com
## service: gmail
# Use a SMTP server for sending notifications
smtp:
username: test
password: password

View File

@ -5,7 +5,7 @@ all components making Authelia highly available.
## Components
This suite will spawn nginx as the edge reverse proxy, redis and mongo for storing
This suite will spawn nginx as the edge reverse proxy, redis and mariadb for storing
user sessions and configurations, LDAP for storing user accounts and authenticating,
as well as a few helpers such as a fake webmail to receive e-mails sent by Authelia
and httpbin to check headers forwarded by Authelia.

View File

@ -2,7 +2,7 @@ const composeFiles = [
'docker-compose.yml',
'example/compose/authelia/docker-compose.backend.yml',
'example/compose/authelia/docker-compose.frontend.yml',
'example/compose/mongo/docker-compose.yml',
'example/compose/mariadb/docker-compose.yml',
'example/compose/redis/docker-compose.yml',
'example/compose/nginx/backend/docker-compose.yml',
'example/compose/nginx/portal/docker-compose.yml',

View File

@ -4,17 +4,19 @@ import WithDriver from "../../../helpers/context/WithDriver";
import Logout from "../../../helpers/Logout";
import { composeFiles } from '../environment';
import DockerCompose from "../../../helpers/context/DockerCompose";
import sleep from "../../../helpers/utils/sleep";
export default function () {
const dockerCompose = new DockerCompose(composeFiles);
WithDriver();
it.only("should be able to login after mongo restarts", async function () {
it.only("should be able to login after mariadb restarts", async function () {
this.timeout(30000);
const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
await dockerCompose.restart('mongo');
await dockerCompose.restart('mariadb');
await sleep(2000);
await Logout(this.driver);
await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html");

View File

@ -1,5 +1,5 @@
import AutheliaSuite from "../../helpers/context/AutheliaSuite";
import MongoConnectionRecovery from "./scenarii/MongoConnectionRecovery";
import MariaConnectionRecovery from "./scenarii/MariaConnectionRecovery";
import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly";
import AccessControl from "./scenarii/AccessControl";
import CustomHeadersForwarded from "./scenarii/CustomHeadersForwarded";
@ -12,7 +12,7 @@ AutheliaSuite(__dirname, function () {
describe('Custom headers forwarded to backend', CustomHeadersForwarded);
describe('Access control', AccessControl);
describe('Mongo broken connection recovery', MongoConnectionRecovery);
describe('Mariadb broken connection recovery', MariaConnectionRecovery);
describe('Enforce internal redirections only', EnforceInternalRedirectionsOnly);
describe('Basic authentication', BasicAuthentication);
describe('Authelia restart', AutheliaRestart);