vault-provision 0.1.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.
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: []