Merge pull request #160 from clems4ever/develop

Release 3.6.0
pull/181/head
Clément Michaud 2017-10-16 22:32:18 +02:00 committed by GitHub
commit 19c846a366
21 changed files with 158 additions and 413 deletions

2
.gitignore vendored
View File

@ -33,3 +33,5 @@ dist/
example/ldap/private.ldif example/ldap/private.ldif
package-lock.json package-lock.json
Configuration.schema.json

View File

@ -1,10 +1,10 @@
src/ client/
server/
test/ test/
doc/ doc/
scripts/ scripts/
images/ images/
example/ example/
dist/test/
.travis.yml .travis.yml
config.test.yml config.test.yml

View File

@ -24,11 +24,12 @@ used in production to secure internal services in a small docker swarm cluster.
5. [Access control](#access-control) 5. [Access control](#access-control)
6. [Basic authentication](#basic-authentication) 6. [Basic authentication](#basic-authentication)
7. [Session management with Redis](#session-management-with-redis) 7. [Session management with Redis](#session-management-with-redis)
4. [Documentation](#documentation) 4. [Security](#security)
5. [Documentation](#documentation)
1. [Authelia configuration](#authelia-configuration) 1. [Authelia configuration](#authelia-configuration)
1. [API documentation](#api-documentation) 2. [API documentation](#api-documentation)
5. [Contributing to Authelia](#contributing-to-authelia) 6. [Contributing to Authelia](#contributing-to-authelia)
6. [License](#license) 7. [License](#license)
--- ---
@ -197,6 +198,29 @@ Please see [config.template.yml] to see an example of configuration.
### Session management with Redis ### Session management with Redis
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in [config.template.yml]. When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in [config.template.yml].
## Security
### Protection against cookie theft
Authelia uses two mechanism to protect against cookie theft:
1. session attribute `httpOnly` set to true make client-side code unable to
read the cookie.
2. session attribute `secure` ensure the cookie will never be sent over an
unsecure HTTP connections.
### Protection against multi-domain cookie attacks
Since Authelia uses multi-domain cookies to perform single sign-on, an
attacker who poisonned a user's DNS cache can easily retrieve the user's
cookies by making the user send a request to one of the attacker's IPs.
To mitigate this risk, it's advisable to only use HTTPS connections with valid
certificates and enforce it with HTTP Strict Transport Security ([HSTS]) so
that the attacker must also require the certificate to retrieve the cookies.
Note that using [HSTS] has consequences. That's why you should read the blog
post nginx has written on [HSTS].
## Documentation ## Documentation
### Authelia configuration ### Authelia configuration
The configuration of the server is defined in the file The configuration of the server is defined in the file
@ -246,4 +270,4 @@ Follow [contributing](CONTRIBUTORS.md) file.
[auth_request]: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html [auth_request]: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html
[Google Authenticator]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en [Google Authenticator]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en
[config.template.yml]: https://github.com/clems4ever/authelia/blob/master/config.template.yml [config.template.yml]: https://github.com/clems4ever/authelia/blob/master/config.template.yml
[HSTS]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/

View File

@ -55,8 +55,10 @@ ldap:
# #
# Note: by default a domain uses "two_factor" method. # Note: by default a domain uses "two_factor" method.
# #
# Note: 'overriden_methods' is a dictionary where keys must be subdomains and # Note: 'per_subdomain_methods' is a dictionary where keys must be subdomains and
# values must be one of the two possible methods. # values must be one of the two possible methods.
#
# Note: 'per_subdomain_methods' is optional.
authentication_methods: authentication_methods:
default_method: two_factor default_method: two_factor
per_subdomain_methods: per_subdomain_methods:

View File

@ -47,6 +47,21 @@ ldap:
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
password: password password: password
# Authentication methods
#
# Authentication methods can be defined per subdomain.
# There are currently two available methods: "basic_auth" and "two_factor"
#
# Note: by default a domain uses "two_factor" method.
#
# Note: 'per_subdomain_methods' is a dictionary where keys must be subdomains and
# values must be one of the two possible methods.
#
# Note: 'per_subdomain_methods' is optional.
authentication_methods:
default_method: two_factor
per_subdomain_methods:
basicauth.test.local: basic_auth
# Access Control # Access Control
# #

View File

@ -5,6 +5,8 @@ services:
restart: always restart: always
volumes: volumes:
- ./config.template.yml:/etc/authelia/config.yml:ro - ./config.template.yml:/etc/authelia/config.yml:ro
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0
depends_on: depends_on:
- redis - redis
networks: networks:

View File

@ -6,6 +6,8 @@ services:
volumes: volumes:
- ./config.template.yml:/etc/authelia/config.yml:ro - ./config.template.yml:/etc/authelia/config.yml:ro
- ./notifications:/var/lib/authelia/notifications - ./notifications:/var/lib/authelia/notifications
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0
depends_on: depends_on:
- redis - redis
networks: networks:

View File

@ -30,11 +30,14 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location / { location / {
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://authelia/; proxy_pass http://authelia/;
@ -57,6 +60,9 @@ http {
ssl on; ssl on;
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
} }
server { server {
@ -69,10 +75,14 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location /auth_verify { location /auth_verify {
internal; internal;
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Content-Length ""; proxy_set_header Content-Length "";
@ -122,10 +132,14 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location /auth_verify { location /auth_verify {
internal; internal;
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Content-Length ""; proxy_set_header Content-Length "";
@ -158,10 +172,14 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location /auth_verify { location /auth_verify {
internal; internal;
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Content-Length ""; proxy_set_header Content-Length "";
@ -194,10 +212,14 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location /auth_verify { location /auth_verify {
internal; internal;
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Content-Length ""; proxy_set_header Content-Length "";
@ -230,10 +252,14 @@ http {
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key; ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location /auth_verify { location /auth_verify {
internal; internal;
proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Content-Length ""; proxy_set_header Content-Length "";

View File

@ -31,6 +31,9 @@ function deploy_on_dockerhub {
if [ "$TRAVIS_BRANCH" == "master" ]; then if [ "$TRAVIS_BRANCH" == "master" ]; then
login_to_dockerhub login_to_dockerhub
deploy_on_dockerhub master deploy_on_dockerhub master
elif [ "$TRAVIS_BRANCH" == "develop" ]; then
login_to_dockerhub
deploy_on_dockerhub develop
elif [ ! -z "$TRAVIS_TAG" ]; then elif [ ! -z "$TRAVIS_TAG" ]; then
login_to_dockerhub login_to_dockerhub
deploy_on_dockerhub $TRAVIS_TAG deploy_on_dockerhub $TRAVIS_TAG

View File

@ -5,6 +5,8 @@ set -e
docker --version docker --version
docker-compose --version docker-compose --version
grunt run:generate-config-schema
# Run unit tests # Run unit tests
grunt test-unit grunt test-unit

View File

@ -1,7 +1,5 @@
#! /usr/bin/env node #! /usr/bin/env node
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
import Server from "./lib/Server"; import Server from "./lib/Server";
import { GlobalDependencies } from "../types/Dependencies"; import { GlobalDependencies } from "../types/Dependencies";
import YAML = require("yamljs"); import YAML = require("yamljs");

View File

@ -23,8 +23,8 @@ import * as http from "http";
const addRequestId = require("express-request-id")(); const addRequestId = require("express-request-id")();
// Constants // Constants
const TRUST_PROXY = "trust proxy"; const TRUST_PROXY = "trust proxy";
const X_POWERED_BY = "x-powered-by";
const VIEWS = "views"; const VIEWS = "views";
const VIEW_ENGINE = "view engine"; const VIEW_ENGINE = "view engine";
const PUG = "pug"; const PUG = "pug";
@ -54,9 +54,9 @@ export default class Server {
app.use(BodyParser.json()); app.use(BodyParser.json());
app.use(deps.session(expressSessionOptions)); app.use(deps.session(expressSessionOptions));
app.use(addRequestId); app.use(addRequestId);
app.disable("x-powered-by"); app.disable(X_POWERED_BY);
app.enable(TRUST_PROXY);
app.set(TRUST_PROXY, 1);
app.set(VIEWS, viewsDirectory); app.set(VIEWS, viewsDirectory);
app.set(VIEW_ENGINE, PUG); app.set(VIEW_ENGINE, PUG);

View File

@ -73,8 +73,8 @@ export interface GmailNotifierConfiguration {
} }
export interface SmtpNotifierConfiguration { export interface SmtpNotifierConfiguration {
username: string; username?: string;
password: string; password?: string;
host: string; host: string;
port: number; port: number;
secure: boolean; secure: boolean;
@ -116,7 +116,7 @@ declare type AuthenticationMethodPerSubdomain = { [subdomain: string]: Authentic
export interface AuthenticationMethodsConfiguration { export interface AuthenticationMethodsConfiguration {
default_method: AuthenticationMethod; default_method: AuthenticationMethod;
per_subdomain_methods: AuthenticationMethodPerSubdomain; per_subdomain_methods?: AuthenticationMethodPerSubdomain;
} }
export interface UserConfiguration { export interface UserConfiguration {

View File

@ -1,368 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"ACLConfiguration": {
"properties": {
"any": {
"items": {
"properties": {
"domain": {
"type": "string"
},
"policy": {
"$ref": "#/definitions/ACLPolicy"
},
"resources": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"domain",
"policy"
],
"type": "object"
},
"type": "array"
},
"default_policy": {
"$ref": "#/definitions/ACLPolicy"
},
"groups": {
"additionalProperties": {
"items": {
"properties": {
"domain": {
"type": "string"
},
"policy": {
"$ref": "#/definitions/ACLPolicy"
},
"resources": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"domain",
"policy"
],
"type": "object"
},
"type": "array"
},
"type": "object"
},
"users": {
"additionalProperties": {
"items": {
"properties": {
"domain": {
"type": "string"
},
"policy": {
"$ref": "#/definitions/ACLPolicy"
},
"resources": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"domain",
"policy"
],
"type": "object"
},
"type": "array"
},
"type": "object"
}
},
"type": "object"
},
"ACLPolicy": {
"enum": [
"allow",
"deny"
],
"type": "string"
},
"AuthenticationMethod": {
"enum": [
"basic_auth",
"two_factor"
],
"type": "string"
},
"AuthenticationMethodsConfiguration": {
"properties": {
"default_method": {
"$ref": "#/definitions/AuthenticationMethod"
},
"per_subdomain_methods": {
"additionalProperties": {
"enum": [
"basic_auth",
"two_factor"
],
"type": "string"
},
"type": "object"
}
},
"required": [
"default_method",
"per_subdomain_methods"
],
"type": "object"
},
"FileSystemNotifierConfiguration": {
"properties": {
"filename": {
"type": "string"
}
},
"required": [
"filename"
],
"type": "object"
},
"GmailNotifierConfiguration": {
"properties": {
"password": {
"type": "string"
},
"sender": {
"type": "string"
},
"username": {
"type": "string"
}
},
"required": [
"password",
"sender",
"username"
],
"type": "object"
},
"LocalStorageConfiguration": {
"properties": {
"in_memory": {
"type": "boolean"
},
"path": {
"type": "string"
}
},
"type": "object"
},
"MongoStorageConfiguration": {
"properties": {
"url": {
"type": "string"
}
},
"required": [
"url"
],
"type": "object"
},
"NotifierConfiguration": {
"properties": {
"filesystem": {
"$ref": "#/definitions/FileSystemNotifierConfiguration"
},
"gmail": {
"$ref": "#/definitions/GmailNotifierConfiguration"
},
"smtp": {
"$ref": "#/definitions/SmtpNotifierConfiguration"
}
},
"type": "object"
},
"RegulationConfiguration": {
"properties": {
"ban_time": {
"type": "number"
},
"find_time": {
"type": "number"
},
"max_retries": {
"type": "number"
}
},
"required": [
"ban_time",
"find_time",
"max_retries"
],
"type": "object"
},
"SessionCookieConfiguration": {
"properties": {
"domain": {
"type": "string"
},
"expiration": {
"type": "number"
},
"redis": {
"$ref": "#/definitions/SessionRedisOptions"
},
"secret": {
"type": "string"
}
},
"required": [
"secret"
],
"type": "object"
},
"SessionRedisOptions": {
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "number"
}
},
"required": [
"host",
"port"
],
"type": "object"
},
"SmtpNotifierConfiguration": {
"properties": {
"host": {
"type": "string"
},
"password": {
"type": "string"
},
"port": {
"type": "number"
},
"secure": {
"type": "boolean"
},
"sender": {
"type": "string"
},
"username": {
"type": "string"
}
},
"required": [
"host",
"password",
"port",
"secure",
"sender",
"username"
],
"type": "object"
},
"StorageConfiguration": {
"properties": {
"local": {
"$ref": "#/definitions/LocalStorageConfiguration"
},
"mongo": {
"$ref": "#/definitions/MongoStorageConfiguration"
}
},
"type": "object"
},
"UserLdapConfiguration": {
"properties": {
"additional_groups_dn": {
"type": "string"
},
"additional_users_dn": {
"type": "string"
},
"base_dn": {
"type": "string"
},
"group_name_attribute": {
"type": "string"
},
"groups_filter": {
"type": "string"
},
"mail_attribute": {
"type": "string"
},
"password": {
"type": "string"
},
"url": {
"type": "string"
},
"user": {
"type": "string"
},
"users_filter": {
"type": "string"
}
},
"required": [
"base_dn",
"password",
"url",
"user"
],
"type": "object"
}
},
"properties": {
"access_control": {
"$ref": "#/definitions/ACLConfiguration"
},
"authentication_methods": {
"$ref": "#/definitions/AuthenticationMethodsConfiguration"
},
"ldap": {
"$ref": "#/definitions/UserLdapConfiguration"
},
"logs_level": {
"type": "string"
},
"notifier": {
"$ref": "#/definitions/NotifierConfiguration"
},
"port": {
"type": "number"
},
"regulation": {
"$ref": "#/definitions/RegulationConfiguration"
},
"session": {
"$ref": "#/definitions/SessionCookieConfiguration"
},
"storage": {
"$ref": "#/definitions/StorageConfiguration"
}
},
"required": [
"ldap",
"notifier",
"regulation",
"session",
"storage"
],
"type": "object"
}

View File

@ -12,7 +12,8 @@ export class SessionConfigurationBuilder {
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
secure: false, secure: true,
httpOnly: true,
maxAge: configuration.session.expiration, maxAge: configuration.session.expiration,
domain: configuration.session.domain domain: configuration.session.domain
}, },

View File

@ -28,11 +28,15 @@ export class MailSenderBuilder implements IMailSenderBuilder {
host: options.host, host: options.host,
port: options.port, port: options.port,
secure: options.secure, // upgrade later with STARTTLS secure: options.secure, // upgrade later with STARTTLS
auth: { };
if (options.username && options.password) {
smtpOptions.auth = {
user: options.username, user: options.username,
pass: options.password pass: options.password
}
}; };
}
return new MailSender(smtpOptions, this.nodemailer); return new MailSender(smtpOptions, this.nodemailer);
} }
} }

View File

@ -1,8 +1,17 @@
import Express = require("express"); import Express = require("express");
import Endpoints = require("../../../../../shared/api"); import Endpoints = require("../../../../../shared/api");
import FirstFactorBlocker from "../FirstFactorBlocker";
import BluebirdPromise = require("bluebird");
import AuthenticationSession = require("../../AuthenticationSession");
export default function(req: Express.Request, res: Express.Response) { export default FirstFactorBlocker(handler);
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
return AuthenticationSession.get(req)
.then(function (authSession) {
res.render("already-logged-in", { res.render("already-logged-in", {
logout_endpoint: Endpoints.LOGOUT_GET logout_endpoint: Endpoints.LOGOUT_GET,
username: authSession.userid
});
}); });
} }

View File

@ -5,5 +5,5 @@ block form-header
<img class="header-img" src="/img/success.png" alt=""> <img class="header-img" src="/img/success.png" alt="">
block content block content
<p>You are already logged in.<br/> <p>You are already logged in as <b>#{ username }</b>.<br/>
| Click <a href="#{ logout_endpoint }">here</a> to log off.<p> | Click <a href="#{ logout_endpoint }">here</a> to log off.<p>

View File

@ -73,7 +73,8 @@ describe("test session configuration builder", function () {
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
secure: false, secure: true,
httpOnly: true,
maxAge: 3600, maxAge: 3600,
domain: "example.com" domain: "example.com"
} }
@ -153,7 +154,8 @@ describe("test session configuration builder", function () {
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
secure: false, secure: true,
httpOnly: true,
maxAge: 3600, maxAge: 3600,
domain: "example.com" domain: "example.com"
}, },

View File

@ -25,7 +25,8 @@ describe("test MailSenderBuilder", function() {
Assert.equal(createTransportStub.getCall(0).args[0].auth.pass, "pass_gmail"); Assert.equal(createTransportStub.getCall(0).args[0].auth.pass, "pass_gmail");
}); });
it("should create a smtp mail sender", function() { describe("build smtp mail sender", function() {
it("should create a smtp mail sender with authenticated user", function() {
const mailSenderBuilder = new MailSenderBuilder(Nodemailer); const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
mailSenderBuilder.buildSmtp({ mailSenderBuilder.buildSmtp({
host: "mail.example.com", host: "mail.example.com",
@ -45,4 +46,20 @@ describe("test MailSenderBuilder", function() {
secure: true, secure: true,
}); });
}); });
it("should create a smtp mail sender with anonymous user", function() {
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
mailSenderBuilder.buildSmtp({
host: "mail.example.com",
port: 25,
secure: true,
sender: "admin@example.com"
});
Assert.deepStrictEqual(createTransportStub.getCall(0).args[0], {
host: "mail.example.com",
port: 25,
secure: true,
});
});
});
}); });

View File

@ -173,6 +173,10 @@ describe("Private pages of the server must not be accessible without session", f
it("should block " + Endpoints.SECOND_FACTOR_TOTP_POST, function () { it("should block " + Endpoints.SECOND_FACTOR_TOTP_POST, function () {
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST); return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST);
}); });
it("should block " + Endpoints.LOGGED_IN, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.LOGGED_IN);
});
}); });
}); });