diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29bbd0ab3..9acfe2396 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -422,15 +422,15 @@ jobs: --initial-admin-password=changeme \ --tuning development \ --add-feature foreman-proxy - - name: Generate certificates bundle on quadlet + - name: Generate auth bundle on quadlet run: | - ./foremanctl certificate-bundle proxy.example.com \ + ./foremanctl auth-bundle proxy.example.com \ ${{ matrix.certificate_source == 'custom_server' && '--certificate-server-certificate /root/custom-certificates/certs/proxy.example.com.crt --certificate-server-key /root/custom-certificates/private/proxy.example.com.key' || '' }} - name: Refresh internal CA to ensure it doesn't break the bundle if: matrix.certificate_source != 'custom_server' run: | ./foremanctl deploy --certificate-ca-renew - - name: Fetch certificates bundle from quadlet + - name: Fetch auth bundle from quadlet run: | ./forge fetch-bundle proxy.example.com - name: Remove old parameters, but keep certificates_source for tests @@ -440,10 +440,8 @@ jobs: run: | ./foremanctl deploy-proxy \ --flavor foreman-proxy-content \ - --certificate-bundle $(pwd)/.var/lib/foremanctl/proxy.example.com.tar.gz \ + --auth-bundle $(pwd)/.var/lib/foremanctl/proxy.example.com.tar.gz \ --foreman-fqdn quadlet.example.com \ - --oauth-consumer-key $(cat .var/lib/foremanctl/foreman-oauth-consumer-key) \ - --oauth-consumer-secret $(cat .var/lib/foremanctl/foreman-oauth-consumer-secret) \ --templates-listen-on http \ --templates-url http://proxy.example.com:8000 - name: Run tests diff --git a/development/playbooks/fetch-bundle/metadata.obsah.yaml b/development/playbooks/fetch-bundle/metadata.obsah.yaml index f2d2d635c..1fc9a64b9 100644 --- a/development/playbooks/fetch-bundle/metadata.obsah.yaml +++ b/development/playbooks/fetch-bundle/metadata.obsah.yaml @@ -1,8 +1,8 @@ --- help: | - Fetch a certificate bundle + Fetch an auth bundle variables: hostname: parameter: hostname - help: Hostname to fetch the certificate bundle for. + help: Hostname to fetch the auth bundle for. diff --git a/docs/developer/deployment.md b/docs/developer/deployment.md index cc8f67bc2..7b9a92d03 100644 --- a/docs/developer/deployment.md +++ b/docs/developer/deployment.md @@ -17,16 +17,19 @@ Deploys a Foreman server. This is the primary deployment type and the default en Deploys a Foreman Proxy node that connects to a Foreman server. -Before running the proxy deployment, a certificate bundle must be generated on the Foreman server and copied to the proxy VM: +Before running the proxy deployment, an auth bundle must be generated on the Foreman server and copied to the proxy VM: -1. On the **Foreman server**, generate a certificate bundle for the proxy hostname: +1. On the **Foreman server**, generate an auth bundle for the proxy hostname: ```bash - ./foremanctl certificate-bundle proxy.example.com + ./foremanctl auth-bundle proxy.example.com ``` This produces a tar archive at a path like `/var/lib/foremanctl/certs/bundles/.tar.gz`. +> [!NOTE] +> The bundle includes the proxy's certificates and OAuth credentials needed for the proxy to communicate with the Foreman server. + 2. Copy the bundle to the **proxy VM**: ```bash @@ -38,7 +41,7 @@ Before running the proxy deployment, a certificate bundle must be generated on t ```bash ./foremanctl deploy-proxy \ --flavor foreman-proxy-content \ - --certificate-bundle /root/proxy.example.com.tar.gz \ + --auth-bundle /root/proxy.example.com.tar.gz \ --foreman-fqdn quadlet.example.com ``` diff --git a/docs/user/certificates.md b/docs/user/certificates.md index 0a3fa8914..4a246b490 100644 --- a/docs/user/certificates.md +++ b/docs/user/certificates.md @@ -84,7 +84,7 @@ After deployment, certificates are available at: - 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/` -**Proxy/Secondary System Certificates (from certificate-bundle):** +**Proxy/Secondary System Certificates (from auth-bundle):** - Generated in per-host subdirectories: `/var/lib/foremanctl/certs/hosts//` - Packaged tarball: `/var/lib/foremanctl/certs/bundles/.tar.gz` @@ -138,20 +138,20 @@ foremanctl deploy --certificate-ca-renew Both flags are **not persisted** in foremanctl’s answers file (one-shot). -### Certificate Bundle for Secondary Systems +### Auth Bundle for Secondary Systems -The `certificate-bundle` command generates a certificate tarball for a secondary system such as a foreman-proxy host. This is the foremanctl equivalent of `foreman-proxy-certs-generate`. +The `auth-bundle` command generates a certificate and OAuth credential tarball for a secondary system such as a foreman-proxy host. This is the foremanctl equivalent of `foreman-proxy-certs-generate`. The internal CA must already exist from a prior `foremanctl deploy`. #### Usage ```bash -# Generate a certificate bundle using the internal CA -foremanctl certificate-bundle proxy.example.com +# Generate an auth bundle using the internal CA +foremanctl auth-bundle proxy.example.com -# Generate a certificate bundle with custom server certificates for the proxy -foremanctl certificate-bundle \ +# Generate an auth bundle with custom server certificates for the proxy +foremanctl auth-bundle \ --certificate-server-certificate /path/to/proxy.example.com.crt \ --certificate-server-key /path/to/proxy.example.com.key \ proxy.example.com @@ -165,7 +165,7 @@ If the Foreman server was deployed with custom server certificates, each proxy m #### Tarball Contents -The tarball uses the same directory structure as `/var/lib/foremanctl/certs/`: +The tarball mirrors the on-disk layout under `/var/lib/foremanctl/`: ``` certs/ @@ -176,8 +176,13 @@ certs/ private/ ├── .key # Server private key └── -client.key # Client private key +oauth/ +├── foreman-oauth-consumer-key # OAuth consumer key for Foreman API auth +└── foreman-oauth-consumer-secret # OAuth consumer secret for Foreman API auth ``` +On extract, `certs/` and `private/` are installed under `/var/lib/foremanctl/certs/`, and `oauth/` is installed under `/var/lib/foremanctl/oauth/`. + When using the internal CA only, `server-ca.crt` and `ca.crt` are identical. When custom server certificates are provided, `server-ca.crt` contains the custom CA and `ca.crt` contains the internal CA. ### Current Limitations @@ -203,7 +208,7 @@ src/roles/certificates/ │ └── custom.yml # Applies user-provided custom server certs └── defaults/main.yml # Default configuration variables -src/roles/certificate_bundle/ +src/roles/auth_bundle/ ├── tasks/main.yml # Packages certificates into a tarball └── defaults/main.yml # Output directory configuration ``` @@ -230,11 +235,11 @@ For `certificate_source: custom_server`: 2. **Custom Server Certificates**: Copy the custom server cert, key, and CA bundle from user-provided paths to `/var/lib/foremanctl/certs/` (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) -#### Certificate Bundle Generation +#### Auth Bundle Generation -The `certificate-bundle` playbook reuses the `certificates` role to generate proxy certificates in a per-host subdirectory (`certificates_ca_directory/hosts//`). It overrides `certificates_ca_directory_certs`, `certificates_ca_directory_keys`, and `certificates_ca_directory_requests` to point to the subdirectory while keeping `certificates_ca_directory` unchanged so the CA can still be found for signing. It sets `certificates_ca: false` (the CA already exists from a prior deploy) and passes the proxy hostname via `certificates_hostnames`. The `certificate_bundle` role then packages the generated certificates along with the CA into a tarball under `certificates_ca_directory/bundles/`. +The `auth-bundle` playbook reuses the `certificates` role to generate proxy certificates in a per-host subdirectory (`certificates_ca_directory/hosts//`). It overrides `certificates_ca_directory_certs`, `certificates_ca_directory_keys`, and `certificates_ca_directory_requests` to point to the subdirectory while keeping `certificates_ca_directory` unchanged so the CA can still be found for signing. It sets `certificates_ca: false` (the CA already exists from a prior deploy) and passes the proxy hostname via `certificates_hostnames`. The `auth_bundle` role then packages the generated certificates along with the CA into a tarball under `certificates_ca_directory/bundles/`. -When custom server certificates are provided for the proxy, `certificates_source` is set to `custom_server` so only client certificates are generated by the internal CA. The custom server certificate, key, and CA are validated by the `certificate_checks` role and then packaged directly into the tarball by the `certificate_bundle` role. +When custom server certificates are provided for the proxy, `certificates_source` is set to `custom_server` so only client certificates are generated by the internal CA. The custom server certificate, key, and CA are validated by the `certificate_checks` role and then packaged directly into the tarball by the `auth_bundle` role. #### Migration from foreman-installer diff --git a/docs/user/parameters.md b/docs/user/parameters.md index 865a0019f..e9f4086be 100644 --- a/docs/user/parameters.md +++ b/docs/user/parameters.md @@ -132,6 +132,13 @@ There are multiple use cases from the users perspective that dictate what parame | `--templates-listen-on` | Templates proxy to listen on https, http, or both | `--foreman-proxy-templates-listen-on` | | `--templates-url` | URL that hosts will use to contact the proxy for provisioning templates | `--foreman-proxy-templates-url` | +### Unmapped + +| foreman-installer Parameter | Description | Reason | +| --------------------------- | ----------- | ------ | +| `--foreman-proxy-oauth-consumer-key` | OAuth consumer key for Smart Proxy | Not required(Managed automatically via auth bundle) | +| `--foreman-proxy-oauth-consumer-secret` | OAuth consumer secret for Smart Proxy | Not required(Managed automatically via auth bundle) | + ### Undetermined | Installer Parameter | Description | Module | Puppet Parameter | @@ -139,8 +146,6 @@ There are multiple use cases from the users perspective that dictate what parame | `--foreman-proxy-cname` | Enables DHCP feature in smart-proxy | foreman_proxy | cname | | `--foreman-proxy-fqdn` | Enables DHCP feature in smart-proxy | foreman_proxy | fqdn | | `--foreman-proxy-foreman-base-url` | | foreman_proxy | foreman_base_url | -| `--foreman-proxy-oauth-consumer-key` | | foreman_proxy | oauth_consumer_key | -| `--foreman-proxy-oauth-consumer-secret` | | foreman_proxy | oauth_consumer_secret | | `--foreman-proxy-register-in-foreman` | | foreman_proxy | register_in_foreman | | `--foreman-proxy-trusted-hosts` | | foreman_proxy | trusted_hosts | | `--foreman-proxy-dhcp` | Enables DHCP feature in smart-proxy | foreman_proxy | dhcp | diff --git a/src/playbooks/auth-bundle/auth-bundle.yaml b/src/playbooks/auth-bundle/auth-bundle.yaml new file mode 100644 index 000000000..3343fe29d --- /dev/null +++ b/src/playbooks/auth-bundle/auth-bundle.yaml @@ -0,0 +1,34 @@ +--- +- name: Generate an auth bundle for a hostname + hosts: + - quadlet + become: true + vars_files: + - "../../vars/base.yaml" + - "../../vars/certificates.yml" + - "../../vars/foreman.yml" + roles: + - role: certificate_checks + when: certificates_source == 'custom_server' + vars: + certificate_checks_certificate: "{{ certificates_custom_server_certificate }}" + certificate_checks_key: "{{ certificates_custom_server_key }}" + certificate_checks_ca: "{{ server_ca_certificate }}" + - role: certificates + vars: + certificates_ca: false + certificates_output_directory: "{{ certificates_ca_directory }}/hosts/{{ hostname }}" + certificates_hostnames: + - "{{ hostname }}" + - role: auth_bundle + vars: + auth_bundle_hostname: "{{ hostname }}" + auth_bundle_ca_bundle: "{{ ca_bundle }}" + auth_bundle_ca_certificate: "{{ ca_certificate }}" + auth_bundle_server_ca_certificate: "{{ server_ca_certificate }}" + auth_bundle_server_certificate: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/certs/{{ hostname }}.crt" + auth_bundle_server_key: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/private/{{ hostname }}.key" + auth_bundle_client_certificate: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/certs/{{ hostname }}-client.crt" + auth_bundle_client_key: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/private/{{ hostname }}-client.key" + auth_bundle_oauth_consumer_key_file: "{{ foreman_oauth_consumer_key_file }}" + auth_bundle_oauth_consumer_secret_file: "{{ foreman_oauth_consumer_secret_file }}" diff --git a/src/playbooks/certificate-bundle/metadata.obsah.yaml b/src/playbooks/auth-bundle/metadata.obsah.yaml similarity index 89% rename from src/playbooks/certificate-bundle/metadata.obsah.yaml rename to src/playbooks/auth-bundle/metadata.obsah.yaml index 3fa6cc15d..b8bfd2bab 100644 --- a/src/playbooks/certificate-bundle/metadata.obsah.yaml +++ b/src/playbooks/auth-bundle/metadata.obsah.yaml @@ -1,11 +1,11 @@ --- help: | - Generate a certificate bundle + Generate an auth bundle variables: hostname: parameter: hostname - help: Hostname to generate a certificate bundle for that will be the common name. + help: Hostname to generate an auth bundle for that will be the common name. certificates_custom_server_certificate: help: Path to a custom server certificate for the proxy. type: AbsolutePath diff --git a/src/playbooks/certificate-bundle/certificate-bundle.yaml b/src/playbooks/certificate-bundle/certificate-bundle.yaml deleted file mode 100644 index 7971da4ec..000000000 --- a/src/playbooks/certificate-bundle/certificate-bundle.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -- name: Generate a certificate bundle for a hostname - hosts: - - quadlet - become: true - vars_files: - - "../../vars/base.yaml" - - "../../vars/certificates.yml" - roles: - - role: certificate_checks - when: certificates_source == 'custom_server' - vars: - certificate_checks_certificate: "{{ certificates_custom_server_certificate }}" - certificate_checks_key: "{{ certificates_custom_server_key }}" - certificate_checks_ca: "{{ server_ca_certificate }}" - - role: certificates - vars: - certificates_ca: false - certificates_output_directory: "{{ certificates_ca_directory }}/hosts/{{ hostname }}" - certificates_hostnames: - - "{{ hostname }}" - - role: certificate_bundle - vars: - certificate_bundle_hostname: "{{ hostname }}" - certificate_bundle_ca_bundle: "{{ ca_bundle }}" - certificate_bundle_ca_certificate: "{{ ca_certificate }}" - certificate_bundle_server_ca_certificate: "{{ server_ca_certificate }}" - certificate_bundle_server_certificate: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/certs/{{ hostname }}.crt" - certificate_bundle_server_key: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/private/{{ hostname }}.key" - certificate_bundle_client_certificate: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/certs/{{ hostname }}-client.crt" - certificate_bundle_client_key: "{{ certificates_ca_directory }}/hosts/{{ hostname }}/private/{{ hostname }}-client.key" diff --git a/src/playbooks/deploy-proxy/deploy-proxy.yaml b/src/playbooks/deploy-proxy/deploy-proxy.yaml index 1462e7c6a..5ba00c2a1 100644 --- a/src/playbooks/deploy-proxy/deploy-proxy.yaml +++ b/src/playbooks/deploy-proxy/deploy-proxy.yaml @@ -25,6 +25,7 @@ certificate_checks_certificate: "{{ server_certificate }}" certificate_checks_key: "{{ server_key }}" certificate_checks_ca: "{{ server_ca_certificate }}" + - role: oauth_from_bundle - role: postgresql when: - database_mode == 'internal' diff --git a/src/playbooks/deploy-proxy/metadata.obsah.yaml b/src/playbooks/deploy-proxy/metadata.obsah.yaml index c2772050b..16070eb65 100644 --- a/src/playbooks/deploy-proxy/metadata.obsah.yaml +++ b/src/playbooks/deploy-proxy/metadata.obsah.yaml @@ -7,20 +7,14 @@ variables: help: Base flavor to use in this deployment. choices: - foreman-proxy-content - certificates_bundle: - help: Path to the certificate bundle tar file. + auth_bundle: + help: Path to the auth bundle tar file. type: AbsolutePath - parameter: --certificate-bundle + parameter: --auth-bundle persist: false foreman_name: parameter: --foreman-fqdn help: FQDN of the Foreman server this proxy connects to. - foreman_proxy_oauth_consumer_key: - parameter: --oauth-consumer-key - help: OAuth key to be used for communication with Foreman. - foreman_proxy_oauth_consumer_secret: - parameter: --oauth-consumer-secret - help: OAuth secret to be used for communication with Foreman. include: - _flavor_features diff --git a/src/roles/auth_bundle/defaults/main.yml b/src/roles/auth_bundle/defaults/main.yml new file mode 100644 index 000000000..8e7a56571 --- /dev/null +++ b/src/roles/auth_bundle/defaults/main.yml @@ -0,0 +1,3 @@ +--- +auth_bundle_output_directory: "{{ certificates_ca_directory }}/bundles" +auth_bundle_server_ca_certificate: "{{ auth_bundle_ca_certificate }}" diff --git a/src/roles/auth_bundle/tasks/main.yml b/src/roles/auth_bundle/tasks/main.yml new file mode 100644 index 000000000..0a31232f3 --- /dev/null +++ b/src/roles/auth_bundle/tasks/main.yml @@ -0,0 +1,107 @@ +--- +- name: Create output directory + ansible.builtin.file: + path: "{{ auth_bundle_output_directory }}" + state: directory + mode: '0755' + +- name: Check for existing auth bundle + ansible.builtin.stat: + path: "{{ auth_bundle_output_directory }}/{{ auth_bundle_hostname }}.tar.gz" + register: _auth_bundle_tarball + +- name: Build auth bundle + when: not _auth_bundle_tarball.stat.exists or (certificates_renew | default(false) | bool) + block: + - name: Create temporary directory + ansible.builtin.tempfile: + state: directory + suffix: auth-bundle-build + register: auth_bundle_build_directory + + - name: Create directory structure + ansible.builtin.file: + state: directory + path: "{{ auth_bundle_build_directory.path }}/certs/{{ item }}" + mode: '0755' + loop: + - certs + - private + - oauth + + - name: Copy default CA certificate + ansible.builtin.copy: + src: "{{ auth_bundle_ca_certificate }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/certs/ca.crt" + remote_src: true + mode: '0444' + + - name: Copy server CA certificate + ansible.builtin.copy: + src: "{{ auth_bundle_server_ca_certificate }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/certs/server-ca.crt" + remote_src: true + mode: '0444' + + - name: Copy CA bundle + ansible.builtin.copy: + src: "{{ auth_bundle_ca_bundle }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/certs/ca-bundle.crt" + remote_src: true + mode: '0444' + + - name: Copy server certificate + ansible.builtin.copy: + src: "{{ auth_bundle_server_certificate }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/certs/{{ auth_bundle_hostname }}.crt" + remote_src: true + mode: '0444' + + - name: Copy server key + ansible.builtin.copy: + src: "{{ auth_bundle_server_key }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/private/{{ auth_bundle_hostname }}.key" + remote_src: true + mode: '0440' + + - name: Copy client certificate + ansible.builtin.copy: + src: "{{ auth_bundle_client_certificate }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/certs/{{ auth_bundle_hostname }}-client.crt" + remote_src: true + mode: '0444' + + - name: Copy client key + ansible.builtin.copy: + src: "{{ auth_bundle_client_key }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/private/{{ auth_bundle_hostname }}-client.key" + remote_src: true + mode: '0440' + + - name: Copy OAuth consumer key + ansible.builtin.copy: + src: "{{ auth_bundle_oauth_consumer_key_file }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/oauth/foreman-oauth-consumer-key" + mode: '0440' + + - name: Copy OAuth consumer secret + ansible.builtin.copy: + src: "{{ auth_bundle_oauth_consumer_secret_file }}" + dest: "{{ auth_bundle_build_directory.path }}/certs/oauth/foreman-oauth-consumer-secret" + mode: '0440' + + - name: Create tarball + community.general.archive: + path: "{{ auth_bundle_build_directory.path }}/certs/*" + dest: "{{ auth_bundle_output_directory }}/{{ auth_bundle_hostname }}.tar.gz" + format: gz + mode: '0640' + + - name: Remove temporary directory + ansible.builtin.file: + path: "{{ auth_bundle_build_directory.path }}" + state: absent + +- name: Report auth bundle location + ansible.builtin.debug: + msg: "Auth bundle created: {{ auth_bundle_output_directory }}/{{ auth_bundle_hostname }}.tar.gz" diff --git a/src/roles/certificate_bundle/defaults/main.yml b/src/roles/certificate_bundle/defaults/main.yml deleted file mode 100644 index 736f7ee44..000000000 --- a/src/roles/certificate_bundle/defaults/main.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -certificate_bundle_output_directory: "{{ certificates_ca_directory }}/bundles" -certificate_bundle_server_ca_certificate: "{{ certificate_bundle_ca_certificate }}" diff --git a/src/roles/certificate_bundle/tasks/main.yml b/src/roles/certificate_bundle/tasks/main.yml deleted file mode 100644 index 288f82f2f..000000000 --- a/src/roles/certificate_bundle/tasks/main.yml +++ /dev/null @@ -1,94 +0,0 @@ ---- -- name: Create output directory - ansible.builtin.file: - path: "{{ certificate_bundle_output_directory }}" - state: directory - mode: '0755' - -- name: Check for existing certificate bundle - ansible.builtin.stat: - path: "{{ certificate_bundle_output_directory }}/{{ certificate_bundle_hostname }}.tar.gz" - register: _certificate_bundle_tarball - -- name: Build certificate bundle - when: not _certificate_bundle_tarball.stat.exists or (certificates_renew | default(false) | bool) - block: - - name: Create temporary directory - ansible.builtin.tempfile: - state: directory - suffix: certificate-bundle-build - register: certificate_bundle_build_directory - - - name: Create directory structure - ansible.builtin.file: - state: directory - path: "{{ certificate_bundle_build_directory.path }}/certs/{{ item }}" - mode: '0755' - loop: - - certs - - private - - - name: Copy default CA certificate - ansible.builtin.copy: - src: "{{ certificate_bundle_ca_certificate }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/certs/ca.crt" - remote_src: true - mode: '0444' - - - name: Copy server CA certificate - ansible.builtin.copy: - src: "{{ certificate_bundle_server_ca_certificate }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/certs/server-ca.crt" - remote_src: true - mode: '0444' - - - name: Copy CA bundle - ansible.builtin.copy: - src: "{{ certificate_bundle_ca_bundle }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/certs/ca-bundle.crt" - remote_src: true - mode: '0444' - - - name: Copy server certificate - ansible.builtin.copy: - src: "{{ certificate_bundle_server_certificate }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/certs/{{ certificate_bundle_hostname }}.crt" - remote_src: true - mode: '0444' - - - name: Copy server key - ansible.builtin.copy: - src: "{{ certificate_bundle_server_key }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/private/{{ certificate_bundle_hostname }}.key" - remote_src: true - mode: '0440' - - - name: Copy client certificate - ansible.builtin.copy: - src: "{{ certificate_bundle_client_certificate }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/certs/{{ certificate_bundle_hostname }}-client.crt" - remote_src: true - mode: '0444' - - - name: Copy client key - ansible.builtin.copy: - src: "{{ certificate_bundle_client_key }}" - dest: "{{ certificate_bundle_build_directory.path }}/certs/private/{{ certificate_bundle_hostname }}-client.key" - remote_src: true - mode: '0440' - - - name: Create tarball - community.general.archive: - path: "{{ certificate_bundle_build_directory.path }}/certs/*" - dest: "{{ certificate_bundle_output_directory }}/{{ certificate_bundle_hostname }}.tar.gz" - format: gz - mode: '0640' - - - name: Remove temporary directory - ansible.builtin.file: - path: "{{ certificate_bundle_build_directory.path }}" - state: absent - -- name: Report certificate bundle location - ansible.builtin.debug: - msg: "Certificate bundle created: {{ certificate_bundle_output_directory }}/{{ certificate_bundle_hostname }}.tar.gz" diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index fc88df1f1..d194c2bcc 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -2,6 +2,7 @@ certificates_source: default certificates_ca: true certificates_ca_directory: /var/lib/foremanctl/certs +certificates_oauth_directory: /var/lib/foremanctl/oauth certificates_ca_directory_certs: "{{ certificates_ca_directory }}/certs" certificates_ca_directory_keys: "{{ certificates_ca_directory }}/private" certificates_ca_directory_requests: "{{ certificates_ca_directory }}/requests" diff --git a/src/roles/certificates/tasks/extract.yml b/src/roles/certificates/tasks/extract.yml index 65efdaa5a..d2938ff73 100644 --- a/src/roles/certificates/tasks/extract.yml +++ b/src/roles/certificates/tasks/extract.yml @@ -1,17 +1,30 @@ --- -- name: Check path to certificate tar file exists +- name: Check path to auth bundle tar file exists ansible.builtin.stat: - path: "{{ certificates_bundle }}" + path: "{{ auth_bundle }}" register: _certificates_tar_file_path delegate_to: localhost become: false -- name: Fail if path to certificate tar file does not exist +- name: Fail if path to auth bundle tar file does not exist ansible.builtin.fail: - msg: "Path to certificate tar file not found: {{ certificates_bundle }}" + msg: "Path to auth bundle tar file not found: {{ auth_bundle }}" when: not _certificates_tar_file_path.stat.exists -- name: Extract certificate bundle +- name: Extract certificates from bundle ansible.builtin.unarchive: - src: "{{ certificates_bundle }}" + src: "{{ auth_bundle }}" dest: "{{ certificates_ca_directory }}" + exclude: + - oauth + - oauth/* + +- name: Extract OAuth credentials from bundle + ansible.builtin.unarchive: + src: "{{ auth_bundle }}" + dest: "{{ certificates_oauth_directory | dirname }}" + exclude: + - certs + - certs/* + - private + - private/* diff --git a/src/roles/certificates/tasks/main.yml b/src/roles/certificates/tasks/main.yml index ac793d261..dd1e86eb4 100644 --- a/src/roles/certificates/tasks/main.yml +++ b/src/roles/certificates/tasks/main.yml @@ -11,6 +11,12 @@ state: directory mode: '0755' +- name: 'Create OAuth directory' + ansible.builtin.file: + path: "{{ certificates_oauth_directory }}" + state: directory + mode: '0755' + - name: 'Create certs directory' ansible.builtin.file: path: "{{ certificates_output_directory_certs }}" @@ -31,7 +37,7 @@ - name: 'Generate certificates' when: - - certificates_bundle is not defined + - auth_bundle is not defined - certificates_generate block: - name: 'Generate CA certificate' @@ -59,8 +65,8 @@ - name: 'Consume certs' when: - - certificates_bundle is defined + - auth_bundle is defined - not certificates_generate block: - - name: Extract proxy certificate bundle + - name: Extract proxy auth bundle ansible.builtin.include_tasks: extract.yml diff --git a/src/roles/oauth_from_bundle/defaults/main.yml b/src/roles/oauth_from_bundle/defaults/main.yml new file mode 100644 index 000000000..03f52be2f --- /dev/null +++ b/src/roles/oauth_from_bundle/defaults/main.yml @@ -0,0 +1,2 @@ +--- +oauth_from_bundle_directory: "{{ certificates_oauth_directory }}" diff --git a/src/roles/oauth_from_bundle/tasks/credential.yml b/src/roles/oauth_from_bundle/tasks/credential.yml new file mode 100644 index 000000000..713cc3e1e --- /dev/null +++ b/src/roles/oauth_from_bundle/tasks/credential.yml @@ -0,0 +1,27 @@ +--- +- name: Load OAuth credential from auth bundle - {{ oauth_from_bundle_credential }} + vars: + oauth_from_bundle_path: "{{ oauth_from_bundle_directory }}/foreman-oauth-consumer-{{ oauth_from_bundle_credential }}" + block: + - name: Check for OAuth consumer file in auth bundle - {{ oauth_from_bundle_credential }} + ansible.builtin.stat: + path: "{{ oauth_from_bundle_path }}" + register: oauth_from_bundle_file + + - name: Fail when OAuth consumer is missing from auth bundle - {{ oauth_from_bundle_credential }} + ansible.builtin.fail: + msg: >- + OAuth consumer {{ oauth_from_bundle_credential }} not found in auth bundle + at {{ oauth_from_bundle_path }}. + Regenerate the bundle with foremanctl auth-bundle. + when: not oauth_from_bundle_file.stat.exists + + - name: Slurp OAuth consumer - {{ oauth_from_bundle_credential }} + ansible.builtin.slurp: + src: "{{ oauth_from_bundle_path }}" + register: oauth_from_bundle_content + + - name: Set OAuth consumer from auth bundle - {{ oauth_from_bundle_credential }} + ansible.builtin.set_fact: + "foreman_proxy_oauth_consumer_{{ oauth_from_bundle_credential }}": "{{ oauth_from_bundle_content.content | b64decode | trim }}" + "pulp_foreman_oauth_consumer_{{ oauth_from_bundle_credential }}": "{{ oauth_from_bundle_content.content | b64decode | trim }}" diff --git a/src/roles/oauth_from_bundle/tasks/main.yml b/src/roles/oauth_from_bundle/tasks/main.yml new file mode 100644 index 000000000..a4780c54b --- /dev/null +++ b/src/roles/oauth_from_bundle/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- name: Load OAuth credentials from auth bundle + ansible.builtin.include_tasks: credential.yml + loop: + - key + - secret + loop_control: + loop_var: oauth_from_bundle_credential diff --git a/src/vars/base.yaml b/src/vars/base.yaml index 27211fd0c..2f99435ca 100644 --- a/src/vars/base.yaml +++ b/src/vars/base.yaml @@ -3,10 +3,13 @@ certificates_hostnames: - "{{ ansible_facts['fqdn'] }}" - localhost +oauth_directory: "{{ obsah_state_path }}/oauth" + certificates_ca_password_file: "{{ obsah_state_path }}/certificates-ca-password" certificates_ca_password: "{{ lookup('ansible.builtin.password', certificates_ca_password_file, chars=['ascii_letters', 'digits']) }}" candlepin_oauth_secret_file: "{{ obsah_state_path }}/candlepin-oauth-secret" candlepin_oauth_secret: "{{ lookup('ansible.builtin.password', candlepin_oauth_secret_file, chars=['ascii_letters', 'digits'], length=32) }}" +certificates_oauth_directory: /var/lib/foremanctl/oauth candlepin_ca_key: "{{ ca_key }}" candlepin_ca_certificate: "{{ ca_certificate }}" diff --git a/src/vars/foreman.yml b/src/vars/foreman.yml index e2326929c..ac1429c83 100644 --- a/src/vars/foreman.yml +++ b/src/vars/foreman.yml @@ -5,9 +5,9 @@ foreman_initial_admin_password: "{{ lookup('ansible.builtin.password', foreman_a foreman_initial_organization: "Default Organization" foreman_initial_location: "Default Location" -foreman_oauth_consumer_key_file: "{{ obsah_state_path }}/foreman-oauth-consumer-key" +foreman_oauth_consumer_key_file: "{{ oauth_directory }}/foreman-oauth-consumer-key" foreman_oauth_consumer_key: "{{ lookup('ansible.builtin.password', foreman_oauth_consumer_key_file, chars=['ascii_letters', 'digits'], length=32) }}" -foreman_oauth_consumer_secret_file: "{{ obsah_state_path }}/foreman-oauth-consumer-secret" +foreman_oauth_consumer_secret_file: "{{ oauth_directory }}/foreman-oauth-consumer-secret" foreman_oauth_consumer_secret: "{{ lookup('ansible.builtin.password', foreman_oauth_consumer_secret_file, chars=['ascii_letters', 'digits'], length=32) }}" foreman_encryption_key_file: "{{ obsah_state_path }}/foreman-encryption-key" foreman_encryption_key: "{{ lookup('ansible.builtin.password', foreman_encryption_key_file, chars=['digits', 'abcdef'], length=32) }}" diff --git a/tests/certificate_bundle_test.py b/tests/auth_bundle_test.py similarity index 89% rename from tests/certificate_bundle_test.py rename to tests/auth_bundle_test.py index 5a2d3cfd7..cfd53b817 100644 --- a/tests/certificate_bundle_test.py +++ b/tests/auth_bundle_test.py @@ -24,6 +24,11 @@ f'private/{HOSTNAME}-client.key', ] +EXPECTED_OAUTH_FILES = [ + 'oauth/foreman-oauth-consumer-key', + 'oauth/foreman-oauth-consumer-secret', +] + @pytest.fixture(scope="module") def generate_custom_proxy_certs(server, certificate_source): @@ -39,7 +44,7 @@ def generate_custom_proxy_certs(server, certificate_source): @pytest.fixture(scope="module") def generate_bundle(server, certificate_source, generate_custom_proxy_certs): - command = ['./foremanctl', 'certificate-bundle'] + command = ['./foremanctl', 'auth-bundle'] if certificate_source == 'custom_server': command.extend([ '--certificate-server-certificate', f'/root/custom-certificates/certs/{HOSTNAME}.crt', @@ -48,7 +53,7 @@ def generate_bundle(server, certificate_source, generate_custom_proxy_certs): command.append(HOSTNAME) result = subprocess.run(command, capture_output=True, text=True) - assert result.returncode == 0, f'certificate-bundle failed: {result.stdout}\n{result.stderr}' + assert result.returncode == 0, f'auth-bundle failed: {result.stdout}\n{result.stderr}' @pytest.fixture(scope="module") @@ -77,6 +82,11 @@ def test_tarball_contains_client_certificate(tarball_members, expected_file): assert expected_file in tarball_members +@pytest.mark.parametrize("expected_file", EXPECTED_OAUTH_FILES) +def test_tarball_contains_oauth_credentials(tarball_members, expected_file): + assert expected_file in tarball_members + + def test_proxy_certs_stored_in_hosts_subdirectory(server, generate_bundle): """Verify proxy certificates are stored in a per-host subdirectory.""" proxy_cert = server.file(f'/var/lib/foremanctl/certs/hosts/{HOSTNAME}/certs/{HOSTNAME}.crt') diff --git a/tests/flavor/foreman-proxy-content/oauth_test.py b/tests/flavor/foreman-proxy-content/oauth_test.py new file mode 100644 index 000000000..977f072db --- /dev/null +++ b/tests/flavor/foreman-proxy-content/oauth_test.py @@ -0,0 +1,18 @@ +import pytest + +OAUTH_FILES = [ + 'foreman-oauth-consumer-key', + 'foreman-oauth-consumer-secret', +] + +OAUTH_DIR = '/var/lib/foremanctl/oauth' + + +@pytest.mark.parametrize("oauth_file", OAUTH_FILES) +def test_oauth_credentials_extracted_from_bundle(server, oauth_file): + """Proxy deploy must extract OAuth credentials from the auth bundle.""" + oauth_path = f'{OAUTH_DIR}/{oauth_file}' + oauth = server.file(oauth_path) + assert oauth.exists, f'{oauth_path} was not extracted from the auth bundle' + assert oauth.size > 0 + assert oauth.mode == 0o440, f'{oauth_path} should have mode 0440, got {oct(oauth.mode)}'