Page MenuHomeDevCentral

D2638.id6673.diff
No OneTemporary

D2638.id6673.diff

diff --git a/_modules/credentials.py b/_modules/credentials.py
new file mode 100644
--- /dev/null
+++ b/_modules/credentials.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+
+# -------------------------------------------------------------
+# Salt — Credentials
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Credentials-related execution module methods
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+from salt.utils.files import fopen
+
+
+# -------------------------------------------------------------
+# HOF utilities
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def _filter_discard_empty_string_values(haystack):
+ if type(haystack) is dict:
+ return {k: v for k, v in haystack.items() if v != ""}
+
+ if type(haystack) is list:
+ return [v for v in haystack if v != ""]
+
+ raise ValueError("Argument isn't a list or a dict: " + str(type(haystack)))
+
+
+def _join_document_fragments(fragments):
+ filtered = _filter_discard_empty_string_values(fragments)
+ return "\n\n".join(filtered)
+
+
+# -------------------------------------------------------------
+# Build Vault policies
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+class VaultSaltRolePolicy:
+ def __init__(self, role):
+ self.role = role
+
+ def build_policy(self):
+ return _join_document_fragments(
+ [
+ self.build_read_secrets_policy(),
+ self.import_extra_policies(),
+ ]
+ )
+
+ #
+ # Secrets from pillar entry vault_secrets_by_role
+ #
+
+ def build_read_secrets_policy(self):
+ vault_paths = __pillar__["vault_secrets_by_role"].get(self.role, [])
+
+ return _join_document_fragments(
+ [self.get_read_rule(vault_path) for vault_path in vault_paths]
+ )
+
+ def get_read_rule(self, vault_path):
+ resolved_vault_path = self.resolve_vault_path(vault_path)
+
+ return f"""path \"{resolved_vault_path}\" {{
+ capabilities = [ \"read\" ]
+}}"""
+
+ @staticmethod
+ def resolve_vault_path(vault_path):
+ for pillar_path, mount_path in __pillar__.get("vault_mount_paths", {}).items():
+ if vault_path.startswith(pillar_path):
+ start_position = len(pillar_path)
+ return mount_path + vault_path[start_position:]
+
+ return vault_path
+
+ #
+ # Import policies from pillar entry vault_extra_policies_by_role
+ #
+
+ def import_extra_policies(self):
+ extra_policies = __pillar__["vault_extra_policies_by_role"].get(self.role, [])
+ return _join_document_fragments(
+ [self.import_policy(policy) for policy in extra_policies]
+ )
+
+ @staticmethod
+ def import_policy(policy):
+ policy_file = f"{__pillar__['vault_policies_source']}/{policy}.hcl"
+
+ if policy_file.startswith("salt://"):
+ policy_file = __salt__["cp.cache_file"](policy_file)
+
+ with fopen(policy_file) as fd:
+ return fd.read()
+
+
+def _compile_roles_policies():
+ return {
+ role: VaultSaltRolePolicy(role).build_policy() for role in _get_relevant_roles()
+ }
+
+
+def _get_relevant_roles():
+ return {
+ role
+ for pillar_entry in [
+ "vault_extra_policies_by_role",
+ "vault_secrets_by_role",
+ ]
+ for role in __pillar__[pillar_entry].keys()
+ }
+
+
+def _build_node_policy(node, roles_policies):
+ rules = [
+ roles_policies[role]
+ for role in __salt__["node.get"]("roles", node)
+ if role in roles_policies
+ ]
+ return _join_document_fragments(rules)
+
+
+def build_policies_by_node():
+ roles_policies = _compile_roles_policies()
+
+ policies = {
+ node: _build_node_policy(node, roles_policies)
+ for node in __pillar__["nodes"].keys()
+ }
+
+ return _filter_discard_empty_string_values(policies)
diff --git a/_states/credentials.py b/_states/credentials.py
new file mode 100644
--- /dev/null
+++ b/_states/credentials.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+# -------------------------------------------------------------
+# Salt — Credentials state
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Allow to declare credentials-related states
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+from salt.utils.files import fopen
+
+
+def vault_policy_present(name, policy_file):
+ """
+ Ensure a Vault policy with the given name is present.
+
+ name
+ The name of the policy
+ policy_file
+ Path to a file on the minion containing rules,
+ formatted in HCL.
+
+ .. code-block:: yaml
+
+ demo_policy:
+ vault.policy_present:
+ - name: foo/bar
+ - policy_file: /opt/vault-policies/demo.hcl
+
+ """
+ with fopen(policy_file) as fd:
+ rules = fd.read()
+
+ return __states__["vault.policy_present"](name, rules)
diff --git a/pillar/credentials/vault.sls b/pillar/credentials/vault.sls
new file mode 100644
--- /dev/null
+++ b/pillar/credentials/vault.sls
@@ -0,0 +1,137 @@
+# -------------------------------------------------------------
+# Salt configuration for Nasqueron servers
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+# -------------------------------------------------------------
+# Vault configuration
+#
+# :: vault_policies_path: path on vault server where to store policies
+#
+# :: vault_policies_source: path to fetch policies from
+# if starting by salt://, from salt files server
+#
+# :: vault_mount_paths: translates secrets paths in policies paths
+#
+# Generally, Vault paths are the same for policies and data access.
+#
+# For kv secrets engine, version 2, writing and reading versions
+# of a kv value are prefixed with the data/ path.
+#
+# credentials.build_policies_by_node will use this dictionary
+# to be able to rewrite secrets paths in data paths.
+#
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+vault_policies_path: /srv/policies/vault
+vault_policies_source: salt://roles/vault/policies/files
+
+vault_mount_paths:
+ ops/secrets: ops/data/secrets
+ ops/privacy: ops/data/privacy
+
+# -------------------------------------------------------------
+# Vault policies to deploy as-is, ie without templating.
+#
+# Entries of vault_policies must match a .hcl file in
+# roles/vault/policies/files folder.
+#
+# If you need a template, create a new pillar entry instead
+# and add the parsing logic either:
+# - directly to roles/vault/policies/
+#
+# - through _modules/credentials.py for policies to apply
+# to Salt nodes, like e.g. vault_secrets_by_role
+#
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+vault_policies:
+ - salt-primary
+
+# -------------------------------------------------------------
+# Vault policies for Salt
+#
+# Declare the extra policies each nodes need.
+#
+# In adition of those extra policies, the vault_secrets_by_role
+# will be parsed for the keys.
+#
+# IMPORTANT: as grains['roles'] can be modified by the node,
+# roles are extracted directly from the pillar.
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+vault_extra_policies_by_role:
+ salt-primary:
+ - salt-primary
+
+# -------------------------------------------------------------
+# Vault secrets by role
+#
+# Paths of the keys the specified role needs access to.
+#
+# Avoid * notation as this namespace is shared between Vault
+# and the applications. As such, only secrets the Salt nodes
+# needs in a state they need to deploy should be listed here.
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+vault_secrets_by_role:
+
+ opensearch:
+ - ops/secrets/nasqueron.opensearch.infra-logs.internal_users.admin
+ - ops/secrets/nasqueron.opensearch.infra-logs.internal_users.dashboards
+
+ paas-docker:
+
+ #
+ # Personal data or personally identifiable information (PII)
+ # related to Nasqueron Operations SIG members.
+ #
+
+ - ops/privacy/ops-cidr
+
+ #
+ # Credentials used by Nasqueron services
+ # Format: ops/secrets/nasqueron.<service>.<type>
+ #
+
+ - ops/secrets/nasqueron.acquisitariat.mysql
+
+ - ops/secrets/nasqueron.auth-grove.mysql
+
+ - ops/secrets/nasqueron.cachet.app_key
+ - ops/secrets/nasqueron.cachet.mysql
+
+ - ops/secrets/nasqueron.etherpad.api
+
+ - ops/secrets/nasqueron.notifications.broker
+ - ops/secrets/nasqueron.notifications.mailgun
+ - ops/secrets/nasqueron.notifications.sentry
+
+ - ops/secrets/nasqueron.pixelfed.app_key
+ - ops/secrets/nasqueron.pixelfed.mailgun
+ - ops/secrets/nasqueron.pixelfed.mysql
+
+ - ops/secrets/nasqueron.sentry.app_key
+ - ops/secrets/nasqueron.sentry.postgresql
+
+ #
+ # Credentials used by Nasqueron members private services
+ # Format: <username>.<service>.<type>
+ #
+
+ - ops/secrets/dereckson.phabricator.mysql
+
+ #
+ # Credentials used by projects hosted by Nasqueron
+ # Format: <project name>.<service>.<type>
+ #
+
+ - ops/secrets/espacewin.bugzilla.mysql
+
+ - ops/secrets/wolfplex.phabricator.mailgun
+ - ops/secrets/wolfplex.phabricator.mysql
+
+ - ops/secrets/zed.phabricator.mysql
+ - ops/secrets/zed.phabricator.sendgrid
diff --git a/pillar/nodes/nodes.sls b/pillar/nodes/nodes.sls
--- a/pillar/nodes/nodes.sls
+++ b/pillar/nodes/nodes.sls
@@ -44,6 +44,9 @@
hostname: complector.nasqueron.org
roles:
- vault
+ - salt-primary
+
+ # Deprecated, use salt-primary, a more inclusive terminology
- saltmaster
zfs:
pool: zroot
diff --git a/pillar/top.sls b/pillar/top.sls
--- a/pillar/top.sls
+++ b/pillar/top.sls
@@ -22,6 +22,9 @@
- opensearch.software
- opensearch.clusters
+ complector:
+ - credentials.vault
+
docker-001:
- credentials.zr
- paas.docker
diff --git a/roles/vault/init.sls b/roles/vault/init.sls
--- a/roles/vault/init.sls
+++ b/roles/vault/init.sls
@@ -7,3 +7,6 @@
include:
- .vault
+
+ # Depends of Vault installed
+ - .policies
diff --git a/roles/vault/policies/files/salt-primary.hcl b/roles/vault/policies/files/salt-primary.hcl
new file mode 100644
--- /dev/null
+++ b/roles/vault/policies/files/salt-primary.hcl
@@ -0,0 +1,69 @@
+# -------------------------------------------------------------
+# Vault configuration - Policy for salt primary server
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# Source file: roles/vault/vault/files/salt_primary.hcl
+# -------------------------------------------------------------
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
+
+# -------------------------------------------------------------
+# Policies management
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+path "sys/policies/acl" {
+ capabilities = ["list"]
+}
+
+path "sys/policies/acl/*" {
+ capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+path "sys/policy" {
+ capabilities = ["list"]
+}
+
+path "sys/policy/*" {
+ capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+# -------------------------------------------------------------
+# Tokens management
+#
+# :: Create, check, revoke tokens to be used by nodes through Salt
+# :: Manage and renew own token
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+path "auth/token/create/salt-node-*" {
+ capabilities = ["update"]
+}
+
+path "auth/token/roles/salt-node-*" {
+ capabilities = ["read"]
+}
+
+path "auth/token/lookup-self" {
+ capabilities = ["read"]
+}
+
+path "auth/token/renew-self" {
+ capabilities = ["update"]
+}
+
+path "auth/token/lookup" {
+ capabilities = ["update"]
+}
+
+path "auth/token/revoke-accessor" {
+ capabilities = ["update"]
+}
+
+path "sys/capabilities-self" {
+ capabilities = ["update"]
+}
diff --git a/roles/vault/policies/init.sls b/roles/vault/policies/init.sls
new file mode 100644
--- /dev/null
+++ b/roles/vault/policies/init.sls
@@ -0,0 +1,52 @@
+# -------------------------------------------------------------
+# Salt — Vault
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+{% set policies_path = pillar['vault_policies_path'] %}
+
+# -------------------------------------------------------------
+# Policies storage folder
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{{ policies_path }}:
+ file.directory:
+ - makedirs: True
+
+# -------------------------------------------------------------
+# Policies from vault_policies pillar entry
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{% for policy in pillar['vault_policies'] %}
+{% set policy_path = policies_path + "/" + policy + ".hcl" %}
+
+{{ policy_path }}:
+ file.managed:
+ - source: salt://roles/vault/policies/files/{{ policy }}.hcl
+
+vault_policy_{{ policy }}:
+ credentials.vault_policy_present:
+ - name: {{ policy }}
+ - policy_file: {{ policy_path }}
+ - onchanges:
+ - file: {{ policy_path }}
+
+{% endfor %}
+
+# -------------------------------------------------------------
+# Policies per nodes intended to be used through Salt
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{% for node, rules in salt['credentials.build_policies_by_node']().items() %}
+salt-node-{{ node }}:
+ vault.policy_present:
+ - rules: |
+ #
+ # <auto-generated>
+ # This policy is managed by our rOPS SaltStack repository.
+ # </auto-generated>
+ #
+ {{ rules | indent(8) }}
+{% endfor %}

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 16, 15:29 (20 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2247709
Default Alt Text
D2638.id6673.diff (14 KB)

Event Timeline