diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b1ddfe9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:slim + +MAINTAINER Team Lucretius + +ADD package.json /tmp/package.json +RUN cd /tmp && npm install +RUN mkdir -p /app/ && cp -a /tmp/node_modules /app/ + +RUN npm install --silent -g webpack + +ADD . /app +WORKDIR /app + +RUN webpack && npm run build + +EXPOSE 8000 + +CMD ["npm", "run", "serve"] \ No newline at end of file diff --git a/app/App.jsx b/app/App.jsx index abd4b92..7bb8d7c 100644 --- a/app/App.jsx +++ b/app/App.jsx @@ -25,8 +25,8 @@ injectTapEventPlugin(); window.CustomEvent = CustomEvent; })(); -const checkAuthToken = (nextState, replace, callback) => { - let vaultAuthToken = window.localStorage.getItem('vaultAuthenticationToken'); +const checkAccessToken = (nextState, replace, callback) => { + let vaultAuthToken = window.localStorage.getItem('vaultAccessToken'); if (!vaultAuthToken) { replace(`/login`) } @@ -41,7 +41,7 @@ ReactDOM.render(( - + diff --git a/app/components/Home/Home.jsx b/app/components/Home/Home.jsx index f2154e4..cac180c 100644 --- a/app/components/Home/Home.jsx +++ b/app/components/Home/Home.jsx @@ -7,17 +7,18 @@ import Health from '../Health/Health.jsx'; import Settings from '../Settings/Settings.jsx'; import Snackbar from 'material-ui/Snackbar'; import { green500, red500, yellow500 } from 'material-ui/styles/colors.js' +import axios from 'axios'; export default class Home extends React.Component { constructor(props) { - super(props); - this.renderContent = this.renderContent.bind(this); - this.state = { - secrets: [], - snackbarMessage: '', - snackbarOpen: false, - snackbarType: 'OK' - } + super(props); + this.renderContent = this.renderContent.bind(this); + this.state = { + secrets: [], + snackbarMessage: '', + snackbarOpen: false, + snackbarType: 'OK' + } } componentDidMount() { @@ -42,69 +43,43 @@ export default class Home extends React.Component { document.addEventListener("addedKey", (e) => { let secrets = this.state.secrets; - secrets.push({ key: e.detail.key, value: e.detail.value}); + secrets.push({ key: e.detail.key, value: e.detail.value }); this.setState({ secrets: secrets }); }); document.addEventListener("deleteKey", (e) => { - let newSecrets = _.filter(this.state.secrets, x => x.key !== e.detail.key); + let newSecrets = _.filter(this.state.secrets, x => x.key !== e.detail.key); this.setState({ secrets: newSecrets }); }); - this.setState({ - secrets: [ - { - key: 'fake_aws_secret', - value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - }, - { - key: 'fake_aws_id', - value: 'AKIAIOSFODNN7EXAMPLE' - }, - { - key: 'key3', - value: 'val3' - }, - { - key: 'key4', - value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - }, - { - key: 'key5', - value: 'AKIAIOSFODNN7EXAMPLE' - }, - { - key: 'key6', - value: 'val3' - }, - { - key: 'key7', - value: 'val3' - }, - { - key: 'key8', - value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - }, - { - key: 'key9', - value: 'AKIAIOSFODNN7EXAMPLE' - }, - { - key: 'key10', - value: 'val3' - } - ] - }) + var keys = []; + axios.get(`/listsecrets?vaultaddr=${encodeURI(window.localStorage.getItem("vaultUrl"))}&token=${encodeURI(window.localStorage.getItem("vaultAccessToken"))}`) + .then((resp) => { + console.log(resp.data.data); + keys = resp.data.data.keys; + + + var secrets = _.map(keys, (key) => { + return { + key: key, + value: "" + } + }); + + this.setState({ + secrets: secrets + }); + }) } renderContent() { - switch(this.props.location.pathname) { + switch (this.props.location.pathname) { case '/secrets': - return + return case '/health': return case '/settings': @@ -121,7 +96,7 @@ export default class Home extends React.Component { } } - render () { + render() { let messageStyle = { backgroundColor: green500 }; if (this.state.snackbarType == 'warn') { messageStyle = { backgroundColor: yellow500 }; @@ -136,10 +111,10 @@ export default class Home extends React.Component { open={this.state.snackbarOpen} message={this.state.snackbarMessage} autoHideDuration={2000} - onRequestClose={() => this.setState({snackbarOpen: false})} - /> -
- + onRequestClose={() => this.setState({ snackbarOpen: false })} + /> +
+
{this.renderContent()}
diff --git a/app/components/Login/Login.jsx b/app/components/Login/Login.jsx index c034005..a625938 100644 --- a/app/components/Login/Login.jsx +++ b/app/components/Login/Login.jsx @@ -6,6 +6,7 @@ import Settings from 'material-ui/svg-icons/action/settings'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import { browserHistory } from 'react-router'; +import axios from 'axios'; export default class Login extends React.Component { constructor(props) { @@ -41,12 +42,36 @@ export default class Login extends React.Component { validateAuthToken(e) { if (e.keyCode === 13) { + console.log(`Validating auth token: ${this.state.authToken}`); if (!window.localStorage.getItem("vaultUrl")) { this.setState({errorMessage: "No Vault url specified. Click the gear to edit your Vault url"}); return; } - window.localStorage.setItem("vaultAuthenticationToken",this.state.authToken); - window.location.href = '/'; + axios.post( + '/login', + { "VaultUrl": window.localStorage.getItem("vaultUrl"), "Creds": {"Type": "GITHUB", "Token": this.state.authToken} } + ) + .then((resp) => { +// { client_token: '145a495d-dc52-4539-1de8-94e819ba1317', +// accessor: '1275f43d-1287-7df2-d17a-6956181a5238', +// policies: [ 'default', 'insp-power-user' ], +// metadata: { org: 'Openmail', username: 'djenriquez' }, +// lease_duration: 3600, +// renewable: true } + let accessToken = _.get(resp, 'data.client_token'); + if(accessToken) { + window.localStorage.setItem("vaultAccessToken",accessToken); + console.log(`Fetched token: ${accessToken}`); + window.location.href = '/'; + } else { + //No access token returned, error + } + }) + .catch((err) => { + console.error(err.stack); + //something went wrong + }); + } } @@ -111,3 +136,4 @@ export default class Login extends React.Component { ); } } + diff --git a/package.json b/package.json index 71e9a81..c05096e 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ }, "dependencies": { "axios": "^0.15.2", + "body-parser": "^1.15.2", "copy-to-clipboard": "^3.0.5", + "express": "^4.14.0", "lodash": "^4.16.6", "material-ui": "^0.16.1", "react": "^15.3.2", diff --git a/server.js b/server.js new file mode 100644 index 0000000..c582365 --- /dev/null +++ b/server.js @@ -0,0 +1,47 @@ +var express = require('express'); +var bodyParser = require('body-parser'); +var path = require('path'); +var axios = require('axios'); +var _ = require('lodash'); +var routeHandler = require('./src/routeHandler'); + +const PORT = 8000; + +var app = express(); +app.set('view engine','.html'); +app.use('/assets', express.static('dist')); + +// parse application/x-www-form-urlencoded +app.use(bodyParser.urlencoded({ extended: false })); + +// parse application/json +app.use(bodyParser.json()); + +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + next(); +}); + +app.listen(PORT, () => { + console.log(`Vault UI listening on: ${PORT}`); +}); + + +app.post('/login', (req,res) => { + routeHandler.login(req, res); +}); + +app.get('/listsecrets', (req, res) => { + routeHandler.listSecrets(req, res); +}); + +app.get('/secret', (req, res) => { + routeHandler.getSecret(req, res); +}) + +app.get('/') + +app.get('*', function(req, res) { + res.sendFile(path.join(__dirname,'/index.html')); +}); \ No newline at end of file diff --git a/src/routeHandler.js b/src/routeHandler.js new file mode 100644 index 0000000..853ec30 --- /dev/null +++ b/src/routeHandler.js @@ -0,0 +1,93 @@ +'use strict'; +var axios = require('axios'); +var _ = require('lodash'); + +/* Returned body + "auth": { + "renewable": true, + "lease_duration": 2764800, + "metadata": { + "username": "vishalnayak", + "org": "hashicorp" + }, + "policies": [ + "default", + "dev-policy" + ], + "accessor": "f93c4b2d-18b6-2b50-7a32-0fecf88237b8", + "client_token": "1977fceb-3bfa-6c71-4d1f-b64af98ac018" + } +*/ +var login = function (req, res) { + let creds = _.get(req, "body.Creds"); + + let endpoint = ''; + let body = {} + + switch (creds.Type.toLowerCase()) { + case 'github': + endpoint = '/v1/auth/github/login'; + body = { + token: creds.Token + }; + } + axios.post(`${_.get(req, "body.VaultUrl")}${endpoint}`, body) + .then((resp) => { + res.json(resp.data.auth); + }) + .catch((err) => { + console.error(err.stack); + }); +}; + +/* Returned body + { + "auth": null, + "data": { + "keys": ["foo", "foo/"] + }, + "lease_duration": 2764800, + "lease_id": "", + "renewable": false + } +*/ +var listSecrets = function (req, res) { + let endpoint = '/v1/secret?list=true'; + let vaultAddr = decodeURI(req.query['vaultaddr']); + let config = { headers : { 'X-Vault-Token': decodeURI(req.query['token']) } } + console.log(`${vaultAddr}${endpoint}`); + axios.get(`${vaultAddr}${endpoint}`, config) + .then((resp) => { + res.json(resp.data); + }) + .catch((err) => { + console.error(err.stack); + }); +} +/* Returned body + { + "foo": "bar" + } + Query params 'secret' and 'vaultaddr' must go through encodeURI() +*/ +var getSecret = function (req, res) { + let endpoint = `/v1/secret/${decodeURI(req.query['secret'])}`; + let vaultAddr = decodeURI(req.query['vaultaddr']); + let config = { headers : { 'X-Vault-Token': req.query['token'] } } + + axios.get(`${vaultAddr}${endpoint}`, config) + .then((resp) => { + res.json(resp.data.data); + }) + .catch((err) => { + console.error(err.stack); + }); +} + +module.exports = (function () { + return { + login: login, + listSecrets: listSecrets, + getSecret: getSecret + } +})(); \ No newline at end of file