Skip to content

Commit 0ffe0c1

Browse files
committed
Use community.crypto for the certificates role
* Use the community.crypto module * Fix re-generating certs * --reset-certificate-cname
1 parent e3b9278 commit 0ffe0c1

8 files changed

Lines changed: 318 additions & 173 deletions

File tree

‎.claude/settings.local.json‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(./foremanctl --help)",
5+
"Bash(./forge --help)",
6+
"Bash(./setup-environment)",
7+
"Bash(./forge vms)",
8+
"Bash(./foremanctl deploy)"
9+
]
10+
}
11+
}

‎CLAUDE.md‎

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
foremanctl is a deployment tool for Foreman and Katello using Podman quadlets and Ansible. It targets Foreman 3.18 with Katello 4.20, Pulp 3.85, and Candlepin 4.6 on CentOS Stream 9 with ansible-core 2.14.
8+
9+
The project uses the [obsah](https://pypi.org/project/obsah/) CLI framework. Two main executables wrap obsah:
10+
11+
- `./foremanctl` - production deployment tool (uses src/ directory)
12+
- `./forge` - development/testing tool (uses development/ directory)
13+
14+
## Setup
15+
16+
```bash
17+
./setup-environment # Creates virtualenv and installs dependencies
18+
source .venv/bin/activate # Activate the virtualenv
19+
```
20+
21+
This installs dependencies from `src/requirements.txt` and `development/requirements.txt`, and builds Ansible collections in `build/collections/`.
22+
23+
## Common Commands
24+
25+
### Deployment (Development)
26+
27+
```bash
28+
./forge vms start # Start Vagrant VMs
29+
./foremanctl deploy --foreman-initial-admin-password=changeme --tuning development # Deploy Foreman
30+
./forge vms stop # Stop VMs
31+
```
32+
33+
### Optional Features
34+
35+
```bash
36+
./forge setup-repositories # Setup repositories
37+
./foremanctl deploy --add-feature hammer # Deploy with hammer CLI
38+
./foremanctl features # List enabled and available features
39+
```
40+
41+
### Testing
42+
43+
```bash
44+
./forge test # Run pytest-based integration tests (requires deployment)
45+
./forge smoker # Run smoker-based tests
46+
```
47+
48+
Test execution generates SSH config at `.tmp/ssh-config` and runs pytest with testinfra. Tests require an active deployment. Hammer tests require hammer to be deployed.
49+
50+
To run specific tests or with custom pytest arguments, use:
51+
52+
```bash
53+
./forge test -- -k test_name # Pass additional pytest arguments after --
54+
```
55+
56+
### Linting
57+
58+
```bash
59+
ansible-lint src/ # Lint production playbooks/roles
60+
ansible-lint development/ # Lint development playbooks/roles
61+
```
62+
63+
Configuration is in `.ansible-lint`.
64+
65+
### Building
66+
67+
```bash
68+
make dist # Create distribution tarball (foremanctl-VERSION.tar.gz)
69+
make clean-var # Clean .var directory
70+
```
71+
72+
## Architecture
73+
74+
### Directory Structure
75+
76+
- **src/** - Production Ansible content
77+
- **playbooks/** - Main playbooks organized by action (deploy, checks, pull-images, features, setup-*)
78+
- **roles/** - Ansible roles for each component (foreman, pulp, candlepin, postgresql, httpd, hammer, certificates, etc.)
79+
- **vars/** - Configuration variables
80+
- **flavors/** - Feature sets (e.g., katello.yml defines enabled features)
81+
- **tuning/** - Resource tuning profiles (development, default, medium, large, extra-large, extra-extra-large)
82+
- **base.yaml**, **database.yml**, **foreman.yml**, **images.yml** - Base configuration
83+
- **default_certificates.yml**, **installer_certificates.yml** - Certificate sources
84+
- **features.yaml** - Feature definitions with dependencies
85+
- **filter_plugins/**, **callback_plugins/** - Ansible plugins
86+
87+
- **development/** - Development-specific Ansible content
88+
- **playbooks/** - Development playbooks (vms, test, smoker, setup-repositories, etc.)
89+
- **roles/** - Development-specific roles
90+
91+
- **tests/** - pytest-based integration tests
92+
- Uses testinfra for infrastructure testing
93+
- Uses apypie for Foreman API testing
94+
- **conftest.py** - pytest fixtures for server, client, database connections, and Foreman API resources
95+
96+
- **inventories/** - Ansible inventory files
97+
- **broker.py** - Dynamic inventory script
98+
99+
- **build/collections/** - Built Ansible collections (generated, not committed)
100+
- **foremanctl/** - Production collections
101+
- **forge/** - Development collections
102+
103+
### Core Components
104+
105+
Each Ansible role manages a specific service/component:
106+
107+
- **foreman** - Main Foreman application
108+
- **pulp** - Content management (RPM, Deb, Container, File repositories)
109+
- **candlepin** - Subscription management
110+
- **postgresql** - Database (internal mode only)
111+
- **httpd** - Web server/reverse proxy
112+
- **redis** - Cache
113+
- **foreman_proxy** - Smart proxy (optional feature)
114+
- **hammer** - CLI tool (optional feature)
115+
- **certificates** - SSL/TLS certificate management
116+
117+
### Configuration as Podman Secrets
118+
119+
Service configuration files are stored as Podman secrets and mounted into containers.
120+
121+
Naming convention:
122+
123+
- Config files: `<role_namespace>-<filename>-<extension>` or `<role_namespace>-<app>-<filename>-<extension>`
124+
- Strings: `<role_namespace>-<descriptive_name>` or `<role_namespace>-<app>-<descriptive_name>`
125+
126+
Required labels:
127+
128+
- Config files: `filename`, `app`
129+
- Strings: `app`
130+
131+
Commands:
132+
133+
```bash
134+
podman secret ls # List secrets
135+
podman secret inspect --showsecret --format "{{.SecretData}}" <name> # View secret content
136+
```
137+
138+
### Features System
139+
140+
Features are defined in `src/features.yaml` and control which components are deployed. Each feature can specify:
141+
142+
- **description** - Human-readable description
143+
- **foreman** - Foreman plugin configuration
144+
- **foreman_proxy** - Proxy plugin configuration
145+
- **hammer** - Hammer CLI plugin
146+
- **dependencies** - Required features
147+
- **internal** - Whether feature is user-facing or internal-only
148+
149+
Flavors (in `src/vars/flavors/`) define preset feature sets. The katello flavor includes foreman, katello, and content management features (rpm, deb, container, ansible).
150+
151+
### Deploy Playbook Flow
152+
153+
The main deployment playbook (`src/playbooks/deploy/deploy.yaml`) executes roles in order:
154+
155+
1. pre_install - Pre-installation tasks
156+
2. checks - System requirement validation
157+
3. certificates - Generate/configure certificates (if certificate_source == 'default')
158+
4. certificate_checks - Validate certificates
159+
5. postgresql - Database setup (if database_mode == 'internal')
160+
6. redis - Redis cache
161+
7. candlepin - Subscription management
162+
8. httpd - Web server
163+
9. pulp - Content management
164+
10. foreman - Main application
165+
11. systemd_target - Systemd target for service management
166+
12. foreman_proxy - Smart proxy (if 'foreman-proxy' in enabled_features)
167+
13. hammer - CLI (if 'hammer' in enabled_features)
168+
14. post_install - Post-installation tasks
169+
170+
### Test Architecture
171+
172+
Tests use pytest with fixtures defined in `tests/conftest.py`:
173+
174+
- **server**, **client**, **database** - testinfra hosts via paramiko SSH
175+
- **foremanapi** - apypie ForemanApi client
176+
- **organization**, **product**, **yum_repository**, **file_repository**, **container_repository** - Foreman API resources (auto-created/cleaned)
177+
- **lifecycle_environment**, **content_view**, **activation_key** - Content management fixtures
178+
179+
Tests connect to VMs using SSH config generated at `.tmp/ssh-config` from Ansible inventory.
180+
181+
## Development Notes
182+
183+
### obsah Framework
184+
185+
Both `foremanctl` and `forge` are bash wrappers that configure environment variables and exec `obsah`. Key environment variables:
186+
187+
- `OBSAH_NAME` - Tool name
188+
- `OBSAH_DATA` - Playbook directory (src/ or development/)
189+
- `OBSAH_INVENTORY` - Inventory directory
190+
- `OBSAH_STATE` - State directory (.var/lib/foremanctl)
191+
- `ANSIBLE_COLLECTIONS_PATH` - Collections path (build/collections/foremanctl or forge)
192+
- `ANSIBLE_LOG_PATH` - Log file location
193+
194+
### Vagrant Development Environment
195+
196+
The Vagrantfile defines VMs for testing. Use `./forge vms start` and `./forge vms stop` to manage them.
197+
198+
### Running Single Tests
199+
200+
```bash
201+
source .venv/bin/activate
202+
pytest tests/foreman_api_test.py::test_foreman_organization -vv
203+
pytest tests/ -k "repository" -vv
204+
```
205+
206+
Ensure deployment is active before running tests.

‎docs/certificates.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ foremanctl deploy \
6565

6666
When CNAMEs are specified, certificates will include all names in the Subject Alternative Name field, allowing the same certificate to be valid for multiple hostnames.
6767

68+
If certificates already exist from a previous deployment, passing `--certificate-cname` will automatically regenerate the server certificate and CSR to include the new CNAMEs. Existing private keys are reused.
69+
70+
To remove all CNAMEs from an existing certificate, use `--reset-certificate-cname`.
71+
72+
On each run the role compares the Subject Alternative Names in the existing server certificate against the desired list (hostname + any `--certificate-cname` values). If they differ, the CSR and certificate are regenerated automatically. This means both adding and removing CNAMEs are handled transparently without manual cleanup.
73+
6874
### Current Limitations
6975

7076
- Cannot provide custom certificate files during deployment
Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---
2-
- name: 'Install openssl'
2+
- name: 'Install crypto dependencies'
33
ansible.builtin.package:
4-
name: openssl
4+
name:
5+
- python3-cryptography
56
state: present
67

78
- name: 'Create certs directory'
@@ -22,31 +23,6 @@
2223
state: directory
2324
mode: '0755'
2425

25-
- name: 'Deploy configuration file'
26-
ansible.builtin.template:
27-
src: openssl.cnf.j2
28-
dest: "{{ certificates_ca_directory }}/openssl.cnf"
29-
owner: root
30-
group: root
31-
mode: '0644'
32-
33-
- name: 'Create index file'
34-
ansible.builtin.file:
35-
path: "{{ certificates_ca_directory }}/index.txt"
36-
state: touch
37-
owner: root
38-
group: root
39-
mode: '0644'
40-
41-
- name: 'Ensure serial starting number'
42-
ansible.builtin.template:
43-
src: serial.j2
44-
dest: "{{ certificates_ca_directory }}/serial"
45-
force: false
46-
owner: root
47-
group: root
48-
mode: '0644'
49-
5026
- name: 'Create CA key password file'
5127
ansible.builtin.copy:
5228
content: "{{ certificates_ca_password }}"
@@ -56,17 +32,36 @@
5632
mode: '0600'
5733
no_log: true
5834

59-
- name: 'Creating CA certificate and key'
60-
ansible.builtin.command: >
61-
openssl req -new
62-
-x509
63-
-nodes
64-
-extensions v3_ca
65-
-days 7300
66-
-config "{{ certificates_ca_directory }}/openssl.cnf"
67-
-subj "/CN=Foreman Self-signed CA"
68-
-keyout "{{ certificates_ca_directory_keys }}/ca.key"
69-
-out "{{ certificates_ca_directory_certs }}/ca.crt"
70-
-passout "file:{{ certificates_ca_directory_keys }}/ca.pwd"
71-
args:
72-
creates: "{{ certificates_ca_directory_certs }}/ca.crt"
35+
- name: 'Create CA private key'
36+
community.crypto.openssl_privatekey:
37+
path: "{{ certificates_ca_directory_keys }}/ca.key"
38+
type: RSA
39+
size: 4096
40+
passphrase: "{{ certificates_ca_password }}"
41+
owner: root
42+
group: root
43+
mode: '0600'
44+
45+
- name: 'Create CA certificate signing request'
46+
community.crypto.openssl_csr:
47+
path: "{{ certificates_ca_directory_requests }}/ca.csr"
48+
privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key"
49+
privatekey_passphrase: "{{ certificates_ca_password }}"
50+
common_name: "Foreman Self-signed CA"
51+
use_common_name_for_san: false
52+
basic_constraints:
53+
- 'CA:TRUE'
54+
basic_constraints_critical: true
55+
key_usage:
56+
- keyCertSign
57+
- cRLSign
58+
key_usage_critical: true
59+
60+
- name: 'Create self-signed CA certificate'
61+
community.crypto.x509_certificate:
62+
path: "{{ certificates_ca_directory_certs }}/ca.crt"
63+
csr_path: "{{ certificates_ca_directory_requests }}/ca.csr"
64+
privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key"
65+
privatekey_passphrase: "{{ certificates_ca_password }}"
66+
provider: selfsigned
67+
selfsigned_not_after: "+7300d"

0 commit comments

Comments
 (0)