blob: ec71d3b301b54ab33907aa86d1d25eafd97a9825 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import logging, json, os
import pg8000.dbapi
class PostgresClient:
def __init__(self, hostname, port, admin_username, password):
self.hostname = hostname
self.port = port
self.username = admin_username
self.password = password
def get_connection(self, db_name=None):
"""
Builds a PGDB Connection, optionally to the specified database name.
return: predefined connection for this PostgresDB client
"""
kwargs = {
"host": self.hostname,
"port": int(self.port),
"user": self.username,
"password": self.password,
}
if db_name:
kwargs["database"] = db_name
return pg8000.dbapi.connect(**kwargs)
def get_connection_to_db(self, db_name):
"""
Builds a PGDB Connection to the specified database name
return: predefined connection for this PostgresDB client
"""
return self.get_connection(db_name)
def _list_user_schemas(self, conn):
"""
Returns the list of non system schema names visible on the given connection.
"""
cur = conn.cursor()
try:
cur.execute("SELECT nspname FROM pg_catalog.pg_namespace where nspname not in ('information_schema','pg_catalog','pg_toast');")
return [record[0] for record in cur.fetchall()]
finally:
cur.close()
def query_all_pg_database_size(self):
"""
Queries the configured Postgres database to retrieve all the existing database size in MB.
return: a dictionary mapping the PG database name to the used space in bytes
"""
pg_dbsize_list = {}
conn = self.get_connection()
try:
logging.info("Querying all Postgres database used space")
# Open a cursor to perform database operations
cur = conn.cursor()
try:
cur.execute("SELECT datname as db_name, pg_database_size(datname) as db_usage FROM pg_database;")
for record in cur.fetchall():
logging.info(record)
pg_dbsize_list[record[0]]=record[1]
finally:
cur.close()
finally:
conn.close()
return pg_dbsize_list
def revoke_access_from_db(self, pg_username, pg_db_name):
"""
Revoke all privileges on given database from the user and update it with SELECT only privileges.
This is done using postgres admin credentials to connect against the database to be capped
param: pg_username
param: pg_db_name database to revoke privileges from
"""
conn = None
try:
conn = self.get_connection_to_db(pg_db_name)
logging.info(f"Querying database {pg_db_name} existing schemas")
pg_user_schema_names = self._list_user_schemas(conn)
cur = conn.cursor()
try:
logging.info(f"executing REVOKE ALL PRIVILEGES ON DATABASE {pg_db_name} FROM {pg_username} and setting RO access")
cur.execute(f"REVOKE ALL PRIVILEGES ON DATABASE {pg_db_name} FROM {pg_username};")
for schema in pg_user_schema_names:
logging.info(f"processing revocation on schema {schema}")
# pg8000 uses the extended query protocol: one statement per execute()
for statement in [
f"REVOKE ALL ON ALL TABLES IN SCHEMA {schema} FROM {pg_username};",
f"REVOKE ALL ON ALL SEQUENCES IN SCHEMA {schema} FROM {pg_username};",
f"REVOKE ALL ON SCHEMA {schema} FROM {pg_username};",
f"REVOKE ALL ON SCHEMA {schema} FROM public;",
f"GRANT USAGE ON SCHEMA {schema} TO {pg_username};",
f"GRANT SELECT ON ALL TABLES IN SCHEMA {schema} TO {pg_username};",
f"GRANT SELECT ON ALL SEQUENCES IN SCHEMA {schema} TO {pg_username};",
f"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} GRANT SELECT ON TABLES TO {pg_username};",
f"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} GRANT SELECT ON SEQUENCES TO {pg_username};",
]:
cur.execute(statement)
cur.execute(f"GRANT CONNECT ON DATABASE {pg_db_name} TO {pg_username};")
conn.commit()
logging.info(f"user {pg_username} updated with RO access on {pg_db_name}")
return True
finally:
cur.close()
except Exception as ex:
logging.error(f"failed to set postgres user {pg_username} in R/O mode. Reason: {ex}")
return False
finally:
if conn:
conn.close()
def grant_access_on_db(self, pg_username, pg_db_name):
"""
Grant all privileges on given database to the given and update it with ALL privileges.
This is done using postgres admin credentials to connect against the database to be capped
param: pg_username
param: pg_db_name database to grant privileges from
"""
conn = None
try:
conn = self.get_connection_to_db(pg_db_name)
logging.info(f"Querying database {pg_db_name} existing schemas")
pg_user_schema_names = self._list_user_schemas(conn)
cur = conn.cursor()
try:
logging.info(f"executing GRANT ALL PRIVILEGES ON DATABASE {pg_db_name} FROM {pg_username} and setting RO access")
cur.execute(f"GRANT ALL PRIVILEGES ON DATABASE {pg_db_name} TO {pg_username};")
for schema in pg_user_schema_names:
logging.info(f"processing grant on schema {schema}")
# pg8000 uses the extended query protocol: one statement per execute()
for statement in [
f"GRANT ALL ON ALL TABLES IN SCHEMA {schema} TO {pg_username};",
f"GRANT ALL ON ALL SEQUENCES IN SCHEMA {schema} TO {pg_username};",
f"GRANT ALL ON SCHEMA {schema} TO {pg_username};",
f"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} GRANT ALL ON TABLES TO {pg_username};",
f"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} GRANT ALL ON SEQUENCES TO {pg_username};",
]:
cur.execute(statement)
conn.commit()
logging.info(f"user {pg_username} updated with FULL access on {pg_db_name}")
return True
finally:
cur.close()
except Exception as ex:
logging.error(f"failed to set FULL ACCESS to postgres user {pg_username}. Reason: {ex}")
return False
finally:
if conn:
conn.close()