def test_quote_identifier(self): eggs = [ ("hello", '"hello"'), ("Hello", '"Hello"'), ("""'""", '''"'"'''), ('"', '''""""'''), ("\\", r'''"\"'''), (r"\"", r'''"\"""'''), # Unicode too, not that anything this odd should get through. ("\\ aargh \u0441\u043b\u043e\u043d", r'U&"\\ aargh \0441\043b\043e\043d"'), ] for raw, quote in eggs: with self.subTest(raw=raw): self.assertEqual(postgresql.quote_identifier(raw), quote)
def test_quote_identifier(self): eggs = [ ('hello', '"hello"'), ('Hello', '"Hello"'), ("""'""", '''"'"'''), ('"', '''""""'''), ('\\', r'''"\"'''), (r'\"', r'''"\"""'''), # Unicode too, not that anything this odd should get through. ('\\ aargh \u0441\u043b\u043e\u043d', r'U&"\\ aargh \0441\043b\043e\043d"') ] for raw, quote in eggs: with self.subTest(raw=raw): self.assertEqual(postgresql.quote_identifier(raw), quote)
def generate_pg_hba_conf(pg_hba, config, rels, _peer_rel=None): """Update the pg_hba.conf file (host based authentication).""" rules = [] # The ordered list, as tuples. # local database user auth-method [auth-options] # host database user address auth-method [auth-options] # hostssl database user address auth-method [auth-options] # hostnossl database user address auth-method [auth-options] # host database user IP-address IP-mask auth-method [auth-opts] # hostssl database user IP-address IP-mask auth-method [auth-opts] # hostnossl database user IP-address IP-mask auth-method [auth-opts] def add(*record): rules.append(tuple(record)) # The charm is running as the root user, and needs to be able to # connect as the postgres user to all databases. add("local", "all", "postgres", "peer", "map=juju_charm") # The local unit needs access to its own database. Let every local # user connect to their matching PostgreSQL user, if it exists, and # nagios with a password. add("local", "all", nagios.nagios_username(), "password") add("local", "all", "all", "peer") if _peer_rel is None: _peer_rel = helpers.get_peer_relation() # Peers need replication access as the charm replication user. if _peer_rel: for peer, relinfo in _peer_rel.items(): for addr in incoming_addresses(relinfo): qaddr = postgresql.quote_identifier(addr) # Magic replication database, for replication. add( "host", "replication", replication.replication_username(), qaddr, "md5", "# {}".format(relinfo), ) # postgres db, so leader can query replication status. add( "host", "postgres", replication.replication_username(), qaddr, "md5", "# {}".format(relinfo), ) # Clients need access to the relation database as the relation users. for rel in rels["db"].values(): if "user" in rel.local: for relinfo in rel.values(): for addr in incoming_addresses(relinfo): # Quote everything, including the address, to disenchant # magic tokens like 'all'. add( "host", postgresql.quote_identifier(rel.local["database"]), postgresql.quote_identifier(rel.local["user"]), postgresql.quote_identifier(addr), "md5", "# {}".format(relinfo), ) add( "host", postgresql.quote_identifier(rel.local["database"]), postgresql.quote_identifier(rel.local["schema_user"]), postgresql.quote_identifier(addr), "md5", "# {}".format(relinfo), ) # Admin clients need access to all databases as any user, not just the # relation user. Most clients will just use the user provided them, # but proxies such as pgbouncer need to open connections as the accounts # it creates. for rel in rels["db-admin"].values(): if "user" in rel.local: for relinfo in rel.values(): for addr in incoming_addresses(relinfo): add( "host", "all", "all", postgresql.quote_identifier(addr), "md5", "# {}".format(relinfo), ) # External replication connections. Somewhat different than before # as the relation gets its own user to avoid sharing credentials, # and logical replication connections will want to specify the # database name. for rel in rels["master"].values(): for relinfo in rel.values(): for addr in incoming_addresses(relinfo): add( "host", "replication", postgresql.quote_identifier(rel.local["user"]), postgresql.quote_identifier(addr), "md5", "# {}".format(relinfo), ) if "database" in rel.local: add( "host", postgresql.quote_identifier(rel.local["database"]), postgresql.quote_identifier(rel.local["user"]), postgresql.quote_identifier(addr), "md5", "# {}".format(relinfo), ) # External administrative addresses, if specified by the operator. for addr in config["admin_addresses"].split(","): if addr: add( "host", "all", "all", postgresql.quote_identifier(postgresql.addr_to_range(addr)), "md5", "# admin_addresses config", ) # And anything-goes rules, if specified by the operator. for line in helpers.split_extra_pg_auth(config["extra_pg_auth"]): add(line + " # extra_pg_auth config") # Deny everything else add("local", "all", "all", "reject", "# Refuse by default") add("host", "all", "all", "all", "reject", "# Refuse by default") # Strip out the existing juju managed section start_mark = "### BEGIN JUJU SETTINGS ###" end_mark = "### END JUJU SETTINGS ###" pg_hba = re.sub( r"^\s*{}.*^\s*{}\s*$".format(re.escape(start_mark), re.escape(end_mark)), "", pg_hba, flags=re.I | re.M | re.DOTALL, ) # Comment out any uncommented lines pg_hba = re.sub(r"^\s*([^#\s].*)$", r"# juju # \1", pg_hba, flags=re.M) # Spit out the updated file rules.insert(0, (start_mark,)) rules.append((end_mark,)) pg_hba += "\n" + "\n".join(" ".join(rule) for rule in rules) return pg_hba
def generate_pg_hba_conf(pg_hba, config, rels): '''Update the pg_hba.conf file (host based authentication).''' rules = [] # The ordered list, as tuples. # local database user auth-method [auth-options] # host database user address auth-method [auth-options] # hostssl database user address auth-method [auth-options] # hostnossl database user address auth-method [auth-options] # host database user IP-address IP-mask auth-method [auth-opts] # hostssl database user IP-address IP-mask auth-method [auth-opts] # hostnossl database user IP-address IP-mask auth-method [auth-opts] def add(*record): rules.append(tuple(record)) # The charm is running as the root user, and needs to be able to # connect as the postgres user to all databases. add('local', 'all', 'postgres', 'peer', 'map=juju_charm') # The local unit needs access to its own database. Let every local # user connect to their matching PostgreSQL user, if it exists, and # nagios with a password. add('local', 'all', nagios.nagios_username(), 'password') add('local', 'all', 'all', 'peer') # Peers need replication access as the charm replication user. if rels.peer: for peer, relinfo in rels.peer.items(): if 'private-address' not in relinfo: continue # Other end not yet provisioned? addr = postgresql.addr_to_range(relinfo['private-address']) qaddr = postgresql.quote_identifier(addr) # Magic replication database, for replication. add('host', 'replication', replication.replication_username(), qaddr, 'md5', '# {}'.format(relinfo)) # postgres database, so the leader can query replication status. add('host', 'postgres', replication.replication_username(), qaddr, 'md5', '# {}'.format(relinfo)) # Clients need access to the relation database as the relation users. for rel in rels['db'].values(): if 'user' in rel.local: for relinfo in rel.values(): if 'private-address' not in relinfo: continue # Other end not yet provisioned? addr = postgresql.addr_to_range(relinfo['private-address']) # Quote everything, including the address, to disenchant # magic tokens like 'all'. add('host', postgresql.quote_identifier(rel.local['database']), postgresql.quote_identifier(rel.local['user']), postgresql.quote_identifier(addr), 'md5', '# {}'.format(relinfo)) add('host', postgresql.quote_identifier(rel.local['database']), postgresql.quote_identifier(rel.local['schema_user']), postgresql.quote_identifier(addr), 'md5', '# {}'.format(relinfo)) # Admin clients need access to all databases as any user, not just the # relation user. Most clients will just use the user provided them, # but proxies such as pgbouncer need to open connections as the accounts # it creates. for rel in rels['db-admin'].values(): if 'user' in rel.local: for relinfo in rel.values(): if 'private-address' not in relinfo: continue # Other end not yet provisioned? addr = postgresql.addr_to_range(relinfo['private-address']) add('host', 'all', 'all', postgresql.quote_identifier(addr), 'md5', '# {}'.format(relinfo)) # External replication connections. Somewhat different than before # as the relation gets its own user to avoid sharing credentials, # and logical replication connections will want to specify the # database name. for rel in rels['master'].values(): for relinfo in rel.values(): if 'private-address' not in relinfo: continue # Other end not yet provisioned? addr = postgresql.addr_to_range(relinfo['private-address']) add('host', 'replication', postgresql.quote_identifier(rel.local['user']), postgresql.quote_identifier(addr), 'md5', '# {}'.format(relinfo)) if 'database' in rel.local: add('host', postgresql.quote_identifier(rel.local['database']), postgresql.quote_identifier(rel.local['user']), postgresql.quote_identifier(addr), 'md5', '# {}'.format(relinfo)) # External administrative addresses, if specified by the operator. for addr in config['admin_addresses'].split(','): if addr: add('host', 'all', 'all', postgresql.quote_identifier(postgresql.addr_to_range(addr)), 'md5', '# admin_addresses config') # And anything-goes rules, if specified by the operator. for line in helpers.split_extra_pg_auth(config['extra_pg_auth']): add(line + ' # extra_pg_auth config') # Deny everything else add('local', 'all', 'all', 'reject', '# Refuse by default') add('host', 'all', 'all', 'all', 'reject', '# Refuse by default') # Strip out the existing juju managed section start_mark = '### BEGIN JUJU SETTINGS ###' end_mark = '### END JUJU SETTINGS ###' pg_hba = re.sub(r'^\s*{}.*^\s*{}\s*$'.format(re.escape(start_mark), re.escape(end_mark)), '', pg_hba, flags=re.I | re.M | re.DOTALL) # Comment out any uncommented lines pg_hba = re.sub(r'^\s*([^#\s].*)$', r'# juju # \1', pg_hba, flags=re.M) # Spit out the updated file rules.insert(0, (start_mark,)) rules.append((end_mark,)) pg_hba += '\n' + '\n'.join(' '.join(rule) for rule in rules) return pg_hba