[BREAKING] Create a suite for kubernetes tests.

Authelia client uses hash router instead of browser router in order to work
with Kubernetes nginx-ingress-controller. This is also better for users having
old browsers.

This commit is breaking because it requires to change the configuration of the
proxy to include the # in the URL of the login portal.
pull/331/head
Clement Michaud 2019-03-03 23:51:52 +01:00
parent f8a12b8482
commit 76fa325f08
157 changed files with 1890 additions and 1762 deletions

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ Configuration.schema.json
users_database.test.yml
.suite
.kube

View File

@ -15,18 +15,16 @@ addons:
hosts:
- admin.example.com
- login.example.com
- single_factor.example.com
- singlefactor.example.com
- dev.example.com
- home.example.com
- mx1.mail.example.com
- mx2.mail.example.com
- public.example.com
- secure.example.com
- authelia.example.com
- admin.example.com
before_install:
- npm install -g npm@'>=2.13.5'
- pushd client && npm install && popd
- mail.example.com
before_script:
- export DISPLAY=:99.0

View File

@ -41,14 +41,18 @@ For more details about the features, follow [Features](./docs/features.md).
## Getting Started
If you want to quickly test Authelia with Docker, we recommend you read
[Getting Started](./docs/getting-started.md).
You can start off with
source bootstrap.sh
If you want to go further, please read [Getting Started](./docs/getting-started.md).
## Deployment
Now that you have tested **Authelia** and you want to try it out in your own infrastructure, you can learn how to deploy and use it with
[Deployment](./docs/deployment-production.md). This guide will show you how to deploy
it on bare metal as well as on Kubernetes.
Now that you have tested **Authelia** and you want to try it out in your own infrastructure,
you can learn how to deploy and use it with [Deployment](./docs/deployment-production.md).
This guide will show you how to deploy it on bare metal as well as on
[Kubernetes](https://kubernetes.io/).
## Security

49
bootstrap.sh 100644
View File

@ -0,0 +1,49 @@
export PATH=$(pwd)/scripts:/tmp:$PATH
export PS1="(authelia) $PS1"
echo "[BOOTSTRAP] Installing npm packages..."
npm i
pushd client
npm i
popd
echo "[BOOTSTRAP] Checking if Docker is installed..."
if [ ! -x "$(command -v docker)" ];
then
echo "[ERROR] You must install docker on your machine.";
return
fi
echo "[BOOTSTRAP] Checking if docker-compose is installed..."
if [ ! -x "$(command -v docker-compose)" ];
then
echo "[ERROR] You must install docker-compose on your machine.";
return;
fi
echo "[BOOTSTRAP] Checking if example.com domain is forwarded to your machine..."
cat /etc/hosts | grep "login.example.com" > /dev/null
if [ $? -ne 0 ];
then
echo "[ERROR] Please add those lines to /etc/hosts:
127.0.0.1 home.example.com
127.0.0.1 public.example.com
127.0.0.1 secure.example.com
127.0.0.1 dev.example.com
127.0.0.1 admin.example.com
127.0.0.1 mx1.mail.example.com
127.0.0.1 mx2.mail.example.com
127.0.0.1 singlefactor.example.com
127.0.0.1 login.example.com"
return;
fi
echo "[BOOTSTRAP] Running additional bootstrap steps..."
authelia-scripts bootstrap
echo "[BOOTSTRAP] Run 'authelia-scripts suites start dockerhub' to start Authelia and visit https://home.example.com:8080."
echo "[BOOTSTRAP] More details at https://github.com/clems4ever/authelia/blob/master/docs/getting-started.md"

View File

@ -3,14 +3,14 @@ import './App.scss';
import { Route, Switch } from "react-router-dom";
import { routes } from './routes/index';
import { createBrowserHistory } from 'history';
import { createHashHistory } from 'history';
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducers';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
const history = createBrowserHistory();
const history = createHashHistory();
const store = createStore(
reducer(history),
compose(

View File

@ -1,15 +0,0 @@
import { Dispatch } from "redux";
import * as AutheliaService from '../services/AutheliaService';
export default async function(url: string) {
try {
// Check the url against the backend before redirecting.
await AutheliaService.checkRedirection(url);
window.location.href = url;
} catch (e) {
console.error(
'Cannot redirect since the URL is not in the protected domain.' +
'This behavior could be malicious so please the issue to an administrator.');
throw e;
}
}

View File

@ -7,7 +7,7 @@ import CircleLoader, { Status } from "../CircleLoader/CircleLoader";
export interface OwnProps {
username: string;
redirectionUrl: string;
redirectionUrl: string | null;
}
export interface DispatchProps {
@ -27,7 +27,7 @@ class AlreadyAuthenticated extends Component<Props> {
</div>
<div className={styles.statusIcon}><CircleLoader status={Status.SUCCESSFUL} /></div>
</div>
<a href={this.props.redirectionUrl}>{this.props.redirectionUrl}</a>
{(this.props.redirectionUrl) ? <a href={this.props.redirectionUrl}>{this.props.redirectionUrl}</a> : null}
<div className={styles.logoutButtonContainer}>
<Button
onClick={this.props.onLogoutClicked}

View File

@ -20,7 +20,7 @@ export interface StateProps {
}
export interface DispatchProps {
onAuthenticationRequested(username: string, password: string, rememberMe: boolean): void;
onAuthenticationRequested(username: string, password: string, rememberMe: boolean): Promise<void>;
}
export type Props = OwnProps & StateProps & DispatchProps;
@ -136,7 +136,11 @@ class FirstFactorForm extends Component<Props, State> {
this.props.onAuthenticationRequested(
this.state.username,
this.state.password,
this.state.rememberMe);
this.state.rememberMe)
.catch((err: Error) => console.error(err))
.finally(() => {
this.setState({username: '', password: ''});
})
}
}

View File

@ -6,7 +6,6 @@ import { RootState } from '../../../reducers';
import * as AutheliaService from '../../../services/AutheliaService';
import to from 'await-to-js';
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
import SafelyRedirectBehavior from '../../../behaviors/SafelyRedirectBehavior';
const mapStateToProps = (state: RootState): StateProps => {
return {
@ -16,7 +15,7 @@ const mapStateToProps = (state: RootState): StateProps => {
}
function onAuthenticationRequested(dispatch: Dispatch, redirectionUrl: string | null) {
return async (username: string, password: string, rememberMe: boolean) => {
return async (username: string, password: string, rememberMe: boolean): Promise<void> => {
let err, res;
// Validate first factor
@ -26,32 +25,37 @@ function onAuthenticationRequested(dispatch: Dispatch, redirectionUrl: string |
if (err) {
await dispatch(authenticateFailure(err.message));
return;
throw new Error(err.message);
}
if (!res) {
await dispatch(authenticateFailure('No response'));
return;
throw new Error('No response');
}
if (res.status === 200) {
const json = await res.json();
if ('error' in json) {
await dispatch(authenticateFailure(json['error']));
return;
throw new Error(json['error']);
}
dispatch(authenticateSuccess());
if ('redirect' in json) {
window.location.href = json['redirect'];
return;
}
// fetch state to move to next stage in case redirect is not possible
await FetchStateBehavior(dispatch);
} else if (res.status === 204) {
dispatch(authenticateSuccess());
// fetch state to move to next stage
FetchStateBehavior(dispatch);
await FetchStateBehavior(dispatch);
} else {
dispatch(authenticateFailure('Unknown error'));
throw new Error('Unknown error... (' + res.status + ')');
}
}
}

View File

@ -3,13 +3,20 @@ import { RootState } from '../../../reducers';
import { Dispatch } from 'redux';
import u2fApi from 'u2f-api';
import to from 'await-to-js';
import { securityKeySignSuccess, securityKeySign, securityKeySignFailure, setSecurityKeySupported, oneTimePasswordVerification, oneTimePasswordVerificationFailure, oneTimePasswordVerificationSuccess } from '../../../reducers/Portal/SecondFactor/actions';
import {
securityKeySignSuccess,
securityKeySign,
securityKeySignFailure,
setSecurityKeySupported,
oneTimePasswordVerification,
oneTimePasswordVerificationFailure,
oneTimePasswordVerificationSuccess
} from '../../../reducers/Portal/SecondFactor/actions';
import SecondFactorForm, { OwnProps, StateProps } from '../../../components/SecondFactorForm/SecondFactorForm';
import * as AutheliaService from '../../../services/AutheliaService';
import { push } from 'connected-react-router';
import fetchState from '../../../behaviors/FetchStateBehavior';
import LogoutBehavior from '../../../behaviors/LogoutBehavior';
import SafelyRedirectBehavior from '../../../behaviors/SafelyRedirectBehavior';
const mapStateToProps = (state: RootState): StateProps => ({
securityKeySupported: state.secondFactor.securityKeySupported,
@ -20,7 +27,7 @@ const mapStateToProps = (state: RootState): StateProps => ({
oneTimePasswordVerificationError: state.secondFactor.oneTimePasswordVerificationError,
});
async function triggerSecurityKeySigning(dispatch: Dispatch) {
async function triggerSecurityKeySigning(dispatch: Dispatch, redirectionUrl: string | null) {
let err, result;
dispatch(securityKeySign());
[err, result] = await to(AutheliaService.requestSigning());
@ -45,25 +52,39 @@ async function triggerSecurityKeySigning(dispatch: Dispatch) {
throw 'No response';
}
[err, result] = await to(AutheliaService.completeSecurityKeySigning(result));
[err, result] = await to(AutheliaService.completeSecurityKeySigning(result, redirectionUrl));
if (err) {
await dispatch(securityKeySignFailure(err.message));
throw err;
}
await dispatch(securityKeySignSuccess());
try {
await redirectIfPossible(dispatch, result as Response);
dispatch(securityKeySignSuccess());
await handleSuccess(dispatch, 1000);
} catch (err) {
dispatch(securityKeySignFailure(err.message));
}
}
async function handleSuccess(dispatch: Dispatch, ownProps: OwnProps, duration?: number) {
async function redirectIfPossible(dispatch: Dispatch, res: Response) {
if (res.status === 204) return;
const body = await res.json();
if ('error' in body) {
throw new Error(body['error']);
}
if ('redirect' in body) {
window.location.href = body['redirect'];
return;
}
return;
}
async function handleSuccess(dispatch: Dispatch, duration?: number) {
async function handle() {
if (ownProps.redirectionUrl) {
try {
await SafelyRedirectBehavior(ownProps.redirectionUrl);
} catch (e) {
await fetchState(dispatch);
}
} else {
await fetchState(dispatch);
}
await fetchState(dispatch);
}
if (duration) {
@ -88,14 +109,13 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
const isU2FSupported = await u2fApi.isSupported();
if (isU2FSupported) {
await dispatch(setSecurityKeySupported(true));
await triggerSecurityKeySigning(dispatch);
await handleSuccess(dispatch, ownProps, 1000);
await triggerSecurityKeySigning(dispatch, ownProps.redirectionUrl);
}
},
onOneTimePasswordValidationRequested: async (token: string) => {
let err, res;
dispatch(oneTimePasswordVerification());
[err, res] = await to(AutheliaService.verifyTotpToken(token));
[err, res] = await to(AutheliaService.verifyTotpToken(token, ownProps.redirectionUrl));
if (err) {
dispatch(oneTimePasswordVerificationFailure(err.message));
throw err;
@ -105,13 +125,13 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
throw 'No response';
}
const body = await res.json();
if ('error' in body) {
dispatch(oneTimePasswordVerificationFailure(body['error']));
throw body['error'];
try {
await redirectIfPossible(dispatch, res);
dispatch(oneTimePasswordVerificationSuccess());
await handleSuccess(dispatch);
} catch (err) {
dispatch(oneTimePasswordVerificationFailure(err.message));
}
dispatch(oneTimePasswordVerificationSuccess());
await handleSuccess(dispatch, ownProps);
},
}
}

View File

@ -75,24 +75,36 @@ export async function requestSigning() {
});
}
export async function completeSecurityKeySigning(response: u2fApi.SignResponse) {
export async function completeSecurityKeySigning(
response: u2fApi.SignResponse, redirectionUrl: string | null) {
const headers: Record<string, string> = {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
if (redirectionUrl) {
headers['X-Target-Url'] = redirectionUrl;
}
return fetchSafe('/api/u2f/sign', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
headers: headers,
body: JSON.stringify(response),
});
}
export async function verifyTotpToken(token: string) {
export async function verifyTotpToken(
token: string, redirectionUrl: string | null) {
const headers: Record<string, string> = {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
if (redirectionUrl) {
headers['X-Target-Url'] = redirectionUrl;
}
return fetchSafe('/api/totp', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
headers: headers,
body: JSON.stringify({token}),
})
}
@ -124,23 +136,3 @@ export async function resetPassword(newPassword: string) {
body: JSON.stringify({password: newPassword})
});
}
export async function checkRedirection(url: string) {
const res = await fetch('/api/redirect', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({url})
})
if (res.status !== 200) {
throw new Error('Status code ' + res.status);
}
const text = await res.text();
if (text !== 'OK') {
throw new Error('Cannot redirect');
}
return;
}

View File

@ -40,7 +40,7 @@ class AuthenticationView extends Component<Props> {
} else if (this.props.stage === Stage.ALREADY_AUTHENTICATED) {
return <AlreadyAuthenticated
username={this.props.remoteState.username}
redirectionUrl={this.props.remoteState.default_redirection_url} />;
redirectionUrl={this.props.redirectionUrl} />;
}
return <FirstFactorForm redirectionUrl={this.props.redirectionUrl} />;
}

View File

@ -130,8 +130,10 @@ access_control:
rules:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: two_factor
- domain: single_factor.example.com
- domain: singlefactor.example.com
policy: one_factor
# Rules applied to 'admin' group

View File

@ -4,17 +4,20 @@ Authelia comes with a set of dedicated scripts doing a broad range of operations
building the distributed version of Authelia, building the Docker image, running suites,
testing the code, etc...
You can access the scripts usage by running the following command:
Those scripts becomes available after sourcing the bootstrap.sh script with
npm run scripts
source bootstrap.sh
Then, you can access the scripts usage by running the following command:
authelia-scripts --help
For instance, you can build Authelia with:
npm run scripts build
authelia-scripts build
Or start the *basic* suite with:
npm run scripts suites start basic
authelia-scripts suites start basic
You will find more information in the scripts usage helpers.

View File

@ -0,0 +1,54 @@
# Build and dev
**Authelia** is written in Typescript and built with [Authelia scripts](./docs/authelia-scripts.md).
In order to build and contribute to **Authelia**, you need to make sure Node with version >= 8 and < 10
and NPM is installed on your machine.
## Build
**Authelia** is made of two parts: the frontend and the backend.
The frontend is a [React](https://reactjs.org/) application written in Typescript and
the backend is an express application also written in Typescript.
The following command builds **Authelia** under dist/:
authelia-scripts build
And then you can also build the Docker image with:
authelia-scripts docker build
## Development
In order to ease development, Authelia uses the concept of [suites]. This is
a kind of virutal environment for **Authelia**, it allows you to run **Authelia** in a complete
environment, develop and test your patches. A hot-reload feature has been implemented so that
you can test your changes in realtime.
The next command will start the suite called [basic](./test/suites/basic/README.md):
authelia-scripts suites start basic
Then, edit the code and observe how **Authelia** is automatically updated.
### Unit tests
To run the unit tests written in Mocha, run:
authelia-scripts unittest
### Integration tests
Integration tests also run with Mocha and are based on Selenium. They generally
require a complete environment made of several components like redis, mongo and a LDAP
to run. That's why [suites] have been created. At this point, the *basic* suite should
already be running and you can run the tests related to this suite with the following
command:
authelia-scripts suites test
[suites]: ./suites.md

View File

@ -1,53 +0,0 @@
# Build
**Authelia** is written in Typescript and built with [Authelia scripts](docs/authelia-scripts.md).
In order to build **Authelia**, you need to make sure Node with version >= 8 and < 10 and NPM is
installed on your machine.
Then, run the following command to install the node modules:
npm install
And, this command to build **Authelia** under dist/:
npm run build
Then you can also build the Docker image with:
npm run docker build
## Details
### Build
**Authelia** is made of two parts: the frontend and the backend.
The frontend is a [React](https://reactjs.org/) application written in Typescript and
the backend is an express application also written in Typescript.
### Tests
There are two kind of tests: unit tests and integration tests.
### Unit tests
To run the unit tests, run:
npm run unittest
### Integration tests
Integration tests run with Mocha and are based on Selenium. They generally
require a complete environment made of several components like redis, mongo and a LDAP
to run.
In order to simplify the creation of such environments, Authelia comes with a concept of
[Suites] that basically act as virtual environments for running either
manual or integration tests.
Please read the documentation related to [Suites] in order to discover
how to run related tests.
[Suites]: ./suites.md

View File

@ -29,14 +29,13 @@ the root of the repo.
### Deploy With Docker
docker pull clems4ever/authelia
docker run -v /path/to/your/config.yml:/etc/authelia/config.yml clems4ever/authelia
## On top of Kubernetes
<img src="/images/kube-logo.png" width="24" align="left">
**Authelia** can also be used on top of [Kubernetes] using
**Authelia** can also be installed on top of [Kubernetes] using
[nginx ingress controller](https://github.com/kubernetes/ingress-nginx).
Please refer to the following [documentation](../example/kube/README.md)

View File

@ -3,61 +3,17 @@
**Authelia** can be tested in a matter of seconds with docker-compose based
on the latest image available on [Dockerhub].
## Pre-requisites
In order to deploy the latest release locally, run the following command and
follow the instructions of bootstrap.sh:
In order to test **Authelia**, we need to make sure that:
- **Docker** and **docker-compose** are installed on your computer.
- Ports 8080 and 8085 are not already used on your machine.
- Some subdomains of **example.com** redirect to your test infrastructure.
source bootstrap.sh
### Docker & docker-compose
Then, start the *dockerhub* [suite].
Make sure you have **docker** and **docker-compose** installed on your
machine.
Here are the versions used for testing in Travis:
authelia-scripts suites start dockerhub
$ docker --version
Docker version 17.03.1-ce, build c6d412e
$ docker-compose --version
docker-compose version 1.14.0, build c7bdf9e
### Available port
Make sure you don't have anything listening on port 8080 and 8085.
The port 8080 will be our frontend load balancer serving both **Authelia**'s portal and the
applications we want to protect.
The port 8085 is serving a webmail used to receive emails sent by **Authelia**
to validate your identity when registering U2F or TOTP secrets or when
resetting your password.
### Subdomain aliases
In order to simulate the behavior of a DNS resolving some test subdomains of **example.com**
to your machine, we need to add the following lines to your **/etc/hosts**. It will alias the
subdomains so that nginx can redirect requests to the correct virtual host.
127.0.0.1 home.example.com
127.0.0.1 public.example.com
127.0.0.1 dev.example.com
127.0.0.1 admin.example.com
127.0.0.1 mx1.mail.example.com
127.0.0.1 mx2.mail.example.com
127.0.0.1 single_factor.example.com
127.0.0.1 login.example.com
## Deploy
To deploy **Authelia** using the latest image from [Dockerhub], run the
following command:
npm install commander
npm run scripts suites start dockerhub
A Suites is a virtual environment for running Authelia. If you want more details please
read the related [documentation](./suites.md).
A [suite] is kind of a virtual environment for running Authelia.
If you want more details please read the related [documentation](./suites.md).
## Test it!
@ -77,25 +33,52 @@ Below is what the login page looks like after you accepted all exceptions:
</p>
You can use one of the users listed in [https://home.example.com:8080/](https://home.example.com:8080/).
The rights granted to each user and group is also provided there.
The rights granted to each user and group is also provided in the page as
a list of rules.
At some point, you'll be required to register your second factor, either
U2F or TOTP. Since your security is **Authelia**'s priority, it will send
At some point, you'll be required to register your second factor device.
Since your security is **Authelia**'s priority, it will send
an email to the email address of the user to confirm the user identity.
Since we're running a test environment, we provide a fake webmail called
*MailCatcher* from which you can checkout the email and confirm
your identity.
The webmail is accessible from
[http://localhost:8085](http://localhost:8085).
**Note:** If you cannot deploy the fake webmail for any reason. You can
configure **Authelia** to use the filesystem notifier (option available
in [config.template.yml]) that will send the content of the email in a
file instead of sending an email. It is advised to not use this option
in production.
[http://mail.example.com:8080](http://mail.example.com:8085).
Enjoy!
## FAQ
### What version of Docker and docker-compose should I use?
Here are the versions used for testing in Travis:
$ docker --version
Docker version 17.03.1-ce, build c6d412e
$ docker-compose --version
docker-compose version 1.14.0, build c7bdf9e
### How am I supposed to access the subdomains of example.com?
Well, in order to test Authelia, we will fake your browser that example.com is
served by your machine. To do that, open */etc/hosts* and append the following
lines:
127.0.0.1 home.example.com
127.0.0.1 public.example.com
127.0.0.1 secure.example.com
127.0.0.1 dev.example.com
127.0.0.1 admin.example.com
127.0.0.1 mx1.mail.example.com
127.0.0.1 mx2.mail.example.com
127.0.0.1 singlefactor.example.com
127.0.0.1 login.example.com
### What should I do if I want to contribute?
You can refer to the dedicated documentation [here](./build-and-dev.md).
[config.template.yml]: ../config.template.yml
[DockerHub]: https://hub.docker.com/r/clems4ever/authelia/
[Build]: ./build.md
[suite]: ./suites.md

View File

@ -1,18 +1,18 @@
# Suites
Authelia is a single component in interaction with many others. Consequently, testing the features
is not as easy as we might think. Consequently, a suite is kind of a 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.
is not as easy as we might think. In order to solve this problem, Authelia came up with the concept of
suite which is a kind of virtual environment for Authelia, it allows to create an environment made of
components such as nginx, redis or mongo in which Authelia can run and be tested.
This abstraction allows to prepare an environment for manual testing during development and also to
craft and run integration tests.
## Start a suite.
Starting a suite called *simple* is done with the following command:
Starting a suite called *basic* is done with the following command:
npm run scripts suites start simple
authelia-scripts suites start basic
It will start the suite and block until you hit ctrl-c to stop the suite.
@ -20,9 +20,9 @@ It will start the suite and block until you hit ctrl-c to stop the suite.
### Run tests of running suite
If you are already running a suite with the previous command, you can simply type:
If a suite is already running, you can simply type:
npm run scripts test
authelia-scripts suites test
and this will run the tests related to the running suite.
@ -31,18 +31,26 @@ and this will run the tests related to the running suite.
However, if no suite is running and you still want to test a particular suite like *complete*.
You can do so with the next command:
npm run scripts test complete
authelia-scripts suites test complete
This command will run the tests for the *complete* suite using the built version of Authelia that
should be located in *dist*.
WARNING: Authelia must be built before running this command.
WARNING: Authelia must be built with `authelia-scripts build` and possibly
`authelia-scripts docker build` before running this command.
### Run all tests of all suites
Running all tests is as easy as making sure that there is no running suite and typing:
Running all tests is easy. Make sure that no suite is already running and run:
npm run scripts test
authelia-scripts suites test
### Run tests in headless mode
In order to run the tests without seeing the windows creating and vanishing, one
can run the tests in headless mode with:
authelia-scripts suites test --headless
## Create a suite
@ -51,5 +59,5 @@ Creating a suite is as easy as creating a new directory with at least two files:
* **environment.ts** - It defines the setup and teardown phases when creating the environment. The *setup*
phase is the phase when the required components will be spawned and Authelia will start while the *teardown*
is executed when the suite is destroyed (ctrl-c hit by the user).
* **test.ts** - It defines a set of tests to run in the virtual environment of the suite.
is executed when the suite is destroyed (ctrl-c hit by the user) or the tests are finished.
* **test.ts** - It defines a set of tests to run against the suite.

View File

@ -0,0 +1,4 @@
FROM nginx:alpine
ADD ./html /usr/share/nginx/html
ADD ./nginx.conf /etc/nginx/nginx.conf

View File

@ -1,9 +1,6 @@
version: '2'
services:
nginx-backend:
image: nginx:alpine
volumes:
- ./example/compose/nginx/backend/html:/usr/share/nginx/html
- ./example/compose/nginx/backend/nginx.conf:/etc/nginx/nginx.conf
image: authelia-example-backend
networks:
- authelianet

View File

@ -15,7 +15,13 @@
public.example.com <a href="https://public.example.com:8080/"> / index.html</a>
</li>
<li>
secret.example.com
secure.example.com <a href="https://secure.example.com:8080/"> / secret.html</a>
</li>
<li>
singlefactor.example.com <a href="https://singlefactor.example.com:8080/secret.html"> / secret.html</a>
</li>
<li>
dev.example.com
<ul>
<li>Groups
<ul>
@ -51,12 +57,9 @@
<li>
mx2.main.example.com <a href="https://mx2.mail.example.com:8080/secret.html"> / secret.html</a>
</li>
<li>
single_factor.example.com <a href="https://single_factor.example.com:8080/secret.html"> / secret.html</a>
</li>
</ul>
You can also log off by visiting the following <a href="https://login.example.com:8080/logout?rd=https://home.example.com:8080/">link</a>.
You can also log off by visiting the following <a href="https://login.example.com:8080/#/logout?rd=https://home.example.com:8080/">link</a>.
<h1>List of users</h1>
Here is the list of credentials you can log in with to test access control.<br/>
@ -74,61 +77,57 @@
<p></p>These rules are extracted from the configuration file
<a href="https://github.com/clems4ever/authelia/blob/master/config.template.yml">config.template.yml</a>.</p>
<pre id="rules" style="border: 1px grey solid; padding: 20px; display: inline-block;">
# Default policy can either be `allow` or `deny`.
# It is the policy applied to any resource if it has not been overriden
# in the `any`, `groups` or `users` category.
default_policy: deny
default_policy: deny
rules:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: two_factor
- domain: singlefactor.example.com
policy: one_factor
# The rules that apply to anyone.
# The value is a list of rules.
# Rules applied to 'admin' group
- domain: 'mx2.mail.example.com'
subject: 'group:admin'
policy: deny
- domain: '*.example.com'
subject: 'group:admin'
policy: two_factor
any:
- domain: public.example.com
policy: allow
# Rules applied to 'dev' group
- domain: dev.example.com
resources:
- '^/groups/dev/.*$'
subject: 'group:dev'
policy: two_factor
# Group-based rules. The key is a group name and the value
# is a list of rules.
# Rules applied to user 'john'
- domain: dev.example.com
resources:
- '^/users/john/.*$'
subject: 'user:john'
policy: two_factor
groups:
admin:
# All resources in all domains
- domain: '*.example.com'
policy: allow
# Except mx2.mail.example.com (it restricts the first rule)
- domain: 'mx2.mail.example.com'
policy: deny
dev:
- domain: dev.example.com
policy: allow
resources:
- '^/groups/dev/.*$'
# User-based rules. The key is a user name and the value
# is a list of rules.
# Rules applied to user 'harry'
- domain: dev.example.com
resources:
- '^/users/harry/.*$'
subject: 'user:harry'
policy: two_factor
users:
john:
- domain: dev.example.com
policy: allow
resources:
- '^/users/john/.*$'
harry:
- domain: dev.example.com
policy: allow
resources:
- '^/users/harry/.*$'
bob:
- domain: '*.mail.example.com'
policy: allow
- domain: 'dev.example.com'
policy: allow
resources:
- '^/users/bob/.*$'
- domain: 'dev.example.com'
policy: allow
resources:
- '^/users/harry/.*$'</pre>
# Rules applied to user 'bob'
- domain: '*.mail.example.com'
subject: 'user:bob'
policy: two_factor
- domain: 'dev.example.com'
resources:
- '^/users/bob/.*$'
subject: 'user:bob'
policy: two_factor
</pre>
</body>
</html>

View File

@ -4,7 +4,6 @@
<link rel="icon" href="/icon.png" type="image/png" />
</head>
<body>
<h1>Secret</h1>
This is a very important secret!<br/>
Go back to <a href="https://home.example.com:8080/">home page</a>.
</body>

View File

@ -18,6 +18,12 @@ http {
server_name public.example.com;
}
server {
listen 80;
root /usr/share/nginx/html/secure;
server_name secure.example.com;
}
server {
listen 80;
root /usr/share/nginx/html/admin;
@ -38,8 +44,8 @@ http {
server {
listen 80;
root /usr/share/nginx/html/single_factor;
server_name single_factor.example.com;
root /usr/share/nginx/html/singlefactor;
server_name singlefactor.example.com;
}
}

View File

@ -1,12 +0,0 @@
version: '2'
services:
nginx-portal:
image: nginx:alpine
volumes:
- ./example/compose/nginx/minimal/nginx.conf:/etc/nginx/nginx.conf
- ./example/compose/nginx/minimal/html:/usr/share/nginx/html
- ./example/compose/nginx/minimal/ssl:/etc/ssl
ports:
- "8080:443"
networks:
- authelianet

View File

@ -1,32 +0,0 @@
<!DOCTYPE>
<html>
<head>
<title>Home page</title>
<link rel="icon" href="/icon.png" type="image/png" />
</head>
<body>
<h1>Access the secret</h1>
<span style="font-size: 1.2em; color: red">You need to log in to access the secret!</span><br/><br/> Try to access it using
the following links to test Authelia.<br/>
<ul>
<li>
admin.example.com <a href="https://admin.example.com:8080/secret.html"> / secret.html</a>
</li>
</ul>
You can also log off by visiting the following <a href="https://login.example.com:8080/logout?rd=https://home.example.com:8080/">link</a>.
<h1>List of users</h1>
Here is the list of credentials you can log in with.<br/>
<br/> Once first factor is passed, you will need to follow the links to register a secret for the second factor.<br/> Authelia
will send you a fictituous email stored in a <strong>local file</strong> called <strong>/tmp/authelia/notification.txt</strong>.<br/>
It will provide you with the link to complete the registration allowing you to authenticate with 2-factor.
<ul>
<li><strong>john / password</strong>: belongs to <em>admin</em> and <em>dev</em> groups.</li>
<li><strong>bob / password</strong>: belongs to <em>dev</em> group only.</li>
<li><strong>harry / password</strong>: does not belong to any group.</li>
</ul>
</body>
</html>

View File

@ -1,96 +0,0 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 443 ssl;
server_name login.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_endpoint http://authelia:8080;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_intercept_errors on;
proxy_pass $upstream_endpoint;
if ($request_method !~ ^(POST)$){
error_page 401 = /error/401;
error_page 403 = /error/403;
error_page 404 = /error/404;
}
}
}
server {
listen 443 ssl;
server_name home.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_endpoint http://nginx-backend;
root /usr/share/nginx/html/home;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
}
server {
listen 443 ssl;
server_name admin.example.com;
root /usr/share/nginx/html/admin;
resolver 127.0.0.11 ipv6=off;
set $upstream_verify http://authelia:8080/api/verify;
ssl_certificate /etc/ssl/server.crt;
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 {
internal;
proxy_set_header Host $http_host;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_pass $upstream_verify;
}
location / {
auth_request /auth_verify;
auth_request_set $redirect $upstream_http_redirect;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
}
}
}

View File

@ -1,11 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDNloThcTVDIU1uscEdGFIDndqcCwnmYF4bs6bpJ1BxkBhq
GUNYRu12hjiHLSA80ZwhNxZ4T5YD4+81Gvs9zMoSGd4jJRSBX6evPTXR8zkmcQI/
EtN7WgXOXhTx3JiIGlPOI3OGJR+rvfc9FiNx30FC1wpw3gt2ZC+NQeedDvdPKwID
AQABoAAwDQYJKoZIhvcNAQELBQADgYEAmCX60kspIw1Zfb79AQOarFW5Q2K2h5Vx
/cRbDyHlKtbmG77EtICccULyqf76B1gNRw5Zq3lSotSUcLzsWcdesXCFDC7k87Qf
mpQKPj6GdTYJvdWf8aDwt32tAqWuBIRoAbdx5WbFPPWVfDcm7zDJefBrhNUDH0Qd
vcnxjvPMmOM=
-----END CERTIFICATE REQUEST-----

View File

@ -106,10 +106,79 @@ http {
}
}
server {
listen 443 ssl;
server_name mail.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_endpoint http://smtp:1080;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location / {
proxy_set_header Host $http_host;
proxy_pass $upstream_endpoint;
}
}
server {
listen 443 ssl;
server_name public.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_verify <%= authelia_backend %>/api/verify;
set $upstream_endpoint http://nginx-backend;
ssl_certificate /etc/ssl/server.crt;
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 {
internal;
proxy_set_header Host $http_host;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_pass $upstream_verify;
}
location / {
auth_request /auth_verify;
auth_request_set $redirect $upstream_http_redirect;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header X-Forwarded-User $user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups;
proxy_set_header Host $http_host;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_endpoint;
}
}
server {
listen 443 ssl;
server_name admin.example.com
secure.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_verify <%= authelia_backend %>/api/verify;
set $upstream_endpoint http://nginx-backend;
@ -150,8 +219,7 @@ http {
proxy_set_header Host $http_host;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_endpoint;
}
@ -167,63 +235,12 @@ http {
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Custom-Forwarded-Groups $groups;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_headers;
}
}
server {
listen 443 ssl;
server_name admin.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_verify <%= authelia_backend %>/api/verify;
set $upstream_endpoint http://nginx-backend;
ssl_certificate /etc/ssl/server.crt;
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 {
internal;
proxy_set_header Host $http_host;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_pass $upstream_verify;
}
location / {
auth_request /auth_verify;
auth_request_set $redirect $upstream_http_redirect;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header X-Forwarded-User $user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups;
proxy_set_header Host $http_host;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
proxy_pass $upstream_endpoint;
}
}
server {
listen 443 ssl;
server_name dev.example.com;
@ -267,8 +284,7 @@ http {
proxy_set_header Host $http_host;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_endpoint;
}
@ -317,8 +333,7 @@ http {
proxy_set_header Host $http_host;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_endpoint;
}
@ -326,7 +341,7 @@ http {
server {
listen 443 ssl;
server_name single_factor.example.com;
server_name singlefactor.example.com;
resolver 127.0.0.11 ipv6=off;
set $upstream_verify <%= authelia_backend %>/api/verify;
@ -371,8 +386,7 @@ http {
proxy_set_header Host $http_host;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_endpoint;
}
@ -388,8 +402,7 @@ http {
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Custom-Forwarded-Groups $groups;
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
error_page 403 = https://login.example.com:8080/error/403;
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
proxy_pass $upstream_headers;
}

View File

@ -4,6 +4,5 @@ services:
image: schickling/mailcatcher
ports:
- "1025:1025"
- "8085:1080"
networks:
- authelianet

View File

@ -4,66 +4,20 @@ Authelia is now available on Kube in order to protect your most critical
applications using 2-factor authentication and Single Sign-On.
This example leverages [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
v0.13.0 to delegate authentications and authorizations to Authelia within
the cluster.
to delegate authentication and authorization to Authelia within the cluster.
## Getting started
In order to deploy Authelia on Kube, you must have a cluster at hand. If you
don't, please follow the next section otherwise skip it and go
to the next.
You can either try to install **Authelia** on your running instance of Kubernetes
or deploy the dedicated [suite](/docs/suites.md) called *kubernetes*.
### Set up a Kube cluster
Hopefully, spawning a development cluster from scratch has become very
easy lately with the use of **minikube**. This project creates a VM on your
computer and start a Kube cluster inside it. It also configure a CLI called
kubectl so that you can deploy applications in the cluster right away.
The simplest way to start a Kubernetes cluster is to deploy the *kubernetes* suite with
Basically, you need to follow the instruction from the [repository](https://github.com/kubernetes/minikube).
It should be a matter of downloading the binary and start the cluster with
two commands:
authelia-scripts suites start kubernetes
```
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
minikube start # you can use --vm-driver flag for selecting your hypervisor (virtualbox by default otherwise)
```
After few seconds, your cluster should be working and you should be able to
get access to the cluster by creating a proxy with
```
kubectl proxy
```
and visiting `http://localhost:8001/ui`
### Deploy Authelia
Once the cluster is ready and you can access it, run the following command to
deploy Authelia:
```
./bootstrap.sh
```
In order to visit the test applications that have been deployed to test
Authelia, edit your /etc/hosts and add the following lines replacing the IP
with the IP of your VM given by minikube:
```
192.168.39.26 login.kube.example.com
192.168.39.26 app1.kube.example.com
192.168.39.26 app2.kube.example.com
192.168.39.26 mail.kube.example.com
192.168.39.26 home.kube.example.com
# The domain of the private docker registry holding dev version of Authelia
192.168.39.26 registry.kube.example.com
```
Once done, you can visit http://home.kube.example.com and follow the
instructions written in the page.
This will take a few seconds (or minutes) to deploy the cluster.
## How does it work?
@ -81,32 +35,26 @@ The authentication is provided at the ingress level by an annotation called
`nginx.ingress.kubernetes.io/auth-url` that is filled with the URL of
Authelia's verification endpoint.
The ingress controller also requires the URL to the
authentication portal so that the user can be redirected in case she is not
yet authenticated.
authentication portal so that the user can be redirected if he is not
yet authenticated. This annotation is as follows:
`nginx.ingress.kubernetes.io/auth-signin: "https://login.example.com:8080/#/"`
Those annotations can be seen in `apps/secure-ingress.yml` configuration.
Those annotations can be seen in `apps/apps.yml` configuration.
### Production grade infrastructure
What is great with using [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
is that it is compatible with [kube-lego](https://github.com/jetstack/kube-lego)
which removes the usual pain of manual SSL certificate renewals. It uses
which removes the usual pain of manually renewing SSL certificates. It uses
letsencrypt to issue and renew certificates every three month without any
manual intervention.
## What do I need know to deploy it in my cluster?
## What do I need to know to deploy it in my cluster?
Given your cluster is already made of an LDAP server, a Redis cluster, a Mongo
cluster and a SMTP server, you'll only need to install the ingress-controller
and Authelia whose kubernetes deployment configurations are respectively in
`ingress-controller` and `authelia` directories. A template configuration
is provided there, you just need to create the configmap to use it within
the cluster.
### I'm already using ingress-nginx
If you're already using ingress-nginx as your ingress controller, you only
need to install Authelia with its configuration and that's it!
Given your cluster already runs a LDAP server, a Redis, a Mongo database,
a SMTP server and a nginx ingress-controller, you can deploy **Authelia**
and update your ingress configurations. An example is provided
[here](./authelia).
## Questions

View File

@ -1,33 +0,0 @@
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: test-app-home
namespace: authelia
labels:
app: test-app-home
spec:
replicas: 1
selector:
matchLabels:
app: test-app-home
template:
metadata:
labels:
app: test-app-home
spec:
containers:
- name: test-app-home
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: app-home-page
mountPath: /usr/share/nginx/html
volumes:
- name: app-home-page
configMap:
name: app-home-page
items:
- key: index.html
path: index.html

View File

@ -1,35 +0,0 @@
<html>
<head>
<title>Authelia Home</title>
</head>
<body>
<h1>Authelia on Kube</h1>
In this example, two applications have been deployed along with Authelia and a fake mailbox in order to confirm your secret registration to Authelia:
<ul>
<li><a href="https://app1.kube.example.com">https://app1.kube.example.com</a></li>
<li><a href="https://app2.kube.example.com">https://app2.kube.example.com</a></li>
<li><a href="http://mail.kube.example.com">http://mail.kube.example.com</a></li>
</ul>
<p>
Please note that <b>app1</b> is publicly available and <b>app2</b> is protected by Authelia.<br/>
<br/>
You can start by visiting <b>app1</b> and then try to access <b>app2</b>. Since <b>app2</b> is protected by Authelia, you will be redirected to Authelia's portal.<br/>
<br/>
If it's the first time you login in this cluster, you'll need to choose your authentication method and follow Authelia's instructions.<br/>
<br/>
Once done, you'll be able to authenticate with your selected second factor method.
</p>
<p>
Here is the list of available users in the LDAP
<ul>
<li><strong>john / password</strong>
<li><strong>bob / password</strong>
<li><strong>harry / password</strong>
</ul>
</p>
<p>
You can always log off by clicking <a href="https://login.kube.example.com/logout?rd=http://home.kube.example.com">here</a>
</p>
</body>
</html>

View File

@ -1,12 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: test-app-home-service
namespace: authelia
spec:
selector:
app: test-app-home
ports:
- protocol: TCP
port: 80

View File

@ -1,33 +0,0 @@
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: test-app1
namespace: authelia
labels:
app: test-app1
spec:
replicas: 1
selector:
matchLabels:
app: test-app1
template:
metadata:
labels:
app: test-app1
spec:
containers:
- name: test-app1
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: app1-page
mountPath: /usr/share/nginx/html
volumes:
- name: app1-page
configMap:
name: app1-page
items:
- key: index.html
path: index.html

View File

@ -1,9 +0,0 @@
<html>
<head>
<title>Application 1</title>
</head>
<body>
<h1>Application 1</h1>
<p><a href="http://home.kube.example.com">Go Home</a></p>
</body>
</html>

View File

@ -1,12 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: test-app1-service
namespace: authelia
spec:
selector:
app: test-app1
ports:
- protocol: TCP
port: 80

View File

@ -1,17 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICvDCCAaQCCQD9zdaObelW0jANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVh
cHAxLmt1YmUuZXhhbXBsZS5jb20wHhcNMTgwMzA0MTUxMDUxWhcNMjgwMzAxMTUx
MDUxWjAgMR4wHAYDVQQDDBVhcHAxLmt1YmUuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+v2s9ABHFgxuYFyGkqlbNQ5OtzHAB79lM
sbAMJ9pnMTBA4FXdHgQuQ6Z4F233hMokVfnX9C2KxuLLOQMXJdoVQGytkbGGCzoz
Hz1qXBjDCwQtUGgLJ6zc3C8QKx90zmY0NmH55ttFCQKPHaaxgrS2YXsPzMlQKrgH
drRklfCpnRZWG9/M1YzXOKeT4VuwTsHyeI8tnco11WJLsZwRxc6TjEgNwwBnct8R
/cDhl5guDhqFPgMuBtzRlTVOeZS8NraHL5GRswoUeFJfS2VA3FTABwRWV3J6d5gl
oXpvMzCB01u2jVKLq75g91lGT6I+AVMgSOJG4Nezum7brS7jJjVFAgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAIFox2Ox/hJqmaSAyE0TqCaLf3UK78z9m/FYUzWoupGE
JAJ83wghVcj7XLKG4x6wN+v342JVa0mQ5X773kqNidHmSdWFPd/hGtx+0dQK3BVV
4CmQCNfA7BZDuxWPW0mcrkKun2aSCq0zjK0f/CzPXZxyPW18EmzSNGkmkXCesM5i
aevdE5PBKikGz4EfzieVTsImdHDfh2/azdRrmSh22x9/tpZy3mMkc5ERpgL2ll+m
HZuZ9FEBQHj92pk2aMBVdxPYpgzWAWbuRUs3vfTIhPlzHcI/ZpE60Ete+yY5lBw/
Gf+ph3HPB8gzSOH/hmcmerX9h6E3MFAncdC4hH3R0j8=
-----END CERTIFICATE-----

View File

@ -1,15 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVYXBwMS5rdWJlLmV4YW1wbGUuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvr9rPQARxYMbmBchpKpWzUOT
rcxwAe/ZTLGwDCfaZzEwQOBV3R4ELkOmeBdt94TKJFX51/QtisbiyzkDFyXaFUBs
rZGxhgs6Mx89alwYwwsELVBoCyes3NwvECsfdM5mNDZh+ebbRQkCjx2msYK0tmF7
D8zJUCq4B3a0ZJXwqZ0WVhvfzNWM1zink+FbsE7B8niPLZ3KNdViS7GcEcXOk4xI
DcMAZ3LfEf3A4ZeYLg4ahT4DLgbc0ZU1TnmUvDa2hy+RkbMKFHhSX0tlQNxUwAcE
VldyeneYJaF6bzMwgdNbto1Si6u+YPdZRk+iPgFTIEjiRuDXs7pu260u4yY1RQID
AQABoAAwDQYJKoZIhvcNAQELBQADggEBALcwQAjNjyhMAbGNpFl6iYhzdhUjz02p
hTpc15T+S4PatbgeERYAIuSGxomPHfh+30udxGDCSy730V2urC0eGPjRpnJpGHNG
1Dau0sgi28TQPOqhBcZm0GNHMocc5iG+AxWAsxNwtSH3wRoeUGYXavcm9/tWvYbi
RIKmzXTQKsmY+qhBo3e/phUFuSU8GARYkfDSVqcTM7C3xswgXYcImrkbHAxvsC7+
3nr8ir2K9t2M3QxV7lTybNacuOF84wL93AZDvJ04HctXXgtt2rSI6vxxt18jyrxJ
yHX9ADrAa+jhPAM664SZs/+l0fBbil2UaMPOKXOCmYGuERpWh1HLHKo=
-----END CERTIFICATE REQUEST-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvr9rPQARxYMbmBchpKpWzUOTrcxwAe/ZTLGwDCfaZzEwQOBV
3R4ELkOmeBdt94TKJFX51/QtisbiyzkDFyXaFUBsrZGxhgs6Mx89alwYwwsELVBo
Cyes3NwvECsfdM5mNDZh+ebbRQkCjx2msYK0tmF7D8zJUCq4B3a0ZJXwqZ0WVhvf
zNWM1zink+FbsE7B8niPLZ3KNdViS7GcEcXOk4xIDcMAZ3LfEf3A4ZeYLg4ahT4D
Lgbc0ZU1TnmUvDa2hy+RkbMKFHhSX0tlQNxUwAcEVldyeneYJaF6bzMwgdNbto1S
i6u+YPdZRk+iPgFTIEjiRuDXs7pu260u4yY1RQIDAQABAoIBAQCwn3ahCUtrZDdM
4T5ZxxCRCJ3aNI8SfBDuHyowV0a4fqd7qz5WfNDKNgIS+T7uDptOgf3SpVr2QasH
GkduS7JgM0NuhJWo1QSTCb5Imfajw7OecfGlQpuh9o/tnMCH3AZvGlwmlkk651jj
REVx4OGMbz8QJkPSY3v8DUKEUQKDSkRhZRhRGDZqbOSeERhOo2O3MqcBaVossYRw
+2Apth2XHg+7mgQjQF+8Z/DeXtLZgOB4VwQ0vkJmSu+FLhWDJqA+WH5JaOrmS3SN
lrPeIEwQMGHmwmIUAaQ7Akkow2xx4VuDRQUcUlitAM21x/MjBdTFbFx975svOiuE
vePNasYBAoGBAOK8e8x0OrqhgsVGWLvJjHnhzTePpemCCB9jRRnTcmMUxXR7moCh
WGgbyQ3+pU15WMxaIpVrGlV3LOwqSEXYspIpBQKv5+kSAaJf/Op3rtuzo57JLMYP
YokMu43ewtSF0CE/7h98vruAYCcDJZs+T7K0lHCBS9pcjhU2MS/ktW2lAoGBANdd
2a1dkEbZyD9NMGD+wWTkctrML3r1ZLy2gq1AX/oyKzdtD6T1nob435RVkyqPGLhr
tEHJuxLiaJlCk5Kd8V84ps0J9SlZTkLzf2ixb06f1YI9ye37UzBr13H59+N6w8Zu
24Gv12ht+WBecWKHgtF16LaCDPYORUMOa3CpgFchAoGBAN6M0Rr6jta3R0tpZBlW
mErd5vd9SQWtO1nLr3zM/f7g2XsfA6T0OXlepHbXFtu3mwBiDIYK7XssEezxB6V/
MK+kEaX0kTZFFVOS0gY2WWyOo7BsmEUDvtz0oXd8SlId0g+A17MSV4hlVnuUbCo3
/DRVaUoQrypzJIcPfTIcVDR9AoGAY3uNrqB2odO9xUfhnhxvtywzxc/l6tVp6CYi
bOc8rnT4M40kWd2/kbdqh7mT1mftUlsmE/GcgZemG41+X46nzYV8v1/nKGeBWDnk
U7cKpHX+iUADg/PBNK/MAHEoSaMOxh21Nc3FIg8Sz6owlAPmsNzXV17xn8NtyRDj
HlKd3yECgYBvB0bV1yXOvfoqGXAyadNJM9eKOuQwhrOJCXzCeMInhUtLpH35kC17
e8rOmoBhV1rLpxlMLV8giyJSRUPoN/TfUHfS2pWfKPobrp50vaUn1hYp6EEr85Qs
DdV2VilJsJN49sMg67GV3Zi4lnjVzYNFegw6y5Xi8l8ulYtzxJnemA==
-----END RSA PRIVATE KEY-----

View File

@ -1,33 +0,0 @@
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: test-app2
namespace: authelia
labels:
app: test-app2
spec:
replicas: 1
selector:
matchLabels:
app: test-app2
template:
metadata:
labels:
app: test-app2
spec:
containers:
- name: test-app2
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: app2-page
mountPath: /usr/share/nginx/html
volumes:
- name: app2-page
configMap:
name: app2-page
items:
- key: index.html
path: index.html

View File

@ -1,9 +0,0 @@
<html>
<head>
<title>Application 2</title>
</head>
<body>
<h1>Application 2</h2>
<p><a href="http://home.kube.example.com">Go Home</a></p>
</body>
</html>

View File

@ -1,12 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: test-app2-service
namespace: authelia
spec:
selector:
app: test-app2
ports:
- protocol: TCP
port: 80

View File

@ -1,17 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICvDCCAaQCCQCE6h+pwV+OTTANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVh
cHAyLmt1YmUuZXhhbXBsZS5jb20wHhcNMTgwMzA0MTUwODUyWhcNMjgwMzAxMTUw
ODUyWjAgMR4wHAYDVQQDDBVhcHAyLmt1YmUuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDuVrXRL0i75y0QpbkFM7h1jIwbKOQYdkCR
tAp4aFjRAnHE1DtiXuexoyhs0hbwtxmFF0RpPjOVLvLl/AZt6i65dC5gl1czLmmH
nzjBzUF4jM5qJioP867vR2+SDnD6YWCQrwYG8bHeFKvppAlTY7g8YoTD/b3JNnV1
t+uaPyNKvu33pOP2vP/w/709WFVEwd/334RFF6fjAyYrOgYgvqcgyJ6E6T4cf/Vl
psgLZpJ7o4VoFHq1MdP6Ro+HzKwHUsDbH53U6wISe3dRBmHjTJH2sp1KuJ5gR1Ko
CiiVfq+CCPOxGKNngQe2EqDHmuVuXu30VFu82hznfkXdlhuzTGSfAgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAEWeTkGmuXnAPU8JGTa+O00kI9nFy10inbiU8O+XuwUL
Cj53CRffbzlsCDRDDxxoBuJ5EvWho5F7MR7A8ZRDfWqLTogvjpVp2YJ+jK/iTbqU
95tCVMByZufa4bHPWmngeYsSu0s0+qRYOeyiCbiFzlzFP3nLSS6aMPwrMUz7/Qp1
XUE1YfyqjKDkvFN4Wq1GKOUZEh4CJh3SuOE/FrRAiaAWnOH4LVDn5TFEbLyEqZLd
BrYEDopRsTpJck/ZEF43GZ0t8Y8CKffpWGV4PvSiUnl/7mKd+i+QsTx8CnnlR9y/
ZvwNRwvRw3uXwkgRinE8wwONAfwB2nIYKyuU9/ODAPs=
-----END CERTIFICATE-----

View File

@ -1,15 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICZTCCAU0CAQAwIDEeMBwGA1UEAwwVYXBwMi5rdWJlLmV4YW1wbGUuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7la10S9Iu+ctEKW5BTO4dYyM
GyjkGHZAkbQKeGhY0QJxxNQ7Yl7nsaMobNIW8LcZhRdEaT4zlS7y5fwGbeouuXQu
YJdXMy5ph584wc1BeIzOaiYqD/Ou70dvkg5w+mFgkK8GBvGx3hSr6aQJU2O4PGKE
w/29yTZ1dbfrmj8jSr7t96Tj9rz/8P+9PVhVRMHf99+ERRen4wMmKzoGIL6nIMie
hOk+HH/1ZabIC2aSe6OFaBR6tTHT+kaPh8ysB1LA2x+d1OsCEnt3UQZh40yR9rKd
SrieYEdSqAoolX6vggjzsRijZ4EHthKgx5rlbl7t9FRbvNoc535F3ZYbs0xknwID
AQABoAAwDQYJKoZIhvcNAQELBQADggEBANqbKTFSeOf9GRgrNuqRGYYdqSPaoXpu
iSKhJRABj4zMOCJlfDpeMQ8mGfmBUV+IHr+X8/nbMt+OMEf4u1+7Mmz4Zfvkt5gP
MBlYbauVxn/uIYp7aZgBUABC7SvLeITRz4rnQW5SvCNyuJAKQh84uF82g47S7Oaz
2dp6NO1nQ/N9SD6y0CyuIXf1KbSk4+lXa3+rGyqpF1aovpXCgvcA3tWrI/Lg2t5E
uPoiHegKGKyWUZeVh8eKY2ZBCl+uRmwLqTTdzj1HcoK5T1slg0X+K9Q1UsGy23Pw
RHFtGuel8msESgTnspzQF3T1uOscOOiQFG3xnoZtxH92gFT+pI7DoEY=
-----END CERTIFICATE REQUEST-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA7la10S9Iu+ctEKW5BTO4dYyMGyjkGHZAkbQKeGhY0QJxxNQ7
Yl7nsaMobNIW8LcZhRdEaT4zlS7y5fwGbeouuXQuYJdXMy5ph584wc1BeIzOaiYq
D/Ou70dvkg5w+mFgkK8GBvGx3hSr6aQJU2O4PGKEw/29yTZ1dbfrmj8jSr7t96Tj
9rz/8P+9PVhVRMHf99+ERRen4wMmKzoGIL6nIMiehOk+HH/1ZabIC2aSe6OFaBR6
tTHT+kaPh8ysB1LA2x+d1OsCEnt3UQZh40yR9rKdSrieYEdSqAoolX6vggjzsRij
Z4EHthKgx5rlbl7t9FRbvNoc535F3ZYbs0xknwIDAQABAoIBAFcXgGDsMlvXYfRP
Woi4GZN6xEe4bYEy1O1pKNpO5wWZKxGNrBWKMIgM4tzA+HkFr2Ge2vTKMfc1rLS1
n3PSuzgxaDELnGWrdAyG9ip7Yo02hsbrIzupBCeTpwVsGYSkyLCWBFHNR/2q+Bbs
RiweqFgIeBNWSV+ZctqNVp6Kq87HuYfm/ka8ewVnPYhEMewuE0vqfVjc+fdPr/5I
4uQCCw0/ZTFFP++hkyNsH1ZCdUeT83xbgUwFA0M/3ckjMhzcP7lncq6Gvy8NpBhI
mle/Ev82yaDRl5VKoenpy2d8L+qvIjRbhZiZuTAU9AWRvOKrTMtzKSpyMgWAQZ64
69ResmECgYEA+2Ws4qBiYd3BKF5tc0fWbS0omThT60oLc6aLu5NoryogHHNwPOud
69nCDHSyYp+PqK1HLrOAVoTLmuwyys15juizewO809gYRgXlNZ9TKED8Znyg004o
EQVYxMevq2kfDOBJkMphLXAvFrRFQVB2Km8EfKkrI++1rhxKGJD+FzMCgYEA8rPU
G1v3/uemBxYO/bEmdjvsDfLI7FBYNmjgvBGJocSwb2sG/tRr0imZBPAuDlCKcZ1E
G3rZOJ0VsLzxX5RCVYFKRQvUtbaNO+uDJjChxQ/fQQdbIoaIMjyAVLI7t3Ei4+nY
7eHYjcHeMX54drO8YqQ6OoWZ4x8DI8KNcxBnzOUCgYA+uMRkmn1RS4For/6At5ih
DpZFfA878fJffVr5hrKkmU7/qjGDkYmKEX9fmjHzdznhbLIIzdIkQ+eElI+rl45P
gHFfLLSM6ipMNiZUtZaKwYP3kfqSHbrTXFEkb2m9y3Fqxf60uDl8m7Oz53Ar9oY0
2hP1gkN4KNNcSESYUnyCjwKBgAR9+ZYMDLoGFZeZ++sMJVcY4tSbQsbE8e0H4ej5
Nh/tYQqe44FB80Dvjip+O4v+R6G0tHcBvhWDKsyboqgPOW8Vtocyodw/JbwPLt09
FzFrislMVo58CPdNEV7/8YUCrg+j22UDwhtVlEQ8QASKbRkySvWcVW3TvB4kUrPn
gNRVAoGAI99QzqSRBSLeDgxxRgTM7l5wzyVEmJfmMqQsIbHvYy6zG9iYjFI6UZ8I
U3C/Sdnq7wCWa70b3L8A1iN2fYVwvkEH5WGbEp5B2Cye50avegbPi1FgGw2VSIuS
ysAkWaVJXsb2oQNtjidRuEflhy4nr7eybwa6cgarh7ci3JB+tQ8=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,130 @@
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: test-app
namespace: authelia
labels:
k8s-app: test-app
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: test-app
spec:
containers:
- name: test-app
imagePullPolicy: Never
image: authelia-example-backend
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: test-app-service
namespace: authelia
labels:
k8s-app: test-app
spec:
selector:
k8s-app: test-app
ports:
- port: 80
name: http
- port: 443
name: https
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: insecure-ingress
namespace: authelia
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/ingress.allow-http: "false"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
tls:
- secretName: test-app-tls
hosts:
- home.example.com
rules:
- host: home.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: secure-ingress
namespace: authelia
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/ingress.allow-http: "false"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/auth-url: "http://authelia-service.authelia.svc.cluster.local/api/verify"
nginx.ingress.kubernetes.io/auth-signin: "https://login.example.com:8080/#/"
spec:
tls:
- secretName: test-app-tls
hosts:
- public.example.com
- admin.example.com
- dev.example.com
- mx1.mail.example.com
- mx2.mail.example.com
- singlefactor.example.com
rules:
- host: public.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80
- host: admin.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80
- host: dev.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80
- host: mx1.mail.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80
- host: mx2.mail.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80
- host: singlefactor.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-service
servicePort: 80

View File

@ -1,28 +0,0 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: insecure-ingress
namespace: authelia
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- secretName: app1-tls
hosts:
- app1.kube.example.com
rules:
- host: app1.kube.example.com
http:
paths:
- path: /
backend:
serviceName: test-app1-service
servicePort: 80
- host: home.kube.example.com
http:
paths:
- path: /
backend:
serviceName: test-app-home-service
servicePort: 80

View File

@ -1,23 +0,0 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: secure-ingress
namespace: authelia
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/auth-url: "http://authelia-service.authelia.svc.cluster.local/api/verify"
nginx.ingress.kubernetes.io/auth-signin: "https://login.kube.example.com"
spec:
tls:
- secretName: app2-tls
hosts:
- app2.kube.example.com
rules:
- host: app2.kube.example.com
http:
paths:
- path: /
backend:
serviceName: test-app2-service
servicePort: 80

View File

@ -20,7 +20,7 @@ logs_level: debug
#
# Note: this parameter is optional. If not provided, user won't
# be redirected upon successful authentication.
default_redirection_url: https://login.kube.example.com
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
@ -79,76 +79,60 @@ authentication_backend:
## file:
## path: ./users_database.yml
# Authentication methods
#
# Authentication methods can be defined per subdomain.
# There are currently two available methods: "single_factor" 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.
#
# Note: authentication_methods is optional. If it is not set all sub-domains
# are protected by two factors.
authentication_methods:
default_method: two_factor
# per_subdomain_methods:
# single_factor.example.com: single_factor
# Access Control
#
# Access control is a set of rules you can use to restrict user access to certain
# resources.
# Any (apply to anyone), per-user or per-group rules can be defined.
#
# If 'access_control' is not defined, ACL rules are disabled and the `allow` default
# policy 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 the pattern in simple quotes when using the wildcard for the YAML
# to be syntaxically correct.
#
# Definition: A `rule` is an object with the following keys: `domain`, `policy`
# and `resources`.
# - `domain` defines which domain or set of domains the rule applies to.
# - `policy` is the policy to apply to resources. It must be either `allow` or `deny`.
# - `resources` is a list of regular expressions that matches a set of resources to
# apply the policy to.
#
# Note: Rules follow an order of priority defined as follows:
# In each category (`any`, `groups`, `users`), the latest rules have the highest
# priority. In other words, it means that if a given resource matches two rules in the
# same category, the latest one overrides the first one.
# Each category has also its own priority. That is, `users` has the highest priority, then
# `groups` and `any` has the lowest priority. It means if two rules in different categories
# match a given resource, the one in the category with the highest priority overrides the
# other one.
#
# For more details about the configuration see config.template.yml at the root of the repo.
access_control:
# Default policy can either be `allow` or `deny`.
# It is the policy applied to any resource if it has not been overriden
# in the `any`, `groups` or `users` category.
default_policy: deny
# The rules that apply to anyone.
# The value is a list of rules.
any:
rules:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: two_factor
- domain: singlefactor.example.com
policy: one_factor
# Rules applied to 'admin' group
- domain: 'mx2.mail.example.com'
subject: 'group:admin'
policy: deny
- domain: '*.example.com'
policy: allow
subject: 'group:admin'
policy: two_factor
# Group-based rules. The key is a group name and the value
# is a list of rules.
groups: {}
# Rules applied to 'dev' group
- domain: dev.example.com
resources:
- '^/groups/dev/.*$'
subject: 'group:dev'
policy: two_factor
# User-based rules. The key is a user name and the value
# is a list of rules.
users: {}
# Rules applied to user 'john'
- domain: dev.example.com
resources:
- '^/users/john/.*$'
subject: 'user:john'
policy: two_factor
# Rules applied to user 'harry'
- domain: dev.example.com
resources:
- '^/users/harry/.*$'
subject: 'user:harry'
policy: two_factor
# Rules applied to user 'bob'
- domain: '*.mail.example.com'
subject: 'user:bob'
policy: two_factor
- domain: 'dev.example.com'
resources:
- '^/users/bob/.*$'
subject: 'user:bob'
policy: two_factor
# Configuration of session cookies

View File

@ -18,8 +18,7 @@ spec:
spec:
containers:
- name: authelia
image: localhost:5000/authelia:latest
imagePullPolicy: Always
image: authelia:dist
ports:
- containerPort: 80
volumeMounts:

View File

@ -10,10 +10,10 @@ spec:
tls:
- secretName: authelia-tls
hosts:
- login.kube.example.com
- login.example.com
rules:
rules:
- host: login.kube.example.com
- host: login.example.com
http:
paths:
- path: /

View File

@ -0,0 +1,8 @@
#!/bin/bash
start_authelia() {
kubectl create configmap authelia-config --namespace=authelia --from-file=authelia/configs/config.yml
kubectl apply -f authelia
}
start_authelia

30
example/kube/bootstrap.sh 100644 → 100755
View File

@ -1,30 +1,19 @@
#!/bin/bash
start_apps() {
# Create the test application pages
kubectl create configmap app1-page --namespace=authelia --from-file=apps/app1/index.html
kubectl create configmap app2-page --namespace=authelia --from-file=apps/app2/index.html
kubectl create configmap app-home-page --namespace=authelia --from-file=apps/app-home/index.html
# Create TLS certificate and key for HTTPS termination
kubectl create secret generic app1-tls --namespace=authelia --from-file=apps/app1/ssl/tls.key --from-file=apps/app1/ssl/tls.crt
kubectl create secret generic app2-tls --namespace=authelia --from-file=apps/app2/ssl/tls.key --from-file=apps/app2/ssl/tls.crt
kubectl create secret generic authelia-tls --namespace=authelia --from-file=authelia/ssl/tls.key --from-file=authelia/ssl/tls.crt
kubectl create secret generic test-app-tls --namespace=authelia --from-file=apps/ssl/tls.key --from-file=apps/ssl/tls.crt
# Spawn the applications
kubectl apply -f apps
kubectl apply -f apps/app1
kubectl apply -f apps/app2
kubectl apply -f apps/app-home
}
start_ingress_controller() {
kubectl apply -f ingress-controller
}
start_authelia() {
kubectl create configmap authelia-config --namespace=authelia --from-file=authelia/configs/config.yml
kubectl apply -f authelia
start_dashboard() {
kubectl apply -f dashboard
}
# Spawn Redis and Mongo as backend for Authelia
@ -34,28 +23,23 @@ start_storage() {
}
# Create a fake mailbox to catch emails sent by Authelia
start_mailcatcher() {
kubectl apply -f mailcatcher
start_mail() {
kubectl apply -f mail
}
start_ldap() {
kubectl apply -f ldap
}
start_docker_registry() {
kubectl apply -f docker-registry
}
# Create the Authelia namespace in the cluster
create_namespace() {
kubectl apply -f namespace.yml
}
create_namespace
start_docker_registry
start_dashboard
start_storage
start_ldap
start_mailcatcher
start_mail
start_ingress_controller
start_authelia
start_apps

View File

@ -1,9 +0,0 @@
#!/bin/bash
build_and_push_authelia() {
cd ../../
docker build -t registry.kube.example.com:80/authelia .
docker push registry.kube.example.com:80/authelia
}
build_and_push_authelia

View File

@ -0,0 +1,179 @@
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------- Dashboard Secret ------------------- #
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-certs
namespace: kube-system
type: Opaque
---
# ------------------- Dashboard Service Account ------------------- #
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
---
# ------------------- Dashboard Role & Role Binding ------------------- #
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kubernetes-dashboard-minimal
namespace: kube-system
rules:
# Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create"]
# Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["create"]
# Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
verbs: ["get", "update", "delete"]
# Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["kubernetes-dashboard-settings"]
verbs: ["get", "update"]
# Allow Dashboard to get metrics from heapster.
- apiGroups: [""]
resources: ["services"]
resourceNames: ["heapster"]
verbs: ["proxy"]
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubernetes-dashboard-minimal
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard-minimal
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
labels:
k8s-app: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kube-system
---
# ------------------- Dashboard Deployment ------------------- #
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --enable-skip-login
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
# ------------------- Dashboard Service ------------------- #
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
ports:
- port: 443
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard

View File

@ -1,35 +0,0 @@
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-registry-proxy
namespace: kube-system
labels:
k8s-app: kube-registry-proxy
kubernetes.io/cluster-service: "true"
version: v0.4
spec:
template:
metadata:
labels:
k8s-app: kube-registry-proxy
kubernetes.io/name: "kube-registry-proxy"
kubernetes.io/cluster-service: "true"
version: v0.4
spec:
containers:
- name: kube-registry-proxy
image: gcr.io/google_containers/kube-registry-proxy:0.4
resources:
limits:
cpu: 100m
memory: 50Mi
env:
- name: REGISTRY_HOST
value: kube-registry.kube-system.svc.cluster.local
- name: REGISTRY_PORT
value: "5000"
ports:
- name: registry
containerPort: 80
hostPort: 5000

View File

@ -1,18 +0,0 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: registry-ingress
namespace: kube-system
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/proxy-body-size: 100m # Avoid 413 Request entity too large
spec:
rules:
- host: registry.kube.example.com
http:
paths:
- path: /
backend:
serviceName: kube-registry
servicePort: 5000

View File

@ -1,44 +0,0 @@
---
apiVersion: v1
kind: ReplicationController
metadata:
name: kube-registry-v0
namespace: kube-system
labels:
k8s-app: kube-registry-upstream
version: v0
kubernetes.io/cluster-service: "true"
spec:
replicas: 1
selector:
k8s-app: kube-registry-upstream
version: v0
template:
metadata:
labels:
k8s-app: kube-registry-upstream
version: v0
kubernetes.io/cluster-service: "true"
spec:
containers:
- name: registry
image: registry:2
resources:
limits:
cpu: 100m
memory: 100Mi
env:
- name: REGISTRY_HTTP_ADDR
value: :5000
- name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
value: /var/lib/registry
volumeMounts:
- name: image-store
mountPath: /var/lib/registry
ports:
- containerPort: 5000
name: registry
protocol: TCP
volumes:
- name: image-store
emptyDir: {}

View File

@ -1,17 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: kube-registry
namespace: kube-system
labels:
k8s-app: kube-registry-upstream
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "KubeRegistry"
spec:
selector:
k8s-app: kube-registry-upstream
ports:
- name: registry
port: 5000
protocol: TCP

View File

@ -1,48 +0,0 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
app: default-http-backend
namespace: authelia
spec:
replicas: 1
template:
metadata:
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
image: gcr.io/google_containers/defaultbackend:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: authelia
labels:
app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: default-http-backend

View File

@ -2,26 +2,27 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller-external
name: nginx-ingress-controller
namespace: authelia
labels:
k8s-app: nginx-ingress-controller-external
k8s-app: nginx-ingress-controller
spec:
replicas: 1
revisionHistoryLimit: 0
template:
metadata:
labels:
k8s-app: nginx-ingress-controller-external
name: nginx-ingress-controller-external
k8s-app: nginx-ingress-controller
name: nginx-ingress-controller
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
terminationGracePeriodSeconds: 60
serviceAccountName: nginx-ingress-controller-serviceaccount
containers:
- image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.13.0
name: nginx-ingress-controller-external
- image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.23.0
name: nginx-ingress-controller
imagePullPolicy: Always
ports:
- containerPort: 80
@ -38,5 +39,4 @@ spec:
args:
- /nginx-ingress-controller
- --ingress-class=nginx
- --election-id=ingress-controller-leader-external
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --election-id=ingress-controller-leader

View File

@ -0,0 +1,141 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-controller-serviceaccount
namespace: authelia
labels:
k8s-app: nginx-ingress-controller
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-controller-clusterrole
labels:
k8s-app: nginx-ingress-controller
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-controller-role
namespace: authelia
labels:
k8s-app: nginx-ingress-controller
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-controller-role-nisa-binding
namespace: authelia
labels:
k8s-app: nginx-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-controller-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-controller-serviceaccount
namespace: authelia
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-controller-clusterrole-nisa-binding
labels:
k8s-app: nginx-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-controller-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-controller-serviceaccount
namespace: authelia
---

View File

@ -2,17 +2,16 @@
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress-controller-external-service
name: nginx-ingress-controller-service
namespace: authelia
labels:
k8s-app: nginx-ingress-controller-external
k8s-app: nginx-ingress-controller
spec:
selector:
k8s-app: nginx-ingress-controller-external
k8s-app: nginx-ingress-controller
type: NodePort
ports:
- port: 80
name: http
- port: 443
name: https
externalIPs:
- 192.168.39.26

View File

@ -7,8 +7,12 @@ metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- secretName: mail-tls
hosts:
- mail.example.com
rules:
- host: mail.kube.example.com
- host: mail.example.com
http:
paths:
- path: /

View File

@ -0,0 +1,22 @@
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: test-app1
namespace: kube-public
labels:
app: test-app1
spec:
replicas: 1
selector:
matchLabels:
app: test-app1
template:
metadata:
labels:
app: test-app1
spec:
containers:
- name: test-app1
image: clems4ever/authelia:kube
imagePullPolicy: Never

47
package-lock.json generated
View File

@ -260,14 +260,14 @@
}
},
"@types/connect-redis": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.7.tgz",
"integrity": "sha512-ui1DPnJxqgBhqPj0XTVyPkzffEX9DIGkb2nT2mzZ0OlsKn/u9BvRvKmjpi4Vydf2uw3z/D4UmMH4KMkilySqvw==",
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.9.tgz",
"integrity": "sha512-LdAbC9EnQkHLMgeV0EKufPbqaRKfsAxgdWaYnDtQOKuJV2U4JpFE2EU4yKJ3G2FjVvNXyzO6rcaBBjPBztGTzg==",
"dev": true,
"requires": {
"@types/express": "4.11.1",
"@types/express-session": "1.15.8",
"@types/redis": "2.8.6"
"@types/redis": "2.8.11"
}
},
"@types/ejs": {
@ -495,12 +495,11 @@
"dev": true
},
"@types/redis": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.6.tgz",
"integrity": "sha512-kaSI4XQwCfJtPiuyCXvLxCaw2N0fMZesdob3Jh01W20vNFct+3lfvJ/4yCJxbSopXOBOzpg+pGxkW6uWZrPZHA==",
"version": "2.8.11",
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.11.tgz",
"integrity": "sha512-i0AqDzjX0FlO+npqkhl1etZw1fGnJc0IeHUHKAf/V7Drk+rDJI634GE9Lwh8aj/2ahuW6jHdWX4ZnyNofWXKrw==",
"dev": true,
"requires": {
"@types/events": "1.2.0",
"@types/node": "10.0.3"
}
},
@ -569,7 +568,8 @@
"@types/url-parse": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.2.tgz",
"integrity": "sha512-Z25Ef2iI0lkiBAoe8qATRHQWy+BWFWuWasD6HB5prsWx2QjLmB/ngXv8v9dePw2jDwdSvET4/6J5HxmeqhslmQ=="
"integrity": "sha512-Z25Ef2iI0lkiBAoe8qATRHQWy+BWFWuWasD6HB5prsWx2QjLmB/ngXv8v9dePw2jDwdSvET4/6J5HxmeqhslmQ==",
"dev": true
},
"@types/winston": {
"version": "2.3.9",
@ -1745,21 +1745,26 @@
}
},
"connect-redis": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-3.3.3.tgz",
"integrity": "sha512-rpWsW2uk1uOe/ccY/JvW+RiLrhZm7auIx8z4yR+KXemFTIhJyD58jXiJbI0E/fZCnybawpdSqOZ+6/ah6aBeyg==",
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-3.4.1.tgz",
"integrity": "sha512-oXNcpLg/PJ6G4gbhyGwrQK9mUQTKYa2aEnOH9kWIxbNUjIFPqUmzz75RdLp5JTPSjrBVcz+9ll4sSxfvlW0ZLA==",
"requires": {
"debug": "3.1.0",
"debug": "4.1.1",
"redis": "2.8.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "2.0.0"
"ms": "2.1.1"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
@ -7489,14 +7494,14 @@
"integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
"requires": {
"double-ended-queue": "2.1.0-0",
"redis-commands": "1.3.5",
"redis-commands": "1.4.0",
"redis-parser": "2.6.0"
}
},
"redis-commands": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz",
"integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA=="
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz",
"integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw=="
},
"redis-parser": {
"version": "2.6.0",

View File

@ -31,11 +31,10 @@
"title": "Authelia API documentation"
},
"dependencies": {
"@types/url-parse": "^1.4.2",
"ajv": "^6.3.0",
"bluebird": "^3.5.0",
"body-parser": "^1.15.2",
"connect-redis": "^3.3.0",
"connect-redis": "^3.4.1",
"crypt3": "^1.0.0",
"ejs": "^2.5.5",
"express": "^4.14.0",
@ -51,7 +50,6 @@
"object-path": "^0.11.3",
"randomatic": "^3.1.0",
"randomstring": "^1.1.5",
"redis": "^2.8.0",
"speakeasy": "^2.0.0",
"u2f": "^0.1.2",
"u2f-api": "^1.0.7",
@ -64,7 +62,7 @@
"@types/body-parser": "^1.16.3",
"@types/chokidar": "^1.7.5",
"@types/commander": "^2.12.2",
"@types/connect-redis": "0.0.7",
"@types/connect-redis": "0.0.9",
"@types/ejs": "^2.3.33",
"@types/express": "^4.0.35",
"@types/express-session": "1.15.8",
@ -81,13 +79,13 @@
"@types/object-path": "^0.9.28",
"@types/query-string": "^5.1.0",
"@types/randomstring": "^1.1.5",
"@types/redis": "^2.6.0",
"@types/request": "^2.0.5",
"@types/request-promise": "^4.1.38",
"@types/selenium-webdriver": "^3.0.4",
"@types/sinon": "^4.3.0",
"@types/speakeasy": "^2.0.2",
"@types/tmp": "0.0.33",
"@types/url-parse": "^1.4.2",
"@types/winston": "^2.3.2",
"@types/yamljs": "^0.2.30",
"apidoc": "^0.17.6",

View File

@ -5,6 +5,7 @@ var program = require('commander');
program
.version('0.0.1')
.command('bootstrap', 'Prepare some containers for development.')
.command('suites', 'Run Authelia in specific environments.')
.command('serve', 'Run Authelia with a customized configuration.')
.command('build', 'Build production version of Authelia from source.')

View File

@ -0,0 +1,24 @@
#!/usr/bin/env node
var { exec } = require('./utils/exec');
var fs = require('fs');
async function main() {
console.log('Build authelia-example-backend docker image.')
await exec('docker build -t authelia-example-backend example/compose/nginx/backend');
if (!fs.existsSync('/tmp/kind')) {
console.log('Install Kind for spawning a Kubernetes cluster.');
await exec('wget https://github.com/clems4ever/kind/releases/download/0.1.0-cmic1/kind-linux-amd64 -O /tmp/kind && chmod +x /tmp/kind');
}
if (!fs.existsSync('/tmp/kubectl')) {
console.log('Install Kubectl for interacting with Kubernetes.');
await exec('wget https://storage.googleapis.com/kubernetes-release/release/v1.13.0/bin/linux/amd64/kubectl -O /tmp/kubectl && chmod +x /tmp/kubectl');
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
})

View File

@ -1,6 +1,14 @@
#!/usr/bin/env node
var { exec } = require('./utils/exec');
var { IMAGE_NAME } = require('./utils/docker');
var { IMAGE_NAME, DOCKERHUB_IMAGE_NAME } = require('./utils/docker');
exec(`docker build -t ${IMAGE_NAME} .`);
async function main() {
await exec(`docker build -t ${IMAGE_NAME}:dist .`);
await exec(`docker tag ${IMAGE_NAME}:dist ${DOCKERHUB_IMAGE_NAME}`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
})

View File

@ -15,8 +15,9 @@ function login_to_dockerhub {
function deploy_on_dockerhub {
TAG=$1
IMAGE_NAME=clems4ever/authelia
IMAGE_WITH_TAG=$IMAGE_NAME:$TAG
IMAGE_NAME=authelia:dist
DOCKERHUB_IMAGE_NAME=clems4ever/authelia
IMAGE_WITH_TAG=$DOCKERHUB_IMAGE_NAME:$TAG
echo "==========================================================="
echo "Docker image $IMAGE_WITH_TAG will be deployed on Dockerhub."
@ -27,12 +28,10 @@ function deploy_on_dockerhub {
}
if [ "$TRAVIS_BRANCH" == "master" ]; then
if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
echo "Building on master branch"
login_to_dockerhub
deploy_on_dockerhub master
elif [ "$TRAVIS_BRANCH" == "develop" ]; then
login_to_dockerhub
deploy_on_dockerhub develop
elif [ ! -z "$TRAVIS_TAG" ]; then
login_to_dockerhub
deploy_on_dockerhub $TRAVIS_TAG

View File

@ -2,7 +2,6 @@
var program = require('commander');
var spawn = require('child_process').spawn;
var fs = require('fs');
let config;

View File

@ -53,7 +53,8 @@ async function main() {
});
}
main().catch((err) => {
console.error(err);
process.exit(1)
});
main()
.catch((err) => {
console.error(err);
process.exit(1)
});

View File

@ -3,6 +3,7 @@
var program = require('commander');
var spawn = require('child_process').spawn;
var fs = require('fs');
var ListSuites = require('./utils/ListSuites');
let suite;
@ -10,52 +11,108 @@ program
.description('Run the tests for the current suite or the provided one.')
.arguments('[suite]')
.option('--headless', 'Run in headless mode.')
.option('--forbid-only', 'Forbid only and pending filters.')
.action((suiteArg) => suite = suiteArg)
.parse(process.argv);
let args = [];
async function runTests(suite, withEnv) {
return new Promise((resolve, reject) => {
let mochaArgs = ['--exit', '--colors', '--require', 'ts-node/register', 'test/suites/' + suite + '/test.ts']
if (program.forbidOnly) {
mochaArgs = ['--forbid-only', '--forbid-pending'] + mochaArgs;
}
const mochaCommand = './node_modules/.bin/mocha ' + mochaArgs.join(' ');
let testProcess;
if (!withEnv) {
testProcess = spawn(mochaCommand, {
shell: true,
env: {
...process.env,
TS_NODE_PROJECT: 'test/tsconfig.json',
HEADLESS: (program.headless) ? 'y' : 'n'
}
});
} else {
testProcess = spawn('./node_modules/.bin/ts-node',
['-P', 'test/tsconfig.json', '--', './scripts/run-environment.ts', suite, mochaCommand], {
env: {
...process.env,
TS_NODE_PROJECT: 'test/tsconfig.json',
HEADLESS: (program.headless) ? 'y' : 'n'
}
});
}
testProcess.stdout.pipe(process.stdout);
testProcess.stderr.pipe(process.stderr);
testProcess.on('exit', function(statusCode) {
if (statusCode == 0) resolve();
reject(new Error('Tests exited with status ' + statusCode));
});
});
}
async function runAllTests() {
const suites = ListSuites();
let failure = false;
for(var s in suites) {
try {
console.log('Running suite %s', suites[s]);
await runTests(suites[s], true);
} catch (e) {
console.error(e);
failure = true;
}
}
if (failure) {
throw new Error('Some tests failed.');
}
}
const ENVIRONMENT_FILENAME = '.suite'; // This file is created by the start command.
const suiteFileExists = fs.existsSync(ENVIRONMENT_FILENAME);
if (suite) {
if (fs.existsSync(ENVIRONMENT_FILENAME) && suite != fs.readFileSync(ENVIRONMENT_FILENAME)) {
if (suiteFileExists && suite != fs.readFileSync(ENVIRONMENT_FILENAME)) {
console.error('You cannot test a suite while another one is running. If you want to test the current suite, ' +
'do not provide the suite argument. Otherwise, stop the current suite and run the command again.');
process.exit(1);
}
console.log('Suite %s provided. Running test related to this suite against built version of Authelia.', suite);
args.push('test/suites/' + suite + '/test.ts');
runTests(suite, !suiteFileExists)
.catch((err) => {
console.error(err);
process.exit(1);
});
}
else if (fs.existsSync(ENVIRONMENT_FILENAME)) {
else if (suiteFileExists) {
suite = fs.readFileSync(ENVIRONMENT_FILENAME);
console.log('Suite %s detected from dev env. Running test related to this suite.', suite);
args.push('test/suites/' + suite + '/test.ts');
runTests(suite, false)
.catch((err) => {
console.error(err);
process.exit(1);
});
}
else {
console.log('No suite provided therefore all suites will be tested.');
args.push('test/suites/**/test.ts');
runAllTests(suite)
.catch((err) => {
console.error(err);
process.exit(1);
});
}
mocha = spawn('./node_modules/.bin/mocha', ['--forbid-only', '--forbid-pending', '--exit', '--colors', '--require', 'ts-node/register', ...args], {
env: {
...process.env,
TS_NODE_PROJECT: 'test/tsconfig.json',
HEADLESS: (program.headless) ? 'y' : 'n',
}
// Sometime SIGINT is received twice, we make sure with this variable that we cleanup
// only once.
var alreadyCleaningUp = false;
// Properly cleanup server and client if ctrl-c is hit
process.on('SIGINT', function() {
if (alreadyCleaningUp) return;
alreadyCleaningUp = true;
console.log('Cleanup procedure is running...');
});
mocha.stdout.pipe(process.stdout);
mocha.stderr.pipe(process.stderr);
mocha.on('exit', function(statusCode) {
if (statusCode != 0) {
console.error("The tests failed... Mocha exited with status code " + statusCode);
fs.readFile('/tmp/authelia-server.log', function(err, data) {
if (err) {
console.error(err);
return;
}
console.log(data.toString('utf-8'));
});
}
process.exit(statusCode);
})

View File

@ -2,17 +2,18 @@
set -e
export PATH=./scripts:$PATH
source bootstrap.sh
docker --version
docker-compose --version
echo "node `node -v`"
echo "npm `npm -v`"
authelia-scripts bootstrap
docker-compose -f docker-compose.yml \
-f example/compose/mongo/docker-compose.yml \
-f example/compose/redis/docker-compose.yml \
-f example/compose/nginx/backend/docker-compose.yml \
-f example/compose/nginx/portal/docker-compose.yml \
-f example/compose/smtp/docker-compose.yml \
-f example/compose/httpbin/docker-compose.yml \

View File

@ -1,7 +1,9 @@
import { exec } from './utils/exec';
const userSuite = process.argv[2];
const command = process.argv[3]; // The command to run once the env is up.
var { setup, teardown } = require(`../test/suites/${userSuite}/environment`);
var { setup, setup_timeout, teardown, teardown_timeout } = require(`../test/suites/${userSuite}/environment`);
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
@ -9,32 +11,64 @@ function sleep(ms: number) {
let teardownInProgress = false;
async function block() {
while (true) {
await sleep(10000);
}
}
async function blockOrRun(cmd: string | null) {
if (cmd) {
await exec(cmd);
} else {
await block();
}
}
process.on('SIGINT', function() {
if (teardownInProgress) return;
teardownInProgress = true;
console.log('Tearing down environment...');
return teardown()
.then(() => {
process.exit(0)
})
.catch((err: Error) => {
console.error(err);
process.exit(1);
});
stop()
.then(() => process.exit(0))
.catch(() => process.exit(1));
});
function main() {
console.log('Setting up environment...');
return setup()
.then(async () => {
while (true) {
await sleep(10000);
}
})
.catch((err: Error) => {
console.error(err);
process.exit(1);
});
async function stop() {
const timer = setTimeout(() => {
console.error('Teardown timed out...');
process.exit(1);
}, teardown_timeout);
console.log('>>> Tearing down environment <<<');
try {
await teardown();
clearTimeout(timer);
} catch (err) {
console.error(err);
throw err;
}
}
main();
async function start() {
const timer = setTimeout(() => {
console.error('Setup timed out...');
teardown().finally(() => process.exit(1));
}, setup_timeout);
console.log('>>> Setting up environment <<<');
try {
await setup();
clearTimeout(timer);
await blockOrRun(command);
if (!teardownInProgress) {
await stop();
process.exit(0);
}
}
catch (err) {
console.error(err);
await stop();
process.exit(1);
}
}
start();

View File

@ -1,5 +1,6 @@
module.exports = {
IMAGE_NAME: 'clems4ever/authelia'
IMAGE_NAME: 'authelia',
DOCKERHUB_IMAGE_NAME: 'clems4ever/authelia'
}

View File

@ -2,15 +2,12 @@ var spawn = require('child_process').spawn;
function exec(cmd) {
return new Promise((resolve, reject) => {
const command = spawn(cmd, {
shell: true
});
const command = spawn(cmd, {shell: true, env: process.env});
command.stdout.pipe(process.stdout);
command.stderr.pipe(process.stderr);
command.on('exit', function(statusCode) {
if (statusCode != 0) {
reject(new Error('Exited with status ' + statusCode));
reject(new Error('Command \'' + cmd + '\' has exited with status ' + statusCode + '.'));
return;
}
resolve();

View File

@ -112,7 +112,7 @@ export function post_start_validation(handler: IdentityValidable,
})
.then((token: string) => {
const host = req.get("Host");
const link_url = util.format("https://%s%s?token=%s", host,
const link_url = util.format("https://%s/#%s?token=%s", host,
handler.destinationPath(), token);
vars.logger.info(req, "Notification sent to user \"%s\"",
identity.userid);

View File

@ -49,8 +49,7 @@ export default class Server {
return ServerVariablesInitializer.initialize(
config, this.globalLogger, this.requestLogger, deps)
.then(function (vars: ServerVariables) {
Configurator.configure(config, app, vars, deps);
return BluebirdPromise.resolve();
return Configurator.configure(config, app, vars, deps);
});
}

View File

@ -45,11 +45,9 @@ function MatchSubject(subject: Subject) {
}
export class Authorizer implements IAuthorizer {
private logger: Winston;
private readonly configuration: ACLConfiguration;
constructor(configuration: ACLConfiguration, logger_: Winston) {
this.logger = logger_;
this.configuration = configuration;
}

View File

@ -3,7 +3,6 @@ import { Configuration } from "./schema/Configuration";
import { GlobalDependencies } from "../../../types/Dependencies";
import ExpressSession = require("express-session");
import ConnectRedis = require("connect-redis");
import Sinon = require("sinon");
import Assert = require("assert");
@ -128,22 +127,15 @@ describe("configuration/SessionConfigurationBuilder", function () {
password: "authelia_pass"
};
const RedisStoreMock = Sinon.spy();
const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
const createClientStub = Sinon.stub();
deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock);
deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock) as any;
deps.Redis = {
createClient: createClientStub
} as any;
SessionConfigurationBuilder.build(configuration, deps);
createClientStub.returns(redisClient);
const options = SessionConfigurationBuilder.build(configuration, deps);
Assert(createClientStub.calledWith({
Assert(RedisStoreMock.calledWith({
host: "redis.example.com",
port: 6379,
password: "authelia_pass"
pass: "authelia_pass",
logErrors: true,
}));
});
});

View File

@ -1,12 +1,8 @@
import ExpressSession = require("express-session");
import Redis = require("redis");
import { Configuration } from "./schema/Configuration";
import { GlobalDependencies } from "../../../types/Dependencies";
import { RedisStoreOptions } from "connect-redis";
export class SessionConfigurationBuilder {
static build(configuration: Configuration, deps: GlobalDependencies): ExpressSession.SessionOptions {
const sessionOptions: ExpressSession.SessionOptions = {
name: configuration.session.name,
@ -22,30 +18,13 @@ export class SessionConfigurationBuilder {
};
if (configuration.session.redis) {
let redisOptions;
const options: Redis.ClientOpts = {
const RedisStore = deps.ConnectRedis(deps.session);
sessionOptions.store = new RedisStore({
host: configuration.session.redis.host,
port: configuration.session.redis.port
};
if (configuration.session.redis.password) {
options["password"] = configuration.session.redis.password;
}
const client = deps.Redis.createClient(options);
client.on("error", function (err: Error) {
console.error("Redis error:", err);
});
redisOptions = {
client: client,
port: configuration.session.redis.port,
pass: configuration.session.redis.password,
logErrors: true
};
if (redisOptions) {
const RedisStore = deps.ConnectRedis(deps.session);
sessionOptions.store = new RedisStore(redisOptions);
}
});
}
return sessionOptions;
}

View File

@ -1,5 +1,3 @@
import Sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import Assert = require("assert");
import FirstFactorPost = require("./post");

View File

@ -14,6 +14,8 @@ import { URLDecomposer } from "../..//utils/URLDecomposer";
import { Object } from "../../../lib/authorization/Object";
import { Subject } from "../../../lib/authorization/Subject";
import AuthenticationError from "../../../lib/authentication/AuthenticationError";
import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe";
import * as URLParse from "url-parse";
export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response)
@ -61,7 +63,7 @@ export default function (vars: ServerVariables) {
vars.regulator.mark(username, true);
})
.then(function() {
const targetUrl = ObjectPath.get(req, "headers.x-target-url", undefined);
const targetUrl = ObjectPath.get<Express.Request, string>(req, "headers.x-target-url", undefined);
if (!targetUrl) {
res.status(204);
@ -83,10 +85,13 @@ export default function (vars: ServerVariables) {
const authorizationLevel = vars.authorizer.authorization(resObject, subject);
if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
res.json({
redirect: targetUrl
});
return BluebirdPromise.resolve();
if (IsRedirectionSafe(vars, authSession, new URLParse(targetUrl))) {
res.json({redirect: targetUrl});
return BluebirdPromise.resolve();
} else {
res.json({error: "You're authenticated but cannot be automatically redirected to an unsafe URL."});
return BluebirdPromise.resolve();
}
}
}

View File

@ -1,31 +0,0 @@
import * as Express from "express";
import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { Level } from "../../authentication/Level";
import * as URLParse from "url-parse";
export default function (vars: ServerVariables) {
return function (req: Express.Request, res: Express.Response) {
if (!req.body.url) {
res.status(400);
vars.logger.error(req, "Provide url for verification to be done.");
return;
}
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
const url = new URLParse(req.body.url);
const urlInDomain = url.hostname.endsWith(vars.config.session.domain);
vars.logger.debug(req, "Check domain %s is in url %s.", vars.config.session.domain, url.hostname);
const sufficientPermissions = authSession.authentication_level >= Level.TWO_FACTOR;
vars.logger.debug(req, "Check that protocol %s is HTTPS.", url.protocol);
const protocolIsHttps = url.protocol === "https:";
if (sufficientPermissions && urlInDomain && protocolIsHttps) {
res.send("OK");
return;
}
res.send("NOK");
};
}

View File

@ -1,24 +1,36 @@
import express = require("express");
import * as URLParse from "url-parse";
import * as ObjectPath from "object-path";
import { ServerVariables } from "../../ServerVariables";
import BluebirdPromise = require("bluebird");
import ErrorReplies = require("../../ErrorReplies");
import UserMessages = require("../../../../../shared/UserMessages");
import { RedirectionMessage } from "../../../../../shared/RedirectionMessage";
import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe";
import { AuthenticationSessionHandler } from "../../../lib/AuthenticationSessionHandler";
export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response)
: BluebirdPromise<void> {
return new BluebirdPromise<void>(function (resolve, reject) {
let redirectUrl: string = "/";
if (vars.config.default_redirection_url) {
let redirectUrl: string = ObjectPath.get<Express.Request, string>(
req, "headers.x-target-url", undefined);
if (!redirectUrl && vars.config.default_redirection_url) {
redirectUrl = vars.config.default_redirection_url;
}
vars.logger.debug(req, "Request redirection to \"%s\".", redirectUrl);
res.json({
redirect: redirectUrl
} as RedirectionMessage);
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
if ((redirectUrl && !IsRedirectionSafe(vars, authSession, new URLParse(redirectUrl)))
|| !redirectUrl) {
res.status(204);
res.send();
return resolve();
}
res.json({redirect: redirectUrl});
return resolve();
})
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,

View File

@ -4,7 +4,7 @@ import BluebirdPromise = require("bluebird");
import express = require("express");
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
import U2f = require("u2f");
import redirect from "../../redirect";
import Redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariables } from "../../../../ServerVariables";
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
@ -41,7 +41,7 @@ export default function (vars: ServerVariables) {
return BluebirdPromise.reject(new Error("Error while signing"));
vars.logger.info(req, "Successful authentication");
authSession.authentication_level = Level.TWO_FACTOR;
redirect(vars)(req, res);
Redirect(vars)(req, res);
return BluebirdPromise.resolve();
})
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,

View File

@ -0,0 +1,14 @@
import { ServerVariables } from "../ServerVariables";
import { Level } from "../authentication/Level";
import * as URLParse from "url-parse";
import { AuthenticationSession } from "AuthenticationSession";
export default function IsRedirectionSafe(
vars: ServerVariables,
authSession: AuthenticationSession,
url: URLParse): boolean {
const urlInDomain = url.hostname.endsWith(vars.config.session.domain);
const protocolIsHttps = url.protocol === "https:";
return urlInDomain && protocolIsHttps;
}

View File

@ -1,33 +0,0 @@
import Assert = require("assert");
import Sinon = require("sinon");
import { SafeRedirector } from "./SafeRedirection";
describe("web_server/middlewares/SafeRedirection", () => {
describe("Url is in protected domain", () => {
before(() => {
this.redirector = new SafeRedirector("example.com");
this.res = {redirect: Sinon.stub()};
});
it("should redirect to provided url", () => {
this.redirector.redirectOrElse(this.res,
"https://mysubdomain.example.com:8080/abc",
"https://authelia.example.com");
Assert(this.res.redirect.calledWith("https://mysubdomain.example.com:8080/abc"));
});
it("should redirect to default url when wrong domain", () => {
this.redirector.redirectOrElse(this.res,
"https://mysubdomain.domain.rtf:8080/abc",
"https://authelia.example.com");
Assert(this.res.redirect.calledWith("https://authelia.example.com"));
});
it("should redirect to default url when not terminating by domain", () => {
this.redirector.redirectOrElse(this.res,
"https://mysubdomain.example.com.rtf:8080/abc",
"https://authelia.example.com");
Assert(this.res.redirect.calledWith("https://authelia.example.com"));
});
});
});

View File

@ -1,21 +0,0 @@
import Express = require("express");
import { BelongToDomain } from "../../../../shared/BelongToDomain";
export class SafeRedirector {
private domain: string;
constructor(domain: string) {
this.domain = domain;
}
redirectOrElse(
res: Express.Response,
url: string,
defaultUrl: string): void {
if (BelongToDomain(url, this.domain)) {
res.redirect(url);
}
res.redirect(defaultUrl);
}
}

View File

@ -34,6 +34,12 @@ export class Configurator {
app.disable(X_POWERED_BY);
app.enable(TRUST_PROXY);
app.use(Helmet());
app.use(function (req, res, next) {
if (!req.session) {
return next(new Error("No session available."));
}
next();
});
RestApi.setup(app, vars);
}

View File

@ -3,7 +3,6 @@ import Express = require("express");
import FirstFactorPost = require("../routes/firstfactor/post");
import LogoutPost from "../routes/logout/post";
import StateGet from "../routes/state/get";
import RedirectPost from "../routes/redirect/post";
import VerifyGet = require("../routes/verify/get");
import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
@ -87,7 +86,6 @@ function setupResetPassword(app: Express.Application, vars: ServerVariables) {
export class RestApi {
static setup(app: Express.Application, vars: ServerVariables): void {
app.get(Endpoints.STATE_GET, StateGet(vars));
app.post(Endpoints.REDIRECT_POST, RedirectPost(vars));
app.post(Endpoints.LOGOUT_POST, LogoutPost(vars));

View File

@ -25,8 +25,8 @@ describe.only("shared/DomainExtractor", function () {
const domain1 = DomainExtractor.fromUrl("https://login.example.com:8080/?rd=https://public.example.com:8080/");
Assert.equal(domain1, "login.example.com");
const domain2 = DomainExtractor.fromUrl("https://single_factor.example.com:8080/secret.html");
Assert.equal(domain2, "single_factor.example.com");
const domain2 = DomainExtractor.fromUrl("https://singlefactor.example.com:8080/secret.html");
Assert.equal(domain2, "singlefactor.example.com");
});
});
});

Some files were not shown because too many files have changed in this diff Show More