Home Technical Support Hardening Linux and Windows with Ansible in a CI/CD Pipeline

Hardening Linux and Windows with Ansible in a CI/CD Pipeline

Last updated on Jan 06, 2026

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 (including changed). 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: true only for non‑critical hardening steps, and capture the result with register so 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

  1. Checkout code – Pull the repository containing the hardening playbooks.

  2. Prepare credentials – Export SSH private key or WinRM password from secret storage.

  3. Run inventory discovery – Dynamically generate an Ansible inventory (e.g., from Terraform output).

  4. Execute hardening playbookansible-playbook -i inventory.yml site.yml.

  5. 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 --diff before committing.

  • Enable Ansible’s --flush-cache in 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_hosts verification) 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!