foreman_vault 0.0.1 → 1.0.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +115 -10
  3. data/app/controllers/concerns/foreman_vault/controller/parameters/vault_connection.rb +1 -1
  4. data/app/lib/foreman_vault/macros.rb +10 -1
  5. data/app/models/concerns/foreman_vault/host_extensions.rb +31 -0
  6. data/app/models/concerns/foreman_vault/orchestration/vault_policy.rb +70 -0
  7. data/app/models/concerns/foreman_vault/provisioning_template_extensions.rb +15 -0
  8. data/app/models/setting/vault.rb +85 -0
  9. data/app/models/vault_connection.rb +38 -7
  10. data/app/services/foreman_vault/vault_auth_method.rb +58 -0
  11. data/app/services/foreman_vault/vault_client.rb +24 -6
  12. data/app/services/foreman_vault/vault_policy.rb +65 -0
  13. data/app/views/unattended/provisioning_templates/VaultPolicy/default.erb +6 -0
  14. data/app/views/vault_connections/_form.html.erb +20 -4
  15. data/app/views/vault_connections/index.html.erb +21 -7
  16. data/app/views/vault_connections/welcome.html.erb +12 -0
  17. data/db/migrate/20201203220058_add_approle_to_vault_connection.rb +8 -0
  18. data/db/seeds.d/103-provisioning_templates.rb +25 -0
  19. data/lib/foreman_vault/engine.rb +15 -3
  20. data/lib/foreman_vault/version.rb +1 -1
  21. data/test/factories/{foreman_vault_factories.rb → vault_connection.rb} +3 -3
  22. data/test/factories/vault_policy_template.rb +11 -0
  23. data/test/factories/vault_setting.rb +10 -0
  24. data/test/fixtures/ca.crt +21 -0
  25. data/test/functional/api/v2/vault_connections_controller_test.rb +1 -1
  26. data/test/models/foreman_vault/orchestration/vault_policy_test.rb +167 -0
  27. data/test/models/vault_policy_template_test.rb +28 -0
  28. data/test/test_plugin_helper.rb +0 -1
  29. data/test/unit/services/foreman_vault/vault_auth_method_test.rb +130 -0
  30. data/test/unit/services/foreman_vault/vault_client_test.rb +67 -8
  31. data/test/unit/services/foreman_vault/vault_policy_test.rb +171 -0
  32. metadata +33 -10
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class VaultPolicyTemplateTest < ActiveSupport::TestCase
6
+ let(:host) { FactoryBot.create(:host, :managed) }
7
+ let(:template) { FactoryBot.create(:provisioning_template, :vault_policy) }
8
+
9
+ it 'is rendered from a database' do
10
+ Foreman::Renderer.expects(:get_source).with(has_entry(klass: Foreman::Renderer::Source::Database))
11
+ Foreman::Renderer.stubs(:get_scope)
12
+ Foreman::Renderer.stubs(:render)
13
+
14
+ template.render
15
+ end
16
+
17
+ test 'render in default mode' do
18
+ assert_nothing_raised { template.render(host: host) }
19
+ end
20
+
21
+ test 'render in safe mode' do
22
+ assert_nothing_raised { template.render(renderer: Foreman::Renderer::SafeModeRenderer, host: host) }
23
+ end
24
+
25
+ test 'render in unsafe mode' do
26
+ assert_nothing_raised { template.render(renderer: Foreman::Renderer::UnsafeModeRenderer, host: host) }
27
+ end
28
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  # This calls the main test_helper in Foreman-core
4
4
  require 'test_helper'
5
- require 'vault'
6
5
 
7
6
  # Add plugin to FactoryBot's paths
8
7
  FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class VaultAuthMethodTest < ActiveSupport::TestCase
6
+ subject { ForemanVault::VaultAuthMethod.new(host) }
7
+
8
+ let(:host) { FactoryBot.create(:host, :managed) }
9
+
10
+ describe '#name' do
11
+ context 'with host and vault_policy_name' do
12
+ setup do
13
+ subject.stubs(:vault_policy_name).returns('vault_policy_name')
14
+ end
15
+
16
+ it { assert_equal "#{host}-vault_policy_name".parameterize, subject.name }
17
+ end
18
+
19
+ context 'without host' do
20
+ setup do
21
+ subject.stubs(:host).returns(nil)
22
+ subject.stubs(:vault_policy_name).returns('vault_policy_name')
23
+ end
24
+
25
+ it { assert_nil subject.name }
26
+ end
27
+
28
+ context 'without vault_policy_name' do
29
+ setup do
30
+ subject.stubs(:vault_policy_name).returns(nil)
31
+ end
32
+
33
+ it { assert_nil subject.name }
34
+ end
35
+ end
36
+
37
+ describe 'valid?' do
38
+ context 'with name and certificate' do
39
+ setup do
40
+ subject.stubs(:name).returns('name')
41
+ subject.stubs(:certificate).returns('cert')
42
+ end
43
+
44
+ it { assert subject.valid? }
45
+ end
46
+
47
+ context 'without name' do
48
+ setup do
49
+ subject.stubs(:name).returns(nil)
50
+ subject.stubs(:certificate).returns('cert')
51
+ end
52
+
53
+ it { assert_not subject.valid? }
54
+ end
55
+
56
+ context 'without certificate' do
57
+ setup do
58
+ subject.stubs(:name).returns('name')
59
+ subject.stubs(:certificate).returns(nil)
60
+ end
61
+
62
+ it { assert_not subject.valid? }
63
+ end
64
+ end
65
+
66
+ describe '#save' do
67
+ context 'when valid' do
68
+ it 'creates auth method in the Vault' do
69
+ subject.stubs(:name).returns('name')
70
+ subject.stubs(:vault_policy_name).returns('vault_policy_name')
71
+ subject.stubs(:certificate).returns('cert')
72
+
73
+ subject.expects(:set_certificate).once.with(
74
+ 'name',
75
+ certificate: 'cert',
76
+ token_policies: 'vault_policy_name',
77
+ allowed_common_names: [host.fqdn]
78
+ )
79
+ subject.save
80
+ end
81
+ end
82
+
83
+ context 'when not valid' do
84
+ it 'does not create auth method in the Vault' do
85
+ subject.stubs(:valid?).returns(false)
86
+
87
+ subject.expects(:set_certificate).never
88
+ subject.save
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#delete' do
94
+ context 'when valid' do
95
+ it 'deletes Certificate' do
96
+ subject.stubs(:valid?).returns(true)
97
+
98
+ subject.expects(:delete_certificate).once.with(subject.name)
99
+ subject.delete
100
+ end
101
+ end
102
+
103
+ context 'when not valid' do
104
+ it 'does not delete Certificate' do
105
+ subject.stubs(:valid?).returns(false)
106
+
107
+ subject.expects(:delete_certificate).never
108
+ subject.delete
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#certificate' do
114
+ setup do
115
+ Setting.find_by(name: 'ssl_ca_file').update(value: cert_path)
116
+ end
117
+
118
+ context 'when certificate file can be read' do
119
+ let(:cert_path) { File.join(ForemanVault::Engine.root, 'test/fixtures/ca.crt') }
120
+
121
+ it { assert_equal File.read(cert_path), subject.send(:certificate) }
122
+ end
123
+
124
+ context 'when certificate file cannot be read' do
125
+ let(:cert_path) { '/tmp/invalid.crt' }
126
+
127
+ it { assert_not subject.send(:certificate) }
128
+ end
129
+ end
130
+ end
@@ -3,10 +3,46 @@
3
3
  require 'test_plugin_helper'
4
4
 
5
5
  class VaultClientTest < ActiveSupport::TestCase
6
- setup do
7
- @subject = ForemanVault::VaultClient.new('http://127.0.0.1:8200', 'e57e5ef2-b25c-a0e5-65a6-863ab095dff6')
8
- @client = mock
9
- Vault::Client.expects(:new).returns(@client)
6
+ subject do
7
+ ForemanVault::VaultClient.new(base_url, token, nil, nil).tap do |vault_client|
8
+ vault_client.instance_variable_set(:@client, client)
9
+ end
10
+ end
11
+
12
+ let(:client) { Vault::Client }
13
+ let(:base_url) { 'http://127.0.0.1:8200' }
14
+ let(:token) { 's.opkr0MAqme5e5nr3i2or5wZC' }
15
+
16
+ describe 'auth with AppRole' do
17
+ subject { ForemanVault::VaultClient.new(base_url, nil, role_id, secret_id) }
18
+
19
+ let(:role_id) { '8403910c-e563-d2f2-1c77-6e26319be8b5' }
20
+ let(:secret_id) { '1058434b-b4aa-bf5a-b376-a15d9efb1059' }
21
+
22
+ setup do
23
+ stub_request(:post, "#{base_url}/v1/auth/approle/login").with(
24
+ body: {
25
+ role_id: role_id,
26
+ secret_id: secret_id
27
+ }
28
+ ).to_return(
29
+ status: 200,
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: {
32
+ auth: {
33
+ client_token: token
34
+ }
35
+ }.to_json
36
+ )
37
+ end
38
+
39
+ it { assert_equal token, subject.send(:client).token }
40
+ end
41
+
42
+ describe 'auth with token' do
43
+ subject { ForemanVault::VaultClient.new(base_url, token, nil, nil) }
44
+
45
+ it { assert_equal token, subject.send(:client).token }
10
46
  end
11
47
 
12
48
  describe '#fetch_expire_time' do
@@ -14,11 +50,11 @@ class VaultClientTest < ActiveSupport::TestCase
14
50
  @time = '2018-08-01T20:08:55.525830559+02:00'
15
51
  response = OpenStruct.new(data: { expire_time: @time })
16
52
  auth_token = mock.tap { |object| object.expects(:lookup_self).once.returns(response) }
17
- @client.expects(:auth_token).once.returns(auth_token)
53
+ client.expects(:auth_token).once.returns(auth_token)
18
54
  end
19
55
 
20
56
  test 'should return expire time' do
21
- assert_equal Time.zone.parse(@time), @subject.fetch_expire_time
57
+ assert_equal Time.zone.parse(@time), subject.fetch_expire_time
22
58
  end
23
59
  end
24
60
 
@@ -29,11 +65,34 @@ class VaultClientTest < ActiveSupport::TestCase
29
65
  response = OpenStruct.new(data: @data)
30
66
  logical = mock.tap { |object| object.expects(:read).once.with(@secret_path).returns(response) }
31
67
 
32
- @client.expects(:logical).once.returns(logical)
68
+ client.expects(:logical).once.returns(logical)
33
69
  end
34
70
 
35
71
  test 'should return expire time' do
36
- assert_equal @data, @subject.fetch_secret(@secret_path)
72
+ assert_equal @data, subject.fetch_secret(@secret_path)
73
+ end
74
+ end
75
+
76
+ describe '#fetch_certificate' do
77
+ setup do
78
+ @pki_path = '/pkiEngine/issue/testRole'
79
+ @data = {
80
+ certificate: 'CERTIFICATE_DATA',
81
+ expiration: 1_582_116_230,
82
+ issuing_ca: 'CA_CERTIFICATE_DATA',
83
+ private_key: 'PRIVATE_KEY_DATA',
84
+ private_key_type: 'rsa',
85
+ serial_number: '7e:2d:c8:dd:df:da:fe:1f:39:da:39:23:4f:74:c8:1f:1d:4a:db:a7'
86
+ }
87
+
88
+ response = OpenStruct.new(data: @data)
89
+ logical = mock.tap { |object| object.expects(:write).once.with(@pki_path).returns(response) }
90
+
91
+ client.expects(:logical).once.returns(logical)
92
+ end
93
+
94
+ test 'should return new certificate' do
95
+ assert_equal @data, subject.issue_certificate(@pki_path)
37
96
  end
38
97
  end
39
98
  end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class VaultPolicyTest < ActiveSupport::TestCase
6
+ subject { ForemanVault::VaultPolicy.new(host) }
7
+
8
+ let(:host) { FactoryBot.create(:host, :managed) }
9
+
10
+ setup do
11
+ FactoryBot.create(:setting, name: 'vault_policy_template', value: 'Default Vault Policy')
12
+ end
13
+
14
+ describe 'valid?' do
15
+ context 'with name and rules' do
16
+ setup do
17
+ subject.stubs(:name).returns('name')
18
+ subject.stubs(:rules).returns('rules')
19
+ end
20
+
21
+ it { assert subject.valid? }
22
+ end
23
+
24
+ context 'without name' do
25
+ setup do
26
+ subject.stubs(:name).returns(nil)
27
+ subject.stubs(:rules).returns('rules')
28
+ end
29
+
30
+ it { assert_not subject.valid? }
31
+ end
32
+
33
+ context 'without rules' do
34
+ setup do
35
+ subject.stubs(:name).returns('name')
36
+ subject.stubs(:rules).returns(nil)
37
+ end
38
+
39
+ it { assert_not subject.valid? }
40
+ end
41
+ end
42
+
43
+ describe '#name' do
44
+ context 'without corresponding Vault Policy template' do
45
+ it { assert_nil subject.name }
46
+ end
47
+
48
+ context 'with corresponding Vault Policy template' do
49
+ setup do
50
+ FactoryBot.create(:provisioning_template, :vault_policy, template: template)
51
+ end
52
+
53
+ let(:template) { '# NAME: <%= @host.name %>' }
54
+
55
+ it { assert_equal host.name.parameterize, subject.name }
56
+
57
+ context 'when name is empty' do
58
+ let(:template) { '# NAME:' }
59
+
60
+ it { assert_nil subject.name }
61
+ end
62
+
63
+ context 'when there is no name magic comment' do
64
+ let(:template) { '# BLAH:' }
65
+
66
+ it { assert_nil subject.name }
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#new?' do
72
+ setup do
73
+ FactoryBot.create(:provisioning_template, :vault_policy)
74
+ end
75
+
76
+ context 'policy already exists in the Vault' do
77
+ setup do
78
+ subject.stubs(:policies).returns([subject.name])
79
+ end
80
+
81
+ it { assert_not subject.new? }
82
+ end
83
+
84
+ context 'policy does not exist in the Vault' do
85
+ setup do
86
+ subject.stubs(:policies).returns([])
87
+ end
88
+
89
+ it { assert subject.new? }
90
+ end
91
+ end
92
+
93
+ describe '#save' do
94
+ context 'when valid' do
95
+ it 'creates Vault Policy' do
96
+ subject.stubs(:name).returns('name')
97
+ subject.stubs(:rules).returns('rules')
98
+
99
+ subject.expects(:put_policy).once.with(subject.name, subject.send(:rules))
100
+ subject.save
101
+ end
102
+ end
103
+
104
+ context 'when not valid' do
105
+ it 'does not create Vault Policy' do
106
+ subject.stubs(:valid?).returns(false)
107
+
108
+ subject.expects(:set_certificate).never
109
+ subject.save
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '#delete' do
115
+ context 'when valid' do
116
+ it 'deletes Vault Policy' do
117
+ subject.stubs(:valid?).returns(true)
118
+
119
+ subject.expects(:delete_policy).once.with(subject.name)
120
+ subject.delete
121
+ end
122
+ end
123
+
124
+ context 'when not valid' do
125
+ it 'does not delete Vault Policy' do
126
+ subject.stubs(:valid?).returns(false)
127
+
128
+ subject.expects(:delete_policy).never
129
+ subject.delete
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#rules' do
135
+ context 'without corresponding Vault Policy template' do
136
+ it { assert_nil subject.send(:rules) }
137
+ end
138
+
139
+ context 'with corresponding Vault Policy template' do
140
+ let(:rules) { 'path "secrets/data/*" { capabilities = ["create", "read", "update"] }' }
141
+
142
+ setup do
143
+ FactoryBot.create(:provisioning_template, :vault_policy, template: template)
144
+ end
145
+
146
+ let(:template) do
147
+ <<~TEMPLATE
148
+ # NAME: <%= @host.name %>
149
+
150
+ #{rules}
151
+ TEMPLATE
152
+ end
153
+
154
+ it { assert_equal "#{rules}\n", subject.send(:rules) }
155
+
156
+ context 'when Vault Policy template renders empty' do
157
+ let(:template) do
158
+ <<~TEMPLATE
159
+ # NAME: <%= @host.name %>
160
+
161
+ <% if false %>
162
+ #{rules}
163
+ <% end %>
164
+ TEMPLATE
165
+ end
166
+
167
+ it { assert_nil subject.send(:rules) }
168
+ end
169
+ end
170
+ end
171
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_vault
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dmTECH GmbH
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-21 00:00:00.000000000 Z
11
+ date: 2021-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: vault
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.54.0
55
- description:
55
+ description:
56
56
  email:
57
57
  - opensource@dm.de
58
58
  executables: []
@@ -68,22 +68,32 @@ files:
68
68
  - app/jobs/refresh_vault_token.rb
69
69
  - app/jobs/refresh_vault_tokens.rb
70
70
  - app/lib/foreman_vault/macros.rb
71
+ - app/models/concerns/foreman_vault/host_extensions.rb
72
+ - app/models/concerns/foreman_vault/orchestration/vault_policy.rb
73
+ - app/models/concerns/foreman_vault/provisioning_template_extensions.rb
74
+ - app/models/setting/vault.rb
71
75
  - app/models/vault_connection.rb
76
+ - app/services/foreman_vault/vault_auth_method.rb
72
77
  - app/services/foreman_vault/vault_client.rb
78
+ - app/services/foreman_vault/vault_policy.rb
73
79
  - app/views/api/v2/vault_connections/base.json.rabl
74
80
  - app/views/api/v2/vault_connections/create.json.rabl
75
81
  - app/views/api/v2/vault_connections/index.json.rabl
76
82
  - app/views/api/v2/vault_connections/main.json.rabl
77
83
  - app/views/api/v2/vault_connections/show.json.rabl
78
84
  - app/views/api/v2/vault_connections/update.json.rabl
85
+ - app/views/unattended/provisioning_templates/VaultPolicy/default.erb
79
86
  - app/views/vault_connections/_form.html.erb
80
87
  - app/views/vault_connections/edit.html.erb
81
88
  - app/views/vault_connections/index.html.erb
82
89
  - app/views/vault_connections/new.html.erb
90
+ - app/views/vault_connections/welcome.html.erb
83
91
  - config/foreman_vault.yaml.example
84
92
  - config/routes.rb
85
93
  - db/migrate/20180725072913_create_vault_connection.foreman_vault.rb
86
94
  - db/migrate/20180809172407_rename_vault_status_to_vault_error.foreman_vault.rb
95
+ - db/migrate/20201203220058_add_approle_to_vault_connection.rb
96
+ - db/seeds.d/103-provisioning_templates.rb
87
97
  - lib/foreman_vault.rb
88
98
  - lib/foreman_vault/engine.rb
89
99
  - lib/foreman_vault/version.rb
@@ -92,19 +102,26 @@ files:
92
102
  - locale/en/foreman_vault.po
93
103
  - locale/foreman_vault.pot
94
104
  - locale/gemspec.rb
95
- - test/factories/foreman_vault_factories.rb
105
+ - test/factories/vault_connection.rb
106
+ - test/factories/vault_policy_template.rb
107
+ - test/factories/vault_setting.rb
108
+ - test/fixtures/ca.crt
96
109
  - test/functional/api/v2/vault_connections_controller_test.rb
97
110
  - test/jobs/refresh_vault_token_test.rb
98
111
  - test/jobs/refresh_vault_tokens_test.rb
112
+ - test/models/foreman_vault/orchestration/vault_policy_test.rb
99
113
  - test/models/vault_connection_test.rb
114
+ - test/models/vault_policy_template_test.rb
100
115
  - test/test_plugin_helper.rb
101
116
  - test/unit/lib/foreman_vault/macros_test.rb
117
+ - test/unit/services/foreman_vault/vault_auth_method_test.rb
102
118
  - test/unit/services/foreman_vault/vault_client_test.rb
119
+ - test/unit/services/foreman_vault/vault_policy_test.rb
103
120
  homepage: https://github.com/dm-drogeriemarkt/foreman_vault
104
121
  licenses:
105
122
  - GPL-3.0
106
123
  metadata: {}
107
- post_install_message:
124
+ post_install_message:
108
125
  rdoc_options: []
109
126
  require_paths:
110
127
  - lib
@@ -119,16 +136,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
136
  - !ruby/object:Gem::Version
120
137
  version: '0'
121
138
  requirements: []
122
- rubyforge_project:
123
- rubygems_version: 2.7.3
124
- signing_key:
139
+ rubygems_version: 3.1.4
140
+ signing_key:
125
141
  specification_version: 4
126
142
  summary: Adds support for using credentials from Hashicorp Vault
127
143
  test_files:
128
144
  - test/unit/lib/foreman_vault/macros_test.rb
129
145
  - test/unit/services/foreman_vault/vault_client_test.rb
146
+ - test/unit/services/foreman_vault/vault_policy_test.rb
147
+ - test/unit/services/foreman_vault/vault_auth_method_test.rb
148
+ - test/models/vault_policy_template_test.rb
130
149
  - test/models/vault_connection_test.rb
131
- - test/factories/foreman_vault_factories.rb
150
+ - test/models/foreman_vault/orchestration/vault_policy_test.rb
151
+ - test/factories/vault_policy_template.rb
152
+ - test/factories/vault_connection.rb
153
+ - test/factories/vault_setting.rb
154
+ - test/fixtures/ca.crt
132
155
  - test/test_plugin_helper.rb
133
156
  - test/jobs/refresh_vault_tokens_test.rb
134
157
  - test/jobs/refresh_vault_token_test.rb