foreman_vault 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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