diff --git a/_modules/credentials.py b/_modules/credentials.py --- a/_modules/credentials.py +++ b/_modules/credentials.py @@ -155,25 +155,9 @@ 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] + [_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 # @@ -195,6 +179,23 @@ return fd.read() +def _get_read_rule(vault_path): + resolved_vault_path = _resolve_vault_path(vault_path) + + return f"""path \"{resolved_vault_path}\" {{ + capabilities = [ \"read\" ] +}}""" + + +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 + + def _compile_roles_policies(): return { role: VaultSaltRolePolicy(role).build_policy() for role in _get_relevant_roles() @@ -218,6 +219,18 @@ for role in __salt__["node.get"]("roles", node) if role in roles_policies ] + + cluster = __salt__["node.get"]("dbserver:cluster", node) + if cluster is not None: + dbserver_rules_paths = __pillar__["vault_secrets_by_dbserver_cluster"].get( + cluster, [] + ) + rules.append( + _join_document_fragments( + [_get_read_rule(vault_path) for vault_path in dbserver_rules_paths] + ) + ) + policy = _join_document_fragments(rules) if not policy: diff --git a/map.jinja b/map.jinja --- a/map.jinja +++ b/map.jinja @@ -191,6 +191,7 @@ 'phpcs': 'pear-PHP_CodeSniffer', 'phpunit': 'phpunit9-php81', 'postgresql': 'postgresql15-server', + 'postgresql-contrib': 'postgresql15-contrib', 'sphinx': 'py36-sphinx', 'tcl': 'tcl86', 'tcltls': 'tcltls', diff --git a/pillar/credentials/vault.sls b/pillar/credentials/vault.sls --- a/pillar/credentials/vault.sls +++ b/pillar/credentials/vault.sls @@ -139,3 +139,15 @@ viperserv: - ops/secrets/nasqueron.viperserv.vault + +# ------------------------------------------------------------- +# Vault secrets by dbserver cluster +# +# Paths of the keys the specified role needs access to. +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +vault_secrets_by_dbserver_cluster: + + # Main PostgreSQL cluster + A: + - ops/secrets/dbserver/cluster-A/users/* diff --git a/pillar/dbserver/cluster-A.sls b/pillar/dbserver/cluster-A.sls new file mode 100644 --- /dev/null +++ b/pillar/dbserver/cluster-A.sls @@ -0,0 +1,22 @@ +dbserver_postgresql: + + server: + # Fantoir database needs the pg_trim extension + with_contrib: True + + users: + # Password paths are relative to ops/secrets/ + fantoir: + password: dbserver/cluster-A/users/fantoir + privileges: + - database: fantoir + scope: schema + privileges: + - ALL + + databases: + fantoir: + encoding: UTF8 + owner: fantoir + extensions: + - pg_trgm diff --git a/pillar/nodes/nodes.sls b/pillar/nodes/nodes.sls --- a/pillar/nodes/nodes.sls +++ b/pillar/nodes/nodes.sls @@ -58,6 +58,26 @@ netmask: *intranought_netmask gateway: 172.27.27.1 + db-A-001: + forest: nasqueron-infra + hostname: db-A-001.nasqueron.drake + roles: + - dbserver-pgsql + zfs: + pool: arcology + dbserver: + cluster: A + network: + ipv6_tunnel: False + + interfaces: + intranought: + device: vmx0 + ipv4: + address: 172.27.27.8 + netmask: *intranought_netmask + gateway: 172.27.27.1 + dwellers: forest: nasqueron-dev-docker hostname: dwellers.nasqueron.org diff --git a/pillar/top.sls b/pillar/top.sls --- a/pillar/top.sls +++ b/pillar/top.sls @@ -32,6 +32,9 @@ - saas.phpbb - saas.sentry + db-A-001: + - dbserver.cluster-A + dwellers: - credentials.zr - paas.docker diff --git a/roles/core/motd/files/db-A-001 b/roles/core/motd/files/db-A-001 new file mode 100644 --- /dev/null +++ b/roles/core/motd/files/db-A-001 @@ -0,0 +1,9 @@ + ____ ______ ___ + / )/ \/ \ db-001-A.nasqueron.drake + ( / __ _\ ) + \ (/ o) ( o) ) IP: {{ ipv4_address.ljust(16) }} OS: FreeBSD 13 + \_ (_ ) \ ) / GW: {{ ipv4_gateway.ljust(16) }} Cluster A | PostgreSQL + \ /\_/ \)_/ + \/ //| |\\ This server hosts databases for Nasqueron projects. + v | | v Use stricly Salt to configure any database or access. + \__/ diff --git a/roles/dbserver-pgsql/server/content.sls b/roles/dbserver-pgsql/server/content.sls new file mode 100644 --- /dev/null +++ b/roles/dbserver-pgsql/server/content.sls @@ -0,0 +1,102 @@ +# ------------------------------------------------------------- +# Salt — Database server — PostgreSQL +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Project: Nasqueron +# Pillar: dbserver_postgresql (in pillar/dbserver) +# License: Trivial work, not eligible to copyright +# If eligible, licensed under BSD-2-Clause +# ------------------------------------------------------------- + +{% set users = salt['pillar.get']("dbserver_postgresql:users", {}) %} +{% set databases = salt['pillar.get']("dbserver_postgresql:databases", {}) %} + +# ------------------------------------------------------------- +# Users +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +{% for username, args in users.items() %} +dbserver_pgsql_user_{{ username }}: + postgres_user.present: + - name: {{ username }} + - password: {{ salt["credentials.get_password"](args["password"]) }} + - encrypted: scram-sha-256 +{% endfor %} + +# ------------------------------------------------------------- +# Databases +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +{% for db_name, args in databases.items() %} +dbserver_pgsql_db_{{ db_name }}: + postgres_database.present: + - name: {{ db_name }} + {% if "encoding" in args %} + - encoding: {{ args["encoding"] }} + {% endif %} + {% if "collation" in args %} + - lc_collate: {{ args["collation"] }} + {% endif %} + {% if "ctype" in args %} + - lc_ctype: {{ args["ctype"] }} + {% endif %} + - owner: {{ args["owner"] }} + - require: + - dbserver_pgsql_user_{{ args["owner"] }} + +{% for extension in args.get("extensions", []) %} +dbserver_pgsql_db_{{ db_name }}_ext_{{ extension }}: + postgres_extension.present: + - maintenance_db: {{ db_name }} + - name: {{ extension }} + - require: + - dbserver_pgsql_db_{{ db_name }} +{% endfor %} + +{% endfor %} + +# ------------------------------------------------------------- +# Privileges +# +# Scopes supported: +# - schema +# - table +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +{% for username, user_args in users.items() %} +{% for privilege in user_args.get("privileges", []) %} + +{% set idx = loop.index %} + +{% if privilege["scope"] == "schema" %} +{% for schema in privilege.get("schemas", ["public"]) %} +dbserver_pgsql_user_{{ username }}_privilege_{{ idx }}_{{ schema }}: + postgres_privileges.present: + - name: {{ username }} + - object_type: schema + - object_name: {{ schema }} + - maintenance_db: {{ privilege["database"] }} + - privileges: {{ privilege["privileges"] }} + - require: + - dbserver_pgsql_user_{{ username }} + - dbserver_pgsql_db_{{ privilege["database"] }} +{% endfor %} +{% endif %} + +{% if privilege["scope"] == "table" %} +{% for table in privilege["tables"] %} +dbserver_pgsql_user_{{ username }}_privilege_{{ idx }}_{{ table }}: + postgres_privileges.present: + - name: {{ username }} + - object_type: table + - object_name: {{ table }} + - prepend: {{ privilege["schema"] }} + - maintenance_db: {{ privilege["database"] }} + - privileges: {{ privilege["privileges"] }} + - require: + - dbserver_pgsql_user_{{ username }} + - dbserver_pgsql_db_{{ privilege["database"] }} +{% endfor %} +{% endif %} + +{% endfor %} +{% endfor %} diff --git a/roles/dbserver-pgsql/server/init.sls b/roles/dbserver-pgsql/server/files/postgresql.rc copy from roles/dbserver-pgsql/server/init.sls copy to roles/dbserver-pgsql/server/files/postgresql.rc --- a/roles/dbserver-pgsql/server/init.sls +++ b/roles/dbserver-pgsql/server/files/postgresql.rc @@ -1,9 +1,9 @@ # ------------------------------------------------------------- -# Salt — Database server — PostgreSQL +# Database server — PostgreSQL — rc configuration # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Project: Nasqueron # License: Trivial work, not eligible to copyright # ------------------------------------------------------------- -include: - - .software +postgresql_enable="YES" +postgresql_data="/var/db/postgres/data" diff --git a/roles/dbserver-pgsql/server/init.sls b/roles/dbserver-pgsql/server/init.sls --- a/roles/dbserver-pgsql/server/init.sls +++ b/roles/dbserver-pgsql/server/init.sls @@ -7,3 +7,6 @@ include: - .software + + # Content includes databases, users, privileges + - .content diff --git a/roles/dbserver-pgsql/server/software.sls b/roles/dbserver-pgsql/server/software.sls --- a/roles/dbserver-pgsql/server/software.sls +++ b/roles/dbserver-pgsql/server/software.sls @@ -15,3 +15,27 @@ pkg.installed: - pkgs: - {{ packages.postgresql }} + {% if pillar["dbserver_postgresql"]["server"]["with_contrib"] | default(False) %} + - {{ packages["postgresql-contrib"] }} + {% endif %} + +# ------------------------------------------------------------- +# PostgreSQL service +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +{% if grains['os'] == 'FreeBSD' %} + +/etc/rc.conf.d/postgresql: + file.managed: + - source: salt://roles/dbserver-pgsql/server/files/postgresql.rc + +initialize_postgresql: + cmd.run: + - name: /usr/local/etc/rc.d/postgresql initdb + - creates: /var/db/postgres/data + +postgresql_running: + service.running: + - name: postgresql + +{% endif %} diff --git a/roles/paas-docker/nginx/files/vhosts/notifications.conf b/roles/paas-docker/nginx/files/vhosts/notifications.conf deleted file mode 100644 --- a/roles/paas-docker/nginx/files/vhosts/notifications.conf +++ /dev/null @@ -1,43 +0,0 @@ -# ------------------------------------------------------------- -# Configuration for Docker PaaS front-end nginx -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Author: Sébastien Santoro aka Dereckson -# Source file: roles/paas-docker/nginx/files/vhosts/notifications.conf -# ------------------------------------------------------------- -# -# -# 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. -# - -server { - listen 80; - listen [::]:80; - server_name {{ fqdn }}; - - include includes/letsencrypt; - - return 301 https://$host$request_uri; -} - -server { - server_name {{ fqdn }}; - - include includes/tls; - ssl_certificate /srv/letsencrypt/etc/live/{{ fqdn }}/fullchain.pem; - ssl_certificate_key /srv/letsencrypt/etc/live/{{ fqdn }}/privkey.pem; - - include includes/letsencrypt; - - location / { - proxy_pass http://localhost:{{ app_port }}; - include includes/proxy_params; - } - - root /var/wwwroot-502/notifications.nasqueron.org; - error_page 502 /502.html; - location /502.html {} - -} diff --git a/top.sls b/top.sls --- a/top.sls +++ b/top.sls @@ -29,6 +29,8 @@ - roles/webserver-legacy 'cloudhugger': - roles/opensearch + 'db-A-001': + - roles/dbserver-pgsql 'docker-001': - roles/paas-docker 'dwellers':