Hardening Linux and Windows with Ansible in a CI/CD Pipeline
Learn how to integrate security‑hardening playbooks into your CI/CD workflow, understand when builds should fail, and master authentication for both Linux and Windows targets.
Introduction
Automating security hardening is a cornerstone of modern DevSecOps. By using Ansible you can apply consistent, repeatable configurations to Linux and Windows hosts directly from your CI/CD pipeline. This article explains:
-
When a pipeline should be marked as failed based on Ansible results.
-
Why private keys are (or aren’t) required in lab environments.
-
How to manage SSH/WinRM authentication and the role of
known_hosts. -
A step‑by‑step example of wiring hardening playbooks into a typical CI/CD job.
Whether you are preparing for a DevSecOps certification or simply want to tighten your production environment, the concepts below will help you build a reliable, secure automation flow.
1. When Should the CI/CD Pipeline Fail?
1.1 Ansible task outcomes
| Outcome | Description | Impact on pipeline |
|---|---|---|
ok |
Task executed successfully, no changes needed. | Build continues. |
changed |
Task applied a change (e.g., updated a permission). | Build continues – change is expected. |
failed |
Task could not complete (e.g., permission denied, missing file). | Pipeline fails if the exit code propagates. |
skipped |
Condition not met (e.g., OS‑specific task on the wrong platform). | Build continues. |
unreachable |
Host could not be contacted (SSH/WinRM error). | Pipeline fails – no way to enforce hardening. |
1.2 How Ansible exit codes affect CI/CD
-
Exit code
0– All tasks completed (includingchanged). CI/CD treats the job as successful. -
Exit code
2– At least one task failed. Most CI runners (GitLab, GitHub Actions, Azure Pipelines) mark the step as failed. -
Exit code
1– Generic error (e.g., syntax error in the playbook). Also fails the job.
Tip: Use
ignore_errors: trueonly for non‑critical hardening steps, and capture the result withregisterso you can decide later whether to abort the pipeline.
1.3 Practical example
- name: Enforce secure file permissions
file:
path: /etc/ssh/sshd_config
mode: '0600'
owner: root
group: root
become: true
register: perm_result
failed_when: perm_result is failed
If the file module cannot set the mode (perhaps because the file is locked), Ansible returns a non‑zero exit code, causing the CI job to fail automatically.
2. Authentication in Lab vs. Production Environments
2.1 Why labs often omit a private key
-
Pre‑provisioned credentials – Lab environments typically expose a default SSH key pair on the build agent, allowing password‑less access to the target VMs.
-
Security sandbox – Since the lab is isolated, the risk of key leakage is minimal, so the instructor can skip the step of uploading a personal private key.
Remember: In real projects you should never embed private keys in the repository. Use secret managers (HashiCorp Vault, Azure Key Vault, GitHub Secrets) and inject them at runtime.
2.2 Adding your own public key to known_hosts
When you connect to a new host for the first time, SSH performs a host key verification. Adding the server’s fingerprint to ~/.ssh/known_hosts (via ssh-keyscan) prevents interactive prompts and protects against man‑in‑the‑middle attacks.
ssh-keyscan -t rsa devsecops-box-p29i9pmx >> ~/.ssh/known_hosts
-
The command fetches the RSA host key and appends it to the local trust store.
-
After this step, Ansible can run non‑interactive SSH commands safely.
2.3 Windows authentication with WinRM
-
Kerberos or NTLM – Most CI runners use a service account with a password stored as a secret.
-
Certificate‑based auth – For higher security, configure WinRM to accept client certificates and store the cert in the pipeline’s secret store.
Reference implementations:
3. Integrating Hardening Playbooks into CI/CD
3.1 High‑level workflow
-
Checkout code – Pull the repository containing the hardening playbooks.
-
Prepare credentials – Export SSH private key or WinRM password from secret storage.
-
Run inventory discovery – Dynamically generate an Ansible inventory (e.g., from Terraform output).
-
Execute hardening playbook –
ansible-playbook -i inventory.yml site.yml. -
Evaluate exit code – CI runner automatically fails the job on non‑zero codes.
3.2 Sample GitHub Actions job
name: Hardening CI
on:
push:
branches: [ main ]
jobs:
harden:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up SSH key
run: |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -t rsa ${{ secrets.TARGET_HOST }} >> ~/.ssh/known_hosts
- name: Install Ansible
run: sudo apt-get update && sudo apt-get install -y ansible
- name: Run Linux hardening
run: |
ansible-playbook -i inventory.yml linux-hardening.yml
If any task in linux-hardening.yml fails, the job stops with a red X, providing immediate feedback to developers.
4. Common Questions & Tips
Q1: Will a “changed” task cause the pipeline to fail?
A: No. “changed” is a normal part of idempotent automation. Only a non‑zero exit code (failed/unreachable) triggers a failure.
Q2: Can I ignore a specific hardening failure without breaking the whole pipeline?
A: Use ignore_errors: true on the task and evaluate the result later. Example:
- name: Ensure auditd is installed
package:
name: auditd
state: present
ignore_errors: true
register: auditd_res
Q3: Do I need to add every host to known_hosts?
A: For static inventories, yes, once per host. For dynamic inventories, you can automate the ssh-keyscan step inside the pipeline.
Q4: How do I handle Windows hosts that require self‑signed certificates?
A: Export the certificate, store it as a secret, and configure WinRM with ansible_winrm_transport=certificate. See the ansible-collection-hardening docs for a ready‑made role.
Tips for a Smooth Experience
-
Validate playbooks locally with
ansible-playbook --check --diffbefore committing. -
Enable Ansible’s
--flush-cachein CI to avoid stale facts. -
Log the full Ansible output (
-vvv) to aid debugging when a build fails. -
Separate “audit” and “remediate” runs – first run a read‑only audit playbook, then conditionally trigger the hardening playbook only if drift is detected.
5. Summary
Hardening Linux and Windows systems with Ansible fits naturally into a CI/CD pipeline:
-
Failure detection relies on Ansible’s exit codes; any task that cannot apply a security control will cause the build to fail.
-
Authentication differs by OS—SSH for Linux (with
known_hostsverification) and WinRM for Windows (password or certificate). -
Lab environments often simplify credential handling, but production pipelines must use secret management and host verification.
By following the workflow and best‑practice tips outlined above, you can deliver continuously hardened infrastructure, catch compliance gaps early, and maintain a secure DevSecOps posture. Happy automating!