/ Saltstack

Saltstack - internal network interfaces

How to list internal network interfaces easily ?

I have more and more servers managed by Saltstack and they are increasingly different from one another. I use multiple distributions and a mix of different hardware and virtual machines.

At some point I was able to say 'eth0' is the internal network interface, 'eth1' is the external network (if any). It does not work anymore, at all. However, this is an information I need, to apply firewall rules for example (see my slightly modified iptables saltstack formula ), or assign a service to an interface.

Tried something with Jinja

And that was a total failure. It's an information we almost have, because we know what IP are assigned to which interface inside grains :

root@cs-salt-master:/srv/pillar# salt 'cs-prd-rbx-ovh-ded-mon-aaa.mon.ded.ovh.rbx.prd.cloud-solutions.fr' grains.get 'ip4_interfaces'
cs-prd-rbx-ovh-ded-mon-aaa.mon.ded.ovh.rbx.prd.cloud-solutions.fr:
    ----------
    eth0:
    eth1:
        - 10.94.66.134
    lo:
        - 127.0.0.1

So I tried to define a pillar with Jinja who listed all internal network interfaces on a minion. However, it did not work, as I did not found a reliable way to test if a variable is defined or not. I tried is defined, is not none, simply if data['inet'], if data['inet'][0]['address'] and I ended up with |default(''). Nothing worked, so when an interface had no IP assigned, Jinja failed to render, with an error like data['inet'] is not defined. The disaster :

hardware:
{%- set internalip = salt.network.ip_addrs(cidr='10.0.0.0/8')[0] %}
{%- set interfaces = salt.network.interfaces() %}
{%- for interface, data in interfaces.iteritems() %}
{%- if data['inet'][0]['address']|default('') == internalip %}
{%- set internalinterface = interface %}
  internal-interface: {{ internalinterface }}
{%- endif %}
{%- endfor %}

I spend an hour trying to get this working, but at least I learned something important : don't use Jinja for anything else than simple templating !

A proper solution

It's an information that doesn't change often (even never in my case), so a custom grain is in fact much more appropriate. Ten minutes and a few python lines later in my favorite editor :

#!/usr/bin/env python

import salt.modules.network

def internal_interfaces():

    grains = {}

    internalips = salt.modules.network.ip_addrs(cidr='10.0.0.0/8')
    interfaces = salt.modules.network.interfaces()
    
    grains['internal_interfaces'] = []

    for interface, data in interfaces.iteritems():
        for internalip in internalips:
            if 'inet' in data and data['inet'][0]['address'] and data['inet'][0]['address'] == internalip:
                grains['internal_interfaces'].append(interface)
    
    return grains

It works perfectly for my use case, with multiple interfaces or interfaces not connected or "new" interface names :

root@cs-salt-master:/srv/pillar# salt 'cs-prd-rbx-ovh-pcc-id-aaa.id.pcc.ovh.rbx.prd.cloud-solutions.fr' grains.get 'internal_interfaces'
cs-prd-rbx-ovh-pcc-id-aaa.id.pcc.ovh.rbx.prd.cloud-solutions.fr:
    - ens192

However I do not check if interfaces have multiple IP, I only check the first one ( data['inet'][0]['address'] ), so keep that in mind if you mix internal and external networks on a single interface.

Reactor

One last problem : custom grains are not synced at first highstate run. It's really important for me, as I use salt-cloud and reactor to do all the work, I need my minions to be 100% working after the first highstate run. There is a simple solution to this problem, that you can find in the official reactor documentation .

In short, it will sync custom grains at minion start, which is exactly what we want.

Enjoy !

Saltstack - internal network interfaces
Share this