Deprecate mongo and add mariadb as storage backend option.
parent
fc099221f7
commit
bd19ee48fd
|
@ -8,7 +8,7 @@ that the system is more reliable overall.
|
||||||
Majors changes:
|
Majors changes:
|
||||||
* The configuration mostly remained the same, only one major key has been added: `jwt_secret` and one key removed: `secure` from the
|
* 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.
|
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.
|
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.
|
* 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.
|
* 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
|
* 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
|
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,
|
* 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.
|
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.
|
* Flush your collections (beware you will lose your previous configuration: U2F, TOTP devices). New collections will be created by Authelia.
|
||||||
|
|
|
@ -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.
|
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
|
### Removal of flag secure for SMTP notifier
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const dockerPullCommandLine = "docker-compose -f docker-compose.yml " +
|
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/redis/docker-compose.yml " +
|
||||||
"-f example/compose/nginx/portal/docker-compose.yml " +
|
"-f example/compose/nginx/portal/docker-compose.yml " +
|
||||||
"-f example/compose/smtp/docker-compose.yml " +
|
"-f example/compose/smtp/docker-compose.yml " +
|
||||||
|
|
|
@ -6,17 +6,15 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/clems4ever/authelia/regulation"
|
|
||||||
|
|
||||||
"github.com/clems4ever/authelia/session"
|
|
||||||
|
|
||||||
"github.com/clems4ever/authelia/authentication"
|
"github.com/clems4ever/authelia/authentication"
|
||||||
"github.com/clems4ever/authelia/authorization"
|
"github.com/clems4ever/authelia/authorization"
|
||||||
"github.com/clems4ever/authelia/configuration"
|
"github.com/clems4ever/authelia/configuration"
|
||||||
"github.com/clems4ever/authelia/logging"
|
"github.com/clems4ever/authelia/logging"
|
||||||
"github.com/clems4ever/authelia/middlewares"
|
"github.com/clems4ever/authelia/middlewares"
|
||||||
"github.com/clems4ever/authelia/notification"
|
"github.com/clems4ever/authelia/notification"
|
||||||
|
"github.com/clems4ever/authelia/regulation"
|
||||||
"github.com/clems4ever/authelia/server"
|
"github.com/clems4ever/authelia/server"
|
||||||
|
"github.com/clems4ever/authelia/session"
|
||||||
"github.com/clems4ever/authelia/storage"
|
"github.com/clems4ever/authelia/storage"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -70,8 +68,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var storageProvider storage.Provider
|
var storageProvider storage.Provider
|
||||||
if config.Storage.Mongo != nil {
|
if config.Storage.SQL != nil {
|
||||||
storageProvider = storage.NewMongoProvider(*config.Storage.Mongo)
|
storageProvider = storage.NewSQLProvider(*config.Storage.SQL)
|
||||||
} else if config.Storage.Local != nil {
|
} else if config.Storage.Local != nil {
|
||||||
storageProvider = storage.NewSQLiteProvider(config.Storage.Local.Path)
|
storageProvider = storage.NewSQLiteProvider(config.Storage.Local.Path)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -243,19 +243,19 @@ regulation:
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets.
|
# 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:
|
storage:
|
||||||
# The directory where the DB files will be saved
|
# The directory where the DB files will be saved
|
||||||
## local:
|
## local:
|
||||||
## path: /var/lib/authelia/db.sqlite3
|
## path: /var/lib/authelia/db.sqlite3
|
||||||
|
|
||||||
# Settings to connect to mongo server
|
# Settings to connect to SQL server
|
||||||
mongo:
|
sql:
|
||||||
url: mongodb://127.0.0.1
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
database: authelia
|
database: authelia
|
||||||
auth:
|
username: authelia
|
||||||
username: authelia
|
password: mypassword
|
||||||
password: authelia
|
|
||||||
|
|
||||||
# Configuration of the notification system.
|
# Configuration of the notification system.
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
package schema
|
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.
|
// LocalStorageConfiguration represents the configuration when using local storage.
|
||||||
type LocalStorageConfiguration struct {
|
type LocalStorageConfiguration struct {
|
||||||
Path string `yaml:"path"`
|
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.
|
// StorageConfiguration represents the configuration of the storage backend.
|
||||||
type StorageConfiguration struct {
|
type StorageConfiguration struct {
|
||||||
Mongo *MongoStorageConfiguration `yaml:"mongo"`
|
|
||||||
Local *LocalStorageConfiguration `yaml:"local"`
|
Local *LocalStorageConfiguration `yaml:"local"`
|
||||||
|
SQL *SQLStorageConfiguration `yaml:"sql"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,8 +44,8 @@ To run the unit tests written, run:
|
||||||
### Integration tests
|
### Integration tests
|
||||||
|
|
||||||
Integration tests run with Mocha and are based on Selenium. They generally
|
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
|
require a complete environment made of several components like redis, a SQL server and a
|
||||||
to run. That's why [suites] have been created. At this point, the *basic* suite should
|
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
|
already be running and you can run the tests related to this suite with the following
|
||||||
command:
|
command:
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
2. [Deploy With Docker](#deploy-with-docker)
|
2. [Deploy With Docker](#deploy-with-docker)
|
||||||
3. [Deploy nginx](#deploy-nginx)
|
3. [Deploy nginx](#deploy-nginx)
|
||||||
4. [Discard components](#discard-components)
|
4. [Discard components](#discard-components)
|
||||||
1. [Discard MongoDB](#discard-mongodb)
|
1. [Discard SQL Server](#discard-sql-server)
|
||||||
2. [Discard Redis](#discard-redis)
|
2. [Discard Redis](#discard-redis)
|
||||||
3. [Discard LDAP](#discard-ldap)
|
3. [Discard LDAP](#discard-ldap)
|
||||||
5. [FAQ](#faq)
|
5. [FAQ](#faq)
|
||||||
|
@ -46,12 +46,11 @@ TODO
|
||||||
|
|
||||||
## Discard components
|
## Discard components
|
||||||
|
|
||||||
### Discard MongoDB
|
### Discard SQL server
|
||||||
|
|
||||||
There is an option in the configuration file to discard MongoDB and use
|
There is an option in the configuration file to avoid using a SQL server and use
|
||||||
your local filesystem to store data in a sqlite3 database. This option will
|
a local sqlite3 database instead. This option will therefore prevent you from running
|
||||||
therefore prevent you from running multiple instances of **Authelia** in
|
multiple instances of **Authelia** in parallel.
|
||||||
parallel.
|
|
||||||
Consequently, this option is not meant to be used in production or at least
|
Consequently, this option is not meant to be used in production or at least
|
||||||
not one that should scale out.
|
not one that should scale out.
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@ authentication and authorization requests for your entire infrastructure.
|
||||||
As **Authelia** will be key to your architecture, it requires several
|
As **Authelia** will be key to your architecture, it requires several
|
||||||
components to make it highly-available. Deploying it in production means having
|
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
|
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
|
store the user sessions in a distributed manner, a SQL server like MariaDB to
|
||||||
configurations and one or more nginx reverse proxies configured to be used with
|
persist user configurations and one or more nginx reverse proxies configured to
|
||||||
Authelia. With such a setup **Authelia** can easily be scaled to multiple instances
|
be used with Authelia. With such a setup **Authelia** can easily be scaled to
|
||||||
to evenly handle the traffic.
|
multiple instances to evenly handle the traffic.
|
||||||
|
|
||||||
**NOTE:** If you don't have all those components, don't worry, there is a way to
|
**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].
|
deploy **Authelia** with only nginx. This is described in [Deployment for Devs].
|
||||||
|
|
|
@ -3,18 +3,18 @@
|
||||||
Authelia is a single component in interaction with many others. Consequently, testing the features
|
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
|
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
|
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
|
This abstraction allows to prepare an environment for manual testing during development and also to
|
||||||
craft and run integration tests efficiently.
|
craft and run integration tests efficiently.
|
||||||
|
|
||||||
## Start a suite.
|
## 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
|
## 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
|
### 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:
|
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
|
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
|
the current development version. If you made some patches, you must build the distributable version
|
||||||
before running the test command:
|
before running the test command:
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
# Build the binary
|
# Build the binary
|
||||||
go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go
|
go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -51,7 +51,7 @@ manual intervention.
|
||||||
|
|
||||||
## What do I need to know to deploy it in my cluster?
|
## 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**
|
a SMTP server and a nginx ingress-controller, you can deploy **Authelia**
|
||||||
and update your ingress configurations. An example is provided
|
and update your ingress configurations. An example is provided
|
||||||
[here](./authelia).
|
[here](./authelia).
|
||||||
|
|
|
@ -2,88 +2,25 @@
|
||||||
# Authelia configuration #
|
# Authelia configuration #
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
# The port to listen on
|
|
||||||
port: 80
|
port: 80
|
||||||
|
|
||||||
# Log level
|
|
||||||
#
|
|
||||||
# Level of verbosity for logs
|
|
||||||
logs_level: debug
|
logs_level: debug
|
||||||
|
|
||||||
jwt_secret: an_unsecure_secret
|
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
|
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:
|
authentication_backend:
|
||||||
# LDAP backend configuration.
|
|
||||||
#
|
|
||||||
# This backend allows Authelia to be scaled to more
|
|
||||||
# than one instance and therefore is recommended for
|
|
||||||
# production.
|
|
||||||
ldap:
|
ldap:
|
||||||
# The url of the ldap server
|
url: ldap-service:389
|
||||||
url: ldap-service:389
|
|
||||||
|
|
||||||
# The base dn for every entries
|
|
||||||
base_dn: dc=example,dc=com
|
base_dn: dc=example,dc=com
|
||||||
|
|
||||||
# An additional dn to define the scope to all users
|
|
||||||
additional_users_dn: ou=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})
|
users_filter: (cn={0})
|
||||||
|
|
||||||
# An additional dn to define the scope of groups
|
|
||||||
additional_groups_dn: ou=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))
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
|
||||||
# The attribute holding the name of the group
|
|
||||||
group_name_attribute: cn
|
group_name_attribute: cn
|
||||||
|
|
||||||
# The attribute holding the mail address of the user
|
|
||||||
mail_attribute: mail
|
mail_attribute: mail
|
||||||
|
|
||||||
# The username and password of the admin user.
|
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
password: password
|
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:
|
access_control:
|
||||||
default_policy: deny
|
default_policy: deny
|
||||||
|
|
||||||
|
@ -137,68 +74,27 @@ access_control:
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
|
|
||||||
|
|
||||||
# Configuration of session cookies
|
|
||||||
#
|
|
||||||
# The session cookies identify the user once logged in.
|
|
||||||
session:
|
session:
|
||||||
# The secret to encrypt the session cookie.
|
|
||||||
secret: unsecure_password
|
secret: unsecure_password
|
||||||
|
|
||||||
# The time in ms before the cookie expires and session is reset.
|
|
||||||
expiration: 3600000 # 1 hour
|
expiration: 3600000 # 1 hour
|
||||||
|
|
||||||
# The inactivity time in ms before the session is reset.
|
|
||||||
inactivity: 300000 # 5 minutes
|
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
|
domain: example.com
|
||||||
|
|
||||||
# The redis connection details
|
|
||||||
redis:
|
redis:
|
||||||
host: redis-service
|
host: redis-service
|
||||||
port: 6379
|
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:
|
regulation:
|
||||||
# The number of failed login attempts before user is banned.
|
|
||||||
# Set it to 0 for disabling regulation.
|
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
|
|
||||||
# The length of time between login attempts before user is banned.
|
|
||||||
find_time: 120
|
find_time: 120
|
||||||
|
|
||||||
# The length of time before a banned user can login again.
|
|
||||||
ban_time: 300
|
ban_time: 300
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets.
|
|
||||||
#
|
|
||||||
# You must use only an available configuration: local, mongo
|
|
||||||
storage:
|
storage:
|
||||||
# The directory where the DB files will be saved
|
sql:
|
||||||
# local: /var/lib/authelia/store
|
host: mariadb-service
|
||||||
|
port: 3306
|
||||||
# Settings to connect to mongo server
|
|
||||||
mongo:
|
|
||||||
url: mongodb://mongo-service
|
|
||||||
database: authelia
|
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:
|
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:
|
smtp:
|
||||||
host: 'mailcatcher-service'
|
host: 'mailcatcher-service'
|
||||||
port: 1025
|
port: 1025
|
||||||
|
|
|
@ -20,7 +20,7 @@ start_dashboard() {
|
||||||
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')
|
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
|
# Please note they are not configured to be distributed on several machines
|
||||||
start_storage() {
|
start_storage() {
|
||||||
kubectl apply -f storage
|
kubectl apply -f storage
|
||||||
|
|
|
@ -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
|
|
@ -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
14
go.mod
|
@ -5,19 +5,15 @@ go 1.13
|
||||||
require (
|
require (
|
||||||
github.com/Workiva/go-datastructures v1.0.50
|
github.com/Workiva/go-datastructures v1.0.50
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
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/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74
|
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74
|
||||||
github.com/fasthttp/router v0.5.2
|
github.com/fasthttp/router v0.5.2
|
||||||
github.com/fasthttp/session v1.1.3
|
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/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/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/otiai10/copy v1.0.2
|
||||||
github.com/pquerna/otp v1.2.0
|
github.com/pquerna/otp v1.2.0
|
||||||
github.com/simia-tech/crypt v0.2.0
|
github.com/simia-tech/crypt v0.2.0
|
||||||
|
@ -27,9 +23,7 @@ require (
|
||||||
github.com/tebeka/selenium v0.9.9
|
github.com/tebeka/selenium v0.9.9
|
||||||
github.com/tstranex/u2f v1.0.0
|
github.com/tstranex/u2f v1.0.0
|
||||||
github.com/valyala/fasthttp v1.6.0
|
github.com/valyala/fasthttp v1.6.0
|
||||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
|
google.golang.org/appengine v1.6.5 // indirect
|
||||||
github.com/xdg/stringprep v1.0.0 // indirect
|
|
||||||
go.mongodb.org/mongo-driver v1.1.2
|
|
||||||
gopkg.in/ldap.v3 v3.1.0
|
gopkg.in/ldap.v3 v3.1.0
|
||||||
gopkg.in/yaml.v2 v2.2.4
|
gopkg.in/yaml.v2 v2.2.4
|
||||||
)
|
)
|
||||||
|
|
42
go.sum
42
go.sum
|
@ -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 h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
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/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/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/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-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/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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/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 h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
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-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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
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=
|
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 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
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.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/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 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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
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/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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
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/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/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 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
|
||||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
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 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
|
||||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
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/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 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
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/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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
|
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 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
|
||||||
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
|
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/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/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 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
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 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY=
|
||||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
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/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=
|
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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
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/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-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-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-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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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-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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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.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.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.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.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-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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/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=
|
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 h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
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/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 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE=
|
||||||
gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ=
|
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -2,228 +2,26 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/clems4ever/authelia/models"
|
|
||||||
|
|
||||||
"github.com/clems4ever/authelia/logging"
|
"github.com/clems4ever/authelia/logging"
|
||||||
_ "github.com/mattn/go-sqlite3" // Load the SQLite Driver used in the connection string.
|
_ "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 {
|
type SQLiteProvider struct {
|
||||||
db *sql.DB
|
SQLProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSQLiteProvider construct a sqlite provider.
|
// NewSQLiteProvider construct a sqlite provider.
|
||||||
func NewSQLiteProvider(path string) *SQLiteProvider {
|
func NewSQLiteProvider(path string) *SQLiteProvider {
|
||||||
provider := SQLiteProvider{}
|
db, err := sql.Open("sqlite3", path)
|
||||||
err := provider.initialize(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Logger().Fatalf("Unable to create SQLite database %s: %s", path, err)
|
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
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -220,19 +220,15 @@ regulation:
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets.
|
# 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:
|
storage:
|
||||||
# The directory where the DB files will be saved
|
# Settings to connect to mariadb server
|
||||||
## local:
|
sql:
|
||||||
## path: /var/lib/authelia/store
|
host: mariadb
|
||||||
|
port: 3306
|
||||||
# Settings to connect to mongo server
|
|
||||||
mongo:
|
|
||||||
url: mongodb://mongo
|
|
||||||
database: authelia
|
database: authelia
|
||||||
auth:
|
username: admin
|
||||||
username: authelia
|
password: password
|
||||||
password: authelia
|
|
||||||
|
|
||||||
# Configuration of the notification system.
|
# Configuration of the notification system.
|
||||||
#
|
#
|
||||||
|
|
|
@ -22,12 +22,12 @@ session:
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
||||||
storage:
|
storage:
|
||||||
mongo:
|
sql:
|
||||||
url: mongodb://mongo
|
host: mariadb
|
||||||
|
port: 3306
|
||||||
database: authelia
|
database: authelia
|
||||||
auth:
|
username: admin
|
||||||
username: authelia
|
password: password
|
||||||
password: authelia
|
|
||||||
|
|
||||||
# TOTP Issuer Name
|
# TOTP Issuer Name
|
||||||
#
|
#
|
|
@ -1,6 +1,7 @@
|
||||||
package suites
|
package suites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -38,6 +39,13 @@ func (de *DockerEnvironment) Up(suitePath string) error {
|
||||||
return cmd.Run()
|
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
|
// Down spawn a docker environment
|
||||||
func (de *DockerEnvironment) Down(suitePath string) error {
|
func (de *DockerEnvironment) Down(suitePath string) error {
|
||||||
cmd := de.createCommandWithStdout("down -v")
|
cmd := de.createCommandWithStdout("down -v")
|
||||||
|
|
|
@ -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`,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
"docker-compose.yml",
|
"docker-compose.yml",
|
||||||
"example/compose/authelia/docker-compose.backend.yml",
|
"example/compose/authelia/docker-compose.backend.yml",
|
||||||
"example/compose/authelia/docker-compose.frontend.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/redis/docker-compose.yml",
|
||||||
"example/compose/nginx/backend/docker-compose.yml",
|
"example/compose/nginx/backend/docker-compose.yml",
|
||||||
"example/compose/nginx/portal/docker-compose.yml",
|
"example/compose/nginx/portal/docker-compose.yml",
|
||||||
|
|
|
@ -14,6 +14,5 @@ func NewHighAvailabilitySuite() *HighAvailabilitySuite {
|
||||||
|
|
||||||
func TestHighAvailabilitySuite(t *testing.T) {
|
func TestHighAvailabilitySuite(t *testing.T) {
|
||||||
RunTypescriptSuite(t, highAvailabilitySuiteName)
|
RunTypescriptSuite(t, highAvailabilitySuiteName)
|
||||||
|
|
||||||
TestRunOneFactor(t)
|
TestRunOneFactor(t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mongoSuiteName = "Mongo"
|
var mariadbSuiteName = "Mariadb"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
dockerEnvironment := NewDockerEnvironment([]string{
|
dockerEnvironment := NewDockerEnvironment([]string{
|
||||||
|
@ -14,14 +14,12 @@ func init() {
|
||||||
"example/compose/nginx/backend/docker-compose.yml",
|
"example/compose/nginx/backend/docker-compose.yml",
|
||||||
"example/compose/nginx/portal/docker-compose.yml",
|
"example/compose/nginx/portal/docker-compose.yml",
|
||||||
"example/compose/smtp/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",
|
"example/compose/ldap/docker-compose.yml",
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up(suitePath)
|
if err := dockerEnvironment.Up(suitePath); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +31,9 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalRegistry.Register(mongoSuiteName, Suite{
|
GlobalRegistry.Register(mariadbSuiteName, Suite{
|
||||||
SetUp: setup,
|
SetUp: setup,
|
||||||
SetUpTimeout: 5 * time.Minute,
|
SetUpTimeout: 3 * time.Minute,
|
||||||
TearDown: teardown,
|
TearDown: teardown,
|
||||||
TearDownTimeout: 2 * time.Minute,
|
TearDownTimeout: 2 * time.Minute,
|
||||||
})
|
})
|
|
@ -6,15 +6,15 @@ import (
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MongoSuite struct {
|
type MariadbSuite struct {
|
||||||
*SeleniumSuite
|
*SeleniumSuite
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMongoSuite() *MongoSuite {
|
func NewMariadbSuite() *MariadbSuite {
|
||||||
return &MongoSuite{SeleniumSuite: new(SeleniumSuite)}
|
return &MariadbSuite{SeleniumSuite: new(SeleniumSuite)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMongoSuite(t *testing.T) {
|
func TestMariadbSuite(t *testing.T) {
|
||||||
suite.Run(t, NewOneFactorSuite())
|
suite.Run(t, NewOneFactorSuite())
|
||||||
suite.Run(t, NewTwoFactorSuite())
|
suite.Run(t, NewTwoFactorSuite())
|
||||||
}
|
}
|
|
@ -36,5 +36,7 @@ func init() {
|
||||||
SetUpTimeout: 5 * time.Minute,
|
SetUpTimeout: 5 * time.Minute,
|
||||||
TearDown: teardown,
|
TearDown: teardown,
|
||||||
TearDownTimeout: 5 * time.Minute,
|
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`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,6 @@ func NewStandaloneSuite() *StandaloneSuite {
|
||||||
func TestStandaloneSuite(t *testing.T) {
|
func TestStandaloneSuite(t *testing.T) {
|
||||||
suite.Run(t, NewOneFactorSuite())
|
suite.Run(t, NewOneFactorSuite())
|
||||||
suite.Run(t, NewTwoFactorSuite())
|
suite.Run(t, NewTwoFactorSuite())
|
||||||
|
|
||||||
|
RunTypescriptSuite(t, standaloneSuiteName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,140 +2,34 @@
|
||||||
# Authelia configuration #
|
# Authelia configuration #
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
# The port to listen on
|
|
||||||
port: 9091
|
port: 9091
|
||||||
|
|
||||||
# Log level
|
|
||||||
#
|
|
||||||
# Level of verbosity for logs
|
|
||||||
logs_level: debug
|
logs_level: debug
|
||||||
|
|
||||||
jwt_secret: a_secret
|
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/
|
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:
|
totp:
|
||||||
issuer: authelia.com
|
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:
|
duo_api:
|
||||||
hostname: api-123456789.example.com
|
hostname: api-123456789.example.com
|
||||||
integration_key: ABCDEF
|
integration_key: ABCDEF
|
||||||
secret_key: 1234567890abcdefghifjkl
|
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:
|
authentication_backend:
|
||||||
# LDAP backend configuration.
|
|
||||||
#
|
|
||||||
# This backend allows Authelia to be scaled to more
|
|
||||||
# than one instance and therefore is recommended for
|
|
||||||
# production.
|
|
||||||
ldap:
|
ldap:
|
||||||
# The url of the ldap server
|
|
||||||
url: ldap://127.0.0.1
|
url: ldap://127.0.0.1
|
||||||
|
|
||||||
# The base dn for every entries
|
|
||||||
base_dn: dc=example,dc=com
|
base_dn: dc=example,dc=com
|
||||||
|
|
||||||
# An additional dn to define the scope to all users
|
|
||||||
additional_users_dn: ou=users
|
additional_users_dn: ou=users
|
||||||
|
users_filter: (cn={0})
|
||||||
# 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
|
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))
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
|
||||||
# The attribute holding the name of the group
|
|
||||||
group_name_attribute: cn
|
group_name_attribute: cn
|
||||||
|
|
||||||
# The attribute holding the mail address of the user
|
|
||||||
mail_attribute: mail
|
mail_attribute: mail
|
||||||
|
|
||||||
# The username and password of the admin user.
|
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
password: password
|
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:
|
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
|
default_policy: deny
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
@ -194,86 +88,31 @@ access_control:
|
||||||
subject: 'user:bob'
|
subject: 'user:bob'
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
|
|
||||||
|
|
||||||
# Configuration of session cookies
|
|
||||||
#
|
|
||||||
# The session cookies identify the user once logged in.
|
|
||||||
session:
|
session:
|
||||||
# The name of the session cookie. (default: authelia_session).
|
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
|
|
||||||
# The secret to encrypt the session cookie.
|
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
|
|
||||||
# The time in ms before the cookie expires and session is reset.
|
|
||||||
expiration: 3600000 # 1 hour
|
expiration: 3600000 # 1 hour
|
||||||
|
|
||||||
# The inactivity time in ms before the session is reset.
|
|
||||||
inactivity: 300000 # 5 minutes
|
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
|
domain: example.com
|
||||||
|
|
||||||
# The redis connection details
|
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
password: authelia
|
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:
|
regulation:
|
||||||
# The number of failed login attempts before user is banned.
|
|
||||||
# Set it to 0 to disable regulation.
|
|
||||||
max_retries: 3
|
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
|
find_time: 120
|
||||||
|
|
||||||
# The length of time before a banned user can login again.
|
|
||||||
ban_time: 300
|
ban_time: 300
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets.
|
|
||||||
#
|
|
||||||
# You must use only an available configuration: local, mongo
|
|
||||||
storage:
|
storage:
|
||||||
# The directory where the DB files will be saved
|
sql:
|
||||||
## local:
|
host: 127.0.0.1
|
||||||
## path: /var/lib/authelia/store
|
port: 3306
|
||||||
|
|
||||||
# Settings to connect to mongo server
|
|
||||||
mongo:
|
|
||||||
url: mongodb://127.0.0.1
|
|
||||||
database: authelia
|
database: authelia
|
||||||
auth:
|
username: authelia
|
||||||
username: authelia
|
password: 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:
|
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:
|
smtp:
|
||||||
username: test
|
username: test
|
||||||
password: password
|
password: password
|
||||||
|
|
|
@ -5,7 +5,7 @@ all components making Authelia highly available.
|
||||||
|
|
||||||
## Components
|
## 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,
|
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
|
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.
|
and httpbin to check headers forwarded by Authelia.
|
||||||
|
|
|
@ -2,7 +2,7 @@ const composeFiles = [
|
||||||
'docker-compose.yml',
|
'docker-compose.yml',
|
||||||
'example/compose/authelia/docker-compose.backend.yml',
|
'example/compose/authelia/docker-compose.backend.yml',
|
||||||
'example/compose/authelia/docker-compose.frontend.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/redis/docker-compose.yml',
|
||||||
'example/compose/nginx/backend/docker-compose.yml',
|
'example/compose/nginx/backend/docker-compose.yml',
|
||||||
'example/compose/nginx/portal/docker-compose.yml',
|
'example/compose/nginx/portal/docker-compose.yml',
|
||||||
|
|
|
@ -4,17 +4,19 @@ import WithDriver from "../../../helpers/context/WithDriver";
|
||||||
import Logout from "../../../helpers/Logout";
|
import Logout from "../../../helpers/Logout";
|
||||||
import { composeFiles } from '../environment';
|
import { composeFiles } from '../environment';
|
||||||
import DockerCompose from "../../../helpers/context/DockerCompose";
|
import DockerCompose from "../../../helpers/context/DockerCompose";
|
||||||
|
import sleep from "../../../helpers/utils/sleep";
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const dockerCompose = new DockerCompose(composeFiles);
|
const dockerCompose = new DockerCompose(composeFiles);
|
||||||
|
|
||||||
WithDriver();
|
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);
|
this.timeout(30000);
|
||||||
|
|
||||||
const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
|
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 Logout(this.driver);
|
||||||
await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html");
|
await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html");
|
|
@ -1,5 +1,5 @@
|
||||||
import AutheliaSuite from "../../helpers/context/AutheliaSuite";
|
import AutheliaSuite from "../../helpers/context/AutheliaSuite";
|
||||||
import MongoConnectionRecovery from "./scenarii/MongoConnectionRecovery";
|
import MariaConnectionRecovery from "./scenarii/MariaConnectionRecovery";
|
||||||
import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly";
|
import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly";
|
||||||
import AccessControl from "./scenarii/AccessControl";
|
import AccessControl from "./scenarii/AccessControl";
|
||||||
import CustomHeadersForwarded from "./scenarii/CustomHeadersForwarded";
|
import CustomHeadersForwarded from "./scenarii/CustomHeadersForwarded";
|
||||||
|
@ -12,7 +12,7 @@ AutheliaSuite(__dirname, function () {
|
||||||
|
|
||||||
describe('Custom headers forwarded to backend', CustomHeadersForwarded);
|
describe('Custom headers forwarded to backend', CustomHeadersForwarded);
|
||||||
describe('Access control', AccessControl);
|
describe('Access control', AccessControl);
|
||||||
describe('Mongo broken connection recovery', MongoConnectionRecovery);
|
describe('Mariadb broken connection recovery', MariaConnectionRecovery);
|
||||||
describe('Enforce internal redirections only', EnforceInternalRedirectionsOnly);
|
describe('Enforce internal redirections only', EnforceInternalRedirectionsOnly);
|
||||||
describe('Basic authentication', BasicAuthentication);
|
describe('Basic authentication', BasicAuthentication);
|
||||||
describe('Authelia restart', AutheliaRestart);
|
describe('Authelia restart', AutheliaRestart);
|
||||||
|
|
Loading…
Reference in New Issue