mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Merge pull request #331 from clems4ever/test-kubernetes
Add a suite for Kubernetes.
This commit is contained in:
commit
d858e13d87
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -38,3 +38,4 @@ Configuration.schema.json
|
|||
users_database.test.yml
|
||||
|
||||
.suite
|
||||
.kube
|
||||
|
|
|
@ -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
|
||||
|
|
14
README.md
14
README.md
|
@ -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
Normal file
49
bootstrap.sh
Normal 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"
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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: ''});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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} />;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
54
docs/build-and-dev.md
Normal file
54
docs/build-and-dev.md
Normal 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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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.
|
4
example/compose/nginx/backend/Dockerfile
Normal file
4
example/compose/nginx/backend/Dockerfile
Normal file
|
@ -0,0 +1,4 @@
|
|||
FROM nginx:alpine
|
||||
|
||||
ADD ./html /usr/share/nginx/html
|
||||
ADD ./nginx.conf /etc/nginx/nginx.conf
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQDNloThcTVDIU1uscEdGFIDndqcCwnmYF4bs6bpJ1BxkBhq
|
||||
GUNYRu12hjiHLSA80ZwhNxZ4T5YD4+81Gvs9zMoSGd4jJRSBX6evPTXR8zkmcQI/
|
||||
EtN7WgXOXhTx3JiIGlPOI3OGJR+rvfc9FiNx30FC1wpw3gt2ZC+NQeedDvdPKwID
|
||||
AQABoAAwDQYJKoZIhvcNAQELBQADgYEAmCX60kspIw1Zfb79AQOarFW5Q2K2h5Vx
|
||||
/cRbDyHlKtbmG77EtICccULyqf76B1gNRw5Zq3lSotSUcLzsWcdesXCFDC7k87Qf
|
||||
mpQKPj6GdTYJvdWf8aDwt32tAqWuBIRoAbdx5WbFPPWVfDcm7zDJefBrhNUDH0Qd
|
||||
vcnxjvPMmOM=
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,5 @@ services:
|
|||
image: schickling/mailcatcher
|
||||
ports:
|
||||
- "1025:1025"
|
||||
- "8085:1080"
|
||||
networks:
|
||||
- authelianet
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
130
example/kube/apps/apps.yml
Normal file
130
example/kube/apps/apps.yml
Normal 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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -18,8 +18,7 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: authelia
|
||||
image: localhost:5000/authelia:latest
|
||||
imagePullPolicy: Always
|
||||
image: authelia:dist
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts:
|
||||
|
|
|
@ -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: /
|
||||
|
|
8
example/kube/bootstrap-authelia.sh
Executable file
8
example/kube/bootstrap-authelia.sh
Executable 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
Normal file → Executable file
30
example/kube/bootstrap.sh
Normal file → Executable 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
|
||||
|
|
|
@ -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
|
179
example/kube/dashboard/dashboard.yaml
Normal file
179
example/kube/dashboard/dashboard.yaml
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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: {}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
141
example/kube/ingress-controller/rbac.yml
Normal file
141
example/kube/ingress-controller/rbac.yml
Normal 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
|
||||
|
||||
---
|
|
@ -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
|
||||
|
|
|
@ -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: /
|
22
example/kube/test.yml
Normal file
22
example/kube/test.yml
Normal 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
47
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.')
|
||||
|
|
24
scripts/authelia-scripts-bootstrap
Executable file
24
scripts/authelia-scripts-bootstrap
Executable 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);
|
||||
})
|
|
@ -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);
|
||||
})
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
var program = require('commander');
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
|
||||
let config;
|
||||
|
||||
|
|
|
@ -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)
|
||||
});
|
|
@ -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);
|
||||
})
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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();
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
|
||||
module.exports = {
|
||||
IMAGE_NAME: 'clems4ever/authelia'
|
||||
IMAGE_NAME: 'authelia',
|
||||
DOCKERHUB_IMAGE_NAME: 'clems4ever/authelia'
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
import FirstFactorPost = require("./post");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
14
server/src/lib/utils/IsRedirectionSafe.ts
Normal file
14
server/src/lib/utils/IsRedirectionSafe.ts
Normal 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;
|
||||
}
|
|
@ -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"));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user