From f6d29081c90af52064b981cdd877a07338d86038 Mon Sep 17 00:00:00 2001 From: vilajit <48951905+vilajit@users.noreply.github.com> Date: Mon, 29 Apr 2019 03:02:04 -0400 Subject: [PATCH] Enabling kerberos support (#1015) * make it possible to create users without passwords * put `krbsrvname` into the connection string if it is specified in the config * update postgres?.yml example files to mention `krbsrvname` --- patroni/postgresql.py | 39 +++++++++++++++++++++++++++------------ postgres0.yml | 9 +++++++++ postgres1.yml | 9 +++++++++ postgres2.yml | 9 +++++++++ 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/patroni/postgresql.py b/patroni/postgresql.py index 7beda6ef..c6eac738 100644 --- a/patroni/postgresql.py +++ b/patroni/postgresql.py @@ -114,6 +114,12 @@ class Postgresql(object): self._configure_server_parameters() self._connect_address = config.get('connect_address') + self._krbsrvname = config.get('krbsrvname') + + # for not so obvious connection attempts that may happen outside of pyscopg2 + if self._krbsrvname: + os.environ['PGKRBSRVNAME'] = self._krbsrvname + self._superuser = config['authentication'].get('superuser', {}) self.resolve_connection_addresses() @@ -155,7 +161,6 @@ class Postgresql(object): self.set_state('stopped') self._role_lock = Lock() self.set_role(self.get_postgres_role_from_data_directory()) - self._state_entry_timestamp = None self._cluster_info_state = {} @@ -364,6 +369,11 @@ class Postgresql(object): self._pending_restart = pending_restart self._server_parameters = server_parameters self._connect_address = config.get('connect_address') + self._krbsrvname = config.get('krbsrvname') + + # for not so obvious connection attempts that may happen outside of pyscopg2 + if self._krbsrvname: + os.environ['PGKRBSRVNAME'] = self._krbsrvname if not local_connection_address_changed: self.resolve_connection_addresses() @@ -1200,9 +1210,9 @@ class Postgresql(object): if not (member and member.conn_url) or member.name == self.name: return None r = member.conn_kwargs(self._replication) - r.update({'application_name': self.name, 'sslmode': 'prefer', 'sslcompression': '1'}) - keywords = 'user password host port sslmode sslcompression application_name'.split() - return ' '.join('{0}={{{0}}}'.format(kw) for kw in keywords).format(**r) + r.update(application_name=self.name, sslmode='prefer', sslcompression='1', krbsrvname=self._krbsrvname) + keywords = 'user password host port sslmode sslcompression application_name krbsrvname'.split() + return ' '.join('{0}={{{0}}}'.format(kw) for kw in keywords if r.get(kw)).format(**r) def check_recovery_conf(self, member): # TODO: recovery.conf could be stale, would be nice to detect that. @@ -1277,7 +1287,7 @@ class Postgresql(object): @contextmanager def _get_replication_connection_cursor(self, host='localhost', port=5432, database=None, **kwargs): with self._get_connection_cursor(host=host, port=int(port), database=database or self._database, replication=1, - user=self._replication['username'], password=self._replication['password'], + user=self._replication['username'], password=self._replication.get('password'), connect_timeout=3, options='-c statement_timeout=2000') as cur: yield cur @@ -1558,17 +1568,22 @@ class Postgresql(object): if 'NOLOGIN' not in options and 'LOGIN' not in options: options.append('LOGIN') - self.query("""DO $$ + params = [name] + if password: + options.extend(['PASSWORD', '%s']) + params.extend([password, password]) + + sql = """DO $$ BEGIN SET local synchronous_commit = 'local'; PERFORM * FROM pg_authid WHERE rolname = %s; IF FOUND THEN - ALTER ROLE "{0}" WITH {1} PASSWORD %s; + ALTER ROLE "{0}" WITH {1}; ELSE - CREATE ROLE "{0}" WITH {1} PASSWORD %s; + CREATE ROLE "{0}" WITH {1}; END IF; -END; -$$""".format(name, ' '.join(options)), name, password, password) +END;$$""".format(name, ' '.join(options)) + self.query(sql, *params) def timeline_wal_position(self): # This method could be called from different threads (simultaneously with some other `_query` calls). @@ -1717,10 +1732,10 @@ $$""".format(name, ' '.join(options)), name, password, password) task.complete(self.run_bootstrap_post_init(config)) if task.result: self.create_or_update_role(self._replication['username'], - self._replication['password'], ['REPLICATION']) + self._replication.get('password'), ['REPLICATION']) for name, value in (config.get('users') or {}).items(): if name not in (self._superuser.get('username'), self._replication['username']): - self.create_or_update_role(name, value['password'], value.get('options', [])) + self.create_or_update_role(name, value.get('password'), value.get('options', [])) # We were doing a custom bootstrap instead of running initdb, therefore we opened trust # access from certain addresses to be able to reach cluster and change password diff --git a/postgres0.yml b/postgres0.yml index 5b4cfe56..fb5cf2d2 100644 --- a/postgres0.yml +++ b/postgres0.yml @@ -55,6 +55,9 @@ bootstrap: - data-checksums pg_hba: # Add following lines to pg_hba.conf after running 'initdb' + # For kerberos gss based connectivity (discard @.*$) + #- host replication replicator 127.0.0.1/32 gss include_realm=0 + #- host all all 0.0.0.0/0 gss include_realm=0 - host replication replicator 127.0.0.1/32 md5 - host all all 0.0.0.0/0 md5 # - hostssl all all 0.0.0.0/0 md5 @@ -71,6 +74,12 @@ bootstrap: - createdb postgresql: + # Fully qualified kerberos ticket file for the running user + # same as KRB5CCNAME used by the GSS +# krb_server_keyfile: /var/spool/keytabs/postgres + + # Server side kerberos spn +# krbsrvname: postgres listen: 127.0.0.1:5432 connect_address: 127.0.0.1:5432 data_dir: data/postgresql0 diff --git a/postgres1.yml b/postgres1.yml index 1ba9bd54..a4b2ca1b 100644 --- a/postgres1.yml +++ b/postgres1.yml @@ -49,6 +49,9 @@ bootstrap: - data-checksums pg_hba: # Add following lines to pg_hba.conf after running 'initdb' + # For kerberos gss based connectivity (discard @.*$) + #- host replication replicator 127.0.0.1/32 gss include_realm=0 + #- host all all 0.0.0.0/0 gss include_realm=0 - host replication replicator 127.0.0.1/32 md5 - host all all 0.0.0.0/0 md5 # - hostssl all all 0.0.0.0/0 md5 @@ -65,6 +68,12 @@ bootstrap: - createdb postgresql: + # Fully qualified kerberos ticket file for the running user + # same as KRB5CCNAME used by the GSS +# krb_server_keyfile: /var/spool/keytabs/postgres + + # Server side kerberos spn +# krbsrvname: postgres listen: 127.0.0.1:5433 connect_address: 127.0.0.1:5433 data_dir: data/postgresql1 diff --git a/postgres2.yml b/postgres2.yml index 684ec066..68e125c5 100644 --- a/postgres2.yml +++ b/postgres2.yml @@ -49,6 +49,9 @@ bootstrap: - data-checksums pg_hba: # Add following lines to pg_hba.conf after running 'initdb' + # For kerberos gss based connectivity (discard @.*$) + #- host replication replicator 127.0.0.1/32 gss include_realm=0 + #- host all all 0.0.0.0/0 gss include_realm=0 - host replication replicator 127.0.0.1/32 md5 - host all all 0.0.0.0/0 md5 # - hostssl all all 0.0.0.0/0 md5 @@ -62,6 +65,12 @@ bootstrap: - createdb postgresql: + # Fully qualified kerberos ticket file for the running user + # same as KRB5CCNAME used by the GSS +# krb_server_keyfile: /var/spool/keytabs/postgres + + # Server side kerberos spn +# krbsrvname: postgres listen: 127.0.0.1:5434 connect_address: 127.0.0.1:5434 data_dir: data/postgresql2