Fix example environment

This commit is contained in:
Clement Michaud 2017-07-14 19:05:42 +02:00
parent 1e1653d81f
commit 8f152d2328
18 changed files with 175 additions and 448 deletions

7
.gitignore vendored
View File

@ -13,9 +13,6 @@ src/.baseDir.ts
*.swp *.swp
/config.yml
npm-debug.log
# Directory used by example # Directory used by example
notifications/ notifications/
@ -29,3 +26,7 @@ dist/
.nyc_output/ .nyc_output/
*.tgz *.tgz
# Specific files
/config.yml
/test/integration/nginx.conf

View File

@ -154,8 +154,9 @@ module.exports = function (grunt) {
grunt.registerTask('build-resources', ['copy:resources', 'copy:views', 'copy:images', 'copy:thirdparties', 'concat:css']); grunt.registerTask('build-resources', ['copy:resources', 'copy:views', 'copy:images', 'copy:thirdparties', 'concat:css']);
grunt.registerTask('build-dev', ['run:tslint', 'run:build', 'browserify:dist', 'build-resources', 'run:make-dev-views']); grunt.registerTask('build-common', ['run:tslint', 'run:build', 'browserify:dist', 'build-resources']);
grunt.registerTask('build-dist', ['build-dev', 'run:minify', 'cssmin']); grunt.registerTask('build-dev', ['build-common', 'run:make-dev-views']);
grunt.registerTask('build-dist', ['build-common', 'run:minify', 'cssmin']);
grunt.registerTask('docker-build', ['run:docker-build']); grunt.registerTask('docker-build', ['run:docker-build']);
grunt.registerTask('docker-restart', ['run:docker-restart']); grunt.registerTask('docker-restart', ['run:docker-restart']);

View File

@ -12,7 +12,7 @@ logs_level: info
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com # Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
ldap: ldap:
# The url of the ldap server # The url of the ldap server
url: ldap://openldap-restriction url: ldap://openldap
# The base dn for every entries # The base dn for every entries
base_dn: dc=example,dc=com base_dn: dc=example,dc=com

View File

@ -1,5 +1,4 @@
version: '2' version: '2'
networks: networks:
example-network: example-network:
driver: bridge driver: bridge

View File

@ -12,13 +12,13 @@ services:
depends_on: depends_on:
- authelia - authelia
networks: networks:
example-network: - example-network
aliases: # aliases:
- home.test.local # - home.test.local
- secret.test.local # - secret.test.local
- secret1.test.local # - secret1.test.local
- secret2.test.local # - secret2.test.local
- mx1.mail.test.local # - mx1.mail.test.local
- mx2.mail.test.local # - mx2.mail.test.local
- auth.test.local # - auth.test.local

View File

@ -40,9 +40,11 @@ http {
proxy_intercept_errors on; proxy_intercept_errors on;
error_page 401 = /error/401; if ($request_method !~ ^(POST)$){
error_page 403 = /error/403; error_page 401 = /error/401;
error_page 404 = /error/404; error_page 403 = /error/403;
error_page 404 = /error/404;
}
} }
} }

View File

@ -2,7 +2,7 @@
service_count=`docker ps -a | grep "Up " | wc -l` service_count=`docker ps -a | grep "Up " | wc -l`
if [ "${service_count}" -eq "4" ] if [ "${service_count}" -eq "5" ]
then then
echo "Service are up and running." echo "Service are up and running."
exit 0 exit 0

View File

@ -2,4 +2,4 @@
set -e set -e
docker-compose -f docker-compose.base.yml -f docker-compose.yml -f example/redis/docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml $* docker-compose -f docker-compose.base.yml -f docker-compose.yml -f example/redis/docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml -f test/integration/docker-compose.yml $*

35
scripts/integration-tests.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
set -e
echo "Make sure services are not already running"
./scripts/dc-example.sh down
echo "Prepare nginx-test configuration"
cat example/nginx/nginx.conf | sed 's/listen 443 ssl/listen 8080 ssl/g' | dd of="test/integration/nginx.conf"
echo "Build services images..."
./scripts/dc-example.sh build
echo "Start services..."
./scripts/dc-example.sh up -d redis openldap
sleep 2
./scripts/dc-example.sh up -d authelia nginx nginx-tests
sleep 3
docker ps -a
echo "Display services logs..."
./scripts/dc-example.sh logs redis
./scripts/dc-example.sh logs openldap
./scripts/dc-example.sh logs nginx
./scripts/dc-example.sh logs nginx-tests
./scripts/dc-example.sh logs authelia
echo "Check number of services"
./scripts/check-services.sh
echo "Run integration tests..."
./scripts/dc-example.sh run --rm integration-tests
echo "Shutdown services..."
./scripts/dc-example.sh down

View File

@ -1,24 +0,0 @@
#!/bin/bash
set -e
echo "Build services images..."
./scripts/dc-test.sh build
echo "Start services..."
./scripts/dc-test.sh up -d redis openldap
sleep 2
./scripts/dc-test.sh up -d authelia nginx
sleep 3
docker ps -a
echo "Display services logs..."
./scripts/dc-test.sh logs authelia
./scripts/dc-test.sh logs nginx
./scripts/dc-test.sh logs openldap
echo "Run integration tests..."
./scripts/dc-test.sh run --rm --name int-test int-test
echo "Shutdown services..."
./scripts/dc-test.sh down

View File

@ -1,15 +0,0 @@
#!/bin/bash
set -e
# Build production environment and set it up
./scripts/dc-example.sh build
./scripts/dc-example.sh up -d
# Wait for services to be running
sleep 5
# Check if services are correctly running
./scripts/check-services.sh
./scripts/dc-example.sh down

View File

@ -11,11 +11,8 @@ grunt test
# Build the app from Typescript and package # Build the app from Typescript and package
grunt build-dist grunt build-dist
# Run integration tests # Run integration/example tests
./scripts/run-int-test.sh ./scripts/integration-tests.sh
# Test staging environment
./scripts/run-staging.sh
# Test npm deployment before actual deployment # Test npm deployment before actual deployment
./scripts/npm-deployment-test.sh ./scripts/npm-deployment-test.sh

View File

@ -1,164 +0,0 @@
import Request = require("request");
import Assert = require("assert");
import Speakeasy = require("speakeasy");
import BluebirdPromise = require("bluebird");
import Util = require("util");
import Sinon = require("sinon");
import Endpoints = require("../../src/server/endpoints");
const EXEC_PATH = "./dist/src/server/index.js";
const CONFIG_PATH = "./test/integration/config.yml";
const j = Request.jar();
const request: typeof Request = <typeof Request>BluebirdPromise.promisifyAll(Request.defaults({ jar: j }));
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const DOMAIN = "test.local";
const PORT = 8080;
const HOME_URL = Util.format("https://%s.%s:%d", "home", DOMAIN, PORT);
const SECRET_URL = Util.format("https://%s.%s:%d", "secret", DOMAIN, PORT);
const SECRET1_URL = Util.format("https://%s.%s:%d", "secret1", DOMAIN, PORT);
const SECRET2_URL = Util.format("https://%s.%s:%d", "secret2", DOMAIN, PORT);
const MX1_URL = Util.format("https://%s.%s:%d", "mx1.mail", DOMAIN, PORT);
const MX2_URL = Util.format("https://%s.%s:%d", "mx2.mail", DOMAIN, PORT);
const BASE_AUTH_URL = Util.format("https://%s.%s:%d", "auth", DOMAIN, PORT);
function waitFor(ms: number): BluebirdPromise<{}> {
return new BluebirdPromise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, ms);
});
}
describe("test the server", function () {
let home_page: string;
let login_page: string;
before(function () {
const home_page_promise = getHomePage()
.then(function (data) {
home_page = data.body;
});
const login_page_promise = getLoginPage()
.then(function (data) {
login_page = data.body;
});
return BluebirdPromise.all([home_page_promise,
login_page_promise]);
});
after(function () {
});
function str_contains(str: string, pattern: string) {
return str.indexOf(pattern) != -1;
}
function home_page_contains(pattern: string) {
return str_contains(home_page, pattern);
}
it("should serve a correct home page", function () {
Assert(home_page_contains(BASE_AUTH_URL + Endpoints.LOGOUT_GET + "?redirect=" + HOME_URL + "/"));
Assert(home_page_contains(HOME_URL + "/secret.html"));
Assert(home_page_contains(SECRET_URL + "/secret.html"));
Assert(home_page_contains(SECRET1_URL + "/secret.html"));
Assert(home_page_contains(SECRET2_URL + "/secret.html"));
Assert(home_page_contains(MX1_URL + "/secret.html"));
Assert(home_page_contains(MX2_URL + "/secret.html"));
});
it("should serve the login page", function () {
return getPromised(BASE_AUTH_URL + Endpoints.FIRST_FACTOR_GET)
.then(function (data: Request.RequestResponse) {
Assert.equal(data.statusCode, 200);
});
});
it("should serve the homepage", function () {
return getPromised(HOME_URL + "/")
.then(function (data: Request.RequestResponse) {
Assert.equal(data.statusCode, 200);
});
});
it("should redirect when logout", function () {
return getPromised(BASE_AUTH_URL + Endpoints.LOGOUT_GET + "?redirect=" + HOME_URL)
.then(function (data: Request.RequestResponse) {
Assert.equal(data.statusCode, 200);
Assert.equal(data.body, home_page);
});
});
it("should be redirected to the login page when accessing secret while not authenticated", function () {
return getPromised(HOME_URL + "/secret.html")
.then(function (data: Request.RequestResponse) {
Assert.equal(data.statusCode, 200);
Assert.equal(data.body, login_page);
});
});
it.skip("should fail the first factor", function () {
return postPromised(BASE_AUTH_URL + Endpoints.FIRST_FACTOR_POST, {
form: {
username: "admin",
password: "password",
}
})
.then(function (data: Request.RequestResponse) {
Assert.equal(data.body, "Bad credentials");
});
});
function login_as(username: string, password: string) {
return postPromised(BASE_AUTH_URL + Endpoints.FIRST_FACTOR_POST, {
form: {
username: "john",
password: "password",
}
})
.then(function (data: Request.RequestResponse) {
Assert.equal(data.statusCode, 302);
return BluebirdPromise.resolve();
});
}
it("should succeed the first factor", function () {
return login_as("john", "password");
});
describe("test ldap connection", function () {
it("should not fail after inactivity", function () {
const clock = Sinon.useFakeTimers();
return login_as("john", "password")
.then(function () {
clock.tick(3600000 * 24); // 24 hour
return login_as("john", "password");
})
.then(function () {
clock.restore();
return BluebirdPromise.resolve();
});
});
});
});
function getPromised(url: string) {
return request.getAsync(url);
}
function postPromised(url: string, body: Object) {
return request.postAsync(url, body);
}
function getHomePage(): BluebirdPromise<Request.RequestResponse> {
return getPromised(HOME_URL + "/");
}
function getLoginPage(): BluebirdPromise<Request.RequestResponse> {
return getPromised(BASE_AUTH_URL + Endpoints.FIRST_FACTOR_GET);
}

View File

@ -1,97 +0,0 @@
# The port to listen on
port: 80
# Log level
#
# Level of verbosity for logs
logs_level: debug
# LDAP configuration
#
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
ldap:
# The url of the ldap server
url: ldap://openldap
# The base dn for every entries
base_dn: dc=example,dc=com
# An additional dn to define the scope to all users
additional_user_dn: ou=users
# The user name attribute of users. Might uid for FreeIPA. 'cn' by default.
user_name_attribute: cn
# An additional dn to define the scope of groups
additional_group_dn: ou=groups
# The group name attribute of group. 'cn' by default.
group_name_attribute: cn
# The username and password of the admin user.
user: cn=admin,dc=example,dc=com
password: password
# Access Control
#
# Access control is a set of rules you can use to restrict the user access.
# Default (anyone), per-user or per-group rules can be defined.
#
# If 'access_control' is not defined, ACL rules are disabled and default policy
# is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
# the rules defined below.
# If no rule is provided, all domains are denied.
#
# '*' means 'any' subdomains and matches any string. It must stand at the
# beginning of the pattern.
access_control:
default:
- home.test.local
groups:
admin:
- '*.test.local'
dev:
- secret.test.local
- secret2.test.local
users:
harry:
- secret1.test.local
bob:
- '*.mail.test.local'
# Configuration of session cookies
#
# _secret_ the secret to encrypt session cookies
# _expiration_ the time before cookies expire
# _domain_ 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.
session:
secret: unsecure_secret
expiration: 3600000
domain: test.local
redis:
host: redis
port: 6379
# The directory where the DB files will be saved
store_directory: /var/lib/authelia/store
# Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration.
# Use only one available configuration: filesystem, gmail
notifier:
# For testing purpose, notifications can be sent in a file
filesystem:
filename: /var/lib/authelia/notifications/notification.txt
# Use your gmail account to send the notifications. You can use an app password.
# gmail:
# username: user
# password: password

View File

@ -1,15 +1,6 @@
version: '2' version: '2'
services: services:
authelia: integration-tests:
image: node:7-alpine
command: node /usr/src/dist/src/server/index.js /etc/authelia/config.yml
volumes:
- ./:/usr/src
- ./test/integration/config.yml:/etc/authelia/config.yml:ro
networks:
- example-network
int-test:
build: ./test/integration build: ./test/integration
command: ./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/integration command: ./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/integration
volumes: volumes:
@ -17,8 +8,7 @@ services:
networks: networks:
- example-network - example-network
nginx-tests:
nginx:
image: nginx:alpine image: nginx:alpine
volumes: volumes:
- ./example/nginx/index.html:/usr/share/nginx/html/index.html - ./example/nginx/index.html:/usr/share/nginx/html/index.html
@ -39,4 +29,3 @@ services:
- mx1.mail.test.local - mx1.mail.test.local
- mx2.mail.test.local - mx2.mail.test.local
- auth.test.local - auth.test.local

112
test/integration/main.ts Normal file
View File

@ -0,0 +1,112 @@
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
import Request = require("request");
import Assert = require("assert");
import BluebirdPromise = require("bluebird");
import Util = require("util");
import Redis = require("redis");
import Endpoints = require("../../src/server/endpoints");
const RequestAsync = BluebirdPromise.promisifyAll(Request) as typeof Request;
const DOMAIN = "test.local";
const PORT = 8080;
const HOME_URL = Util.format("https://%s.%s:%d", "home", DOMAIN, PORT);
const SECRET_URL = Util.format("https://%s.%s:%d", "secret", DOMAIN, PORT);
const SECRET1_URL = Util.format("https://%s.%s:%d", "secret1", DOMAIN, PORT);
const SECRET2_URL = Util.format("https://%s.%s:%d", "secret2", DOMAIN, PORT);
const MX1_URL = Util.format("https://%s.%s:%d", "mx1.mail", DOMAIN, PORT);
const MX2_URL = Util.format("https://%s.%s:%d", "mx2.mail", DOMAIN, PORT);
const BASE_AUTH_URL = Util.format("https://%s.%s:%d", "auth", DOMAIN, PORT);
const FIRST_FACTOR_URL = Util.format("%s/api/firstfactor", BASE_AUTH_URL);
const LOGOUT_URL = Util.format("%s/logout", BASE_AUTH_URL);
const redisOptions = {
host: "redis",
port: 6379
};
describe("test example environment", function () {
let redisClient: Redis.RedisClient;
before(function () {
redisClient = Redis.createClient(redisOptions);
});
function str_contains(str: string, pattern: string) {
return str.indexOf(pattern) != -1;
}
function test_homepage_is_correct(body: string) {
Assert(str_contains(body, BASE_AUTH_URL + Endpoints.LOGOUT_GET + "?redirect=" + HOME_URL + "/"));
Assert(str_contains(body, HOME_URL + "/secret.html"));
Assert(str_contains(body, SECRET_URL + "/secret.html"));
Assert(str_contains(body, SECRET1_URL + "/secret.html"));
Assert(str_contains(body, SECRET2_URL + "/secret.html"));
Assert(str_contains(body, MX1_URL + "/secret.html"));
Assert(str_contains(body, MX2_URL + "/secret.html"));
Assert(str_contains(body, "Access the secret"));
}
it("should access the home page", function () {
return RequestAsync.getAsync(HOME_URL)
.then(function (response: Request.RequestResponse) {
Assert.equal(200, response.statusCode);
test_homepage_is_correct(response.body);
});
});
it("should access the authentication page", function () {
return RequestAsync.getAsync(BASE_AUTH_URL)
.then(function (response: Request.RequestResponse) {
Assert.equal(200, response.statusCode);
Assert(response.body.indexOf("Sign in") > -1);
});
});
it("should fail first factor when wrong credentials are provided", function () {
return RequestAsync.postAsync(FIRST_FACTOR_URL, {
json: true,
body: {
username: "john",
password: "wrong password"
}
})
.then(function (response: Request.RequestResponse) {
Assert.equal(401, response.statusCode);
});
});
it("should redirect when correct credentials are provided during first factor", function () {
return RequestAsync.postAsync(FIRST_FACTOR_URL, {
json: true,
body: {
username: "john",
password: "password"
}
})
.then(function (response: Request.RequestResponse) {
Assert.equal(302, response.statusCode);
});
});
it("should have registered four sessions in redis", function (done) {
redisClient.dbsize(function (err: Error, count: number) {
Assert.equal(3, count);
done();
});
});
it("should redirect to home page when logout is called", function () {
return RequestAsync.getAsync(Util.format("%s?redirect=%s", LOGOUT_URL, HOME_URL))
.then(function (response: Request.RequestResponse) {
Assert.equal(200, response.statusCode);
Assert(response.body.indexOf("Access the secret") > -1);
});
});
});

View File

@ -1,86 +0,0 @@
# nginx-sso - example nginx config
#
# (c) 2015 by Johannes Gilger <heipei@hackvalue.de>
#
# This is an example config for using nginx with the nginx-sso cookie system.
# For simplicity, this config sets up two fictional vhosts that you can use to
# test against both components of the nginx-sso system: ssoauth & ssologin.
# In a real deployment, these vhosts would be separate hosts.
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
server {
listen 8080 ssl;
server_name auth.test.local localhost;
ssl on;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
location / {
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://authelia/;
proxy_intercept_errors on;
error_page 401 = /error/401;
error_page 403 = /error/403;
error_page 404 = /error/404;
}
}
server {
listen 8080 ssl;
root /usr/share/nginx/html;
server_name secret1.test.local secret2.test.local secret.test.local
home.test.local mx1.mail.test.local mx2.mail.test.local;
ssl on;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
error_page 401 = @error401;
location @error401 {
return 302 https://auth.test.local:8080;
}
location /auth_verify {
internal;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://authelia/verify;
}
location = /secret.html {
auth_request /auth_verify;
auth_request_set $user $upstream_http_x_remote_user;
proxy_set_header X-Forwarded-User $user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups;
auth_request_set $expiry $upstream_http_remote_expiry;
proxy_set_header Remote-Expiry $expiry;
}
}
}

View File

@ -1,23 +0,0 @@
import Redis = require("redis");
import Assert = require("assert");
const redisOptions = {
host: "redis",
port: 6379
};
describe("test redis is correctly used", function () {
let redisClient: Redis.RedisClient;
before(function () {
redisClient = Redis.createClient(redisOptions);
});
it("should have registered at least one session", function (done) {
redisClient.dbsize(function (err: Error, count: number) {
Assert.equal(1, count);
done();
});
});
});