diff --git a/server/static.go b/server/static.go index 27865fc..aa7c470 100644 --- a/server/static.go +++ b/server/static.go @@ -7,1813 +7,1813 @@ type staticFile struct { // This file is autogenerated by go generate. Do not modify. var webStatics = map[string]staticFile{ - "/": { Content: ` - - - Doomsday - - - - - - -
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -`, MIMEType: `text/html` }, - "/web/assets/stylesheet.css": { Content: `#viewport { - display: flex; - width: 100%; - height: calc(100vh - 50px); - overflow: auto; - background: repeating-linear-gradient( - 45deg, - rgba(0, 0, 0, 0), - rgba(0, 0, 0, 0) 60px, - rgba(0, 0, 0, 0.15) 60px, - rgba(0, 0, 0, 0.15) 120px - ); - padding-top: 50px; +.horizontal-line { + display: block; + height: 2px; + width: 95%; + margin: auto; + background-color: rgba(0,0,0,0.100); } -#login { - height: 100%; +.cert-list-footer-left-buffer .horizontal-line { width: 100%; - display: none; + background-color: white; + height: 4px; } -#login-page-container { - height: 100%; - width: 100%; - display: flex; - align-items: center; - justify-content: center; +.horizontal-line-taper { + height: 4px; + width: 40px; + position: relative; + margin-right: 1%; + background-image: linear-gradient(to right, rgba(255,255,255,1), rgba(0,0,0,0)); } -#login-box { +.expired-card { background-color: #252525; - border-radius: 14px; - display: flex; - flex-wrap: wrap; - justify-content: center; - align-content: center; - width: 400px; + box-shadow: 7px 5px rgb(229, 53, 69); + border-color: rgba(255, 255, 255, 0.25) rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) rgba(255, 255, 255, 0.25) !important; } -#login-box label { - color: white; - width: 100%; +.expired-card .certs-content-header { + border-color: rgba(255, 255, 255, 0.1); } -#login-form { - display: flex; - flex-wrap: wrap; - justify-content: center; - align-content: center; - margin: 1em; - width: 100%; +.expired-card .certs-content-value { + color: rgb(229, 53, 69); } -.login-text-box { - margin: 0.75em 1.5em; - width: 100%; - display: flex; - flex-wrap: wrap; +@media only screen and (min-width: 600px) { + #navbar #subtitle { + display: inline-block; + } } -.login-text-box input[type=text], input[type=password] { - font-size: 20px; - width: 100%; - border-radius: 6px; +@media only screen and (min-width: 1200px) { + .cert-grid-container { + width: 50%; + } + } -.login-button-box { - margin: 0.4em; - display: flex; - justify-content: center; - width: 100%; +@media only screen and (min-width: 1800px) { + .cert-grid-container { + width: 33.33333%; + } } -.login-button-box input[type=submit] { - font-size: 20px; - font-family: inherit; - border-radius: 6px; - padding: 0em 1em; +@media only screen and (min-width: 2400px) { + .cert-grid-container { + width: 25%; + } +} + +@media only screen and (min-width: 3000px) { + .cert-grid-container { + width: 20%; + } +} + +@media only screen and (min-width: 3600px) { + .cert-grid-container { + width: 16.66666%; + } } +`, MIMEType: `text/css` }, + "/web/assets/lens.js": { Content: `var Lens = {}; +;(function () { + + var nil = function () { }; + + if (!console) { + console = { log: nil }; + } + + var log = { + debug: nil, + info: nil, + warn: nil, + error: nil, + + level: function (level) { + log.debug = nil; + log.info = nil; + log.warn = nil; + log.error = nil; + + switch (level) { + case 'debug': log.debug = console.log; + case 'info': log.info = console.log; + case 'warn': log.warn = console.log; + case 'error': log.error = console.log; + } + } + }; + + Lens.log = log; +})() +;(function () { + const SET_ATTRIBUTE = 1, + REMOVE_ATTRIBUTE = 2, + SET_PROPERTY = 3, + REMOVE_PROPERTY = 4, + REPLACE_NODE = 5, + APPEND_CHILD = 6, + REMOVE_CHILD = 7, + REPLACE_TEXT = 8; + + /* diff the ATTRIBUTES of two nodes, and return a + (potentially empty) patch op list. */ + var diffa = function (a, b) { + const ops = []; + const _a = {}; + const _b = {}; -.login-error { - margin: 1em; - margin-top: 0; - padding: 0.2em; - font-size: 16px; - width: 90%; - text-align: center; - border-radius: 6px; - background-color: rgb(229, 53, 69); -} + for (let i = 0; i < a.attributes.length; i++) { + _a[a.attributes[i].nodeName] = a.attributes[i].nodeValue; + } + for (let i = 0; i < b.attributes.length; i++) { + _b[b.attributes[i].nodeName] = b.attributes[i].nodeValue; + } -#navbar { - background-color: #252525; - display: flex; - align-content: center; - font-size: 40; - height: 1.5em; - border-bottom: solid rgba(0,0,0,0.4) thin; - z-index: 100; - top: 0; - width: 100%; -} + /* if the attribute is only defined in (a), then + it has been removed in (b) and should be patched + as a REMOVE_ATTRIBUTE. */ + for (let attr in _a) { + if (!(attr in _b)) { + ops.push({ + op: REMOVE_ATTRIBUTE, + node: a, + key: attr, + }); + } + } -#navbar-left-buffer { - width: 0px; -} + /* if the attribute is only defined in (b), or is + defined in both with different values, patch as + a SET_ATTRIBUTE to get the correct value. */ + for (let attr in _b) { + if (!(attr in _a) || _a[attr] !== _b[attr]) { + ops.push({ + op: SET_ATTRIBUTE, + node: a, + key: attr, + value: _b[attr] + }); + } + } -.navbar-content { - display: flex; - align-items: center; - justify-content: center; - padding-left: 15px; - padding-right: 15px; -} + return ops; + }; -.navbar-border { - border-right-color: rgba(255, 255, 255, 0.3); - border-right-style: solid; - border-right-width: thin; -} + var diffe = function (a, b) { + if (a.localName === b.localName) { + return diffa(a, b); + } + return null; + }; -.sticky { - position: fixed; -} + var difft = function (a, b) { + if (a.textContent === b.textContent) { + return []; + } + return [{ + op: REPLACE_TEXT, + node: a, + with: b.textContent + }]; + }; -#navbar #logo { - color: white; - font-size: 40; - position: relative; - top: -2px; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} + /* diff two NODEs, without recursing through child nodes */ + var diffn1 = function (a, b) { + if (a.nodeType != b.nodeType) { + /* nothing in common */ + return null; + } -#hamburger { - height: 25px; - width: 25px; - flex: 1 1 auto; -} + if (a.nodeType == Node.ELEMENT_NODE) { + return diffe(a, b); + } + if (a.nodeType == Node.TEXT_NODE) { + return difft(a, b); + } + if (a.nodeType == Node.COMMENT_NODE) { + return null; + } -#hamburger-box { - display: flex; - align-items: center; -} + console.log('unrecognized a type %s', a.nodeType); + return null; + }; -.navbar-button:hover { - background-color: rgba(255, 255, 255, 0.1); -} + /* diff two NODEs, co-recursively with diff() */ + var diffn = function (a, b) { + let ops = diffn1(a, b); -.navbar-button:active { - background-color: rgba(255, 255, 255, 0.2); -} + if (ops) { + return ops.concat(diff(a, b)); + } -#hamburger-menu { - background-color: rgba(37, 37, 37, 0.9); - height: calc(100% - 60px); - width: 250px; - z-index: 100; - top: 60px; - left: -251px; - display: inline-block; -} + return [{ + op: REPLACE_NODE, + node: a, + with: b + }]; + }; -.hamburger-menu-button { - display: flex; - align-items: center; - align-content: center; - justify-content: center; - height: 30px; - padding: 5px 20px 5px 20px; - font-size: 24px; - color: white; - border-style: solid none solid none; - border-width: thin; - border-color: rgba(255, 255, 255, 0.3); - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: #252525; -} + window.diff = function (a, b) { + let ops = []; + const { childNodes: _a } = a; + const { childNodes: _b } = b; -.hamburger-menu-button-inactive { - display: flex; - align-items: center; - align-content: center; - justify-content: center; - height: 30px; - padding: 5px 20px 5px 20px; - font-size: 24px; - color: grey; - border-style: solid none solid none; - border-width: thin; - border-color: rgba(255, 255, 255, 0.3); - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: #252525; -} + const _al = _a ? _a.length : 0; + const _bl = _b ? _b.length : 0; -#navbar #subtitle { - color: white; - font-size: 20; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} + for (let i = 0; i < _bl; i++) { + if (!_a[i]) { + ops.push({ + op: APPEND_CHILD, + node: a, + child: _b[i] + }); + continue; + } -#certs { - margin-top: 20px; - flex: 1 1 auto; -} + ops = ops.concat(diffn(_a[i], _b[i])); + } -#no-cert-container { - display: flex; -} + for (var i = _bl; i < _al; i++) { + ops.push({ + op: REMOVE_CHILD, + node: a, + child: _a[i] + }); + } + + return ops; + }; -.no-cert-textbox { - border-style: solid; - border-radius: 8px; - border-color: white; - border-width: thick; - font-weight: bold; - background-color: rgba(0, 0, 0, 0.15); - color: white; - font-size: 4vw; - padding: 0.75em; - position: relative; - top: 30vh; - margin: auto; -} -body { - margin: 0px; - background-color: #444444; -} -div { - font-family: "Walter Turncoat", "Verdana"; - color: rgba(0,0,0,.8); -} -.cert-grid-container { - width: 100%; -} + window.patch = function (e, ops) { + for (let i = 0; i < ops.length; i++) { + switch (ops[i].op) { + case SET_ATTRIBUTE: ops[i].node.setAttribute(ops[i].key, ops[i].value); break; + case REMOVE_ATTRIBUTE: ops[i].node.removeAttribute(ops[i].key); break; + case SET_PROPERTY: /* FIXME needs implemented! */ break; + case REMOVE_PROPERTY: /* FIXME needs implemented! */ break; + case REPLACE_NODE: ops[i].node.parentNode.replaceChild(ops[i].with, ops[i].node); break; + case APPEND_CHILD: ops[i].node.appendChild(ops[i].child); break; + case REMOVE_CHILD: ops[i].node.removeChild(ops[i].child); break; + case REPLACE_TEXT: ops[i].node.textContent = ops[i].with; break; + default: + console.log('unrecognized patch op %d for ', ops[i].op, ops[i]); + break; + } + } + }; -.certs-content-header { - background-color: rgba(0,0,0,.0700); - border-radius: 8px 8px 0px 0px; - border-bottom-style: solid; - border-bottom-color: rgba(0,0,0,0.0500); - border-width: thin; - text-align: center; - font-size: 24; - padding: 0.3em 0em; - font-weight: bold; - color: #FFFFFF; -} -.certs-content-body { - padding: 0.5em 1em; -} -.cert-list-footer-container { - margin: 4px 8px 4px 0; - display: flex; -} -.cert-list-footer-left-buffer { - align-self: center; - align-content: center; - display: flex; - flex: 1 1 auto; -} + window.explainPatch = function (ops) { + var l = []; + for (let i = 0; i < ops.length; i++) { + switch (ops[i].op) { + case SET_ATTRIBUTE: l.push(['SET_ATTRIBUTE', ops[i].node, ops[i].key+'='+ops[i].value]); break; + case REMOVE_ATTRIBUTE: l.push(['REMOVE_ATTRIBUTE', ops[i].node, ops[i].key]); break; + case SET_PROPERTY: l.push(['SET_PROPERTY', 'FIXME']); break; + case REMOVE_PROPERTY: l.push(['REMOVE_PROPERTY', 'FIXME']); break; + case REPLACE_NODE: l.push(['REPLACE_NODE', ops[i].node, { with: ops[i].with }]); break; + case APPEND_CHILD: l.push(['APPEND_CHILD', ops[i].node, { child: ops[i].child }]); break; + case REMOVE_CHILD: l.push(['REMOVE_CHILD', ops[i].node, { child: ops[i].child }]); break; + case REPLACE_TEXT: l.push(['REPLACE_TEXT', ops[i].node, { with: ops[i].with }]); break; + default: l.push(['**UNKNOWN**', ops[i]]); break; + } + } + return l; + }; +})(window, document); +;(function () { -.cert-list-footer-timeline-label { - color: white; - text-align: right; - font-size: 20px; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} + var __templates = {}; + var template = function (name, data) { + if (!(name in __templates)) { + Lens.log.debug('template {%s} not found in the cache; compiling from source.', name); + __templates[name] = compile(name); + } + return __templates[name](data || {}); + }; -.cert-list-body-timeline-container { - position: relative; - margin-right: 10px; -} + var parse = function (src) { + var tokenizer = new RegExp('([\\s\\S]*?)\\[\\[([\\s\\S]*?)\\]\\]([\\s\\S]*)'); + var str = function (s) { + if (!s) { return "''"; } + return "'"+s.replace(/(['\\])/g, '\\$1').replace(/\n/g, "\\n")+"'"; + }; -.cert-list-body-line-taper-in { - height: 40px; - width: 4px; - position: relative; - background-image: linear-gradient(to top, rgba(255,255,255,1), rgba(0,0,0,0)); -} + var code = []; + for (;;) { + var tokens = tokenizer.exec(src) + if (!tokens) { + code.push('__ += '+str(src)+';'); + break; + } -.cert-list-body-timeline-line { - width: 4px; - position: absolute; - height: calc(100% - 80px); - background-color: white; -} + if (tokens[2][0] == ':') { /* trim preceeding literal */ + tokens[1] = tokens[1].replace(/\s+$/, ''); + tokens[2] = tokens[2].substr(1); + } + if (tokens[2][tokens[2].length - 1] == ':') { /* trim following literal */ + tokens[3] = tokens[3].replace(/^\s+/, ''); + tokens[2] = tokens[2].substr(0, tokens[2].length-2); + } -.cert-list-body-line-taper-out { - height: 40px; - width: 4px; - position: absolute; - top: calc(100% - 40px); - background-image: linear-gradient(to bottom, rgba(255,255,255,1), rgba(0,0,0,0)); -} + code.push('__ += '+str(tokens[1])+';'); + if (tokens[2][0] == '=') { + code.push('__ += ('+tokens[2].replace(/^=\s*/, '')+');'); -.cert-list-body { - display: flex; -} + } else if (tokens[2][0] != '#') { /* skip comments */ + code.push(tokens[2]); + } -.cert-list-body-card-container { - flex: 1 1 auto; -} + src = tokens[3]; + } -.cert-list-body-card-container { - display: flex; - flex-wrap: wrap; - flex: 1 1 auto; -} + return code.join(''); + }; -.cert-list { - width: 100%; -} + var compile = function (name) { + name = name.toString(); + var script = document.getElementById('template:'+name); + if (!script) { + Lens.log.error('unable to find a + - [[= include('other-template') ]] + - */ - include: function (name, data) { - __ += template(name, data || _); - return ''; - } - }; + - /* aliases ... */ - lens.u = encodeURIComponent; - lens.h = lens.escapeHTML; - var include = lens.include; + - Lens.log.debug('evaluating the {%s} template', name); - eval(code); - return __; - }; - }; + - jQuery.fn.template = function (name, data, force) { - if (force || this.length == 0) { - this.html(template(name, data)); - } else { - window.patch(this[0], diff(this[0], $('
'+template(name, data)+'
')[0])); - } - return this; - }; + - } else if (typeof(window) !== 'undefined') { - window.template = template; + - } else { - throw 'neither jQuery or top-level window object were found; unsure where to attach template()...'; - } -})(); -;(function () { - var strftime = function (fmt, d) { - if (!(d instanceof Date)) { - var _d = new Date(); - if (!isNaN(d)) { - _d.setTime(d * 1000); /* epoch s -> ms */ - } - d = _d; - } - if (typeof(d) === 'undefined') { - return ""; - } + - en_US = { - pref: { - /* %c */ datetime: function (d) { return strftime("%a %b %e %H:%M:%S %Y", d); }, - /* %x */ date: function (d) { return strftime("%m/%d/%Y", d); }, - /* %X */ time: function (d) { return strftime("%H:%M:%S", d); } - }, - weekday: { - abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - full: ['Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday'] - }, - month: { - abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - full: ['January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December'] - }, - AM: "AM", am: "am", PM: "PM", pm: "pm", - ordinal: ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th', 'th', // 1 - 10 - 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', // 11 - 20 - 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th', 'th', // 21 - 30 - 'st'], - zero: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', - '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', - '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', - '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', - '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', - '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'], + - var lc = en_US; + + + +`, MIMEType: `text/html` }, } diff --git a/storage/vault.go b/storage/vault.go index bd3d479..b7e2f29 100644 --- a/storage/vault.go +++ b/storage/vault.go @@ -12,7 +12,7 @@ import ( ) type VaultAccessor struct { - client *vaultkv.Client + client *vaultkv.KV basePath string } @@ -44,7 +44,7 @@ func newVaultAccessor(conf VaultConfig) (*VaultAccessor, error) { } return &VaultAccessor{ - client: &vaultkv.Client{ + client: (&vaultkv.Client{ VaultURL: u, AuthToken: conf.Auth.Token, Client: &http.Client{ @@ -55,7 +55,7 @@ func newVaultAccessor(conf VaultConfig) (*VaultAccessor, error) { }, }, //Trace: os.Stdout, - }, + }).NewKV(), basePath: conf.BasePath, }, nil @@ -65,7 +65,7 @@ func newVaultAccessor(conf VaultConfig) (*VaultAccessor, error) { // return it as a map. func (v *VaultAccessor) Get(path string) (map[string]string, error) { ret := make(map[string]string) - err := v.client.Get(path, &ret) + _, err := v.client.Get(path, &ret, nil) return ret, err } diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/README.md b/vendor/github.com/cloudfoundry-community/vaultkv/README.md index 2659d0b..a0eb445 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/README.md +++ b/vendor/github.com/cloudfoundry-community/vaultkv/README.md @@ -43,9 +43,4 @@ func main() { ## Testing -There are Ginkgo tests. These tests will run the suite against multiple -versions of Vault. This can be cumbersome for general development, because -the tests can take awhile to run (each spec restarts an external Vault -process). If you only want to test against the latest version of Vault, set -the environment variable `VAULTKV_TEST_ONLY_LATEST`. Please test without this -variable set before submitting any PRs. \ No newline at end of file +Run `./test` in the base directory to test all supported Vault versions. Run `./test latest` to test only the latest supported version of Vault. \ No newline at end of file diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/auth.go b/vendor/github.com/cloudfoundry-community/vaultkv/auth.go index d6322bd..1bf0d55 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/auth.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/auth.go @@ -99,3 +99,16 @@ func (v *Client) AuthUserpass(username, password string) (ret *AuthOutput, err e return } + +//TokenRenewSelf takes the token in the Client object and attempts to renew its +// lease. +func (v *Client) TokenRenewSelf() (err error) { + return v.doRequest("POST", "/auth/token/renew-self", nil, nil) +} + +//TokenIsValid returns no error if it can look itself up. This can error +// if the token is valid but somebody has configured policies such that it can not +// look itself up. It can also error, of course, if the token is invalid. +func (v *Client) TokenIsValid() (err error) { + return v.doRequest("GET", "/auth/token/lookup-self", nil, nil) +} diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/auth_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/auth_test.go new file mode 100644 index 0000000..034f08a --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/auth_test.go @@ -0,0 +1,55 @@ +package vaultkv_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Auth", func() { + BeforeEach(func() { + InitAndUnsealVault() + }) + + Describe("TokenIsValid", func() { + JustBeforeEach(func() { + err = vault.TokenIsValid() + }) + + Context("When the token is valid", func() { + It("should not err", func() { + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("When the token is invalid", func() { + BeforeEach(func() { + if vault.AuthToken[0] == 'a' { + vault.AuthToken = "b" + vault.AuthToken[1:] + } else { + vault.AuthToken = "a" + vault.AuthToken[1:] + } + }) + It("should err", func() { + Expect(err).To(HaveOccurred()) + }) + }) + + Context("When the token not properly formatted", func() { + BeforeEach(func() { + vault.AuthToken = vault.AuthToken[1:] + }) + It("should err", func() { + Expect(err).To(HaveOccurred()) + }) + }) + + Context("When there is no token", func() { + BeforeEach(func() { + vault.AuthToken = "" + }) + It("should err", func() { + Expect(err).To(HaveOccurred()) + }) + }) + }) +}) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/badstate_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/badstate_test.go new file mode 100644 index 0000000..8cc139b --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/badstate_test.go @@ -0,0 +1,165 @@ +package vaultkv_test + +import ( + "fmt" + + "github.com/cloudfoundry-community/vaultkv" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = When("the vault is uninitialized", func() { + type spec struct { + Name string + Setup func() + MinVersion *semver + } + Specify("Most commands should return ErrUninitialized", func() { + for _, s := range []spec{ + spec{"Health", func() { err = vault.Health(true) }, nil}, + spec{"EnableSecretsMount", func() { err = vault.EnableSecretsMount("beep", vaultkv.Mount{}) }, nil}, + spec{"Unseal", func() { _, err = vault.Unseal("pLacEhoLdeR=") }, nil}, + spec{"Get", func() { err = vault.Get("secret/sure/whatever", nil) }, nil}, + spec{"Set", func() { err = vault.Set("secret/sure/whatever", map[string]string{"foo": "bar"}) }, nil}, + spec{"Delete", func() { err = vault.Delete("secret/sure/whatever") }, nil}, + spec{"List", func() { _, err = vault.List("secret/sure/whatever") }, nil}, + spec{"V2Get", func() { _, err = vault.V2Get("secret", "foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"V2Set", func() { _, err = vault.V2Set("secret", "foo", map[string]string{"beep": "boop"}, nil) }, &semver{0, 10, 0}}, + spec{"V2Delete", func() { err = vault.V2Delete("secret", "foo", nil) }, &semver{0, 10, 0}}, + spec{"V2Undelete", func() { err = vault.V2Undelete("secret", "foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"V2Destroy", func() { err = vault.V2Destroy("secret", "foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"V2DestroyMetadata", func() { err = vault.V2DestroyMetadata("secret", "foo") }, &semver{0, 10, 0}}, + spec{"V2GetMetadata", func() { _, err = vault.V2GetMetadata("secret", "foo") }, &semver{0, 10, 0}}, + spec{"KVGet", func() { _, err = vault.NewKV().Get("secret/foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"KVSet", func() { _, err = vault.NewKV().Set("secret/foo", map[string]string{"beep": "boop"}, nil) }, &semver{0, 10, 10}}, + spec{"KVDelete", func() { err = vault.NewKV().Delete("secret/foo", nil) }, &semver{0, 10, 0}}, + spec{"KVUndelete", func() { err = vault.NewKV().Undelete("secret/foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"KVDestroy", func() { err = vault.NewKV().Destroy("secret/foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"KVDestroyAll", func() { err = vault.NewKV().DestroyAll("secret/foo") }, &semver{0, 10, 0}}, + } { + if s.MinVersion != nil && parseSemver(currentVaultVersion).LessThan(*s.MinVersion) { + continue + } + (s.Setup)() + Expect(err).To(HaveOccurred(), + fmt.Sprintf("`%s' did not produce an error", s.Name)) + Expect(vaultkv.IsUninitialized(err)).To(BeTrue()) + } + }) +}) + +var _ = When("the vault is initialized", func() { + type spec struct { + Name string + Setup func() + MinVersion *semver + } + + var initOut *vaultkv.InitVaultOutput + + BeforeEach(func() { + initOut, err = vault.InitVault(vaultkv.InitConfig{ + Shares: 1, + Threshold: 1, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + When("the vault is sealed", func() { + Specify("Most commands should return ErrSealed", func() { + for _, s := range []spec{ + spec{"Health", func() { err = vault.Health(true) }, nil}, + spec{"EnableSecretsMount", func() { err = vault.EnableSecretsMount("beep", vaultkv.Mount{}) }, nil}, + spec{"DisableSecretsMount", func() { err = vault.DisableSecretsMount("beep") }, nil}, + spec{"Get", func() { err = vault.Get("secret/sure/whatever", nil) }, nil}, + spec{"Set", func() { err = vault.Set("secret/sure/whatever", map[string]string{"foo": "bar"}) }, nil}, + spec{"Delete", func() { err = vault.Delete("secret/sure/whatever") }, nil}, + spec{"List", func() { _, err = vault.List("secret/sure/whatever") }, nil}, + spec{"V2Get", func() { _, err = vault.V2Get("secret", "foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"V2Set", func() { _, err = vault.V2Set("secret", "foo", map[string]string{"beep": "boop"}, nil) }, &semver{0, 10, 0}}, + spec{"V2Delete", func() { err = vault.V2Delete("secret", "foo", nil) }, &semver{0, 10, 0}}, + spec{"V2Undelete", func() { err = vault.V2Undelete("secret", "foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"V2Destroy", func() { err = vault.V2Destroy("secret", "foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"V2DestroyMetadata", func() { err = vault.V2DestroyMetadata("secret", "foo") }, &semver{0, 10, 0}}, + spec{"V2GetMetadata", func() { _, err = vault.V2GetMetadata("secret", "foo") }, &semver{0, 10, 0}}, + spec{"KVGet", func() { _, err = vault.NewKV().Get("secret/foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"KVSet", func() { _, err = vault.NewKV().Set("secret/foo", map[string]string{"beep": "boop"}, nil) }, &semver{0, 10, 10}}, + spec{"KVDelete", func() { err = vault.NewKV().Delete("secret/foo", nil) }, &semver{0, 10, 0}}, + spec{"KVUndelete", func() { err = vault.NewKV().Undelete("secret/foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"KVDestroy", func() { err = vault.NewKV().Destroy("secret/foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"KVDestroyAll", func() { err = vault.NewKV().DestroyAll("secret/foo") }, &semver{0, 10, 0}}, + } { + if s.MinVersion != nil && parseSemver(currentVaultVersion).LessThan(*s.MinVersion) { + continue + } + (s.Setup)() + Expect(err).To(HaveOccurred(), + fmt.Sprintf("`%s' did not produce an error", s.Name)) + Expect(vaultkv.IsSealed(err)).To(BeTrue(), + fmt.Sprintf("`%s' did not make error of type *ErrSealed", s.Name)) + } + }) + + When("the vault is unsealed", func() { + BeforeEach(func() { + sealState, err := vault.Unseal(initOut.Keys[0]) + Expect(err).NotTo(HaveOccurred()) + Expect(sealState).NotTo(BeNil()) + Expect(sealState.Sealed).To(BeFalse()) + }) + Specify("KV commands targeted at non-existent things should 404", func() { + for _, s := range []spec{ + spec{"Get", func() { err = vault.Get("secret/sure/whatever", nil) }, nil}, + spec{"List", func() { _, err = vault.List("secret/sure/whatever") }, nil}, + spec{"V2Get", func() { _, err = vault.V2Get("secret", "foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"V2GetMetadata", func() { _, err = vault.V2GetMetadata("secret", "foo") }, &semver{0, 10, 0}}, + } { + if s.MinVersion != nil && parseSemver(currentVaultVersion).LessThan(*s.MinVersion) { + continue + } + (s.Setup)() + Expect(err).To(HaveOccurred(), + fmt.Sprintf("`%s' did not produce an error", s.Name)) + Expect(vaultkv.IsNotFound(err)).To(BeTrue(), + fmt.Sprintf("`%s' did not make error of type *ErrNotFound", s.Name)) + } + }) + + When("the auth token is wrong", func() { + BeforeEach(func() { + //If this is your token, I'm sorry + vault.AuthToken = "01234567-89ab-cdef-0123-456789abcdef" + }) + Specify("Most commands should give a 403", func() { + for _, s := range []spec{ + spec{"EnableSecretsMount", func() { err = vault.EnableSecretsMount("beep", vaultkv.Mount{}) }, nil}, + spec{"DisableSecretsMount", func() { err = vault.DisableSecretsMount("beep") }, nil}, + spec{"Get", func() { err = vault.Get("secret/sure/whatever", nil) }, nil}, + spec{"Set", func() { err = vault.Set("secret/sure/whatever", map[string]string{"foo": "bar"}) }, nil}, + spec{"Delete", func() { err = vault.Delete("secret/sure/whatever") }, nil}, + spec{"List", func() { _, err = vault.List("secret/sure/whatever") }, nil}, + spec{"V2Get", func() { _, err = vault.V2Get("secret", "foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"V2Set", func() { _, err = vault.V2Set("secret", "foo", map[string]string{"beep": "boop"}, nil) }, &semver{0, 10, 0}}, + spec{"V2Delete", func() { err = vault.V2Delete("secret", "foo", nil) }, &semver{0, 10, 0}}, + spec{"V2Undelete", func() { err = vault.V2Undelete("secret", "foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"V2Destroy", func() { err = vault.V2Destroy("secret", "foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"V2DestroyMetadata", func() { err = vault.V2DestroyMetadata("secret", "foo") }, &semver{0, 10, 0}}, + spec{"V2GetMetadata", func() { _, err = vault.V2GetMetadata("secret", "foo") }, &semver{0, 10, 0}}, + spec{"KVGet", func() { _, err = vault.NewKV().Get("secret/foo", nil, nil) }, &semver{0, 10, 0}}, + spec{"KVSet", func() { _, err = vault.NewKV().Set("secret/foo", map[string]string{"beep": "boop"}, nil) }, &semver{0, 10, 10}}, + spec{"KVDelete", func() { err = vault.NewKV().Delete("secret/foo", &vaultkv.KVDeleteOpts{V1Destroy: true}) }, &semver{0, 10, 0}}, + spec{"KVUndelete", func() { err = vault.NewKV().Undelete("secret/foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"KVDestroy", func() { err = vault.NewKV().Destroy("secret/foo", []uint{1}) }, &semver{0, 10, 0}}, + spec{"KVDestroyAll", func() { err = vault.NewKV().DestroyAll("secret/foo") }, &semver{0, 10, 0}}, + } { + (s.Setup)() + Expect(err).To(HaveOccurred(), + fmt.Sprintf("`%s' did not produce an error", s.Name)) + Expect(vaultkv.IsForbidden(err)).To(BeTrue(), + fmt.Sprintf("`%s' did not give a 403 - gave %s", s.Name, err)) + } + }) + }) + }) + }) +}) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/client.go b/vendor/github.com/cloudfoundry-community/vaultkv/client.go index d09a732..2ae59f9 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/client.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/client.go @@ -31,6 +31,8 @@ type vaultResponse struct { //There's totally more to the response, but this is all I care about atm. } +//URL encoded values can be given as a *url.Values as "input" when performing +// a GET call func (v *Client) doRequest( method, path string, input interface{}, @@ -42,11 +44,12 @@ func (v *Client) doRequest( u.Host = fmt.Sprintf("%s:8200", u.Host) } + var query url.Values var body io.Reader if input != nil { if strings.ToUpper(method) == "GET" { //Input has to be a url.Values - u.RawQuery = input.(url.Values).Encode() + query = input.(url.Values) } else { body = &bytes.Buffer{} err := json.NewEncoder(body.(*bytes.Buffer)).Encode(input) @@ -56,10 +59,40 @@ func (v *Client) doRequest( } } - req, err := http.NewRequest(method, u.String(), body) + resp, err := v.Curl(method, path, query, body) if err != nil { return err } + defer resp.Body.Close() + + if resp.StatusCode/100 != 2 { + return v.parseError(resp) + } + + if output != nil && resp.StatusCode == 200 { + err = json.NewDecoder(resp.Body).Decode(output) + } + + return err +} + +//Curl takes the given path, prepends /v1/ to it, and makes the request +// with the remainder of the given parameters. Errors returned only reflect +// transport errors, not HTTP semantic errors +func (v *Client) Curl(method string, path string, urlQuery url.Values, body io.Reader) (*http.Response, error) { + //Setup URL + u := *v.VaultURL + u.Path = fmt.Sprintf("/v1/%s", strings.Trim(path, "/")) + if u.Port() == "" { + u.Host = fmt.Sprintf("%s:8200", u.Host) + } + u.RawQuery = urlQuery.Encode() + + //Do the request + req, err := http.NewRequest(method, u.String(), body) + if err != nil { + return nil, err + } if v.Trace != nil { dump, _ := httputil.DumpRequest(req, true) v.Trace.Write([]byte(fmt.Sprintf("Request:\n%s\n", dump))) @@ -69,35 +102,32 @@ func (v *Client) doRequest( if token == "" { token = "01234567-89ab-cdef-0123-456789abcdef" } - req.Header.Add("X-Vault-Token", token) + req.Header.Set("X-Vault-Token", token) client := v.Client if client == nil { client = http.DefaultClient } + if client.CheckRedirect == nil { + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if len(via) > 10 { + return fmt.Errorf("Stopped after 10 redirects") + } + req.Header.Set("X-Vault-Token", token) + return nil + } + } + resp, err := client.Do(req) if err != nil { - return &ErrTransport{message: err.Error()} + return nil, &ErrTransport{message: err.Error()} } - defer resp.Body.Close() if v.Trace != nil { dump, _ := httputil.DumpResponse(resp, true) v.Trace.Write([]byte(fmt.Sprintf("Response:\n%s\n", dump))) } - if resp.StatusCode/100 != 2 { - err = v.parseError(resp) - if err != nil { - return err - } - } - - //If the status code is 204, there is no body. That leaves only 200. - if output != nil && resp.StatusCode == 200 { - err = json.NewDecoder(resp.Body).Decode(&output) - } - - return err + return resp, nil } diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/errors.go b/vendor/github.com/cloudfoundry-community/vaultkv/errors.go index 851a00e..2ac8bec 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/errors.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/errors.go @@ -17,6 +17,12 @@ func (e *ErrBadRequest) Error() string { return e.message } +//IsBadRequest returns true if the error is an ErrBadRequest +func IsBadRequest(err error) bool { + _, is := err.(*ErrBadRequest) + return is +} + //ErrForbidden represents 403 status codes returned from the API. This could be // if your auth is wrong or expired, or you simply don't have access to do the // particular thing you're trying to do. Check your privilege. @@ -28,6 +34,12 @@ func (e *ErrForbidden) Error() string { return e.message } +//IsForbidden returns true if the error is an ErrForbidden +func IsForbidden(err error) bool { + _, is := err.(*ErrForbidden) + return is +} + //ErrNotFound represents 404 status codes returned from the API. This could be // either that the thing you're looking for doesn't exist, or in some cases // that you don't have access to the thing you're looking for and that Vault is @@ -40,6 +52,12 @@ func (e *ErrNotFound) Error() string { return e.message } +//IsNotFound returns true if the error is an ErrNotFound +func IsNotFound(err error) bool { + _, is := err.(*ErrNotFound) + return is +} + //ErrStandby is only returned from Health() if standbyok is set to false and the // node you're querying is a standby. type ErrStandby struct { @@ -50,6 +68,12 @@ func (e *ErrStandby) Error() string { return e.message } +//IsErrStandby returns true if the error is an ErrStandby +func IsErrStandby(err error) bool { + _, is := err.(*ErrStandby) + return is +} + //ErrInternalServer represents 500 status codes that are returned from the API. //See: their fault. type ErrInternalServer struct { @@ -60,6 +84,12 @@ func (e *ErrInternalServer) Error() string { return e.message } +//IsInternalServer returns true if the error is an ErrInternalServer +func IsInternalServer(err error) bool { + _, is := err.(*ErrInternalServer) + return is +} + //ErrSealed represents the 503 status code that is returned by Vault most // commonly if the Vault is currently sealed, but could also represent the Vault // being in a maintenance state. @@ -71,6 +101,12 @@ func (e *ErrSealed) Error() string { return e.message } +//IsSealed returns true if the error is an ErrSealed +func IsSealed(err error) bool { + _, is := err.(*ErrSealed) + return is +} + //ErrUninitialized represents a 503 status code being returned and the Vault //being uninitialized. type ErrUninitialized struct { @@ -81,6 +117,12 @@ func (e *ErrUninitialized) Error() string { return e.message } +//IsUninitialized returns true if the error is an ErrUninitialized +func IsUninitialized(err error) bool { + _, is := err.(*ErrUninitialized) + return is +} + //ErrTransport is returned if an error was encountered trying to reach the API, // as opposed to an error from the API, is returned type ErrTransport struct { @@ -91,13 +133,39 @@ func (e *ErrTransport) Error() string { return e.message } +//IsTransport returns true if the error is an ErrTransport +func IsTransport(err error) bool { + _, is := err.(*ErrTransport) + return is +} + +//ErrKVUnsupported is returned by the KV object when the user requests an +// operation that cannot be performed by the actual version of the KV backend +// that the KV object is abstracting +type ErrKVUnsupported struct { + message string +} + +func (e *ErrKVUnsupported) Error() string { + return e.message +} + +//IsErrKVUnsupported returns true if the error is an ErrKVUnsupported +func IsErrKVUnsupported(err error) bool { + _, is := err.(*ErrKVUnsupported) + return is +} + type apiError struct { Errors []string `json:"errors"` } func (v *Client) parseError(r *http.Response) (err error) { errorsStruct := apiError{} - json.NewDecoder(r.Body).Decode(&errorsStruct) + err = json.NewDecoder(r.Body).Decode(&errorsStruct) + if err != nil { + return err + } errorMessage := strings.Join(errorsStruct.Errors, "\n") switch r.StatusCode { diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/kv.go b/vendor/github.com/cloudfoundry-community/vaultkv/kv.go index 96db632..796cedf 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/kv.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/kv.go @@ -1,70 +1,418 @@ package vaultkv -//Get retrieves the secret at the given path and unmarshals it into the given -//output object. If the object is nil, an unmarshal will not be attempted (this -//can be used to check for existence). If the object could not be unmarshalled -//into, the resultant error is returned. Example path would be /secret/foo, if -//Key/Value backend were mounted at "/secret". The Vault must be unsealed and -// initialized for this endpoint to work. No assumptions are made about the -// mounting point of your Key/Value backend. -func (v *Client) Get(path string, output interface{}) error { - var unmarshalInto interface{} - if output != nil { - unmarshalInto = &vaultResponse{Data: &output} - } - - err := v.doRequest("GET", path, nil, unmarshalInto) - if err != nil { - return err +import ( + "fmt" + "strings" + "sync" +) + +//KV provides an abstraction to the Vault tree which makes dealing with +// the potential of both KV v1 and KV v2 backends easier to work with. +// KV v1 backends are exposed through this interface much like KV v2 +// backends with only one version. There are limitations around Delete +// and Undelete calls because of the lack of versioning in KV v1 backends. +// See the documentation around those functions for more details. +// An empty KV struct is not request-ready. Please call Client.NewKV instead. +type KV struct { + Client *Client + //Map from mount name to [true if version 2. False otherwise] + mounts map[string]kvMount + lock sync.RWMutex +} + +type kvMount interface { + Get(mount, subpath string, output interface{}, opts *KVGetOpts) (meta KVVersion, err error) + Set(mount, subpath string, values map[string]string, opts *KVSetOpts) (meta KVVersion, err error) + List(mount, subpath string) (paths []string, err error) + Delete(mount, subpath string, opts *KVDeleteOpts) (err error) + Undelete(mount, subpath string, versions []uint) (err error) + Destroy(mount, subpath string, versions []uint) (err error) + DestroyAll(mount, subpath string) (err error) + Versions(mount, subpath string) (ret []KVVersion, err error) + MountVersion() (version uint) +} + +/*==================== + KV V1 +====================*/ +type kvv1Mount struct { + client *Client +} + +func v1ConstructPath(mount, subpath string) string { + mount = strings.Trim(mount, "/") + subpath = strings.Trim(subpath, "/") + return strings.Trim(fmt.Sprintf("%s/%s", mount, subpath), "/") +} + +func (k kvv1Mount) Get(mount, subpath string, output interface{}, opts *KVGetOpts) (meta KVVersion, err error) { + if opts != nil && opts.Version > 1 { + err = &ErrNotFound{"No versions greater than one in KV v1 backend"} + return + } + + path := v1ConstructPath(mount, subpath) + err = k.client.Get(path, output) + if err == nil { + meta.Version = 1 + } + return +} + +func (k kvv1Mount) List(mount, subpath string) (paths []string, err error) { + path := v1ConstructPath(mount, subpath) + return k.client.List(path) +} + +func (k kvv1Mount) Set(mount, subpath string, values map[string]string, opts *KVSetOpts) (meta KVVersion, err error) { + path := v1ConstructPath(mount, subpath) + err = k.client.Set(path, values) + if err == nil { + meta.Version = 1 + } + return +} + +func (k kvv1Mount) Delete(mount, subpath string, opts *KVDeleteOpts) (err error) { + if opts == nil || !opts.V1Destroy { + return &ErrKVUnsupported{"Refusing to destroy KV v1 value from delete call"} + } + + versions := []uint{} + if opts != nil { + versions = opts.Versions + } + + return k.Destroy(mount, subpath, versions) +} + +func (k kvv1Mount) Undelete(mount, subpath string, versions []uint) (err error) { + return &ErrKVUnsupported{"Cannot undelete secret in KV v1 backend"} +} + +func (k kvv1Mount) Destroy(mount, subpath string, versions []uint) (err error) { + shouldDelete := len(versions) == 0 + for _, v := range versions { + if v <= 1 { + shouldDelete = true + } } + if shouldDelete { + path := v1ConstructPath(mount, subpath) + err = k.client.Delete(path) + } return err } -//List returns the list of paths nested directly under the given path. If this -//is not a "directory" for any paths, then ErrNotFound is returned. In the list -//of paths returned on success, if a path ends with a slash, then it is also a -//"directory". The Vault must be unsealed and initialized for this endpoint to -//work. No assumptions are made about the mounting point of your Key/Value -//backend. -func (v *Client) List(path string) ([]string, error) { - ret := []string{} - - err := v.doRequest("LIST", path, nil, &vaultResponse{ - Data: &struct { - Keys *[]string `json:"keys"` - }{ - Keys: &ret, - }, - }) +func (k kvv1Mount) DestroyAll(mount, subpath string) (err error) { + path := v1ConstructPath(mount, subpath) + return k.client.Delete(path) +} + +func (k kvv1Mount) Versions(mount, subpath string) (ret []KVVersion, err error) { + path := v1ConstructPath(mount, subpath) + err = k.client.Get(path, nil) if err != nil { return nil, err } + ret = []KVVersion{{Version: 1}} + return +} + +func (k kvv1Mount) MountVersion() (version uint) { + return 1 +} + +/*==================== + KV V2 +====================*/ +type kvv2Mount struct { + client *Client +} + +func (k kvv2Mount) Get(mount, subpath string, output interface{}, opts *KVGetOpts) (meta KVVersion, err error) { + var o *V2GetOpts + if opts != nil { + o = &V2GetOpts{ + Version: opts.Version, + } + } + + var m V2Version + m, err = k.client.V2Get(mount, subpath, output, o) + if err == nil { + meta.Deleted = m.DeletedAt != nil + meta.Destroyed = m.Destroyed + meta.Version = m.Version + } + return +} + +func (k kvv2Mount) List(mount, subpath string) (paths []string, err error) { + return k.client.V2List(mount, subpath) +} + +func (k kvv2Mount) Set(mount, subpath string, values map[string]string, opts *KVSetOpts) (meta KVVersion, err error) { + var m V2Version + m, err = k.client.V2Set(mount, subpath, values, nil) + if err == nil { + meta.Version = m.Version + } + return +} + +func (k kvv2Mount) Delete(mount, subpath string, opts *KVDeleteOpts) (err error) { + versions := []uint{} + if opts != nil { + versions = opts.Versions + } + return k.client.V2Delete(mount, subpath, &V2DeleteOpts{Versions: versions}) +} + +func (k kvv2Mount) Undelete(mount, subpath string, versions []uint) (err error) { + return k.client.V2Undelete(mount, subpath, versions) +} + +func (k kvv2Mount) Destroy(mount, subpath string, versions []uint) (err error) { + return k.client.V2Destroy(mount, subpath, versions) +} + +func (k kvv2Mount) DestroyAll(mount, subpath string) (err error) { + return k.client.V2DestroyMetadata(mount, subpath) +} + +func (k kvv2Mount) Versions(mount, subpath string) (ret []KVVersion, err error) { + var meta V2Metadata + meta, err = k.client.V2GetMetadata(mount, subpath) + if err != nil { + return nil, err + } + + ret = make([]KVVersion, len(meta.Versions)) + for i := range meta.Versions { + ret[i].Deleted = meta.Versions[i].DeletedAt != nil + ret[i].Destroyed = meta.Versions[i].Destroyed + ret[i].Version = meta.Versions[i].Version + } + return +} + +func (k kvv2Mount) MountVersion() (version uint) { + return 2 +} + +/*========================== + KV Abstraction +==========================*/ + +//NewKV returns an initialized KV object. +func (v *Client) NewKV() *KV { + return &KV{Client: v, mounts: map[string]kvMount{}} +} + +func (k *KV) mountForPath(path string) (mountPath string, ret kvMount, err error) { + pathParts := strings.Split(strings.Trim(path, "/"), "/") + var found bool + k.lock.RLock() + for i := 1; i <= len(pathParts); i++ { + mountPath = strings.Join(pathParts[:i], "/") + ret, found = k.mounts[mountPath] + if found { + break + } + } + k.lock.RUnlock() + if found { + return + } + + k.lock.Lock() + defer k.lock.Unlock() + for i := 1; i <= len(pathParts); i++ { + mountPath = strings.Join(pathParts[:i], "/") + ret, found = k.mounts[mountPath] + if found { + break + } + } + if found { + return + } + + mountPath, isV2, err := k.Client.IsKVv2Mount(path) + if err != nil { + return + } + + ret = kvv1Mount{k.Client} + if isV2 { + ret = kvv2Mount{k.Client} + } + + k.mounts[mountPath] = ret + + return +} + +func subtractMount(mount string, path string) string { + mount = strings.Trim(mount, "/") + path = strings.Trim(path, "/") + var ret string + if mount != path { + ret = strings.Trim(strings.TrimPrefix(path, mount), "/") + } + return ret +} + +//KVGetOpts are options applicable to KV.Get +type KVGetOpts struct { + // Version is the version of the resource to retrieve. Setting this to zero (or + // not setting it at all) will retrieve the latest version + Version uint +} + +//KVVersion contains information about a version of a secret. +type KVVersion struct { + Version uint + Deleted bool + Destroyed bool +} + +//Get retrieves the value at the given path in the tree. This follows the +//semantics of Client.Get or Client.V2Get, chosen based on the backend mounted +//at the path given. +func (k *KV) Get(path string, output interface{}, opts *KVGetOpts) (meta KVVersion, err error) { + mountPath, mount, err := k.mountForPath(path) + if err != nil { + return + } - return ret, err + path = subtractMount(mountPath, path) + return mount.Get(mountPath, path, output, opts) } -//Set puts the values in the given map at the given path. The keys in the map -//become the keys at the path, and the values in the map become the values of -//those keys. The Vault must be unsealed and initialized for this endpoint to -//work. No assumptions are made about the mounting point of your Key/Value -//backend. -func (v *Client) Set(path string, values map[string]string) error { - err := v.doRequest("PUT", path, &values, nil) +//List retrieves the paths under the given path. If the path does not exist or +//it is not a folder, ErrNotFound is thrown. Results ending with a slash are +//folders. +func (k *KV) List(path string) (paths []string, err error) { + mountPath, mount, err := k.mountForPath(path) if err != nil { - return err + return } - return nil + path = subtractMount(mountPath, path) + return mount.List(mountPath, path) } -//Delete attempts to delete the value at the specified path. No error is -//returned if there is already no value at the given path. -func (v *Client) Delete(path string) error { - err := v.doRequest("DELETE", path, nil, nil) +//KVSetOpts are the options for a set call to the KV.Set() call. Currently there +// are none, but it exists in case the API adds support in the future for things +// that we can put here. +type KVSetOpts struct{} + +//Set puts the values given at the path given. If KV v1, the previous value, if +//any, is overwritten. If KV v2, a new version is created. +func (k *KV) Set(path string, values map[string]string, opts *KVSetOpts) (meta KVVersion, err error) { + mountPath, mount, err := k.mountForPath(path) if err != nil { - return err + return } - return nil + path = subtractMount(mountPath, path) + return mount.Set(mountPath, path, values, opts) +} + +//KVDeleteOpts are options applicable to KV.Delete +type KVDeleteOpts struct { + //Versions are the versions of the secret to delete. If left nil, + // the latest version is deleted. + Versions []uint + //V1Destroy, if true, will call Client.Delete if the given path + // to delete is a V1 backend (thus permanently destroying the secret). + // If it is false and the backend is V1, an ErrKVUnsupported error will + // be thrown. This has no effect on KV v2 backends. + V1Destroy bool +} + +//Delete attempts to mark the secret at the given path (and version) as deleted. +// For KV v1, temporarily deleting a secret is not possible. Use the V1Destroy +// option as a way to safeguard against unwanted destruction of secrets. +func (k *KV) Delete(path string, opts *KVDeleteOpts) (err error) { + mountPath, mount, err := k.mountForPath(path) + if err != nil { + return + } + + path = subtractMount(mountPath, path) + return mount.Delete(mountPath, path, opts) +} + +//Undelete attempts to unmark deletion on a previously deleted version. +// KV v1 backends cannot do this, and so if the backend is KV v1, this +// returns an ErrKVUnsupported. +func (k *KV) Undelete(path string, versions []uint) (err error) { + mountPath, mount, err := k.mountForPath(path) + if err != nil { + return + } + + path = subtractMount(mountPath, path) + return mount.Undelete(mountPath, path, versions) +} + +//Destroy attempts to irrevocably delete the given versions at the given +// path. For KV v1 backends, this is a call to Client.Delete. for KV v2 +// backends, this is a call to Client.V2Destroy +func (k *KV) Destroy(path string, versions []uint) (err error) { + mountPath, mount, err := k.mountForPath(path) + if err != nil { + return + } + + path = subtractMount(mountPath, path) + return mount.Destroy(mountPath, path, versions) +} + +//DestroyAll attempts to irrevocably delete all versions of the secret +// at the given path. For KV v1 backends, this is a call to Client.Delete. +// For v2 backends, this is a call to Client.V2DestroyMetadata +func (k *KV) DestroyAll(path string) (err error) { + mountPath, mount, err := k.mountForPath(path) + if err != nil { + return + } + + path = subtractMount(mountPath, path) + return mount.DestroyAll(mountPath, path) +} + +//Versions returns the versions of the secret available. If no secret +// exists at this path, ErrNotFound is returned. If the secret exists +// and this is a KV v1 backend, one version is returned. +func (k *KV) Versions(path string) (ret []KVVersion, err error) { + mountPath, mount, err := k.mountForPath(path) + if err != nil { + return + } + + path = subtractMount(mountPath, path) + return mount.Versions(mountPath, path) +} + +//MountVersion returns the KV version of the mount for the given path. +// v1 mounts return 1; v2 mounts return 2. +func (k *KV) MountVersion(mount string) (version uint, err error) { + _, m, err := k.mountForPath(mount) + if err != nil { + return + } + + return m.MountVersion(), nil +} + +//MountPath returns the path of the mount on which the given path is mounted. +// If no such mount can be found, an error is returned. +func (k *KV) MountPath(path string) (mount string, err error) { + mount, _, err = k.mountForPath(path) + return } diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/kv1.go b/vendor/github.com/cloudfoundry-community/vaultkv/kv1.go new file mode 100644 index 0000000..12eeea5 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/kv1.go @@ -0,0 +1,68 @@ +package vaultkv + +import ( + "fmt" + "reflect" +) + +//Get retrieves the secret at the given path and unmarshals it into the given +//output object using the semantics of encoding/json.Unmarshal. If the object +//is nil, an unmarshal will not be attempted (this can be used to check for +//existence). If the object could not be unmarshalled into, the resultant error +//is returned. Example path would be /secret/foo, if Key/Value backend were +//mounted at "/secret". The Vault must be unsealed and initialized for this +//endpoint to work. No assumptions are made about the mounting point of your +//Key/Value backend. +func (v *Client) Get(path string, output interface{}) error { + if output != nil && + reflect.ValueOf(output).Kind() != reflect.Ptr { + return fmt.Errorf("Get output target must be a pointer if non-nil") + } + + err := v.doRequest("GET", path, nil, &vaultResponse{Data: output}) + if err != nil { + return err + } + + return err +} + +//List returns the list of paths nested directly under the given path. If this +//is not a "directory" for any paths, then ErrNotFound is returned. In the list +//of paths returned on success, if a path ends with a slash, then it is also a +//"directory". The Vault must be unsealed and initialized for this endpoint to +//work. No assumptions are made about the mounting point of your Key/Value +//backend. +func (v *Client) List(path string) ([]string, error) { + ret := []string{} + + err := v.doRequest("LIST", path, nil, &vaultResponse{ + Data: &struct { + Keys *[]string `json:"keys"` + }{ + Keys: &ret, + }, + }) + if err != nil { + return nil, err + } + + return ret, err +} + +//Set puts the values in the given map at the given path. The keys in the map +//become the keys at the path, and the values in the map become the values of +//those keys. The Vault must be unsealed and initialized for this endpoint to +//work. No assumptions are made about the mounting point of your Key/Value +//backend. +func (v *Client) Set(path string, values map[string]string) error { + //TODO: This function should be changed to accept a map[string]interface{} + //Then tests should be written for cases other than map[string]string + return v.doRequest("PUT", path, &values, nil) +} + +//Delete attempts to delete the value at the specified path. No error is +//returned if there is already no value at the given path. +func (v *Client) Delete(path string) error { + return v.doRequest("DELETE", path, nil, nil) +} diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/kv1_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/kv1_test.go new file mode 100644 index 0000000..d1f4256 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/kv1_test.go @@ -0,0 +1,355 @@ +package vaultkv_test + +import ( + "fmt" + "sort" + "strings" + + "github.com/cloudfoundry-community/vaultkv" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("KVv1", func() { + //This is a hack because I don't want to refactor _everything_ in these tests + var getAssertionPath string + + var AssertGetEquals = func(expected map[string]string) func() { + return func() { + output := make(map[string]string) + err = vault.Get(getAssertionPath, &output) + Expect(err).NotTo(HaveOccurred()) + Expect(output).To(Equal(expected)) + } + } + + var AssertExists = func(exists bool) func() { + var fn func() + if exists { + fn = func() { + err = vault.Get(getAssertionPath, nil) + Expect(err).NotTo(HaveOccurred()) + } + } else { + fn = func() { + err = vault.Get(getAssertionPath, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + } + } + + return fn + } + + BeforeEach(func() { + InitAndUnsealVault() + }) + + Describe("Setting something in the Vault", func() { + var testPath string + var testValue map[string]string + BeforeEach(func() { + testPath = "secret/foo" + testValue = map[string]string{ + "foo": "bar", + "beep": "boop", + } + }) + + JustBeforeEach(func() { + err = vault.Set(testPath, testValue) + }) + + When("the value is nil", func() { + BeforeEach(func() { + testValue = nil + getAssertionPath = testPath + }) + + It("should err properly", func() { + By("returning ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("having Get find nothing at this path after the call") + AssertExists(false)() + }) + }) + + When("the path is doesn't correspond to a mounted backend", func() { + BeforeEach(func() { + testPath = "notabackend/foo" + }) + + It("should err properly", func() { + By("returning ErrNotFound") + AssertErrorOfType(&vaultkv.ErrNotFound{})() + + By("having Get find nothing at this path after the call") + AssertExists(false)() + }) + }) + + When("the path has a leading slash", func() { + BeforeEach(func() { + testPath = "/secret/foo" + }) + + It("should get inserted properly", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having get find the key at the path without a slash") + getAssertionPath = strings.TrimPrefix(testPath, "/") + AssertExists(true)() + + By("having get find the key at the path without a slash") + AssertGetEquals(map[string]string{"foo": "bar", "beep": "boop"})() + + By("having get find the key at the path with a slash") + getAssertionPath = testPath + AssertExists(true)() + + By("having get find the key at the path with a slash") + AssertGetEquals(map[string]string{"foo": "bar", "beep": "boop"})() + }) + }) + + When("the path has a trailing slash", func() { + BeforeEach(func() { + testPath = "secret/foo/" + }) + + It("should get inserted properly", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having get find the key at the path without a slash") + getAssertionPath = strings.TrimSuffix(testPath, "/") + AssertExists(true)() + + By("having get find the key at the path without a slash") + AssertGetEquals(map[string]string{"foo": "bar", "beep": "boop"})() + + By("having get find the key at the path with a slash") + getAssertionPath = testPath + AssertExists(true)() + + By("having get find the key at the path with a slash") + AssertGetEquals(map[string]string{"foo": "bar", "beep": "boop"})() + }) + }) + + When("setting an already set key", func() { + var secondTestValue map[string]string + BeforeEach(func() { + secondTestValue = map[string]string{ + "thisisanotherkey": "thisisanothervalue", + } + getAssertionPath = testPath + }) + + JustBeforeEach(func() { + err = vault.Set(testPath, secondTestValue) + }) + + It("should overwrite the value", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having Get find the value that was added second") + AssertGetEquals(map[string]string{"thisisanotherkey": "thisisanothervalue"}) + }) + }) + + Describe("Get", func() { + var getTestPath string + var getOutputValue map[string]string + + BeforeEach(func() { + getOutputValue = make(map[string]string) + }) + + JustBeforeEach(func() { + err = vault.Get(getTestPath, &getOutputValue) + }) + + When("the key exists", func() { + BeforeEach(func() { + getTestPath = testPath + }) + + It("should retrieve the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the value that was inserted") + Expect(getOutputValue).To(Equal(testValue)) + }) + }) + + When("the key doesn't exist", func() { + BeforeEach(func() { + getTestPath = fmt.Sprintf("%sabcd", testPath) + }) + + It("should return ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) + }) + }) + + Describe("Delete", func() { + var deleteTestPath string + JustBeforeEach(func() { + err = vault.Delete(deleteTestPath) + }) + + When("the key exists", func() { + BeforeEach(func() { + deleteTestPath = testPath + getAssertionPath = testPath + }) + + It("should delete the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("get not finding the deleted key") + AssertExists(false)() + }) + }) + + When("the key doesn't exist", func() { + BeforeEach(func() { + deleteTestPath = fmt.Sprintf("%sabcd", testPath) + }) + + It("should not return an error", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) + + Describe("Adding another key with multiple parts", func() { + var secondTestPath string + var secondTestValue map[string]string + BeforeEach(func() { + secondTestPath = "secret/beep/bar" + secondTestValue = map[string]string{ + "werealljustlittlebabybirds": "peckingourwayoutofourshells", + } + }) + + JustBeforeEach(func() { + err = vault.Set(secondTestPath, secondTestValue) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("List", func() { + var listTestPath string + var listTestOutput []string + + JustBeforeEach(func() { + listTestOutput, err = vault.List(listTestPath) + }) + + //Order doesn't matter + var AssertListEquals = func(expected []string) func() { + return func() { + Expect(listTestOutput).ToNot(BeNil()) + Expect(expected).ToNot(BeNil()) + sort.Strings(expected) + sort.Strings(listTestOutput) + Expect(listTestOutput).To(Equal(expected)) + } + } + + Context("on `secret'", func() { + BeforeEach(func() { + listTestPath = "secret" + }) + + It("should return the correct paths", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the correct list of paths") + AssertListEquals([]string{"foo", "beep/"}) + }) + }) + + Context("on the dir of the nested key", func() { + BeforeEach(func() { + listTestPath = "secret/beep" + }) + + It("should return the correct paths", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the correct list of paths") + AssertListEquals([]string{"bar"}) + }) + }) + + When("the path doesn't exist", func() { + BeforeEach(func() { + listTestPath = "secret/boo/hiss" + }) + + It("should return an ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) + }) + + When("the path is a secret, not a folder", func() { + BeforeEach(func() { + listTestPath = "secret/foo" + }) + + It("should return an ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) + }) + }) + + Describe("Get", func() { + var getTestPath string + var getOutputValue map[string]string + + BeforeEach(func() { + getOutputValue = make(map[string]string) + }) + + JustBeforeEach(func() { + err = vault.Get(getTestPath, &getOutputValue) + }) + Context("on the nested key", func() { + BeforeEach(func() { + getTestPath = secondTestPath + }) + + It("should retrieve the value", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the value that was inserted") + Expect(getOutputValue).To(Equal(secondTestValue)) + }) + }) + }) + + Describe("Delete", func() { + var deleteTestPath string + JustBeforeEach(func() { + err = vault.Delete(deleteTestPath) + }) + Context("on the nested key", func() { + BeforeEach(func() { + deleteTestPath = secondTestPath + getAssertionPath = deleteTestPath + }) + + It("should delete the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having Get be unable to find the key") + AssertExists(false)() + }) + }) + }) + }) + }) +}) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/kv2.go b/vendor/github.com/cloudfoundry-community/vaultkv/kv2.go new file mode 100644 index 0000000..6b3e563 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/kv2.go @@ -0,0 +1,390 @@ +package vaultkv + +import ( + "fmt" + "net/url" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +//Use some heuristics to determine what is the most likely mount path if Vault won't +// tell us with its old, crusty version +func mountPathDefault(path string) string { + path = strings.TrimLeft(path, "/") + var prefix string + if strings.HasPrefix(path, "auth/") { + path = strings.TrimPrefix(path, "auth/") + prefix = "auth/" + } + + return fmt.Sprintf("%s%s", prefix, strings.Split(path, "/")[0]) +} + +//IsKVv2Mount returns true if the mount is a version 2 KV mount and false +//otherwise. This will also simply return false if no mount exists at the given +//mount point or if the Vault is too old to have the API endpoint to look for +//the mount. If a different API error occurs, it will be propagated out. +func (c *Client) IsKVv2Mount(path string) (mountPath string, isV2 bool, err error) { + path = strings.TrimPrefix(path, "/") + output := struct { + Data struct { + Secret map[string]struct { + Type string `json:"type"` + Options struct { + Version string `json:"version"` + } `json:"options"` + } `json:"secret"` + } `json:"data"` + }{} + + err = c.doRequest( + "GET", + fmt.Sprintf("/sys/internal/ui/mounts"), + nil, &output) + + mountPath = strings.Trim(mountPathDefault(path), "/") + if err != nil { + //If we got a 404, this version of Vault is too old to possibly have a v2 backend + if _, is404 := err.(*ErrNotFound); is404 { + err = nil + } + + //Then either the token is invalid (and we should err) or this could be + // an old version of Vault that doesn't have this endpoint yet, and so its + // interpreting this as a call to the sys/* region which this token may not have + // access to. In this case, it would be too old of a version to have a v2 backend. + if _, is403 := err.(*ErrForbidden); is403 { + if c.TokenIsValid() == nil { + err = nil + } + } + + return + } + + if output.Data.Secret == nil { + return + } + + path = strings.Replace(path, "//", "/", -1) + path = strings.Trim(path, "/") + pathSplit := strings.Split(path, "/") + + for i := 1; i <= len(pathSplit); i++ { + thisPath := strings.Join(pathSplit[:i], "/") + "/" + if out, found := output.Data.Secret[thisPath]; found { + mountPath = strings.TrimRight(thisPath, "/") + isV2 = out.Options.Version == "2" + break + } + } + + return +} + +//SplitMount takes the given path and splits it into the respective mount name +// and path under the mount and returns both parts +func SplitMount(path string) (mount string, subpath string) { + path = strings.TrimLeft(path, "/") + splits := strings.SplitN(path, "/", 2) + mount = splits[0] + if len(splits) > 1 { + subpath = splits[1] + } + + subpath = fmt.Sprintf("/%s", strings.TrimLeft(subpath, "/")) + return +} + +//V2Version is information about a version of a secret. The DeletedAt member +// will be nil to signify that a version is not deleted. Take note of the +// difference between "deleted" and "destroyed" - a deletion simply marks a +// secret as deleted, preventing it from being read. A destruction actually +// removes the data from storage irrevocably. +type V2Version struct { + CreatedAt time.Time + DeletedAt *time.Time + Destroyed bool + Version uint +} + +type v2VersionAPI struct { + CreatedTime string `json:"created_time"` + DeletionTime string `json:"deletion_time"` + Destroyed bool `json:"destroyed"` + Version uint `json:"version"` +} + +func (v v2VersionAPI) Parse() V2Version { + ret := V2Version{ + Destroyed: v.Destroyed, + Version: v.Version, + } + + //Parse those times + ret.CreatedAt, _ = time.Parse(time.RFC3339Nano, v.CreatedTime) + tmpDeletion, err := time.Parse(time.RFC3339Nano, v.DeletionTime) + if err == nil { + ret.DeletedAt = &tmpDeletion + } + + return ret +} + +//V2GetOpts are options to specify in a V2Get request. +type V2GetOpts struct { + // Version is the version of the resource to retrieve. Setting this to zero (or + // not setting it at all) will retrieve the latest version + Version uint +} + +//V2Get will get a secret from the given path in a KV version 2 secrets backend. +//If the secret is at "/bar" in the backend mounted at "foo", then the path +//should be "foo/bar". The response will be decoded into the item pointed to +//by output using encoding/json.Unmarshal semantics. The version to retrieve +//can be selected by setting Version in the V2GetOpts struct at opts. +func (c *Client) V2Get(mount, subpath string, output interface{}, opts *V2GetOpts) (meta V2Version, err error) { + if output != nil && + reflect.ValueOf(output).Kind() != reflect.Ptr { + err = fmt.Errorf("V2Get output target must be a pointer if non-nil") + return + } + + type outputData struct { + Metadata v2VersionAPI `json:"metadata"` + Data interface{} `json:"data"` + } + + unmarshalInto := &struct { + Data outputData `json:"data"` + }{ + Data: outputData{ + Metadata: v2VersionAPI{}, + Data: output, + }, + } + + query := url.Values{} + if opts != nil { + query.Add("version", strconv.FormatUint(uint64(opts.Version), 10)) + } + + path := fmt.Sprintf("%s/data/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + err = c.doRequest("GET", path, query, unmarshalInto) + if err != nil { + return + } + + meta = unmarshalInto.Data.Metadata.Parse() + return +} + +//V2List returns the list of paths nested directly under the given path. If this +//is not a "directory" for any paths, then ErrNotFound is returned. In the list +//of paths returned on success, if a path ends with a slash, then it is also a +//"directory". The Vault must be unsealed and initialized for this endpoint to +//work. No assumptions are made about the mounting point of your Key/Value +//backend. +func (v *Client) V2List(mount, subpath string) ([]string, error) { + ret := []string{} + path := fmt.Sprintf("%s/metadata/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + + err := v.doRequest("LIST", path, nil, &vaultResponse{ + Data: &struct { + Keys *[]string `json:"keys"` + }{ + Keys: &ret, + }, + }) + if err != nil { + return nil, err + } + + return ret, err +} + +//V2SetOpts are options that can be specified to a V2Set call +type V2SetOpts struct { + //CAS provides a check-and-set version number. If this is set to zero, then + // the value will only be written if the key does not yet exist. If the CAS + //number is non-zero, then this will only be written if the current version + //for your this secret matches the CAS value. + CAS *uint `json:"cas,omitempty"` +} + +//WithCAS returns a pointer to a new V2SetOpts with the CAS value set to the +//given value. If i is zero, then the value will only be written if the key +//does not exist. If i is non-zero, then the value will only be written if the +//currently existing version matches i. Not calling CAS will result in no +//restriction on writing. If the mount is set up for requiring CAS, then not +//setting CAS with this function a valid number will result in a failure when +//attempting to write. +func (s V2SetOpts) WithCAS(i uint) *V2SetOpts { + s.CAS = new(uint) + *s.CAS = i + return &s +} + +//V2Set uses encoding/json.Marshal on the object given in values to encode +// the secret as JSON, and writes it to the path given. Populate ops to use the +// check-and-set functionality. Returns the metadata about the written secret +// if the write is successful. +func (c *Client) V2Set(mount, subpath string, values interface{}, opts *V2SetOpts) (meta V2Version, err error) { + input := struct { + Options *V2SetOpts `json:"options,omitempty"` + Data interface{} `json:"data"` + }{ + Options: opts, + Data: &values, + } + + output := struct { + Data v2VersionAPI `json:"data"` + }{ + Data: v2VersionAPI{}, + } + + path := fmt.Sprintf("%s/data/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + + err = c.doRequest("PUT", path, &input, &output) + if err != nil { + return + } + + meta = output.Data.Parse() + return +} + +//V2DeleteOpts are options that can be provided to a V2Delete call. +type V2DeleteOpts struct { + Versions []uint `json:"versions"` +} + +//V2Delete marks a secret version at the given path as deleted. If opts is not +// provided or the Versions slice therein is left nil, the latest version is +// deleted. Otherwise, the specified versions are deleted. Note that the deleted +// data from this call is recoverable from a call to V2Undelete. +func (c *Client) V2Delete(mount, subpath string, opts *V2DeleteOpts) error { + method := "DELETE" + path := fmt.Sprintf("%s/data/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + + if opts != nil && len(opts.Versions) > 0 { + method = "POST" + path = fmt.Sprintf("%s/delete/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + } else { + opts = nil + } + + return c.doRequest(method, path, opts, nil) +} + +//V2Undelete marks the specified versions at the specified paths as not deleted. +func (c *Client) V2Undelete(mount, subpath string, versions []uint) error { + path := fmt.Sprintf("%s/undelete/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + return c.doRequest("POST", path, struct { + Versions []uint `json:"versions"` + }{ + Versions: versions, + }, nil) +} + +//V2Destroy permanently deletes the specified versions at the specified path. +func (c *Client) V2Destroy(mount, subpath string, versions []uint) error { + path := fmt.Sprintf("%s/destroy/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + return c.doRequest("POST", path, struct { + Versions []uint `json:"versions"` + }{ + Versions: versions, + }, nil) +} + +//V2DestroyMetadata permanently destroys all secret versions and all metadata +// associated with the secret at the specified path. +func (c *Client) V2DestroyMetadata(mount, subpath string) error { + path := fmt.Sprintf("%s/metadata/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + return c.doRequest("DELETE", path, nil, nil) +} + +//V2Metadata is the metadata associated with a secret +type V2Metadata struct { + CreatedAt time.Time + UpdatedAt time.Time + //CurrentVersion is the highest version number that has been created for this + //secret. Deleteing or destroying the highest version does not change this + //number. + CurrentVersion uint + OldestVersion uint + MaxVersions uint + Versions []V2Version +} + +type v2MetadataAPI struct { + Data struct { + CreatedTime string `json:"created_time"` + CurrentVersion uint `json:"current_version"` + MaxVersions uint `json:"max_versions"` + OldestVersion uint `json:"oldest_version"` + UpdatedTime string `json:"updated_time"` + Versions map[string]v2VersionAPI `json:"versions"` + } `json:"data"` +} + +//Version returns the version with the given number in the metadata as a +//V2Version object , if present. If no version with that number is present, an +//error is returned. +func (m V2Metadata) Version(number uint) (version V2Version, err error) { + if len(m.Versions) == 0 { + err = fmt.Errorf("That version does not exist in the metadata") + return + } + + firstVersion := m.Versions[0] + index := int(number) - int(firstVersion.Version) + if index < 0 || index > len(m.Versions) { + err = fmt.Errorf("That version does not exist in the metadata") + return + } + + version = m.Versions[index] + return +} + +func (m v2MetadataAPI) Parse() V2Metadata { + ret := V2Metadata{ + CurrentVersion: m.Data.CurrentVersion, + MaxVersions: m.Data.MaxVersions, + OldestVersion: m.Data.OldestVersion, + } + + ret.CreatedAt, _ = time.Parse(time.RFC3339Nano, m.Data.CreatedTime) + ret.UpdatedAt, _ = time.Parse(time.RFC3339Nano, m.Data.UpdatedTime) + + for number, metadata := range m.Data.Versions { + toAdd := metadata.Parse() + version64, _ := strconv.ParseUint(number, 10, 64) + toAdd.Version = uint(version64) + ret.Versions = append(ret.Versions, toAdd) + } + + sort.Slice(ret.Versions, + func(i, j int) bool { return ret.Versions[i].Version < ret.Versions[j].Version }, + ) + + return ret +} + +//V2GetMetadata gets the metadata associated with the secret at the specified +// path. +func (c *Client) V2GetMetadata(mount, subpath string) (meta V2Metadata, err error) { + path := fmt.Sprintf("%s/metadata/%s", strings.Trim(mount, "/"), strings.Trim(subpath, "/")) + output := v2MetadataAPI{} + err = c.doRequest("GET", path, nil, &output) + if err != nil { + return + } + meta = output.Parse() + return +} diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/kv2_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/kv2_test.go new file mode 100644 index 0000000..40c6bdf --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/kv2_test.go @@ -0,0 +1,632 @@ +package vaultkv_test + +import ( + "fmt" + "strings" + "time" + + "github.com/cloudfoundry-community/vaultkv" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("KVv2", func() { + const testMountName = "beep/bada/boop" + BeforeEach(func() { + if parseSemver(currentVaultVersion).LessThan(semver{0, 10, 0}) { + Skip("This version of Vault does not support KVv2") + } else { + InitAndUnsealVault() + err = vault.EnableSecretsMount(testMountName, vaultkv.Mount{ + Type: vaultkv.MountTypeKV, + Options: vaultkv.KVMountOptions{}.WithVersion(2), + }) + + Expect(err).NotTo(HaveOccurred()) + } + }) + + path := func(mount, path string) (subpath string) { + mount = strings.Trim(mount, "/") + path = strings.Trim(path, "/") + if mount == path { + return "" + } + ret := strings.TrimPrefix(path, fmt.Sprintf("%s/", mount)) + return ret + } + + Describe("V2Set", func() { + var testSetPath string + var testSetValues map[string]interface{} + var testSetOptions *vaultkv.V2SetOpts + var testVersionOutput vaultkv.V2Version + BeforeEach(func() { + testSetPath = fmt.Sprintf("%s/boop", testMountName) + }) + + JustBeforeEach(func() { + testVersionOutput, err = vault.V2Set(testMountName, path(testMountName, testSetPath), testSetValues, testSetOptions) + }) + + AfterEach(func() { + testSetValues = nil + testSetOptions = nil + testVersionOutput = vaultkv.V2Version{} + }) + + Context("With a nil input", func() { + BeforeEach(func() { + testSetValues = nil + }) + + It("should write nil to the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the proper version output") + Expect(testVersionOutput.Version).To(BeEquivalentTo(1)) + }) + + Describe("V2Get", func() { + When("outputting into a pointer", func() { + var testGetOutput *map[string]interface{} + JustBeforeEach(func() { + testGetOutput = &map[string]interface{}{} + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), &testGetOutput, nil) + }) + + It("should populate the pointer properly", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("setting the pointer to nil") + Expect(testGetOutput).To(BeNil()) + }) + }) + + When("outputting into a map", func() { + var testGetOutput map[string]interface{} + JustBeforeEach(func() { + testGetOutput = map[string]interface{}{} + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), &testGetOutput, nil) + }) + + It("should populate the map properly", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("leaving the map empty") + Expect(testGetOutput).To(BeEmpty()) + }) + }) + }) + }) + + Context("With a non-empty map input", func() { + BeforeEach(func() { + testSetValues = map[string]interface{}{"foo": "bar"} + }) + + It("should write the proper values to the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the proper version output") + Expect(testVersionOutput.Version).To(BeEquivalentTo(1)) + }) + + Describe("V2Get", func() { + var testGetOutput map[string]interface{} + var testGetVersionOutput vaultkv.V2Version + JustBeforeEach(func() { + testGetOutput = map[string]interface{}{} + testGetVersionOutput, err = vault.V2Get(testMountName, path(testMountName, testSetPath), &testGetOutput, nil) + }) + + It("should get the undeleted key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the same version info as the Set") + Expect(testGetVersionOutput).To(Equal(testVersionOutput)) + + By("returning the same values that were set") + Expect(testGetOutput).To(Equal(testSetValues)) + }) + + Describe("V2List", func() { + var testListPath string + var testListOutput []string + JustBeforeEach(func() { + testListOutput, err = vault.V2List(testMountName, path(testMountName, testListPath)) + }) + + When("the path exists", func() { + BeforeEach(func() { + _, err = vault.V2Set(testMountName, "foo/bar", testSetValues, nil) + Expect(err).NotTo(HaveOccurred()) + testListPath = testMountName + }) + + It("should list the keys", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning the expected keys") + Expect(testListOutput).To(Equal([]string{"boop", "foo/"})) + }) + }) + + When("the path does not exist", func() { + BeforeEach(func() { + testListPath = fmt.Sprintf("%s/this/shouldnt/exist", testMountName) + }) + + It("should return ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) + }) + }) + }) + + Describe("V2Delete", func() { + var testDeleteOptions *vaultkv.V2DeleteOpts + JustBeforeEach(func() { + err = vault.V2Delete(testMountName, path(testMountName, testSetPath), testDeleteOptions) + }) + Context("Not specifying a version to delete", func() { + BeforeEach(func() { + testDeleteOptions = nil + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + + Describe("V2Get", func() { + JustBeforeEach(func() { + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), nil, nil) + }) + + It("should return ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) + }) + + Describe("V2GetMetadata", func() { + var testMetadataOutput vaultkv.V2Metadata + JustBeforeEach(func() { + testMetadataOutput, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + }) + + It("should return metadata reflecting the delete", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having the latest version be 1") + Expect(testMetadataOutput.CurrentVersion).To(BeEquivalentTo(1)) + + By("marking creation as at the correct time") + Expect(time.Since(testMetadataOutput.CreatedAt) < time.Second*5).To(BeTrue()) + + By("having the correct number of versions") + Expect(testMetadataOutput.Versions).To(HaveLen(1)) + + By("having a version numbered '1'") + version, versionErr := testMetadataOutput.Version(1) + Expect(versionErr).NotTo(HaveOccurred()) + + By("marking version 1 as having been deleted") + Expect(version.DeletedAt).ToNot(BeNil()) + + By("marking version deletion as at the correct time") + Expect(time.Since(*version.DeletedAt) < time.Second*5).To(BeTrue()) + + By("marking version creation as at the correct time") + Expect(time.Since(version.CreatedAt) < time.Second*5).To(BeTrue()) + }) + }) + + Describe("V2Undelete", func() { + JustBeforeEach(func() { + err = vault.V2Undelete(testMountName, path(testMountName, testSetPath), []uint{testVersionOutput.Version}) + }) + + It("should undelete the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2Get finding the undeleted key") + testGetOutput := map[string]interface{}{} + var testGetVersionOutput vaultkv.V2Version + testGetVersionOutput, err = vault.V2Get(testMountName, path(testMountName, testSetPath), &testGetOutput, nil) + Expect(err).NotTo(HaveOccurred()) + + By("V2Get returning the V2Set's original version info") + Expect(testGetVersionOutput).To(Equal(testVersionOutput)) + + By("V2Get returning the originally set values") + Expect(testGetOutput).To(Equal(testSetValues)) + }) + + Describe("V2GetMetadata", func() { + var testMetadataOutput vaultkv.V2Metadata + JustBeforeEach(func() { + testMetadataOutput, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + }) + + It("should return metadata reflecting the undelete", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having the current version be 1") + Expect(testMetadataOutput.CurrentVersion).To(BeEquivalentTo(1)) + + By("marking creation as at the correct time") + Expect(time.Since(testMetadataOutput.CreatedAt) < time.Second*5).To(BeTrue()) + + By("having the correct number of versions") + Expect(testMetadataOutput.Versions).To(HaveLen(1)) + + By("having a version numbered '1'") + version, versionErr := testMetadataOutput.Version(1) + Expect(versionErr).NotTo(HaveOccurred()) + + By("having version 1 not marked as deleted") + Expect(version.DeletedAt).To(BeNil()) + + By("marking version creation as at the correct time") + Expect(time.Since(version.CreatedAt) < time.Second*5).To(BeTrue()) + }) + }) + }) + + }) + + Context("Specifying a version to delete", func() { + When("the version exists", func() { + BeforeEach(func() { + testDeleteOptions = &vaultkv.V2DeleteOpts{ + Versions: []uint{1}, + } + }) + + It("should delete the specified version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2Get being unable to find it") + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{})() + }) + + Context("and then deleting it again", func() { + JustBeforeEach(func() { + err = vault.V2Delete(testMountName, path(testMountName, testSetPath), testDeleteOptions) + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) + + When("the version does not exist", func() { + BeforeEach(func() { + testDeleteOptions = &vaultkv.V2DeleteOpts{ + Versions: []uint{12}, + } + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) + }) + + Describe("V2Destroy", func() { + When("the version exists and it is the only version", func() { + JustBeforeEach(func() { + err = vault.V2Destroy(testMountName, path(testMountName, testSetPath), []uint{1}) + }) + + It("should delete the metadata", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2Get being unable to find the key") + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + + By("V2GetMetadata being unable to find the key") + _, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + }) + }) + + When("the version does not exist", func() { + JustBeforeEach(func() { + err = vault.V2Destroy(testMountName, path(testMountName, testSetPath), []uint{12}) + }) + + It("should not delete anything", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2Get being able to find the key") + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), nil, nil) + Expect(err).NotTo(HaveOccurred()) + + By("V2GetMetadata being able to find the key") + var meta vaultkv.V2Metadata + meta, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + Expect(err).NotTo(HaveOccurred()) + + By("V2GetMetadata reporting that version 1 still exists") + _, err = meta.Version(1) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + When("the path does not exist", func() { + JustBeforeEach(func() { + err = vault.V2Destroy(testMountName, path(testMountName, testSetPath+"abcd"), []uint{12}) + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) + + Describe("V2DestroyMetadata", func() { + JustBeforeEach(func() { + err = vault.V2DestroyMetadata(testMountName, path(testMountName, testSetPath)) + }) + + It("should delete the metadata", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2Get being unable to find the key") + _, err = vault.V2Get(testMountName, path(testMountName, testSetPath), nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + + By("V2GetMetadata being unable to find the key") + _, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + }) + }) + + Context("When there are two versions written", func() { + var testSet2Values map[string]interface{} + BeforeEach(func() { + testSet2Values = map[string]interface{}{"wee": "woo"} + }) + JustBeforeEach(func() { + testVersionOutput, err = vault.V2Set(testMountName, path(testMountName, testSetPath), testSet2Values, nil) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("V2Get", func() { + var testGet2Options *vaultkv.V2GetOpts + var testGet2Output map[string]interface{} + var testGet2Version vaultkv.V2Version + JustBeforeEach(func() { + testGet2Output = map[string]interface{}{} + testGet2Version, err = vault.V2Get(testMountName, path(testMountName, testSetPath), &testGet2Output, testGet2Options) + }) + + When("there are no options specified", func() { + BeforeEach(func() { + testGet2Options = nil + }) + + It("should get the latest version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having the retrieved value match what was put in second") + Expect(testGet2Output).To(BeEquivalentTo(testSet2Values)) + + By("having the returned version be 2") + Expect(testGet2Version.Version).To(BeEquivalentTo(2)) + }) + }) + + When("the version specified is `0'", func() { + BeforeEach(func() { + testGet2Options = &vaultkv.V2GetOpts{Version: 0} + }) + + It("should get the latest version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having the retrieved value match what was put in second") + Expect(testGet2Output).To(BeEquivalentTo(testSet2Values)) + + By("having the returned version be 2") + Expect(testGet2Version.Version).To(BeEquivalentTo(2)) + }) + }) + + When("the version specified is `1'", func() { + BeforeEach(func() { + testGet2Options = &vaultkv.V2GetOpts{Version: 1} + }) + It("should get version 1", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having the retrieved value match what was put in first") + Expect(testGet2Output).To(BeEquivalentTo(testSetValues)) + + By("having the returned version be 1") + Expect(testGet2Version.Version).To(BeEquivalentTo(1)) + }) + }) + + When("the version specified is `12'", func() { + BeforeEach(func() { + testGet2Options = &vaultkv.V2GetOpts{Version: 12} + }) + It("should err properly", func() { + By("return ErrNotFound") + AssertErrorOfType(&vaultkv.ErrNotFound{}) + }) + }) + }) + + Describe("V2Delete", func() { + var testDeleteOpts *vaultkv.V2DeleteOpts + JustBeforeEach(func() { + vault.V2Delete(testMountName, path(testMountName, testSetPath), testDeleteOpts) + }) + + AfterEach(func() { + testDeleteOpts = nil + }) + + Context("the first version", func() { + BeforeEach(func() { + testDeleteOpts = &vaultkv.V2DeleteOpts{ + Versions: []uint{1}, + } + }) + + It("should delete the first version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2GetMetadata returning that the first version has a deletion time") + var meta vaultkv.V2Metadata + meta, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + Expect(err).NotTo(HaveOccurred()) + + var v1 vaultkv.V2Version + v1, err = meta.Version(1) + Expect(err).NotTo(HaveOccurred()) + + Expect(v1.Version).To(BeEquivalentTo(1)) + Expect(v1.DeletedAt).NotTo(BeNil()) + + By("V2GetMetadata returning that the second version is not deleted") + var v2 vaultkv.V2Version + v2, err = meta.Version(2) + Expect(err).NotTo(HaveOccurred()) + + Expect(v2.Version).To(BeEquivalentTo(2)) + Expect(v2.DeletedAt).To(BeNil()) + }) + }) + + Context("the second version", func() { + BeforeEach(func() { + testDeleteOpts = &vaultkv.V2DeleteOpts{ + Versions: []uint{1}, + } + }) + + Specify("CurrentVersion should be 2", func() { + var meta vaultkv.V2Metadata + meta, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + Expect(err).NotTo(HaveOccurred()) + Expect(meta.CurrentVersion).To(BeEquivalentTo(2)) + }) + }) + }) + + Describe("V2Undelete", func() { + var versionsToUndelete []uint + JustBeforeEach(func() { + err = vault.V2Undelete(testMountName, path(testMountName, testSetPath), versionsToUndelete) + }) + + Context("When the version is not deleted", func() { + BeforeEach(func() { + versionsToUndelete = []uint{2} + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + + Context("When the version does not exist", func() { + BeforeEach(func() { + versionsToUndelete = []uint{12} + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) + + Describe("V2Destroy", func() { + var versionsToDestroy []uint + JustBeforeEach(func() { + err = vault.V2Destroy(testMountName, path(testMountName, testSetPath), versionsToDestroy) + }) + + Context("On one of the existing versions", func() { + BeforeEach(func() { + versionsToDestroy = []uint{1} + }) + + It("should destroy only the targeted version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("V2GetMetadata returning metadata for both the deleted and non-deleted versions") + var meta vaultkv.V2Metadata + meta, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + Expect(err).NotTo(HaveOccurred()) + Expect(meta.Versions).To(HaveLen(2)) + + By("Having the destroyed version be marked as destroyed") + var v1 vaultkv.V2Version + v1, err = meta.Version(1) + Expect(err).NotTo(HaveOccurred()) + Expect(v1.Version).To(BeEquivalentTo(1)) + Expect(v1.Destroyed).To(BeTrue()) + + By("Having the remaining version not be marked as destroyed") + var v2 vaultkv.V2Version + v2, err = meta.Version(2) + Expect(err).NotTo(HaveOccurred()) + Expect(v2.Version).To(BeEquivalentTo(2)) + Expect(v2.Destroyed).To(BeFalse()) + }) + }) + + Context("on the latest version", func() { + BeforeEach(func() { + versionsToDestroy = []uint{2} + }) + + Specify("CurrentVersion should be 2", func() { + var meta vaultkv.V2Metadata + meta, err = vault.V2GetMetadata(testMountName, path(testMountName, testSetPath)) + Expect(err).NotTo(HaveOccurred()) + Expect(meta.CurrentVersion).To(BeEquivalentTo(2)) + }) + }) + }) + }) + }) + + When("Check and Set is set to 0", func() { + BeforeEach(func() { + testSetOptions = vaultkv.V2SetOpts{}.WithCAS(0) + }) + Context("and the key does not yet exist", func() { + It("should write the values", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning proper metadata") + Expect(testVersionOutput.Version).To(BeEquivalentTo(1)) + }) + }) + + Context("and the key already exists", func() { + BeforeEach(func() { + var meta vaultkv.V2Version + meta, err = vault.V2Set(testMountName, path(testMountName, testSetPath), testSetValues, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(meta.Version).To(BeEquivalentTo(1)) + }) + + It("should return ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) + }) + }) + }) +}) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/kv_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/kv_test.go index b58c224..c6ef6c8 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/kv_test.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/kv_test.go @@ -2,359 +2,490 @@ package vaultkv_test import ( "fmt" - "sort" - "strings" - "github.com/cloudfoundry-community/vaultkv" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" -) -var _ = Describe("Kv", func() { - //This is a hack because I don't want to refactor _everything_ in these tests - var getAssertionPath string - - var AssertGetEquals = func(expected map[string]string) func() { - return func() { - output := make(map[string]string) - err = vault.Get(getAssertionPath, &output) - Expect(err).NotTo(HaveOccurred()) - Expect(output).To(Equal(expected)) - } - } - - var AssertExists = func(exists bool) func() { - var fn func() - if exists { - fn = func() { - err = vault.Get(getAssertionPath, nil) - AssertNoError()() - } - } else { - fn = func() { - err = vault.Get(getAssertionPath, nil) - AssertErrorOfType(&vaultkv.ErrNotFound{}) - } - } + "github.com/cloudfoundry-community/vaultkv" +) - return fn - } +var _ = Describe("KV", func() { + const testMountName = "zip/zop/zoobity/bop" + var testkv *vaultkv.KV + BeforeEach(func() { + InitAndUnsealVault() + testkv = vault.NewKV() + Expect(testkv).NotTo(BeNil()) + }) - When("the vault is not initialized", func() { - Describe("Get", func() { + unityTests := func() { + Describe("MountPath", func() { + var mountOutput string JustBeforeEach(func() { - err = vault.Get("secret/sure/whatever", nil) + mountOutput, err = testkv.MountPath(fmt.Sprintf("%s/boop", testMountName)) }) - It("should return ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) - }) - Describe("Set", func() { - JustBeforeEach(func() { - err = vault.Set("secret/sure/whatever", map[string]string{"foo": "bar"}) + It("should return the proper mount name", func() { + By("not returning an error") + Expect(err).NotTo(HaveOccurred()) + + By("having the returned mount name be the same as the created mount's name") + Expect(mountOutput).To(BeEquivalentTo(testMountName)) }) - It("should return ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) }) - Describe("Delete", func() { - JustBeforeEach(func() { - err = vault.Delete("secret/sure/whatever") + Describe("Set", func() { + var testSetPath string + var testSetValues map[string]string + var testSetOptions *vaultkv.KVSetOpts + var testVersionOutput vaultkv.KVVersion + BeforeEach(func() { + testSetPath = fmt.Sprintf("%s/boop", testMountName) }) - It("should return ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) - }) - Describe("List", func() { JustBeforeEach(func() { - _, err = vault.List("secret/sure/whatever") + testVersionOutput, err = testkv.Set(testSetPath, testSetValues, testSetOptions) }) - It("should return ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) - }) - }) - - When("the vault is initialized", func() { - var initOut *vaultkv.InitVaultOutput - BeforeEach(func() { - initOut, err = vault.InitVault(vaultkv.InitConfig{ - Shares: 1, - Threshold: 1, + AfterEach(func() { + testSetValues = nil + testSetOptions = nil + testVersionOutput = vaultkv.KVVersion{} }) - AssertNoError()() - }) - When("the vault is sealed", func() { - Describe("Get", func() { - JustBeforeEach(func() { - err = vault.Get("secret/sure/whatever", nil) + Context("With a non-empty map input", func() { + BeforeEach(func() { + testSetValues = map[string]string{"foo": "bar"} }) - It("should return ErrSealed", AssertErrorOfType(&vaultkv.ErrSealed{})) - }) - Describe("Set", func() { - JustBeforeEach(func() { - err = vault.Set("secret/sure/whatever", map[string]string{"foo": "bar"}) - }) - It("should return ErrSealed", AssertErrorOfType(&vaultkv.ErrSealed{})) - }) + It("should write the proper values to the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) - Describe("Delete", func() { - JustBeforeEach(func() { - err = vault.Delete("secret/sure/whatever") + By("returning the proper version output") + Expect(testVersionOutput.Version).To(BeEquivalentTo(uint(1))) }) - It("should return ErrSealed", AssertErrorOfType(&vaultkv.ErrSealed{})) - }) - Describe("List", func() { - JustBeforeEach(func() { - _, err = vault.List("secret/sure/whatever") - }) + Describe("List", func() { + var testListPath string + var testListOutput []string + JustBeforeEach(func() { + testListOutput, err = testkv.List(testListPath) + }) - It("should return ErrSealed", AssertErrorOfType(&vaultkv.ErrSealed{})) - }) - }) + When("the path exists", func() { + BeforeEach(func() { + _, err = testkv.Set(fmt.Sprintf("%s/foo/bar", testMountName), testSetValues, nil) + Expect(err).NotTo(HaveOccurred()) + testListPath = testMountName + }) - When("the vault is unsealed", func() { - BeforeEach(func() { - _, err = vault.Unseal(initOut.Keys[0]) - AssertNoError() - }) + It("should list the keys", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) - Describe("Setting something in the Vault", func() { - var testPath string - var testValue map[string]string - BeforeEach(func() { - testPath = "secret/foo" - testValue = map[string]string{ - "foo": "bar", - "beep": "boop", - } - }) + By("returning the expected keys") + Expect(testListOutput).To(Equal([]string{"boop", "foo/"})) + }) + }) - JustBeforeEach(func() { - err = vault.Set(testPath, testValue) - }) + When("the path does not exist", func() { + BeforeEach(func() { + testListPath = fmt.Sprintf("%s/this/shouldnt/exist", testMountName) + }) - When("the value is nil", func() { - BeforeEach(func() { - testValue = nil - getAssertionPath = testPath + It("should return ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) }) - - It("should return ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - Specify("Get should find no key at this path", AssertExists(false)) }) - When("the path is doesn't correspond to a mounted backend", func() { - BeforeEach(func() { - testPath = "notabackend/foo" + Describe("Get", func() { + var testGetOutput map[string]string + var testGetVersionOutput vaultkv.KVVersion + JustBeforeEach(func() { + testGetOutput = map[string]string{} + testGetVersionOutput, err = testkv.Get(testSetPath, &testGetOutput, nil) }) - It("should return ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) - Specify("Get should find no key at this path", AssertExists(false)) - }) + It("should get the key", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) - When("the path has a leading slash", func() { - BeforeEach(func() { - testPath = "/secret/foo" - getAssertionPath = strings.TrimPrefix(testPath, "/") - }) + By("returning the same version info as the Set") + Expect(testGetVersionOutput).To(Equal(testVersionOutput)) - It("should not return an error", AssertNoError()) - Specify("Get should find the key at the path without a slash", - AssertExists(true)) - Specify("Get should find the inserted value at the path without a slash", - AssertGetEquals(map[string]string{"foo": "bar", "beep": "boop"})) + By("returning the same values that were set") + Expect(testGetOutput).To(Equal(testSetValues)) + }) }) - When("the path has a trailing slash", func() { - BeforeEach(func() { - testPath = "secret/foo/" - getAssertionPath = strings.TrimSuffix(testPath, "/") + Describe("Delete", func() { + var testDeleteVersions []uint + JustBeforeEach(func() { + err = testkv.Delete(testSetPath, &vaultkv.KVDeleteOpts{ + Versions: testDeleteVersions, + V1Destroy: true, + }) + }) + AfterEach(func() { + testDeleteVersions = nil }) + Context("Not specifying a version to delete", func() { + It("should delete the only (newest) version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("Get being unable to find it") + _, err = testkv.Get(testSetPath, nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{})() + }) + }) + + Context("Specifying a version to delete", func() { + When("the version exists", func() { + BeforeEach(func() { + testDeleteVersions = []uint{1} + }) + + It("should delete the specified version", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("Get being unable to find it") + _, err = testkv.Get(testSetPath, nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{})() + }) + + Context("and then deleting it again", func() { + JustBeforeEach(func() { + err = testkv.Delete(testSetPath, &vaultkv.KVDeleteOpts{ + Versions: testDeleteVersions, + V1Destroy: true, + }) + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) - It("should not return an error", AssertNoError()) - Specify("Get should find the key at the path without a slash", - AssertExists(true)) - Specify("Get should find the inserted value at the path without a slash", - AssertGetEquals(map[string]string{"foo": "bar", "beep": "boop"})) + When("the version does not exist", func() { + BeforeEach(func() { + testDeleteVersions = []uint{12} + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) }) - When("setting an already set key", func() { - var secondTestValue map[string]string - BeforeEach(func() { - secondTestValue = map[string]string{ - "thisisanotherkey": "thisisanothervalue", - } - getAssertionPath = testPath + Describe("Destroy", func() { + When("the version exists and it is the only version", func() { + JustBeforeEach(func() { + err = testkv.Destroy(testSetPath, []uint{1}) + }) + + It("should delete the metadata", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("Get being unable to find the key") + _, err = testkv.Get(testSetPath, nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + + By("Versions being unable to find the key") + _, err = testkv.Versions(testSetPath) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + }) }) - JustBeforeEach(func() { - err = vault.Set(testPath, secondTestValue) + When("the version does not exist", func() { + JustBeforeEach(func() { + err = testkv.Destroy(testSetPath, []uint{12}) + }) + + It("should not delete anything", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("Get being able to find the key") + _, err = testkv.Get(testSetPath, nil, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Versions being able to find the key") + var meta []vaultkv.KVVersion + meta, err = testkv.Versions(testSetPath) + Expect(err).NotTo(HaveOccurred()) + + By("Versions reporting that version 1 still exists") + Expect(meta).To(HaveLen(1)) + }) }) - It("should not return an error", AssertNoError()) - Specify("Get should find the value that was added second", - AssertGetEquals(map[string]string{"thisisanotherkey": "thisisanothervalue"})) - }) + When("the path does not exist", func() { + JustBeforeEach(func() { + err = testkv.Destroy(testSetPath+"abcd", []uint{12}) + }) - Describe("Get", func() { - var getTestPath string - var getOutputValue map[string]string - - var AssertGetEqualsSet = func() func() { - return func() { - Expect(getOutputValue).To(Equal(testValue)) - } - } - BeforeEach(func() { - getOutputValue = make(map[string]string) + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) }) + }) + Describe("DestroyAll", func() { JustBeforeEach(func() { - err = vault.Get(getTestPath, &getOutputValue) + err = testkv.DestroyAll(testSetPath) }) - When("the key exists", func() { - BeforeEach(func() { - getTestPath = testPath - }) + It("should delete the metadata", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) - It("should not return an error", AssertNoError()) - It("should return the same value as what was inserted", AssertGetEqualsSet()) + By("Get being unable to find the key") + _, err = testkv.Get(testSetPath, nil, nil) + AssertErrorOfType(&vaultkv.ErrNotFound{}) + + By("Versions being unable to find the key") + _, err = testkv.Versions(testSetPath) + AssertErrorOfType(&vaultkv.ErrNotFound{}) }) + }) + }) + }) + } - When("the key doesn't exist", func() { - BeforeEach(func() { - getTestPath = fmt.Sprintf("%sabcd", testPath) - }) + Context("With a KV v1 mount", func() { + BeforeEach(func() { + mountType := vaultkv.MountTypeKV + if parseSemver(currentVaultVersion).LessThan(semver{0, 8, 0}) { + mountType = vaultkv.MountTypeGeneric + } + err = vault.EnableSecretsMount(testMountName, vaultkv.Mount{ + Type: mountType, + Options: vaultkv.KVMountOptions{}.WithVersion(1), + }) - It("should return ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) - }) + Expect(err).NotTo(HaveOccurred()) + }) + + unityTests() + + //There are some things that we cannot make exactly the same between kv v1 and v2. We test those things here. + Describe("v1 specific", func() { + Describe("isKVv2Mount", func() { + var mountName string + var isV2 bool + JustBeforeEach(func() { + mountName, isV2, err = vault.IsKVv2Mount(testMountName) + Expect(err).NotTo(HaveOccurred()) }) - Describe("Delete", func() { - var deleteTestPath string + It("should return the mount name and that it is not a v2 mount", func() { + Expect(mountName).To(BeEquivalentTo(testMountName)) + Expect(isV2).To(BeFalse()) + }) + }) + + Describe("Version", func() { + var testOutputVersion uint + JustBeforeEach(func() { + testOutputVersion, err = testkv.MountVersion(testMountName) + }) + + It("should return 1", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + By("returning the correct version number") + Expect(testOutputVersion).To(BeEquivalentTo(1)) + }) + }) + + Describe("Set", func() { + var testSetPath string + var testSetValues map[string]string + var testVersionOutput vaultkv.KVVersion + BeforeEach(func() { + testSetPath = fmt.Sprintf("%s/boop", testMountName) + }) + + JustBeforeEach(func() { + testSetValues = map[string]string{"foo": "bar"} + testVersionOutput, err = testkv.Set(testSetPath, testSetValues, nil) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("Getting a version >1", func() { JustBeforeEach(func() { - err = vault.Delete(deleteTestPath) + _, err = testkv.Get(testSetPath, nil, &vaultkv.KVGetOpts{Version: 2}) }) - When("the key exists", func() { - BeforeEach(func() { - deleteTestPath = testPath - getAssertionPath = testPath - }) + It("should return ErrNotFound", func() { + AssertErrorOfType(&vaultkv.ErrNotFound{}) + }) + }) - It("should not return an error", AssertNoError()) - Specify("Get should not find the key", AssertExists(false)) + When("Overwriting the previously Set key", func() { + JustBeforeEach(func() { + testSetValues = map[string]string{"beep": "boop"} + testVersionOutput, err = testkv.Set(testSetPath, testSetValues, nil) }) - When("the key doesn't exist", func() { - BeforeEach(func() { - deleteTestPath = fmt.Sprintf("%sabcd", testPath) - }) + It("should overwrite the key without issue", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) - It("should not return an error", AssertNoError()) + By("returning that the version is one") + Expect(testVersionOutput.Version).To(BeEquivalentTo(1)) }) }) - Describe("Adding another key with multiple parts", func() { - var secondTestPath string - var secondTestValue map[string]string - BeforeEach(func() { - secondTestPath = "secret/foo/bar" - secondTestValue = map[string]string{ - "werealljustlittlebabybirds": "peckingourwayoutofourshells", - } + Describe("Delete with V1Destroy set to false", func() { + JustBeforeEach(func() { + err = testkv.Delete(testSetPath, &vaultkv.KVDeleteOpts{ + Versions: []uint{1}, + V1Destroy: false, + }) + }) + + It("should return ErrKVUnsupported", func() { + AssertErrorOfType(&vaultkv.ErrKVUnsupported{}) }) + }) + Describe("Undelete", func() { JustBeforeEach(func() { - err = vault.Set(secondTestPath, secondTestValue) - AssertNoError()() + err = testkv.Undelete(testSetPath, []uint{1}) + }) + It("should return ErrKVUnsupported", func() { + AssertErrorOfType(&vaultkv.ErrKVUnsupported{}) }) + }) + }) + }) + }) - Describe("List", func() { - var listTestPath string - var listTestOutput []string + Context("With a KV v2 mount", func() { + BeforeEach(func() { + if parseSemver(currentVaultVersion).LessThan(semver{0, 10, 0}) { + Skip("This version of Vault does not support KVv2") + } else { + err = vault.EnableSecretsMount(testMountName, vaultkv.Mount{ + Type: vaultkv.MountTypeKV, + Options: vaultkv.KVMountOptions{}.WithVersion(2), + }) + } - JustBeforeEach(func() { - listTestOutput, err = vault.List(listTestPath) - }) + Expect(err).NotTo(HaveOccurred()) + }) - //Order doesn't matter - var AssertListEquals = func(expected []string) func() { - return func() { - Expect(listTestOutput).ToNot(BeNil()) - Expect(expected).ToNot(BeNil()) - sort.Strings(expected) - sort.Strings(listTestOutput) - Expect(listTestOutput).To(Equal(expected)) - } - } - - Context("on `secret'", func() { - BeforeEach(func() { - listTestPath = "secret" - }) + unityTests() - It("should not return an error", AssertNoError()) - It("should return the correct list of paths", AssertListEquals([]string{"foo", "foo/"})) - }) + Describe("KV v2 specific", func() { + Describe("isKVv2Mount", func() { + var mountName string + var isV2 bool + JustBeforeEach(func() { + mountName, isV2, err = vault.IsKVv2Mount(testMountName) + Expect(err).NotTo(HaveOccurred()) + }) - Context("on the dir of the nested key", func() { - BeforeEach(func() { - listTestPath = "secret/foo" - }) + Specify("should return the mount name and that it is a v2 mount", func() { + Expect(mountName).To(BeEquivalentTo(testMountName)) + Expect(isV2).To(BeTrue()) + }) + }) - It("should not return an error", AssertNoError()) - It("should return the correct list of paths", AssertListEquals([]string{"bar"})) - }) + Describe("Version", func() { + var testOutputVersion uint + JustBeforeEach(func() { + testOutputVersion, err = testkv.MountVersion(testMountName) + }) + It("should return 2", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + By("returning the correct version number") + Expect(testOutputVersion).To(BeEquivalentTo(2)) + }) + }) - When("the path doesn't exist", func() { - BeforeEach(func() { - listTestPath = "secret/boo/hiss" - }) + Describe("Set", func() { + var testVersionOutput vaultkv.KVVersion + var testSetPath string + var testSetValue map[string]string + JustBeforeEach(func() { + testSetPath = fmt.Sprintf("%s/testfield", testMountName) + testSetValue = map[string]string{"foo": "bar"} + testVersionOutput, err = testkv.Set(testSetPath, testSetValue, nil) + Expect(err).NotTo(HaveOccurred()) + }) - It("should return an ErrNotFound", AssertErrorOfType(&vaultkv.ErrNotFound{})) - }) + Describe("setting another version", func() { + var testVersionOutput2 vaultkv.KVVersion + var testSetValue2 map[string]string + JustBeforeEach(func() { + testSetValue2 = map[string]string{"beep": "boop"} + testVersionOutput2, err = testkv.Set(testSetPath, testSetValue2, nil) + Expect(err).NotTo(HaveOccurred()) }) - Describe("Get", func() { - var getTestPath string - var getOutputValue map[string]string + It("should have version 2", func() { + Expect(testVersionOutput2.Version).To(BeEquivalentTo(2)) + }) - BeforeEach(func() { - getOutputValue = make(map[string]string) - }) + Describe("Get", func() { + var testGetVersionOutput vaultkv.KVVersion + var testGetValue map[string]string + var testGetVersion uint JustBeforeEach(func() { - err = vault.Get(getTestPath, &getOutputValue) + testGetVersionOutput, err = testkv.Get(testSetPath, &testGetValue, &vaultkv.KVGetOpts{Version: testGetVersion}) + Expect(err).NotTo(HaveOccurred()) }) - Context("on the nested key", func() { - BeforeEach(func() { - getTestPath = secondTestPath + + Context("getting version 1", func() { + BeforeEach(func() { testGetVersion = 1 }) + It("should get the first version", func() { + Expect(testGetVersionOutput.Version).To(Equal(testGetVersion)) + Expect(testGetValue).To(Equal(testSetValue)) }) + }) - It("should not return an error", AssertNoError()) - It("should return the correct value", func() { - Expect(getOutputValue).To(Equal(secondTestValue)) + Context("getting version 2", func() { + BeforeEach(func() { testGetVersion = 2 }) + It("should get the second version", func() { + Expect(testGetVersionOutput.Version).To(Equal(testGetVersion)) + Expect(testGetValue).To(Equal(testSetValue2)) }) }) }) + }) + + Describe("Delete", func() { + JustBeforeEach(func() { + err = testkv.Delete(testSetPath, &vaultkv.KVDeleteOpts{Versions: []uint{testVersionOutput.Version}}) + Expect(err).NotTo(HaveOccurred()) + }) - Describe("Delete", func() { - var deleteTestPath string + It("should have the version marked as deleted", func() { + var versionData []vaultkv.KVVersion + versionData, err = testkv.Versions(testSetPath) + Expect(versionData).To(HaveLen(1)) + Expect(versionData[0].Deleted).To(BeTrue()) + }) + Describe("Undelete", func() { JustBeforeEach(func() { - err = vault.Delete(deleteTestPath) + err = testkv.Undelete(testSetPath, []uint{testVersionOutput.Version}) }) - Context("on the nested key", func() { - BeforeEach(func() { - deleteTestPath = secondTestPath - getAssertionPath = deleteTestPath - }) - It("should not return an error", AssertNoError()) - Specify("Get should not find the key", AssertExists(false)) + It("should undelete the secret", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + By("having Get fetch the secret that was initially inserted") + value := map[string]string{} + version, err := testkv.Get(testSetPath, &value, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(version.Version).To(Equal(testVersionOutput.Version)) + Expect(value).To(Equal(testSetValue)) }) }) + }) }) }) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/mount.go b/vendor/github.com/cloudfoundry-community/vaultkv/mount.go new file mode 100644 index 0000000..327712c --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/mount.go @@ -0,0 +1,199 @@ +package vaultkv + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" +) + +const ( + //MountTypeGeneric is what the key value backend was called prior to 0.8.0 + MountTypeGeneric = "generic" + //MountTypeKV is the type string to get a Key Value backend + MountTypeKV = "kv" +) + +//Mount represents a backend mounted at a point in Vault. +type Mount struct { + //The type of mount at this point + Type string + Description string + Config *MountConfig + Options map[string]interface{} +} + +//MountConfig specifies configuration options given when initializing a backend. +type MountConfig struct { + DefaultLeaseTTL time.Duration + MaxLeaseTTL time.Duration + PluginName string + ForceNoCache bool +} + +type mountListAPI struct { + Type string `json:"type"` + Description string `json:"description"` + Config mountConfigListAPI `json:"config"` + Options map[string]interface{} `json:"options"` +} + +func (m mountListAPI) Parse() Mount { + return Mount{ + Type: m.Type, + Description: m.Description, + Config: m.Config.Parse(), + Options: m.Options, + } +} + +type mountConfigListAPI struct { + //time in seconds + DefaultLeaseTTL int `json:"default_lease_ttl"` + MaxLeaseTTL int `json:"max_lease_ttl"` + PluginName string `json:"plugin_name"` + ForceNoCache bool `json:"force_no_cache"` +} + +func (m mountConfigListAPI) Parse() *MountConfig { + return &MountConfig{ + DefaultLeaseTTL: time.Duration(m.DefaultLeaseTTL) * time.Second, + MaxLeaseTTL: time.Duration(m.MaxLeaseTTL) * time.Second, + PluginName: m.PluginName, + ForceNoCache: m.ForceNoCache, + } +} + +type mountConfigEnableAPI struct { + DefaultLeaseTTL string `json:"default_lease_ttl,omitempty"` + MaxLeaseTTL string `json:"max_lease_ttl,omitempty"` + PluginName string `json:"plugin_name,omitempty"` + ForceNoCache bool `json:"force_no_cache,omitempty"` +} + +func newMountConfigEnableAPI(conf *MountConfig) *mountConfigEnableAPI { + if conf == nil { + return nil + } + + return &mountConfigEnableAPI{ + DefaultLeaseTTL: func() string { + if conf.DefaultLeaseTTL == 0 { + return "" + } + return conf.DefaultLeaseTTL.String() + }(), + MaxLeaseTTL: func() string { + if conf.MaxLeaseTTL == 0 { + return "" + } + return conf.DefaultLeaseTTL.String() + }(), + PluginName: conf.PluginName, + ForceNoCache: conf.ForceNoCache, + } +} + +//ListMounts queries the Vault backend for a list of active mounts that can +// be seen with the current authentication token. It is returned as a map +// of mount points to mount information. +func (c *Client) ListMounts() (map[string]Mount, error) { + output := map[string]interface{}{} + //Prior to 1.10, the mount names were top level keys. Then, they duplicated the + // information into "data" with other metadata in the top level keys. So we need + // to check if the data key is there (and isn't just a mount name) + err := c.doRequest("GET", "/sys/mounts", nil, &output) + if err != nil { + return nil, err + } + + var mounts map[string]mountListAPI + if dataKey, ok := output["data"]; ok { + mounts = getMountList(dataKey) + } + + if mounts == nil { + mounts := getMountList(output) + if mounts == nil { + return nil, fmt.Errorf("Could not parse mount list") + } + } + + ret := map[string]Mount{} + for k, v := range mounts { + ret[strings.TrimRight(k, "/")] = v.Parse() + } + + return ret, err +} + +func getMountList(candidate interface{}) map[string]mountListAPI { + //check if data key is not a mount name + b, err := json.Marshal(&candidate) + if err != nil { + return nil + } + + tmpOutput := map[string]mountListAPI{} + err = json.Unmarshal(b, &tmpOutput) + if err != nil { + return nil + } + + return tmpOutput +} + +//KVMountOptions is a map[string]interface{} that can be given as the options +//when mounting a backend. It has Version manipulation functions to make life +//easier. +type KVMountOptions map[string]interface{} + +//GetVersion retruns the version held in the KVMountOptions object +func (o KVMountOptions) GetVersion() int { + if o == nil { + return 1 + } + + version, hasExplicitVersion := o["version"] + if !hasExplicitVersion { + return 1 + } + + vStr := version.(string) + ret, _ := strconv.Atoi(vStr) + return ret +} + +//WithVersion returns a new KVMountOptions object with the given version +func (o KVMountOptions) WithVersion(version int) KVMountOptions { + if o == nil { + o = make(map[string]interface{}, 1) + } + + o["version"] = strconv.Itoa(version) + return o +} + +//EnableSecretsMount mounts a secrets backend at the given path, configured with +// the given Mount configuration. +func (c *Client) EnableSecretsMount(path string, config Mount) error { + input := struct { + Type string `json:"type"` + Description string `json:"description"` + Config *mountConfigEnableAPI `json:"config,omitempty"` + Options interface{} `json:"options,omitempty"` + }{ + Type: config.Type, + Description: config.Description, + Config: newMountConfigEnableAPI(config.Config), + Options: config.Options, + } + + return c.doRequest("POST", fmt.Sprintf("/sys/mounts/%s", path), &input, nil) +} + +//DisableSecretsMount deletes the mount at the given path. +func (c *Client) DisableSecretsMount(path string) error { + return c.doRequest("DELETE", fmt.Sprintf("/sys/mounts/%s", path), nil, nil) +} diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/rekey.go b/vendor/github.com/cloudfoundry-community/vaultkv/rekey.go index 1f9810f..a6ee6d2 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/rekey.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/rekey.go @@ -2,7 +2,8 @@ package vaultkv import ( "encoding/json" - "strings" + "fmt" + "regexp" ) //Rekey represents a rekey operation currently in progress in the Vault. This @@ -41,17 +42,13 @@ type RekeyState struct { Backup bool `json:"backup"` } -//NewRekey will cancel the current rekey if one is in progress. If starting a -// rekey is successful, a *Rekey is returned containing the necessary state -// for submitting keys for this rekey operation. +//NewRekey will start a new rekey operation. If successful, a *Rekey is +//returned containing the necessary state for submitting keys for this rekey +//operation. func (v *Client) NewRekey(conf RekeyConfig) (*Rekey, error) { - err := v.rekeyCancel() - if err != nil { - return nil, err - } - - err = v.rekeyStart(conf) + err := v.rekeyStart(conf) if err != nil { + err = v.correct500Error(err) return nil, err } @@ -65,6 +62,7 @@ func (v *Client) CurrentRekey() (*Rekey, error) { var state RekeyState err := v.doSysRequest("GET", "/sys/rekey/init", nil, &state) if err != nil { + err = v.correct500Error(err) return nil, err } @@ -78,19 +76,40 @@ func (v *Client) CurrentRekey() (*Rekey, error) { }, nil } +//This is here because in Vault 0.10.3, a regression was introduced that causes +// rekey operations against an uninitialized or sealed Vault to return a 500 +// instead of a 503 +func (v *Client) correct500Error(err error) error { + //Thanks, Vault 0.10.3 + if _, is500 := err.(*ErrInternalServer); is500 { + tmpErr := v.Health(true) + if _, isUninitialized := tmpErr.(*ErrUninitialized); isUninitialized { + err = tmpErr + } else if _, isSealed := tmpErr.(*ErrSealed); isSealed { + err = tmpErr + } + } + + return err +} + func (v *Client) rekeyStart(conf RekeyConfig) error { return v.doSysRequest("PUT", "/sys/rekey/init", &conf, nil) } //Cancel tells Vault to forget about the current rekey operation func (r *Rekey) Cancel() error { - return r.client.rekeyCancel() + return r.client.RekeyCancel() } -func (v *Client) rekeyCancel() error { +//RekeyCancel tells Vault to forget about the current rekey operation +func (v *Client) RekeyCancel() error { return v.doSysRequest("DELETE", "/sys/rekey/init", nil, nil) } +//Before 0.10, it was "no rekey in progress". In 0.10, the word barrier was added +var rekeyRegexp = regexp.MustCompile("no (barrier )?rekey in progress") + //Submit gives keys to the rekey operation specified by this *Rekey object. Any //keys beyond the current required amount are ignored. If the Rekey is //successful after all keys have been sent, then done will be returned as true. @@ -99,7 +118,8 @@ func (v *Client) rekeyCancel() error { //cancelled, but is instead reset. No error is given for an incorrect key //before the threshold is reached. An *ErrBadRequest may also be returned if //there is no longer any rekey in progress, but in this case, done will be -//returned as true. +//returned as true. To retrieve the new keys after submitting enough existing +//keys, call Keys() on the Rekey object. func (r *Rekey) Submit(keys ...string) (done bool, err error) { for _, key := range keys { var result interface{} @@ -109,8 +129,9 @@ func (r *Rekey) Submit(keys ...string) (done bool, err error) { r.state.Progress = 0 //I really hate error string checking, but there's no good way that doesn't //require another API call (which could, in turn, err, and leave us in a - //wrong state) - if strings.Contains(ebr.message, "no rekey in progress") { + //wrong state). This checks if the rekey operation is no longer in + //progress + if rekeyRegexp.MatchString(ebr.message) { done = true } } @@ -140,6 +161,15 @@ type rekeyKeys struct { } func (v *Client) rekeySubmit(key string, nonce string) (ret interface{}, err error) { + if key == "" { + err = fmt.Errorf("no key provided") + return + } + if nonce == "" { + err = fmt.Errorf("no nonce provided") + return + } + tempMap := make(map[string]interface{}) err = v.doSysRequest( "PUT", diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/rekey_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/rekey_test.go index 341b4ee..1e7a9bd 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/rekey_test.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/rekey_test.go @@ -42,7 +42,7 @@ var _ = Describe("Rekey", func() { Shares: initShares, Threshold: initThreshold, }) - AssertNoError()() + Expect(err).NotTo(HaveOccurred()) }) When("Vault is sealed", func() { @@ -69,7 +69,7 @@ var _ = Describe("Rekey", func() { When("Vault is unsealed", func() { JustBeforeEach(func() { err = initOutput.Unseal() - AssertNoError()() + Expect(err).NotTo(HaveOccurred()) }) Describe("CurrentRekey with no rekey in progress", func() { @@ -107,51 +107,57 @@ var _ = Describe("Rekey", func() { rekeyConf.Threshold = 1 }) - It("should start a rekey with no error", AssertNoError()) - Specify("remaining should report one", AssertRemaining(1)) + It("should rekey properly", func() { + By("initializing the rekey without erroring") + Expect(err).NotTo(HaveOccurred()) - Describe("State", func() { - var state vaultkv.RekeyState - JustBeforeEach(func() { - state = rekey.State() - Expect(state).NotTo(BeNil()) - }) + By("having remaining report one") + AssertRemaining(1)() - It("should have PendingShares as one", func() { Expect(state.PendingShares).To(Equal(1)) }) - It("should have PendingThreshold as one", func() { Expect(state.PendingThreshold).To(Equal(1)) }) - It("should have Required as one", func() { Expect(state.Required).To(Equal(1)) }) - It("should have Progress as zero", func() { Expect(state.Progress).To(Equal(0)) }) - }) + By("having State not return nil") + state := rekey.State() - Describe("Submitting one key", func() { - var rekeyDone bool + //State with zero keys submitted + By("having the state say PendingShares is one") + Expect(state.PendingShares).To(Equal(1)) - JustBeforeEach(func() { - rekeyDone, err = rekey.Submit(initOutput.Keys[0]) - }) + By("having the state say PendingThreshold is one") + Expect(state.PendingThreshold).To(Equal(1)) - It("should not have returned an error", AssertNoError()) - It("should say that the rekey is done", func() { Expect(rekeyDone).To(BeTrue()) }) + By("having the state say Required is one") + Expect(state.Required).To(Equal(1)) - Describe("Keys", func() { - var rekeyKeys []string - JustBeforeEach(func() { - rekeyKeys = rekey.Keys() - }) + By("having the state say Progress is zero") + Expect(state.Progress).To(Equal(0)) - It("should have one key", func() { Expect(rekeyKeys).To(HaveLen(1)) }) - Specify("Remaining should return zero", AssertRemaining(0)) - }) + var rekeyDone bool + rekeyDone, err = rekey.Submit(initOutput.Keys[0]) + By("having the first key submission not err") + Expect(err).NotTo(HaveOccurred()) + + By("having the first key submission finish the rekey") + Expect(rekeyDone).To(BeTrue()) + + By("having Keys have one new key") + Expect(rekey.Keys()).To(HaveLen(1)) + + By("having Remaining return zero") + AssertRemaining(0)() }) - Describe("Submitting too many keys", func() { + Describe("Submitting too many keys all at once", func() { var rekeyDone bool JustBeforeEach(func() { rekeyDone, err = rekey.Submit(initOutput.Keys[0], "a", "b", "c") }) - It("should not have returned an error", AssertNoError()) - It("should say that the rekey is done", func() { Expect(rekeyDone).To(BeTrue()) }) + It("should properly unseal the vault (as long as the first keys are correct)", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("saying that the rekey is done") + Expect(rekeyDone).To(BeTrue()) + }) }) Describe("Submitting an incorrect key", func() { @@ -161,8 +167,13 @@ var _ = Describe("Rekey", func() { rekeyDone, err = rekey.Submit("k8vk0IdoDeNAJl5JDJ282eehqIbRLv5WWoBy6ppBK9c=") }) - It("should return ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - It("should not claim to be done", func() { Expect(rekeyDone).To(BeFalse()) }) + It("should err properly", func() { + By("returning an ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("saying that it's not done") + Expect(rekeyDone).To(BeFalse()) + }) }) }) @@ -188,46 +199,45 @@ var _ = Describe("Rekey", func() { rekeyConf.Threshold = 1 }) - It("should not return an error", AssertNoError()) - Specify("Remaining should return three", AssertRemaining(3)) + It("should allow rekey operations", func() { + By("not erroring from the creation of the rekey") + Expect(err).NotTo(HaveOccurred()) + + By("having Remaining return three") + AssertRemaining(3)() - Describe("Submitting one key", func() { + By("having the first key submission not err") var rekeyDone bool - JustBeforeEach(func() { - rekeyDone, err = rekey.Submit(initOutput.Keys[0]) - }) + rekeyDone, err = rekey.Submit(initOutput.Keys[0]) + Expect(err).NotTo(HaveOccurred()) - It("should not return an error", AssertNoError()) - It("should not consider the rekey done", func() { Expect(rekeyDone).To(BeFalse()) }) - Specify("Remaining should say two", AssertRemaining(2)) + By("not claiming to be done with the rekey") + Expect(rekeyDone).To(BeFalse()) - Describe("Getting the existing rekey operation with CurrentRekey", func() { - JustBeforeEach(func() { - rekey, err = vault.CurrentRekey() - }) + By("having Remaining return two") + AssertRemaining(2)() - It("should not return an error", AssertNoError()) - It("should return a non-nil rekey", func() { Expect(rekey).NotTo(BeNil()) }) - Specify("The rekey object should specify two keys remaining", AssertRemaining(2)) - }) + By("getting the current rekey operation not erroring") + rekey, err = vault.CurrentRekey() + Expect(err).NotTo(HaveOccurred()) - Describe("Cancelling the rekey", func() { - JustBeforeEach(func() { - err = rekey.Cancel() - }) + By("the CurrentRekey operation not returning nil") + Expect(rekey).NotTo(BeNil()) - It("should not return an error", AssertNoError()) + By("the CurrentRekey return value's Remaining should return two") + AssertRemaining(2) - Describe("Submitting after the cancellation", func() { - var rekeyDone bool - JustBeforeEach(func() { - rekeyDone, err = rekey.Submit(initOutput.Keys[0]) - }) + By("cancelling the rekey not returning an error") + err = rekey.Cancel() + Expect(err).NotTo(HaveOccurred()) + + By("submitting after the rekey was cancelled returning an ErrBadRequest") + rekeyDone, err = rekey.Submit(initOutput.Keys[0]) + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("the submission after the rekey was cancelled returning that the rekey is done") + Expect(rekeyDone).To(BeTrue()) - It("should report being done", func() { Expect(rekeyDone).To(BeTrue()) }) - It("should return an ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - }) - }) }) Describe("Submitting all necessary keys", func() { @@ -237,10 +247,18 @@ var _ = Describe("Rekey", func() { rekeyDone, err = rekey.Submit(initOutput.Keys...) }) - It("should not return an error", AssertNoError()) - It("should consider the rekey done", func() { Expect(rekeyDone).To(BeTrue()) }) - Specify("remaining should return 0", AssertRemaining(0)) - Specify("Keys should return 1 key", AssertHasKeys(1)) + It("should rekey the vault successfully", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("claiming that the rekey is done") + Expect(rekeyDone).To(BeTrue()) + + By("having Remaining return 0") + + By("Keys returning the 1 new key") + AssertHasKeys(1)() + }) }) Context("One Submit call at a time", func() { @@ -248,13 +266,20 @@ var _ = Describe("Rekey", func() { JustBeforeEach(func() { for _, key := range initOutput.Keys { rekeyDone, err = rekey.Submit(key) - AssertNoError() + Expect(err).NotTo(HaveOccurred()) } }) - It("should consider the rekey done", func() { Expect(rekeyDone).To(BeTrue()) }) - Specify("remaining should return 0", AssertRemaining(0)) - Specify("Keys should return 1 key", AssertHasKeys(1)) + It("should rekey successfully", func() { + By("returning that the rekey is done") + Expect(rekeyDone).To(BeTrue()) + + By("having Remaining return zero") + AssertRemaining(0)() + + By("having Keys return the one new key") + AssertHasKeys(1)() + }) }) }) }) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/root.go b/vendor/github.com/cloudfoundry-community/vaultkv/root.go new file mode 100644 index 0000000..4541e12 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/root.go @@ -0,0 +1,196 @@ +package vaultkv + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "fmt" + "regexp" +) + +//GenerateRoot has functions for generating a new root token. Create this +//object with NewGenerateRoot(). That function performs the necessary +//initialization for the process +type GenerateRoot struct { + client *Client + otp []byte + //Versions of Vault before 11.2 encode their 16 byte root tokens as base16 uuids + // After that... they are encoded as base64 strings + shouldNotUUIDify bool + state GenerateRootState +} + +//GenerateRootState contains state information about the GenerateRoot operation +type GenerateRootState struct { + Started bool `json:"started"` + Nonce string `json:"nonce"` + Progress int `json:"progress"` + Required int `json:"required"` + //Vault versions >= 0.9.x return the value as encoded_token + EncodedToken string `json:"encoded_token"` + //Vault versions before 0.9.x returned the value as encoded_root_token + EncodedRootToken string `json:"encoded_root_token"` + //Vault versions before 0.11.2 had the user generate their own 16-byte root token + // Versions after that have the API generate it for you (at a length that is + // decided by the API). + OTP string `json:"otp"` + OTPLength int `json:"otp_length"` + Complete bool `json:"complete"` +} + +//NewGenerateRoot initializes and returns a new generate root object. +func (v *Client) NewGenerateRoot() (*GenerateRoot, error) { + ret := GenerateRoot{ + client: v, + otp: make([]byte, 16), + } + _, err := rand.Read(ret.otp) + if err != nil { + return nil, fmt.Errorf("Could not generate random values") + } + + base64OTP := make([]byte, base64.StdEncoding.EncodedLen(len(ret.otp))) + base64.StdEncoding.Encode(base64OTP, ret.otp) + + err = v.doRequest("PUT", "/sys/generate-root/attempt", + map[string]string{"otp": string(base64OTP)}, &ret.state) + if err != nil && !IsBadRequest(err) { + return nil, err + } + + if ret.state.OTPLength != 0 || IsBadRequest(err) { + //Then we need to let the API generate the root token + err = ret.Cancel() + if err != nil { + return nil, err + } + + //In 0.11.2 and 0.11.3, you can't provide an empty body or else Vault EOFs. + // So you have to give an empty string otp to prompt Vault to make an otp + // of the proper length for you. This was fixed in 0.11.4. + err = v.doRequest("PUT", "/sys/generate-root/attempt", + map[string]string{"otp": ""}, &ret.state) + if err != nil { + return nil, err + } + ret.otp = []byte(ret.state.OTP) + ret.shouldNotUUIDify = true + } + + return &ret, nil +} + +var genRootRegexp = regexp.MustCompile("no root generation in progress") + +//Submit gives keys to the generate root token operation specified by this +//*GenerateRoot object. Any keys beyond the current required amount are +//ignored. If the Rekey is successful after all keys have been sent, then done +//will be returned as true. If the threshold is reached and any of the keys +//were incorrect, an *ErrBadRequest is returned and done is false. In this +//case, the generate root is not cancelled, but is instead reset. No error is +//given for an incorrect key before the threshold is reached. An *ErrBadRequest +//may also be returned if there is no longer any generate root token operation +//in progress, but in this case, done will be returned as true. To retrieve the +//new keys after submitting enough existing keys, call RootToken() on the +//GenerateRoot object. +func (g *GenerateRoot) Submit(keys ...string) (done bool, err error) { + for _, key := range keys { + g.state, err = g.client.genRootSubmit(key, g.state.Nonce) + if err != nil { + if ebr, is400 := err.(*ErrBadRequest); is400 { + g.state.Progress = 0 + //I really hate error string checking, but there's no good way that doesn't + //require another API call (which could, in turn, err, and leave us in a + //wrong state). This checks if the generate root token is no longer in + // progress + if genRootRegexp.MatchString(ebr.message) { + done = true + } + } + + return + } + + if g.state.Complete { + break + } + } + + return g.state.Complete, nil +} + +//Cancel cancels the current generate root operation +func (g *GenerateRoot) Cancel() error { + return g.client.GenerateRootCancel() +} + +//GenerateRootCancel cancels the current generate root operation +func (v *Client) GenerateRootCancel() error { + return v.doSysRequest("DELETE", "/sys/generate-root/attempt", nil, nil) +} + +func (v *Client) genRootSubmit(key string, nonce string) (ret GenerateRootState, err error) { + err = v.doSysRequest( + "PUT", + "/sys/generate-root/update", + &struct { + Key string `json:"key"` + Nonce string `json:"nonce"` + }{ + Key: key, + Nonce: nonce, + }, + &ret, + ) + + return +} + +//Remaining returns the number of keys yet required by this generate root token +//operation. This does not refresh state, and only reflects the last action of +//this GenerateRoot object. +func (g *GenerateRoot) Remaining() int { + return g.state.Required - g.state.Progress +} + +//State returns the current state of the generate root operation. This does not +//refresh state, and only reflects the last action of this GenerateRoot object. +func (g *GenerateRoot) State() GenerateRootState { + return g.state +} + +//RootToken returns the new root token from this operation if the operation has +//been successful. The return value is undefined if the operation is not yet +//successful. +func (g *GenerateRoot) RootToken() (string, error) { + rawTok := g.state.EncodedToken + if rawTok == "" { + rawTok = g.state.EncodedRootToken + } + + for len(rawTok)%4 != 0 { + rawTok += "=" + } + + tok, err := base64.StdEncoding.DecodeString(rawTok) + if err != nil { + return "", fmt.Errorf("Could not decode base64 token: %s", err) + } + + if len(tok) != len(g.otp) { + return "", fmt.Errorf("token length / one-time password length mismatch (%d/%d)", len(tok), len(g.otp)) + } + for i := 0; i < len(g.otp); i++ { + tok[i] ^= g.otp[i] + } + + ret := string(tok) + if !g.shouldNotUUIDify { + tokHex := make([]byte, hex.EncodedLen(len(tok))) + hex.Encode(tokHex, tok) + ret = fmt.Sprintf("%s-%s-%s-%s-%s", + tokHex[0:8], tokHex[8:12], tokHex[12:16], tokHex[16:20], tokHex[20:]) + } + + return ret, nil +} diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/root_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/root_test.go new file mode 100644 index 0000000..1b5ea96 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/root_test.go @@ -0,0 +1,223 @@ +package vaultkv_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry-community/vaultkv" +) + +var _ = Describe("Generate Root", func() { + When("the vault is not initialized", func() { + Describe("Starting a new generate root operation", func() { + JustBeforeEach(func() { + _, err = vault.NewGenerateRoot() + }) + + It("should return ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) + }) + }) + + When("the vault is initialized", func() { + var initShares, initThreshold int + var initOutput *vaultkv.InitVaultOutput + BeforeEach(func() { + initShares = 1 + initThreshold = 1 + }) + + JustBeforeEach(func() { + initOutput, err = vault.InitVault(vaultkv.InitConfig{ + Shares: initShares, + Threshold: initThreshold, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + When("Vault is sealed", func() { + Describe("Starting a new generate root operation", func() { + JustBeforeEach(func() { + _, err = vault.NewGenerateRoot() + }) + + It("should return ErrSealed", AssertErrorOfType(&vaultkv.ErrSealed{})) + }) + }) + + When("Vault is unsealed", func() { + JustBeforeEach(func() { + err = initOutput.Unseal() + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("Starting a new generate root operation", func() { + var genRoot *vaultkv.GenerateRoot + + var AssertRemaining = func(rem int) func() { + return func() { + Expect(genRoot.Remaining()).To(Equal(rem)) + } + } + + JustBeforeEach(func() { + genRoot, err = vault.NewGenerateRoot() + }) + + Context("With one key in the previous initialization", func() { + It("should generate a new root token properly", func() { + By("initializing the generate root operation without erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having remaining report one") + AssertRemaining(1)() + + By("having State not return nil") + state := genRoot.State() + + //State with zero keys submitted + By("having the state say Required is one") + Expect(state.Required).To(Equal(1)) + + By("having the state say Progress is zero") + Expect(state.Progress).To(Equal(0)) + + var genRootDone bool + genRootDone, err = genRoot.Submit(initOutput.Keys[0]) + By("having the first key submission not err") + Expect(err).NotTo(HaveOccurred()) + + By("having the first key submission finish the generate root operation") + Expect(genRootDone).To(BeTrue()) + + By("having Remaining return zero") + AssertRemaining(0)() + + var newToken string + newToken, err = genRoot.RootToken() + By("having RootToken not err") + Expect(err).NotTo(HaveOccurred()) + + By("having RootToken give back the root token") + Expect(newToken).NotTo(BeEmpty()) + + By("Being able to use the returned token to authenticate") + vault.AuthToken = newToken + mountType := vaultkv.MountTypeKV + if parseSemver(currentVaultVersion).LessThan(semver{0, 8, 0}) { + mountType = vaultkv.MountTypeGeneric + } + + err = vault.EnableSecretsMount("beep", vaultkv.Mount{Type: mountType}) + Expect(err).NotTo(HaveOccurred()) + }) + + Describe("Submitting too many keys all at once", func() { + var genRootDone bool + JustBeforeEach(func() { + genRootDone, err = genRoot.Submit(initOutput.Keys[0], "a", "b", "c") + }) + + It("should properly generate a new root token (as long as the first keys are correct)", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("saying that the generate root operation is done") + Expect(genRootDone).To(BeTrue()) + }) + }) + + Describe("Submitting an incorrect key", func() { + var genRootDone bool + JustBeforeEach(func() { + //If this is somehow your unseal key, then I'm sorry + genRootDone, err = genRoot.Submit("k8vk0IdoDeNAJl5JDJ282eehqIbRLv5WWoBy6ppBK9c=") + }) + + It("should err properly", func() { + By("returning an ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("saying that it's not done") + Expect(genRootDone).To(BeFalse()) + }) + }) + }) + + Context("With multiple keys in the previous initialization", func() { + BeforeEach(func() { + initShares = 3 + initThreshold = 3 + }) + + It("should allow new root token generation attempt to be created", func() { + By("not erroring from the creation of the generate root operation") + Expect(err).NotTo(HaveOccurred()) + + By("having Remaining return three") + AssertRemaining(3)() + + By("having the first key submission not err") + var genRootDone bool + genRootDone, err = genRoot.Submit(initOutput.Keys[0]) + Expect(err).NotTo(HaveOccurred()) + + By("not claiming to be done with the generate root operation") + Expect(genRootDone).To(BeFalse()) + + By("having Remaining return two") + AssertRemaining(2)() + + By("cancelling the generate root operation not returning an error") + err = genRoot.Cancel() + Expect(err).NotTo(HaveOccurred()) + + By("submitting after the generate root operation was cancelled returning an ErrBadRequest") + genRootDone, err = genRoot.Submit(initOutput.Keys[0]) + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("the submission after the generate root operation was cancelled returning that the operation is done") + Expect(genRootDone).To(BeTrue()) + + }) + + Describe("Submitting all necessary keys", func() { + var genRootDone bool + Context("All at once", func() { + JustBeforeEach(func() { + genRootDone, err = genRoot.Submit(initOutput.Keys...) + }) + + It("should generate a new root token successfully", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("claiming that the generate root operation is done") + Expect(genRootDone).To(BeTrue()) + + By("having Remaining return 0") + }) + }) + + Context("One Submit call at a time", func() { + var genRootDone bool + JustBeforeEach(func() { + for _, key := range initOutput.Keys { + genRootDone, err = genRoot.Submit(key) + Expect(err).NotTo(HaveOccurred()) + } + }) + + It("should generate a new root token successfully", func() { + By("returning that the generate root operation is done") + Expect(genRootDone).To(BeTrue()) + + By("having Remaining return zero") + AssertRemaining(0)() + }) + }) + }) + }) + }) + }) + }) +}) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/sys.go b/vendor/github.com/cloudfoundry-community/vaultkv/sys.go index 79e4fe9..16f9381 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/sys.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/sys.go @@ -15,9 +15,9 @@ func (v *Client) doSysRequest( err := v.doRequest(method, path, input, output) //In sys contexts, 400 can mean that the Vault is uninitialized. if _, is400 := err.(*ErrBadRequest); is400 { - initialized, err := v.IsInitialized() - if err != nil { - return err + initialized, initErr := v.IsInitialized() + if initErr != nil { + return initErr } if !initialized { @@ -140,9 +140,16 @@ func (v *Client) InitVault(in InitConfig) (out *InitVaultOutput, err error) { //Seal puts to the /sys/seal endpoint to seal the Vault. // If the Vault is already sealed, this doesn't return an error. // If the Vault is unsealed and an incorrect token is provided, then this -// returns *ErrForbidden. +// returns *ErrForbidden. Newer versions of Vault (0.11.2+) APIs return errors +// if the Vault is uninitialized or already sealed. This function squelches +// these errors for consistency with earlier versions of Vault func (v *Client) Seal() error { - return v.doSysRequest("PUT", "/sys/seal", nil, nil) + err := v.doSysRequest("PUT", "/sys/seal", nil, nil) + if err != nil && (IsUninitialized(err) || IsSealed(err)) { + err = nil + } + + return err } //Unseal puts to the /sys/unseal endpoint with a single key to progress the diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/sys_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/sys_test.go index 404b484..f0b7cda 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/sys_test.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/sys_test.go @@ -24,16 +24,6 @@ var _ = Describe("Sys", func() { } } - Describe("SealStatus", func() { - JustBeforeEach(func() { - _, err = vault.SealStatus() - }) - - When("Vault is uninitialized", func() { - It("should return an ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) - }) - }) - Describe("Initialization", func() { var output *vaultkv.InitVaultOutput var input vaultkv.InitConfig @@ -84,18 +74,30 @@ var _ = Describe("Sys", func() { } }) - It("should not err", AssertNoError()) - It("should return a root token", AssertHasRootToken()) - It("should have one unseal key", AssertHasUnsealKeys(1)) - It("should be initialized", AssertInitializationStatus(true)) + It("should initialize the vault", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning a root token") + AssertHasRootToken()() + + By("returning one unseal key") + AssertHasUnsealKeys(1)() + + By("having the vault say it is initialized") + AssertInitializationStatus(true)() + }) Describe("Unseal with an InitVaultOutput", func() { JustBeforeEach(func() { err = output.Unseal() }) - It("should not return an error", AssertNoError()) - Specify("SealStatus should return unsealed", func() { + It("should unseal the vault properly", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("the vault saying that it is unsealed") sealState, err := vault.SealStatus() Expect(err).NotTo(HaveOccurred()) Expect(sealState).NotTo(BeNil()) @@ -112,10 +114,19 @@ var _ = Describe("Sys", func() { } }) - It("should not err", AssertNoError()) - It("should return a root token", AssertHasRootToken()) - It("should have three unseal keys", AssertHasUnsealKeys(3)) - It("should be initialized", AssertInitializationStatus(true)) + It("should initialize the vault", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning a root token") + AssertHasRootToken()() + + By("returning the correct amount of unseal keys") + AssertHasUnsealKeys(3)() + + By("having the vault say it is initialized") + AssertInitializationStatus(true)() + }) }) When("0 secret shares are requested", func() { @@ -125,8 +136,13 @@ var _ = Describe("Sys", func() { Threshold: 0, } - It("should return an ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - It("should be initialized", AssertInitializationStatus(false)) + It("should err properly", func() { + By("returning ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("having the vault say that it is not yet initialized") + AssertInitializationStatus(false)() + }) }) }) @@ -137,8 +153,13 @@ var _ = Describe("Sys", func() { Threshold: 4, } - It("should return an ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - It("should be initialized", AssertInitializationStatus(false)) + It("should err properly", func() { + By("returning ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("having the vault say that it is not yet initialized") + AssertInitializationStatus(false)() + }) }) }) }) @@ -153,8 +174,13 @@ var _ = Describe("Sys", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should return an ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - It("should be initialized", AssertInitializationStatus(true)) + It("should err properly", func() { + By("returning ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("having the vault say that it is still initialized") + AssertInitializationStatus(true)() + }) }) }) @@ -184,10 +210,6 @@ var _ = Describe("Sys", func() { } } - When("the vault is uninitialized", func() { - It("should return an ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) - }) - When("the vault is initialized", func() { var initOut *vaultkv.InitVaultOutput Context("with one share", func() { @@ -203,16 +225,15 @@ var _ = Describe("Sys", func() { unsealKey = initOut.Keys[0] }) - It("should not return an error", AssertNoError()) - Specify("Unseal should return that Vault is unsealed", AssertSealed(false)) - Specify("SealStatus should return that the Vault is unsealed", AssertStatusSealed(false)) + It("should unseal the vault", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) - When("the auth token is missing", func() { - JustBeforeEach(func() { - vault.AuthToken = "" - }) + By("returning that the unseal is finished") + AssertSealed(false)() - Specify("SealStatus should return that the vault is unsealed", AssertStatusSealed(false)) + By("having the vault say that it is now unsealed") + AssertStatusSealed(false)() }) When("an unseal is attempted after the vault is unsealed", func() { @@ -221,20 +242,33 @@ var _ = Describe("Sys", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should not return an error", AssertNoError()) - Specify("Unseal should return that Vault is unsealed", AssertSealed(false)) - Specify("SealStatus should return that the Vault is unsealed", AssertStatusSealed(false)) + It("should idempotently state that the vault is unsealed", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("returning that the unseal is finished") + AssertSealed(false)() + + By("having the vault say that it is now unsealed") + AssertStatusSealed(false)() + }) }) When("an unseal reset is requested after the vault is unsealed", func() { - It("should not return an error", AssertNoError()) - Specify("Unseal should return that Vault is unsealed", AssertSealed(false)) - Specify("SealStatus should return that the Vault is unsealed", AssertStatusSealed(false)) + JustBeforeEach(func() { + err = vault.ResetUnseal() + }) + + It("should err properly", func() { + By("returning an ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + }) }) }) When("the unseal key is wrong", func() { BeforeEach(func() { + // make the unseal key wrong unsealKey = initOut.Keys[0] replacementChar := "a" if unsealKey[0] == 'a' { @@ -244,21 +278,38 @@ var _ = Describe("Sys", func() { unsealKey = fmt.Sprintf("%s%s", replacementChar, unsealKey[1:]) }) - It("should return an ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - Specify("SealStatus should return that the Vault is still sealed", AssertStatusSealed(true)) + It("should err properly", func() { + By("returning an ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("having the vault say that it is still sealed") + AssertStatusSealed(true)() + }) }) When("the unseal key is improperly formatted", func() { - It("should return an ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) - Specify("SealStatus should return that the Vault is still sealed", AssertStatusSealed(true)) + BeforeEach(func() { + unsealKey = "bettergocatchitlol.png" + }) + + It("should err properly", func() { + By("returning an ErrBadRequest") + AssertErrorOfType(&vaultkv.ErrBadRequest{})() + + By("having the vault say that it is still sealed") + AssertStatusSealed(true)() + }) }) }) Context("with a threshold greater than one", func() { + var testShares, testThreshold int BeforeEach(func() { + testShares = 3 + testThreshold = 3 initOut, err = vault.InitVault(vaultkv.InitConfig{ - Shares: 3, - Threshold: 3, + Shares: testShares, + Threshold: testThreshold, }) }) @@ -268,34 +319,82 @@ var _ = Describe("Sys", func() { Specify("SealStatus should return that the Vault is still sealed", AssertStatusSealed(true)) }) - When("the unseal key is correct", func() { + When("the unseal keys are correct", func() { BeforeEach(func() { unsealKey = initOut.Keys[0] }) - It("should not return an error", AssertNoError()) - It("should increase the progress count", AssertProgressIs(1)) - Specify("Unseal should return that the vault is still sealed", AssertSealed(true)) - Specify("SealStatus should return that the Vault is still sealed", AssertStatusSealed(true)) + It("should unseal the vault", func() { + By("not returning an error after the first key is given") + Expect(err).NotTo(HaveOccurred()) - Context("and then another key is given", func() { - JustBeforeEach(func() { - output, err = vault.Unseal(initOut.Keys[1]) - }) + By("increasing the progress count to one") + AssertProgressIs(1)() + + /* + Test that values are getting populated, but it should be redundant + after the first key + */ + By("returning that the progress is 1") + Expect(output.Progress).To(Equal(1)) + + By("returning the correct threshold required") + Expect(output.Threshold).To(Equal(testThreshold)) + + By("returning the correct number of shares") + Expect(output.NumShares).To(Equal(testShares)) + + By("having a nonce") + Expect(output.Nonce).ToNot(BeEmpty()) + + By("returning the correct version") + Expect(output.Version).To(Equal(currentVaultVersion)) + + By("returning that the Vault is still sealed") + AssertSealed(true)() + + By("the vault saying that the vault is still sealed") + AssertStatusSealed(true)() + + By("not returning an error after the second key is given") + output, err = vault.Unseal(initOut.Keys[1]) + Expect(err).NotTo(HaveOccurred()) + + By("increasing the progress count to two") + AssertProgressIs(2)() + + By("returning that the Vault is still sealed") + AssertSealed(true)() - It("should not return an error", AssertNoError()) - It("should increase the progress count", AssertProgressIs(2)) - Specify("Unseal should return that the vault is still sealed", AssertSealed(true)) - Specify("SealStatus should return that the Vault is still sealed", AssertStatusSealed(true)) + By("the vault saying that the vault is still sealed") + AssertStatusSealed(true)() + + By("not returning an error after the final key is given") + output, err = vault.Unseal(initOut.Keys[2]) + Expect(err).NotTo(HaveOccurred()) + + By("returning that the Vault is now unsealed") + AssertSealed(false)() + + By("the vault saying that the vault is now unsealed") + AssertStatusSealed(false)() }) - Context("and then the unseal attempt is reset", func() { + Describe("ResetUnseal", func() { JustBeforeEach(func() { + output, err = vault.Unseal(initOut.Keys[0]) + Expect(err).NotTo(HaveOccurred()) + AssertProgressIs(1)() err = vault.ResetUnseal() }) - It("should not return an error", AssertNoError()) - It("should reset the progress count", AssertProgressIs(0)) + It("should reset the current unseal attempt", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having SealState claim that the progress is 0") + AssertProgressIs(0)() + }) }) }) }) @@ -308,7 +407,7 @@ var _ = Describe("Sys", func() { }) When("the vault is not initialized", func() { - It("should not return an error", AssertNoError()) + It("should not return an error", func() { Expect(err).NotTo(HaveOccurred()) }) }) When("the vault is initialized", func() { @@ -320,8 +419,14 @@ var _ = Describe("Sys", func() { }) }) When("the vault is already sealed", func() { - It("should not return an error", AssertNoError()) - Specify("The vault should be sealed", AssertStatusSealed(true)) + + It("should idempotently return that the vault is sealed", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("the vault claiming that it is still sealed") + AssertStatusSealed(true)() + }) }) When("the vault is unsealed", func() { @@ -331,19 +436,144 @@ var _ = Describe("Sys", func() { Expect(sealState).NotTo(BeNil()) Expect(sealState.Sealed).To(BeFalse()) }) - It("should not return an error", AssertNoError()) - Specify("The vault should be sealed", AssertStatusSealed(true)) - Context("but the user is not authenticated", func() { - BeforeEach(func() { - vault.AuthToken = "" + It("should seal the vault", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("the vault saying that it is now sealed") + AssertStatusSealed(true)() + }) + }) + }) + }) + + Describe("Mount", func() { + var testBackendName string + var testBackendConfig vaultkv.Mount + + JustBeforeEach(func() { + InitAndUnsealVault() + err = vault.EnableSecretsMount( + testBackendName, + testBackendConfig, + ) + }) + + BeforeEach(func() { + testBackendName = "beepboop" + testBackendConfig = vaultkv.Mount{ + Description: "a test mount", + } + }) + + Describe("Mounting a non-existent backend type", func() { + BeforeEach(func() { + testBackendConfig.Type = "dcgeduceohdursaoceh" + }) + + It("should return ErrBadRequest", AssertErrorOfType(&vaultkv.ErrBadRequest{})) + }) + + Describe("Mounting a KVv1 backend", func() { + BeforeEach(func() { + testBackendConfig.Type = vaultkv.MountTypeKV + if parseSemver(currentVaultVersion).LessThan(semver{0, 8, 0}) { + testBackendConfig.Type = vaultkv.MountTypeGeneric + } + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + + Describe("Listing the backends", func() { + var backendList map[string]vaultkv.Mount + JustBeforeEach(func() { + backendList, err = vault.ListMounts() + }) + + It("should show the new backend in the list", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having a backend with the correct name") + backend, ok := backendList[testBackendName] + Expect(ok).To(BeTrue()) + + By("having that backend display the correct type") + Expect(backend.Type).To(Equal(testBackendConfig.Type)) + + By("having that backend display the correct description") + Expect(backend.Description).To(Equal(testBackendConfig.Description)) + }) + }) + + Describe("Unmounting the backend", func() { + JustBeforeEach(func() { + err = vault.DisableSecretsMount(testBackendName) + }) + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + + Describe("Listing the backends", func() { + var backendList map[string]vaultkv.Mount + JustBeforeEach(func() { + backendList, err = vault.ListMounts() }) - It("should return ErrForbidden", AssertErrorOfType(&vaultkv.ErrForbidden{})) - Specify("The vault should remain unsealed", AssertStatusSealed(false)) + It("should provide a list that has the backend gone", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + + By("having the mount not be present") + _, ok := backendList[testBackendName] + Expect(ok).To(BeFalse()) + }) }) }) + Describe("Unmounting a backend that doesn't exist", func() { + JustBeforeEach(func() { + err = vault.DisableSecretsMount("hsaetdieogudsoearu") + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + }) + }) + + Describe("Mounting a KVv2 backend", func() { + BeforeEach(func() { + if parseSemver(currentVaultVersion).LessThan(semver{0, 10, 0}) { + Skip("KV version 2 did not exist before 0.10.0") + } + + testBackendConfig = vaultkv.Mount{ + Type: vaultkv.MountTypeKV, + Description: "A test v2 backend", + Options: vaultkv.KVMountOptions{}.WithVersion(2), + } + }) + + It("should not err", func() { Expect(err).NotTo(HaveOccurred()) }) + + Describe("Listing the backends", func() { + var backendList map[string]vaultkv.Mount + JustBeforeEach(func() { + backendList, err = vault.ListMounts() + }) + + It("should have a backend which is properly configured", func() { + By("not erroring") + Expect(err).NotTo(HaveOccurred()) + By("being in the returned list") + backend, ok := backendList[testBackendName] + Expect(ok).To(BeTrue(), "Expected the created backend to appear in the mount list") + + By("having a options entry") + Expect(backend.Options).NotTo(BeNil()) + + By("being version 2") + Expect(vaultkv.KVMountOptions(backend.Options).GetVersion()).To(Equal(2)) + }) + }) }) }) @@ -352,10 +582,6 @@ var _ = Describe("Sys", func() { err = vault.Health(true) }) - When("the vault is not initialized", func() { - It("should return ErrUninitialized", AssertErrorOfType(&vaultkv.ErrUninitialized{})) - }) - When("the vault is initialized", func() { var initOut *vaultkv.InitVaultOutput BeforeEach(func() { @@ -365,10 +591,6 @@ var _ = Describe("Sys", func() { }) }) - When("the vault is sealed", func() { - It("should return ErrSealed", AssertErrorOfType(&vaultkv.ErrSealed{})) - }) - When("the vault is unsealed", func() { BeforeEach(func() { sealState, err := vault.Unseal(initOut.Keys[0]) @@ -377,14 +599,14 @@ var _ = Describe("Sys", func() { Expect(sealState.Sealed).To(BeFalse()) }) - It("should not return an error", AssertNoError()) + It("should not return an error", func() { Expect(err).NotTo(HaveOccurred()) }) When("the auth token is wrong", func() { BeforeEach(func() { vault.AuthToken = "" }) - It("should not return an error", AssertNoError()) + It("should not return an error", func() { Expect(err).NotTo(HaveOccurred()) }) }) }) }) diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/test b/vendor/github.com/cloudfoundry-community/vaultkv/test new file mode 100755 index 0000000..9175e07 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/vaultkv/test @@ -0,0 +1,17 @@ +#!/bin/bash + +vaultVersions=( +"0.9.6" +"0.10.4" +"0.11.6" +"1.0.3" +"1.1.1" +) + +if [[ $1 == "latest" ]]; then + vaultVersions=("${vaultVersions[${#vaultVersions[@]} - 1]}") +fi + +for version in "${vaultVersions[@]}"; do + ginkgo -noisySkippings=false -p -cover -coverprofile "vaultkv${version}.coverprofile" -- -v="$version" +done diff --git a/vendor/github.com/cloudfoundry-community/vaultkv/vaultkv_suite_test.go b/vendor/github.com/cloudfoundry-community/vaultkv/vaultkv_suite_test.go index b13add1..6a6e122 100644 --- a/vendor/github.com/cloudfoundry-community/vaultkv/vaultkv_suite_test.go +++ b/vendor/github.com/cloudfoundry-community/vaultkv/vaultkv_suite_test.go @@ -9,6 +9,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "flag" "fmt" "io" "io/ioutil" @@ -19,11 +20,14 @@ import ( "os" "path/filepath" "runtime" + "strconv" + "strings" "testing" "time" "github.com/cloudfoundry-community/vaultkv" . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" . "github.com/onsi/gomega" ) @@ -36,33 +40,68 @@ func TestVaultkv(t *testing.T) { AfterEach(StopVault) RegisterFailHandler(Fail) - for i, version := range vaultVersions { - currentVaultVersion = version - RunSpecs(t, fmt.Sprintf("Vaultkv - Vault Version %s", currentVaultVersion)) - if i != len(vaultVersions)-1 { - fmt.Println("") - fmt.Println("") - fmt.Println("========================================================") - fmt.Println(`|/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\|`) - fmt.Println("========================================================") - fmt.Println("") - } + + if currentVaultVersion == "" { + panic("Must specify vault version") } + + RunSpecs(t, fmt.Sprintf("Vaultkv - Vault Version %s", currentVaultVersion)) + fmt.Println("") + fmt.Println("") + fmt.Println("========================================================") + fmt.Println(`|/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\|`) + fmt.Println("========================================================") + fmt.Println("") } func init() { - vaultVersions = []string{ - "0.6.5", - "0.7.3", - "0.8.3", - "0.9.6", + flag.StringVar(¤tVaultVersion, "v", "", "version specifies the vault version to test") +} + +type semver struct { + major, minor, patch uint +} + +func parseSemver(s string) semver { + sections := strings.Split(s, ".") + if len(sections) != 3 { + panic(fmt.Sprintf("You didn't give me a real semver: %s", s)) + } + + sectionsInt := [3]uint64{} + for i, section := range sections { + sectionsInt[i], err = strconv.ParseUint(section, 10, 64) + if err != nil { + panic("Semver section was not parseable as a uint") + } } - if os.Getenv("VAULTKV_TEST_ONLY_LATEST") != "" { - vaultVersions = vaultVersions[len(vaultVersions)-1:] + return semver{ + major: uint(sectionsInt[0]), + minor: uint(sectionsInt[1]), + patch: uint(sectionsInt[2]), } } +func (s1 semver) LessThan(s2 semver) bool { + if s1.major < s2.major { + return true + } + if s1.major > s2.major { + return false + } + + if s1.minor < s2.minor { + return true + } + + if s1.minor > s2.minor { + return false + } + + return s1.patch < s2.patch +} + //The current vault client used by each spec var vault *vaultkv.Client var err error @@ -169,9 +208,23 @@ func downloadVault(version string) error { return nil } -var _ = BeforeSuite(func() { +var _ = SynchronizedBeforeSuite(func() []byte { + _, err = os.Stat(buildVaultPath(currentVaultVersion)) + if err != nil { + if os.IsNotExist(err) { + err = downloadVault(currentVaultVersion) + if err != nil { + panic(fmt.Sprintf("Could not download vault: %s", err)) + } + } else { + panic(fmt.Sprintf("Could not stat Vault path `%s': %s", buildVaultPath(currentVaultVersion), err.Error())) + } + } + + return []byte(buildVaultPath(currentVaultVersion)) +}, func(vaultPath []byte) { var err error - const uriStr = "https://127.0.0.1:8201" + var uriStr = fmt.Sprintf("https://127.0.0.1:%d", 8202+(config.GinkgoConfig.ParallelNode*2)) vaultURI, err = url.Parse(uriStr) if err != nil { panic(fmt.Sprintf("Could not parse Vault URI: %s", uriStr)) @@ -245,10 +298,12 @@ listener "tcp" { } `, vaultURI.Host, certLocation, keyLocation) _, err = configFile.WriteString(vaultConfig) + if err != nil { panic(fmt.Sprintf("Could not write test config to file: %s", err)) } + configFile.Close() }) var _ = AfterSuite(func() { @@ -277,14 +332,7 @@ func StartVault(version string) { var err error _, err = os.Stat(buildVaultPath(version)) if err != nil { - if !os.IsNotExist(err) { - panic(fmt.Sprintf("Could not lookup Vault path `%s': %s", buildVaultPath(version), err.Error())) - } - - err = downloadVault(version) - if err != nil { - panic(fmt.Sprintf("When downloading Vault version `%s': %s", version, err.Error())) - } + panic(fmt.Sprintf("Could not stat Vault path `%s': %s", buildVaultPath(version), err.Error())) } //Gotta get that IPC from Vault in case we want to report errors @@ -369,7 +417,6 @@ func StopVault() { _ = <-processChan processOutputWriter.Close() processOutputReader.Close() - currentVaultProcess = nil } @@ -387,15 +434,27 @@ func NewTestClient() *vaultkv.Client { } } -func AssertNoError() func() { - return func() { - Expect(err).NotTo(HaveOccurred()) - } -} - func AssertErrorOfType(t interface{}) func() { return func() { Expect(err).To(HaveOccurred()) Expect(err).To(BeAssignableToTypeOf(t)) } } + +func InitAndUnsealVault() { + var initOut *vaultkv.InitVaultOutput + initOut, err = vault.InitVault(vaultkv.InitConfig{ + Shares: 1, + Threshold: 1, + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = vault.Unseal(initOut.Keys[0]) + Expect(err).NotTo(HaveOccurred()) + + err = vault.DisableSecretsMount("secret") + Expect(err).NotTo(HaveOccurred()) + + err = vault.EnableSecretsMount("secret", vaultkv.Mount{Type: "kv"}) + Expect(err).NotTo(HaveOccurred()) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 777da87..fe8afdd 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -21,10 +21,10 @@ "revisionTime": "2015-10-22T06:55:26Z" }, { - "checksumSHA1": "miWIA09L2UolqhOZiYo8liQxZmM=", + "checksumSHA1": "9IY/+XS/ETTqHBDFRt4iqtySSDo=", "path": "github.com/cloudfoundry-community/vaultkv", - "revision": "acdcc5e29d4703bb1b20548f3cb26b70773fb19e", - "revisionTime": "2018-04-10T15:24:34Z" + "revision": "0ed39daafa01eaa5f526c6bc2581e80896e9674f", + "revisionTime": "2019-04-18T21:12:05Z" }, { "checksumSHA1": "PM+dS3SGHWrjdQsPtbdnvanfgC8=",