Skip to content
This repository has been archived by the owner on Mar 27, 2019. It is now read-only.

Commit

Permalink
Support for custom mountpoint on login (#60)
Browse files Browse the repository at this point in the history
* eslint fixes. use callVaultApi instead of axios directly
* Support for cunfigurable mountpoint on authentication
* Add VAULT_AUTH_BACKEND_PATH env var and document
* Hide control inside Card
  • Loading branch information
Matteo Sessa authored Mar 1, 2017
1 parent 5691a00 commit 984c97b
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 69 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ Configuration is accessed by clicking on the configuration cog on the login page

### Vault Endpoint
Users can enter in the full endpoint to Vault, including scheme. When running the docker image, it is possible to
set the environment variables `VAULT_URL_DEFAULT` and `VAULT_AUTH_DEFAULT`.
`VAULT_URL_DEFAULT` will set the default endpoint for users without needing them to enter one in the UI.
`VAULT_AUTH_DEFAULT` will set the default authentication method type. Supported values are: GITHUB, TOKEN, LDAP, USERNAMEPASSWORD
set the following environment variables to pre-configure authentication settings:
- `VAULT_URL_DEFAULT` will set the default vault endpoint.
- `VAULT_AUTH_DEFAULT` will set the default authentication method type. See below for supported authentication methods.
- `VAULT_AUTH_BACKEND_PATH` will set the default backend path. Useful when multiple backends of the same type are mounted on the vault file system.

This defaults can be overridden if the user fills out the endpoint and auth method manually.

## Authentication
Currently supported authentication backends:
- [GitHub](https://www.vaultproject.io/docs/auth/github.html)
- [Username & Password](https://www.vaultproject.io/docs/auth/userpass.html)
- [LDAP](https://www.vaultproject.io/docs/auth/ldap.html)
- [Tokens](https://www.vaultproject.io/docs/auth/token.html)
Currently supported authentication methods:
- `GITHUB` : When using the [GitHub](https://www.vaultproject.io/docs/auth/github.html) backend
- `USERNAMEPASSWORD` : When using the [Username & Password](https://www.vaultproject.io/docs/auth/userpass.html) or [RADIUS](https://www.vaultproject.io/docs/auth/radius.html) backends
- `LDAP` : When using the [LDAP](https://www.vaultproject.io/docs/auth/ldap.html) backend
- `TOKEN` : When using the [Tokens](https://www.vaultproject.io/docs/auth/token.html) backend

### Token authentication by header (SSO)
In some cases, users might want to use middleware to authenticate into Vault-UI for purposes like SSO. In this case, the `VAULT_SUPPLIED_TOKEN_HEADER` may be populated with the name of the header that contains a token to be used for authentication.
Expand Down
170 changes: 110 additions & 60 deletions app/components/Login/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import Dialog from 'material-ui/Dialog';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import FlatButton from 'material-ui/FlatButton';
import Snackbar from 'material-ui/Snackbar';
import { browserHistory } from 'react-router';
import axios from 'axios';
import { Card, CardHeader, CardText } from 'material-ui/Card';
import _ from 'lodash';
import { callVaultApi } from '../shared/VaultUtils.jsx'

Expand All @@ -32,36 +30,42 @@ export default class Login extends React.Component {
password: "",
loginMethodType: this.getVaultAuthMethod(),
tmpLoginMethodType: this.getVaultAuthMethod(),
authBackendPath: this.getAuthBackendPath(),
tmpAuthBackendPath: '',
settingsChanged: false,
};

_.bindAll(
this,
'validateAuthToken',
'getAuthBackendPath',
'validateToken',
'getDefaultBackendPathForMethod',
'submitSettings',
'renderSettingsDialog',
'renderSelectedLoginOption',
'validateUsernamePassword',
'checkSettings',
'login'
)

// If a token was supplied in the window.suppliedAuthToken variable, then simulate a login
if (window.suppliedAuthToken && this.state.vaultUrl) {
this.state.loginMethodType = 'TOKEN';
this.state.authToken = window.suppliedAuthToken;
this.validateToken({ keyCode: 13 });
}

}

componentDidMount() {
this.setState({ show: true });
if (!this.state.vaultUrl) {
// If a token was supplied in the window.suppliedAuthToken variable, then simulate a login
if (window.suppliedAuthToken && this.state.vaultUrl) {
this.setState({
openSettings: true
loginMethodType: 'TOKEN',
authToken: window.suppliedAuthToken
}, () => {
this.validateToken({ keyCode: 13 });
});
} else {
this.setState({ show: true });
if (!this.state.vaultUrl) {
this.setState({
openSettings: true
});
}
}
}

Expand All @@ -79,49 +83,60 @@ export default class Login extends React.Component {
return window.defaultAuth;
}

getDefaultBackendPathForMethod(type) {
switch (type) {
case 'TOKEN':
return 'token'
case 'GITHUB':
return 'github'
case 'LDAP':
return 'ldap'
case 'USERNAMEPASSWORD':
return 'userpass'
default:
return ''
}
}

getAuthBackendPath() {
if (window.localStorage.getItem("loginBackendPath"))
return window.localStorage.getItem("loginBackendPath");
else if (window.defaultBackendPath)
return window.defaultBackendPath;
else
return this.getDefaultBackendPathForMethod(this.getVaultAuthMethod())
}

login() {
let method = '';
let uri = '';
let query = null;
let data = null;
let headers = null;

switch (this.state.loginMethodType) {
case "TOKEN":
method = 'get';
uri = 'auth/token/lookup-self';
headers = { "X-Vault-Token": this.state.authToken };
uri = `auth/${this.state.authBackendPath}/lookup-self`;
break;
case "GITHUB":
method = 'post';
uri = `auth/github/login`;
uri = `auth/${this.state.authBackendPath}/login`;
data = { token: this.state.authToken };
break;
case "LDAP":
method = 'post';
uri = `auth/ldap/login/${this.state.username}`;
uri = `auth/${this.state.authBackendPath}/login/${this.state.username}`;
data = { password: this.state.password };
break;
case "USERNAMEPASSWORD":
method = 'post';
uri = `auth/userpass/login/${this.state.username}`;
uri = `auth/${this.state.authBackendPath}/login/${this.state.username}`;
data = { password: this.state.password };
break;
default:
throw new Error(`Login method type: '${this.state.loginMethodType}' is not supported`);
}

let instance = axios.create({
baseURL: '/v1/'
});

instance.request({
url: uri,
method: method,
data: data,
params: { "vaultaddr": this.state.vaultUrl },
headers: headers
})
callVaultApi(method, uri, null, data, null, this.state.loginMethodType == 'TOKEN' ? this.state.authToken : null, this.state.vaultUrl)
.then((resp) => {
//console.log(resp);
if (this.state.loginMethodType == "TOKEN") {
Expand Down Expand Up @@ -201,6 +216,7 @@ export default class Login extends React.Component {
window.localStorage.setItem("vaultAccessToken", accessToken);
window.localStorage.setItem('vaultUrl', this.getVaultUrl());
window.localStorage.setItem('loginMethodType', this.getVaultAuthMethod());
window.localStorage.setItem('loginBackendPath', this.getAuthBackendPath());
if (this.props.location.query.returnto && this.props.location.query.returnto.indexOf('/') === 0)
window.location.href = this.props.location.query.returnto;
else
Expand All @@ -210,20 +226,24 @@ export default class Login extends React.Component {
}
}

submitSettings(e) {
submitSettings() {
if (this.state.settingsChanged) {
if (!this.state.tmpVaultUrl) {
this.setState({ errorMessage: 'Please enter a Vault URL' });
}
else if (!this.state.tmpLoginMethodType) {
this.setState({ errorMessage: 'Please select an authentication backend' });
} else if (!this.state.authBackendPath) {
this.setState({ errorMessage: 'Please select a valid path for the authentication backend' })
} else {
window.localStorage.setItem("vaultUrl", this.state.tmpVaultUrl);
window.localStorage.setItem("loginMethodType", this.state.tmpLoginMethodType);
window.localStorage.setItem('loginBackendPath', this.state.tmpAuthBackendPath);
this.setState({
errorMessage: '',
vaultUrl: this.state.tmpVaultUrl,
loginMethodType: this.state.tmpLoginMethodType,
authBackendPath: this.state.tmpAuthBackendPath,
openSettings: false
});
}
Expand All @@ -232,7 +252,7 @@ export default class Login extends React.Component {
}
}

checkSettings(e) {
checkSettings() {
this.setState({
errorMessage: this.state.vaultUrl ? '' : 'No Vault URL specified. Click the gear to edit your Vault URL.',
openSettings: false
Expand All @@ -248,33 +268,55 @@ export default class Login extends React.Component {
]

let handleSelectFieldChange = (e, i, v) => {
this.setState({ tmpLoginMethodType: v, settingsChanged: true });
this.setState({ tmpLoginMethodType: v, tmpAuthBackendPath: this.getDefaultBackendPathForMethod(v), settingsChanged: true });
}

return (
<Dialog
title="Settings"
title="Authentication Settings"
actions={actions}
autoScrollBodyContent={true}
modal={true}
open={this.state.openSettings}
>
<TextField
id="vaultUrl"
fullWidth={true}
className="col-xs-12"
defaultValue={this.state.vaultUrl}
onChange={(e, v) => this.setState({ tmpVaultUrl: v, settingsChanged: true })}
>
<div>
<TextField
id="vaultUrl"
fullWidth={true}
className="col-xs-12"
floatingLabelFixed={true}
floatingLabelText="Vault Server URL"
defaultValue={this.state.vaultUrl}
onChange={(e, v) => this.setState({ tmpVaultUrl: v, settingsChanged: true })}
/>
<SelectField
style={{ paddingLeft: 8 }}
value={this.state.tmpLoginMethodType}
onChange={handleSelectFieldChange.bind(this)}
floatingLabelText="Login Method">
<MenuItem value={"GITHUB"} primaryText="Github" />
<MenuItem value={"TOKEN"} primaryText="Token" />
<MenuItem value={"LDAP"} primaryText="LDAP" />
<MenuItem value={"USERNAMEPASSWORD"} primaryText="Username & Password" />
</SelectField>
</div>
<div>
<SelectField
style={{ paddingLeft: 8 }}
value={this.state.tmpLoginMethodType}
onChange={handleSelectFieldChange.bind(this)}
floatingLabelText="Login Method">
<MenuItem value={"GITHUB"} primaryText="Github" />
<MenuItem value={"TOKEN"} primaryText="Token" />
<MenuItem value={"LDAP"} primaryText="LDAP" />
<MenuItem value={"USERNAMEPASSWORD"} primaryText="Username & Password" />
</SelectField>
</div>
<div>
<Card initiallyExpanded={false}>
<CardHeader title="Advanced Options" actAsExpander={true} showExpandableButton={true} />
<CardText expandable={true}>
<TextField
style={{ paddingLeft: 8 }}
id="backendPath"
floatingLabelFixed={true}
floatingLabelText="Auth backend path"
value={this.state.tmpAuthBackendPath}
onChange={(e, v) => this.setState({ tmpAuthBackendPath: v, settingsChanged: true })}
/>
</CardText>
</Card>
</div>
<div className={styles.error}>{this.state.errorMessage}</div>
</Dialog>
)
Expand All @@ -291,7 +333,7 @@ export default class Login extends React.Component {
hintText="Enter Github token"
onKeyDown={this.validateAuthToken}
onChange={(e, v) => this.setState({ authToken: v })}
/>
/>
);
case "TOKEN":
return (
Expand All @@ -302,7 +344,7 @@ export default class Login extends React.Component {
hintText="Enter token"
onKeyDown={this.validateToken}
onChange={(e, v) => this.setState({ authToken: v })}
/>
/>
);
case "LDAP":
return (
Expand All @@ -313,15 +355,15 @@ export default class Login extends React.Component {
hintText="Enter LDAP username"
onKeyDown={this.validateUsernamePassword}
onChange={(e, v) => this.setState({ username: v })}
/>
/>
<TextField
fullWidth={true}
className="col-xs-12"
type="password"
hintText="Enter LDAP password"
onKeyDown={this.validateUsernamePassword}
onChange={(e, v) => this.setState({ password: v })}
/>
/>
<div className={styles.error}>{this.state.errorMessage}</div>
</div>
);
Expand All @@ -334,15 +376,15 @@ export default class Login extends React.Component {
hintText="Enter username"
onKeyDown={this.validateUsernamePassword}
onChange={(e, v) => this.setState({ username: v })}
/>
/>
<TextField
fullWidth={true}
className="col-xs-12"
type="password"
hintText="Enter password"
onKeyDown={this.validateUsernamePassword}
onChange={(e, v) => this.setState({ password: v })}
/>
/>
<div className={styles.error}>{this.state.errorMessage}</div>
</div>
)
Expand All @@ -360,7 +402,15 @@ export default class Login extends React.Component {
{this.renderSelectedLoginOption()}
</div>
<div className="col-xs-1">
<IconButton tooltip="Settings" onTouchTap={() => this.setState({ openSettings: true, tmpLoginMethodType: this.state.loginMethodType, tmpVaultUrl: this.state.vaultUrl })}>
<IconButton tooltip="Settings" onTouchTap={() => {
this.setState({
errorMessage: '',
openSettings: true,
tmpLoginMethodType: this.state.loginMethodType,
tmpVaultUrl: this.state.vaultUrl,
tmpAuthBackendPath: this.state.authBackendPath
})
}}>
<Settings />
</IconButton>
</div>
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<script>window.defaultUrl = "{{defaultUrl}}"</script>
<script>window.defaultAuth = "{{defaultAuth}}"</script>
<script>window.suppliedAuthToken = "{{suppliedAuthToken}}"</script>
<script>window.defaultBackendPath = "{{defaultBackendPath}}"</script>
<script src="/assets/bundle.js"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions run-docker-compose-dev
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ exec_in_vault() {
exec_in_vault vault auth "$(docker-compose logs vault | grep 'Root Token:' | tail -n 1 | awk '{ print $NF }')"
exec_in_vault vault status
exec_in_vault vault auth-enable userpass
exec_in_vault vault auth-enable -path=userpass2 userpass
exec_in_vault vault auth-enable github
exec_in_vault vault auth-enable radius
exec_in_vault vault auth-enable -path=awsaccount1 aws-ec2
exec_in_vault vault policy-write admin /app/admin.hcl
exec_in_vault vault write auth/userpass/users/test password=test policies=admin
exec_in_vault vault write auth/userpass2/users/john password=doe policies=admin
exec_in_vault vault write auth/userpass/users/lame password=lame policies=default
exec_in_vault vault write auth/radius/users/test password=test policies=admin
exec_in_vault vault write secret/test somekey=somedata
Expand Down
4 changes: 3 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var PORT = 8000;
var VAULT_URL_DEFAULT = process.env.VAULT_URL_DEFAULT || "";
var VAULT_AUTH_DEFAULT = process.env.VAULT_AUTH_DEFAULT || "GITHUB";
var VAULT_SUPPLIED_TOKEN_HEADER = process.env.VAULT_SUPPLIED_TOKEN_HEADER
var VAULT_AUTH_BACKEND_PATH = process.env.VAULT_AUTH_BACKEND_PATH

var app = express();
app.set('view engine', 'html');
Expand Down Expand Up @@ -52,6 +53,7 @@ app.get('*', function (req, res) {
res.render(path.join(__dirname, '/index.html'),{
defaultUrl: VAULT_URL_DEFAULT,
defaultAuth: VAULT_AUTH_DEFAULT,
suppliedAuthToken: VAULT_SUPPLIED_TOKEN_HEADER ? req.header(VAULT_SUPPLIED_TOKEN_HEADER) : ""
suppliedAuthToken: VAULT_SUPPLIED_TOKEN_HEADER ? req.header(VAULT_SUPPLIED_TOKEN_HEADER) : "",
defaultBackendPath: VAULT_AUTH_BACKEND_PATH
});
});

0 comments on commit 984c97b

Please sign in to comment.