Skip to content

Commit a954026

Browse files
committed
Merge remote-tracking branch 'origin/master' into add_shutdown_to_client
2 parents e157058 + 48cb396 commit a954026

15 files changed

Lines changed: 315 additions & 8 deletions

File tree

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ language: ruby
44
cache: bundler
55

66
env:
7+
- VAULT_VERSION=0.8.3
78
- VAULT_VERSION=0.7.3
89
- VAULT_VERSION=0.6.5
910
- VAULT_VERSION=0.5.3

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# Vault Ruby Changelog
22

3+
## v0.11.0 (March 19, 2018)
4+
5+
IMPROVEMENTS
6+
7+
- Access to health has been added.
8+
- Added ability to handle a Base64 encoded PEM (useful for certs in environment variables)
9+
- Added IAM EC2 authentication support
10+
- Add custom mount path support to TLS authentication
11+
312
## v0.10.1 (May 8, 2017)
413

514
IMPROVEMENTS
615

716
- `vault-ruby` is licensed under Mozilla Public License 2.0, and has been for over 2 years. This patch release updates the gemspec to use the correct SPDX ID string for reporting this license, but **no change to the licensing of this gem has occurred**.
817

18+
919
## v0.10.0 (April 19, 2017)
1020

1121
IMPROVEMENTS

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ Vault.configure do |config|
5353
# Custom SSL PEM, also read as ENV["VAULT_SSL_CERT"]
5454
config.ssl_pem_file = "/path/on/disk.pem"
5555

56+
# As an alternative to a pem file, you can provide the raw PEM string, also read in the following order of preference:
57+
# ENV["VAULT_SSL_PEM_CONTENTS_BASE64"] then ENV["VAULT_SSL_PEM_CONTENTS"]
58+
config.ssl_pem_contents = "-----BEGIN ENCRYPTED..."
59+
5660
# Use SSL verification, also read as ENV["VAULT_SSL_VERIFY"]
5761
config.ssl_verify = false
5862

@@ -75,6 +79,16 @@ client_1 = Vault::Client.new(address: "https://vault.mycompany.com")
7579
client_2 = Vault::Client.new(address: "https://other-vault.mycompany.com")
7680
```
7781

82+
And if you want to authenticate with a `AWS EC2` :
83+
84+
```ruby
85+
# Export VAULT_ADDR to ENV then
86+
# Get the pkcs7 value from AWS
87+
signature = `curl http://169.254.169.254/latest/dynamic/instance-identity/pkcs7`
88+
vault_token = Vault.auth.aws_ec2(ENV['EC2_ROLE'], signature, nil)
89+
vault_client = Vault::Client.new(address: ENV["VAULT_ADDR"], token: vault_token.auth.client_token)
90+
```
91+
7892
### Making requests
7993
All of the methods and API calls are heavily documented with examples inline using YARD. In order to keep the examples versioned with the code, the README only lists a few examples for using the Vault gem. Please see the inline documentation for the full API documentation. The tests in the 'spec' directory are an additional source of examples.
8094

lib/vault/api/auth.rb

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,69 @@ def github(github_token)
174174
# @param [String] pkcs7
175175
# pkcs7 returned by the instance identity document (with line breaks removed)
176176
# @param [String] nonce optional
177+
# @param [String] route optional
177178
#
178179
# @return [Secret]
179-
def aws_ec2(role, pkcs7, nonce = nil)
180+
def aws_ec2(role, pkcs7, nonce = nil, route = nil)
181+
route ||= '/v1/auth/aws-ec2/login'
180182
payload = { role: role, pkcs7: pkcs7 }
181183
# Set a custom nonce if client is providing one
182184
payload[:nonce] = nonce if nonce
183-
json = client.post('/v1/auth/aws-ec2/login', JSON.fast_generate(payload))
185+
json = client.post(route, JSON.fast_generate(payload))
186+
secret = Secret.decode(json)
187+
client.token = secret.auth.client_token
188+
return secret
189+
end
190+
191+
# Authenticate via AWS IAM auth method by providing a AWS CredentialProvider (either ECS, AssumeRole, etc.)
192+
# If authentication is successful, the resulting token will be stored on the client and used
193+
# for future requests.
194+
#
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="">
197+
#
198+
# @param [String] role
199+
# @param [CredentialProvider] credentials_provider
200+
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CredentialProvider.html
201+
# @param [String] iam_auth_header_value optional
202+
# As of Jan 2018, Vault will accept ANY or NO header if none is configured by the Vault server admin
203+
# @param [String] sts_endpoint optional
204+
# https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html
205+
# @return [Secret]
206+
def aws_iam(role, credentials_provider, iam_auth_header_value = nil, sts_endpoint = 'https://sts.amazonaws.com')
207+
require "aws-sigv4"
208+
require "base64"
209+
210+
request_body = 'Action=GetCallerIdentity&Version=2011-06-15'
211+
request_method = 'POST'
212+
213+
vault_headers = {
214+
'User-Agent' => Vault::Client::USER_AGENT,
215+
'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'
216+
}
217+
218+
vault_headers['X-Vault-AWS-IAM-Server-ID'] = iam_auth_header_value if iam_auth_header_value
219+
220+
sig4_headers = Aws::Sigv4::Signer.new(
221+
service: 'sts',
222+
region: region_from_sts_endpoint(sts_endpoint),
223+
credentials_provider: credentials_provider
224+
).sign_request(
225+
http_method: request_method,
226+
url: sts_endpoint,
227+
headers: vault_headers,
228+
body: request_body
229+
).headers
230+
231+
payload = {
232+
role: role,
233+
iam_http_request_method: request_method,
234+
iam_request_url: Base64.strict_encode64(sts_endpoint),
235+
iam_request_headers: Base64.strict_encode64(vault_headers.merge(sig4_headers).to_json),
236+
iam_request_body: Base64.strict_encode64(request_body)
237+
}
238+
239+
json = client.post('/v1/auth/aws/login', JSON.fast_generate(payload))
184240
secret = Secret.decode(json)
185241
client.token = secret.auth.client_token
186242
return secret
@@ -215,5 +271,21 @@ def tls(pem = nil, path = 'cert')
215271
client.token = secret.auth.client_token
216272
return secret
217273
end
274+
275+
private
276+
277+
# Parse an AWS region from a STS endpoint
278+
# STS in the China (Beijing) region (cn-north-1) is sts.cn-north-1.amazonaws.com.cn
279+
# Take care changing below regex with that edge case in mind
280+
#
281+
# @param [String] sts_endpoint
282+
# https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html
283+
#
284+
# @return [String] aws region
285+
def region_from_sts_endpoint(sts_endpoint)
286+
valid_sts_endpoint = %r{https:\/\/sts\.?(.*).amazonaws.com}.match(sts_endpoint)
287+
raise "Unable to parse STS endpoint #{sts_endpoint}" unless valid_sts_endpoint
288+
valid_sts_endpoint[1].empty? ? 'us-east-1' : valid_sts_endpoint[1]
289+
end
218290
end
219291
end

lib/vault/api/sys.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Sys < Request; end
1616

1717
require_relative "sys/audit"
1818
require_relative "sys/auth"
19+
require_relative "sys/health"
1920
require_relative "sys/init"
2021
require_relative "sys/leader"
2122
require_relative "sys/lease"

lib/vault/api/sys/audit.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Audit < Response
1919
end
2020

2121
class Sys
22-
# List all audis for the vault.
22+
# List all audits for the vault.
2323
#
2424
# @example
2525
# Vault.sys.audits #=> { :file => #<Audit> }
@@ -70,5 +70,22 @@ def disable_audit(path)
7070
client.delete("/v1/sys/audit/#{encode_path(path)}")
7171
return true
7272
end
73+
74+
# Generates a HMAC verifier for a given input.
75+
#
76+
# @example
77+
# Vault.sys.audit_hash("file-audit", "my input") #=> "hmac-sha256:30aa7de18a5e90bbc1063db91e7c387b32b9fa895977eb8c177bbc91e7d7c542"
78+
#
79+
# @param [String] path
80+
# the path of the audit backend
81+
# @param [String] input
82+
# the input to generate a HMAC for
83+
#
84+
# @return [String]
85+
def audit_hash(path, input)
86+
json = client.post("/v1/sys/audit-hash/#{encode_path(path)}", JSON.fast_generate(input: input))
87+
json = json[:data] if json[:data]
88+
json[:hash]
89+
end
7390
end
7491
end

lib/vault/api/sys/health.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
require "json"
2+
3+
module Vault
4+
class HealthStatus < Response
5+
# @!attribute [r] initialized
6+
# Whether the Vault server is Initialized.
7+
# @return [Boolean]
8+
field :initialized, as: :initialized?
9+
10+
# @!attribute [r] sealed
11+
# Whether the Vault server is Sealed.
12+
# @return [Boolean]
13+
field :sealed, as: :sealed?
14+
15+
# @!attribute [r] standby
16+
# Whether the Vault server is in Standby mode.
17+
# @return [Boolean]
18+
field :standby, as: :standby?
19+
20+
# @!attribute [r] replication_performance_mode
21+
# Verbose description of DR mode (added in 0.9.2)
22+
# @return [String]
23+
field :replication_performance_mode
24+
25+
# @!attribute [r] replication_dr_mode
26+
# Verbose description of DR mode (added in 0.9.2)
27+
# @return [String]
28+
field :replication_dr_mode
29+
30+
# @!attribute [r] server_time_utc
31+
# Server time in Unix seconds, UTC
32+
# @return [Fixnum]
33+
field :server_time_utc
34+
35+
# @!attribute [r] version
36+
# Server Vault version string (added in 0.6.1)
37+
# @return [String]
38+
field :version
39+
40+
# @!attribute [r] cluster_name
41+
# Server cluster name
42+
# @return [String]
43+
field :cluster_name
44+
45+
# @!attribute [r] cluster_id
46+
# Server cluster UUID
47+
# @return [String]
48+
field :cluster_id
49+
end
50+
51+
class Sys
52+
# Show the health status for this vault.
53+
#
54+
# @example
55+
# Vault.sys.health_status #=> #Vault::HealthStatus @initialized=true, @sealed=false, @standby=false, @replication_performance_mode="disabled", @replication_dr_mode="disabled", @server_time_utc=1519776728, @version="0.9.3", @cluster_name="vault-cluster-997f514e", @cluster_id="c2dad70a-6d88-a06d-69f6-9ae7f5485998">
56+
#
57+
# @return [HealthStatus]
58+
def health_status
59+
json = client.get("/v1/sys/health", {:sealedcode => 200, :uninitcode => 200, :standbycode => 200})
60+
return HealthStatus.decode(json)
61+
end
62+
end
63+
end

lib/vault/defaults.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "pathname"
2+
require "base64"
23

34
module Vault
45
module Defaults
@@ -126,7 +127,11 @@ def ssl_ciphers
126127
# the value for {#ssl_pem_file}, if set.
127128
# @return [String, nil]
128129
def ssl_pem_contents
129-
ENV["VAULT_SSL_PEM_CONTENTS"]
130+
if ENV["VAULT_SSL_PEM_CONTENTS_BASE64"]
131+
Base64.decode64(ENV["VAULT_SSL_PEM_CONTENTS_BASE64"])
132+
else
133+
ENV["VAULT_SSL_PEM_CONTENTS"]
134+
end
130135
end
131136

132137
# The path to a pem on disk to use with custom SSL verification

lib/vault/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Vault
2-
VERSION = "0.10.1"
2+
VERSION = "0.11.0"
33
end

spec/integration/api/auth_spec.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "spec_helper"
2+
require "aws-sigv4"
23

34
module Vault
45
describe Auth do
@@ -211,5 +212,51 @@ module Vault
211212
}.to_not change { subject.token }
212213
end
213214
end
215+
216+
describe "#aws_iam", vault: "> 0.7.3" do
217+
before(:context) do
218+
vault_test_client.sys.enable_auth("aws", "aws", nil)
219+
vault_test_client.post("/v1/auth/aws/config/client", JSON.fast_generate("iam_server_id_header_value" => "iam_header_canary"))
220+
end
221+
222+
after(:context) do
223+
vault_test_client.sys.disable_auth("aws")
224+
end
225+
226+
let!(:old_token) { subject.token }
227+
let(:credentials_provider) do
228+
double(
229+
credentials:
230+
double(access_key_id: 'very', secret_access_key: 'secure', session_token: 'thing')
231+
)
232+
end
233+
let(:secret) { double(auth: double(client_token: 'a great token')) }
234+
235+
after do
236+
subject.token = old_token
237+
end
238+
239+
it "does not authenticate if iam_server_id_header_value does not match" do
240+
expect(::Aws::Sigv4::Signer).to(
241+
receive(:new).with(
242+
service: 'sts', region: 'cn-north-1', credentials_provider: credentials_provider
243+
).and_call_original
244+
)
245+
expect do
246+
subject.auth.aws_iam('a_rolename', credentials_provider, 'mismatched_iam_header', 'https://sts.cn-north-1.amazonaws.com.cn')
247+
end.to raise_error(Vault::HTTPClientError, /expected iam_header_canary but got mismatched_iam_header/)
248+
end
249+
250+
it "authenticates and saves the token on the client" do
251+
expect(subject).to receive(:post).and_return 'huzzah!'
252+
expect(Secret).to receive(:decode).and_return secret
253+
expect(::Aws::Sigv4::Signer).to(
254+
receive(:new).with(
255+
service: 'sts', region: 'cn-north-1', credentials_provider: credentials_provider
256+
).and_call_original
257+
)
258+
subject.auth.aws_iam('a_rolename', credentials_provider, 'iam_header_canary', 'https://sts.cn-north-1.amazonaws.com.cn')
259+
end
260+
end
214261
end
215262
end

0 commit comments

Comments
 (0)