Ansible: access group vars for groups the current host is not a member of

27-01-2017 | Remy van Elst


Table of Contents


This guide shows you how to access group variables for a group the current host is not a member of. In Ansible you can access other host variables using hostvars['hostname'] but not group variables. The way described here is workable, but do I consider it a dirty hack. So why did I need this? I have a setup where ssl is offloaded by haproxy servers, but the virtual hosts and ssl configuration are defined in Apache servers. The loadbalancers and appservers are two different hostgroups, the ssl settings are in the appserver group_vars, which the hosts in the loadbalancer group need to access. The best way to do this is change the haproxy playbooks and configuration and define the certificates there, but in this specific case that wasn't a workable solution. Editing two yaml files (one for the appservers and one for the loadbalancers) was not an option in this situation.

If you like this article, consider sponsoring me by trying out a Digital Ocean VPS. With this link you'll get a $5 VPS for 2 months free (as in, you get $10 credit). (referral link)

This is applicable for other scenario's as well. In templates this can be worked around by looping over all the groups, then over all the hosts in the specific group, then if the host is the first in the loop, go over all the hostvars for that host and then access the group var you want. This stackoverflow post goes over that. In my case I needed to access it in a playbook, the looping construction woudln't work there.

The inventory

In the inventory file I created a new group. This group has the two other groups, appservers and loadbalancers in them and nothing else. Like so:

[appserver]
app1.cluster   
app2.cluster   

[loadbalancer]
lb1.cluster   
lb2.cluster   

[ssl:children]
loadbalancer
appserver

The shortnames expand to specifc SSH configuration. The ssl hostgroup effectively includes the following hosts:

  • app1
  • app2
  • lb1
  • lb2

However, we do need to modify the playbook that modifies the vhosts to only place the certificates on the loadbalancers, and do nothing with the certificates on the appservers. The playbook should still do it's normal thing, configuring the vhosts, on the appservers, but not on the loadbalancers.

The documentation has more information on hostgroups based on other groups.

The playbook

The playbook (deploy-vhosts.yml) first was this:

---

- hosts: appserver
  roles:
    - apache-vhost

After changing the hostgroup we need to add the specific role that deploys the certificates. We also put a when in place to make sure the two roles only run on the hosts where they should and not the other hosts:

---

- hosts: ssl
  roles:
    - {role: apache-vhost, when: "'appserver' in group_names" }
    - {role: sslcerts, when: "'loadbalancer' in group_names " }

The group vars

The group vars for the appservers contain the following information to configure the virtual hosts:

---
apache_vhost:
  example.cluster.nl:
    name: example.cluster.nl
    docroot: /home/example-cluster/domains/example.cluster.nl/public-html/
    webuser: example-cluster
    ssl_name: example.cluster.nl
    serveraliases:
      - www.example.cluster.nl

  example2.cluster.nl:
    name: example2.cluster.nl
    docroot: /home/example2-cluster/domains/example2.cluster.nl/public-html/
    webuser: example2-cluster
    ssl_name: example2.cluster.nl
    serveraliases:
      - www.example2.cluster.nl

The problem is that, when the deploy vhosts playbook in run on the loadbalancers, they cannot access these variables since they are not in the same group.

The role

The role sslcerts has one task file and one handler (restart haproxy). It makes sure the folder for the certificates exists and it places the certificate files there. There are extra when statements to make sure it only runs on the loadbalancers.

- name: create ssl folder
  file:
    path=/etc/ssl/cluster/
    state=directory
    owner=root
    group=root
  when: "'loadbalancer' in group_names"
  tags: ssl

- name: place certificates
  copy:
    src=files/ssl/{{ item.value.ssl_name }}.pem
    dest=/etc/ssl/cluster/{{ item.value.ssl_name }}.pem
  with_dict: '{{ apache_vhost }}'
  when: "'loadbalancer' in group_names"
  notify: restart haproxy
  tags: ssl

The crux

Even after configuring the special playbook that runs on all the hosts, the loadbalancers still cannot access the group variables from the appservers. The dirty hack part is that we symlink the group vars from the appserver folder to the loadbalancer folder:

ln -s /home/deploy/ansible/group_vars/appserver/apache-vhost.yml /home/deploy/ansible/group_vars/loadbalancer/apache-vhost-symlink.yml

Now, the loadbalancers have the group variable apache_vhost as well, and when the file in the appserver folder is changed, the loadbalancer file is as well because it's a symlink.

haproxy

haproxy in this case has one frontend where all traffic comes into, the appservers handle the different virtual hosts:

frontend https-in
      mode http
      bind 1.2.3.4:443 ssl crt /etc/ssl/cluster/ no-sslv3
      acl secure dst_port eq 443
      rsprep ^Set-Cookie:\ (.*) Set-Cookie:\ \1;\ Secure if secure
      rspadd Strict-Transport-Security:\ max-age=31536000 if secure
      option httplog
      option forwardfor
      option http-server-close
      option httpclose
      reqadd X-Forwarded-Proto:\ https
      default_backend appserver

haproxy handles sni transparantly based on the requested hostname, if it finds the certificate in the folder /etc/ssl/cluster/. The files there are a concatenation of the public key, the private key and if needed the certificate chain.

Conclusion

In this case it might be better to change the haproxy playbook to handle this, or to change the group_vars to all. However, this environment has certain constraints set, so this 'suboptimal' workaround works.


Tags: ansible, client-side-ssl, haproxy, loadbalancer, ssl,