AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Zero Trust Security: Implement Zero Trust Architecture for Enterprise Infrastructure

By Luca Berton · Published 2024-01-01 · Category: troubleshooting

Implement Zero Trust security architecture with Ansible. Automate micro-segmentation, identity-based access, certificate management, and continuous.

Introduction

Zero Trust assumes no implicit trust — every request must be authenticated, authorized, and encrypted regardless of network location. Implementing Zero Trust manually across hundreds of servers is impractical. Ansible automates the core pillars of Zero Trust: identity verification, micro-segmentation, certificate-based authentication, and continuous compliance checking.

See also: HashiCorp Vault Integration with Ansible Automation Platform: Credential Management at Scale

Zero Trust Pillars with Ansible

┌──────────────────────────────────────────────────┐
│                 Zero Trust Architecture           │
├──────────────┬───────────────┬───────────────────┤
│  Identity    │  Network      │  Data             │
│  Verification│  Segmentation │  Protection       │
├──────────────┼───────────────┼───────────────────┤
│ SSH certs    │ iptables/nft  │ Encryption at rest│
│ mTLS         │ Security grps │ TLS everywhere    │
│ Short-lived  │ Micro-perimeter│ Vault secrets    │
│ credentials  │ Service mesh  │ Key rotation      │
└──────────────┴───────────────┴───────────────────┘
        │               │               │
        └───────────────┼───────────────┘
                        │
              Continuous Verification
              (Ansible scheduled scans)

SSH Certificate Authority

Replace static SSH keys with short-lived certificates signed by a CA:

---
- name: Configure SSH Certificate Authority
  hosts: localhost
  connection: local
  tasks:
    - name: Generate CA key pair
      community.crypto.openssh_keypair:
        path: /etc/ssh/ca/ssh_ca
        type: ed25519
        comment: "SSH CA - {{ ansible_date_time.date }}"
      run_once: true

- name: Configure hosts to trust CA
  hosts: all
  become: true
  tasks:
    - name: Copy CA public key
      ansible.builtin.copy:
        src: /etc/ssh/ca/ssh_ca.pub
        dest: /etc/ssh/trusted_ca.pub
        mode: '0644'

    - name: Configure sshd to trust CA
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        line: "TrustedUserCAKeys /etc/ssh/trusted_ca.pub"
        regexp: "^TrustedUserCAKeys"
      notify: restart sshd

    - name: Set certificate max validity
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        line: "AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u"
        regexp: "^AuthorizedPrincipalsFile"
      notify: restart sshd

    - name: Create principals for users
      ansible.builtin.copy:
        content: |
          deploy
          admin
        dest: "/etc/ssh/auth_principals/{{ item }}"
        mode: '0644'
      loop:
        - deploy
        - ubuntu
        - root

- name: Issue short-lived user certificates
  hosts: localhost
  tasks:
    - name: Sign user key (8-hour validity)
      ansible.builtin.command: >
        ssh-keygen -s /etc/ssh/ca/ssh_ca
        -I "{{ user_name }}@{{ ansible_date_time.iso8601 }}"
        -n deploy
        -V +8h
        {{ user_public_key_path }}
      changed_when: true

See also: Ansible Secrets Management: Best Practices for Enterprise Credential Security

mTLS Everywhere

Deploy Internal CA and Certificates

- name: Deploy mTLS infrastructure
  hosts: all
  become: true
  vars:
    ca_cert_path: /etc/ssl/internal-ca
    cert_validity: 90  # days
  tasks:
    - name: Create certificate directory
      ansible.builtin.file:
        path: "{{ ca_cert_path }}"
        state: directory
        mode: '0755'

    - name: Generate server private key
      community.crypto.openssl_privatekey:
        path: "{{ ca_cert_path }}/{{ inventory_hostname }}.key"
        size: 4096

    - name: Generate CSR
      community.crypto.openssl_csr:
        path: "{{ ca_cert_path }}/{{ inventory_hostname }}.csr"
        privatekey_path: "{{ ca_cert_path }}/{{ inventory_hostname }}.key"
        common_name: "{{ inventory_hostname }}"
        subject_alt_name:
          - "DNS:{{ inventory_hostname }}"
          - "DNS:{{ inventory_hostname }}.{{ domain }}"
          - "IP:{{ ansible_default_ipv4.address }}"

    - name: Sign certificate with internal CA
      community.crypto.x509_certificate:
        path: "{{ ca_cert_path }}/{{ inventory_hostname }}.crt"
        csr_path: "{{ ca_cert_path }}/{{ inventory_hostname }}.csr"
        ownca_path: "{{ ca_cert_path }}/ca.crt"
        ownca_privatekey_path: "{{ ca_cert_path }}/ca.key"
        ownca_not_after: "+{{ cert_validity }}d"
        provider: ownca

    - name: Deploy CA trust bundle
      ansible.builtin.copy:
        src: files/internal-ca.crt
        dest: /etc/ssl/certs/internal-ca.crt
      notify: update ca trust

  handlers:
    - name: update ca trust
      ansible.builtin.command: update-ca-certificates

Configure Services for mTLS

    - name: Configure Nginx with mTLS
      ansible.builtin.template:
        src: nginx-mtls.conf.j2
        dest: /etc/nginx/sites-enabled/app.conf
      notify: reload nginx
# templates/nginx-mtls.conf.j2
server {
    listen 443 ssl;
    server_name {{ inventory_hostname }};

    ssl_certificate     {{ ca_cert_path }}/{{ inventory_hostname }}.crt;
    ssl_certificate_key {{ ca_cert_path }}/{{ inventory_hostname }}.key;
    ssl_client_certificate {{ ca_cert_path }}/ca.crt;
    ssl_verify_client on;  # Require client certificate

    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers on;
}

Micro-Segmentation

Host-Based Firewall (nftables)

- name: Implement micro-segmentation
  hosts: all
  become: true
  tasks:
    - name: Deploy nftables rules
      ansible.builtin.template:
        src: nftables.conf.j2
        dest: /etc/nftables.conf
        mode: '0600'
      notify: reload nftables

    - name: Enable nftables
      ansible.builtin.systemd:
        name: nftables
        state: started
        enabled: true
# templates/nftables.conf.j2
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Allow established connections
        ct state established,related accept
        ct state invalid drop

        # Allow loopback
        iifname "lo" accept

        # Allow SSH only from bastion
{% for bastion in groups['bastion'] %}
        ip saddr {{ hostvars[bastion].ansible_host }} tcp dport 22 accept
{% endfor %}

        # Allow monitoring from Prometheus
{% for mon in groups['monitoring'] %}
        ip saddr {{ hostvars[mon].ansible_host }} tcp dport 9100 accept
{% endfor %}

{% if 'webservers' in group_names %}
        # Web servers: allow HTTPS from load balancers only
{% for lb in groups['loadbalancers'] %}
        ip saddr {{ hostvars[lb].ansible_host }} tcp dport 443 accept
{% endfor %}
{% endif %}

{% if 'databases' in group_names %}
        # Databases: allow from app servers only
{% for app in groups['webservers'] %}
        ip saddr {{ hostvars[app].ansible_host }} tcp dport 5432 accept
{% endfor %}
{% endif %}

        # Log and drop everything else
        log prefix "nftables-drop: " counter drop
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

See also: Ansible Container Security: Image Scanning, Runtime Protection, and Supply Chain Security

Continuous Verification

Security Compliance Scan

- name: Zero Trust compliance verification
  hosts: all
  become: true
  tasks:
    - name: Verify no password authentication
      ansible.builtin.command: grep "^PasswordAuthentication no" /etc/ssh/sshd_config
      register: ssh_nopass
      changed_when: false
      failed_when: ssh_nopass.rc != 0

    - name: Verify TLS certificates not expired
      community.crypto.x509_certificate_info:
        path: "{{ ca_cert_path }}/{{ inventory_hostname }}.crt"
      register: cert_info

    - name: Alert on expiring certificates
      ansible.builtin.debug:
        msg: "WARNING: Certificate expires in {{ cert_info.not_after }} - {{ inventory_hostname }}"
      when: cert_info.not_after | to_datetime('%Y%m%d%H%M%SZ') < (now() + timedelta(days=30))

    - name: Verify firewall is active
      ansible.builtin.systemd:
        name: nftables
      register: fw_status

    - name: Assert firewall running
      ansible.builtin.assert:
        that: fw_status.status.ActiveState == 'active'
        fail_msg: "CRITICAL: Firewall not running on {{ inventory_hostname }}"

    - name: Check for unauthorized SSH keys
      ansible.builtin.find:
        paths: /root/.ssh/
        patterns: "authorized_keys*"
      register: ssh_keys

    - name: Verify no static root SSH keys
      ansible.builtin.assert:
        that: ssh_keys.matched == 0
        fail_msg: "Static SSH keys found on {{ inventory_hostname }} — should use CA certs"

    - name: Check listening ports
      ansible.builtin.shell: ss -tlnp | awk '{print $4}' | grep -v '^Local'
      register: open_ports
      changed_when: false

    - name: Report unexpected ports
      ansible.builtin.debug:
        msg: "Unexpected ports on {{ inventory_hostname }}: {{ open_ports.stdout_lines }}"
      when: open_ports.stdout_lines | length > expected_port_count | default(5)

Certificate Rotation

- name: Rotate certificates approaching expiry
  hosts: all
  become: true
  tasks:
    - name: Check certificate expiry
      community.crypto.x509_certificate_info:
        path: "{{ ca_cert_path }}/{{ inventory_hostname }}.crt"
      register: cert_info

    - name: Regenerate if expiring within 30 days
      block:
        - name: Generate new key
          community.crypto.openssl_privatekey:
            path: "{{ ca_cert_path }}/{{ inventory_hostname }}.key"
            size: 4096
            force: true

        - name: Generate new CSR
          community.crypto.openssl_csr:
            path: "{{ ca_cert_path }}/{{ inventory_hostname }}.csr"
            privatekey_path: "{{ ca_cert_path }}/{{ inventory_hostname }}.key"
            common_name: "{{ inventory_hostname }}"
            subject_alt_name:
              - "DNS:{{ inventory_hostname }}"
              - "IP:{{ ansible_default_ipv4.address }}"

        - name: Sign new certificate
          community.crypto.x509_certificate:
            path: "{{ ca_cert_path }}/{{ inventory_hostname }}.crt"
            csr_path: "{{ ca_cert_path }}/{{ inventory_hostname }}.csr"
            ownca_path: "{{ ca_cert_path }}/ca.crt"
            ownca_privatekey_path: "{{ ca_cert_path }}/ca.key"
            ownca_not_after: "+90d"
            provider: ownca
          notify: reload services
      when: >
        cert_info.not_after | to_datetime('%Y%m%d%H%M%SZ')
        < (now() + timedelta(days=30))

Best Practices

  1. Short-lived credentials — SSH certs (8h), TLS certs (90d), API tokens (1h)
  2. No static keys — Replace authorized_keys with CA-signed certificates
  3. Default deny — Firewall drops everything not explicitly allowed
  4. Service-to-service mTLS — Every internal connection authenticated with certificates
  5. Continuous scanning — Schedule compliance checks in AAP (daily minimum)
  6. Automate rotation — Certificate and credential rotation should be automated, not manual
  7. Log everything — Firewall drops, certificate operations, access attempts
  8. Least privilege — Each service gets only the network access it needs

FAQ

Zero Trust with legacy systems?

Start with firewall micro-segmentation (doesn't require app changes). Add mTLS where possible. Use jump hosts/bastion for systems that can't support modern auth.

How to handle certificate revocation?

Use short-lived certificates (hours/days) instead of long-lived + CRL. If certificates expire faster than your detection time, revocation becomes unnecessary.

Performance impact of mTLS?

Modern TLS 1.3 handshakes add ~1ms latency. For internal services, this is negligible. The security benefit far outweighs the cost.

Conclusion

Ansible makes Zero Trust achievable at scale by automating the hardest parts — certificate lifecycle, firewall rules, compliance verification, and credential rotation. Instead of a one-time security project, Zero Trust becomes a continuously enforced baseline across your entire infrastructure.

Category: troubleshooting

Browse all Ansible tutorials · AnsiblePilot Home