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 '' grains.get 'ip4_interfaces'  

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 :

{%- set internalip ='')[0] %}
{%- set 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


def internal_interfaces():

    grains = {}

    internalips ='')
    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:

    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 '' grains.get 'internal_interfaces'  
    - 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.


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 !