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 Container Security: Image Scanning, Runtime Protection, and Supply Chain 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 Secrets Management: Best Practices for Enterprise Credential 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

Short-lived credentials — SSH certs (8h), TLS certs (90d), API tokens (1h) No static keys — Replace authorized_keys with CA-signed certificates Default deny — Firewall drops everything not explicitly allowed Service-to-service mTLS — Every internal connection authenticated with certificates Continuous scanning — Schedule compliance checks in AAP (daily minimum) Automate rotation — Certificate and credential rotation should be automated, not manual Log everything — Firewall drops, certificate operations, access attempts 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.

Related Articles

HashiCorp Vault Integration with AAPAnsible Compliance AutomationAnsible Network Automation

Category: troubleshooting

Browse all Ansible tutorials · AnsiblePilot Home