mirror of
https://codeberg.org/guix/guix.git
synced 2025-10-02 02:15:12 +00:00
services: postgresql-role: Add support for password files.
This commit adds a password-file to the postgresql-role field. It allows users to provision Postgres roles with a set password. * gnu/services/databases.scm (postgresql-role): Add password-file field. (postgresql-role-configuration): Add requirement field. (postgresql-create-roles): Add support for setting passwords from a file without leaking passwords to the command line. (postgresql-role-shepherd-service): Add support for customizable requirements. (postgresql-role-service-type): Pass on postgresql-role-configuration fields values by default, this way user configured fields are not lost. * gnu/tests/databases.scm: Test it. * doc/guix.texi: Document the new field and fix the extension point example. Change-Id: I3aabaa10b0c5e826c5aa874e5649e25a3508a585 Signed-off-by: Maxim Cournoyer <maxim.cournoyer@gmail.com>
This commit is contained in:
parent
b2b7d2a327
commit
9d216d2ae9
3 changed files with 107 additions and 12 deletions
|
@ -27745,9 +27745,10 @@ example:
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(service-extension postgresql-role-service-type
|
(service-extension postgresql-role-service-type
|
||||||
(const (postgresql-role
|
(const (list
|
||||||
(name "alice")
|
(postgresql-role
|
||||||
(create-database? #t))))
|
(name "alice")
|
||||||
|
(create-database? #t)))))
|
||||||
@end lisp
|
@end lisp
|
||||||
@end defvar
|
@end defvar
|
||||||
|
|
||||||
|
@ -27770,6 +27771,10 @@ The role permissions list. Supported permissions are @code{bypassrls},
|
||||||
@item @code{create-database?} (default: @code{#f})
|
@item @code{create-database?} (default: @code{#f})
|
||||||
whether to create a database with the same name as the role.
|
whether to create a database with the same name as the role.
|
||||||
|
|
||||||
|
@item @code{password-file} (default: @code{#f})
|
||||||
|
A string representing the path of a file that contains the password to be set
|
||||||
|
for the role.
|
||||||
|
|
||||||
@item @code{encoding} (default: @code{"UTF8"})
|
@item @code{encoding} (default: @code{"UTF8"})
|
||||||
The character set to use for storing text in the database.
|
The character set to use for storing text in the database.
|
||||||
|
|
||||||
|
@ -27798,6 +27803,12 @@ The PostgreSQL host to connect to.
|
||||||
@item @code{log} (default: @code{"/var/log/postgresql_roles.log"})
|
@item @code{log} (default: @code{"/var/log/postgresql_roles.log"})
|
||||||
File name of the log file.
|
File name of the log file.
|
||||||
|
|
||||||
|
@item @code{shepherd-requirement} (default: @code{'(user-processes postgres)})
|
||||||
|
|
||||||
|
The Shepherd services dependencies to use. Add extra dependencies to
|
||||||
|
@code{%default-postgresql-role-shepherd-requirement} to extend its
|
||||||
|
value.
|
||||||
|
|
||||||
@item @code{roles} (default: @code{'()})
|
@item @code{roles} (default: @code{'()})
|
||||||
The initial PostgreSQL roles to create.
|
The initial PostgreSQL roles to create.
|
||||||
@end table
|
@end table
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
;;; Copyright © 2020, 2022 Marius Bakke <marius@gnu.org>
|
;;; Copyright © 2020, 2022 Marius Bakke <marius@gnu.org>
|
||||||
;;; Copyright © 2021 David Larsson <david.larsson@selfhosted.xyz>
|
;;; Copyright © 2021 David Larsson <david.larsson@selfhosted.xyz>
|
||||||
;;; Copyright © 2021 Aljosha Papsch <ep@stern-data.com>
|
;;; Copyright © 2021 Aljosha Papsch <ep@stern-data.com>
|
||||||
|
;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@autistici.org>
|
||||||
;;;
|
;;;
|
||||||
;;; This file is part of GNU Guix.
|
;;; This file is part of GNU Guix.
|
||||||
;;;
|
;;;
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
#:autoload (gnu system accounts) (default-shell)
|
#:autoload (gnu system accounts) (default-shell)
|
||||||
#:use-module (gnu packages admin)
|
#:use-module (gnu packages admin)
|
||||||
#:use-module (gnu packages base)
|
#:use-module (gnu packages base)
|
||||||
|
#:use-module (gnu packages bash)
|
||||||
#:use-module (gnu packages databases)
|
#:use-module (gnu packages databases)
|
||||||
#:use-module (guix build-system trivial)
|
#:use-module (guix build-system trivial)
|
||||||
#:use-module (guix build union)
|
#:use-module (guix build union)
|
||||||
|
@ -68,14 +70,18 @@
|
||||||
postgresql-service
|
postgresql-service
|
||||||
postgresql-service-type
|
postgresql-service-type
|
||||||
|
|
||||||
|
%default-postgresql-role-shepherd-requirement
|
||||||
|
|
||||||
postgresql-role
|
postgresql-role
|
||||||
postgresql-role?
|
postgresql-role?
|
||||||
postgresql-role-name
|
postgresql-role-name
|
||||||
|
postgresql-role-password-file
|
||||||
postgresql-role-permissions
|
postgresql-role-permissions
|
||||||
postgresql-role-create-database?
|
postgresql-role-create-database?
|
||||||
postgresql-role-configuration
|
postgresql-role-configuration
|
||||||
postgresql-role-configuration?
|
postgresql-role-configuration?
|
||||||
postgresql-role-configuration-host
|
postgresql-role-configuration-host
|
||||||
|
postgresql-role-configuration-shepherd-requirement
|
||||||
postgresql-role-configuration-roles
|
postgresql-role-configuration-roles
|
||||||
|
|
||||||
postgresql-role-service-type
|
postgresql-role-service-type
|
||||||
|
@ -390,6 +396,8 @@ and stores the database cluster in @var{data-directory}."
|
||||||
postgresql-role make-postgresql-role
|
postgresql-role make-postgresql-role
|
||||||
postgresql-role?
|
postgresql-role?
|
||||||
(name postgresql-role-name) ;string
|
(name postgresql-role-name) ;string
|
||||||
|
(password-file postgresql-role-password-file ;string
|
||||||
|
(default #f))
|
||||||
(permissions postgresql-role-permissions
|
(permissions postgresql-role-permissions
|
||||||
(default '(createdb login))) ;list
|
(default '(createdb login))) ;list
|
||||||
(create-database? postgresql-role-create-database? ;boolean
|
(create-database? postgresql-role-create-database? ;boolean
|
||||||
|
@ -403,9 +411,15 @@ and stores the database cluster in @var{data-directory}."
|
||||||
(template postgresql-role-template ;string
|
(template postgresql-role-template ;string
|
||||||
(default "template1")))
|
(default "template1")))
|
||||||
|
|
||||||
|
(define %default-postgresql-role-shepherd-requirement
|
||||||
|
'(user-processes postgres))
|
||||||
|
|
||||||
(define-record-type* <postgresql-role-configuration>
|
(define-record-type* <postgresql-role-configuration>
|
||||||
postgresql-role-configuration make-postgresql-role-configuration
|
postgresql-role-configuration make-postgresql-role-configuration
|
||||||
postgresql-role-configuration?
|
postgresql-role-configuration?
|
||||||
|
(shepherd-requirement
|
||||||
|
postgresql-role-configuration-shepherd-requirement ;list-of-symbols
|
||||||
|
(default %default-postgresql-role-shepherd-requirement))
|
||||||
(host postgresql-role-configuration-host ;string
|
(host postgresql-role-configuration-host ;string
|
||||||
(default "/var/run/postgresql"))
|
(default "/var/run/postgresql"))
|
||||||
(log postgresql-role-configuration-log ;string
|
(log postgresql-role-configuration-log ;string
|
||||||
|
@ -425,19 +439,35 @@ and stores the database cluster in @var{data-directory}."
|
||||||
permissions)
|
permissions)
|
||||||
" ")))
|
" ")))
|
||||||
|
|
||||||
|
(define (password-value role)
|
||||||
|
(string-append "password_" (postgresql-role-name role)))
|
||||||
|
|
||||||
|
(define (role->password-variable role)
|
||||||
|
(let ((file-name (postgresql-role-password-file role)))
|
||||||
|
(if (string? file-name)
|
||||||
|
;; This way passwords do not leak to the command line.
|
||||||
|
#~(string-append "-v \"" #$(password-value role)
|
||||||
|
"=$(" #$coreutils "/bin/cat " #$file-name ")\"")
|
||||||
|
"")))
|
||||||
|
|
||||||
(define (roles->queries roles)
|
(define (roles->queries roles)
|
||||||
(apply mixed-text-file "queries"
|
(apply mixed-text-file "queries"
|
||||||
(append-map
|
(append-map
|
||||||
(lambda (role)
|
(lambda (role)
|
||||||
(match-record role <postgresql-role>
|
(match-record role <postgresql-role>
|
||||||
(name permissions create-database? encoding collation ctype
|
(name permissions create-database? encoding collation ctype
|
||||||
template)
|
template password-file)
|
||||||
`("SELECT NOT(EXISTS(SELECT 1 FROM pg_catalog.pg_roles WHERE \
|
`("SELECT NOT(EXISTS(SELECT 1 FROM pg_catalog.pg_roles WHERE \
|
||||||
rolname = '" ,name "')) as not_exists;\n"
|
rolname = '" ,name "')) as not_exists;\n"
|
||||||
"\\gset\n"
|
"\\gset\n"
|
||||||
"\\if :not_exists\n"
|
"\\if :not_exists\n"
|
||||||
"CREATE ROLE \"" ,name "\""
|
"CREATE ROLE \"" ,name "\""
|
||||||
" WITH " ,(format-permissions permissions)
|
" WITH " ,(format-permissions permissions)
|
||||||
|
,(if (and (string? password-file)
|
||||||
|
(not (string-null? password-file)))
|
||||||
|
(string-append
|
||||||
|
"\nPASSWORD :'" (password-value role) "'")
|
||||||
|
"")
|
||||||
";\n"
|
";\n"
|
||||||
,@(if create-database?
|
,@(if create-database?
|
||||||
`("CREATE DATABASE \"" ,name "\""
|
`("CREATE DATABASE \"" ,name "\""
|
||||||
|
@ -452,20 +482,30 @@ rolname = '" ,name "')) as not_exists;\n"
|
||||||
|
|
||||||
(let ((host (postgresql-role-configuration-host config))
|
(let ((host (postgresql-role-configuration-host config))
|
||||||
(roles (postgresql-role-configuration-roles config)))
|
(roles (postgresql-role-configuration-roles config)))
|
||||||
#~(let ((psql #$(file-append postgresql "/bin/psql")))
|
(program-file "run-queries"
|
||||||
(list psql "-a" "-h" #$host "-f" #$(roles->queries roles)))))
|
#~(let ((bash #$(file-append bash-minimal "/bin/bash"))
|
||||||
|
(psql #$(file-append postgresql "/bin/psql")))
|
||||||
|
(define command
|
||||||
|
(string-append
|
||||||
|
"set -e; exec " psql " -a -h " #$host " -f "
|
||||||
|
#$(roles->queries roles) " "
|
||||||
|
(string-join
|
||||||
|
(list
|
||||||
|
#$@(map role->password-variable roles))
|
||||||
|
" ")))
|
||||||
|
(execlp bash bash "-c" command)))))
|
||||||
|
|
||||||
(define (postgresql-role-shepherd-service config)
|
(define (postgresql-role-shepherd-service config)
|
||||||
(match-record config <postgresql-role-configuration>
|
(match-record config <postgresql-role-configuration>
|
||||||
(log)
|
(log shepherd-requirement)
|
||||||
(list (shepherd-service
|
(list (shepherd-service
|
||||||
(requirement '(user-processes postgres))
|
(requirement shepherd-requirement)
|
||||||
(provision '(postgres-roles))
|
(provision '(postgres-roles))
|
||||||
(one-shot? #t)
|
(one-shot? #t)
|
||||||
(start
|
(start
|
||||||
#~(lambda args
|
#~(lambda args
|
||||||
(zero? (spawn-command
|
(zero? (spawn-command
|
||||||
#$(postgresql-create-roles config)
|
(list #$(postgresql-create-roles config))
|
||||||
#:user "postgres"
|
#:user "postgres"
|
||||||
#:group "postgres"
|
#:group "postgres"
|
||||||
;; XXX: As of Shepherd 1.0.2, #:log-file is not
|
;; XXX: As of Shepherd 1.0.2, #:log-file is not
|
||||||
|
@ -484,6 +524,7 @@ rolname = '" ,name "')) as not_exists;\n"
|
||||||
(match-record config <postgresql-role-configuration>
|
(match-record config <postgresql-role-configuration>
|
||||||
(host roles)
|
(host roles)
|
||||||
(postgresql-role-configuration
|
(postgresql-role-configuration
|
||||||
|
(inherit config)
|
||||||
(host host)
|
(host host)
|
||||||
(roles (append roles extended-roles))))))
|
(roles (append roles extended-roles))))))
|
||||||
(default-value (postgresql-role-configuration))
|
(default-value (postgresql-role-configuration))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
;;; GNU Guix --- Functional package management for GNU
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
;;; Copyright © 2017 Christopher Baines <mail@cbaines.net>
|
;;; Copyright © 2017 Christopher Baines <mail@cbaines.net>
|
||||||
;;; Copyright © 2020, 2022 Marius Bakke <marius@gnu.org>
|
;;; Copyright © 2020, 2022 Marius Bakke <marius@gnu.org>
|
||||||
|
;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@autistici.org>
|
||||||
;;;
|
;;;
|
||||||
;;; This file is part of GNU Guix.
|
;;; This file is part of GNU Guix.
|
||||||
;;;
|
;;;
|
||||||
|
@ -142,6 +143,8 @@
|
||||||
|
|
||||||
(define %postgresql-os
|
(define %postgresql-os
|
||||||
(simple-operating-system
|
(simple-operating-system
|
||||||
|
(extra-special-file "/password"
|
||||||
|
(plain-file "password" "hello"))
|
||||||
(service postgresql-service-type
|
(service postgresql-service-type
|
||||||
(postgresql-configuration
|
(postgresql-configuration
|
||||||
(postgresql postgresql)
|
(postgresql postgresql)
|
||||||
|
@ -158,6 +161,10 @@
|
||||||
(roles
|
(roles
|
||||||
(list (postgresql-role
|
(list (postgresql-role
|
||||||
(name "root")
|
(name "root")
|
||||||
|
(create-database? #t))
|
||||||
|
(postgresql-role
|
||||||
|
(name "a_database")
|
||||||
|
(password-file "/password")
|
||||||
(create-database? #t))))))))
|
(create-database? #t))))))))
|
||||||
|
|
||||||
(define (run-postgresql-test)
|
(define (run-postgresql-test)
|
||||||
|
@ -230,17 +237,53 @@
|
||||||
(marionette-eval
|
(marionette-eval
|
||||||
'(begin
|
'(begin
|
||||||
(use-modules (gnu services herd)
|
(use-modules (gnu services herd)
|
||||||
|
(srfi srfi-1)
|
||||||
(ice-9 popen))
|
(ice-9 popen))
|
||||||
(current-output-port
|
(current-output-port
|
||||||
(open-file "/dev/console" "w0"))
|
(open-file "/dev/console" "w0"))
|
||||||
|
(every
|
||||||
|
(lambda (role)
|
||||||
|
(let* ((port (open-pipe*
|
||||||
|
OPEN_READ
|
||||||
|
#$(file-append postgresql "/bin/psql")
|
||||||
|
"-tA" "-c"
|
||||||
|
(string-append
|
||||||
|
"SELECT 1 FROM pg_database WHERE"
|
||||||
|
" datname='" role "'")))
|
||||||
|
(output (get-string-all port)))
|
||||||
|
(close-pipe port)
|
||||||
|
(string-contains output "1")))
|
||||||
|
'("root" "a_database")))
|
||||||
|
marionette))
|
||||||
|
|
||||||
|
(test-assert "database use fails without a password"
|
||||||
|
(marionette-eval
|
||||||
|
'(begin
|
||||||
|
(setgid (passwd:gid (getpwnam "alice")))
|
||||||
|
(setuid (passwd:uid (getpw "alice")))
|
||||||
|
(not (zero?
|
||||||
|
(system* #$(file-append postgresql "/bin/psql")
|
||||||
|
"-tA" "-h" "localhost" "-U" "a_database" "-c"
|
||||||
|
(string-append "SELECT 1 FROM pg_database "
|
||||||
|
"WHERE datname='a_database'")))))
|
||||||
|
marionette))
|
||||||
|
|
||||||
|
(test-assert "database passwords are set"
|
||||||
|
(marionette-eval
|
||||||
|
'(begin
|
||||||
|
(use-modules (ice-9 popen))
|
||||||
|
(setgid (passwd:gid (getpwnam "alice")))
|
||||||
|
(setuid (passwd:uid (getpw "alice")))
|
||||||
|
(setenv "PGPASSWORD"
|
||||||
|
(call-with-input-file "/password" get-string-all))
|
||||||
(let* ((port (open-pipe*
|
(let* ((port (open-pipe*
|
||||||
OPEN_READ
|
OPEN_READ
|
||||||
#$(file-append postgresql "/bin/psql")
|
#$(file-append postgresql "/bin/psql")
|
||||||
"-tA" "-c" "SELECT 1 FROM pg_database WHERE
|
"-U" "a_database" "-tA" "-h" "localhost" "-c"
|
||||||
datname='root'"))
|
"SELECT 1 FROM pg_database WHERE datname='a_database'"))
|
||||||
(output (get-string-all port)))
|
(output (get-string-all port)))
|
||||||
(close-pipe port)
|
(close-pipe port)
|
||||||
(string-contains output "1")))
|
(string=? output "1\n")))
|
||||||
marionette))
|
marionette))
|
||||||
|
|
||||||
(test-end))))
|
(test-end))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue