The Ops Community

Joseph D. Marhee
Joseph D. Marhee

Posted on

Creating Internal-CA Signed Cert Services with Ansible

I use an Ansible role based approach to deploying service configurations on my testing infrastructure; most of these services that run on VMs (rather than part of my Kubernetes infrasturcture) are deployed as Docker containers, and then an Nginx proxy configuration is generated. I have two roles, one that simply creates a self-signed certificate for the service, and other that uses an internal CA to sign the certificates. I'm going to demonstrate the use of the latter in this piece.

In the top-level playbook that invokes this role, you might have a host group like this:

 - hosts:
      - cicd
   become: true
   vars:
    ca_cert_name: "rootCA"
    ca_key_path: "/etc/ssl/private/rootCA.key"
   roles:
          - { role: ca-signed-cert-service, common_name: "build.krebstar.internal", backend_addr: "http://127.0.0.1:8900" }
Enter fullscreen mode Exit fullscreen mode

Where under (amongst whatever other roles) roles for that host group, would be the ca-signed-cert-service role, and the common_name for the certificate and the backend_addr that will wind up in the Nginx proxy configuration.

The structure of the roles/ca-signed-service directory is:

files  handlers  tasks  templates
Enter fullscreen mode Exit fullscreen mode

So, in templates, we'll start with nginx.conf.j2:

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name {{ common_name }};

        ssl_certificate /etc/ssl/cert/{{ common_name }}.crt;
        ssl_certificate_key /etc/ssl/private/{{ common_name }}.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

        location / {
            proxy_pass {{ backend_addr }};
            proxy_set_header    Host            $host;
            proxy_set_header    X-Real-IP       $remote_addr;
            proxy_set_header    X-Forwarded-for $remote_addr;
            port_in_redirect off;
            proxy_connect_timeout 300;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name {{ common_name }};
    return 301 https://$host$request_uri;
}
Enter fullscreen mode Exit fullscreen mode

which Ansible, when the tasks run, will populate the template with the common_name and backend_addr you specified earlier.

In tasks/main.yml:

---
- name: Creates directory for Certificate Signing Requests, Keys, and Certfiles
  file:
    path: "/etc/ssl/{{ item }}"
    state: directory
    owner: root
    group: root
    mode: 0775
    recurse: yes
  with_items:
    - csr
    - private
    - cert
    - certs

- name: Install CA
  file:
    src: "{{ ca_cert_name }}.pem"
    dest: "/etc/ssl/certs/{{ ca_cert_name }}.pem"
  notify: Update ca-certificates
Enter fullscreen mode Exit fullscreen mode

we create the expected directories for storing the rootCA and the new csrs, keys, and certs, and then install the new CA.

We then proceed to generate a new cert signed by the new CA:

- name: Generate an OpenSSL private key for {{ common_name }}
  openssl_privatekey:
    path: /etc/ssl/private/{{ common_name }}.pem
    size: 1024

- name: Generate a Certificate Signing Request for {{common_name}}
  openssl_csr:
    path: /etc/ssl/csr/{{ common_name }}.csr
    privatekey_path: /etc/ssl/private/{{ common_name }}.pem
    common_name: "{{ common_name }}"

- name: Generate an OpenSSL certificate signed with your own CA certificate
  openssl_certificate:
    path: /etc/ssl/cert/{{ common_name }}.crt
    csr_path: /etc/ssl/csr/{{ common_name }}.csr
    ownca_path: "/etc/ssl/certs/{{ ca_cert_name }}.pem"
    ownca_privatekey_path: "{{ca_key_path}}"
    provider: ownca
Enter fullscreen mode Exit fullscreen mode

and install Nginx and template the service configuration:

- name: Install nginx Package
  apt:
    update_cache: yes
    name: nginx
    state: present
  notify: Start nginx

- name: Nginx Configuration
  file:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf

- name: Nginx Config for {{ common_name }}
  template: 
    src: nginx_conf.j2 
    dest: "/etc/nginx/sites-enabled/{{ common_name }}.conf"
  notify: Reload nginx
Enter fullscreen mode Exit fullscreen mode

You'll notice in the above there are a few instances of a notify directive, meaning that when that step runs, another command should be triggered, and this is done by notifying a handler, so for the notifications in our tasks, we'll have the following in handlers/main.yml:

---
- name: Reload nginx
  service:
    name: "nginx"
    state: reloaded

- name: Start nginx
  service:
    name: "nginx"
    state: started

- name: Kill pid
  shell: 'kill -HUP $(cat /var/run/nginx.pid)'

- name: Update ca-certificates
  shell: update-ca-certificates
Enter fullscreen mode Exit fullscreen mode

So, for example, after we install the CA cert, we would use notify: Update ca-certificates to run that update-ca-certificates command, or notify: Start nginx or Reload nginx to use the service command in Ansible to attempt to have that service state becoming started or reloaded.

Discussion (0)