hybrid_platforms_conductor 33.3.0 → 33.7.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +31 -2
  4. data/docs/config_dsl.md +45 -0
  5. data/docs/plugins/cmdb/host_keys.md +3 -1
  6. data/docs/plugins/connector/ssh.md +1 -0
  7. data/lib/hybrid_platforms_conductor/actions_executor.rb +29 -1
  8. data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
  9. data/lib/hybrid_platforms_conductor/cmd_runner.rb +4 -4
  10. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
  11. data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
  12. data/lib/hybrid_platforms_conductor/config.rb +2 -0
  13. data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
  14. data/lib/hybrid_platforms_conductor/connector.rb +5 -2
  15. data/lib/hybrid_platforms_conductor/credentials.rb +122 -97
  16. data/lib/hybrid_platforms_conductor/deployer.rb +7 -9
  17. data/lib/hybrid_platforms_conductor/github.rb +39 -0
  18. data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +1 -1
  19. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +27 -17
  20. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +13 -12
  21. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +6 -4
  22. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +1 -1
  23. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +37 -25
  24. data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +5 -6
  25. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +1 -1
  26. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +1 -1
  27. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +7 -4
  28. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
  29. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +3 -2
  30. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
  31. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
  32. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +17 -3
  33. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +30 -10
  34. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
  35. data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
  36. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -2
  37. data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +1 -1
  38. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -2
  39. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +7 -3
  40. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +8 -4
  41. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -2
  42. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -2
  43. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -2
  44. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +1 -1
  45. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +1 -2
  46. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
  47. data/lib/hybrid_platforms_conductor/logger_helpers.rb +24 -1
  48. data/lib/hybrid_platforms_conductor/test.rb +21 -7
  49. data/lib/hybrid_platforms_conductor/tests_runner.rb +7 -6
  50. data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
  51. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  52. data/spec/hybrid_platforms_conductor_test.rb +6 -0
  53. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +15 -0
  54. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +32 -0
  55. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +87 -0
  56. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +30 -0
  57. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +10 -0
  58. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +38 -0
  59. data/spec/hybrid_platforms_conductor_test/api/actions_executor/helpers_spec.rb +195 -0
  60. data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +14 -0
  61. data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +11 -0
  62. data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +251 -0
  63. data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +215 -0
  64. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +280 -319
  65. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
  66. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +49 -10
  67. data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +38 -0
  68. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
  69. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
  70. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +5 -3
  71. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/chef_versions.yml +3 -0
  72. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/nodes/node.json +15 -0
  73. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/policyfiles/test_policy.rb +3 -0
  74. data/spec/hybrid_platforms_conductor_test/shared_examples/deployer.rb +134 -0
  75. data/spec/hybrid_platforms_conductor_test/test_connector.rb +2 -2
  76. metadata +36 -2
@@ -132,6 +132,36 @@ describe HybridPlatformsConductor::ActionsExecutor do
132
132
  end
133
133
  end
134
134
 
135
+ it 'creates SSH master to several nodes differing only by the SSH port' do
136
+ with_test_platform(
137
+ {
138
+ nodes: {
139
+ 'node1' => { meta: { host_ip: '192.168.42.1', ssh_port: 6661 } },
140
+ 'node2' => { meta: { host_ip: '192.168.42.1', ssh_port: 6662 } },
141
+ 'node3' => { meta: { host_ip: '192.168.42.1', ssh_port: 6663 } }
142
+ }
143
+ }
144
+ ) do
145
+ with_cmd_runner_mocked(
146
+ [
147
+ ['which env', proc { [0, "/usr/bin/env\n", ''] }],
148
+ ['ssh -V 2>&1', proc { [0, "OpenSSH_7.4p1 Debian-10+deb9u7, OpenSSL 1.0.2u 20 Dec 2019\n", ''] }]
149
+ ] + ssh_expected_commands_for(
150
+ {
151
+ 'node1' => { connection: '192.168.42.1', user: 'test_user', port: 6661 },
152
+ 'node2' => { connection: '192.168.42.1', user: 'test_user', port: 6662 },
153
+ 'node3' => { connection: '192.168.42.1', user: 'test_user', port: 6663 }
154
+ }
155
+ )
156
+ ) do
157
+ test_connector.ssh_user = 'test_user'
158
+ test_connector.with_connection_to(%w[node1 node2 node3]) do |connected_nodes|
159
+ expect(connected_nodes.sort).to eq %w[node1 node2 node3].sort
160
+ end
161
+ end
162
+ end
163
+ end
164
+
135
165
  it 'creates SSH master to several nodes with ssh connections transformed' do
136
166
  with_test_platform(
137
167
  { nodes: {
@@ -175,6 +175,16 @@ describe HybridPlatformsConductor::ActionsExecutor do
175
175
  end
176
176
  end
177
177
 
178
+ it 'generates a simple config for a node with host_ip and a given SSH port' do
179
+ with_test_platform({ nodes: { 'node' => { meta: { host_ip: '192.168.42.42', ssh_port: 666 } } } }) do
180
+ expect(ssh_config_for('node')).to eq <<~EO_SSH_CONFIG
181
+ Host hpc.node
182
+ Hostname 192.168.42.42
183
+ Port 666
184
+ EO_SSH_CONFIG
185
+ end
186
+ end
187
+
178
188
  it 'generates a simple config for several nodes' do
179
189
  with_test_platform(
180
190
  {
@@ -13,6 +13,15 @@ describe HybridPlatformsConductor::ActionsExecutor do
13
13
  end
14
14
  end
15
15
 
16
+ it 'executes bash commands remotely from a SecretString' do
17
+ with_test_platform_for_remote_testing(
18
+ expected_cmds: [[%r{.+/ssh hpc\.node /bin/bash <<'HPC_EOF'\nbash_cmd.bash\nHPC_EOF}, proc { [0, 'Bash commands executed on node', ''] }]],
19
+ expected_stdout: 'Bash commands executed on node'
20
+ ) do
21
+ test_connector.remote_bash(SecretString.new('bash_cmd.bash', silenced_str: '__INVALID_BASH__'))
22
+ end
23
+ end
24
+
16
25
  it 'executes bash commands remotely with timeout' do
17
26
  with_test_platform_for_remote_testing(
18
27
  expected_cmds: [
@@ -88,6 +97,25 @@ describe HybridPlatformsConductor::ActionsExecutor do
88
97
  end
89
98
  end
90
99
 
100
+ it 'executes really big bash commands remotely using a SecretString' do
101
+ cmd = "echo #{'1' * 131_060}"
102
+ with_test_platform_for_remote_testing(
103
+ expected_cmds: [
104
+ [
105
+ %r{.+/hpc_temp_cmds_.+\.sh$},
106
+ proc do |received_cmd|
107
+ expect(File.read(received_cmd)).to match(%r{.+/ssh hpc\.node /bin/bash <<'HPC_EOF'\n#{Regexp.escape(cmd)}\nHPC_EOF})
108
+ [0, 'Bash commands executed on node', '']
109
+ end
110
+ ]
111
+ ],
112
+ expected_stdout: 'Bash commands executed on node'
113
+ ) do
114
+ # Use an argument that exceeds the max arg length limit
115
+ test_connector.remote_bash(SecretString.new(cmd, silenced_str: '__INVALID_BASH__'))
116
+ end
117
+ end
118
+
91
119
  it 'copies files remotely with sudo' do
92
120
  with_test_platform_for_remote_testing(
93
121
  expected_cmds: [
@@ -153,6 +181,16 @@ describe HybridPlatformsConductor::ActionsExecutor do
153
181
  end
154
182
  end
155
183
 
184
+ it 'executes bash commands remotely without Session Exec capabilities using a SecretString' do
185
+ with_test_platform_for_remote_testing(
186
+ expected_cmds: [[%r{^\{ cat \| .+/ssh hpc\.node -T; \} <<'HPC_EOF'\nbash_cmd.bash\nHPC_EOF$}, proc { [0, 'Bash commands executed on node', ''] }]],
187
+ expected_stdout: 'Bash commands executed on node',
188
+ session_exec: false
189
+ ) do
190
+ test_connector.remote_bash(SecretString.new('bash_cmd.bash', silenced_str: '__INVALID_BASH__'))
191
+ end
192
+ end
193
+
156
194
  it 'copies files remotely without Session Exec capabilities' do
157
195
  with_test_platform_for_remote_testing(
158
196
  expected_cmds: [
@@ -0,0 +1,195 @@
1
+ describe HybridPlatformsConductor::ActionsExecutor do
2
+
3
+ context 'when checking helpers' do
4
+
5
+ it 'gives access to connectors' do
6
+ with_test_platform({}) do
7
+ expect(test_actions_executor.connector(:ssh)).not_to be_nil
8
+ end
9
+ end
10
+
11
+ it 'returns if a user has privileged access on a node' do
12
+ with_test_platform({ nodes: { 'node' => {} } }) do
13
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
14
+ expect(test_actions_executor.privileged_access?('node')).to eq false
15
+ end
16
+ end
17
+
18
+ it 'returns if a user has privileged access on a node when connecting with root' do
19
+ with_test_platform({ nodes: { 'node' => {} } }) do
20
+ test_actions_executor.connector(:ssh).ssh_user = 'root'
21
+ expect(test_actions_executor.privileged_access?('node')).to eq true
22
+ end
23
+ end
24
+
25
+ it 'returns if a user has privileged access on a local node' do
26
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
27
+ with_cmd_runner_mocked [
28
+ ['whoami', proc { [0, 'test_user', ''] }]
29
+ ] do
30
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
31
+ expect(test_actions_executor.privileged_access?('node')).to eq false
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'returns if a user has privileged access on a local node when local user is root' do
37
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
38
+ with_cmd_runner_mocked [
39
+ ['whoami', proc { [0, 'root', ''] }]
40
+ ] do
41
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
42
+ expect(test_actions_executor.privileged_access?('node')).to eq true
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'with connection on a remote node' do
48
+
49
+ it 'returns the correct sudo prefix' do
50
+ with_test_platform({ nodes: { 'node' => {} } }) do
51
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
52
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'sudo -u root '
53
+ end
54
+ end
55
+
56
+ it 'returns the correct sudo prefix with env forwarding' do
57
+ with_test_platform({ nodes: { 'node' => {} } }) do
58
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
59
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'sudo -u root -E '
60
+ end
61
+ end
62
+
63
+ it 'returns the correct sudo prefix when connecting as root' do
64
+ with_test_platform({ nodes: { 'node' => {} } }) do
65
+ test_actions_executor.connector(:ssh).ssh_user = 'root'
66
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
67
+ end
68
+ end
69
+
70
+ it 'returns the correct sudo prefix with a different sudo' do
71
+ with_test_platform(
72
+ { nodes: { 'node' => {} } },
73
+ additional_config: <<~'EO_CONFIG'
74
+ sudo_for { |user| "other_sudo --user #{user}" }
75
+ EO_CONFIG
76
+ ) do
77
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
78
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'other_sudo --user root '
79
+ end
80
+ end
81
+
82
+ it 'returns the correct sudo prefix with a different sudo and env forwarding' do
83
+ with_test_platform(
84
+ { nodes: { 'node' => {} } },
85
+ additional_config: <<~'EO_CONFIG'
86
+ sudo_for { |user| "other_sudo --user #{user}" }
87
+ EO_CONFIG
88
+ ) do
89
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
90
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'other_sudo --user root -E '
91
+ end
92
+ end
93
+
94
+ it 'returns the correct sudo prefix with a different sudo when connecting as root' do
95
+ with_test_platform(
96
+ { nodes: { 'node' => {} } },
97
+ additional_config: <<~'EO_CONFIG'
98
+ sudo_for { |user| "other_sudo --user #{user}" }
99
+ EO_CONFIG
100
+ ) do
101
+ test_actions_executor.connector(:ssh).ssh_user = 'root'
102
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ context 'with connection on a local node' do
109
+
110
+ it 'returns the correct sudo prefix' do
111
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
112
+ with_cmd_runner_mocked [
113
+ ['whoami', proc { [0, 'test_user', ''] }]
114
+ ] do
115
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
116
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'sudo -u root '
117
+ end
118
+ end
119
+ end
120
+
121
+ it 'returns the correct sudo prefix with env forwarding' do
122
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
123
+ with_cmd_runner_mocked [
124
+ ['whoami', proc { [0, 'test_user', ''] }]
125
+ ] do
126
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
127
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'sudo -u root -E '
128
+ end
129
+ end
130
+ end
131
+
132
+ it 'returns the correct sudo prefix when connecting as root' do
133
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
134
+ with_cmd_runner_mocked [
135
+ ['whoami', proc { [0, 'root', ''] }]
136
+ ] do
137
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
138
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
139
+ end
140
+ end
141
+ end
142
+
143
+ it 'returns the correct sudo prefix with a different sudo' do
144
+ with_test_platform(
145
+ { nodes: { 'node' => { meta: { local_node: true } } } },
146
+ additional_config: <<~'EO_CONFIG'
147
+ sudo_for { |user| "other_sudo --user #{user}" }
148
+ EO_CONFIG
149
+ ) do
150
+ with_cmd_runner_mocked [
151
+ ['whoami', proc { [0, 'test_user', ''] }]
152
+ ] do
153
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
154
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'other_sudo --user root '
155
+ end
156
+ end
157
+ end
158
+
159
+ it 'returns the correct sudo prefix with a different sudo and env forwarding' do
160
+ with_test_platform(
161
+ { nodes: { 'node' => { meta: { local_node: true } } } },
162
+ additional_config: <<~'EO_CONFIG'
163
+ sudo_for { |user| "other_sudo --user #{user}" }
164
+ EO_CONFIG
165
+ ) do
166
+ with_cmd_runner_mocked [
167
+ ['whoami', proc { [0, 'test_user', ''] }]
168
+ ] do
169
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
170
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'other_sudo --user root -E '
171
+ end
172
+ end
173
+ end
174
+
175
+ it 'returns the correct sudo prefix with a different sudo when connecting as root' do
176
+ with_test_platform(
177
+ { nodes: { 'node' => { meta: { local_node: true } } } },
178
+ additional_config: <<~'EO_CONFIG'
179
+ sudo_for { |user| "other_sudo --user #{user}" }
180
+ EO_CONFIG
181
+ ) do
182
+ with_cmd_runner_mocked [
183
+ ['whoami', proc { [0, 'root', ''] }]
184
+ ] do
185
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
186
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
187
+ end
188
+ end
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+
195
+ end
@@ -7,6 +7,13 @@ describe HybridPlatformsConductor::CmdRunner do
7
7
  end
8
8
  end
9
9
 
10
+ it 'runs a simple bash command in a SecretString' do
11
+ with_repository do |repository|
12
+ test_cmd_runner.run_cmd SecretString.new("echo TestContent >#{repository}/test_file", silenced_str: '__INVALID_BASH__')
13
+ expect(File.read("#{repository}/test_file")).to eq "TestContent\n"
14
+ end
15
+ end
16
+
10
17
  it 'runs a simple bash command and returns exit code, stdout and stderr correctly' do
11
18
  with_repository do
12
19
  expect(test_cmd_runner.run_cmd('echo TestStderr 1>&2 ; echo TestStdout')).to eq [0, "TestStdout\n", "TestStderr\n"]
@@ -20,6 +27,13 @@ describe HybridPlatformsConductor::CmdRunner do
20
27
  end
21
28
  end
22
29
 
30
+ it 'runs a simple bash command and forces usage of bash in a SecretString' do
31
+ with_repository do
32
+ # Use set -o pipefail that does not work in /bin/sh
33
+ expect(test_cmd_runner.run_cmd(SecretString.new('set -o pipefail ; echo TestStderr 1>&2 ; echo TestStdout', silenced_str: '__INVALID_BASH__'), force_bash: true)).to eq [0, "TestStdout\n", "TestStderr\n"]
34
+ end
35
+ end
36
+
23
37
  it 'runs a simple bash command and logs stdout and stderr to a file' do
24
38
  with_repository do |repository|
25
39
  test_cmd_runner.run_cmd 'echo TestStderr 1>&2 ; sleep 1 ; echo TestStdout', log_to_file: "#{repository}/test_file"
@@ -29,6 +29,17 @@ describe HybridPlatformsConductor::Config do
29
29
  end
30
30
  end
31
31
 
32
+ it 'can check if we are in debug mode' do
33
+ with_platforms(
34
+ <<~EO_CONFIG
35
+ os_image :image_1, '/path/to/image_1' if log_debug?
36
+ os_image :image_2, '/path/to/image_2'
37
+ EO_CONFIG
38
+ ) do
39
+ expect(test_config.known_os_images.sort).to eq %i[image_2].sort
40
+ end
41
+ end
42
+
32
43
  it 'returns the tests provisioner correctly' do
33
44
  with_platforms 'tests_provisioner :test_provisioner' do
34
45
  expect(test_config.tests_provisioner_id).to eq :test_provisioner
@@ -0,0 +1,251 @@
1
+ describe HybridPlatformsConductor::Credentials do
2
+
3
+ # Create a container class for the credential Mixin to be tested, as a plugin as credentials can be used in any plugin.
4
+ let(:credential_tester_class) do
5
+ Class.new(HybridPlatformsConductor::Plugin) do
6
+ include HybridPlatformsConductor::Credentials
7
+ end
8
+ end
9
+
10
+ # Expect credentials to be as a given user and password
11
+ #
12
+ # Parameters::
13
+ # * *expected_user* (String or nil): The expected user
14
+ # * *expected_password* (String or nil): The expected password
15
+ # * *resource* (String or nil): The resource for which we query the credentials, or nil if none [default: nil]
16
+ def expect_credentials_to_be(expected_user, expected_password, resource: nil)
17
+ creds = {}
18
+ password_class = nil
19
+ credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
20
+ with_credentials_for(:test_credential, resource: resource) do |user, password|
21
+ password_class = password.class
22
+ creds = {
23
+ user: user,
24
+ # We clone the value as for security reasons it is removed when exiting the block
25
+ password: password&.to_unprotected.clone
26
+ }
27
+ end
28
+ end
29
+ # Make sure we always return a SecretString for the password
30
+ expect(password_class).to be SecretString unless password_class == NilClass
31
+ expect(creds).to eq(
32
+ user: expected_user,
33
+ password: expected_password
34
+ )
35
+ end
36
+
37
+ it 'returns no credentials when they are not set' do
38
+ with_platforms '' do
39
+ # Check that .netrc won't be read
40
+ expect(::Netrc).not_to receive(:read)
41
+ expect_credentials_to_be nil, nil
42
+ end
43
+ end
44
+
45
+ it 'returns credentials taken from environment variables' do
46
+ with_platforms '' do
47
+ ENV['hpc_user_for_test_credential'] = 'env_test_user'
48
+ ENV['hpc_password_for_test_credential'] = 'env_test_password'
49
+ begin
50
+ # Check that .netrc won't be read
51
+ expect(::Netrc).not_to receive(:read)
52
+ expect_credentials_to_be 'env_test_user', 'env_test_password'
53
+ ensure
54
+ ENV.delete('hpc_user_for_test_credential')
55
+ ENV.delete('hpc_password_for_test_credential')
56
+ end
57
+ end
58
+ end
59
+
60
+ it 'erases the value of the password taken from environment variable after usage' do
61
+ with_platforms '' do
62
+ ENV['hpc_user_for_test_credential'] = 'env_test_user'
63
+ ENV['hpc_password_for_test_credential'] = 'env_test_password'
64
+ begin
65
+ leaked_password = nil
66
+ credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
67
+ with_credentials_for(:test_credential) do |_user, password|
68
+ leaked_password = password
69
+ end
70
+ end
71
+ expect(leaked_password.to_unprotected).to eq "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
72
+ ensure
73
+ ENV.delete('hpc_user_for_test_credential')
74
+ ENV.delete('hpc_password_for_test_credential')
75
+ end
76
+ end
77
+ end
78
+
79
+ it 'returns credentials taken from .netrc when a resource is specified' do
80
+ with_platforms '' do
81
+ expect(::Netrc).to receive(:read) do
82
+ mocked_netrc = instance_double(::Netrc)
83
+ expect(mocked_netrc).to receive(:[]).with('my_domain.com').and_return %w[test_user test_password]
84
+ expect(mocked_netrc).to receive(:instance_variable_get).with(:@data).and_return []
85
+ mocked_netrc
86
+ end
87
+ expect_credentials_to_be 'test_user', 'test_password', resource: 'http://My_Domain.com/path/to/resource'
88
+ end
89
+ end
90
+
91
+ it 'returns credentials taken from .netrc when a non-URL resource is specified' do
92
+ with_platforms '' do
93
+ expect(::Netrc).to receive(:read) do
94
+ mocked_netrc = instance_double(::Netrc)
95
+ expect(mocked_netrc).to receive(:[]).with('This is:not/ a URL!').and_return %w[test_user test_password]
96
+ expect(mocked_netrc).to receive(:instance_variable_get).with(:@data).and_return []
97
+ mocked_netrc
98
+ end
99
+ expect_credentials_to_be 'test_user', 'test_password', resource: 'This is:not/ a URL!'
100
+ end
101
+ end
102
+
103
+ it 'erases the value of the password taken from netrc after usage' do
104
+ with_platforms '' do
105
+ netrc_data = [['mocked_data']]
106
+ expect(::Netrc).to receive(:read) do
107
+ mocked_netrc = instance_double(::Netrc)
108
+ expect(mocked_netrc).to receive(:[]).with('my_domain.com').and_return %w[test_user test_password]
109
+ expect(mocked_netrc).to receive(:instance_variable_get).with(:@data).and_return netrc_data
110
+ mocked_netrc
111
+ end
112
+ leaked_password = nil
113
+ credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
114
+ with_credentials_for(:test_credential, resource: 'http://My_Domain.com/path/to/resource') do |_user, password|
115
+ leaked_password = password
116
+ end
117
+ end
118
+ expect(leaked_password).to eq "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
119
+ expect(netrc_data).to eq [['GotYou!!!' * 100]]
120
+ end
121
+ end
122
+
123
+ it 'returns credentials taken from config' do
124
+ with_platforms(
125
+ <<~'EO_CONFIG'
126
+ credentials_for(:test_credential) do |resource, requester|
127
+ requester.call "user_for_#{resource}", "password_for_#{resource}"
128
+ end
129
+ EO_CONFIG
130
+ ) do
131
+ # Check that netrc is not called when config is used, and that env vars are ignored
132
+ ENV['hpc_user_for_test_credential'] = 'env_test_user'
133
+ ENV['hpc_password_for_test_credential'] = 'env_test_password'
134
+ begin
135
+ # Check that .netrc won't be read
136
+ expect(::Netrc).not_to receive(:read)
137
+ expect_credentials_to_be 'user_for_', 'password_for_'
138
+ ensure
139
+ ENV.delete('hpc_user_for_test_credential')
140
+ ENV.delete('hpc_password_for_test_credential')
141
+ end
142
+ end
143
+ end
144
+
145
+ it 'returns credentials taken from config for a given resource' do
146
+ with_platforms(
147
+ <<~'EO_CONFIG'
148
+ credentials_for(:test_credential) do |resource, requester|
149
+ requester.call "user_for_#{resource}", "password_for_#{resource}"
150
+ end
151
+ EO_CONFIG
152
+ ) do
153
+ # Check that netrc is not called when config is used, and that env vars are ignored
154
+ ENV['hpc_user_for_test_credential'] = 'env_test_user'
155
+ ENV['hpc_password_for_test_credential'] = 'env_test_password'
156
+ begin
157
+ # Check that .netrc won't be read
158
+ expect(::Netrc).not_to receive(:read)
159
+ expect_credentials_to_be 'user_for_test_resource', 'password_for_test_resource', resource: 'test_resource'
160
+ ensure
161
+ ENV.delete('hpc_user_for_test_credential')
162
+ ENV.delete('hpc_password_for_test_credential')
163
+ end
164
+ end
165
+ end
166
+
167
+ it 'returns credentials taken from config for a given resource even when they are nil' do
168
+ with_platforms(
169
+ <<~'EO_CONFIG'
170
+ credentials_for(:test_credential) do |resource, requester|
171
+ requester.call nil, nil
172
+ end
173
+ EO_CONFIG
174
+ ) do
175
+ # Check that netrc is not called when config is used, and that env vars are ignored
176
+ ENV['hpc_user_for_test_credential'] = 'env_test_user'
177
+ ENV['hpc_password_for_test_credential'] = 'env_test_password'
178
+ begin
179
+ # Check that .netrc won't be read
180
+ expect(::Netrc).not_to receive(:read)
181
+ expect_credentials_to_be nil, nil, resource: 'test_resource'
182
+ ensure
183
+ ENV.delete('hpc_user_for_test_credential')
184
+ ENV.delete('hpc_password_for_test_credential')
185
+ end
186
+ end
187
+ end
188
+
189
+ it 'returns credentials taken from config after filtering the resource name' do
190
+ with_platforms(
191
+ <<~'EO_CONFIG'
192
+ credentials_for(:test_credential, resource: 'another_resource') do |resource, requester|
193
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
194
+ end
195
+ credentials_for(:test_credential, resource: /test_.*/) do |resource, requester|
196
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
197
+ end
198
+ credentials_for(:test_credential, resource: /_resource/) do |resource, requester|
199
+ requester.call "correct_user_for_#{resource}", "correct_password_for_#{resource}"
200
+ end
201
+ credentials_for(:test_credential, resource: 'test_resource2') do |resource, requester|
202
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
203
+ end
204
+ EO_CONFIG
205
+ ) do
206
+ expect_credentials_to_be 'correct_user_for_test_resource', 'correct_password_for_test_resource', resource: 'test_resource'
207
+ end
208
+ end
209
+
210
+ it 'returns credentials taken from config after filtering the resource name when no resource is given' do
211
+ with_platforms(
212
+ <<~'EO_CONFIG'
213
+ credentials_for(:test_credential, resource: 'another_resource') do |resource, requester|
214
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
215
+ end
216
+ credentials_for(:test_credential, resource: /test_.*/) do |resource, requester|
217
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
218
+ end
219
+ credentials_for(:test_credential) do |resource, requester|
220
+ requester.call "correct_user_for_#{resource}", "correct_password_for_#{resource}"
221
+ end
222
+ credentials_for(:test_credential, resource: /_resource/) do |resource, requester|
223
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
224
+ end
225
+ credentials_for(:test_credential, resource: 'test_resource2') do |resource, requester|
226
+ requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
227
+ end
228
+ EO_CONFIG
229
+ ) do
230
+ expect_credentials_to_be 'correct_user_for_', 'correct_password_for_'
231
+ end
232
+ end
233
+
234
+ it 'fails if the requester is not called from config' do
235
+ with_platforms(
236
+ <<~'EO_CONFIG'
237
+ credentials_for(:test_credential) do |resource, requester|
238
+ end
239
+ EO_CONFIG
240
+ ) do
241
+ expect do
242
+ credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
243
+ with_credentials_for(:test_credential) do |_user, _password|
244
+ nil
245
+ end
246
+ end
247
+ end.to raise_error 'Requester not called by the credentials provider for test_credential (resource: ) - Please check the credentials_for code in your configuration.'
248
+ end
249
+ end
250
+
251
+ end