SaltStack security pitfalls - Part 1

June 2018 ยท 4 minute read

Introduction

SaltStack is a really neat automation and configuration management platform.
It used to be my CM of choice and I still like it, but I’ve since migrated to Ansible for various reasons.

In this blog series I will cover some security pitfalls I’ve encountered during assessments of environments using SaltStack.
I will (hopefully) not drop any zero-days or preach about the dangers of automatic key acceptance (which I’ve seen enabled several times :-/ ), but rather document common configuration issues.
They may seem lame to power users of Salt, but I’ve stumbled upon them in the wild - therefore, I feel like it’s worth spreading the word.

Basic knowledge of Salt is somewhat of a prerequisite.

This post will describe two issues that could be exploited by a rouge minion (client managed by SaltStack). In this scenario, an attacker has somehow compromised a minion and gained root access to the host.

Lacking access control for file server

While fairly well documented, it may come as a surprise to some users that the entire state tree on the Salt master is accessible by all authenticated minions. This means that a minion can request states targeted for others and gain access to potentially juicy information.

You can try this out by using the “salt-call” utility on a Salt minion. The command below shows which states are targeted for the minion:

minion-1 # salt-call state.show_top

local:
    ----------
    base:
        - lpkgs

It is however possible to access non-targeted states as well:

minion-1 # salt-call state.show_sls mail_server

local:
    ----------
    mail_server:
        ----------
        pkg:
            |_
              ----------
              pkgs:
                  - postfix
            - installed
[...]

This access control issue does not just affect the states, but everything severed by the Salt file server.
You can test this with “salt-call” as well, but you may need to manually install the required modules on the minion (or just install the “salt-master” package).

The commands in the example below can be used by an attacker with minion access to identify interesting files on the master and extract them:

minion-1 # salt-call cp.list_master                                                                                 

local:
    - juciy_configs/somewhat_sensitive.conf
    - lpkgs.sls
    - mail_server.sls
    - ppkgs.sls
    - top.sls
[...]

minion-1 # salt-call cp.get_file_str 'salt://juciy_configs/somewhat_sensitive.conf'                                 

local:
    secret_password=god

This process could of course be automated and scripted.

Sensitive information should not be stored in states or on the file server, unless you wish for it to be accessible by all minions. This is especially true for multi-tenant environments.

So whats the solution? As far as I know it is not possible to configure access control for the file server - please let me know if I have missed something!
You could try to store as much sensitive information as possible in pillar, which is a similar to states but is regulated by access control, but most configuration data could be considered sensitive if accessed by an attacker.

Another solution is to setup separate Salt masters and create “trust zones” in a syndication, but your paranoia might get to you and result in one master for every minion.

Speaking of pillar - there are some ways to mess that up as well!

Targeting pillar by grains

In Salt lingo, grains are data about minions collected by the minions themselves, such as disk utilization, interface names and similar.
They can be very handy and save you a lot of time while templating, but their content cannot be trusted as they are returned by the minions.

One case where use of grains could be exploited is in the targeting of pillar.

Imagine that you have a pillar declaration that targets based on the grain “roles”:

base:
  'G@roles:frontend':
    - frontend

  'G@roles:backend':
    - backend

  '*':
    - all

If everyone is playing fair, a frontend minion should only be able to access the targeted data:

frontend-42 # salt-call pillar.items

local:
    ----------
    cdn_password:
        S3cret!
    log_api:
        https://log.example.com/api

It is however trivial to manually specify this grain by editing the minion configuration and restarting the service:

grains:
  roles:
    - backend
frontend-42 # salt-call pillar.items
local:
    ----------
    db_password:
        sommar1998
    log_api:
        https://log.example.com/api

Wrapping up

Keeping your configuration management system (or in this case “automation platform”) is of uttermost importance, as a compromise will likely result in the end of your environment.
My goal with this blog series is not to bash on SaltStack - it’s great, try it out if you have not already - but with great power comes great responsibility.