vault 0.11.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dcb5948ae3d3f53115a8e0aef77b70d18c042550
4
- data.tar.gz: 143ffc1b71f99550e83548ba92f1edb0e7e4ef0a
2
+ SHA256:
3
+ metadata.gz: cd39dd364209eee99a269200710d73fbdd4d9fe7007b5b8db78d1861b5671c53
4
+ data.tar.gz: 3b9cd61321644f7a45d87e7f08dadd7cb6c20da6537e46a01f82b2975d302852
5
5
  SHA512:
6
- metadata.gz: a7473e4c1791e62f8814a677d0c71bce2ca9b51a32c534dfd11830bcbbd3a2b8e993dacf3df52bc747fc1473931801733731de5e3a00c1078a9e59f2cbaa75b8
7
- data.tar.gz: 7c7a6793019c4f67a7927f01e839cbe9d0ac288dea9affe6e564d6482eedb4da5553cf01a823b9615c572e06a937bef532fd4a80777feb2488e5b3f41cd7e9a6
6
+ metadata.gz: 9a5e084c51cf4528095d4911acf8b983e1b61673441862fee7a58c640faae85bd81157a2a53d64db3fe8387136f8ceff7da2ea33e295ad6c8813b1e9fd3ff4a0
7
+ data.tar.gz: '0256808c555dece8ff08124fbdfad7a92b054db0c85f3e0e3d907d28b30c9d3f5d4b9e9a96287c1f2b8697e06dfdab57f8ddc0ee66a0947e780b7833e196bbc2'
@@ -0,0 +1,42 @@
1
+ version: 2.1
2
+
3
+ references:
4
+ images:
5
+ ubuntu: &UBUNTU_IMAGE ubuntu-1604:201903-01
6
+
7
+ jobs:
8
+ test:
9
+ machine:
10
+ image: *UBUNTU_IMAGE
11
+ parameters:
12
+ ruby-version:
13
+ type: string
14
+ vault-version:
15
+ type: string
16
+ steps:
17
+ - checkout
18
+ - run:
19
+ name: Install vault
20
+ command: |
21
+ curl -sLo vault.zip https://releases.hashicorp.com/vault/<< parameters.vault-version >>/vault_<< parameters.vault-version >>_linux_amd64.zip
22
+ unzip vault.zip
23
+ mkdir -p ~/bin
24
+ mv vault ~/bin
25
+ export PATH="~/bin:$PATH"
26
+ - run:
27
+ name: Run tests
28
+ command: |
29
+ export VAULT_VERSION=<< parameters.vault-version >>
30
+ rvm use << parameters.ruby-version >> --install --binary --fuzzy
31
+ bundle install --jobs=3 --retry=3 --path=vendor/bundle
32
+ bundle exec rake
33
+
34
+ workflows:
35
+ run-tests:
36
+ jobs:
37
+ - test:
38
+ matrix:
39
+ parameters:
40
+ ruby-version: ["2.2", "2.3", "2.4"]
41
+ vault-version: ["1.0.3", "1.1.5", "1.2.4", "1.3.0"]
42
+ name: test-ruby-<< matrix.ruby-version >>-vault-<< matrix.vault-version >>
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.gem
3
3
  *.rbc
4
4
  /.config
5
+ /.vscode
5
6
  /coverage/
6
7
  /InstalledFiles
7
8
  /pkg/
@@ -1,5 +1,45 @@
1
1
  # Vault Ruby Changelog
2
2
 
3
+ ## v0.14.0 (May 28, 2020)
4
+
5
+ IMPROVEMENTS
6
+
7
+ - Added support for the Transform Secrets Engine
8
+
9
+ ## v0.13.2 (May 7, 2020)
10
+
11
+ BUG FIXES
12
+
13
+ - Fixed the ability to use namespace as an option for each request. Previously, that option was ignored.
14
+ - aws-sigv4 gem was unlocked after a bug in 1.1.2 broke CI
15
+
16
+ ## v0.13.1 (April 28, 2020)
17
+
18
+ IMPROVEMENTS
19
+
20
+ - Added support for defining a namespace when initializing the client, as well as options for changing the namespace via method.
21
+ - Added support for sys/namespaces API. Ability to Get, Create, Delete, and List namespaces has been provided.
22
+
23
+ ## v0.13.0 (October 1, 2019)
24
+
25
+ IMPROVEMENTS
26
+
27
+ - Add support for versioned KV secrets in the client
28
+
29
+ ## v0.12.0 (August 14, 2018)
30
+
31
+ IMPROVEMENTS
32
+
33
+ - Expose the github login path as an optional argument
34
+ - Support HTTP basic auth [GH-181]
35
+ - Expose the AWS IAM path to use [GH-180]
36
+ - Add GCP Auth [GH-173]
37
+ - Add shutdown functionality to close persistent connections [GH-175]
38
+
39
+ BUG FIXES
40
+
41
+ - Specifing the hostname for SNI didn't work. The functionality has been disabled for now.
42
+
3
43
  ## v0.11.0 (March 19, 2018)
4
44
 
5
45
  IMPROVEMENTS
data/README.md CHANGED
@@ -28,6 +28,8 @@ Start a Vault client:
28
28
  ```ruby
29
29
  Vault.address = "http://127.0.0.1:8200" # Also reads from ENV["VAULT_ADDR"]
30
30
  Vault.token = "abcd-1234" # Also reads from ENV["VAULT_TOKEN"]
31
+ # Optional - if using the Namespace enterprise feature
32
+ # Vault.namespace = "my-namespace" # Also reads from ENV["VAULT_NAMESPACE"]
31
33
 
32
34
  Vault.sys.mounts #=> { :secret => #<struct Vault::Mount type="generic", description="generic secret storage"> }
33
35
  ```
@@ -43,6 +45,8 @@ Vault.configure do |config|
43
45
 
44
46
  # The token to authenticate with Vault, also read as ENV["VAULT_TOKEN"]
45
47
  config.token = "abcd-1234"
48
+ # Optional - if using the Namespace enterprise feature
49
+ # config.namespace = "my-namespace" # Also reads from ENV["VAULT_NAMESPACE"]
46
50
 
47
51
  # Proxy connection information, also read as ENV["VAULT_PROXY_(thing)"]
48
52
  config.proxy_address = "..."
@@ -85,7 +89,8 @@ And if you want to authenticate with a `AWS EC2` :
85
89
  # Export VAULT_ADDR to ENV then
86
90
  # Get the pkcs7 value from AWS
87
91
  signature = `curl http://169.254.169.254/latest/dynamic/instance-identity/pkcs7`
88
- vault_token = Vault.auth.aws_ec2(ENV['EC2_ROLE'], signature, nil)
92
+ iam_role = `curl http://169.254.169.254/latest/meta-data/iam/security-credentials/`
93
+ vault_token = Vault.auth.aws_ec2(iam_role, signature, nil)
89
94
  vault_client = Vault::Client.new(address: ENV["VAULT_ADDR"], token: vault_token.auth.client_token)
90
95
  ```
91
96
 
@@ -117,7 +122,9 @@ For advanced users, the first argument of the block is the attempt number and th
117
122
 
118
123
  ```ruby
119
124
  Vault.with_retries(Vault::HTTPConnectionError, Vault::HTTPError) do |attempt, e|
120
- log "Received exception #{e} from Vault - attempt #{attempt}"
125
+ if e
126
+ log "Received exception #{e} from Vault - attempt #{attempt}"
127
+ end
121
128
  Vault.logical.read("secret/bacon")
122
129
  end
123
130
  ```
@@ -206,7 +213,8 @@ Development
206
213
 
207
214
  Important Notes:
208
215
 
209
- - **All new features must include test coverage.** At a bare minimum, Unit tests are required. It is preferred if you include acceptance tests as well.
216
+ - **All new features must include test coverage.** At a bare minimum, Unit tests are required. It is preferred if you include integration tests as well.
210
217
  - **The tests must be be idempotent.** The HTTP calls made during a test should be able to be run over and over.
211
218
  - **Tests are order independent.** The default RSpec configuration randomizes the test order, so this should not be a problem.
212
219
  - **Integration tests require Vault** Vault must be available in the path for the integration tests to pass.
220
+ - **In order to be considered an integration test:** The test MUST use the `vault_test_client` or `vault_redirect_test_client` as the client. This spawns a process, or uses an already existing process from another test, to run against.
@@ -5,8 +5,10 @@ module Vault
5
5
  require_relative "api/auth_tls"
6
6
  require_relative "api/auth"
7
7
  require_relative "api/help"
8
+ require_relative "api/kv"
8
9
  require_relative "api/logical"
9
10
  require_relative "api/secret"
10
11
  require_relative "api/sys"
12
+ require_relative "api/transform"
11
13
  end
12
14
  end
@@ -155,9 +155,9 @@ module Vault
155
155
  # @param [String] github_token
156
156
  #
157
157
  # @return [Secret]
158
- def github(github_token)
158
+ def github(github_token, path="/v1/auth/github/login")
159
159
  payload = {token: github_token}
160
- json = client.post("/v1/auth/github/login", JSON.fast_generate(payload))
160
+ json = client.post(path, JSON.fast_generate(payload))
161
161
  secret = Secret.decode(json)
162
162
  client.token = secret.auth.client_token
163
163
  return secret
@@ -193,7 +193,7 @@ module Vault
193
193
  # for future requests.
194
194
  #
195
195
  # @example
196
- # Vault.auth.aws_iam("dev-role-iam", Aws::AssumeRoleCredentials.new, "vault.example.com", "https://sts.us-east-2.amazonaws.com") #=> #<Vault::Secret lease_id="">
196
+ # Vault.auth.aws_iam("dev-role-iam", Aws::InstanceProfileCredentials.new, "vault.example.com", "https://sts.us-east-2.amazonaws.com") #=> #<Vault::Secret lease_id="">
197
197
  #
198
198
  # @param [String] role
199
199
  # @param [CredentialProvider] credentials_provider
@@ -202,14 +202,17 @@ module Vault
202
202
  # As of Jan 2018, Vault will accept ANY or NO header if none is configured by the Vault server admin
203
203
  # @param [String] sts_endpoint optional
204
204
  # https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html
205
+ # @param [String] route optional
205
206
  # @return [Secret]
206
- def aws_iam(role, credentials_provider, iam_auth_header_value = nil, sts_endpoint = 'https://sts.amazonaws.com')
207
+ def aws_iam(role, credentials_provider, iam_auth_header_value = nil, sts_endpoint = 'https://sts.amazonaws.com', route = nil)
207
208
  require "aws-sigv4"
208
209
  require "base64"
209
210
 
210
211
  request_body = 'Action=GetCallerIdentity&Version=2011-06-15'
211
212
  request_method = 'POST'
212
213
 
214
+ route ||= '/v1/auth/aws/login'
215
+
213
216
  vault_headers = {
214
217
  'User-Agent' => Vault::Client::USER_AGENT,
215
218
  'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'
@@ -236,7 +239,29 @@ module Vault
236
239
  iam_request_body: Base64.strict_encode64(request_body)
237
240
  }
238
241
 
239
- json = client.post('/v1/auth/aws/login', JSON.fast_generate(payload))
242
+ json = client.post(route, JSON.fast_generate(payload))
243
+ secret = Secret.decode(json)
244
+ client.token = secret.auth.client_token
245
+ return secret
246
+ end
247
+
248
+ # Authenticate via the GCP authentication method. If authentication is
249
+ # successful, the resulting token will be stored on the client and used
250
+ # for future requests.
251
+ #
252
+ # @example
253
+ # Vault.auth.gcp("read-only", "jwt", "gcp") #=> #<Vault::Secret lease_id="">
254
+ #
255
+ # @param [String] role
256
+ # @param [String] jwt
257
+ # jwt returned by the instance identity metadata, or iam api
258
+ # @param [String] path optional
259
+ # the path were the gcp auth backend is mounted
260
+ #
261
+ # @return [Secret]
262
+ def gcp(role, jwt, path = 'gcp')
263
+ payload = { role: role, jwt: jwt }
264
+ json = client.post("/v1/auth/#{CGI.escape(path)}/login", JSON.fast_generate(payload))
240
265
  secret = Secret.decode(json)
241
266
  client.token = secret.auth.client_token
242
267
  return secret
@@ -0,0 +1,207 @@
1
+ require_relative "secret"
2
+ require_relative "../client"
3
+ require_relative "../request"
4
+ require_relative "../response"
5
+
6
+ module Vault
7
+ class Client
8
+ # A proxy to the {KV} methods.
9
+ # @return [KV]
10
+ def kv(mount)
11
+ KV.new(self, mount)
12
+ end
13
+ end
14
+
15
+ class KV < Request
16
+ attr_reader :mount
17
+
18
+ def initialize(client, mount)
19
+ super client
20
+
21
+ @mount = mount
22
+ end
23
+
24
+ # List the names of secrets at the given path, if the path supports
25
+ # listing. If the the path does not exist, an empty array will be returned.
26
+ #
27
+ # @example
28
+ # Vault.kv("secret").list("foo") #=> ["bar", "baz"]
29
+ #
30
+ # @param [String] path
31
+ # the path to list
32
+ #
33
+ # @return [Array<String>]
34
+ def list(path = "", options = {})
35
+ headers = extract_headers!(options)
36
+ json = client.list("/v1/#{mount}/metadata/#{encode_path(path)}", {}, headers)
37
+ json[:data][:keys] || []
38
+ rescue HTTPError => e
39
+ return [] if e.code == 404
40
+ raise
41
+ end
42
+
43
+ # Read the secret at the given path. If the secret does not exist, +nil+
44
+ # will be returned. The latest version is returned by default, but you
45
+ # can request a specific version.
46
+ #
47
+ # @example
48
+ # Vault.kv("secret").read("password") #=> #<Vault::Secret lease_id="">
49
+ #
50
+ # @param [String] path
51
+ # the path to read
52
+ # @param [Integer] version
53
+ # the version of the secret
54
+ #
55
+ # @return [Secret, nil]
56
+ def read(path, version = nil, options = {})
57
+ headers = extract_headers!(options)
58
+ params = {}
59
+ params[:version] = version unless version.nil?
60
+
61
+ json = client.get("/v1/#{mount}/data/#{encode_path(path)}", params, headers)
62
+ return Secret.decode(json[:data])
63
+ rescue HTTPError => e
64
+ return nil if e.code == 404
65
+ raise
66
+ end
67
+
68
+ # Read the metadata of a secret at the given path. If the secret does not
69
+ # exist, nil will be returned.
70
+ #
71
+ # @example
72
+ # Vault.kv("secret").read_metadata("password") => {...}
73
+ #
74
+ # @param [String] path
75
+ # the path to read
76
+ #
77
+ # @return [Hash, nil]
78
+ def read_metadata(path)
79
+ client.get("/v1/#{mount}/metadata/#{encode_path(path)}")[:data]
80
+ rescue HTTPError => e
81
+ return nil if e.code == 404
82
+ raise
83
+ end
84
+
85
+ # Write the secret at the given path with the given data. Note that the
86
+ # data must be a {Hash}!
87
+ #
88
+ # @example
89
+ # Vault.logical.write("secret/password", value: "secret") #=> #<Vault::Secret lease_id="">
90
+ #
91
+ # @param [String] path
92
+ # the path to write
93
+ # @param [Hash] data
94
+ # the data to write
95
+ #
96
+ # @return [Secret]
97
+ def write(path, data = {}, options = {})
98
+ headers = extract_headers!(options)
99
+ json = client.post("/v1/#{mount}/data/#{encode_path(path)}", JSON.fast_generate(:data => data), headers)
100
+ if json.nil?
101
+ return true
102
+ else
103
+ return Secret.decode(json)
104
+ end
105
+ end
106
+
107
+ # Write the metadata of a secret at the given path. Note that the data must
108
+ # be a {Hash}.
109
+ #
110
+ # @example
111
+ # Vault.kv("secret").write_metadata("password", max_versions => 3)
112
+ #
113
+ # @param [String] path
114
+ # the path to write
115
+ # @param [Hash] metadata
116
+ # the metadata to write
117
+ #
118
+ # @return [true]
119
+ def write_metadata(path, metadata = {})
120
+ client.post("/v1/#{mount}/metadata/#{encode_path(path)}", JSON.fast_generate(metadata))
121
+
122
+ true
123
+ end
124
+
125
+ # Delete the secret at the given path. If the secret does not exist, vault
126
+ # will still return true.
127
+ #
128
+ # @example
129
+ # Vault.logical.delete("secret/password") #=> true
130
+ #
131
+ # @param [String] path
132
+ # the path to delete
133
+ #
134
+ # @return [true]
135
+ def delete(path)
136
+ client.delete("/v1/#{mount}/data/#{encode_path(path)}")
137
+
138
+ true
139
+ end
140
+
141
+ # Mark specific versions of a secret as deleted.
142
+ #
143
+ # @example
144
+ # Vault.kv("secret").delete_versions("password", [1, 2])
145
+ #
146
+ # @param [String] path
147
+ # the path to remove versions from
148
+ # @param [Array<Integer>] versions
149
+ # an array of versions to remove
150
+ #
151
+ # @return [true]
152
+ def delete_versions(path, versions)
153
+ client.post("/v1/#{mount}/delete/#{encode_path(path)}", JSON.fast_generate(versions: versions))
154
+
155
+ true
156
+ end
157
+
158
+ # Mark specific versions of a secret as active.
159
+ #
160
+ # @example
161
+ # Vault.kv("secret").undelete_versions("password", [1, 2])
162
+ #
163
+ # @param [String] path
164
+ # the path to enable versions for
165
+ # @param [Array<Integer>] versions
166
+ # an array of versions to mark as undeleted
167
+ #
168
+ # @return [true]
169
+ def undelete_versions(path, versions)
170
+ client.post("/v1/#{mount}/undelete/#{encode_path(path)}", JSON.fast_generate(versions: versions))
171
+
172
+ true
173
+ end
174
+
175
+ # Completely remove a secret and its metadata.
176
+ #
177
+ # @example
178
+ # Vault.kv("secret").destroy("password")
179
+ #
180
+ # @param [String] path
181
+ # the path to remove
182
+ #
183
+ # @return [true]
184
+ def destroy(path)
185
+ client.delete("/v1/#{mount}/metadata/#{encode_path(path)}")
186
+
187
+ true
188
+ end
189
+
190
+ # Completely remove specific versions of a secret.
191
+ #
192
+ # @example
193
+ # Vault.kv("secret").destroy_versions("password", [1, 2])
194
+ #
195
+ # @param [String] path
196
+ # the path to remove versions from
197
+ # @param [Array<Integer>] versions
198
+ # an array of versions to destroy
199
+ #
200
+ # @return [true]
201
+ def destroy_versions(path, versions)
202
+ client.post("/v1/#{mount}/destroy/#{encode_path(path)}", JSON.fast_generate(versions: versions))
203
+
204
+ true
205
+ end
206
+ end
207
+ end
@@ -32,6 +32,18 @@ module Vault
32
32
  # @return [Hash<Symbol, Object>]
33
33
  field :data, freeze: true
34
34
 
35
+ # @!attribute [r] metadata
36
+ # Read-only metadata information related to the secret.
37
+ #
38
+ # @example Reading metadata
39
+ # secret = Vault.logical(:versioned).read("secret", "foo")
40
+ # secret.metadata[:created_time] #=> "2018-12-08T04:22:54.168065Z"
41
+ # secret.metadata[:version] #=> 1
42
+ # secret.metadata[:destroyed] #=> false
43
+ #
44
+ # @return [Hash<Symbol, Object>]
45
+ field :metadata, freeze: true
46
+
35
47
  # @!attribute [r] lease_duration
36
48
  # The number of seconds this lease is valid. If this number is 0 or nil,
37
49
  # the secret does not expire.
@@ -21,5 +21,6 @@ require_relative "sys/init"
21
21
  require_relative "sys/leader"
22
22
  require_relative "sys/lease"
23
23
  require_relative "sys/mount"
24
+ require_relative "sys/namespace"
24
25
  require_relative "sys/policy"
25
26
  require_relative "sys/seal"
@@ -16,6 +16,11 @@ module Vault
16
16
  # Type of the mount.
17
17
  # @return [String]
18
18
  field :type
19
+
20
+ # @!attribute [r] type
21
+ # Options given to the mount.
22
+ # @return [Hash<Symbol, Object>]
23
+ field :options
19
24
  end
20
25
 
21
26
  class Sys < Request
@@ -44,8 +49,8 @@ module Vault
44
49
  # the type of mount
45
50
  # @param [String] description
46
51
  # a human-friendly description (optional)
47
- def mount(path, type, description = nil)
48
- payload = { type: type }
52
+ def mount(path, type, description = nil, options = {})
53
+ payload = options.merge type: type
49
54
  payload[:description] = description if !description.nil?
50
55
 
51
56
  client.post("/v1/sys/mounts/#{encode_path(path)}", JSON.fast_generate(payload))
@@ -0,0 +1,85 @@
1
+ module Vault
2
+ class Namespace < Response
3
+ # @!attribute [r] id
4
+ # ID of the namespace
5
+ # @return [String]
6
+ field :id
7
+
8
+ # @!attribute [r] path
9
+ # Path of the namespace, includes parent paths if nested.
10
+ # @return [String]
11
+ field :path
12
+ end
13
+
14
+ class Sys
15
+ # List all namespaces in a given scope. Ignores nested namespaces.
16
+ #
17
+ # @example
18
+ # Vault.sys.namespaces #=> { :foo => #<struct Vault::Namespace id="xxxx1", path="foo/" }
19
+ #
20
+ # @return [Hash<Symbol, Namespace>]
21
+ #
22
+ # NOTE: Due to a bug in Vault Enterprise, to be fixed soon, this method CAN return a pure JSON string if a scoping namespace is provided.
23
+ def namespaces(scoped=nil)
24
+ path = ["v1", scoped, "sys", "namespaces"].compact
25
+ json = client.list(path.join("/"))
26
+ json = json[:data] if json[:data]
27
+ if json[:key_info]
28
+ json = json[:key_info]
29
+ hash = {}
30
+ json.each do |k,v|
31
+ hash[k.to_s.chomp("/").to_sym] = Namespace.decode(v)
32
+ end
33
+ hash
34
+ else
35
+ json
36
+ end
37
+ end
38
+
39
+ # Create a namespace. Nests the namespace if a namespace header is provided.
40
+ #
41
+ # @example
42
+ # Vault.sys.create_namespace("foo")
43
+ #
44
+ # @param [String] namespace
45
+ # the potential path of the namespace, without any parent path provided
46
+ #
47
+ # @return [true]
48
+ def create_namespace(namespace)
49
+ client.put("/v1/sys/namespaces/#{namespace}", {})
50
+ return true
51
+ end
52
+
53
+ # Delete a namespace. Raises an error if the namespace provided is not empty.
54
+ #
55
+ # @example
56
+ # Vault.sys.delete_namespace("foo")
57
+ #
58
+ # @param [String] namespace
59
+ # the path of the namespace to be deleted
60
+ #
61
+ # @return [true]
62
+ def delete_namespace(namespace)
63
+ client.delete("/v1/sys/namespaces/#{namespace}")
64
+ return true
65
+ end
66
+
67
+ # Retrieve a namespace by path.
68
+ #
69
+ # @example
70
+ # Vault.sys.get_namespace("foo")
71
+ #
72
+ # @param [String] namespace
73
+ # the path of the namespace ot be retrieved
74
+ #
75
+ # @return [Namespace]
76
+ def get_namespace(namespace)
77
+ json = client.get("/v1/sys/namespaces/#{namespace}")
78
+ if data = json.dig(:data)
79
+ Namespace.decode(data)
80
+ else
81
+ json
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,29 @@
1
+ require_relative '../client'
2
+ require_relative '../request'
3
+
4
+ module Vault
5
+ class Client
6
+ # A proxy to the {Transform} methods.
7
+ # @return [Transform]
8
+ def transform
9
+ @transform ||= Transform.new(self)
10
+ end
11
+ end
12
+
13
+ class Transform < Request
14
+ def encode(role_name:, **opts)
15
+ opts ||= {}
16
+ client.post("/v1/transform/encode/#{encode_path(role_name)}", JSON.fast_generate(opts))
17
+ end
18
+
19
+ def decode(role_name:, **opts)
20
+ opts ||= {}
21
+ client.post("/v1/transform/decode/#{encode_path(role_name)}", JSON.fast_generate(opts))
22
+ end
23
+ end
24
+ end
25
+
26
+ require_relative 'transform/alphabet'
27
+ require_relative 'transform/role'
28
+ require_relative 'transform/template'
29
+ require_relative 'transform/transformation'
@@ -0,0 +1,43 @@
1
+ require_relative '../../request'
2
+ require_relative '../../response'
3
+
4
+ module Vault
5
+ class Transform < Request
6
+ class Alphabet < Response
7
+ # @!attribute [r] id
8
+ # String listing all possible characters of the alphabet
9
+ # @return [String]
10
+ field :alphabet
11
+ end
12
+
13
+ def create_alphabet(name, alphabet:, **opts)
14
+ opts ||= {}
15
+ opts[:alphabet] = alphabet
16
+ client.post("/v1/transform/alphabet/#{encode_path(name)}", JSON.fast_generate(opts))
17
+ return true
18
+ end
19
+
20
+ def get_alphabet(name)
21
+ json = client.get("/v1/transform/alphabet/#{encode_path(name)}")
22
+ if data = json.dig(:data)
23
+ Alphabet.decode(data)
24
+ else
25
+ json
26
+ end
27
+ end
28
+
29
+ def delete_alphabet(name)
30
+ client.delete("/v1/transform/alphabet/#{encode_path(name)}")
31
+ true
32
+ end
33
+
34
+ def alphabets
35
+ json = client.list("/v1/transform/alphabet")
36
+ if keys = json.dig(:data, :keys)
37
+ keys
38
+ else
39
+ json
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../../request'
2
+ require_relative '../../response'
3
+
4
+ module Vault
5
+ class Transform < Request
6
+ class Role < Response
7
+ # @!attribute [r] transformations
8
+ # Array of all transformations the role has access to
9
+ # @return [Array<String>]
10
+ field :transformations
11
+ end
12
+
13
+ def create_role(name, **opts)
14
+ opts ||= {}
15
+ client.post("/v1/transform/role/#{encode_path(name)}", JSON.fast_generate(opts))
16
+ return true
17
+ end
18
+
19
+ def get_role(name)
20
+ json = client.get("/v1/transform/role/#{encode_path(name)}")
21
+ if data = json.dig(:data)
22
+ Role.decode(data)
23
+ else
24
+ json
25
+ end
26
+ end
27
+
28
+ def delete_role(name)
29
+ client.delete("/v1/transform/role/#{encode_path(name)}")
30
+ true
31
+ end
32
+
33
+ def roles
34
+ json = client.list("/v1/transform/role")
35
+ if keys = json.dig(:data, :keys)
36
+ keys
37
+ else
38
+ json
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../../request'
2
+ require_relative '../../response'
3
+
4
+ module Vault
5
+ class Transform < Request
6
+ class Template < Response
7
+ # @!attribute [r] alphabet
8
+ # Name of the alphabet to be used in the template
9
+ # @return [String]
10
+ field :alphabet
11
+
12
+ # @!attribute [r] pattern
13
+ # Regex string to detect and match for the template
14
+ # @return [String]
15
+ field :pattern
16
+
17
+ # @!attribute [r] type
18
+ # Type of the template, currently, only "regex" is supported
19
+ # @return [String]
20
+ field :type
21
+ end
22
+
23
+ def create_template(name, type:, pattern:, **opts)
24
+ opts ||= {}
25
+ opts[:type] = type
26
+ opts[:pattern] = pattern
27
+ client.post("/v1/transform/template/#{encode_path(name)}", JSON.fast_generate(opts))
28
+ return true
29
+ end
30
+
31
+ def get_template(name)
32
+ json = client.get("/v1/transform/template/#{encode_path(name)}")
33
+ if data = json.dig(:data)
34
+ Template.decode(data)
35
+ else
36
+ json
37
+ end
38
+ end
39
+
40
+ def delete_template(name)
41
+ client.delete("/v1/transform/template/#{encode_path(name)}")
42
+ true
43
+ end
44
+
45
+ def templates
46
+ json = client.list("/v1/transform/template")
47
+ if keys = json.dig(:data, :keys)
48
+ keys
49
+ else
50
+ json
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,61 @@
1
+ require_relative '../../request'
2
+ require_relative '../../response'
3
+
4
+ module Vault
5
+ class Transform < Request
6
+ class Transformation < Response
7
+ # @!attribute [r] allowed_roles
8
+ # Array of role names that are allowed to use this transformation
9
+ # @return [Array<String>]
10
+ field :allowed_roles
11
+
12
+ # @!attribute [r] templates
13
+ # Array of template names accessible to this transformation
14
+ # @return [Array<String>]
15
+ field :templates
16
+
17
+ # @!attribute [r] tweak_source
18
+ # String representing how a tweak is provided for this transformation.
19
+ # Available tweaks are "supplied", "generated", and "internal"
20
+ # @return [String]
21
+ field :tweak_source
22
+
23
+ # @!attribute [r] type
24
+ # String representing the type of transformation this is.
25
+ # Available types are "fpe", and "masking"
26
+ # @return [String]
27
+ field :type
28
+ end
29
+
30
+ def create_transformation(name, type:, template:, **opts)
31
+ opts ||= {}
32
+ opts[:type] = type
33
+ opts[:template] = template
34
+ client.post("/v1/transform/transformation/#{encode_path(name)}", JSON.fast_generate(opts))
35
+ return true
36
+ end
37
+
38
+ def get_transformation(name)
39
+ json = client.get("/v1/transform/transformation/#{encode_path(name)}")
40
+ if data = json.dig(:data)
41
+ Transformation.decode(data)
42
+ else
43
+ json
44
+ end
45
+ end
46
+
47
+ def delete_transformation(name)
48
+ client.delete("/v1/transform/transformation/#{encode_path(name)}")
49
+ true
50
+ end
51
+
52
+ def transformations
53
+ json = client.list("/v1/transform/transformation")
54
+ if keys = json.dig(:data, :keys)
55
+ keys
56
+ else
57
+ json
58
+ end
59
+ end
60
+ end
61
+ end
@@ -16,6 +16,9 @@ module Vault
16
16
  # The name of the header used to hold the Vault token.
17
17
  TOKEN_HEADER = "X-Vault-Token".freeze
18
18
 
19
+ # The name of the header used to hold the Namespace.
20
+ NAMESPACE_HEADER = "X-Vault-Namespace".freeze
21
+
19
22
  # The name of the header used to hold the wrapped request ttl.
20
23
  WRAP_TTL_HEADER = "X-Vault-Wrap-TTL".freeze
21
24
 
@@ -85,10 +88,6 @@ module Vault
85
88
 
86
89
  @nhp = PersistentHTTP.new("vault-ruby", nil, pool_size)
87
90
 
88
- if hostname
89
- @nhp.hostname = hostname
90
- end
91
-
92
91
  if proxy_address
93
92
  proxy_uri = URI.parse "http://#{proxy_address}"
94
93
 
@@ -158,6 +157,12 @@ module Vault
158
157
 
159
158
  private :pool
160
159
 
160
+ # Shutdown any open pool connections. Pool will be recreated upon next request.
161
+ def shutdown
162
+ @nhp.shutdown()
163
+ @nhp = nil
164
+ end
165
+
161
166
  # Creates and yields a new client object with the given token. This may be
162
167
  # used safely in a threadsafe manner because the original client remains
163
168
  # unchanged. The value of the block is returned.
@@ -236,6 +241,9 @@ module Vault
236
241
  # Build the URI and request object from the given information
237
242
  uri = build_uri(verb, path, data)
238
243
  request = class_for_request(verb).new(uri.request_uri)
244
+ if uri.userinfo()
245
+ request.basic_auth uri.user, uri.password
246
+ end
239
247
 
240
248
  if proxy_address and uri.scheme.downcase == "https"
241
249
  raise SecurityError, "no direct https connection to vault"
@@ -250,6 +258,12 @@ module Vault
250
258
  headers[TOKEN_HEADER] ||= token
251
259
  end
252
260
 
261
+ # Add the Vault Namespace header - users could still override this on a
262
+ # per-request basis
263
+ if !namespace.nil?
264
+ headers[NAMESPACE_HEADER] ||= namespace
265
+ end
266
+
253
267
  # Add headers
254
268
  headers.each do |key, value|
255
269
  request.add_field(key, value)
@@ -7,6 +7,7 @@ module Vault
7
7
  :address,
8
8
  :token,
9
9
  :hostname,
10
+ :namespace,
10
11
  :open_timeout,
11
12
  :proxy_address,
12
13
  :proxy_password,
@@ -61,6 +61,13 @@ module Vault
61
61
  nil
62
62
  end
63
63
 
64
+
65
+ # Vault Namespace, if any.
66
+ # @return [String, nil]
67
+ def namespace
68
+ ENV["VAULT_NAMESPACE"]
69
+ end
70
+
64
71
  # The SNI host to use when connecting to Vault via TLS.
65
72
  # @return [String, nil]
66
73
  def hostname
@@ -29,6 +29,7 @@ module Vault
29
29
  def extract_headers!(options = {})
30
30
  extract = {
31
31
  wrap_ttl: Vault::Client::WRAP_TTL_HEADER,
32
+ namespace: Vault::Client::NAMESPACE_HEADER,
32
33
  }
33
34
 
34
35
  {}.tap do |h|
@@ -1,3 +1,3 @@
1
1
  module Vault
2
- VERSION = "0.11.0"
2
+ VERSION = "0.14.0"
3
3
  end
@@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_runtime_dependency "aws-sigv4"
23
23
 
24
- spec.add_development_dependency "bundler"
25
- spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "bundler", "~> 2"
25
+ spec.add_development_dependency "pry", "~> 0.13.1"
26
26
  spec.add_development_dependency "rake", "~> 12.0"
27
27
  spec.add_development_dependency "rspec", "~> 3.5"
28
- spec.add_development_dependency "yard"
29
- spec.add_development_dependency "webmock", "~> 2.3"
28
+ spec.add_development_dependency "yard", "~> 0.9.24"
29
+ spec.add_development_dependency "webmock", "~> 3.8.3"
30
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vault
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Vargo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-19 00:00:00.000000000 Z
11
+ date: 2020-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sigv4
@@ -28,30 +28,30 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '2'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.13.1
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 0.13.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -84,30 +84,30 @@ dependencies:
84
84
  name: yard
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: 0.9.24
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: 0.9.24
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: webmock
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '2.3'
103
+ version: 3.8.3
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '2.3'
110
+ version: 3.8.3
111
111
  description: Vault is a Ruby API client for interacting with a Vault server.
112
112
  email:
113
113
  - sethvargo@gmail.com
@@ -115,9 +115,9 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - ".circleci/config.yml"
118
119
  - ".gitignore"
119
120
  - ".rspec"
120
- - ".travis.yml"
121
121
  - CHANGELOG.md
122
122
  - Gemfile
123
123
  - LICENSE
@@ -130,6 +130,7 @@ files:
130
130
  - lib/vault/api/auth_tls.rb
131
131
  - lib/vault/api/auth_token.rb
132
132
  - lib/vault/api/help.rb
133
+ - lib/vault/api/kv.rb
133
134
  - lib/vault/api/logical.rb
134
135
  - lib/vault/api/secret.rb
135
136
  - lib/vault/api/sys.rb
@@ -140,8 +141,14 @@ files:
140
141
  - lib/vault/api/sys/leader.rb
141
142
  - lib/vault/api/sys/lease.rb
142
143
  - lib/vault/api/sys/mount.rb
144
+ - lib/vault/api/sys/namespace.rb
143
145
  - lib/vault/api/sys/policy.rb
144
146
  - lib/vault/api/sys/seal.rb
147
+ - lib/vault/api/transform.rb
148
+ - lib/vault/api/transform/alphabet.rb
149
+ - lib/vault/api/transform/role.rb
150
+ - lib/vault/api/transform/template.rb
151
+ - lib/vault/api/transform/transformation.rb
145
152
  - lib/vault/client.rb
146
153
  - lib/vault/configurable.rb
147
154
  - lib/vault/defaults.rb
@@ -177,8 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
184
  - !ruby/object:Gem::Version
178
185
  version: '0'
179
186
  requirements: []
180
- rubyforge_project:
181
- rubygems_version: 2.6.14
187
+ rubygems_version: 3.1.2
182
188
  signing_key:
183
189
  specification_version: 4
184
190
  summary: Vault is a Ruby API client for interacting with a Vault server.
@@ -1,26 +0,0 @@
1
- dist: trusty
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
-
6
- env:
7
- - VAULT_VERSION=0.8.3
8
- - VAULT_VERSION=0.7.3
9
- - VAULT_VERSION=0.6.5
10
- - VAULT_VERSION=0.5.3
11
-
12
- before_install:
13
- - curl -sLo vault.zip https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip
14
- - unzip vault.zip
15
- - mkdir -p ~/bin
16
- - mv vault ~/bin
17
- - export PATH="~/bin:$PATH"
18
-
19
- branches:
20
- only:
21
- - master
22
-
23
- rvm:
24
- - 2.2
25
- - 2.3
26
- - 2.4