如何使用 saltstack 设置随机密钥
how to set a random key once using saltstack
我为 rails 安装一个配置,看起来像这样(简化):
production:
secret_key_base: 800afb35d5086b2c60ebd35c01b2bd2b522c2492
db_username: ...
db_password: ...
因此它是从模板文件安装的
{{ role }}:
secret_key_base: {{ secret_key }}
db_username: {{ db_user }}
db_password: {{ db_pass }}
角色和数据库 user/pass 从 pillar 中提取并安装在该文件中。 secret_key 随机生成是有意义的,例如 {{ salt['random.get_str'](length=80) }}
。但是我想生成一次,而不是每次渲染模板的时候。 (更改密钥会使 cookie 无效,而不是对每个 salt 运行 做些什么。)
我找到的唯一解决方案是两阶段:我有一个 template.in
文件
{{ role }}:
secret_key_base: ||secret_key_base||
db_username: {{ db_user }}
db_password: {{ db_pass }}
我在任何给定的 minion 上 sed 到我的模板文件中:
/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
这可行,但缺点是对 secrets.yml.in
的更改不会传播到 secrets.yml
。 (假设我们向秘密文件添加另一个密钥。)它也感觉比必要的更笨重。
有没有更好的方法?
更好的方法
如评论中所述,更好的方法是手动生成秘密(毕竟,它仅在主机设置时完成)并将其存储在 pillar 中,无论如何我们必须对每个秘密说几句主持人。
这是工作代码最终的样子,对于那些可能想要看到更复杂的东西的人来说是未简化的。大部分的复杂性是我的 host_credentials
支柱数据,它试图描述我们需要了解的关于每台主机的所有信息。
{% 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 }}
顺便说一句,我很高兴地发现 jinja2 是 white-space 不可知论者,这极大地提高了此类查找的可读性。
我理解正确吗:
- 这是 salt-masterless 运行?
- 你在无主节点上生成一些模板文件(只生成一些静态部分)
- 您将 file.managed
状态应用于先前生成的文件?
假设我的理解:
首先,如果您设置正确 ,它会传播到其他州 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)
备注:
- 你看到 file.blockreplace
了吗?看来你可以用它来替换第一个 cmd.run
并获得文件更改检测 "for free"
- 至于一次性密码生成,有一个很好的技巧可以简单地通过使用 grains.get_or_set_hash
来做到这一点
由于生成的密码对于给定的 minion(在您的情况下是无主节点)没有更改,因此 file.blockreplace
不会报告任何更改,除非您向模板添加更改
话虽如此,我认为我们可以更进一步,您的状态实际上可以像这样简单(模板更改将始终传播,密钥将生成一次):
/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') }}
我建议将秘密放在支柱中,并在主节点上(手动)生成一次值。这样您就可以避免在 SLS 文件中执行有状态的即时魔术。
jma 更新了他的问题以包含示例解决方案。
我为 rails 安装一个配置,看起来像这样(简化):
production:
secret_key_base: 800afb35d5086b2c60ebd35c01b2bd2b522c2492
db_username: ...
db_password: ...
因此它是从模板文件安装的
{{ role }}:
secret_key_base: {{ secret_key }}
db_username: {{ db_user }}
db_password: {{ db_pass }}
角色和数据库 user/pass 从 pillar 中提取并安装在该文件中。 secret_key 随机生成是有意义的,例如 {{ salt['random.get_str'](length=80) }}
。但是我想生成一次,而不是每次渲染模板的时候。 (更改密钥会使 cookie 无效,而不是对每个 salt 运行 做些什么。)
我找到的唯一解决方案是两阶段:我有一个 template.in
文件
{{ role }}:
secret_key_base: ||secret_key_base||
db_username: {{ db_user }}
db_password: {{ db_pass }}
我在任何给定的 minion 上 sed 到我的模板文件中:
/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
这可行,但缺点是对 secrets.yml.in
的更改不会传播到 secrets.yml
。 (假设我们向秘密文件添加另一个密钥。)它也感觉比必要的更笨重。
有没有更好的方法?
更好的方法
如评论中所述,更好的方法是手动生成秘密(毕竟,它仅在主机设置时完成)并将其存储在 pillar 中,无论如何我们必须对每个秘密说几句主持人。
这是工作代码最终的样子,对于那些可能想要看到更复杂的东西的人来说是未简化的。大部分的复杂性是我的 host_credentials
支柱数据,它试图描述我们需要了解的关于每台主机的所有信息。
{% 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 }}
顺便说一句,我很高兴地发现 jinja2 是 white-space 不可知论者,这极大地提高了此类查找的可读性。
我理解正确吗:
- 这是 salt-masterless 运行?
- 你在无主节点上生成一些模板文件(只生成一些静态部分)
- 您将 file.managed
状态应用于先前生成的文件?
假设我的理解:
首先,如果您设置正确 ,它会传播到其他州 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)
备注:
- 你看到 file.blockreplace
了吗?看来你可以用它来替换第一个 cmd.run
并获得文件更改检测 "for free"
- 至于一次性密码生成,有一个很好的技巧可以简单地通过使用 grains.get_or_set_hash
由于生成的密码对于给定的 minion(在您的情况下是无主节点)没有更改,因此 file.blockreplace
不会报告任何更改,除非您向模板添加更改
话虽如此,我认为我们可以更进一步,您的状态实际上可以像这样简单(模板更改将始终传播,密钥将生成一次):
/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') }}
我建议将秘密放在支柱中,并在主节点上(手动)生成一次值。这样您就可以避免在 SLS 文件中执行有状态的即时魔术。
jma 更新了他的问题以包含示例解决方案。