|
17 | 17 | from nailgun.entity_mixins import TaskFailedError |
18 | 18 | import pytest |
19 | 19 |
|
20 | | -from robottelo.config import get_credentials |
| 20 | +from robottelo.config import get_credentials, settings |
| 21 | +from robottelo.exceptions import CLIFactoryError |
21 | 22 | from robottelo.hosts import get_sat_version |
| 23 | +from robottelo.logging import logger |
| 24 | +from robottelo.utils.installer import InstallerCommand |
22 | 25 |
|
23 | 26 | CAPSULE_TARGET_VERSION = f'6.{get_sat_version().minor}.z' |
24 | 27 |
|
25 | 28 | pytestmark = pytest.mark.destructive |
26 | 29 |
|
27 | 30 |
|
| 31 | +def create_CA(host, path='/root/CA', name=None): |
| 32 | + """Create a new CA keypair""" |
| 33 | + assert host.execute(f'mkdir -p {path}').status == 0 |
| 34 | + filename = 'id_ca' if name is None else f'id_{name}_ca' |
| 35 | + assert ( |
| 36 | + host.execute( |
| 37 | + f'cd {path} && if ! [ -f {filename} ]; then ssh-keygen -t ed25519 -f {filename} -N ""; fi' |
| 38 | + ).status |
| 39 | + == 0 |
| 40 | + ) |
| 41 | + return filename |
| 42 | + |
| 43 | + |
| 44 | +@pytest.fixture |
| 45 | +def ca_sat(target_sat): |
| 46 | + """Setup SSH key certs and CA public key on Satellite""" |
| 47 | + path = "/root/CA" |
| 48 | + sat_ssh_path = '/var/lib/foreman-proxy/ssh/' |
| 49 | + filename = create_CA(target_sat, path) |
| 50 | + ca_path = f'{path}/{filename}' |
| 51 | + key_name = 'id_rsa_foreman_proxy' |
| 52 | + cert_name = f'{key_name}-cert.pub' |
| 53 | + assert ( |
| 54 | + target_sat.execute( |
| 55 | + f'cd {sat_ssh_path} && cp {ca_path}.pub . && restorecon {filename}.pub && chown foreman-proxy {filename}.pub && chgrp foreman-proxy {filename}.pub' |
| 56 | + ).status |
| 57 | + == 0 |
| 58 | + ) |
| 59 | + assert ( |
| 60 | + target_sat.execute( |
| 61 | + f'cd {sat_ssh_path} && ssh-keygen -s {ca_path} -I satellite -n root {key_name}.pub && restorecon {cert_name} && chown foreman-proxy {cert_name} && chgrp foreman-proxy {cert_name}' |
| 62 | + ).status |
| 63 | + == 0 |
| 64 | + ) |
| 65 | + return (target_sat, f'{sat_ssh_path}/{filename}.pub') |
| 66 | + |
| 67 | + |
| 68 | +@pytest.fixture |
| 69 | +def ca_contenthost(rhel_contenthost): |
| 70 | + """Setup SSH key certs and CA public key on content host""" |
| 71 | + path = '/root/CA' |
| 72 | + host_ssh_path = '/etc/ssh' |
| 73 | + filename = create_CA(rhel_contenthost, path, 'host') |
| 74 | + ca_path = f'{path}/{filename}' |
| 75 | + # create a host key and sign it |
| 76 | + key_name = 'ssh_host_ed25519_key' |
| 77 | + cert_name = f'{key_name}-cert.pub' |
| 78 | + assert ( |
| 79 | + rhel_contenthost.execute( |
| 80 | + f'cd {host_ssh_path} && if ! [ -f {key_name} ]; then ssh-keygen -t ed25519 -f {key_name} -N ""; fi' |
| 81 | + ).status |
| 82 | + == 0 |
| 83 | + ) |
| 84 | + assert ( |
| 85 | + rhel_contenthost.execute( |
| 86 | + f'cd {host_ssh_path} && ssh-keygen -s {ca_path} -I host -n {rhel_contenthost.hostname} -h {key_name}.pub' |
| 87 | + ).status |
| 88 | + == 0 |
| 89 | + ) |
| 90 | + # setup cert usage |
| 91 | + assert ( |
| 92 | + rhel_contenthost.execute( |
| 93 | + f'mkdir -p {host_ssh_path}/sshd_config.d && cd {host_ssh_path}/sshd_config.d && echo "HostCertificate {host_ssh_path}/{cert_name}" > 60-host-cert.conf' |
| 94 | + ).status |
| 95 | + == 0 |
| 96 | + ) |
| 97 | + assert rhel_contenthost.execute('systemctl restart sshd').status == 0 |
| 98 | + return (rhel_contenthost, f'{ca_path}.pub') |
| 99 | + |
| 100 | + |
| 101 | +@pytest.fixture |
| 102 | +def host_ca_file_on_satellite(ca_contenthost): |
| 103 | + """Return path of CA public key on Satellite""" |
| 104 | + return f'/var/lib/foreman-proxy/ssh/{ca_contenthost[1].split("/")[-1]}' |
| 105 | + |
| 106 | + |
28 | 107 | def test_negative_run_capsule_upgrade_playbook_on_satellite(target_sat): |
29 | 108 | """Run Capsule Upgrade playbook against the Satellite itself |
30 | 109 |
|
@@ -135,3 +214,262 @@ def test_positive_use_alternate_directory( |
135 | 214 | task = target_sat.cli.Task.list_tasks({'search': command})[0] |
136 | 215 | search = target_sat.cli.Task.list_tasks({'search': f'id={task["id"]}'}) |
137 | 216 | assert search[0]['action'] == task['action'] |
| 217 | + |
| 218 | + |
| 219 | +def register_host(satellite, host, org, cockpit=False): |
| 220 | + """Register a content host to Satellite""" |
| 221 | + if cockpit: |
| 222 | + rhelver = host.os_version.major |
| 223 | + if rhelver > 7: |
| 224 | + repo = [settings.repos[f'rhel{rhelver}_os']['baseos']] |
| 225 | + else: |
| 226 | + repo = [settings.repos['rhel7_os'], settings.repos['rhel7_extras']] |
| 227 | + else: |
| 228 | + repo = [] |
| 229 | + satellite.register_host_custom_repo(org, host, repo) |
| 230 | + return org |
| 231 | + |
| 232 | + |
| 233 | +def test_execution(satellite, host): |
| 234 | + """Run a job invocation and return its results""" |
| 235 | + command = "echo rex_passed $(date) > /root/test" |
| 236 | + invocation_command = satellite.cli_factory.job_invocation( |
| 237 | + { |
| 238 | + 'job-template': 'Run Command - Script Default', |
| 239 | + 'inputs': f'command={command}', |
| 240 | + 'search-query': f"name ~ {host.hostname}", |
| 241 | + } |
| 242 | + ) |
| 243 | + return satellite.cli.JobInvocation.info({'id': invocation_command['id']}) |
| 244 | + |
| 245 | + |
| 246 | +def log_count(satellite, host): |
| 247 | + """Return number of lines mentioning CA was used in sshd log, |
| 248 | + for later use |
| 249 | + """ |
| 250 | + return int( |
| 251 | + host.execute( |
| 252 | + f'journalctl -u sshd | grep {satellite.ip_addr} | grep CA | wc -l' |
| 253 | + ).stdout.strip() |
| 254 | + ) |
| 255 | + |
| 256 | + |
| 257 | +def copy_host_CA(host, satellite, host_path, satellite_path): |
| 258 | + """Copy CA public key from host to Satellite (for use in installer)""" |
| 259 | + host_ca_file_local = f'/tmp/{gen_string("alpha")}' |
| 260 | + host.get(host_path, host_ca_file_local) |
| 261 | + satellite.put(host_ca_file_local, satellite_path) |
| 262 | + |
| 263 | + |
| 264 | +@pytest.mark.no_containers |
| 265 | +@pytest.mark.rhel_ver_match([settings.content_host.default_rhel_version]) |
| 266 | +def test_positive_ssh_ca_sat_only(ca_sat, rhel_contenthost, function_org): |
| 267 | + """Setup Satellite's SSH cert, register host and run REX on that host |
| 268 | +
|
| 269 | + :id: 353a21bf-f379-440a-9dc6-e17bf6414713 |
| 270 | +
|
| 271 | + :expectedresults: Verify the job has been run successfully against the host, Sat's public key hasn't been added to host's authorized_keys and CA verification has been used instead |
| 272 | +
|
| 273 | + :parametrized: yes |
| 274 | + """ |
| 275 | + sat = ca_sat[0] |
| 276 | + host = rhel_contenthost |
| 277 | + sat_ca_file = ca_sat[1] |
| 278 | + saved_count = log_count(sat, host) |
| 279 | + command = InstallerCommand( |
| 280 | + foreman_proxy_plugin_remote_execution_script_ssh_user_ca_public_key_file=sat_ca_file, |
| 281 | + ) |
| 282 | + assert sat.install(command).status == 0 |
| 283 | + register_host(sat, host, function_org, cockpit=True) |
| 284 | + result = test_execution(sat, host) |
| 285 | + # assert the run actually happened and it was authenticated using cert |
| 286 | + assert result['success'] == '1' |
| 287 | + logger.debug(result) |
| 288 | + assert log_count(sat, host) == saved_count + 1 |
| 289 | + check = host.execute('grep rex_passed /root/test') |
| 290 | + assert check.status == 0 |
| 291 | + check = host.execute(f'grep {sat.hostname} /root/.ssh/authorized_keys') |
| 292 | + assert check.status != 0 |
| 293 | + |
| 294 | + |
| 295 | +@pytest.mark.no_containers |
| 296 | +@pytest.mark.rhel_ver_match([settings.content_host.default_rhel_version]) |
| 297 | +def test_negative_ssh_ca_sat_wrong_cert(ca_sat, rhel_contenthost, function_org): |
| 298 | + """Setup Satellite's SSH cert, register host, setup incorrect cert on Satellite and run REX on the host |
| 299 | +
|
| 300 | + :id: 7ddd170b-d489-4e2a-93af-ccf0c1a9d4ca |
| 301 | +
|
| 302 | + :expectedresults: Verify the job has failed against the host |
| 303 | +
|
| 304 | + :parametrized: yes |
| 305 | + """ |
| 306 | + sat = ca_sat[0] |
| 307 | + host = rhel_contenthost |
| 308 | + sat_ca_file = ca_sat[1] |
| 309 | + command = InstallerCommand( |
| 310 | + foreman_proxy_plugin_remote_execution_script_ssh_user_ca_public_key_file=sat_ca_file, |
| 311 | + ) |
| 312 | + assert sat.install(command).status == 0 |
| 313 | + register_host(sat, host, function_org, cockpit=True) |
| 314 | + |
| 315 | + # create a different cert for the Satellite, with a wrong principal |
| 316 | + sat_ssh_path = '/var/lib/foreman-proxy/ssh/' |
| 317 | + key_name = 'id_rsa_foreman_proxy' |
| 318 | + cert_name = f'{key_name}-cert.pub' |
| 319 | + assert ( |
| 320 | + sat.execute( |
| 321 | + f'cd {sat_ssh_path} && ssh-keygen -s /root/CA/id_ca -I satellite -n wronguser {key_name}.pub && restorecon {cert_name} && chown foreman-proxy {cert_name} && chgrp foreman-proxy {cert_name}' |
| 322 | + ).status |
| 323 | + == 0 |
| 324 | + ) |
| 325 | + |
| 326 | + # assert the run fails |
| 327 | + with pytest.raises(CLIFactoryError) as err: |
| 328 | + test_execution(sat, host) |
| 329 | + assert 'A sub task failed' in err.value.args[0] |
| 330 | + check = host.execute('grep rex_passed /root/test') |
| 331 | + assert check.status != 0 |
| 332 | + |
| 333 | + |
| 334 | +@pytest.mark.no_containers |
| 335 | +@pytest.mark.rhel_ver_match([settings.content_host.default_rhel_version]) |
| 336 | +def test_positive_ssh_ca_host_only( |
| 337 | + target_sat, ca_contenthost, host_ca_file_on_satellite, function_org |
| 338 | +): |
| 339 | + """Setup host's SSH cert, add CA to Sat, register host and run REX on that host |
| 340 | +
|
| 341 | + :id: 0ad9bbf7-0be5-49ca-8d79-969242b6b9bc |
| 342 | +
|
| 343 | + :expectedresults: Verify the job has been run successfully against the host |
| 344 | +
|
| 345 | + :parametrized: yes |
| 346 | + """ |
| 347 | + sat = target_sat |
| 348 | + host = ca_contenthost[0] |
| 349 | + host_ca_file = ca_contenthost[1] |
| 350 | + copy_host_CA(host, sat, host_ca_file, host_ca_file_on_satellite) |
| 351 | + saved_count = log_count(sat, host) |
| 352 | + command = InstallerCommand( |
| 353 | + foreman_proxy_plugin_remote_execution_script_ssh_host_ca_public_keys_file=host_ca_file_on_satellite, |
| 354 | + ) |
| 355 | + assert sat.install(command).status == 0 |
| 356 | + register_host(sat, host, function_org, cockpit=True) |
| 357 | + result = test_execution(sat, host) |
| 358 | + # assert the run actually happened and it was NOT authenticated using cert |
| 359 | + assert result['success'] == '1' |
| 360 | + logger.debug(result) |
| 361 | + assert log_count(sat, host) == saved_count |
| 362 | + check = host.execute('grep rex_passed /root/test') |
| 363 | + assert check.status == 0 |
| 364 | + |
| 365 | + |
| 366 | +@pytest.mark.no_containers |
| 367 | +@pytest.mark.rhel_ver_match([settings.content_host.default_rhel_version]) |
| 368 | +def test_negative_ssh_ca_host_wrong_cert( |
| 369 | + target_sat, ca_contenthost, host_ca_file_on_satellite, function_org |
| 370 | +): |
| 371 | + """Setup host's SSH cert, add a different CA to Sat, register host and run REX on that host |
| 372 | +
|
| 373 | + :id: 9e23d27d-a3a8-4c0d-9a0f-892d392aa660 |
| 374 | +
|
| 375 | + :expectedresults: Verify the job has failed against the host |
| 376 | +
|
| 377 | + :parametrized: yes |
| 378 | + """ |
| 379 | + sat = target_sat |
| 380 | + host = ca_contenthost[0] |
| 381 | + fake_ca_dir = '/'.join(host_ca_file_on_satellite.split('/')[:-1]) |
| 382 | + fake_ca_filename = host_ca_file_on_satellite.split('/')[-1] |
| 383 | + sat.execute( |
| 384 | + f'cd {fake_ca_dir} && {{ rm -f {fake_ca_filename}; ssh-keygen -t ed25519 -f {fake_ca_filename} -N ""; }}' |
| 385 | + ) |
| 386 | + command = InstallerCommand( |
| 387 | + foreman_proxy_plugin_remote_execution_script_ssh_host_ca_public_keys_file=host_ca_file_on_satellite, |
| 388 | + ) |
| 389 | + assert sat.install(command).status == 0 |
| 390 | + register_host(sat, host, function_org, cockpit=True) |
| 391 | + # assert the run failed |
| 392 | + with pytest.raises(CLIFactoryError) as err: |
| 393 | + test_execution(sat, host) |
| 394 | + assert 'A sub task failed' in err.value.args[0] |
| 395 | + check = host.execute('grep rex_passed /root/test') |
| 396 | + assert check.status != 0 |
| 397 | + |
| 398 | + |
| 399 | +@pytest.mark.e2e |
| 400 | +@pytest.mark.no_containers |
| 401 | +@pytest.mark.rhel_ver_match([settings.content_host.default_rhel_version]) |
| 402 | +def test_positive_ssh_ca_sat_and_host_ssh_ansible_cockpit( |
| 403 | + ca_sat, ca_contenthost, host_ca_file_on_satellite, function_org |
| 404 | +): |
| 405 | + """Setup Satellite's SSH cert, setup host's SSH cert, add CA to Sat, register host and run REX on that host |
| 406 | +
|
| 407 | + :id: 97c17417-3b20-4876-bf9c-7219a91acee2 |
| 408 | +
|
| 409 | + :expectedresults: Verify the job has been run successfully against the host, Sat's public key hasn't been added to host's authorized_keys and CA verification has been used instead |
| 410 | +
|
| 411 | +
|
| 412 | + :parametrized: yes |
| 413 | + """ |
| 414 | + sat = ca_sat[0] |
| 415 | + sat_ca_file = ca_sat[1] |
| 416 | + host = ca_contenthost[0] |
| 417 | + host_ca_file = ca_contenthost[1] |
| 418 | + copy_host_CA(host, sat, host_ca_file, host_ca_file_on_satellite) |
| 419 | + # setup CA |
| 420 | + saved_count = log_count(sat, host) |
| 421 | + command = InstallerCommand( |
| 422 | + foreman_proxy_plugin_remote_execution_script_ssh_user_ca_public_key_file=sat_ca_file, |
| 423 | + foreman_proxy_plugin_remote_execution_script_ssh_host_ca_public_keys_file=host_ca_file_on_satellite, |
| 424 | + ) |
| 425 | + assert sat.install(command).status == 0 |
| 426 | + register_host(sat, host, function_org, cockpit=True) |
| 427 | + # SSH REX |
| 428 | + result = test_execution(sat, host) |
| 429 | + # assert the run actually happened and it was authenticated using cert |
| 430 | + assert result['success'] == '1' |
| 431 | + logger.debug(result) |
| 432 | + assert log_count(sat, host) == saved_count + 1 |
| 433 | + check = host.execute('grep rex_passed /root/test') |
| 434 | + assert check.status == 0 |
| 435 | + check = host.execute(f'grep {sat.hostname} /root/.ssh/authorized_keys') |
| 436 | + assert check.status != 0 |
| 437 | + # ANSIBLE REX |
| 438 | + saved_count = log_count(sat, host) |
| 439 | + command = "echo rex2_passed $(date) > /root/test" |
| 440 | + invocation_command = sat.cli_factory.job_invocation( |
| 441 | + { |
| 442 | + 'job-template': 'Run Command - Ansible Default', |
| 443 | + 'inputs': f'command={command}', |
| 444 | + 'search-query': f'name ~ {host.hostname}', |
| 445 | + } |
| 446 | + ) |
| 447 | + result = sat.cli.JobInvocation.info({'id': invocation_command['id']}) |
| 448 | + # assert the run actually happened and it was authenticated using cert |
| 449 | + assert result['success'] == '1' |
| 450 | + logger.debug(result) |
| 451 | + assert log_count(sat, host) == saved_count + 1 |
| 452 | + check = host.execute('grep rex2_passed /root/test') |
| 453 | + assert check.status == 0 |
| 454 | + # COCKPIT |
| 455 | + saved_count = log_count(sat, host) |
| 456 | + # setup Satellite for cockpit |
| 457 | + sat.register_to_cdn() |
| 458 | + sat.install_cockpit() |
| 459 | + sat.cli.Service.restart() |
| 460 | + # setup cockpit on host |
| 461 | + host.install_cockpit() |
| 462 | + # run cockpit |
| 463 | + # note that merely opening a cockpit in UI connects to ssh already |
| 464 | + # note that this is a UI part, as opposed to the previous parts and previous SSH CA tests |
| 465 | + with sat.ui_session() as session: |
| 466 | + session.organization.select(org_name=function_org.name) |
| 467 | + hostname_inside_cockpit = session.host.get_webconsole_content( |
| 468 | + entity_name=host.hostname, |
| 469 | + rhel_version=host.os_version.major, |
| 470 | + ) |
| 471 | + assert host.hostname in hostname_inside_cockpit, ( |
| 472 | + f'cockpit page shows hostname {hostname_inside_cockpit} instead of {host.hostname}' |
| 473 | + ) |
| 474 | + # assert the cockpit access was authenticated using cert |
| 475 | + assert log_count(sat, host) == saved_count + 1 |
0 commit comments