diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 356fd5855..6fff6b13a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,6 @@ jobs: matrix: certificate_source: - default - - installer security: - none database: @@ -67,9 +66,6 @@ jobs: - centos/stream10 iop: - enabled - exclude: - - certificate_source: installer - box: centos/stream10 include: - certificate_source: default security: fapolicyd @@ -115,10 +111,6 @@ jobs: - name: Configure repositories run: | ./forge setup-repositories - - name: Create installer certificates - if: contains(matrix.certificate_source, 'installer') - run: | - ./forge installer-certs - name: Create custom certificates if: matrix.certificate_source == 'custom_server' run: | @@ -311,6 +303,68 @@ jobs: ## If no one connects after 5 minutes, shut down server. wait-timeout-minutes: 5 + migration: + strategy: + fail-fast: false + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + - name: Setup libvirt for Vagrant + uses: voxpupuli/setup-vagrant@v0 + - name: Install Ansible + run: pip install --upgrade ansible-core + - name: Setup environment + run: ./setup-environment + - name: Start VMs + run: | + ./forge vms start --vms "quadlet client" + - name: Configure repositories + run: | + ./forge setup-repositories + - name: Mock foreman-installer environment + run: | + ./forge mock-installer + - name: Run image pull + run: | + ./foremanctl pull-images + - name: Run migration + run: | + ./foremanctl migrate --output /var/lib/foremanctl/parameters.yaml + - name: Run deployment + run: | + ./foremanctl deploy --foreman-initial-admin-password=changeme --initial-organization "Foreman CI" --initial-location "Internet" --tuning development + - name: Run tests + run: | + ./forge test --pytest-args="--certificate-source=default" + - name: Run smoker + run: | + ./forge smoker + - name: Archive smoker report + if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: smoker-migration + path: "/home/runner/smoker/report/" + - name: Generate sos reports + if: ${{ always() }} + run: ./forge sos + - name: Archive sos reports + if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: sosreport-migration + path: sos/ + - name: Setup upterm session + if: ${{ failure() }} + uses: owenthereal/action-upterm@v1 + with: + limit-access-to-actor: true + wait-timeout-minutes: 5 + # A dummy job that you can mark as a required check instead of each individual test test-suite: if: always() @@ -318,6 +372,7 @@ jobs: - tests - devel-tests - upgrade + - migration - ansible-lint - python-lint runs-on: ubuntu-latest diff --git a/development/playbooks/deploy-dev/deploy-dev.yaml b/development/playbooks/deploy-dev/deploy-dev.yaml index caf89c1d9..803b3816c 100644 --- a/development/playbooks/deploy-dev/deploy-dev.yaml +++ b/development/playbooks/deploy-dev/deploy-dev.yaml @@ -5,7 +5,7 @@ vars_files: - "../../../src/vars/defaults.yml" - "../../../src/vars/flavors/{{ flavor }}.yml" - - "../../../src/vars/{{ certificates_source }}_certificates.yml" + - "../../../src/vars/certificates.yml" - "../../../src/vars/images.yml" - "../../../src/vars/database.yml" - "../../../src/vars/foreman.yml" diff --git a/development/playbooks/installer-certs/installer-certs.yaml b/development/playbooks/installer-certs/installer-certs.yaml deleted file mode 100644 index 8dfd687b9..000000000 --- a/development/playbooks/installer-certs/installer-certs.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- name: Deploy certificates based on foreman-installer - hosts: - - quadlet - become: true - roles: - - foreman_installer_certs diff --git a/development/playbooks/mock-installer/mock-installer.yaml b/development/playbooks/mock-installer/mock-installer.yaml new file mode 100644 index 000000000..64e5534e5 --- /dev/null +++ b/development/playbooks/mock-installer/mock-installer.yaml @@ -0,0 +1,7 @@ +--- +- name: Mock foreman-installer environment for migration testing + hosts: + - quadlet + become: true + roles: + - mock_foreman_installer diff --git a/development/roles/foreman_installer_certs/tasks/main.yml b/development/roles/foreman_installer_certs/tasks/main.yml deleted file mode 100644 index 484ab9c77..000000000 --- a/development/roles/foreman_installer_certs/tasks/main.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: Enable foreman-installer PR 935 Copr repo - community.general.copr: - host: copr.fedorainfracloud.org - state: enabled - name: packit/theforeman-foreman-installer-935 - chroot: rhel-9-x86_64 - -- name: Install foreman-installer package - ansible.builtin.package: - name: foreman-installer-katello - -# utilize https://github.com/theforeman/foreman-installer/pull/935 -- name: Generate certs - ansible.builtin.command: foreman-certs --apache true --foreman true --candlepin true --iop true - changed_when: false diff --git a/development/roles/mock_foreman_installer/tasks/main.yml b/development/roles/mock_foreman_installer/tasks/main.yml new file mode 100644 index 000000000..f906019b9 --- /dev/null +++ b/development/roles/mock_foreman_installer/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Enable foreman-installer PR 935 Copr repo + community.general.copr: + host: copr.fedorainfracloud.org + state: enabled + name: packit/theforeman-foreman-installer-935 + chroot: rhel-9-x86_64 + +- name: Install foreman-installer package + ansible.builtin.package: + name: foreman-installer-katello + +# utilize https://github.com/theforeman/foreman-installer/pull/935 +- name: Generate certs + ansible.builtin.command: foreman-certs --apache true --foreman true --candlepin true --iop true + changed_when: false + +- name: Create installer scenarios directory + ansible.builtin.file: + path: /etc/foreman-installer/scenarios.d + state: directory + mode: '0755' + +- name: Place answers file fixture + ansible.builtin.copy: + src: "{{ playbook_dir }}/../../../tests/fixtures/installer-answers/katello-answers.yaml" + dest: /etc/foreman-installer/scenarios.d/katello-answers.yaml + mode: '0600' + +- name: Place scenario file fixture + ansible.builtin.copy: + src: "{{ playbook_dir }}/../../../tests/fixtures/installer-answers/last_scenario.yaml" + dest: /etc/foreman-installer/scenarios.d/last_scenario.yaml + mode: '0644' diff --git a/docs/user/certificates.md b/docs/user/certificates.md index b9ad45408..d8d9dc885 100644 --- a/docs/user/certificates.md +++ b/docs/user/certificates.md @@ -6,7 +6,7 @@ This document describes how certificate generation and management works in forem ### Certificate Sources -foremanctl supports three certificate sources that determine how certificates are obtained: +foremanctl supports two certificate sources that determine how certificates are obtained: **Default Source (`certificate_source: default`)** - Automatically generates self-signed certificates during deployment @@ -19,11 +19,6 @@ foremanctl supports three certificate sources that determine how certificates ar - Server certificate, key, and CA bundle are copied to `/root/certificates/` - Certificate source persists across deployments; original files only needed on first deploy or when updating certificates -**Installer Source (`certificate_source: installer`)** -- Uses existing certificates from a previous `foreman-installer` deployment -- Useful for migration scenarios where certificates already exist -- Certificate files must be present at expected foreman-installer paths - ### Usage #### Using Auto-Generated Certificates (Default) @@ -59,13 +54,17 @@ foremanctl deploy \ foremanctl deploy --certificate-source=default ``` -#### Using Existing Installer Certificates +#### Migrating from foreman-installer + +When migrating from a `foreman-installer` deployment, use the `migrate` command to normalize existing certificates into foremanctl's canonical structure: ```bash -# Use certificates from previous foreman-installer -foremanctl deploy --certificate-source=installer +foremanctl migrate --output /var/lib/foremanctl/parameters.yaml +foremanctl deploy ``` +The `migrate` command copies certificates from `/root/ssl-build/` into `/root/certificates/`, persists the CA passphrase so foremanctl can issue new certificates, and backs up the original directory to `/root/ssl-build.bak/`. See the [migration guide](../migration-guide.md) for full details. + ### Certificate Locations After deployment, certificates are available at: @@ -81,10 +80,9 @@ After deployment, certificates are available at: - Server CA Certificate: `/root/certificates/certs/server-ca.crt` (custom CA that signed server cert) - Client Certificate: `/root/certificates/certs/-client.crt` (generated by internal CA) -**Installer Source:** -- CA Certificate: `/root/ssl-build/katello-default-ca.crt` -- Server Certificate: `/root/ssl-build//-apache.crt` -- Client Certificate: `/root/ssl-build//-foreman-client.crt` +**After Migration:** +- Certificates from `foreman-installer` are normalized into the same paths as the Default Source above +- Original directory is backed up to `/root/ssl-build.bak/` ### CNAME Support @@ -131,7 +129,6 @@ The `--certificate-renew` flag is **not persisted** in foremanctl’s answers fi - Uses the same lifetime for both client and server certificates - Limited certificate customization options -- Custom server certificates cannot be combined with `certificate_source: installer` - CNAMEs are only applied to certificates generated by the internal CA ## Internal Design @@ -174,10 +171,9 @@ For `certificate_source: custom_server`: 2. **Custom Server Certificates**: Copy the custom server cert, key, and CA bundle from user-provided paths to `/root/certificates/` (only when certificate paths are provided) 3. **Host Certificate Issuance**: Generate client certificate and localhost certificate signed by the internal CA (server cert for FQDN is skipped) -For `certificate_source: installer`: +#### Migration from foreman-installer -- Uses existing certificates from `/root/ssl-build/` generated by foreman-installer -- No certificate generation performed; files must already exist +The `foremanctl migrate` command includes a `migrate_foreman_installer` role that normalizes `foreman-installer` certificates into the canonical `/root/certificates/` structure. It also reads the CA passphrase from the installer's password file and persists it into foremanctl's configuration so that subsequent deploys can issue new certificates using the original CA. #### Variable System @@ -197,14 +193,6 @@ client_certificate: "{{ certificates_ca_directory }}/certs/{{ ansible_facts['fqd - The `server_ca_certificate` points to the custom CA that signed the server certificate - The `ca_bundle` contains both the internal CA and custom server CA -**Installer Source (`src/vars/installer_certificates.yml`):** -```yaml -ca_certificate: "/root/ssl-build/katello-default-ca.crt" -server_certificate: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-apache.crt" -server_ca_certificate: "/root/ssl-build/katello-server-ca.crt" -client_certificate: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.crt" -``` - #### Integration with Deployment In `src/playbooks/deploy/deploy.yaml`: diff --git a/src/playbooks/_certificate_source/metadata.obsah.yaml b/src/playbooks/_certificate_source/metadata.obsah.yaml index 03eb3a847..2be245b6a 100644 --- a/src/playbooks/_certificate_source/metadata.obsah.yaml +++ b/src/playbooks/_certificate_source/metadata.obsah.yaml @@ -1,9 +1,8 @@ --- variables: certificates_source: - help: Where certificates are coming from. Currently default Ansible role, the foreman-installer, or custom server certificates. + help: Where certificates are coming from. Currently default Ansible role or custom server certificates. parameter: --certificate-source choices: - default - - installer - custom_server diff --git a/src/playbooks/deploy/deploy.yaml b/src/playbooks/deploy/deploy.yaml index 837d36c98..8fb18f0ff 100644 --- a/src/playbooks/deploy/deploy.yaml +++ b/src/playbooks/deploy/deploy.yaml @@ -6,7 +6,7 @@ vars_files: - "../../vars/defaults.yml" - "../../vars/flavors/{{ flavor }}.yml" - - "../../vars/{{ certificates_source }}_certificates.yml" + - "../../vars/certificates.yml" - "../../vars/images.yml" - "../../vars/tuning/{{ tuning }}.yml" - "../../vars/database.yml" diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index c1a32b484..2b04775e3 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -60,9 +60,6 @@ variables: constraints: required_together: - [certificates_custom_server_certificate, certificates_custom_server_key, certificates_custom_server_ca_certificate] - forbidden_if: - - [certificates_source, installer, [certificates_custom_server_certificate, certificates_custom_server_key, certificates_custom_server_ca_certificate]] - include: - _certificate_source diff --git a/src/playbooks/migrate/migrate.yaml b/src/playbooks/migrate/migrate.yaml index 6fa2ec577..eefaafd73 100644 --- a/src/playbooks/migrate/migrate.yaml +++ b/src/playbooks/migrate/migrate.yaml @@ -3,6 +3,7 @@ hosts: - quadlet gather_facts: false + become: true tasks: - name: Run migration migrate_answers: @@ -27,3 +28,10 @@ vars: _unmappable_warning: "Warning: {{ migration_result.unmappable | length }} parameter(s) could not be mapped - see warnings above" _output_file_msg: "Output written to: {{ migration_result.output_file }}" + +- name: Migrate foreman-installer certificates + hosts: + - quadlet + become: true + roles: + - role: migrate_foreman_installer diff --git a/src/plugins/modules/migrate_answers.py b/src/plugins/modules/migrate_answers.py index 920538813..1fcd42f29 100755 --- a/src/plugins/modules/migrate_answers.py +++ b/src/plugins/modules/migrate_answers.py @@ -27,10 +27,10 @@ def cast_database_mode(value): ('foreman', 'initial_admin_username'): 'foreman_initial_admin_username', ('foreman', 'initial_admin_password'): 'foreman_initial_admin_password', - # Certificate configuration - ('foreman', 'server_ssl_cert'): 'server_certificate', - ('foreman', 'server_ssl_key'): 'server_key', - ('foreman', 'server_ssl_ca'): 'ca_certificate', + # Certificate paths are handled by the migrate_foreman_installer role + ('foreman', 'server_ssl_cert'): 'IGNORE', + ('foreman', 'server_ssl_key'): 'IGNORE', + ('foreman', 'server_ssl_ca'): 'IGNORE', # TODO: Add more mappings as discovered } @@ -167,6 +167,7 @@ def write_output(data, output_path=None, working_directory=None): absolute_path = os.path.join(working_directory, output_path) else: absolute_path = os.path.abspath(output_path) + os.makedirs(os.path.dirname(absolute_path), exist_ok=True) with open(absolute_path, 'w') as f: f.write(yaml_content) return absolute_path diff --git a/src/roles/migrate_foreman_installer/defaults/main.yml b/src/roles/migrate_foreman_installer/defaults/main.yml new file mode 100644 index 000000000..ee4b0ad9f --- /dev/null +++ b/src/roles/migrate_foreman_installer/defaults/main.yml @@ -0,0 +1,2 @@ +--- +migrate_foreman_installer_ca_directory: /root/certificates diff --git a/src/roles/migrate_foreman_installer/tasks/main.yml b/src/roles/migrate_foreman_installer/tasks/main.yml new file mode 100644 index 000000000..060e2cf9c --- /dev/null +++ b/src/roles/migrate_foreman_installer/tasks/main.yml @@ -0,0 +1,152 @@ +--- +- name: Check if installer certificates exist + ansible.builtin.stat: + path: /root/ssl-build/katello-default-ca.crt + register: migrate_foreman_installer_installer_ca + +- name: Normalize installer certificates + when: migrate_foreman_installer_installer_ca.stat.exists + block: + - name: Install crypto dependencies + ansible.builtin.package: + name: + - python3-cryptography + state: present + + - name: Create certificate directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0755' + loop: + - "{{ migrate_foreman_installer_ca_directory }}/certs" + - "{{ migrate_foreman_installer_ca_directory }}/private" + - "{{ migrate_foreman_installer_ca_directory }}/requests" + + - name: Copy CA certificate from installer + ansible.builtin.copy: + src: /root/ssl-build/katello-default-ca.crt + dest: "{{ migrate_foreman_installer_ca_directory }}/certs/ca.crt" + remote_src: true + mode: '0444' + + - name: Copy server CA certificate from installer + ansible.builtin.copy: + src: /root/ssl-build/katello-server-ca.crt + dest: "{{ migrate_foreman_installer_ca_directory }}/certs/server-ca.crt" + remote_src: true + mode: '0444' + + - name: Detect custom server certificates + ansible.builtin.stat: + path: "{{ item }}" + checksum_algorithm: sha256 + loop: + - /root/ssl-build/katello-default-ca.crt + - /root/ssl-build/katello-server-ca.crt + register: migrate_foreman_installer_ca_checksums + + - name: Set custom server certificate flag + ansible.builtin.set_fact: + migrate_foreman_installer_custom_server_certs: >- + {{ migrate_foreman_installer_ca_checksums.results[0].stat.checksum + != migrate_foreman_installer_ca_checksums.results[1].stat.checksum }} + + - name: Persist certificates_source for custom server certificates + ansible.builtin.lineinfile: + path: /var/lib/foremanctl/parameters.yaml + regexp: '^certificates_source:' + line: "certificates_source: custom_server" + create: true + mode: '0600' + when: migrate_foreman_installer_custom_server_certs + + - name: Create CA bundle from installer certificates + ansible.builtin.assemble: + src: "{{ migrate_foreman_installer_ca_directory }}/certs" + dest: "{{ migrate_foreman_installer_ca_directory }}/certs/ca-bundle.crt" + regexp: '(ca|server-ca)\.crt$' + mode: '0444' + + - name: Copy CA key from installer + ansible.builtin.copy: + src: /root/ssl-build/katello-default-ca.key + dest: "{{ migrate_foreman_installer_ca_directory }}/private/ca.key" + remote_src: true + mode: '0440' + + - name: Copy CA password from installer + ansible.builtin.copy: + src: /root/ssl-build/katello-default-ca.pwd + dest: "{{ migrate_foreman_installer_ca_directory }}/private/ca.pwd" + remote_src: true + mode: '0600' + + - name: Read CA password from installer + ansible.builtin.slurp: + src: /root/ssl-build/katello-default-ca.pwd + register: migrate_foreman_installer_installer_ca_password + no_log: true + + - name: Persist CA password to foremanctl configuration + ansible.builtin.lineinfile: + path: /var/lib/foremanctl/parameters.yaml + regexp: '^certificates_ca_password:' + line: "certificates_ca_password: \"{{ migrate_foreman_installer_installer_ca_password.content | b64decode | trim }}\"" + create: true + mode: '0600' + no_log: true + + - name: Copy server certificate from installer + ansible.builtin.copy: + src: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-apache.crt" + dest: "{{ migrate_foreman_installer_ca_directory }}/certs/{{ ansible_facts['fqdn'] }}.crt" + remote_src: true + mode: '0444' + + - name: Copy server key from installer + ansible.builtin.copy: + src: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-apache.key" + dest: "{{ migrate_foreman_installer_ca_directory }}/private/{{ ansible_facts['fqdn'] }}.key" + remote_src: true + mode: '0440' + + - name: Copy client certificate from installer + ansible.builtin.copy: + src: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.crt" + dest: "{{ migrate_foreman_installer_ca_directory }}/certs/{{ ansible_facts['fqdn'] }}-client.crt" + remote_src: true + mode: '0444' + + - name: Copy client key from installer + ansible.builtin.copy: + src: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.key" + dest: "{{ migrate_foreman_installer_ca_directory }}/private/{{ ansible_facts['fqdn'] }}-client.key" + remote_src: true + mode: '0440' + + - name: Copy localhost certificate from installer + ansible.builtin.copy: + src: /root/ssl-build/localhost/localhost-tomcat.crt + dest: "{{ migrate_foreman_installer_ca_directory }}/certs/localhost.crt" + remote_src: true + mode: '0444' + + - name: Copy localhost key from installer + ansible.builtin.copy: + src: /root/ssl-build/localhost/localhost-tomcat.key + dest: "{{ migrate_foreman_installer_ca_directory }}/private/localhost.key" + remote_src: true + mode: '0440' + + - name: Backup installer certificate directory + ansible.builtin.copy: + src: /root/ssl-build/ + dest: /root/ssl-build.bak/ + remote_src: true + mode: preserve + + - name: Remove original installer certificate directory + ansible.builtin.file: + path: /root/ssl-build + state: absent diff --git a/src/vars/default_certificates.yml b/src/vars/certificates.yml similarity index 100% rename from src/vars/default_certificates.yml rename to src/vars/certificates.yml diff --git a/src/vars/custom_server_certificates.yml b/src/vars/custom_server_certificates.yml deleted file mode 100644 index a70f33583..000000000 --- a/src/vars/custom_server_certificates.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -certificates_ca_directory: /root/certificates -ca_key_password: "{{ certificates_ca_directory }}/private/ca.pwd" -ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt" -ca_key: "{{ certificates_ca_directory }}/private/ca.key" -server_certificate: "{{ certificates_ca_directory }}/certs/{{ ansible_facts['fqdn'] }}.crt" -server_key: "{{ certificates_ca_directory }}/private/{{ ansible_facts['fqdn'] }}.key" -server_ca_certificate: "{{ certificates_ca_directory }}/certs/server-ca.crt" -ca_bundle: "{{ certificates_ca_directory }}/certs/ca-bundle.crt" -client_certificate: "{{ certificates_ca_directory }}/certs/{{ ansible_facts['fqdn'] }}-client.crt" -client_key: "{{ certificates_ca_directory }}/private/{{ ansible_facts['fqdn'] }}-client.key" -client_ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt" -localhost_key: "{{ certificates_ca_directory }}/private/localhost.key" -localhost_certificate: "{{ certificates_ca_directory }}/certs/localhost.crt" diff --git a/src/vars/installer_certificates.yml b/src/vars/installer_certificates.yml deleted file mode 100644 index 6939e6310..000000000 --- a/src/vars/installer_certificates.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -ca_key_password: "/root/ssl-build/katello-default-ca.pwd" -ca_certificate: "/root/ssl-build/katello-default-ca.crt" -ca_key: "/root/ssl-build/katello-default-ca.key" -server_certificate: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-apache.crt" -server_key: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-apache.key" -server_ca_certificate: "/root/ssl-build/katello-server-ca.crt" -ca_bundle: "/root/ssl-build/ca-bundle.crt" -client_certificate: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.crt" -client_key: "/root/ssl-build/{{ ansible_facts['fqdn'] }}/{{ ansible_facts['fqdn'] }}-foreman-client.key" -client_ca_certificate: "{{ ca_certificate }}" -localhost_key: "/root/ssl-build/localhost/localhost-tomcat.key" -localhost_certificate: "/root/ssl-build/localhost/localhost-tomcat.crt" - -iop_gateway_server_certificate: "/root/ssl-build/localhost/localhost-iop-core-gateway-server.crt" -iop_gateway_server_key: "/root/ssl-build/localhost/localhost-iop-core-gateway-server.key" -iop_gateway_server_ca_certificate: "/root/ssl-build/katello-default-ca.crt" -iop_gateway_client_certificate: "/root/ssl-build/localhost/localhost-iop-core-gateway-client.crt" -iop_gateway_client_key: "/root/ssl-build/localhost/localhost-iop-core-gateway-client.key" -iop_gateway_client_ca_certificate: "/root/ssl-build/katello-server-ca.crt" -iop_vmaas_client_ca_certificate: "/root/ssl-build/katello-server-ca.crt" -iop_cvemap_downloader_client_cert: "{{ client_certificate }}" -iop_cvemap_downloader_client_key: "{{ client_key }}" -iop_cvemap_downloader_client_ca: "{{ client_ca_certificate }}" diff --git a/tests/certificates_test.py b/tests/certificates_test.py index 4619221a1..e65a813b5 100644 --- a/tests/certificates_test.py +++ b/tests/certificates_test.py @@ -21,7 +21,7 @@ def test_default_server_ca_matches_internal_ca(server, certificates, default_cer ca_info = certificate_info(server, certificates['ca_certificate']) server_ca_info = certificate_info(server, certificates['server_ca_certificate']) assert ca_info['subject'] == server_ca_info['subject'], \ - "Default/installer server CA should match the internal CA" + "Default server CA should match the internal CA" def test_custom_server_ca_differs_from_internal_ca(server, certificates, custom_certificates): diff --git a/tests/conftest.py b/tests/conftest.py index 9bf66ec3b..84d884769 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -42,7 +42,7 @@ def enabled_features(self): def pytest_addoption(parser): - parser.addoption("--certificate-source", action="store", default="default", choices=('default', 'installer', 'custom_server'), help="Certificate source used during deployment") + parser.addoption("--certificate-source", action="store", default="default", choices=('default', 'custom_server'), help="Certificate source used during deployment") parser.addoption("--database-mode", action="store", default="internal", choices=('internal', 'external'), help="Whether the database is internal or external") @@ -77,11 +77,10 @@ def client_fqdn(client): @pytest.fixture(scope="module") -def certificates(certificate_source, server_fqdn): +def certificates(server_fqdn): env = Environment(loader=FileSystemLoader("."), autoescape=select_autoescape()) - template = env.get_template(f"./src/vars/{certificate_source}_certificates.yml") - context = {'certificates_ca_directory': '/var/lib/foremanctl/certs', - 'ansible_facts': {'fqdn': server_fqdn}} + template = env.get_template("./src/vars/certificates.yml") + context = {'ansible_facts': {'fqdn': server_fqdn}} # we have vars that refer to other vars, so load them once and then re-render the template context.update(yaml.safe_load(template.render(context))) return yaml.safe_load(template.render(context)) diff --git a/tests/fixtures/installer-answers/katello-answers.yaml b/tests/fixtures/installer-answers/katello-answers.yaml new file mode 100644 index 000000000..369daeebb --- /dev/null +++ b/tests/fixtures/installer-answers/katello-answers.yaml @@ -0,0 +1,34 @@ +--- +# Fixture representing a foreman-installer-katello answers file. +# Used by forge mock-installer to simulate what foreman-installer leaves behind. +# Passwords are intentionally fake placeholders. +foreman: + db_host: localhost + db_port: 5432 + db_database: foreman + db_username: foreman + db_password: changeme + db_manage: true + db_manage_rake: true + initial_admin_username: admin + initial_admin_password: changeme + server_ssl_cert: /etc/pki/katello/certs/katello-apache.crt + server_ssl_key: /etc/pki/katello/private/katello-apache.key + server_ssl_ca: /etc/pki/katello/certs/katello-default-ca.crt + db_adapter: postgresql + db_pool: 5 + oauth_active: true + oauth_consumer_key: changeme + oauth_consumer_secret: changeme + locations_enabled: true + organizations_enabled: true +katello: + candlepin_db_host: localhost + candlepin_db_port: 5432 + candlepin_db_name: candlepin + candlepin_db_user: candlepin + candlepin_db_password: changeme + candlepin_manage_db: true + pulp_worker_count: 2 +puppet: + enabled: false diff --git a/tests/fixtures/installer-answers/last_scenario.yaml b/tests/fixtures/installer-answers/last_scenario.yaml new file mode 100644 index 000000000..a83421f52 --- /dev/null +++ b/tests/fixtures/installer-answers/last_scenario.yaml @@ -0,0 +1,4 @@ +--- +# Fixture representing /etc/foreman-installer/scenarios.d/last_scenario.yaml. +# The :answer_file key uses Ruby YAML symbol notation as written by foreman-installer. +":answer_file": "/etc/foreman-installer/scenarios.d/katello-answers.yaml" diff --git a/tests/migration_test.py b/tests/migration_test.py new file mode 100644 index 000000000..898a2b112 --- /dev/null +++ b/tests/migration_test.py @@ -0,0 +1,61 @@ +import pytest +import yaml + + +@pytest.fixture(scope="module") +def migrated_environment(server): + if not server.file("/root/ssl-build.bak").exists: + pytest.skip("Not a migrated environment") + + +def test_installer_directory_removed(server, migrated_environment): + assert not server.file("/root/ssl-build").exists + + +def test_installer_backup_exists(server, migrated_environment): + backup = server.file("/root/ssl-build.bak") + assert backup.exists + assert backup.is_directory + + +@pytest.mark.parametrize("subdir", ["certs", "private", "requests"]) +def test_certificate_directories(server, migrated_environment, subdir): + d = server.file(f"/root/certificates/{subdir}") + assert d.exists + assert d.is_directory + assert d.mode == 0o755 + + +def test_ca_password_file(server, migrated_environment): + f = server.file("/root/certificates/private/ca.pwd") + assert f.exists + assert f.mode == 0o600 + + +def test_ca_password_persisted(server, migrated_environment): + f = server.file("/var/lib/foremanctl/parameters.yaml") + assert f.exists + params = yaml.safe_load(f.content_string) + assert "certificates_ca_password" in params + assert len(params["certificates_ca_password"]) > 0 + + +def test_default_certs_no_custom_source(server, migrated_environment): + f = server.file("/var/lib/foremanctl/parameters.yaml") + assert f.exists + params = yaml.safe_load(f.content_string) + assert "certificates_source" not in params + + +def test_answers_migration_database_mode(server, migrated_environment): + f = server.file("/var/lib/foremanctl/parameters.yaml") + assert f.exists + params = yaml.safe_load(f.content_string) + assert params.get("database_mode") == "internal" + + +def test_answers_migration_admin_username(server, migrated_environment): + f = server.file("/var/lib/foremanctl/parameters.yaml") + assert f.exists + params = yaml.safe_load(f.content_string) + assert params.get("foreman_initial_admin_username") == "admin" diff --git a/tests/unit/migrate_test.py b/tests/unit/migrate_test.py index d95bec7ba..dbb3a0460 100644 --- a/tests/unit/migrate_test.py +++ b/tests/unit/migrate_test.py @@ -59,6 +59,25 @@ def test_ignore_parameters(self): assert 'db_manage_rake' not in str(result['unmappable']) assert result['mapped']['database_host'] == 'localhost' + def test_certificate_parameters_ignored(self): + """Test that certificate path parameters are ignored (handled by migration role)""" + old_config = { + 'foreman': { + 'server_ssl_cert': '/etc/pki/katello/certs/server.crt', + 'server_ssl_key': '/etc/pki/katello/private/server.key', + 'server_ssl_ca': '/etc/pki/katello/certs/ca.crt', + 'db_host': 'localhost' + } + } + + result = migrate_answers.apply_mappings(old_config) + + assert 'server_certificate' not in result['mapped'] + assert 'server_key' not in result['mapped'] + assert 'ca_certificate' not in result['mapped'] + assert not any('ssl' in p for p in result['unmappable']) + assert result['mapped']['database_host'] == 'localhost' + def test_unmappable_parameters(self): """Test that unmappable parameters are reported""" old_config = {