From 35f7f21c921ddfe00f69b6f4cc798f4e327efc99 Mon Sep 17 00:00:00 2001 From: djenriquez Date: Sun, 6 Nov 2016 00:54:13 -0700 Subject: [PATCH 1/6] Add dockerfile --- Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f9cf33d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:slim + +MAINTAINER Team Lecretius + +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 From fcf0c35768ddab7298d5345888cee7ae7d67b0af Mon Sep 17 00:00:00 2001 From: djenriquez Date: Sun, 6 Nov 2016 00:54:44 -0700 Subject: [PATCH 2/6] Add request to vault API, use accessToken --- app/App.jsx | 6 +++--- app/components/Login/Login.jsx | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) 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/Login/Login.jsx b/app/components/Login/Login.jsx index 0de1fc5..5f84fc4 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) { @@ -40,12 +41,30 @@ 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( + `${window.localStorage.getItem("vaultUrl")}/v1/auth/github/login`, + { "token": this.state.authToken }, + { headers: {'Access-Control-Allow-Originh': '*', 'Content-Type': 'text/plain'}} + ) + .then((data) => { + let accessToken = _.get(data, 'auth.client_token'); + if(accessToken) { + window.localStorage.setItem("vaultAccessToken",accessToken); + console.log(`Fetched token: ${accessToken}`); + window.location.href = '/'; + } else { + //No access token returned, error + } + }) + .catch(() => { + //something went wrong + }); + } } @@ -110,3 +129,4 @@ export default class Login extends React.Component { ); } } + From bea0715984f9b9629b764bb071ec12108ac507c3 Mon Sep 17 00:00:00 2001 From: djenriquez Date: Sun, 6 Nov 2016 00:55:22 -0700 Subject: [PATCH 3/6] Attempt to add express --- package.json | 5 ++++- server/public/index.html | 14 ++++++++++++++ server/server.js | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 server/public/index.html create mode 100644 server/server.js diff --git a/package.json b/package.json index 71e9a81..79ba78f 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,15 @@ }, "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", "react-dom": "^15.3.2", "react-router": "^3.0.0", - "react-tap-event-plugin": "^1.0.0" + "react-tap-event-plugin": "^1.0.0", + "serve-favicon": "^2.3.0" } } diff --git a/server/public/index.html b/server/public/index.html new file mode 100644 index 0000000..293de17 --- /dev/null +++ b/server/public/index.html @@ -0,0 +1,14 @@ + + + Vault UI + + + + + + + +
+ + + \ No newline at end of file diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..3061a4d --- /dev/null +++ b/server/server.js @@ -0,0 +1,26 @@ +import express from 'express'; +import bodyParser from 'body-parser'; +import favicon from 'serve-favicon'; + +const PORT = 8000; + +var app = express(); + +app.use('/static', express.static('dist')); +app.use(express.static('./src/server/public')); + +// 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}`); +}); \ No newline at end of file From e2d1ce70766878880c9b6ee94089e191dba37ec4 Mon Sep 17 00:00:00 2001 From: djenriquez Date: Sun, 6 Nov 2016 00:57:16 -0700 Subject: [PATCH 4/6] Spellcheck --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f9cf33d..b1ddfe9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:slim -MAINTAINER Team Lecretius +MAINTAINER Team Lucretius ADD package.json /tmp/package.json RUN cd /tmp && npm install From 1ae4d0963c1afdd845606c2e0537161d4b317bd6 Mon Sep 17 00:00:00 2001 From: djenriquez Date: Sun, 6 Nov 2016 01:07:18 -0700 Subject: [PATCH 5/6] Clean up --- server/server.js => server.js | 7 +++++-- server/public/index.html | 14 -------------- 2 files changed, 5 insertions(+), 16 deletions(-) rename server/server.js => server.js (83%) delete mode 100644 server/public/index.html diff --git a/server/server.js b/server.js similarity index 83% rename from server/server.js rename to server.js index 3061a4d..b9721f8 100644 --- a/server/server.js +++ b/server.js @@ -6,8 +6,7 @@ const PORT = 8000; var app = express(); -app.use('/static', express.static('dist')); -app.use(express.static('./src/server/public')); +app.use('/assets', express.static('dist')); // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })); @@ -23,4 +22,8 @@ app.use((req, res, next) => { app.listen(PORT, () => { console.log(`Vault UI listening on: ${PORT}`); +}); + +app.get('*', function(req, res) { + res.render('index.html'); }); \ No newline at end of file diff --git a/server/public/index.html b/server/public/index.html deleted file mode 100644 index 293de17..0000000 --- a/server/public/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - Vault UI - - - - - - - -
- - - \ No newline at end of file From 4e8ec5522679631a8b2a636065e919052167df09 Mon Sep 17 00:00:00 2001 From: DJ Enriquez Date: Mon, 7 Nov 2016 17:25:21 -0800 Subject: [PATCH 6/6] Hooking into Vault - Using express backend server to make calls to the Vault API to bypass CORs - Adding login, list secrets, and get secrets calls --- app/components/Home/Home.jsx | 99 +++++++++++++--------------------- app/components/Login/Login.jsx | 18 ++++--- package.json | 3 +- server.js | 28 ++++++++-- src/routeHandler.js | 93 ++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 75 deletions(-) create mode 100644 src/routeHandler.js diff --git a/app/components/Home/Home.jsx b/app/components/Home/Home.jsx index 19e656e..7118906 100644 --- a/app/components/Home/Home.jsx +++ b/app/components/Home/Home.jsx @@ -6,17 +6,18 @@ import Secrets from '../Secrets/Secrets.jsx'; import Health from '../Health/Health.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() { @@ -38,71 +39,45 @@ 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 + return default: return (
@@ -115,7 +90,7 @@ export default class Home extends React.Component { } } - render () { + render() { let messageStyle = { backgroundColor: green500 }; if (this.state.snackbarType == 'warn') { messageStyle = { backgroundColor: yellow500 }; @@ -130,10 +105,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 5f84fc4..c8c712d 100644 --- a/app/components/Login/Login.jsx +++ b/app/components/Login/Login.jsx @@ -47,12 +47,17 @@ export default class Login extends React.Component { return; } axios.post( - `${window.localStorage.getItem("vaultUrl")}/v1/auth/github/login`, - { "token": this.state.authToken }, - { headers: {'Access-Control-Allow-Originh': '*', 'Content-Type': 'text/plain'}} + '/login', + { "VaultUrl": window.localStorage.getItem("vaultUrl"), "Creds": {"Type": "GITHUB", "Token": this.state.authToken} } ) - .then((data) => { - let accessToken = _.get(data, 'auth.client_token'); + .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}`); @@ -61,7 +66,8 @@ export default class Login extends React.Component { //No access token returned, error } }) - .catch(() => { + .catch((err) => { + console.error(err.stack); //something went wrong }); diff --git a/package.json b/package.json index 79ba78f..c05096e 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "react": "^15.3.2", "react-dom": "^15.3.2", "react-router": "^3.0.0", - "react-tap-event-plugin": "^1.0.0", - "serve-favicon": "^2.3.0" + "react-tap-event-plugin": "^1.0.0" } } diff --git a/server.js b/server.js index b9721f8..c582365 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,14 @@ -import express from 'express'; -import bodyParser from 'body-parser'; -import favicon from 'serve-favicon'; +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 @@ -24,6 +27,21 @@ 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.render('index.html'); + 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