how to set a random key once using saltstack - salt-stack

I install a configuration for rails that looks like this (simplified):
production:
secret_key_base: 800afb35d5086b2c60ebd35c01b2bd2b522c2492
db_username: ...
db_password: ...
and so it gets installed from a template file
{{ role }}:
secret_key_base: {{ secret_key }}
db_username: {{ db_user }}
db_password: {{ db_pass }}
And the role and db user/pass get pulled from pillar and installed in that file. The secret_key it would make sense to generate randomly, e.g., {{ salt['random.get_str'](length=80) }}. But I want to generate it once, not every time the template is rendered. (Changing the secret key invalidates cookies, not something to do on each salt run.)
The only solution I've found is two-phase: I have a template.in file
{{ role }}:
secret_key_base: ||secret_key_base||
db_username: {{ db_user }}
db_password: {{ db_pass }}
that I sed into my template file on any given minion:
/srv/salt/rails/secrets.yml:
cmd.run:
# Fill in the secret key base (used for cookies). We can't use
# jinja2 for this, since jinja would complain about the other
# variables that it doesn't know how to replace. We want our
# output to be a jinja template.
- name: |
cat /srv/salt/rails/secrets.yml.in | \
sed -e 's/||secret_key_base||/{{ salt['random.get_str'](length=80) }}/;' | \
cat > /srv/salt/rails/secrets.yml
chmod 400 /srv/salt/rails/secrets.yml
- creates: /srv/salt/rails/secrets.yml
- runas: root
/var/railroad/{{host_role}}/shared/config/secrets.yml:
file.managed:
- source: salt://rails/secrets.yml
- mode: 400
- user: railroad-{{host_role}}
- group: railroad-{{host_role}}
- template: jinja
- defaults:
role: host_role
db_username: m_u
db_password: m_p
This works but has the disadvantage that a change to secrets.yml.in would not be propagated on to secrets.yml. (Suppose we add another key to the secrets file.) It also feels clunkier than necessary.
Is there a better way?
A better way
As noted in the comments, a better way is to just generate the secret by hand (after all, it's only done at host setup) and store it in pillar, where we anyway have to say a few things about each host.
Here is what the working code eventually looked like, unsimplified for those who might want to see something more complex. Much of the complexity is my host_credentials pillar data, which tries to characterise all that we need to know about each host.
{% set fqdn = grains.get('fqdn', 'unknown-host-fqdn') %}
{% set host_role = pillar['host_credentials']
[grains.get('fqdn')]
['role'] %}
{% set examplecom_web_app = pillar['host_credentials']
[grains.get('fqdn')]
['examplecom-web-app'] %}
{% set mysql_server_host = examplecom_web_app.mysql.host %}
{% set mysql_server_database = examplecom_web_app.mysql.database %}
{% set mysql_server_role = examplecom_web_app.mysql.role %}
{% set mysql_server_spec = pillar['host_credentials']
[mysql_server_host]
['mysql'] %}
{% set mongodb_server_host = examplecom_web_app.mongodb.host %}
{% set mongodb_server_spec = pillar['host_credentials']
[mongodb_server_host]
['mongodb'] %}
/var/examplecom/railroad/{{host_role}}/shared/config/secrets.yml:
file.managed:
- source: salt://rails/secrets.yml
- mode: 400
- user: railroad-{{host_role}}
- group: railroad-{{host_role}}
- template: jinja
- defaults:
role: {{ host_role }}
secret_key_base: {{ examplecom_web_app.secret_key_base }}
mysql_hostname: {{ mysql_server_host }}
mysql_username: {{ mysql_server_spec[mysql_server_database]
[mysql_server_role]
['username'] }}
mysql_password: {{ mysql_server_spec[mysql_server_database]
[mysql_server_role]
['password'] }}
mongodb_hostname: {{ mongodb_server_host }}
mongodb_username: {{ mongodb_server_spec.username }}
mongodb_password: {{ mongodb_server_spec.password }}
As an aside, I was happy to discover that jinja2 is white-space agnostic, which helps enormously with readability on such lookups.

I recommend putting the secret in a pillar and generate the value once (manually) on the master. That way you can avoid doing the stateful on-the-fly-magic in your SLS file.
jma updated his question to include an example solution.

Do I understand you correctly:
- this is salt-masterless run?
- you generate some template file on masterless node (to have some static parts generated once)
- you apply file.managed state to previously generated file?
Assuming my understandings:
First of all it will be propagated to another states if you set proper watch/require statements but in your case this would be harder to achieve as you've used cmd.run for template parsing (you must add stateful argument to express that there is some potential state changes underlying)
Remarks:
- did you see file.blockreplace? it seems that you can use this to replace first cmd.run and get file change detection "for free"
- as for one time password generation, there is nice trick to do this simply by using grains.get_or_set_hash
As the generated password is not changing for given minion (you master-less node in your case) the file.blockreplace won't report any changes unless you add changes to the template
Having said this, I think we can go further and your state actually can be as simple as this (template changes will be propagated always, key will be generated once):
/var/railroad/{{host_role}}/shared/config/secrets.yml:
file.managed:
- source: salt://rails/secrets.yml
- mode: 400
- user: railroad-{{host_role}}
- group: railroad-{{host_role}}
- template: jinja
- defaults:
role: host_role
db_username: m_u
db_password: {{ salt['grains.get_or_set_hash']('some:place:secret_key_base') }}

Related

How to execute multiple scripts with Salt cmd.script?

I'm trying to apply salt state that consist of multiple scripts. But some reason only script4 is running.
Here's my state file.
#!jinja|yaml|gpg
Distro_Specific_Scripts:
cmd.script:
- name: Run Scripts.sh
{% if grains['os_family'] == 'RedHat' %}
- source: salt://scripts/files/scripts/script_for_redhat.sh
{% elif grains['os_family'] == 'Debian' %}
- source: salt://scripts/files/scripts/script_for_ubuntu.sh
{% endif %}
- source: salt://scripts/files/scripts/script2.sh
- source: salt://scripts/files/scripts/script3.sh
- source: salt://scripts/files/scripts/script4.sh
Would you share your experience about running multiple scripts? or Would you tell me where i went wrong.
There are multiple ways to write this.
OPTION 1:
This will be the simplest method. If you have scripts corresponding to the grains['os_family'], i.e.
script_for_debian.sh for Debian
script_for_ubuntu.sh for Ubuntu
script_for_redhat.sh for RedHat
run_my_scripts:
cmd.script:
- names:
- salt://scripts/files/scripts/script_for_{{ grains['os_family'] | lower }}.sh
- salt://scripts/files/scripts/script2.sh
- salt://scripts/files/scripts/script3.sh
- salt://scripts/files/scripts/script4.sh
OPTION 2:
If not, then we can set the script name conditionally and outside the state declaration:
{% set os_specific_script = salt['grains.filter_by']({
'Debian': 'script_for_ubuntu.sh',
'RedHat': 'script_for_redhat.sh'
}) %}
run_my_scripts:
cmd.script:
- names:
- salt://scripts/files/scripts/{{ os_specific_script }}
- salt://scripts/files/scripts/script2.sh
- salt://scripts/files/scripts/script3.sh
- salt://scripts/files/scripts/script4.sh
If you have control over the name of scripts, OPTION 1 will be a better choice.
Actually I found a way to achieve my goal. But if there's a smartest way to do it any help would be appreciated :)
#!jinja|yaml|gpg
Distro_Specific_Scripts:
cmd.script:
- name: Distro Specific Scripts
{% if grains['os_family'] == 'RedHat' %}
- source: salt://scripts/files/scripts/script_for_redhat.sh
{% elif grains['os_family'] == 'Debian' %}
- source: salt://scripts/files/scripts/script_for_debian.sh
{% endif %}
script2:
cmd.script:
- name: script2.sh
- source: salt://scripts/files/scripts/script2.sh
script3:
cmd.script:
- name: script3
- source: salt://scripts/files/scripts/script2.sh
script4:
cmd.script:
- name: script4.sh
- source: salt://scripts/files/scripts/script2.sh

Passing variables with include in salt-stack

I have several states that are almost the same. All of them deploy project, create virtualenv and configure supervisor. Difference is only in repo, project name and some additional actions.
A lot of code is duplicated. Is it possible to put the same parts into file and include it with additional variables?
In Ansible it can be done this way:
tasks:
- include: wordpress.yml
vars:
wp_user: timmy
ssh_keys:
- keys/one.txt
- keys/two.txt
This question looks similar to this one
If I understood your question correctly - I believe the best way to achieve what you want is to use Salt Macros.
With this most of your state will go to macros with placeholders being parameters like:
# lib.sls
{% macro create_user(user, password) %}
{{user}}:
user.present:
- home: /home/{{user}}
- password: {{password}}
{% endmacro %}
Then your state will look like:
# john.sls
{% from 'lib.sls' import create_user with context %}
{{ create_user('john', '<password hash>') }}
and:
# jane.sls
{% from 'lib.sls' import create_user with context %}
{{ create_user('john', '<password hash>') }}
As I found out there is another way to archive it without messing with templates (more Ansible way). Create an abstract state "python-project". Create concrete roles then and provide different pillars to these roles:
salt/top.sls:
base:
'roles:python-project-1':
- match: grain
- python-project
'roles:python-project-2':
- match: grain
- python-project
pillar/top.sls:
base:
'roles:python-project-1':
- match: grain
- common-pillars
- pillars-for-the-first
'roles:python-project-2':
- match: grain
- common-pillars
- pillars-for-the-second
Structure:
pillar/top.sls
pillar/common-pillars/init.sls
pillar/pillars-for-the-first/init.sls
pillar/pillars-for-the-second/init.sls
salt/top.sls
salt/python-project/init.sls
You can use Jinja imports to do that:
top.sls
{% set user = 'john' %}
{% include 'config.sls' %}
config.sls
file.managed:
- name: '/Users/{{ user }}/.config
- user: {{ user }}

Defining states depending on existance of a file/directory

How is it possible to get something like the following running:
{% if not exist('/tmp/dummy/') then %}
dummy:
file.touch:
- name: /tmp/dummy/tmp.txt
...
{% endif %}
I need it for installing software from a ZIP-file. I want to unzip on the minions, but there I don't want to have any remnants of licence files, which I only need for installation, left.
You can use unless for this.
dummy:
file.touch:
- name: /tmp/dummy/tmp.txt
- unless: test -d /tmp/dummy/
{% if 1 == salt['cmd.retcode']('test -f /tmp/woo.test') %}
ack:
file.touch:
- name: /tmp/woo.test
{% endif %}
You could use the pure python renderer for salt. Here is how it would look like.
#!py
import os
def run():
# defines the hash config
config = {}
if (not os.path.isfile("/tmp/dummy")):
config["dummy"] = {
"file.touch": [
{'name': '/tmp/dummy'},
],
}
return config

symfony2 multilanguage message error with form validator through yml

I have a yml file for the user form validation (a form like many others):
# src/User/RegBundle/Resources/config/validation.yml
User\RegBundle\Entity\User:
properties:
name:
- NotBlank: ~
- Length:
min: 2
max: 255
minMessage: error_min_message
maxMessage: error_max_message
The form is rendered into the twig like below
//...
{{ form_row(form.name) }}
{{ form_row(form.surname) }}
{{ form_row(form.gender) }}
{{ form_row(form.email) }}
//...
My symfony2 website has a multilanguage structure and there are the messages.mylang.xliff file for the words translation.
All works right.
My question is whether there's a way to insert the "error_min_message" into the messages.mylang.xliff or there are others ways to translate that messages.
Validation strings go to validators.LANG.FORMAT instead of messages.LANG.FORMAT. Don't forget to clear the cache after you add a new translation file.

Limit amount of words displayed from MySQL on a Twig page

I wish to limit the amount of text displayed from a mysql statement. So for example, in my database I have a page where there are 1000 words contained in the content field, I want to be able to just display 200 of those words.
How can this be done using TWIG?
Cheers!
Sounds like your are looking for the "truncate" filter.
In your app/config/config.yml add::
services:
twig.extension.text:
class: Twig_Extensions_Extension_Text
tags:
- { name: twig.extension }
Then you can do in your templates:
{{ var.foo | truncate(200) }}
{{ "Hello good Sir!" | truncate(4) }}

Resources