vault-provision 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +50 -0
  5. data/README.md +4 -0
  6. data/Rakefile +12 -0
  7. data/VERSION +1 -0
  8. data/examples/basic/auth/.keep +0 -0
  9. data/examples/basic/auth/ldap/.keep +0 -0
  10. data/examples/basic/auth/ldap/config.json +13 -0
  11. data/examples/basic/auth/ldap/groups/admin.json +3 -0
  12. data/examples/basic/auth/ldap/groups/operators.json +3 -0
  13. data/examples/basic/auth/token/.keep +0 -0
  14. data/examples/basic/pki-intermediate/config/.keep +0 -0
  15. data/examples/basic/pki-intermediate/config/crl.json +3 -0
  16. data/examples/basic/pki-intermediate/config/urls.json +4 -0
  17. data/examples/basic/pki-intermediate/intermediate/generate/internal.json +7 -0
  18. data/examples/basic/pki-intermediate/roles/.keep +0 -0
  19. data/examples/basic/pki-intermediate/roles/dvcert.json +19 -0
  20. data/examples/basic/pki-intermediate/roles/unlimited.json +18 -0
  21. data/examples/basic/pki-root/config/.keep +0 -0
  22. data/examples/basic/pki-root/config/crl.json +3 -0
  23. data/examples/basic/pki-root/config/urls.json +4 -0
  24. data/examples/basic/pki-root/roles/.keep +0 -0
  25. data/examples/basic/pki-root/roles/unlimited.json +18 -0
  26. data/examples/basic/pki-root/root/generate/internal.json +7 -0
  27. data/examples/basic/sys/auth.json +10 -0
  28. data/examples/basic/sys/auth/.keep +0 -0
  29. data/examples/basic/sys/auth/ldap.json +4 -0
  30. data/examples/basic/sys/auth/token.json +4 -0
  31. data/examples/basic/sys/mounts/.keep +0 -0
  32. data/examples/basic/sys/mounts/cubbyhole.json +8 -0
  33. data/examples/basic/sys/mounts/pki-intermediate.json +8 -0
  34. data/examples/basic/sys/mounts/pki-intermediate/tune.json +3 -0
  35. data/examples/basic/sys/mounts/pki-root.json +8 -0
  36. data/examples/basic/sys/mounts/pki-root/tune.json +3 -0
  37. data/examples/basic/sys/mounts/secret.json +8 -0
  38. data/examples/basic/sys/mounts/squirrel.json +8 -0
  39. data/examples/basic/sys/mounts/sys.json +8 -0
  40. data/examples/basic/sys/policy/.keep +0 -0
  41. data/examples/basic/sys/policy/default.hcl +21 -0
  42. data/examples/basic/sys/policy/master_of_secrets.json +21 -0
  43. data/examples/basic/sys/policy/pki-intermediates.json +21 -0
  44. data/examples/basic/sys/policy/response-wrapping.hcl +5 -0
  45. data/examples/basic/sys/policy/root.json +0 -0
  46. data/lib/vault/provision.rb +52 -0
  47. data/lib/vault/provision/auth.rb +4 -0
  48. data/lib/vault/provision/auth/ldap.rb +5 -0
  49. data/lib/vault/provision/auth/ldap/config.rb +43 -0
  50. data/lib/vault/provision/auth/ldap/groups.rb +3 -0
  51. data/lib/vault/provision/generic.rb +8 -0
  52. data/lib/vault/provision/pki.rb +23 -0
  53. data/lib/vault/provision/pki/config.rb +4 -0
  54. data/lib/vault/provision/pki/config/crl.json +24 -0
  55. data/lib/vault/provision/pki/config/urls.rb +24 -0
  56. data/lib/vault/provision/pki/intermediate.rb +4 -0
  57. data/lib/vault/provision/pki/intermediate/generate.rb +5 -0
  58. data/lib/vault/provision/pki/intermediate/generate/exported.rb +2 -0
  59. data/lib/vault/provision/pki/intermediate/generate/internal.rb +43 -0
  60. data/lib/vault/provision/pki/roles.rb +25 -0
  61. data/lib/vault/provision/pki/root.rb +4 -0
  62. data/lib/vault/provision/pki/root/generate.rb +5 -0
  63. data/lib/vault/provision/pki/root/generate/exported.rb +2 -0
  64. data/lib/vault/provision/pki/root/generate/internal.rb +24 -0
  65. data/lib/vault/provision/prototype.rb +25 -0
  66. data/lib/vault/provision/sys.rb +49 -0
  67. data/lib/vault/provision/sys/auth.rb +22 -0
  68. data/lib/vault/provision/sys/policy.rb +18 -0
  69. data/lib/vault_provision.rb +1 -0
  70. data/spec/spec_helper.rb +46 -0
  71. data/spec/vault_provision_spec.rb +59 -0
  72. data/vault-provision.gemspec +16 -0
  73. metadata +156 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77e526d32e962aec4cfdafc7f667c1767211bcae
4
+ data.tar.gz: 1b6ca464da212e641f8b47119f30203bcb45663d
5
+ SHA512:
6
+ metadata.gz: 0b2c5748e7d17721c40954677ae908dbe2f292db74c6e1ec5ede155efb90ece8db460412afea558e11583dd89477777b3c95d314d51cb97e68477683f5b6f950
7
+ data.tar.gz: e1c9aa9abc26e3a20a110bd5074f8d5231d4cb03e528051d34a7fbf0459d642d7dd24d3b6188df9590111a6a2d8bede0279bb61ad3395e3e01cc9e706bc81bf2
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ .bundle
2
+ .DS_Store
3
+ vendor/bundle
4
+ default.pem
5
+ *.swo
6
+ resources_dump.json
7
+ hosts.txt
8
+ out
9
+ coverage
10
+ todo
11
+ *.gem
12
+ vendor/ruby
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake', '~>12.0'
6
+ gem 'rspec', '~>3.5.0'
7
+ gem 'rspec-core', '~>3.5.4'
8
+
9
+ gem 'activesupport', '~>5.0.2'
10
+ gem 'vault', '~>0.9.0'
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ vault-provision (0.0.0)
5
+ vault (~> 0.9.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (5.0.2)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (~> 0.7)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
15
+ concurrent-ruby (1.0.5)
16
+ diff-lcs (1.3)
17
+ i18n (0.8.1)
18
+ minitest (5.10.1)
19
+ rake (12.0.0)
20
+ rspec (3.5.0)
21
+ rspec-core (~> 3.5.0)
22
+ rspec-expectations (~> 3.5.0)
23
+ rspec-mocks (~> 3.5.0)
24
+ rspec-core (3.5.4)
25
+ rspec-support (~> 3.5.0)
26
+ rspec-expectations (3.5.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.5.0)
29
+ rspec-mocks (3.5.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.5.0)
32
+ rspec-support (3.5.0)
33
+ thread_safe (0.3.6)
34
+ tzinfo (1.2.3)
35
+ thread_safe (~> 0.1)
36
+ vault (0.9.0)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ activesupport (~> 5.0.2)
43
+ rake (~> 12.0)
44
+ rspec (~> 3.5.0)
45
+ rspec-core (~> 3.5.4)
46
+ vault (~> 0.9.0)
47
+ vault-provision!
48
+
49
+ BUNDLED WITH
50
+ 1.14.6
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ This repo is trying to be a ruby implementation of what's described here:
2
+
3
+ https://www.hashicorp.com/blog/codifying-vault-policies-and-configuration/
4
+ https://github.com/hashicorp/vault-provision-example
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/setup'
2
+ require 'rspec'
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new :spec
7
+ rescue LoadError => e
8
+ puts "load error: #{e.message}"
9
+ end
10
+
11
+ task :default => :spec
12
+ task :test => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
File without changes
File without changes
@@ -0,0 +1,13 @@
1
+ {
2
+ "binddn": "",
3
+ "bindpass": "",
4
+ "certificate": "",
5
+ "discoverdn": false,
6
+ "groupdn": "ou=groups,dc=example,dc=com",
7
+ "insecure_tls": false,
8
+ "starttls": false,
9
+ "upndomain": "",
10
+ "url": "ldaps://ldap.example.com",
11
+ "userattr": "uid",
12
+ "userdn": "ou=people,dc=example,dc=com"
13
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "policies": "default,security_admin"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "policies": "default,master_of_secrets"
3
+ }
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ {
2
+ "expiry": "72h"
3
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "crl_distribution_points": "https://cdn.example.com/intermediate.crl",
3
+ "issuing_certificates": "https://cdn.example.com/intermediate.crt"
4
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "common_name": "some_intermediate_ca",
3
+ "exclude_cn_from_sans": true,
4
+ "ttl": "26280h",
5
+ "key_type": "rsa",
6
+ "key_bits": 2048
7
+ }
File without changes
@@ -0,0 +1,19 @@
1
+ {
2
+ "allow_any_name": false,
3
+ "allow_bare_domains": true,
4
+ "allow_ip_sans": true,
5
+ "allow_localhost": true,
6
+ "allow_subdomains": true,
7
+ "allow_token_displayname": false,
8
+ "allowed_domains": "example.com,example.net,vault.example.com",
9
+ "client_flag": true,
10
+ "code_signing_flag": false,
11
+ "email_protection_flag": false,
12
+ "enforce_hostnames": true,
13
+ "key_bits": 4096,
14
+ "key_type": "rsa",
15
+ "max_ttl": "4380h0m0s",
16
+ "server_flag": true,
17
+ "ttl": "4380h0m0s",
18
+ "use_csr_common_name": true
19
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "allow_any_name": true,
3
+ "allow_bare_domains": true,
4
+ "allow_base_domain": true,
5
+ "allow_ip_sans": true,
6
+ "allow_localhost": true,
7
+ "allow_subdomains": true,
8
+ "client_flag": true,
9
+ "code_signing_flag": false,
10
+ "email_protection_flag": false,
11
+ "enforce_hostnames": false,
12
+ "key_bits": 4096,
13
+ "key_type": "rsa",
14
+ "max_ttl": "4380h0m0s",
15
+ "server_flag": true,
16
+ "ttl": "4380h0m0s",
17
+ "use_csr_common_name": true
18
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ {
2
+ "expiry": "72h"
3
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "crl_distribution_points": "https://cdn.example.com/root.crl",
3
+ "issuing_certificates": "https://cdn.example.com/root.crt"
4
+ }
File without changes
@@ -0,0 +1,18 @@
1
+ {
2
+ "allow_any_name": true,
3
+ "allow_bare_domains": true,
4
+ "allow_base_domain": true,
5
+ "allow_ip_sans": true,
6
+ "allow_localhost": true,
7
+ "allow_subdomains": true,
8
+ "client_flag": true,
9
+ "code_signing_flag": false,
10
+ "email_protection_flag": false,
11
+ "enforce_hostnames": false,
12
+ "key_bits": 4096,
13
+ "key_type": "rsa",
14
+ "max_ttl": "4380h0m0s",
15
+ "server_flag": true,
16
+ "ttl": "4380h0m0s",
17
+ "use_csr_common_name": true
18
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "common_name": "some_root_ca",
3
+ "exclude_cn_from_sans": true,
4
+ "ttl": "87600h",
5
+ "key_type": "rsa",
6
+ "key_bits": 2048
7
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "ldap/": {
3
+ "description": "",
4
+ "type": "ldap"
5
+ },
6
+ "token/": {
7
+ "description": "token based credentials",
8
+ "type": "token"
9
+ }
10
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ {
2
+ "description": "",
3
+ "type": "ldap"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "description": "token based credentials",
3
+ "type": "token"
4
+ }
File without changes
@@ -0,0 +1,8 @@
1
+ {
2
+ "config": {
3
+ "default_lease_ttl": 0,
4
+ "max_lease_ttl": 0
5
+ },
6
+ "description": "per-token private secret storage",
7
+ "type": "cubbyhole"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "config": {
3
+ "default_lease_ttl": 0,
4
+ "max_lease_ttl": 315360000
5
+ },
6
+ "description": "",
7
+ "type": "pki"
8
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "max_lease_ttl": 315360000
3
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "config": {
3
+ "default_lease_ttl": 0,
4
+ "max_lease_ttl": 315360000
5
+ },
6
+ "description": "",
7
+ "type": "pki"
8
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "max_lease_ttl": 315360000
3
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "config": {
3
+ "default_lease_ttl": 0,
4
+ "max_lease_ttl": 0
5
+ },
6
+ "description": "generic secret storage",
7
+ "type": "generic"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "config": {
3
+ "default_lease_ttl": 0,
4
+ "max_lease_ttl": 0
5
+ },
6
+ "description": "https://youtu.be/-S_F9U9gNEQ",
7
+ "type": "generic"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "config": {
3
+ "default_lease_ttl": 0,
4
+ "max_lease_ttl": 0
5
+ },
6
+ "description": "system endpoints used for control, policy and debugging",
7
+ "type": "system"
8
+ }
File without changes
@@ -0,0 +1,21 @@
1
+
2
+ path "auth/token/lookup-self" {
3
+ capabilities = ["read"]
4
+ }
5
+
6
+ path "auth/token/renew-self" {
7
+ capabilities = ["update"]
8
+ }
9
+
10
+ path "auth/token/revoke-self" {
11
+ capabilities = ["update"]
12
+ }
13
+
14
+ path "cubbyhole/*" {
15
+ capabilities = ["create", "read", "update", "delete", "list"]
16
+ }
17
+
18
+ path "cubbyhole" {
19
+ capabilities = ["list"]
20
+ }
21
+
@@ -0,0 +1,21 @@
1
+ {
2
+ "path": [
3
+ {
4
+ "secret/*": {
5
+ "capabilities": [
6
+ "create",
7
+ "read",
8
+ "update",
9
+ "delete",
10
+ "list"
11
+ ]
12
+ },
13
+ "sys/auth/*": {
14
+ "capabilities": [
15
+ "read",
16
+ "list"
17
+ ]
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "path": [
3
+ {
4
+ "pki-intermediate/issue/dvcert": {
5
+ "capabilities": [
6
+ "create",
7
+ "read",
8
+ "update",
9
+ "delete",
10
+ "list"
11
+ ]
12
+ },
13
+ "sys/*": {
14
+ "capabilities": [
15
+ "read",
16
+ "list"
17
+ ]
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,5 @@
1
+
2
+ path "cubbyhole/response" {
3
+ capabilities = ["create", "read"]
4
+ }
5
+
File without changes
@@ -0,0 +1,52 @@
1
+ require 'vault'
2
+ require 'active_support/inflector'
3
+
4
+ class Vault::Provision; end
5
+ require 'vault/provision/prototype'
6
+
7
+ require 'vault/provision/auth'
8
+ require 'vault/provision/sys'
9
+ require 'vault/provision/pki'
10
+ require 'vault/provision/generic'
11
+
12
+ # controller for the children
13
+ class Vault::Provision
14
+ SYSTEM_POLICIES = ['response-wrapping', 'root'].freeze
15
+
16
+ attr_accessor :vault, :instance_dir, :intermediate_issuer
17
+
18
+ def initialize instance_dir,
19
+ address: ENV['VAULT_ADDR'],
20
+ token: ENV['VAULT_TOKEN'],
21
+ intermediate_issuer: {},
22
+ pki_force: false
23
+
24
+ @instance_dir = instance_dir
25
+ @vault = Vault::Client.new address: address, token: token
26
+ @intermediate_issuer = intermediate_issuer
27
+ @pki_force = pki_force
28
+ @handlers = [
29
+ Sys::Auth,
30
+ Auth::Ldap::Config,
31
+ Sys::Mounts,
32
+ Pki::Root::Generate::Internal,
33
+ Pki::Intermediate::Generate::Internal,
34
+ Pki::Config::Urls,
35
+ Pki::Roles,
36
+ Generic,
37
+ Sys::Policy,
38
+ #Auth::Ldap::Groups,
39
+ ]
40
+ end
41
+
42
+ def provision!
43
+ @handlers.each do |handler|
44
+ puts "* Calling handler #{handler}"
45
+ handler.new(self).provision!
46
+ end
47
+ end
48
+
49
+ def pki_force?
50
+ @pki_force
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ # let there be authentication backends!
2
+ class Vault::Provision::Auth; end
3
+
4
+ require 'vault/provision/auth/ldap'
@@ -0,0 +1,5 @@
1
+ # placeholder
2
+ class Vault::Provision::Auth::Ldap; end
3
+
4
+ require 'vault/provision/auth/ldap/config'
5
+ require 'vault/provision/auth/ldap/groups'
@@ -0,0 +1,43 @@
1
+ # config LDAP authn
2
+ class Vault::Provision::Auth::Ldap::Config < Vault::Provision::Prototype
3
+ def ap_file auth_point
4
+ "#{@instance_dir}/auth/#{auth_point}/config.json"
5
+ end
6
+
7
+ def repo_files
8
+ return @repo_files if @repo_files
9
+ #puts "*** calling repo_files"
10
+
11
+ auths = @vault.sys.auths
12
+
13
+ aps = auths.keys.select do |auth_point|
14
+ next unless auths[auth_point].type == 'ldap'
15
+ #puts "**** got auth mount #{auth_point}"
16
+ next unless FileTest.file? ap_file(auth_point)
17
+
18
+ repo_config = JSON.parse(File.read(ap_file(auth_point)))
19
+ vault_config = begin
20
+ @vault.get("auth/#{auth_point}config")['data']
21
+ rescue Vault::HTTPClientError => e
22
+ #puts "**** new #{auth_point} config"
23
+ raise e unless e.code == 404
24
+ {}
25
+ end
26
+
27
+ # for each key in the repo JSON file's hash, compare to current
28
+ # vault state. If they're identical, go on to the next mount point.
29
+ !repo_config.keys.inject(true) { |acc,elem| acc && vault_config[elem] == repo_config[elem]}
30
+ end
31
+ #puts "**** aps is #{aps}"
32
+ map_out = aps.map { |auth_point| ap_file(auth_point) }
33
+ #puts "**** returning map_out of #{map_out}"
34
+ @repo_files = map_out
35
+ end
36
+
37
+ def provision!
38
+ repo_files.each do |rf|
39
+ auth_point = rf.split('/')[-2]
40
+ @vault.post "v1/auth/#{auth_point}/config", File.read(rf)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ # placeholder
2
+ class Vault::Provision::Auth::Ldap::Groups < Vault::Provision::Prototype
3
+ end
@@ -0,0 +1,8 @@
1
+ # Generic secret (k/v pairs) backend provisioning
2
+ class Vault::Provision::Generic < Vault::Provision::Prototype
3
+ # the generic secret API doesn't have anything to configure!
4
+ # https://www.vaultproject.io/api/secret/generic/index.html
5
+ def provision!
6
+ true
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ # PKI/CA backend provisioning
2
+ module Vault::Provision::Pki
3
+ # placeholder
4
+ class Root; end
5
+ # placeholder
6
+ class Intermediate; end
7
+
8
+ def generated? path
9
+ @vault.get "#{path}/ca/pem"
10
+ true
11
+ rescue Vault::HTTPClientError
12
+ false
13
+ end
14
+
15
+ def ca_type path
16
+ path.match(/pki-intermediate/) && true
17
+ end
18
+ end
19
+
20
+ require 'vault/provision/pki/root'
21
+ require 'vault/provision/pki/intermediate'
22
+ require 'vault/provision/pki/config'
23
+ require 'vault/provision/pki/roles'
@@ -0,0 +1,4 @@
1
+ # config crl & distribution points for CAs
2
+ class Vault::Provision::Pki::Config; end
3
+
4
+ require 'vault/provision/pki/config/urls'
@@ -0,0 +1,24 @@
1
+ # config crl & distribution points for CAs
2
+ class Vault::Provision::Pki::Config::Urls < Vault::Provision::Prototype
3
+ include Vault::Provision::Pki
4
+
5
+ def provision!
6
+ paths = @vault.sys.mounts.select { |_,a| a.type == 'pki' } .keys
7
+
8
+ paths.each do |path|
9
+ r_crl_file = "#{@instance_dir}/#{path}/config/crl.json"
10
+ r_urls_file = "#{@instance_dir}/#{path}/config/urls.json"
11
+
12
+ r_crl = JSON.dump(File.read(r_crl_file))
13
+ r_urls = JSON.dump(File.read(r_urls_file))
14
+
15
+ v_crl = @vault.get("#{path}config/crl")['data']
16
+ same = r_crl.keys.inject(true) {|acc,elem| acc && r_crl[elem] == v_crl[elem] }
17
+ @vault.post("#{path}config/crl", r_crl) unless same
18
+
19
+ v_urls = @vault.get("#{path}config/urls")['data']
20
+ same = r_urls.keys.inject(true) {|acc,elem| acc && r_urls[elem] == v_urls[elem] }
21
+ @vault.post("#{path}config/urls", r_urls) unless same
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # config crl & distribution points for CAs
2
+ class Vault::Provision::Pki::Config::Urls < Vault::Provision::Prototype
3
+ include Vault::Provision::Pki
4
+
5
+ def urls_file mount_point
6
+ "#{@instance_dir}/#{mount_point}/config/urls.json"
7
+ end
8
+
9
+ def repo_files
10
+ mounts = @vault.sys.mounts
11
+ pki_mounts = mounts.keys.select do |mp|
12
+ mounts[mp].type == 'pki' && FileTest.file?(urls_file(mp))
13
+ end
14
+ pki_mounts.map { |mp| urls_file(mp) }
15
+ end
16
+
17
+ def provision!
18
+ repo_files.each do |rf|
19
+ mount_point = rf.split('/')[-3]
20
+ #puts "**** mount #{mount_point} rf => #{rf}"
21
+ @vault.post "v1/#{mount_point}/config/urls", File.read(rf)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ # placeholder
2
+ class Vault::Provision::Pki::Intermediate; end
3
+
4
+ require 'vault/provision/pki/intermediate/generate'
@@ -0,0 +1,5 @@
1
+ # placeholder
2
+ class Vault::Provision::Pki::Intermediate::Generate; end
3
+
4
+ require 'vault/provision/pki/intermediate/generate/internal'
5
+ require 'vault/provision/pki/intermediate/generate/exported'
@@ -0,0 +1,2 @@
1
+ # placeholder
2
+ class Vault::Provision::Pki::Intermediate::Generate::Exported; end
@@ -0,0 +1,43 @@
1
+ # create the CA
2
+ class Vault::Provision::Pki::Intermediate::Generate::Internal < Vault::Provision::Prototype
3
+ include Vault::Provision::Pki
4
+
5
+ def gen_file mount_point
6
+ "#{@instance_dir}/#{mount_point}/intermediate/generate/internal.json"
7
+ end
8
+
9
+ def repo_files
10
+ mounts = @vault.sys.mounts
11
+ generators = mounts.keys.select do |mp|
12
+ mounts[mp].type == 'pki' && FileTest.file?(gen_file(mp))
13
+ end
14
+ generators.map { |mp| gen_file(mp) }
15
+ end
16
+
17
+ def provision!
18
+ repo_files.each do |rf|
19
+ mount_point = rf.split('/')[-4]
20
+ next if generated? mount_point
21
+ resp = @vault.post "v1/#{mount_point}/intermediate/generate/internal",
22
+ File.read(rf)
23
+ sign_intermediate_csr(mount_point, resp[:data][:csr])
24
+ end
25
+ end
26
+
27
+ def sign_intermediate_csr mount_point, csr
28
+ return if @intermediate_issuer.empty?
29
+ root = @intermediate_issuer[mount_point.to_sym]
30
+ return if root.nil?
31
+
32
+ req = JSON.parse(File.read(gen_file(mount_point)))
33
+ resp = @vault.post "v1/#{root}/root/sign-intermediate",
34
+ JSON.dump(csr: csr,
35
+ common_name: req['common_name'],
36
+ ttl: req['ttl'],
37
+ max_path_length: 0,
38
+ exclude_cn_from_sans: true)
39
+
40
+ @vault.post "v1/#{mount_point}/intermediate/set-signed",
41
+ JSON.dump(certificate: resp[:data][:certificate])
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ # templates for certs
2
+ class Vault::Provision::Pki::Roles < Vault::Provision::Prototype
3
+ include Vault::Provision::Pki
4
+
5
+ def repo_files
6
+ mounts = @vault.sys.mounts
7
+ pki_mounts = mounts.keys.select { |mp| mounts[mp].type == 'pki' }
8
+ roles = []
9
+ pki_mounts.each do |mp|
10
+ Find.find("#{@instance_dir}/#{mp}/roles/").each do |rf|
11
+ next unless rf.end_with? '.json'
12
+ roles << rf
13
+ end
14
+ end
15
+ roles
16
+ end
17
+
18
+ def provision!
19
+ repo_files.each do |rf|
20
+ mount_point = rf.split('/')[-3]
21
+ role_name = File.basename(rf, '.json')
22
+ @vault.post "v1/#{mount_point}/roles/#{role_name}", File.read(rf)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ # placeholder
2
+ class Vault::Provision::Pki::Root; end
3
+
4
+ require 'vault/provision/pki/root/generate'
@@ -0,0 +1,5 @@
1
+ # placeholder
2
+ class Vault::Provision::Pki::Root::Generate; end
3
+
4
+ require 'vault/provision/pki/root/generate/internal'
5
+ require 'vault/provision/pki/root/generate/exported'
@@ -0,0 +1,2 @@
1
+ # placeholder
2
+ class Vault::Provision::Pki::Root::Generate::Exported; end
@@ -0,0 +1,24 @@
1
+ # create the CA
2
+ class Vault::Provision::Pki::Root::Generate::Internal < Vault::Provision::Prototype
3
+ include Vault::Provision::Pki
4
+
5
+ def gen_file mount_point
6
+ "#{@instance_dir}/#{mount_point}/root/generate/internal.json"
7
+ end
8
+
9
+ def repo_files
10
+ mounts = @vault.sys.mounts
11
+ generators = mounts.keys.select do |mp|
12
+ mounts[mp].type == 'pki' && FileTest.file?(gen_file(mp))
13
+ end
14
+ generators.map { |mp| gen_file(mp) }
15
+ end
16
+
17
+ def provision!
18
+ repo_files.each do |rf|
19
+ mount_point = rf.split('/')[-4]
20
+ next if generated? mount_point
21
+ @vault.post "v1/#{mount_point}/root/generate/internal", File.read(rf)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # prototype for the individual hierarchy paths
2
+ class Vault::Provision::Prototype
3
+ def initialize boss
4
+ @vault = boss.vault
5
+ @instance_dir = boss.instance_dir
6
+ @intermediate_issuer = boss.intermediate_issuer
7
+ end
8
+
9
+ def repo_prefix
10
+ ActiveSupport::Inflector.underscore(self.class.to_s)
11
+ .split('/')[2..-1].join('/')
12
+ end
13
+
14
+ def repo_path
15
+ "#{@instance_dir}/#{repo_prefix}"
16
+ end
17
+
18
+ def repo_files
19
+ Find.find(repo_path).select { |rf| rf.end_with?('.json') }
20
+ end
21
+
22
+ def provision!
23
+ puts "#{self.class} says: Go climb a tree!"
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require 'find'
2
+
3
+ # systems backend provisioning
4
+ class Vault::Provision::Sys; end
5
+ require 'vault/provision/sys/auth'
6
+ require 'vault/provision/sys/policy'
7
+
8
+ # secret mounts
9
+ class Vault::Provision::Sys::Mounts < Vault::Provision::Prototype
10
+ SYSTEM_MOUNTS = [
11
+ 'token',
12
+ 'cubbyhole',
13
+ 'sys',
14
+ 'secret'
15
+ ].freeze
16
+
17
+ def provision!
18
+ mounts = @vault.sys.mounts
19
+
20
+ repo_path = "#{@instance_dir}/sys/mounts"
21
+ change = []
22
+ Find.find(repo_path).each do |rf|
23
+ next unless rf.end_with?('.json')
24
+ next if rf.end_with?('/tune.json')
25
+
26
+ rf_base = File.basename rf, '.json'
27
+ next if SYSTEM_MOUNTS.include? rf_base
28
+ # puts "** processing mount #{rf_base}"
29
+
30
+ path = rf[(repo_path.length + 1)..-6].to_sym
31
+ r_conf = JSON.parse(File.read(rf))
32
+ rcc = r_conf['config'] || {}
33
+
34
+ unless mounts[path]
35
+ @vault.sys.mount(path.to_s, r_conf['type'], r_conf['description'])
36
+ @vault.sys.mount_tune(path.to_s, rcc)
37
+ change << @vault.sys.mounts[path]
38
+ next
39
+ end
40
+
41
+ vmc = mounts[path].config || {}
42
+ next if rcc.keys.inject(true) { |acc, elem| acc && (vmc[elem.to_sym] == rcc[elem]) }
43
+
44
+ @vault.sys.mount_tune(path.to_s, rcc)
45
+ change << @vault.sys.mounts[path]
46
+ end
47
+ change
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ # helps to enable authentication
2
+ class Vault::Provision::Sys::Auth < Vault::Provision::Prototype
3
+ def provision!
4
+ #puts "files: #{repo_files}"
5
+ auths = @vault.sys.auths
6
+
7
+ change = []
8
+ repo_files.each do |rf|
9
+ path = rf[(repo_path.length + 1)..-6].to_sym
10
+ r_conf = JSON.parse(File.read(rf))
11
+ # puts "** found #{path}"
12
+
13
+ next if auths[path]
14
+ # puts "** processing #{path}"
15
+ @vault.sys.enable_auth(path.to_s,
16
+ r_conf['type'], r_conf['description'])
17
+ change << @vault.sys.auths[path]
18
+ end
19
+
20
+ change
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # for rubocop, this comment is a matter of policy
2
+ class Vault::Provision::Sys::Policy < Vault::Provision::Prototype
3
+ def repo_files
4
+ Find.find(repo_path).select { |rf| rf.end_with?('.json', '.hcl') }
5
+ end
6
+
7
+ def provision!
8
+ repo_files.each do |rf|
9
+ policy_name = if rf.end_with? '.json'
10
+ File.basename(rf, '.json')
11
+ elsif rf.end_with? '.hcl'
12
+ File.basename(rf, '.hcl')
13
+ end
14
+ next if Vault::Provision::SYSTEM_POLICIES.include? policy_name
15
+ @vault.sys.put_policy(policy_name, File.read(rf))
16
+ end
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ require 'vault/provision'
@@ -0,0 +1,46 @@
1
+ GEM_DIR=File.expand_path(File.dirname(__FILE__) + '/../').freeze
2
+ $: << "#{GEM_DIR}/lib"
3
+
4
+ require 'vault_provision'
5
+ require 'open3'
6
+
7
+ DEV_VAULT_TOKEN = 'kittens'.freeze
8
+ DEV_VAULT_ADDR = 'http://127.0.0.1:8200'.freeze
9
+ EXAMPLE_DIR = "#{GEM_DIR}/examples/basic".freeze
10
+
11
+ ENV['VAULT_DEV_ROOT_TOKEN_ID'] = DEV_VAULT_TOKEN
12
+ ENV['VAULT_TOKEN'] = DEV_VAULT_TOKEN
13
+ ENV['VAULT_ADDR'] = DEV_VAULT_ADDR
14
+
15
+ Vault.configure do |config|
16
+ config.address = DEV_VAULT_ADDR
17
+ config.token = DEV_VAULT_TOKEN
18
+ end
19
+
20
+ def vault_server
21
+ stdin, stdout, stderr, server = Open3.popen3('vault server -dev')
22
+ cleanup = lambda do |_|
23
+ stdin.close
24
+ stdout.close
25
+ stderr.close
26
+ Process.kill :INT, server.pid
27
+ end
28
+ [:INT, :EXIT].each { |sig| trap(sig, cleanup) }
29
+ puts "server is #{server.pid}"
30
+ sleep(1) # woo race condition! wait for server to start up
31
+ server
32
+ end
33
+
34
+ def client
35
+ @client ||= Vault::Client.new
36
+ end
37
+
38
+ RSpec.configure do |config|
39
+ config.tty = true
40
+ config.raise_errors_for_deprecations!
41
+ end
42
+
43
+ @server = vault_server
44
+ signatories = {'pki-intermediate': 'pki-root'}
45
+
46
+ Vault::Provision.new(EXAMPLE_DIR, intermediate_issuer: signatories).provision!
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vault::Provision do
4
+ it "has a cubbyhole" do
5
+ expect(client.sys.mounts[:cubbyhole].description).to \
6
+ include 'per-token private secret storage'
7
+ end
8
+
9
+ it "has an ldap auth" do
10
+ expect(client.sys.auths[:ldap].type).to be == 'ldap'
11
+ end
12
+
13
+ it "has a token auth" do
14
+ expect(client.sys.auths[:token].type).to be == 'token'
15
+ end
16
+
17
+ it "has an ldap config" do
18
+ config_data = client.get('v1/auth/ldap/config')[:data]
19
+ expect(config_data[:url]).to be == 'ldaps://ldap.example.com'
20
+ end
21
+
22
+ it "has a pki-root mount" do
23
+ expect(client.sys.mounts.keys).to include :'pki-root'
24
+ end
25
+
26
+ it "has a CA" do
27
+ expect(client.get('v1/pki-root/ca/pem')).to be
28
+ end
29
+
30
+ it "has pki-root config urls" do
31
+ expect(client.get('v1/pki-root/config/urls')[:data][:crl_distribution_points].to_s).to include 'https://cdn.example.com'
32
+ end
33
+
34
+ it "has pki-intermediate config urls" do
35
+ expect(client.get('v1/pki-intermediate/config/urls')[:data][:issuing_certificates].to_s).to include 'https://cdn.example.com'
36
+ end
37
+
38
+ it "has pki-intermediate ca" do
39
+ expect(client.get('v1/pki-intermediate/ca/pem')).to be
40
+ end
41
+
42
+ it "has a dvcert role for intermediate" do
43
+ expect(client.get('v1/pki-intermediate/roles/dvcert')[:data][:allowed_domains]).to include "vault.example.com"
44
+ expect(client.get('v1/pki-intermediate/roles/dvcert')[:data][:allow_any_name]).to be_falsey
45
+ end
46
+
47
+ it "has an unlimited role for root" do
48
+ expect(client.get('v1/pki-root/roles/unlimited')[:data][:allow_any_name]).to be_truthy
49
+ end
50
+
51
+ it "has a master_of_secrets policy" do
52
+ expect(client.sys.policy('master_of_secrets').rules).to include '"sys/auth/*"'
53
+ expect(client.sys.policy('master_of_secrets').rules).to include '"secret/*"'
54
+ end
55
+
56
+ it "has a secret squirrel" do
57
+ expect(client.sys.mounts[:squirrel].type).to be == 'generic'
58
+ end
59
+ end
@@ -0,0 +1,16 @@
1
+ require 'find'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'vault-provision'
5
+ s.version = File.read("VERSION").chomp
6
+ s.summary = 'Provisioning utility for HashiCorp\'s Vault'
7
+ s.description = ''
8
+ s.authors = ["Tom Maher"]
9
+ s.email = "tmaher@pw0n.me"
10
+ s.license = "Apache-2.0"
11
+ s.files = `git ls-files`.split("\n")
12
+ s.homepage = 'https://github.com/tmaher/vault-provision'
13
+ s.add_dependency 'vault', '~>0.9.0'
14
+ s.add_development_dependency "rake", '~>12'
15
+ s.add_development_dependency "rspec", '~>3'
16
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vault-provision
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Maher
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: vault
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ description: ''
56
+ email: tmaher@pw0n.me
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".gitignore"
62
+ - Gemfile
63
+ - Gemfile.lock
64
+ - README.md
65
+ - Rakefile
66
+ - VERSION
67
+ - examples/basic/auth/.keep
68
+ - examples/basic/auth/ldap/.keep
69
+ - examples/basic/auth/ldap/config.json
70
+ - examples/basic/auth/ldap/groups/admin.json
71
+ - examples/basic/auth/ldap/groups/operators.json
72
+ - examples/basic/auth/token/.keep
73
+ - examples/basic/pki-intermediate/config/.keep
74
+ - examples/basic/pki-intermediate/config/crl.json
75
+ - examples/basic/pki-intermediate/config/urls.json
76
+ - examples/basic/pki-intermediate/intermediate/generate/internal.json
77
+ - examples/basic/pki-intermediate/roles/.keep
78
+ - examples/basic/pki-intermediate/roles/dvcert.json
79
+ - examples/basic/pki-intermediate/roles/unlimited.json
80
+ - examples/basic/pki-root/config/.keep
81
+ - examples/basic/pki-root/config/crl.json
82
+ - examples/basic/pki-root/config/urls.json
83
+ - examples/basic/pki-root/roles/.keep
84
+ - examples/basic/pki-root/roles/unlimited.json
85
+ - examples/basic/pki-root/root/generate/internal.json
86
+ - examples/basic/sys/auth.json
87
+ - examples/basic/sys/auth/.keep
88
+ - examples/basic/sys/auth/ldap.json
89
+ - examples/basic/sys/auth/token.json
90
+ - examples/basic/sys/mounts/.keep
91
+ - examples/basic/sys/mounts/cubbyhole.json
92
+ - examples/basic/sys/mounts/pki-intermediate.json
93
+ - examples/basic/sys/mounts/pki-intermediate/tune.json
94
+ - examples/basic/sys/mounts/pki-root.json
95
+ - examples/basic/sys/mounts/pki-root/tune.json
96
+ - examples/basic/sys/mounts/secret.json
97
+ - examples/basic/sys/mounts/squirrel.json
98
+ - examples/basic/sys/mounts/sys.json
99
+ - examples/basic/sys/policy/.keep
100
+ - examples/basic/sys/policy/default.hcl
101
+ - examples/basic/sys/policy/master_of_secrets.json
102
+ - examples/basic/sys/policy/pki-intermediates.json
103
+ - examples/basic/sys/policy/response-wrapping.hcl
104
+ - examples/basic/sys/policy/root.json
105
+ - lib/vault/provision.rb
106
+ - lib/vault/provision/auth.rb
107
+ - lib/vault/provision/auth/ldap.rb
108
+ - lib/vault/provision/auth/ldap/config.rb
109
+ - lib/vault/provision/auth/ldap/groups.rb
110
+ - lib/vault/provision/generic.rb
111
+ - lib/vault/provision/pki.rb
112
+ - lib/vault/provision/pki/config.rb
113
+ - lib/vault/provision/pki/config/crl.json
114
+ - lib/vault/provision/pki/config/urls.rb
115
+ - lib/vault/provision/pki/intermediate.rb
116
+ - lib/vault/provision/pki/intermediate/generate.rb
117
+ - lib/vault/provision/pki/intermediate/generate/exported.rb
118
+ - lib/vault/provision/pki/intermediate/generate/internal.rb
119
+ - lib/vault/provision/pki/roles.rb
120
+ - lib/vault/provision/pki/root.rb
121
+ - lib/vault/provision/pki/root/generate.rb
122
+ - lib/vault/provision/pki/root/generate/exported.rb
123
+ - lib/vault/provision/pki/root/generate/internal.rb
124
+ - lib/vault/provision/prototype.rb
125
+ - lib/vault/provision/sys.rb
126
+ - lib/vault/provision/sys/auth.rb
127
+ - lib/vault/provision/sys/policy.rb
128
+ - lib/vault_provision.rb
129
+ - spec/spec_helper.rb
130
+ - spec/vault_provision_spec.rb
131
+ - vault-provision.gemspec
132
+ homepage: https://github.com/tmaher/vault-provision
133
+ licenses:
134
+ - Apache-2.0
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.4.5.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Provisioning utility for HashiCorp's Vault
156
+ test_files: []