From bb7f94d82e68822aa9d82c5d596608873fee95aa Mon Sep 17 00:00:00 2001 From: Tony Cai Date: Tue, 4 Jul 2017 18:55:35 -0700 Subject: [PATCH] Change authentication from cookies to token-based (#105) * Minor text fix * Frontend switched from cookie to token based authentication * Removed csrf from frontend, since cookies are no longer used * Removed csrf from backend --- frontend/client/components/layout/Navbar.vue | 14 +--- frontend/client/views/admin/Mounts.vue | 24 +++++- frontend/client/views/admin/Policies.vue | 20 +++-- frontend/client/views/admin/Requests.vue | 20 ++--- frontend/client/views/admin/Users.vue | 53 ++++++------ frontend/client/views/bulletinboard/index.vue | 8 +- frontend/client/views/login/index.vue | 66 +++++---------- frontend/client/views/secrets/index.vue | 14 ++-- frontend/client/views/tools/CreateToken.vue | 37 ++++----- frontend/client/views/tools/Dependencies.vue | 45 +++++++--- frontend/client/views/tools/Transit.vue | 16 ++-- frontend/client/views/tools/Wrapper.vue | 20 ++--- handlers/handlers.go | 83 ++++--------------- handlers/mount.go | 3 - handlers/policy.go | 10 --- handlers/secret.go | 3 - handlers/transit.go | 2 - handlers/user.go | 7 -- server.go | 13 --- 19 files changed, 192 insertions(+), 266 deletions(-) diff --git a/frontend/client/components/layout/Navbar.vue b/frontend/client/components/layout/Navbar.vue index 1c1b77c7..d54d762a 100644 --- a/frontend/client/components/layout/Navbar.vue +++ b/frontend/client/components/layout/Navbar.vue @@ -48,7 +48,6 @@ @@ -106,6 +105,7 @@ export default { }, mounted: function () { + // refresh current time every second, since time is not reactive setInterval(() => { this.now = moment() }, 1000) @@ -133,20 +133,10 @@ export default { }), tokenExpiresIn: function () { - if (this.session === null) { - return '' - } - if (this.session['token_expiry'] === 'never') { + if (this.session === null || this.session['token_expiry'] === 'never') { return '' } return this.now.to(moment(this.session['token_expiry'], 'ddd, h:mm:ss A MMMM Do YYYY')) - }, - - cookieExpiresIn: function () { - if (this.session === null) { - return '' - } - return this.now.to(moment(this.session['cookie_expiry'], 'ddd, h:mm:ss A MMMM Do YYYY')) } }, diff --git a/frontend/client/views/admin/Mounts.vue b/frontend/client/views/admin/Mounts.vue index a6132a3b..fc956691 100644 --- a/frontend/client/views/admin/Mounts.vue +++ b/frontend/client/views/admin/Mounts.vue @@ -94,8 +94,17 @@ export default { } }, + computed: { + session: function () { + return this.$store.getters.session + } + }, + mounted: function () { - this.$http.get('/api/mounts').then((response) => { + this.$http.get('/api/mounts', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }) + .then((response) => { this.mounts = [] this.csrf = response.headers['x-csrf-token'] let result = response.data.result @@ -117,7 +126,9 @@ export default { methods: { getMountConfig: function (index) { this.selectedIndex = index - this.$http.get('/api/mounts/' + this.mounts[index].path.slice(0, -1)) + this.$http.get('/api/mounts/' + this.mounts[index].path.slice(0, -1), { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }) .then((response) => { this.mountConfig = JSON.stringify(response.data.result, null, 4) this.mountConfigModified = this.mountConfig @@ -135,7 +146,10 @@ export default { default_lease_ttl: parsed.default_lease_ttl.toString(), max_lease_ttl: parsed.max_lease_ttl.toString() }, { - headers: {'X-CSRF-Token': this.csrf} + headers: { + 'X-CSRF-Token': this.csrf, + 'X-Vault-Token': this.session ? this.session.token : '' + } }) .then((response) => { @@ -145,7 +159,9 @@ export default { type: 'success' }) // update page data accordingly - this.$http.get(address).then((response) => { + this.$http.get(address, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.mounts[this.selectedIndex].conf = response.data.result this.mountConfig = JSON.stringify(response.data.result, null, 4) this.mountConfigModified = this.mountConfig diff --git a/frontend/client/views/admin/Policies.vue b/frontend/client/views/admin/Policies.vue index 092d71f6..1ebecd9f 100644 --- a/frontend/client/views/admin/Policies.vue +++ b/frontend/client/views/admin/Policies.vue @@ -129,7 +129,6 @@ const querystring = require('querystring') export default { data () { return { - csrf: '', policies: [], policyRules: '', policyRulesModified: '', @@ -146,9 +145,11 @@ export default { }, mounted: function () { - this.$http.get('/api/policy').then((response) => { + this.$http.get('/api/policy', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }) + .then((response) => { this.policies = response.data.result - this.csrf = response.headers['x-csrf-token'] }) .catch((error) => { this.$onError(error) @@ -156,6 +157,9 @@ export default { }, computed: { + session: function () { + return this.$store.getters.session + }, filteredPolicies: function () { if (this.nameFilter) { // filter by name @@ -179,7 +183,9 @@ export default { this.policyRules = '' this.policyRulesModified = '' this.selectedPolicy = policyName - this.$http.get('/api/policy?policy=' + policyName).then((response) => { + this.$http.get('/api/policy?policy=' + policyName, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.policyRules = response.data.result this.policyRulesModified = this.policyRules }) @@ -199,7 +205,9 @@ export default { // crawl through each policy for (var i = 0; i < this.policies.length; i++) { let policyName = this.policies[i] - this.$http.get('/api/policy?policy=' + policyName).then((response) => { + this.$http.get('/api/policy?policy=' + policyName, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { var searchString = this.search.regex ? this.search.str : this.makeRegex(this.search.str) if (response.data.result.match(searchString)) { this.search.found.push(policyName) @@ -218,7 +226,7 @@ export default { addPolicyRequest: function () { this.$http.post('/api/policy/request?policy=' + this.selectedPolicy, querystring.stringify({ rules: this.policyRulesModified }), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { diff --git a/frontend/client/views/admin/Requests.vue b/frontend/client/views/admin/Requests.vue index fbb9714c..c68042a8 100644 --- a/frontend/client/views/admin/Requests.vue +++ b/frontend/client/views/admin/Requests.vue @@ -175,7 +175,6 @@ const querystring = require('querystring') export default { data () { return { - csrf: '', searchString: '', searchType: 'changeid', request: null, @@ -191,6 +190,10 @@ export default { }, computed: { + session: function () { + return this.$store.getters.session + }, + searchURL: function () { var url = '/api/policy/request?type=' + this.searchType if (this.searchType === 'changeid') { @@ -217,8 +220,9 @@ export default { if (this.request !== null) { return } - this.$http.get(this.searchURL).then((response) => { - this.csrf = response.headers['x-csrf-token'] + this.$http.get(this.searchURL, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.request = response.data.result this.progress = response.data.progress this.required = response.data.required @@ -232,12 +236,9 @@ export default { this.$http.post(this.updateURL, querystring.stringify({ unseal: this.unsealToken }), { - headers: {'X-CSRF-Token': this.csrf} - }) - - .then((response) => { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.unsealToken = '' - // if more unseals are needed if (response.data.progress) { this.progress = response.data.progress @@ -254,7 +255,6 @@ export default { type: 'warning' }) } - // if change was successfully completed } else { this.progress = this.required @@ -277,7 +277,7 @@ export default { reject: function () { this.$http.delete('/api/policy/request/' + this.searchString, { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.$notify({ diff --git a/frontend/client/views/admin/Users.vue b/frontend/client/views/admin/Users.vue index 5acf7d11..34bccfc5 100644 --- a/frontend/client/views/admin/Users.vue +++ b/frontend/client/views/admin/Users.vue @@ -292,7 +292,6 @@ export default { data () { return { - csrf: '', tabName: 'token', tableData: [], tableColumns: [ @@ -328,6 +327,10 @@ export default { }, computed: { + session: function () { + return this.$store.getters.session + }, + selectedItemTitle: function () { if (this.selectedIndex !== -1) { return String(this.tableData[this.selectedIndex][this.tableColumns[0]]) @@ -387,9 +390,10 @@ export default { if (index === 0) { // tokens tab requires special pagination - this.$http.get('/api/token/accessors').then((response) => { + this.$http.get('/api/token/accessors', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.accessors = response.data.result - this.csrf = response.headers['x-csrf-token'] this.lastPage = Math.ceil(this.accessors.length / 300) this.loadPage(1) }) @@ -398,9 +402,10 @@ export default { }) } else { // otherwise populate new table data according to tab name - this.$http.get('/api/users?type=' + this.tabName).then((response) => { + this.$http.get('/api/users?type=' + this.tabName, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.tableData = response.data.result - this.csrf = response.headers['x-csrf-token'] }) .catch((error) => { this.$onError(error) @@ -426,29 +431,23 @@ export default { }, deleteItem (index) { - // fetching extra csrf will be unnecessary after API redesign - this.$http.get('/api/users/csrf').then((response) => { - this.$http.post('/api/users/revoke', { - Type: this.tabName.toLowerCase(), - ID: this.tableData[index][this.tableColumns[0]] - }, { - headers: {'X-CSRF-Token': response.headers['x-csrf-token']} - }) - .then((response) => { - this.closeDeleteModal() - this.tableData.splice(index, 1) - this.$notify({ - title: 'Success', - message: 'Deletion successful', - type: 'success' - }) - }) - .catch((error) => { - this.closeDeleteModal() - this.$onError(error) + this.$http.post('/api/users/revoke', { + Type: this.tabName.toLowerCase(), + ID: this.tableData[index][this.tableColumns[0]] + }, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }) + .then((response) => { + this.closeDeleteModal() + this.tableData.splice(index, 1) + this.$notify({ + title: 'Success', + message: 'Deletion successful', + type: 'success' }) }) .catch((error) => { + this.closeDeleteModal() this.$onError(error) }) }, @@ -465,7 +464,7 @@ export default { this.$http.post('/api/token/lookup-accessor', { Accessors: this.accessors.slice((pg - 1) * 300, pg * 300).join(',') }, { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.tableData = response.data.result @@ -514,7 +513,7 @@ export default { this.$http.post('/api/token/lookup-accessor', { Accessors: this.accessors.slice(i * 300, (i + 1) * 300).join(',') }, { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { var found = false diff --git a/frontend/client/views/bulletinboard/index.vue b/frontend/client/views/bulletinboard/index.vue index 01db4ab6..b71482e5 100644 --- a/frontend/client/views/bulletinboard/index.vue +++ b/frontend/client/views/bulletinboard/index.vue @@ -40,12 +40,14 @@ export default { data () { return { - csrf: '', bulletins: [] } }, computed: { + session: function () { + return this.$store.getters.session + }, bulletinPairs: function () { var pairs = [] for (var i = 0; i < this.bulletins.length; i += 2) { @@ -60,7 +62,9 @@ export default { }, mounted: function () { - this.$http.get('/api/bulletins').then((response) => { + this.$http.get('/api/bulletins', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.bulletins = response.data.result }) .catch((error) => { diff --git a/frontend/client/views/login/index.vue b/frontend/client/views/login/index.vue index 0a8df739..23c12070 100644 --- a/frontend/client/views/login/index.vue +++ b/frontend/client/views/login/index.vue @@ -109,7 +109,7 @@ - + {{ key }} @@ -191,7 +191,6 @@ import moment from 'moment' export default { data () { return { - csrf: '', type: 'Token', ID: '', Password: '', @@ -201,48 +200,26 @@ export default { }, mounted: function () { - // fetch csrf for login post request later - this.fetchCSRF() // fetch vault cluster details this.getHealth() - // if stored session is out of date, notify user - if (this.session && moment().isAfter(moment(this.session['cookie_expiry'], 'ddd, h:mm:ss A MMMM Do YYYY'))) { - window.localStorage.removeItem('session') - this.$notify({ - title: 'Session expired', - message: 'Please login again', - type: 'warning' - }) - this.$store.commit('clearSession') - } }, computed: { + session: function () { + return this.$store.getters.session + }, healthKeys: function () { return Object.keys(this.healthData) }, renewable: function () { return (this.session && this.session['renewable']) }, - session: function () { - return this.$store.getters.session - }, sessionKeys: function () { return (this.session === null) || Object.keys(this.session) } }, methods: { - fetchCSRF: function () { - this.$http.get('/api/login/csrf') - .then((response) => { - this.csrf = response.headers['x-csrf-token'] - }) - .catch((error) => { - this.$onError(error) - }) - }, - getHealth: function () { this.healthLoading = true this.$http.get('/api/health') @@ -263,7 +240,7 @@ export default { ID: this.ID, Password: this.Password }, { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { // notify user, and clear inputs @@ -274,15 +251,14 @@ export default { }) this.clearFormData() - // construct session data var newSession = { + 'token': response.data.result['cipher'], 'type': this.type, - 'display_name': response.data.data['display_name'], - 'meta': response.data.data['meta'], - 'policies': response.data.data['policies'], - 'renewable': response.data.data['renewable'], - 'token_expiry': response.data.data['ttl'] === 0 ? 'never' : moment().add(response.data.data['ttl'], 'seconds').format('ddd, h:mm:ss A MMMM Do YYYY'), - 'cookie_expiry': moment().add(8, 'hours').format('ddd, h:mm:ss A MMMM Do YYYY') // 8 hours from now + 'display_name': response.data.result['display_name'], + 'meta': response.data.result['meta'], + 'policies': response.data.result['policies'], + 'renewable': response.data.result['renewable'], + 'token_expiry': response.data.result['ttl'] === 0 ? 'never' : moment().add(response.data.result['ttl'], 'seconds').format('ddd, h:mm:ss A MMMM Do YYYY') } // store session data in localstorage and mutate vuex state @@ -320,23 +296,23 @@ export default { renewLogin: function () { this.$http.post('/api/login/renew-self', {}, { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { + // deep copy session, update fields, and mutate state + let newSession = JSON.parse(JSON.stringify(this.session)) + + newSession['meta'] = response.data.result['meta'] + newSession['policies'] = response.data.result['policies'] + newSession['token_expiry'] = response.data.result['ttl'] === 0 ? 'never' : moment().add(response.data.result['ttl'], 'seconds').format('ddd, h:mm:ss A MMMM Do YYYY') + + window.localStorage.setItem('session', JSON.stringify(newSession)) + this.$store.commit('setSession', newSession) this.$notify({ title: 'Renew success!', message: '', type: 'success' }) - this.session['meta'] = response.data.data['meta'] - this.session['policies'] = response.data.data['policies'] - this.session['token_expiry'] = response.data.data['ttl'] === 0 ? 'never' : new Date(Date.now() + response.data.data['ttl'] * 1000).toString() - - // store session data in localstorage - window.localStorage.setItem('session', JSON.stringify(this.session)) - - // mutate state of vuex - this.$store.commit('setSession', this.session) }) .catch((error) => { this.$onError(error) diff --git a/frontend/client/views/secrets/index.vue b/frontend/client/views/secrets/index.vue index 1f498052..5ac03c63 100644 --- a/frontend/client/views/secrets/index.vue +++ b/frontend/client/views/secrets/index.vue @@ -239,7 +239,6 @@ export default { data () { return { - csrf: '', currentPath: '', currentPathCopy: '', displayJSON: false, @@ -257,6 +256,10 @@ export default { }, computed: { + session: function () { + return this.$store.getters.session + }, + currentPathType: function () { if (this.currentPath === '' || this.currentPath === '/') { return 'Mount' @@ -312,10 +315,11 @@ export default { this.confirmDelete = false this.displayJSON = false - this.$http.get('/api/secrets?path=' + encodeURIComponent(path)).then((response) => { + this.$http.get('/api/secrets?path=' + encodeURIComponent(path), { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.tableData = [] this.currentPath = response.data.path - this.csrf = response.headers['x-csrf-token'] let result = response.data.result if (this.currentPath.slice(-1) === '/') { @@ -434,7 +438,7 @@ export default { this.$http.post('/api/secrets?path=' + encodeURIComponent(this.currentPath), querystring.stringify({ body: body }), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.$notify({ @@ -510,7 +514,7 @@ export default { // request deletion of secret this.$http.delete('/api/secrets?path=' + encodeURIComponent(path), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.$notify({ diff --git a/frontend/client/views/tools/CreateToken.vue b/frontend/client/views/tools/CreateToken.vue index d307c772..e30e37cf 100644 --- a/frontend/client/views/tools/CreateToken.vue +++ b/frontend/client/views/tools/CreateToken.vue @@ -333,7 +333,6 @@ export default { data () { return { - csrf: '', bRenewable: true, bNoParent: false, bPeriodic: false, @@ -360,6 +359,10 @@ export default { }, computed: { + session: function () { + return this.$store.getters.session + }, + // returns all policies in availablePolicies that contain the policyFilter substring filteredPolicies: function () { var filter = this.policyFilter @@ -419,23 +422,8 @@ export default { }, mounted: function () { - this.$http.get('/api/users/csrf') - .then((response) => { - this.csrf = response.headers['x-csrf-token'] - }) - .catch((error) => { - this.$onError(error) - }) - // fetch available policies - try { - var session = JSON.parse(window.localStorage.getItem('session')) - if (Date.now() > Date.parse(session['cookie_expiry'])) { - throw session - } else { - this.availablePolicies = session.policies - } - } catch (e) { + if (!this.session || !this.session['policies']) { this.$notify({ title: 'Session not found', message: 'Please login', @@ -443,9 +431,12 @@ export default { }) return } + this.availablePolicies = this.session['policies'] // check if roles are available to logged in user - this.$http.get('/api/users/listroles').then((response) => { + this.$http.get('/api/users/listroles', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { if (response.data.result !== null) { this.availableRoles = response.data.result } @@ -463,7 +454,9 @@ export default { // if root policy, fetch all available policies from server if (this.availablePolicies.indexOf('root') > -1) { - this.$http.get('/api/policy').then((response) => { + this.$http.get('/api/policy', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.availablePolicies = response.data.result // default policy is always an option, and the first item in list var i = this.availablePolicies.indexOf('default') @@ -526,7 +519,7 @@ export default { this.createdToken = null this.$http.post('/api/users/create?type=token' + this.wrapParam, this.payloadJSON, { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.$notify({ @@ -544,7 +537,9 @@ export default { loadRoleDetails: function (rolename) { this.selectedRoleLoading = true this.selectedRoleDetails = '' - this.$http.get('/api/users/role?rolename=' + rolename) + this.$http.get('/api/users/role?rolename=' + rolename, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }) .then((response) => { this.selectedRoleDetails = response.data.result this.selectedRoleLoading = false diff --git a/frontend/client/views/tools/Dependencies.vue b/frontend/client/views/tools/Dependencies.vue index 864558a3..aa36f0fb 100644 --- a/frontend/client/views/tools/Dependencies.vue +++ b/frontend/client/views/tools/Dependencies.vue @@ -185,7 +185,6 @@ export default { data () { return { - csrf: '', supportedResourceTypes: ['Policy', 'Mount'], resourceType: '', loading: false, @@ -210,6 +209,10 @@ export default { }, computed: { + session: function () { + return this.$store.getters.session + }, + resultNames: function () { var names = [] for (var i = 0; i < this.results.length; i++) { @@ -287,7 +290,9 @@ export default { this.selectedPolicy = '' // fetch list of policies - this.$http.get('/api/policy').then((response) => { + this.$http.get('/api/policy', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.policies = response.data.result this.loading = false }) @@ -307,7 +312,9 @@ export default { this.selectedMount = '' // fetch list of mounts - this.$http.get('/api/mounts').then((response) => { + this.$http.get('/api/mounts', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.mounts = Object.keys(response.data.result) this.loading = false }) @@ -331,9 +338,10 @@ export default { let policy = this.selectedPolicy // fetch a list of all accessors - this.$http.get('/api/token/accessors').then((response) => { + this.$http.get('/api/token/accessors', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { let accessors = response.data.result - let csrf = response.headers['x-csrf-token'] result.Loading = Math.ceil(accessors.length / 300) for (var i = 0; i < Math.ceil(accessors.length / 300); i++) { @@ -341,7 +349,7 @@ export default { this.$http.post('/api/token/lookup-accessor', { Accessors: accessors.slice(i * 300, (i + 1) * 300).join(',') }, { - headers: {'X-CSRF-Token': csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) // on success, parse each token detail for the target policy @@ -377,7 +385,9 @@ export default { this.results.push(result) // fetch all users and filter by policy - this.$http.get('/api/users?type=userpass').then((response) => { + this.$http.get('/api/users?type=userpass', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { let users = response.data.result for (var i = 0; i < users.length; i++) { if (users[i].Policies.includes(this.selectedPolicy)) { @@ -404,7 +414,9 @@ export default { let policy = this.selectedPolicy // fetch all users and filter by policy - this.$http.get('/api/users/listroles').then((response) => { + this.$http.get('/api/users/listroles', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { // if there are no roles found if (response.data.result === null) { result.Loading = false @@ -413,8 +425,9 @@ export default { result.Loading = response.data.result.length for (var i = 0; i < response.data.result.length; i++) { let rolename = response.data.result[i] - this.$http.get('/api/users/role?rolename=' + rolename) - .then((response) => { + this.$http.get('/api/users/role?rolename=' + rolename, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { if (response.data.result && response.data.result['allowed_policies'].includes(policy)) { result.Dependents.push(rolename) } @@ -444,7 +457,9 @@ export default { this.results.push(result) // fetch all users and filter by policy - this.$http.get('/api/users?type=approle').then((response) => { + this.$http.get('/api/users?type=approle', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { let users = response.data.result for (var i = 0; i < users.length; i++) { if (users[i].Policies.includes(this.selectedPolicy)) { @@ -471,12 +486,16 @@ export default { this.results.push(result) // fetch all policies - this.$http.get('/api/policy').then((response) => { + this.$http.get('/api/policy', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { result.Loading = response.data.result.length // for each policy, check rules for mount for (var i = 0; i < response.data.result.length; i++) { let policyname = response.data.result[i] - this.$http.get('/api/policy?policy=' + policyname).then((response) => { + this.$http.get('/api/policy?policy=' + policyname, { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { // prefix with quote marks to ensure it's the mount that is matched if (response.data.result.includes('"' + this.selectedMount) || response.data.result.includes('\'' + this.selectedMount)) { diff --git a/frontend/client/views/tools/Transit.vue b/frontend/client/views/tools/Transit.vue index 1066f5ae..a9967f57 100644 --- a/frontend/client/views/tools/Transit.vue +++ b/frontend/client/views/tools/Transit.vue @@ -113,7 +113,6 @@ export default { data () { return { - csrf: '', plaintext: '', cipher: '', userTransitKey: '', @@ -121,9 +120,16 @@ export default { } }, + computed: { + session: function () { + return this.$store.getters.session + } + }, + mounted: function () { - this.$http.get('/api/transit').then((response) => { - this.csrf = response.headers['x-csrf-token'] + this.$http.get('/api/transit', { + headers: {'X-Vault-Token': this.session ? this.session.token : ''} + }).then((response) => { this.userTransitKey = response.headers['usertransitkey'] }) .catch((error) => { @@ -141,7 +147,7 @@ export default { plaintext: this.plaintext, key: this.userTransitKey }), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { @@ -168,7 +174,7 @@ export default { cipher: this.cipher, key: this.userTransitKey }), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { diff --git a/frontend/client/views/tools/Wrapper.vue b/frontend/client/views/tools/Wrapper.vue index fb64b434..df37828b 100644 --- a/frontend/client/views/tools/Wrapper.vue +++ b/frontend/client/views/tools/Wrapper.vue @@ -158,7 +158,6 @@ const querystring = require('querystring') export default { data () { return { - csrf: '', tableData: [], currToken: '', newKey: '', @@ -167,19 +166,16 @@ export default { } }, + computed: { + session: function () { + return this.$store.getters.session + } + }, + mounted: function () { - // fetch csrf token upon mounting - this.$http.get('/api/wrapping') - .then((response) => { - this.csrf = response.headers['x-csrf-token'] - }) - .catch((error) => { - this.$onError(error) - }) }, methods: { - // takes out "isClicked" field in tableData so content can be sent off packData: function () { var data = {} @@ -199,7 +195,7 @@ export default { wrapttl: this.wrap_ttl, data: JSON.stringify(this.packData()) }), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.$message({ @@ -222,7 +218,7 @@ export default { this.$http.post('/api/wrapping/unwrap', querystring.stringify({ wrappingToken: this.currToken }), { - headers: {'X-CSRF-Token': this.csrf} + headers: {'X-Vault-Token': this.session ? this.session.token : ''} }) .then((response) => { this.tableData = [] diff --git a/handlers/handlers.go b/handlers/handlers.go index b8689576..4b815d41 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -7,31 +7,12 @@ import ( "strings" "github.com/caiyeon/goldfish/vault" - "github.com/gorilla/csrf" - "github.com/gorilla/securecookie" "github.com/labstack/echo" ) // for returning JSON bodies type H map[string]interface{} -// for storing ciphers of user credentials -var scookie = &securecookie.SecureCookie{} - -func init() { - // setup cookie encryption keys - hashKey := securecookie.GenerateRandomKey(64) - blockKey := securecookie.GenerateRandomKey(32) - if hashKey == nil || blockKey == nil { - panic("Failed to generate random hashkey") - } - scookie = securecookie.New(hashKey, blockKey) - scookie = scookie.MaxAge(14400) // 8 hours - if scookie == nil { - panic("Failed to initialize gorilla/securecookie") - } -} - // returns the http status code found in the error message func parseError(c echo.Context, err error) error { errCode := strings.Split(err.Error(), "Code:") @@ -52,15 +33,6 @@ func parseError(c echo.Context, err error) error { }) } -func FetchCSRF() echo.HandlerFunc { - return func(c echo.Context) error { - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) - return c.JSON(http.StatusOK, H{ - "status": "fetched", - }) - } -} - func VaultHealth() echo.HandlerFunc { return func(c echo.Context) error { resp, err := vault.VaultHealth() @@ -103,24 +75,11 @@ func Login() echo.HandlerFunc { }) } - // store auth.Type and auth.ID (now a cipher) in cookie - if encoded, err := scookie.Encode("auth", auth); err == nil { - cookie := &http.Cookie{ - Name: "auth", - Value: encoded, - Path: "/", - } - http.SetCookie(c.Response().Writer, cookie) - } else { - return c.JSON(http.StatusInternalServerError, H{ - "error": "Goldfish could not encode cookie", - }) - } - // return useful information to user return c.JSON(http.StatusOK, H{ "status": "Logged in", - "data": map[string]interface{}{ + "result": map[string]interface{}{ + "cipher": auth.ID, "display_name": data["display_name"], "id": data["id"], "meta": data["meta"], @@ -148,7 +107,7 @@ func RenewSelf() echo.HandlerFunc { } return c.JSON(http.StatusOK, H{ - "data": map[string]interface{}{ + "result": map[string]interface{}{ "meta": resp.Auth.Metadata, "policies": resp.Auth.Policies, "ttl": resp.Auth.LeaseDuration, @@ -157,37 +116,29 @@ func RenewSelf() echo.HandlerFunc { } } -// if session is valid (prefer headers over cookie), returns true -// otherwise, writes errors to response, and returns false +// reads header as an encrypted func getSession(c echo.Context) (*vault.AuthInfo) { - var auth = &vault.AuthInfo{} - - // check headers first - if token := c.Request().Header.Get("X-Vault-Token"); token != "" { - auth.Type = "token" - auth.ID = token - return auth + var auth = &vault.AuthInfo{ + Type: "token", } - // fetch auth from cookie - cookie, err := c.Request().Cookie("auth") - if err != nil { - c.JSON(http.StatusForbidden, H{ - "error": "Please login first", - }) - return nil - } - if err := scookie.Decode("auth", cookie.Value, &auth); err != nil { + // check headers first + if auth.ID = c.Request().Header.Get("X-Vault-Token"); auth.ID == "" { c.JSON(http.StatusForbidden, H{ "error": "Please login first", }) return nil } - // if cookie is valid, decrypt it with transit key - if err := auth.DecryptAuth(); err != nil { - parseError(c, err) - return nil + // if header is transit encrypted, decrypt first + if strings.HasPrefix(auth.ID, "vault:") { + if err := auth.DecryptAuth(); err != nil { + c.JSON(http.StatusForbidden, H{ + "error": "Cipher invalid. Please logout and login again", + }) + return nil + } } + return auth } diff --git a/handlers/mount.go b/handlers/mount.go index 6dced1f5..47cec1d3 100644 --- a/handlers/mount.go +++ b/handlers/mount.go @@ -3,7 +3,6 @@ package handlers import ( "net/http" - "github.com/gorilla/csrf" vaultapi "github.com/hashicorp/vault/api" "github.com/labstack/echo" ) @@ -22,8 +21,6 @@ func GetMounts() echo.HandlerFunc { return parseError(c, err) } - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) - return c.JSON(http.StatusOK, H{ "result": mounts, }) diff --git a/handlers/policy.go b/handlers/policy.go index 56961b3e..c3995e4f 100644 --- a/handlers/policy.go +++ b/handlers/policy.go @@ -15,7 +15,6 @@ import ( "github.com/fatih/structs" - "github.com/gorilla/csrf" "github.com/gorilla/securecookie" "github.com/hashicorp/go-uuid" @@ -67,7 +66,6 @@ func GetPolicy() echo.HandlerFunc { return parseError(c, err) } - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) return c.JSON(http.StatusOK, H{ "result": result, }) @@ -286,8 +284,6 @@ func getPolicyRequestByChangeID(c echo.Context, auth *vault.AuthInfo, hash strin }) } - // return request - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) return c.JSON(http.StatusOK, H{ "result": request, "progress": request.Progress, @@ -320,8 +316,6 @@ func getPolicyRequestByCommitHash(c echo.Context, auth *vault.AuthInfo, hash str } } - // return request - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) return c.JSON(http.StatusOK, H{ "result": changes, "progress": progress, @@ -617,8 +611,6 @@ func updatePolicyRequestByChangeID(c echo.Context, auth *vault.AuthInfo, hash st return parseError(c, err) } - // return request - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) return c.JSON(http.StatusOK, H{ "result": policyNow, }) @@ -760,8 +752,6 @@ func updatePolicyRequestByCommitHash(c echo.Context, auth *vault.AuthInfo, hash } } - // return request - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) return c.JSON(http.StatusOK, H{ "result": "Complete", }) diff --git a/handlers/secret.go b/handlers/secret.go index 4007d349..4339778e 100644 --- a/handlers/secret.go +++ b/handlers/secret.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/caiyeon/goldfish/vault" - "github.com/gorilla/csrf" "github.com/labstack/echo" ) @@ -23,8 +22,6 @@ func GetSecrets() echo.HandlerFunc { path = conf.DefaultSecretPath } - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) - if path == "" || path[len(path)-1:] == "/" { // listing a directory if result, err := auth.ListSecret(path); err != nil { diff --git a/handlers/transit.go b/handlers/transit.go index 2c083709..3654a084 100644 --- a/handlers/transit.go +++ b/handlers/transit.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/caiyeon/goldfish/vault" - "github.com/gorilla/csrf" "github.com/labstack/echo" ) @@ -25,7 +24,6 @@ func TransitInfo() echo.HandlerFunc { } conf := vault.GetConfig() - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) c.Response().Writer.Header().Set("UserTransitKey", conf.UserTransitKey) return c.JSON(http.StatusOK, H{ "status": "fetched", diff --git a/handlers/user.go b/handlers/user.go index 2e535b8e..2712a7f5 100644 --- a/handlers/user.go +++ b/handlers/user.go @@ -6,7 +6,6 @@ import ( "strconv" "github.com/caiyeon/goldfish/vault" - "github.com/gorilla/csrf" "github.com/hashicorp/vault/api" "github.com/labstack/echo" ) @@ -39,8 +38,6 @@ func GetUsers() echo.HandlerFunc { return parseError(c, err) } - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) - return c.JSON(http.StatusOK, H{ "result": result, }) @@ -62,8 +59,6 @@ func GetTokenCount() echo.HandlerFunc { return parseError(c, err) } - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) - return c.JSON(http.StatusOK, H{ "result": result, }) @@ -85,8 +80,6 @@ func GetTokenAccessors() echo.HandlerFunc { return parseError(c, err) } - c.Response().Writer.Header().Set("X-CSRF-Token", csrf.Token(c.Request())) - return c.JSON(http.StatusOK, H{ "result": result, }) diff --git a/server.go b/server.go index 857ddf17..39d20fb2 100644 --- a/server.go +++ b/server.go @@ -12,8 +12,6 @@ import ( "github.com/caiyeon/goldfish/config" "github.com/caiyeon/goldfish/handlers" "github.com/caiyeon/goldfish/vault" - "github.com/gorilla/csrf" - "github.com/gorilla/securecookie" "github.com/labstack/echo" "github.com/labstack/echo/middleware" @@ -98,14 +96,6 @@ func main() { e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.BodyLimit("32M")) - e.Use(echo.WrapMiddleware( - csrf.Protect( - // Generate a new encryption key for cookies each launch - // invalidating previous goldfish instance's cookies is purposeful - []byte(securecookie.GenerateRandomKey(32)), - // https-only unless tls_disable - csrf.Secure(!cfg.Listener.Tls_disable), - ))) // unless explicitly disabled, some extra https configurations need to be set if !cfg.Listener.Tls_disable { @@ -151,7 +141,6 @@ func main() { // API routing e.GET("/api/health", handlers.VaultHealth()) - e.GET("/api/login/csrf", handlers.FetchCSRF()) e.POST("/api/login", handlers.Login()) e.POST("/api/login/renew-self", handlers.RenewSelf()) @@ -160,7 +149,6 @@ func main() { e.DELETE("/api/token/revoke-accessor", handlers.DeleteTokenByAccessor()) e.GET("/api/users", handlers.GetUsers()) - e.GET("/api/users/csrf", handlers.FetchCSRF()) e.GET("/api/tokencount", handlers.GetTokenCount()) e.GET("/api/users/role", handlers.GetRole()) e.GET("/api/users/listroles", handlers.ListRoles()) @@ -189,7 +177,6 @@ func main() { e.GET("/api/bulletins", handlers.GetBulletins()) - e.GET("/api/wrapping", handlers.FetchCSRF()) e.POST("/api/wrapping/wrap", handlers.WrapHandler()) e.POST("/api/wrapping/unwrap", handlers.UnwrapHandler())