[BREAKING] Create a suite for kubernetes tests.
Authelia client uses hash router instead of browser router in order to work with Kubernetes nginx-ingress-controller. This is also better for users having old browsers. This commit is breaking because it requires to change the configuration of the proxy to include the # in the URL of the login portal.pull/331/head
parent
f8a12b8482
commit
76fa325f08
|
@ -38,3 +38,4 @@ Configuration.schema.json
|
||||||
users_database.test.yml
|
users_database.test.yml
|
||||||
|
|
||||||
.suite
|
.suite
|
||||||
|
.kube
|
||||||
|
|
|
@ -15,18 +15,16 @@ addons:
|
||||||
hosts:
|
hosts:
|
||||||
- admin.example.com
|
- admin.example.com
|
||||||
- login.example.com
|
- login.example.com
|
||||||
- single_factor.example.com
|
- singlefactor.example.com
|
||||||
- dev.example.com
|
- dev.example.com
|
||||||
- home.example.com
|
- home.example.com
|
||||||
- mx1.mail.example.com
|
- mx1.mail.example.com
|
||||||
- mx2.mail.example.com
|
- mx2.mail.example.com
|
||||||
- public.example.com
|
- public.example.com
|
||||||
|
- secure.example.com
|
||||||
- authelia.example.com
|
- authelia.example.com
|
||||||
- admin.example.com
|
- admin.example.com
|
||||||
|
- mail.example.com
|
||||||
before_install:
|
|
||||||
- npm install -g npm@'>=2.13.5'
|
|
||||||
- pushd client && npm install && popd
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- export DISPLAY=:99.0
|
- 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
|
## Getting Started
|
||||||
|
|
||||||
If you want to quickly test Authelia with Docker, we recommend you read
|
You can start off with
|
||||||
[Getting Started](./docs/getting-started.md).
|
|
||||||
|
source bootstrap.sh
|
||||||
|
|
||||||
|
If you want to go further, please read [Getting Started](./docs/getting-started.md).
|
||||||
|
|
||||||
## Deployment
|
## 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
|
Now that you have tested **Authelia** and you want to try it out in your own infrastructure,
|
||||||
[Deployment](./docs/deployment-production.md). This guide will show you how to deploy
|
you can learn how to deploy and use it with [Deployment](./docs/deployment-production.md).
|
||||||
it on bare metal as well as on Kubernetes.
|
This guide will show you how to deploy it on bare metal as well as on
|
||||||
|
[Kubernetes](https://kubernetes.io/).
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|
|
@ -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 { Route, Switch } from "react-router-dom";
|
||||||
import { routes } from './routes/index';
|
import { routes } from './routes/index';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import reducer from './reducers';
|
import reducer from './reducers';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
|
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
|
||||||
|
|
||||||
const history = createBrowserHistory();
|
const history = createHashHistory();
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
reducer(history),
|
reducer(history),
|
||||||
compose(
|
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 {
|
export interface OwnProps {
|
||||||
username: string;
|
username: string;
|
||||||
redirectionUrl: string;
|
redirectionUrl: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DispatchProps {
|
export interface DispatchProps {
|
||||||
|
@ -27,7 +27,7 @@ class AlreadyAuthenticated extends Component<Props> {
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.statusIcon}><CircleLoader status={Status.SUCCESSFUL} /></div>
|
<div className={styles.statusIcon}><CircleLoader status={Status.SUCCESSFUL} /></div>
|
||||||
</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}>
|
<div className={styles.logoutButtonContainer}>
|
||||||
<Button
|
<Button
|
||||||
onClick={this.props.onLogoutClicked}
|
onClick={this.props.onLogoutClicked}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export interface StateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DispatchProps {
|
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;
|
export type Props = OwnProps & StateProps & DispatchProps;
|
||||||
|
@ -136,7 +136,11 @@ class FirstFactorForm extends Component<Props, State> {
|
||||||
this.props.onAuthenticationRequested(
|
this.props.onAuthenticationRequested(
|
||||||
this.state.username,
|
this.state.username,
|
||||||
this.state.password,
|
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 * as AutheliaService from '../../../services/AutheliaService';
|
||||||
import to from 'await-to-js';
|
import to from 'await-to-js';
|
||||||
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
|
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
|
||||||
import SafelyRedirectBehavior from '../../../behaviors/SafelyRedirectBehavior';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState): StateProps => {
|
const mapStateToProps = (state: RootState): StateProps => {
|
||||||
return {
|
return {
|
||||||
|
@ -16,7 +15,7 @@ const mapStateToProps = (state: RootState): StateProps => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAuthenticationRequested(dispatch: Dispatch, redirectionUrl: string | null) {
|
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;
|
let err, res;
|
||||||
|
|
||||||
// Validate first factor
|
// Validate first factor
|
||||||
|
@ -26,32 +25,37 @@ function onAuthenticationRequested(dispatch: Dispatch, redirectionUrl: string |
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
await dispatch(authenticateFailure(err.message));
|
await dispatch(authenticateFailure(err.message));
|
||||||
return;
|
throw new Error(err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
await dispatch(authenticateFailure('No response'));
|
await dispatch(authenticateFailure('No response'));
|
||||||
return;
|
throw new Error('No response');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if ('error' in json) {
|
if ('error' in json) {
|
||||||
await dispatch(authenticateFailure(json['error']));
|
await dispatch(authenticateFailure(json['error']));
|
||||||
return;
|
throw new Error(json['error']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(authenticateSuccess());
|
||||||
if ('redirect' in json) {
|
if ('redirect' in json) {
|
||||||
window.location.href = json['redirect'];
|
window.location.href = json['redirect'];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch state to move to next stage in case redirect is not possible
|
||||||
|
await FetchStateBehavior(dispatch);
|
||||||
} else if (res.status === 204) {
|
} else if (res.status === 204) {
|
||||||
dispatch(authenticateSuccess());
|
dispatch(authenticateSuccess());
|
||||||
|
|
||||||
// fetch state to move to next stage
|
// fetch state to move to next stage
|
||||||
FetchStateBehavior(dispatch);
|
await FetchStateBehavior(dispatch);
|
||||||
} else {
|
} else {
|
||||||
dispatch(authenticateFailure('Unknown error'));
|
dispatch(authenticateFailure('Unknown error'));
|
||||||
|
throw new Error('Unknown error... (' + res.status + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,20 @@ import { RootState } from '../../../reducers';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import u2fApi from 'u2f-api';
|
import u2fApi from 'u2f-api';
|
||||||
import to from 'await-to-js';
|
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 SecondFactorForm, { OwnProps, StateProps } from '../../../components/SecondFactorForm/SecondFactorForm';
|
||||||
import * as AutheliaService from '../../../services/AutheliaService';
|
import * as AutheliaService from '../../../services/AutheliaService';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
import fetchState from '../../../behaviors/FetchStateBehavior';
|
import fetchState from '../../../behaviors/FetchStateBehavior';
|
||||||
import LogoutBehavior from '../../../behaviors/LogoutBehavior';
|
import LogoutBehavior from '../../../behaviors/LogoutBehavior';
|
||||||
import SafelyRedirectBehavior from '../../../behaviors/SafelyRedirectBehavior';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState): StateProps => ({
|
const mapStateToProps = (state: RootState): StateProps => ({
|
||||||
securityKeySupported: state.secondFactor.securityKeySupported,
|
securityKeySupported: state.secondFactor.securityKeySupported,
|
||||||
|
@ -20,7 +27,7 @@ const mapStateToProps = (state: RootState): StateProps => ({
|
||||||
oneTimePasswordVerificationError: state.secondFactor.oneTimePasswordVerificationError,
|
oneTimePasswordVerificationError: state.secondFactor.oneTimePasswordVerificationError,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function triggerSecurityKeySigning(dispatch: Dispatch) {
|
async function triggerSecurityKeySigning(dispatch: Dispatch, redirectionUrl: string | null) {
|
||||||
let err, result;
|
let err, result;
|
||||||
dispatch(securityKeySign());
|
dispatch(securityKeySign());
|
||||||
[err, result] = await to(AutheliaService.requestSigning());
|
[err, result] = await to(AutheliaService.requestSigning());
|
||||||
|
@ -45,26 +52,40 @@ async function triggerSecurityKeySigning(dispatch: Dispatch) {
|
||||||
throw 'No response';
|
throw 'No response';
|
||||||
}
|
}
|
||||||
|
|
||||||
[err, result] = await to(AutheliaService.completeSecurityKeySigning(result));
|
[err, result] = await to(AutheliaService.completeSecurityKeySigning(result, redirectionUrl));
|
||||||
if (err) {
|
if (err) {
|
||||||
await dispatch(securityKeySignFailure(err.message));
|
await dispatch(securityKeySignFailure(err.message));
|
||||||
throw err;
|
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() {
|
async function handle() {
|
||||||
if (ownProps.redirectionUrl) {
|
|
||||||
try {
|
|
||||||
await SafelyRedirectBehavior(ownProps.redirectionUrl);
|
|
||||||
} catch (e) {
|
|
||||||
await fetchState(dispatch);
|
await fetchState(dispatch);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
await fetchState(dispatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration) {
|
if (duration) {
|
||||||
setTimeout(handle, duration);
|
setTimeout(handle, duration);
|
||||||
|
@ -88,14 +109,13 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
|
||||||
const isU2FSupported = await u2fApi.isSupported();
|
const isU2FSupported = await u2fApi.isSupported();
|
||||||
if (isU2FSupported) {
|
if (isU2FSupported) {
|
||||||
await dispatch(setSecurityKeySupported(true));
|
await dispatch(setSecurityKeySupported(true));
|
||||||
await triggerSecurityKeySigning(dispatch);
|
await triggerSecurityKeySigning(dispatch, ownProps.redirectionUrl);
|
||||||
await handleSuccess(dispatch, ownProps, 1000);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onOneTimePasswordValidationRequested: async (token: string) => {
|
onOneTimePasswordValidationRequested: async (token: string) => {
|
||||||
let err, res;
|
let err, res;
|
||||||
dispatch(oneTimePasswordVerification());
|
dispatch(oneTimePasswordVerification());
|
||||||
[err, res] = await to(AutheliaService.verifyTotpToken(token));
|
[err, res] = await to(AutheliaService.verifyTotpToken(token, ownProps.redirectionUrl));
|
||||||
if (err) {
|
if (err) {
|
||||||
dispatch(oneTimePasswordVerificationFailure(err.message));
|
dispatch(oneTimePasswordVerificationFailure(err.message));
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -105,13 +125,13 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
|
||||||
throw 'No response';
|
throw 'No response';
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = await res.json();
|
try {
|
||||||
if ('error' in body) {
|
await redirectIfPossible(dispatch, res);
|
||||||
dispatch(oneTimePasswordVerificationFailure(body['error']));
|
|
||||||
throw body['error'];
|
|
||||||
}
|
|
||||||
dispatch(oneTimePasswordVerificationSuccess());
|
dispatch(oneTimePasswordVerificationSuccess());
|
||||||
await handleSuccess(dispatch, ownProps);
|
await handleSuccess(dispatch);
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(oneTimePasswordVerificationFailure(err.message));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,24 +75,36 @@ export async function requestSigning() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function completeSecurityKeySigning(response: u2fApi.SignResponse) {
|
export async function completeSecurityKeySigning(
|
||||||
return fetchSafe('/api/u2f/sign', {
|
response: u2fApi.SignResponse, redirectionUrl: string | null) {
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
const headers: Record<string, string> = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
}
|
||||||
|
if (redirectionUrl) {
|
||||||
|
headers['X-Target-Url'] = redirectionUrl;
|
||||||
|
}
|
||||||
|
return fetchSafe('/api/u2f/sign', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
body: JSON.stringify(response),
|
body: JSON.stringify(response),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyTotpToken(token: string) {
|
export async function verifyTotpToken(
|
||||||
return fetchSafe('/api/totp', {
|
token: string, redirectionUrl: string | null) {
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
const headers: Record<string, string> = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
}
|
||||||
|
if (redirectionUrl) {
|
||||||
|
headers['X-Target-Url'] = redirectionUrl;
|
||||||
|
}
|
||||||
|
return fetchSafe('/api/totp', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
body: JSON.stringify({token}),
|
body: JSON.stringify({token}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -124,23 +136,3 @@ export async function resetPassword(newPassword: string) {
|
||||||
body: JSON.stringify({password: newPassword})
|
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) {
|
} else if (this.props.stage === Stage.ALREADY_AUTHENTICATED) {
|
||||||
return <AlreadyAuthenticated
|
return <AlreadyAuthenticated
|
||||||
username={this.props.remoteState.username}
|
username={this.props.remoteState.username}
|
||||||
redirectionUrl={this.props.remoteState.default_redirection_url} />;
|
redirectionUrl={this.props.redirectionUrl} />;
|
||||||
}
|
}
|
||||||
return <FirstFactorForm redirectionUrl={this.props.redirectionUrl} />;
|
return <FirstFactorForm redirectionUrl={this.props.redirectionUrl} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,8 +130,10 @@ access_control:
|
||||||
rules:
|
rules:
|
||||||
# Rules applied to everyone
|
# Rules applied to everyone
|
||||||
- domain: public.example.com
|
- domain: public.example.com
|
||||||
|
policy: bypass
|
||||||
|
- domain: secure.example.com
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
- domain: single_factor.example.com
|
- domain: singlefactor.example.com
|
||||||
policy: one_factor
|
policy: one_factor
|
||||||
|
|
||||||
# Rules applied to 'admin' group
|
# 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,
|
building the distributed version of Authelia, building the Docker image, running suites,
|
||||||
testing the code, etc...
|
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:
|
For instance, you can build Authelia with:
|
||||||
|
|
||||||
npm run scripts build
|
authelia-scripts build
|
||||||
|
|
||||||
Or start the *basic* suite with:
|
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.
|
You will find more information in the scripts usage helpers.
|
|
@ -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
|
### Deploy With Docker
|
||||||
|
|
||||||
docker pull clems4ever/authelia
|
|
||||||
docker run -v /path/to/your/config.yml:/etc/authelia/config.yml clems4ever/authelia
|
docker run -v /path/to/your/config.yml:/etc/authelia/config.yml clems4ever/authelia
|
||||||
|
|
||||||
## On top of Kubernetes
|
## On top of Kubernetes
|
||||||
|
|
||||||
<img src="/images/kube-logo.png" width="24" align="left">
|
<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).
|
[nginx ingress controller](https://github.com/kubernetes/ingress-nginx).
|
||||||
|
|
||||||
Please refer to the following [documentation](../example/kube/README.md)
|
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
|
**Authelia** can be tested in a matter of seconds with docker-compose based
|
||||||
on the latest image available on [Dockerhub].
|
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:
|
source bootstrap.sh
|
||||||
- **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.
|
|
||||||
|
|
||||||
### Docker & docker-compose
|
Then, start the *dockerhub* [suite].
|
||||||
|
|
||||||
Make sure you have **docker** and **docker-compose** installed on your
|
authelia-scripts suites start dockerhub
|
||||||
machine.
|
|
||||||
Here are the versions used for testing in Travis:
|
|
||||||
|
|
||||||
$ docker --version
|
A [suite] is kind of a virtual environment for running Authelia.
|
||||||
Docker version 17.03.1-ce, build c6d412e
|
If you want more details please read the related [documentation](./suites.md).
|
||||||
|
|
||||||
$ 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).
|
|
||||||
|
|
||||||
## Test it!
|
## Test it!
|
||||||
|
|
||||||
|
@ -77,25 +33,52 @@ Below is what the login page looks like after you accepted all exceptions:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
You can use one of the users listed in [https://home.example.com:8080/](https://home.example.com:8080/).
|
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
|
At some point, you'll be required to register your second factor device.
|
||||||
U2F or TOTP. Since your security is **Authelia**'s priority, it will send
|
Since your security is **Authelia**'s priority, it will send
|
||||||
an email to the email address of the user to confirm the user identity.
|
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
|
Since we're running a test environment, we provide a fake webmail called
|
||||||
*MailCatcher* from which you can checkout the email and confirm
|
*MailCatcher* from which you can checkout the email and confirm
|
||||||
your identity.
|
your identity.
|
||||||
The webmail is accessible from
|
The webmail is accessible from
|
||||||
[http://localhost:8085](http://localhost:8085).
|
[http://mail.example.com:8080](http://mail.example.com: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.
|
|
||||||
|
|
||||||
Enjoy!
|
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
|
[config.template.yml]: ../config.template.yml
|
||||||
[DockerHub]: https://hub.docker.com/r/clems4ever/authelia/
|
[DockerHub]: https://hub.docker.com/r/clems4ever/authelia/
|
||||||
[Build]: ./build.md
|
[suite]: ./suites.md
|
|
@ -1,18 +1,18 @@
|
||||||
# Suites
|
# Suites
|
||||||
|
|
||||||
Authelia is a single component in interaction with many others. Consequently, testing the features
|
Authelia is a single component in interaction with many others. Consequently, testing the features
|
||||||
is not as easy as we might think. Consequently, a suite is kind of a virtual environment for Authelia,
|
is not as easy as we might think. In order to solve this problem, Authelia came up with the concept of
|
||||||
it allows to create an environment made of components such as nginx, redis or mongo in which Authelia can
|
suite which is a kind of virtual environment for Authelia, it allows to create an environment made of
|
||||||
run and be tested.
|
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
|
This abstraction allows to prepare an environment for manual testing during development and also to
|
||||||
craft and run integration tests.
|
craft and run integration tests.
|
||||||
|
|
||||||
## Start a suite.
|
## 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.
|
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
|
### 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.
|
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*.
|
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:
|
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
|
This command will run the tests for the *complete* suite using the built version of Authelia that
|
||||||
should be located in *dist*.
|
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
|
### 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
|
## 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*
|
* **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*
|
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).
|
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 in the virtual environment of the suite.
|
* **test.ts** - It defines a set of tests to run against the suite.
|
|
@ -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'
|
version: '2'
|
||||||
services:
|
services:
|
||||||
nginx-backend:
|
nginx-backend:
|
||||||
image: nginx:alpine
|
image: authelia-example-backend
|
||||||
volumes:
|
|
||||||
- ./example/compose/nginx/backend/html:/usr/share/nginx/html
|
|
||||||
- ./example/compose/nginx/backend/nginx.conf:/etc/nginx/nginx.conf
|
|
||||||
networks:
|
networks:
|
||||||
- authelianet
|
- authelianet
|
||||||
|
|
|
@ -15,7 +15,13 @@
|
||||||
public.example.com <a href="https://public.example.com:8080/"> / index.html</a>
|
public.example.com <a href="https://public.example.com:8080/"> / index.html</a>
|
||||||
</li>
|
</li>
|
||||||
<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>
|
<ul>
|
||||||
<li>Groups
|
<li>Groups
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -51,12 +57,9 @@
|
||||||
<li>
|
<li>
|
||||||
mx2.main.example.com <a href="https://mx2.mail.example.com:8080/secret.html"> / secret.html</a>
|
mx2.main.example.com <a href="https://mx2.mail.example.com:8080/secret.html"> / secret.html</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
single_factor.example.com <a href="https://single_factor.example.com:8080/secret.html"> / secret.html</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</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>
|
<h1>List of users</h1>
|
||||||
Here is the list of credentials you can log in with to test access control.<br/>
|
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
|
<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>
|
<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;">
|
<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
|
||||||
|
|
||||||
# The rules that apply to anyone.
|
rules:
|
||||||
# The value is a list of rules.
|
# Rules applied to everyone
|
||||||
|
|
||||||
any:
|
|
||||||
- domain: public.example.com
|
- domain: public.example.com
|
||||||
policy: allow
|
policy: bypass
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: two_factor
|
||||||
|
- domain: singlefactor.example.com
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
# Group-based rules. The key is a group name and the value
|
# Rules applied to 'admin' group
|
||||||
# is a list of rules.
|
|
||||||
|
|
||||||
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'
|
- domain: 'mx2.mail.example.com'
|
||||||
|
subject: 'group:admin'
|
||||||
policy: deny
|
policy: deny
|
||||||
dev:
|
- domain: '*.example.com'
|
||||||
|
subject: 'group:admin'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group
|
||||||
- domain: dev.example.com
|
- domain: dev.example.com
|
||||||
policy: allow
|
|
||||||
resources:
|
resources:
|
||||||
- '^/groups/dev/.*$'
|
- '^/groups/dev/.*$'
|
||||||
|
subject: 'group:dev'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
# User-based rules. The key is a user name and the value
|
# Rules applied to user 'john'
|
||||||
# is a list of rules.
|
|
||||||
|
|
||||||
users:
|
|
||||||
john:
|
|
||||||
- domain: dev.example.com
|
- domain: dev.example.com
|
||||||
policy: allow
|
|
||||||
resources:
|
resources:
|
||||||
- '^/users/john/.*$'
|
- '^/users/john/.*$'
|
||||||
harry:
|
subject: 'user:john'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
|
||||||
|
# Rules applied to user 'harry'
|
||||||
- domain: dev.example.com
|
- domain: dev.example.com
|
||||||
policy: allow
|
|
||||||
resources:
|
resources:
|
||||||
- '^/users/harry/.*$'
|
- '^/users/harry/.*$'
|
||||||
bob:
|
subject: 'user:harry'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'bob'
|
||||||
- domain: '*.mail.example.com'
|
- domain: '*.mail.example.com'
|
||||||
policy: allow
|
subject: 'user:bob'
|
||||||
|
policy: two_factor
|
||||||
- domain: 'dev.example.com'
|
- domain: 'dev.example.com'
|
||||||
policy: allow
|
|
||||||
resources:
|
resources:
|
||||||
- '^/users/bob/.*$'
|
- '^/users/bob/.*$'
|
||||||
- domain: 'dev.example.com'
|
subject: 'user:bob'
|
||||||
policy: allow
|
policy: two_factor
|
||||||
resources:
|
</pre>
|
||||||
- '^/users/harry/.*$'</pre>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<link rel="icon" href="/icon.png" type="image/png" />
|
<link rel="icon" href="/icon.png" type="image/png" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Secret</h1>
|
|
||||||
This is a very important secret!<br/>
|
This is a very important secret!<br/>
|
||||||
Go back to <a href="https://home.example.com:8080/">home page</a>.
|
Go back to <a href="https://home.example.com:8080/">home page</a>.
|
||||||
</body>
|
</body>
|
|
@ -18,6 +18,12 @@ http {
|
||||||
server_name public.example.com;
|
server_name public.example.com;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/html/secure;
|
||||||
|
server_name secure.example.com;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
root /usr/share/nginx/html/admin;
|
root /usr/share/nginx/html/admin;
|
||||||
|
@ -38,8 +44,8 @@ http {
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
root /usr/share/nginx/html/single_factor;
|
root /usr/share/nginx/html/singlefactor;
|
||||||
server_name single_factor.example.com;
|
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 {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name public.example.com;
|
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;
|
resolver 127.0.0.11 ipv6=off;
|
||||||
set $upstream_verify <%= authelia_backend %>/api/verify;
|
set $upstream_verify <%= authelia_backend %>/api/verify;
|
||||||
set $upstream_endpoint http://nginx-backend;
|
set $upstream_endpoint http://nginx-backend;
|
||||||
|
@ -150,8 +219,7 @@ http {
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
|
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;
|
proxy_pass $upstream_endpoint;
|
||||||
}
|
}
|
||||||
|
@ -167,63 +235,12 @@ http {
|
||||||
auth_request_set $groups $upstream_http_remote_groups;
|
auth_request_set $groups $upstream_http_remote_groups;
|
||||||
proxy_set_header Custom-Forwarded-Groups $groups;
|
proxy_set_header Custom-Forwarded-Groups $groups;
|
||||||
|
|
||||||
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
|
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
|
||||||
error_page 403 = https://login.example.com:8080/error/403;
|
|
||||||
|
|
||||||
proxy_pass $upstream_headers;
|
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 {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name dev.example.com;
|
server_name dev.example.com;
|
||||||
|
@ -267,8 +284,7 @@ http {
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
|
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;
|
proxy_pass $upstream_endpoint;
|
||||||
}
|
}
|
||||||
|
@ -317,8 +333,7 @@ http {
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
|
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;
|
proxy_pass $upstream_endpoint;
|
||||||
}
|
}
|
||||||
|
@ -326,7 +341,7 @@ http {
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name single_factor.example.com;
|
server_name singlefactor.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
resolver 127.0.0.11 ipv6=off;
|
||||||
set $upstream_verify <%= authelia_backend %>/api/verify;
|
set $upstream_verify <%= authelia_backend %>/api/verify;
|
||||||
|
@ -371,8 +386,7 @@ http {
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
|
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;
|
proxy_pass $upstream_endpoint;
|
||||||
}
|
}
|
||||||
|
@ -388,8 +402,7 @@ http {
|
||||||
auth_request_set $groups $upstream_http_remote_groups;
|
auth_request_set $groups $upstream_http_remote_groups;
|
||||||
proxy_set_header Custom-Forwarded-Groups $groups;
|
proxy_set_header Custom-Forwarded-Groups $groups;
|
||||||
|
|
||||||
error_page 401 =302 https://login.example.com:8080?rd=$redirect;
|
error_page 401 =302 https://login.example.com:8080/#/?rd=$redirect;
|
||||||
error_page 403 = https://login.example.com:8080/error/403;
|
|
||||||
|
|
||||||
proxy_pass $upstream_headers;
|
proxy_pass $upstream_headers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,5 @@ services:
|
||||||
image: schickling/mailcatcher
|
image: schickling/mailcatcher
|
||||||
ports:
|
ports:
|
||||||
- "1025:1025"
|
- "1025:1025"
|
||||||
- "8085:1080"
|
|
||||||
networks:
|
networks:
|
||||||
- authelianet
|
- 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.
|
applications using 2-factor authentication and Single Sign-On.
|
||||||
|
|
||||||
This example leverages [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
|
This example leverages [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
|
||||||
v0.13.0 to delegate authentications and authorizations to Authelia within
|
to delegate authentication and authorization to Authelia within the cluster.
|
||||||
the cluster.
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
In order to deploy Authelia on Kube, you must have a cluster at hand. If you
|
You can either try to install **Authelia** on your running instance of Kubernetes
|
||||||
don't, please follow the next section otherwise skip it and go
|
or deploy the dedicated [suite](/docs/suites.md) called *kubernetes*.
|
||||||
to the next.
|
|
||||||
|
|
||||||
### Set up a Kube cluster
|
### Set up a Kube cluster
|
||||||
|
|
||||||
Hopefully, spawning a development cluster from scratch has become very
|
The simplest way to start a Kubernetes cluster is to deploy the *kubernetes* suite with
|
||||||
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.
|
|
||||||
|
|
||||||
Basically, you need to follow the instruction from the [repository](https://github.com/kubernetes/minikube).
|
authelia-scripts suites start kubernetes
|
||||||
It should be a matter of downloading the binary and start the cluster with
|
|
||||||
two commands:
|
|
||||||
|
|
||||||
```
|
This will take a few seconds (or minutes) to deploy the cluster.
|
||||||
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.
|
|
||||||
|
|
||||||
## How does it work?
|
## 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
|
`nginx.ingress.kubernetes.io/auth-url` that is filled with the URL of
|
||||||
Authelia's verification endpoint.
|
Authelia's verification endpoint.
|
||||||
The ingress controller also requires the URL to the
|
The ingress controller also requires the URL to the
|
||||||
authentication portal so that the user can be redirected in case she is not
|
authentication portal so that the user can be redirected if he is not
|
||||||
yet authenticated.
|
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
|
### Production grade infrastructure
|
||||||
|
|
||||||
What is great with using [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
|
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)
|
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
|
letsencrypt to issue and renew certificates every three month without any
|
||||||
manual intervention.
|
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
|
Given your cluster already runs a LDAP server, a Redis, a Mongo database,
|
||||||
cluster and a SMTP server, you'll only need to install the ingress-controller
|
a SMTP server and a nginx ingress-controller, you can deploy **Authelia**
|
||||||
and Authelia whose kubernetes deployment configurations are respectively in
|
and update your ingress configurations. An example is provided
|
||||||
`ingress-controller` and `authelia` directories. A template configuration
|
[here](./authelia).
|
||||||
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!
|
|
||||||
|
|
||||||
## Questions
|
## 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-----
|
|
|
@ -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
|
# Note: this parameter is optional. If not provided, user won't
|
||||||
# be redirected upon successful authentication.
|
# 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
|
# The authentication backend to use for verifying user passwords
|
||||||
# and retrieve information such as email address and groups
|
# and retrieve information such as email address and groups
|
||||||
|
@ -79,76 +79,60 @@ authentication_backend:
|
||||||
## file:
|
## file:
|
||||||
## path: ./users_database.yml
|
## 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
|
||||||
#
|
#
|
||||||
# Access control is a set of rules you can use to restrict user access to certain
|
# For more details about the configuration see config.template.yml at the root of the repo.
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
access_control:
|
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
|
default_policy: deny
|
||||||
|
|
||||||
# The rules that apply to anyone.
|
rules:
|
||||||
# The value is a list of rules.
|
# Rules applied to everyone
|
||||||
any:
|
- 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'
|
- domain: '*.example.com'
|
||||||
policy: allow
|
subject: 'group:admin'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
# Group-based rules. The key is a group name and the value
|
# Rules applied to 'dev' group
|
||||||
# is a list of rules.
|
- domain: dev.example.com
|
||||||
groups: {}
|
resources:
|
||||||
|
- '^/groups/dev/.*$'
|
||||||
|
subject: 'group:dev'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
# User-based rules. The key is a user name and the value
|
# Rules applied to user 'john'
|
||||||
# is a list of rules.
|
- domain: dev.example.com
|
||||||
users: {}
|
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
|
# Configuration of session cookies
|
||||||
|
|
|
@ -18,8 +18,7 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: authelia
|
- name: authelia
|
||||||
image: localhost:5000/authelia:latest
|
image: authelia:dist
|
||||||
imagePullPolicy: Always
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|
|
@ -10,10 +10,10 @@ spec:
|
||||||
tls:
|
tls:
|
||||||
- secretName: authelia-tls
|
- secretName: authelia-tls
|
||||||
hosts:
|
hosts:
|
||||||
- login.kube.example.com
|
- login.example.com
|
||||||
rules:
|
rules:
|
||||||
rules:
|
rules:
|
||||||
- host: login.kube.example.com
|
- host: login.example.com
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
|
|
|
@ -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
|
|
@ -1,30 +1,19 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
start_apps() {
|
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
|
# 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 test-app-tls --namespace=authelia --from-file=apps/ssl/tls.key --from-file=apps/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
|
|
||||||
|
|
||||||
# Spawn the applications
|
# Spawn the applications
|
||||||
kubectl apply -f apps
|
kubectl apply -f apps
|
||||||
kubectl apply -f apps/app1
|
|
||||||
kubectl apply -f apps/app2
|
|
||||||
kubectl apply -f apps/app-home
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start_ingress_controller() {
|
start_ingress_controller() {
|
||||||
kubectl apply -f ingress-controller
|
kubectl apply -f ingress-controller
|
||||||
}
|
}
|
||||||
|
|
||||||
start_authelia() {
|
start_dashboard() {
|
||||||
kubectl create configmap authelia-config --namespace=authelia --from-file=authelia/configs/config.yml
|
kubectl apply -f dashboard
|
||||||
kubectl apply -f authelia
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Spawn Redis and Mongo as backend for Authelia
|
# Spawn Redis and Mongo as backend for Authelia
|
||||||
|
@ -34,28 +23,23 @@ start_storage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a fake mailbox to catch emails sent by Authelia
|
# Create a fake mailbox to catch emails sent by Authelia
|
||||||
start_mailcatcher() {
|
start_mail() {
|
||||||
kubectl apply -f mailcatcher
|
kubectl apply -f mail
|
||||||
}
|
}
|
||||||
|
|
||||||
start_ldap() {
|
start_ldap() {
|
||||||
kubectl apply -f ldap
|
kubectl apply -f ldap
|
||||||
}
|
}
|
||||||
|
|
||||||
start_docker_registry() {
|
|
||||||
kubectl apply -f docker-registry
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create the Authelia namespace in the cluster
|
# Create the Authelia namespace in the cluster
|
||||||
create_namespace() {
|
create_namespace() {
|
||||||
kubectl apply -f namespace.yml
|
kubectl apply -f namespace.yml
|
||||||
}
|
}
|
||||||
|
|
||||||
create_namespace
|
create_namespace
|
||||||
start_docker_registry
|
start_dashboard
|
||||||
start_storage
|
start_storage
|
||||||
start_ldap
|
start_ldap
|
||||||
start_mailcatcher
|
start_mail
|
||||||
start_ingress_controller
|
start_ingress_controller
|
||||||
start_authelia
|
|
||||||
start_apps
|
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
|
|
|
@ -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
|
apiVersion: extensions/v1beta1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-ingress-controller-external
|
name: nginx-ingress-controller
|
||||||
namespace: authelia
|
namespace: authelia
|
||||||
labels:
|
labels:
|
||||||
k8s-app: nginx-ingress-controller-external
|
k8s-app: nginx-ingress-controller
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
revisionHistoryLimit: 0
|
revisionHistoryLimit: 0
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
k8s-app: nginx-ingress-controller-external
|
k8s-app: nginx-ingress-controller
|
||||||
name: nginx-ingress-controller-external
|
name: nginx-ingress-controller
|
||||||
annotations:
|
annotations:
|
||||||
prometheus.io/port: '10254'
|
prometheus.io/port: '10254'
|
||||||
prometheus.io/scrape: 'true'
|
prometheus.io/scrape: 'true'
|
||||||
spec:
|
spec:
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
|
serviceAccountName: nginx-ingress-controller-serviceaccount
|
||||||
containers:
|
containers:
|
||||||
- image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.13.0
|
- image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.23.0
|
||||||
name: nginx-ingress-controller-external
|
name: nginx-ingress-controller
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
|
@ -38,5 +39,4 @@ spec:
|
||||||
args:
|
args:
|
||||||
- /nginx-ingress-controller
|
- /nginx-ingress-controller
|
||||||
- --ingress-class=nginx
|
- --ingress-class=nginx
|
||||||
- --election-id=ingress-controller-leader-external
|
- --election-id=ingress-controller-leader
|
||||||
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
|
|
||||||
|
|
|
@ -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
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-ingress-controller-external-service
|
name: nginx-ingress-controller-service
|
||||||
namespace: authelia
|
namespace: authelia
|
||||||
labels:
|
labels:
|
||||||
k8s-app: nginx-ingress-controller-external
|
k8s-app: nginx-ingress-controller
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
k8s-app: nginx-ingress-controller-external
|
k8s-app: nginx-ingress-controller
|
||||||
|
type: NodePort
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
name: http
|
name: http
|
||||||
- port: 443
|
- port: 443
|
||||||
name: https
|
name: https
|
||||||
externalIPs:
|
|
||||||
- 192.168.39.26
|
|
||||||
|
|
|
@ -7,8 +7,12 @@ metadata:
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: "nginx"
|
kubernetes.io/ingress.class: "nginx"
|
||||||
spec:
|
spec:
|
||||||
|
tls:
|
||||||
|
- secretName: mail-tls
|
||||||
|
hosts:
|
||||||
|
- mail.example.com
|
||||||
rules:
|
rules:
|
||||||
- host: mail.kube.example.com
|
- host: mail.example.com
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
|
@ -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
|
|
@ -260,14 +260,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/connect-redis": {
|
"@types/connect-redis": {
|
||||||
"version": "0.0.7",
|
"version": "0.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.9.tgz",
|
||||||
"integrity": "sha512-ui1DPnJxqgBhqPj0XTVyPkzffEX9DIGkb2nT2mzZ0OlsKn/u9BvRvKmjpi4Vydf2uw3z/D4UmMH4KMkilySqvw==",
|
"integrity": "sha512-LdAbC9EnQkHLMgeV0EKufPbqaRKfsAxgdWaYnDtQOKuJV2U4JpFE2EU4yKJ3G2FjVvNXyzO6rcaBBjPBztGTzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/express": "4.11.1",
|
"@types/express": "4.11.1",
|
||||||
"@types/express-session": "1.15.8",
|
"@types/express-session": "1.15.8",
|
||||||
"@types/redis": "2.8.6"
|
"@types/redis": "2.8.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/ejs": {
|
"@types/ejs": {
|
||||||
|
@ -495,12 +495,11 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/redis": {
|
"@types/redis": {
|
||||||
"version": "2.8.6",
|
"version": "2.8.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.11.tgz",
|
||||||
"integrity": "sha512-kaSI4XQwCfJtPiuyCXvLxCaw2N0fMZesdob3Jh01W20vNFct+3lfvJ/4yCJxbSopXOBOzpg+pGxkW6uWZrPZHA==",
|
"integrity": "sha512-i0AqDzjX0FlO+npqkhl1etZw1fGnJc0IeHUHKAf/V7Drk+rDJI634GE9Lwh8aj/2ahuW6jHdWX4ZnyNofWXKrw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/events": "1.2.0",
|
|
||||||
"@types/node": "10.0.3"
|
"@types/node": "10.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -569,7 +568,8 @@
|
||||||
"@types/url-parse": {
|
"@types/url-parse": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.2.tgz",
|
"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": {
|
"@types/winston": {
|
||||||
"version": "2.3.9",
|
"version": "2.3.9",
|
||||||
|
@ -1745,21 +1745,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"connect-redis": {
|
"connect-redis": {
|
||||||
"version": "3.3.3",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-3.4.1.tgz",
|
||||||
"integrity": "sha512-rpWsW2uk1uOe/ccY/JvW+RiLrhZm7auIx8z4yR+KXemFTIhJyD58jXiJbI0E/fZCnybawpdSqOZ+6/ah6aBeyg==",
|
"integrity": "sha512-oXNcpLg/PJ6G4gbhyGwrQK9mUQTKYa2aEnOH9kWIxbNUjIFPqUmzz75RdLp5JTPSjrBVcz+9ll4sSxfvlW0ZLA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "3.1.0",
|
"debug": "4.1.1",
|
||||||
"redis": "2.8.0"
|
"redis": "2.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "3.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
"requires": {
|
"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==",
|
"integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"double-ended-queue": "2.1.0-0",
|
"double-ended-queue": "2.1.0-0",
|
||||||
"redis-commands": "1.3.5",
|
"redis-commands": "1.4.0",
|
||||||
"redis-parser": "2.6.0"
|
"redis-parser": "2.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redis-commands": {
|
"redis-commands": {
|
||||||
"version": "1.3.5",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz",
|
||||||
"integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA=="
|
"integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw=="
|
||||||
},
|
},
|
||||||
"redis-parser": {
|
"redis-parser": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
|
|
|
@ -31,11 +31,10 @@
|
||||||
"title": "Authelia API documentation"
|
"title": "Authelia API documentation"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/url-parse": "^1.4.2",
|
|
||||||
"ajv": "^6.3.0",
|
"ajv": "^6.3.0",
|
||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
"connect-redis": "^3.3.0",
|
"connect-redis": "^3.4.1",
|
||||||
"crypt3": "^1.0.0",
|
"crypt3": "^1.0.0",
|
||||||
"ejs": "^2.5.5",
|
"ejs": "^2.5.5",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
|
@ -51,7 +50,6 @@
|
||||||
"object-path": "^0.11.3",
|
"object-path": "^0.11.3",
|
||||||
"randomatic": "^3.1.0",
|
"randomatic": "^3.1.0",
|
||||||
"randomstring": "^1.1.5",
|
"randomstring": "^1.1.5",
|
||||||
"redis": "^2.8.0",
|
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"u2f": "^0.1.2",
|
"u2f": "^0.1.2",
|
||||||
"u2f-api": "^1.0.7",
|
"u2f-api": "^1.0.7",
|
||||||
|
@ -64,7 +62,7 @@
|
||||||
"@types/body-parser": "^1.16.3",
|
"@types/body-parser": "^1.16.3",
|
||||||
"@types/chokidar": "^1.7.5",
|
"@types/chokidar": "^1.7.5",
|
||||||
"@types/commander": "^2.12.2",
|
"@types/commander": "^2.12.2",
|
||||||
"@types/connect-redis": "0.0.7",
|
"@types/connect-redis": "0.0.9",
|
||||||
"@types/ejs": "^2.3.33",
|
"@types/ejs": "^2.3.33",
|
||||||
"@types/express": "^4.0.35",
|
"@types/express": "^4.0.35",
|
||||||
"@types/express-session": "1.15.8",
|
"@types/express-session": "1.15.8",
|
||||||
|
@ -81,13 +79,13 @@
|
||||||
"@types/object-path": "^0.9.28",
|
"@types/object-path": "^0.9.28",
|
||||||
"@types/query-string": "^5.1.0",
|
"@types/query-string": "^5.1.0",
|
||||||
"@types/randomstring": "^1.1.5",
|
"@types/randomstring": "^1.1.5",
|
||||||
"@types/redis": "^2.6.0",
|
|
||||||
"@types/request": "^2.0.5",
|
"@types/request": "^2.0.5",
|
||||||
"@types/request-promise": "^4.1.38",
|
"@types/request-promise": "^4.1.38",
|
||||||
"@types/selenium-webdriver": "^3.0.4",
|
"@types/selenium-webdriver": "^3.0.4",
|
||||||
"@types/sinon": "^4.3.0",
|
"@types/sinon": "^4.3.0",
|
||||||
"@types/speakeasy": "^2.0.2",
|
"@types/speakeasy": "^2.0.2",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
|
"@types/url-parse": "^1.4.2",
|
||||||
"@types/winston": "^2.3.2",
|
"@types/winston": "^2.3.2",
|
||||||
"@types/yamljs": "^0.2.30",
|
"@types/yamljs": "^0.2.30",
|
||||||
"apidoc": "^0.17.6",
|
"apidoc": "^0.17.6",
|
||||||
|
|
|
@ -5,6 +5,7 @@ var program = require('commander');
|
||||||
program
|
program
|
||||||
.version('0.0.1')
|
.version('0.0.1')
|
||||||
|
|
||||||
|
.command('bootstrap', 'Prepare some containers for development.')
|
||||||
.command('suites', 'Run Authelia in specific environments.')
|
.command('suites', 'Run Authelia in specific environments.')
|
||||||
.command('serve', 'Run Authelia with a customized configuration.')
|
.command('serve', 'Run Authelia with a customized configuration.')
|
||||||
.command('build', 'Build production version of Authelia from source.')
|
.command('build', 'Build production version of Authelia from source.')
|
||||||
|
|
|
@ -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
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var { exec } = require('./utils/exec');
|
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 {
|
function deploy_on_dockerhub {
|
||||||
TAG=$1
|
TAG=$1
|
||||||
IMAGE_NAME=clems4ever/authelia
|
IMAGE_NAME=authelia:dist
|
||||||
IMAGE_WITH_TAG=$IMAGE_NAME:$TAG
|
DOCKERHUB_IMAGE_NAME=clems4ever/authelia
|
||||||
|
IMAGE_WITH_TAG=$DOCKERHUB_IMAGE_NAME:$TAG
|
||||||
|
|
||||||
echo "==========================================================="
|
echo "==========================================================="
|
||||||
echo "Docker image $IMAGE_WITH_TAG will be deployed on Dockerhub."
|
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
|
login_to_dockerhub
|
||||||
deploy_on_dockerhub master
|
deploy_on_dockerhub master
|
||||||
elif [ "$TRAVIS_BRANCH" == "develop" ]; then
|
|
||||||
login_to_dockerhub
|
|
||||||
deploy_on_dockerhub develop
|
|
||||||
elif [ ! -z "$TRAVIS_TAG" ]; then
|
elif [ ! -z "$TRAVIS_TAG" ]; then
|
||||||
login_to_dockerhub
|
login_to_dockerhub
|
||||||
deploy_on_dockerhub $TRAVIS_TAG
|
deploy_on_dockerhub $TRAVIS_TAG
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
let config;
|
let config;
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,8 @@ async function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main()
|
||||||
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
});
|
});
|
|
@ -3,6 +3,7 @@
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var ListSuites = require('./utils/ListSuites');
|
||||||
|
|
||||||
let suite;
|
let suite;
|
||||||
|
|
||||||
|
@ -10,52 +11,108 @@ program
|
||||||
.description('Run the tests for the current suite or the provided one.')
|
.description('Run the tests for the current suite or the provided one.')
|
||||||
.arguments('[suite]')
|
.arguments('[suite]')
|
||||||
.option('--headless', 'Run in headless mode.')
|
.option('--headless', 'Run in headless mode.')
|
||||||
|
.option('--forbid-only', 'Forbid only and pending filters.')
|
||||||
.action((suiteArg) => suite = suiteArg)
|
.action((suiteArg) => suite = suiteArg)
|
||||||
.parse(process.argv);
|
.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 ENVIRONMENT_FILENAME = '.suite'; // This file is created by the start command.
|
||||||
|
const suiteFileExists = fs.existsSync(ENVIRONMENT_FILENAME);
|
||||||
|
|
||||||
if (suite) {
|
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, ' +
|
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.');
|
'do not provide the suite argument. Otherwise, stop the current suite and run the command again.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log('Suite %s provided. Running test related to this suite against built version of Authelia.', suite);
|
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);
|
suite = fs.readFileSync(ENVIRONMENT_FILENAME);
|
||||||
console.log('Suite %s detected from dev env. Running test related to this suite.', suite);
|
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 {
|
else {
|
||||||
console.log('No suite provided therefore all suites will be tested.');
|
console.log('No suite provided therefore all suites will be tested.');
|
||||||
args.push('test/suites/**/test.ts');
|
runAllTests(suite)
|
||||||
}
|
.catch((err) => {
|
||||||
|
|
||||||
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',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
console.error(err);
|
||||||
return;
|
process.exit(1);
|
||||||
}
|
|
||||||
console.log(data.toString('utf-8'));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
process.exit(statusCode);
|
|
||||||
})
|
// 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...');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,18 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
export PATH=./scripts:$PATH
|
source bootstrap.sh
|
||||||
|
|
||||||
docker --version
|
docker --version
|
||||||
docker-compose --version
|
docker-compose --version
|
||||||
echo "node `node -v`"
|
echo "node `node -v`"
|
||||||
echo "npm `npm -v`"
|
echo "npm `npm -v`"
|
||||||
|
|
||||||
|
authelia-scripts bootstrap
|
||||||
|
|
||||||
docker-compose -f docker-compose.yml \
|
docker-compose -f docker-compose.yml \
|
||||||
-f example/compose/mongo/docker-compose.yml \
|
-f example/compose/mongo/docker-compose.yml \
|
||||||
-f example/compose/redis/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/nginx/portal/docker-compose.yml \
|
||||||
-f example/compose/smtp/docker-compose.yml \
|
-f example/compose/smtp/docker-compose.yml \
|
||||||
-f example/compose/httpbin/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 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) {
|
function sleep(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
@ -9,32 +11,64 @@ function sleep(ms: number) {
|
||||||
|
|
||||||
let teardownInProgress = false;
|
let teardownInProgress = false;
|
||||||
|
|
||||||
process.on('SIGINT', function() {
|
async function block() {
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
console.log('Setting up environment...');
|
|
||||||
return setup()
|
|
||||||
.then(async () => {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await sleep(10000);
|
await sleep(10000);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
async function blockOrRun(cmd: string | null) {
|
||||||
|
if (cmd) {
|
||||||
|
await exec(cmd);
|
||||||
|
} else {
|
||||||
|
await block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('SIGINT', function() {
|
||||||
|
if (teardownInProgress) return;
|
||||||
|
teardownInProgress = true;
|
||||||
|
|
||||||
|
stop()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch(() => 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
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) {
|
function exec(cmd) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const command = spawn(cmd, {
|
const command = spawn(cmd, {shell: true, env: process.env});
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
command.stdout.pipe(process.stdout);
|
command.stdout.pipe(process.stdout);
|
||||||
command.stderr.pipe(process.stderr);
|
command.stderr.pipe(process.stderr);
|
||||||
|
|
||||||
command.on('exit', function(statusCode) {
|
command.on('exit', function(statusCode) {
|
||||||
if (statusCode != 0) {
|
if (statusCode != 0) {
|
||||||
reject(new Error('Exited with status ' + statusCode));
|
reject(new Error('Command \'' + cmd + '\' has exited with status ' + statusCode + '.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
@ -112,7 +112,7 @@ export function post_start_validation(handler: IdentityValidable,
|
||||||
})
|
})
|
||||||
.then((token: string) => {
|
.then((token: string) => {
|
||||||
const host = req.get("Host");
|
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);
|
handler.destinationPath(), token);
|
||||||
vars.logger.info(req, "Notification sent to user \"%s\"",
|
vars.logger.info(req, "Notification sent to user \"%s\"",
|
||||||
identity.userid);
|
identity.userid);
|
||||||
|
|
|
@ -49,8 +49,7 @@ export default class Server {
|
||||||
return ServerVariablesInitializer.initialize(
|
return ServerVariablesInitializer.initialize(
|
||||||
config, this.globalLogger, this.requestLogger, deps)
|
config, this.globalLogger, this.requestLogger, deps)
|
||||||
.then(function (vars: ServerVariables) {
|
.then(function (vars: ServerVariables) {
|
||||||
Configurator.configure(config, app, vars, deps);
|
return Configurator.configure(config, app, vars, deps);
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,11 +45,9 @@ function MatchSubject(subject: Subject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Authorizer implements IAuthorizer {
|
export class Authorizer implements IAuthorizer {
|
||||||
private logger: Winston;
|
|
||||||
private readonly configuration: ACLConfiguration;
|
private readonly configuration: ACLConfiguration;
|
||||||
|
|
||||||
constructor(configuration: ACLConfiguration, logger_: Winston) {
|
constructor(configuration: ACLConfiguration, logger_: Winston) {
|
||||||
this.logger = logger_;
|
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { Configuration } from "./schema/Configuration";
|
||||||
import { GlobalDependencies } from "../../../types/Dependencies";
|
import { GlobalDependencies } from "../../../types/Dependencies";
|
||||||
|
|
||||||
import ExpressSession = require("express-session");
|
import ExpressSession = require("express-session");
|
||||||
import ConnectRedis = require("connect-redis");
|
|
||||||
import Sinon = require("sinon");
|
import Sinon = require("sinon");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
@ -128,22 +127,15 @@ describe("configuration/SessionConfigurationBuilder", function () {
|
||||||
password: "authelia_pass"
|
password: "authelia_pass"
|
||||||
};
|
};
|
||||||
const RedisStoreMock = Sinon.spy();
|
const RedisStoreMock = Sinon.spy();
|
||||||
const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
|
deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock);
|
||||||
const createClientStub = Sinon.stub();
|
|
||||||
|
|
||||||
deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock) as any;
|
SessionConfigurationBuilder.build(configuration, deps);
|
||||||
deps.Redis = {
|
|
||||||
createClient: createClientStub
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
createClientStub.returns(redisClient);
|
Assert(RedisStoreMock.calledWith({
|
||||||
|
|
||||||
const options = SessionConfigurationBuilder.build(configuration, deps);
|
|
||||||
|
|
||||||
Assert(createClientStub.calledWith({
|
|
||||||
host: "redis.example.com",
|
host: "redis.example.com",
|
||||||
port: 6379,
|
port: 6379,
|
||||||
password: "authelia_pass"
|
pass: "authelia_pass",
|
||||||
|
logErrors: true,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,12 +1,8 @@
|
||||||
import ExpressSession = require("express-session");
|
import ExpressSession = require("express-session");
|
||||||
import Redis = require("redis");
|
|
||||||
|
|
||||||
import { Configuration } from "./schema/Configuration";
|
import { Configuration } from "./schema/Configuration";
|
||||||
import { GlobalDependencies } from "../../../types/Dependencies";
|
import { GlobalDependencies } from "../../../types/Dependencies";
|
||||||
import { RedisStoreOptions } from "connect-redis";
|
|
||||||
|
|
||||||
export class SessionConfigurationBuilder {
|
export class SessionConfigurationBuilder {
|
||||||
|
|
||||||
static build(configuration: Configuration, deps: GlobalDependencies): ExpressSession.SessionOptions {
|
static build(configuration: Configuration, deps: GlobalDependencies): ExpressSession.SessionOptions {
|
||||||
const sessionOptions: ExpressSession.SessionOptions = {
|
const sessionOptions: ExpressSession.SessionOptions = {
|
||||||
name: configuration.session.name,
|
name: configuration.session.name,
|
||||||
|
@ -22,30 +18,13 @@ export class SessionConfigurationBuilder {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (configuration.session.redis) {
|
if (configuration.session.redis) {
|
||||||
let redisOptions;
|
|
||||||
const options: Redis.ClientOpts = {
|
|
||||||
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,
|
|
||||||
logErrors: true
|
|
||||||
};
|
|
||||||
|
|
||||||
if (redisOptions) {
|
|
||||||
const RedisStore = deps.ConnectRedis(deps.session);
|
const RedisStore = deps.ConnectRedis(deps.session);
|
||||||
sessionOptions.store = new RedisStore(redisOptions);
|
sessionOptions.store = new RedisStore({
|
||||||
}
|
host: configuration.session.redis.host,
|
||||||
|
port: configuration.session.redis.port,
|
||||||
|
pass: configuration.session.redis.password,
|
||||||
|
logErrors: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return sessionOptions;
|
return sessionOptions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
import Sinon = require("sinon");
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import FirstFactorPost = require("./post");
|
import FirstFactorPost = require("./post");
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { URLDecomposer } from "../..//utils/URLDecomposer";
|
||||||
import { Object } from "../../../lib/authorization/Object";
|
import { Object } from "../../../lib/authorization/Object";
|
||||||
import { Subject } from "../../../lib/authorization/Subject";
|
import { Subject } from "../../../lib/authorization/Subject";
|
||||||
import AuthenticationError from "../../../lib/authentication/AuthenticationError";
|
import AuthenticationError from "../../../lib/authentication/AuthenticationError";
|
||||||
|
import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe";
|
||||||
|
import * as URLParse from "url-parse";
|
||||||
|
|
||||||
export default function (vars: ServerVariables) {
|
export default function (vars: ServerVariables) {
|
||||||
return function (req: express.Request, res: express.Response)
|
return function (req: express.Request, res: express.Response)
|
||||||
|
@ -61,7 +63,7 @@ export default function (vars: ServerVariables) {
|
||||||
vars.regulator.mark(username, true);
|
vars.regulator.mark(username, true);
|
||||||
})
|
})
|
||||||
.then(function() {
|
.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) {
|
if (!targetUrl) {
|
||||||
res.status(204);
|
res.status(204);
|
||||||
|
@ -83,10 +85,13 @@ export default function (vars: ServerVariables) {
|
||||||
|
|
||||||
const authorizationLevel = vars.authorizer.authorization(resObject, subject);
|
const authorizationLevel = vars.authorizer.authorization(resObject, subject);
|
||||||
if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
|
if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
|
||||||
res.json({
|
if (IsRedirectionSafe(vars, authSession, new URLParse(targetUrl))) {
|
||||||
redirect: targetUrl
|
res.json({redirect: targetUrl});
|
||||||
});
|
|
||||||
return BluebirdPromise.resolve();
|
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 express = require("express");
|
||||||
|
import * as URLParse from "url-parse";
|
||||||
|
import * as ObjectPath from "object-path";
|
||||||
import { ServerVariables } from "../../ServerVariables";
|
import { ServerVariables } from "../../ServerVariables";
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import ErrorReplies = require("../../ErrorReplies");
|
import ErrorReplies = require("../../ErrorReplies");
|
||||||
import UserMessages = require("../../../../../shared/UserMessages");
|
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) {
|
export default function (vars: ServerVariables) {
|
||||||
return function (req: express.Request, res: express.Response)
|
return function (req: express.Request, res: express.Response)
|
||||||
: BluebirdPromise<void> {
|
: BluebirdPromise<void> {
|
||||||
|
|
||||||
return new BluebirdPromise<void>(function (resolve, reject) {
|
return new BluebirdPromise<void>(function (resolve, reject) {
|
||||||
let redirectUrl: string = "/";
|
let redirectUrl: string = ObjectPath.get<Express.Request, string>(
|
||||||
if (vars.config.default_redirection_url) {
|
req, "headers.x-target-url", undefined);
|
||||||
|
|
||||||
|
if (!redirectUrl && vars.config.default_redirection_url) {
|
||||||
redirectUrl = vars.config.default_redirection_url;
|
redirectUrl = vars.config.default_redirection_url;
|
||||||
}
|
}
|
||||||
vars.logger.debug(req, "Request redirection to \"%s\".", redirectUrl);
|
|
||||||
res.json({
|
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||||
redirect: redirectUrl
|
if ((redirectUrl && !IsRedirectionSafe(vars, authSession, new URLParse(redirectUrl)))
|
||||||
} as RedirectionMessage);
|
|| !redirectUrl) {
|
||||||
|
res.status(204);
|
||||||
|
res.send();
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({redirect: redirectUrl});
|
||||||
return resolve();
|
return resolve();
|
||||||
})
|
})
|
||||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import BluebirdPromise = require("bluebird");
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
|
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
|
||||||
import U2f = require("u2f");
|
import U2f = require("u2f");
|
||||||
import redirect from "../../redirect";
|
import Redirect from "../../redirect";
|
||||||
import ErrorReplies = require("../../../../ErrorReplies");
|
import ErrorReplies = require("../../../../ErrorReplies");
|
||||||
import { ServerVariables } from "../../../../ServerVariables";
|
import { ServerVariables } from "../../../../ServerVariables";
|
||||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||||
|
@ -41,7 +41,7 @@ export default function (vars: ServerVariables) {
|
||||||
return BluebirdPromise.reject(new Error("Error while signing"));
|
return BluebirdPromise.reject(new Error("Error while signing"));
|
||||||
vars.logger.info(req, "Successful authentication");
|
vars.logger.info(req, "Successful authentication");
|
||||||
authSession.authentication_level = Level.TWO_FACTOR;
|
authSession.authentication_level = Level.TWO_FACTOR;
|
||||||
redirect(vars)(req, res);
|
Redirect(vars)(req, res);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||||
|
|
|
@ -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.disable(X_POWERED_BY);
|
||||||
app.enable(TRUST_PROXY);
|
app.enable(TRUST_PROXY);
|
||||||
app.use(Helmet());
|
app.use(Helmet());
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
if (!req.session) {
|
||||||
|
return next(new Error("No session available."));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
RestApi.setup(app, vars);
|
RestApi.setup(app, vars);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import Express = require("express");
|
||||||
import FirstFactorPost = require("../routes/firstfactor/post");
|
import FirstFactorPost = require("../routes/firstfactor/post");
|
||||||
import LogoutPost from "../routes/logout/post";
|
import LogoutPost from "../routes/logout/post";
|
||||||
import StateGet from "../routes/state/get";
|
import StateGet from "../routes/state/get";
|
||||||
import RedirectPost from "../routes/redirect/post";
|
|
||||||
import VerifyGet = require("../routes/verify/get");
|
import VerifyGet = require("../routes/verify/get");
|
||||||
import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
|
import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
|
||||||
|
|
||||||
|
@ -87,7 +86,6 @@ function setupResetPassword(app: Express.Application, vars: ServerVariables) {
|
||||||
export class RestApi {
|
export class RestApi {
|
||||||
static setup(app: Express.Application, vars: ServerVariables): void {
|
static setup(app: Express.Application, vars: ServerVariables): void {
|
||||||
app.get(Endpoints.STATE_GET, StateGet(vars));
|
app.get(Endpoints.STATE_GET, StateGet(vars));
|
||||||
app.post(Endpoints.REDIRECT_POST, RedirectPost(vars));
|
|
||||||
|
|
||||||
app.post(Endpoints.LOGOUT_POST, LogoutPost(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/");
|
const domain1 = DomainExtractor.fromUrl("https://login.example.com:8080/?rd=https://public.example.com:8080/");
|
||||||
Assert.equal(domain1, "login.example.com");
|
Assert.equal(domain1, "login.example.com");
|
||||||
|
|
||||||
const domain2 = DomainExtractor.fromUrl("https://single_factor.example.com:8080/secret.html");
|
const domain2 = DomainExtractor.fromUrl("https://singlefactor.example.com:8080/secret.html");
|
||||||
Assert.equal(domain2, "single_factor.example.com");
|
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