use unix_socket auth for MySQL
This commit is contained in:
parent
0d94b1fafe
commit
1cd07c228c
|
@ -30,7 +30,7 @@ host all postgres 0.0.0.0/0 md5
|
||||||
local all all peer
|
local all all peer
|
||||||
host all all localhost md5
|
host all all localhost md5
|
||||||
|
|
||||||
local sameuser all md5
|
local sameuser all peer
|
||||||
host sameuser all 0.0.0.0/0 md5
|
host sameuser all 0.0.0.0/0 md5
|
||||||
EOF
|
EOF
|
||||||
grep -Eq "^listen_addresses = '*'$" $POSTGRES_DIR/postgresql.conf || \
|
grep -Eq "^listen_addresses = '*'$" $POSTGRES_DIR/postgresql.conf || \
|
||||||
|
|
|
@ -81,7 +81,7 @@ host all postgres 0.0.0.0/0 md5
|
||||||
local all all peer
|
local all all peer
|
||||||
host all all localhost md5
|
host all all localhost md5
|
||||||
|
|
||||||
local sameuser all md5
|
local sameuser all peer
|
||||||
host sameuser all 0.0.0.0/0 md5
|
host sameuser all 0.0.0.0/0 md5
|
||||||
```
|
```
|
||||||
**Warning**: in prod, the postgres user should only be allowed to connect locally,
|
**Warning**: in prod, the postgres user should only be allowed to connect locally,
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import os
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import click
|
||||||
|
from zope import component
|
||||||
|
|
||||||
|
from ..utils import http_post, http_get, http_delete
|
||||||
|
from .utils import handle_sync_response, check_file_path, check_if_in_development
|
||||||
|
from ceo_common.interfaces import IConfig
|
||||||
|
|
||||||
|
|
||||||
|
def db_cli_response(filename: str, user_dict: Dict, password: str, db_type: str, op: str):
|
||||||
|
cfg_srv = component.getUtility(IConfig)
|
||||||
|
db_host = cfg_srv.get(f'{db_type}_host')
|
||||||
|
username = user_dict['uid']
|
||||||
|
if db_type == 'mysql':
|
||||||
|
db_type_name = 'MySQL'
|
||||||
|
db_cli_local_cmd = f'mysql {username}'
|
||||||
|
db_cli_cmd = f'mysql {username} -h {db_host} -u {username} -p'
|
||||||
|
else:
|
||||||
|
db_type_name = 'PostgreSQL'
|
||||||
|
db_cli_local_cmd = f'psql {username}'
|
||||||
|
db_cli_cmd = f'psql -d {username} -h {db_host} -U {username} -W'
|
||||||
|
username = user_dict['uid']
|
||||||
|
info = f"""{db_type_name} Database Information for {username}
|
||||||
|
|
||||||
|
Your new {db_type_name} database was created. To connect, use the following options:
|
||||||
|
|
||||||
|
Database: {username}
|
||||||
|
Username: {username}
|
||||||
|
Password: {password}
|
||||||
|
Host: {db_host}
|
||||||
|
|
||||||
|
On {db_host} to connect using the {db_type_name} command-line client use
|
||||||
|
|
||||||
|
{db_cli_local_cmd}
|
||||||
|
|
||||||
|
From other CSC machines you can connect using
|
||||||
|
|
||||||
|
{db_cli_cmd}
|
||||||
|
"""
|
||||||
|
wrote_to_file = False
|
||||||
|
try:
|
||||||
|
# TODO: use phosphoric-acid to write to file (phosphoric-acid makes
|
||||||
|
# internal API call to caffeine)
|
||||||
|
with click.open_file(filename, "w") as f:
|
||||||
|
f.write(info)
|
||||||
|
os.chown(filename, user_dict['uid_number'], user_dict['gid_number'])
|
||||||
|
os.chmod(filename, 0o640)
|
||||||
|
wrote_to_file = True
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
if op == 'create':
|
||||||
|
click.echo(f'{db_type_name} database created.')
|
||||||
|
click.echo(f'''Connection Information:
|
||||||
|
|
||||||
|
Database: {username}
|
||||||
|
Username: {username}
|
||||||
|
Password: {password}
|
||||||
|
Host: {db_host}''')
|
||||||
|
if wrote_to_file:
|
||||||
|
click.echo(f"\nThese settings have been written to {filename}.")
|
||||||
|
else:
|
||||||
|
click.echo(f"\nWe were unable to write these settings to {filename}.")
|
||||||
|
|
||||||
|
|
||||||
|
def create(username: str, db_type: str):
|
||||||
|
db_type_name = 'MySQL' if db_type == 'mysql' else 'PostgreSQL'
|
||||||
|
resp = http_get(f'/api/members/{username}')
|
||||||
|
user_dict = handle_sync_response(resp)
|
||||||
|
click.confirm(f'Are you sure you want to create a {db_type_name} database for {username}?', abort=True)
|
||||||
|
|
||||||
|
info_file_path = os.path.join(user_dict['home_directory'], f"ceo-{db_type}-info")
|
||||||
|
check_file_path(info_file_path)
|
||||||
|
|
||||||
|
resp = http_post(f'/api/db/{db_type}/{username}')
|
||||||
|
result = handle_sync_response(resp)
|
||||||
|
password = result['password']
|
||||||
|
|
||||||
|
db_cli_response(info_file_path, user_dict, password, db_type, 'create')
|
||||||
|
|
||||||
|
|
||||||
|
def pwreset(username: str, db_type: str):
|
||||||
|
db_type_name = 'MySQL' if db_type == 'mysql' else 'PostgreSQL'
|
||||||
|
resp = http_get(f'/api/members/{username}')
|
||||||
|
user_dict = handle_sync_response(resp)
|
||||||
|
click.confirm(f'Are you sure you want reset the {db_type_name} password for {username}?', abort=True)
|
||||||
|
|
||||||
|
info_file_path = os.path.join(user_dict['home_directory'], f"ceo-{db_type}-info")
|
||||||
|
check_file_path(info_file_path)
|
||||||
|
|
||||||
|
resp = http_post(f'/api/db/{db_type}/{username}/pwreset')
|
||||||
|
result = handle_sync_response(resp)
|
||||||
|
password = result['password']
|
||||||
|
|
||||||
|
db_cli_response(info_file_path, user_dict, password, db_type, 'pwreset')
|
||||||
|
|
||||||
|
|
||||||
|
def delete(username: str, db_type: str):
|
||||||
|
check_if_in_development()
|
||||||
|
db_type_name = 'MySQL' if db_type == 'mysql' else 'PostgreSQL'
|
||||||
|
click.confirm(f"Are you sure you want to delete the {db_type_name} database for {username}?", abort=True)
|
||||||
|
resp = http_delete(f'/api/db/{db_type}/{username}')
|
||||||
|
handle_sync_response(resp)
|
|
@ -1,49 +1,6 @@
|
||||||
import click
|
import click
|
||||||
import os
|
|
||||||
|
|
||||||
from zope import component
|
from .database import create as db_create, pwreset as db_pwreset, delete as db_delete
|
||||||
from ceo_common.interfaces import IConfig
|
|
||||||
|
|
||||||
from ..utils import http_post, http_get, http_delete
|
|
||||||
from .utils import handle_sync_response, check_file_path, check_if_in_development
|
|
||||||
|
|
||||||
|
|
||||||
def mysql_cli_response(file, username, password):
|
|
||||||
cfg_srv = component.getUtility(IConfig)
|
|
||||||
mysql_host = cfg_srv.get('mysql_host')
|
|
||||||
info = f"""MySQL Database Information for {username}
|
|
||||||
|
|
||||||
Your new MySQL database was created. To connect, use the following options:
|
|
||||||
|
|
||||||
Database: {username}
|
|
||||||
Username: {username}
|
|
||||||
Password: {password}
|
|
||||||
Host: {mysql_host}
|
|
||||||
|
|
||||||
On {mysql_host} to connect using the MySQL command-line client use
|
|
||||||
|
|
||||||
mysql {username} -u {username} -p
|
|
||||||
|
|
||||||
From other CSC machines you can connect using
|
|
||||||
|
|
||||||
mysql {username} -h {mysql_host} -u {username} -p
|
|
||||||
|
|
||||||
"""
|
|
||||||
with click.open_file(file, "w") as f:
|
|
||||||
f.write(info)
|
|
||||||
os.chown(file, username, username)
|
|
||||||
os.chmod(file, 0o640)
|
|
||||||
|
|
||||||
click.echo(f"""MySQL database created
|
|
||||||
|
|
||||||
Connection Information:
|
|
||||||
|
|
||||||
Database: {username}
|
|
||||||
Username: {username}
|
|
||||||
Password: {password}
|
|
||||||
Host: {mysql_host}
|
|
||||||
|
|
||||||
Settings and more info has been written to {file}""")
|
|
||||||
|
|
||||||
|
|
||||||
@click.group(short_help='Perform operations on MySQL')
|
@click.group(short_help='Perform operations on MySQL')
|
||||||
|
@ -54,39 +11,16 @@ def mysql():
|
||||||
@mysql.command(short_help='Create a MySQL database for a user')
|
@mysql.command(short_help='Create a MySQL database for a user')
|
||||||
@click.argument('username')
|
@click.argument('username')
|
||||||
def create(username):
|
def create(username):
|
||||||
resp = http_get(f'/api/members/{username}')
|
db_create(username, 'mysql')
|
||||||
result = handle_sync_response(resp)
|
|
||||||
info_file_path = os.path.join(result['home_directory'], "ceo-mysql-info")
|
|
||||||
|
|
||||||
check_file_path(info_file_path)
|
|
||||||
|
|
||||||
resp = http_post(f'/api/db/mysql/{username}')
|
|
||||||
result = handle_sync_response(resp)
|
|
||||||
password = result['password']
|
|
||||||
|
|
||||||
mysql_cli_response(info_file_path, username, password)
|
|
||||||
|
|
||||||
|
|
||||||
@mysql.command(short_help='Reset the password of a MySQL user')
|
@mysql.command(short_help='Reset the password of a MySQL user')
|
||||||
@click.argument('username')
|
@click.argument('username')
|
||||||
def pwreset(username):
|
def pwreset(username):
|
||||||
resp = http_get(f'/api/members/{username}')
|
db_pwreset(username, 'mysql')
|
||||||
result = handle_sync_response(resp)
|
|
||||||
info_file_path = os.path.join(result['home_directory'], "ceo-mysql-info")
|
|
||||||
|
|
||||||
check_file_path(info_file_path)
|
|
||||||
|
|
||||||
resp = http_post(f'/api/db/mysql/{username}/pwreset')
|
|
||||||
result = handle_sync_response(resp)
|
|
||||||
password = result['password']
|
|
||||||
|
|
||||||
mysql_cli_response(info_file_path, username, password)
|
|
||||||
|
|
||||||
|
|
||||||
@mysql.command(short_help="Delete the database of a MySQL user")
|
@mysql.command(short_help="Delete the database of a MySQL user")
|
||||||
@click.argument('username')
|
@click.argument('username')
|
||||||
def delete(username):
|
def delete(username):
|
||||||
check_if_in_development()
|
db_delete(username, 'mysql')
|
||||||
click.confirm("Are you sure?", abort=True)
|
|
||||||
resp = http_delete(f'/api/db/mysql/{username}')
|
|
||||||
handle_sync_response(resp)
|
|
||||||
|
|
|
@ -1,49 +1,6 @@
|
||||||
import click
|
import click
|
||||||
import os
|
|
||||||
|
|
||||||
from zope import component
|
from .database import create as db_create, pwreset as db_pwreset, delete as db_delete
|
||||||
from ceo_common.interfaces import IConfig
|
|
||||||
|
|
||||||
from ..utils import http_post, http_get, http_delete
|
|
||||||
from .utils import handle_sync_response, check_file_path, check_if_in_development
|
|
||||||
|
|
||||||
|
|
||||||
def psql_cli_response(file, username, password):
|
|
||||||
cfg_srv = component.getUtility(IConfig)
|
|
||||||
psql_host = cfg_srv.get('postgresql_host')
|
|
||||||
info = f"""PostgreSQL Database Information for {username}
|
|
||||||
|
|
||||||
Your new PostgreSQL database was created. To connect, use the following options:
|
|
||||||
|
|
||||||
Database: {username}
|
|
||||||
Username: {username}
|
|
||||||
Password: {password}
|
|
||||||
Host: {psql_host}
|
|
||||||
|
|
||||||
On {psql_host} to connect using the PostgreSQL command-line client use
|
|
||||||
|
|
||||||
psql -d {username} -U {username} -W
|
|
||||||
|
|
||||||
From other CSC machines you can connect using
|
|
||||||
|
|
||||||
psql -d {username} -h {psql_host} -U {username} -W
|
|
||||||
|
|
||||||
"""
|
|
||||||
with click.open_file(file, "w") as f:
|
|
||||||
f.write(info)
|
|
||||||
os.chown(file, username, username)
|
|
||||||
os.chmod(file, 0o640)
|
|
||||||
|
|
||||||
click.echo(f"""PostgreSQL database created
|
|
||||||
|
|
||||||
Connection Information:
|
|
||||||
|
|
||||||
Database: {username}
|
|
||||||
Username: {username}
|
|
||||||
Password: {password}
|
|
||||||
Host: {psql_host}
|
|
||||||
|
|
||||||
Settings and more info has been written to {file}""")
|
|
||||||
|
|
||||||
|
|
||||||
@click.group(short_help='Perform operations on PostgreSQL')
|
@click.group(short_help='Perform operations on PostgreSQL')
|
||||||
|
@ -54,39 +11,16 @@ def postgresql():
|
||||||
@postgresql.command(short_help='Create a PostgreSQL database for a user')
|
@postgresql.command(short_help='Create a PostgreSQL database for a user')
|
||||||
@click.argument('username')
|
@click.argument('username')
|
||||||
def create(username):
|
def create(username):
|
||||||
resp = http_get(f'/api/members/{username}')
|
db_create(username, 'postgresql')
|
||||||
result = handle_sync_response(resp)
|
|
||||||
info_file_path = os.path.join(result['home_directory'], "ceo-psql-info")
|
|
||||||
|
|
||||||
check_file_path(info_file_path)
|
|
||||||
|
|
||||||
resp = http_post(f'/api/db/postgresql/{username}')
|
|
||||||
result = handle_sync_response(resp)
|
|
||||||
password = result['password']
|
|
||||||
|
|
||||||
psql_cli_response(info_file_path, username, password)
|
|
||||||
|
|
||||||
|
|
||||||
@postgresql.command(short_help='Reset the password of a PostgreSQL user')
|
@postgresql.command(short_help='Reset the password of a PostgreSQL user')
|
||||||
@click.argument('username')
|
@click.argument('username')
|
||||||
def pwreset(username):
|
def pwreset(username):
|
||||||
resp = http_get(f'/api/members/{username}')
|
db_pwreset(username, 'postgresql')
|
||||||
result = handle_sync_response(resp)
|
|
||||||
info_file_path = os.path.join(result['home_directory'], "ceo-psql-info")
|
|
||||||
|
|
||||||
check_file_path(info_file_path)
|
|
||||||
|
|
||||||
resp = http_post(f'/api/db/postgresql/{username}/pwreset')
|
|
||||||
result = handle_sync_response(resp)
|
|
||||||
password = result['password']
|
|
||||||
|
|
||||||
psql_cli_response(info_file_path, username, password)
|
|
||||||
|
|
||||||
|
|
||||||
@postgresql.command(short_help="Delete the database of a PostgreSQL user")
|
@postgresql.command(short_help="Delete the database of a PostgreSQL user")
|
||||||
@click.argument('username')
|
@click.argument('username')
|
||||||
def delete(username):
|
def delete(username):
|
||||||
check_if_in_development()
|
db_delete(username, 'postgresql')
|
||||||
click.confirm("Are you sure?", abort=True)
|
|
||||||
resp = http_delete(f'/api/db/postgresql/{username}')
|
|
||||||
handle_sync_response(resp)
|
|
||||||
|
|
|
@ -49,24 +49,21 @@ class MySQLService:
|
||||||
search_for_user = f"SELECT user FROM mysql.user WHERE user='{username}'"
|
search_for_user = f"SELECT user FROM mysql.user WHERE user='{username}'"
|
||||||
search_for_db = f"SHOW DATABASES LIKE '{username}'"
|
search_for_db = f"SHOW DATABASES LIKE '{username}'"
|
||||||
# CREATE USER can't be used in a query with multiple statements
|
# CREATE USER can't be used in a query with multiple statements
|
||||||
create_user_commands = [
|
create_local_user_cmd = f"CREATE USER '{username}'@'localhost' IDENTIFIED VIA unix_socket"
|
||||||
f"CREATE USER '{username}'@'localhost' IDENTIFIED BY %(password)s",
|
create_user_cmd = f"CREATE USER '{username}'@'%' IDENTIFIED BY %(password)s"
|
||||||
f"CREATE USER '{username}'@'%' IDENTIFIED BY %(password)s",
|
|
||||||
]
|
|
||||||
create_database = f"""
|
create_database = f"""
|
||||||
CREATE DATABASE {username};
|
CREATE DATABASE {username};
|
||||||
GRANT ALL PRIVILEGES ON {username}.* TO '{username}'@'localhost';
|
GRANT ALL PRIVILEGES ON {username}.* TO '{username}'@'localhost' IDENTIFIED VIA unix_socket;
|
||||||
GRANT ALL PRIVILEGES ON {username}.* TO '{username}'@'%';
|
GRANT ALL PRIVILEGES ON {username}.* TO '{username}'@'%';
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with self.mysql_connection() as con, con.cursor() as cursor:
|
with self.mysql_connection() as con, con.cursor() as cursor:
|
||||||
if response_is_empty(search_for_user, con):
|
if not response_is_empty(search_for_user, con):
|
||||||
for cmd in create_user_commands:
|
|
||||||
cursor.execute(cmd, {'password': password})
|
|
||||||
if response_is_empty(search_for_db, con):
|
|
||||||
cursor.execute(create_database)
|
|
||||||
else:
|
|
||||||
raise UserAlreadyExistsError()
|
raise UserAlreadyExistsError()
|
||||||
|
cursor.execute(create_local_user_cmd)
|
||||||
|
cursor.execute(create_user_cmd, {'password': password})
|
||||||
|
if response_is_empty(search_for_db, con):
|
||||||
|
cursor.execute(create_database)
|
||||||
return password
|
return password
|
||||||
|
|
||||||
def reset_db_passwd(self, username: str) -> str:
|
def reset_db_passwd(self, username: str) -> str:
|
||||||
|
@ -78,10 +75,9 @@ class MySQLService:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with self.mysql_connection() as con, con.cursor() as cursor:
|
with self.mysql_connection() as con, con.cursor() as cursor:
|
||||||
if not response_is_empty(search_for_user, con):
|
if response_is_empty(search_for_user, con):
|
||||||
cursor.execute(reset_password, {'password': password})
|
|
||||||
else:
|
|
||||||
raise UserNotFoundError(username)
|
raise UserNotFoundError(username)
|
||||||
|
cursor.execute(reset_password, {'password': password})
|
||||||
return password
|
return password
|
||||||
|
|
||||||
def delete_db(self, username: str):
|
def delete_db(self, username: str):
|
||||||
|
|
|
@ -56,7 +56,6 @@ class User:
|
||||||
|
|
||||||
self.ldap_srv = component.getUtility(ILDAPService)
|
self.ldap_srv = component.getUtility(ILDAPService)
|
||||||
self.krb_srv = component.getUtility(IKerberosService)
|
self.krb_srv = component.getUtility(IKerberosService)
|
||||||
self.file_srv = component.getUtility(IFileService)
|
|
||||||
|
|
||||||
def to_dict(self, get_forwarding_addresses: bool = False) -> Dict:
|
def to_dict(self, get_forwarding_addresses: bool = False) -> Dict:
|
||||||
data = {
|
data = {
|
||||||
|
@ -103,10 +102,12 @@ class User:
|
||||||
self.krb_srv.change_password(self.uid, password)
|
self.krb_srv.change_password(self.uid, password)
|
||||||
|
|
||||||
def create_home_dir(self):
|
def create_home_dir(self):
|
||||||
self.file_srv.create_home_dir(self)
|
file_srv = component.getUtility(IFileService)
|
||||||
|
file_srv.create_home_dir(self)
|
||||||
|
|
||||||
def delete_home_dir(self):
|
def delete_home_dir(self):
|
||||||
self.file_srv.delete_home_dir(self)
|
file_srv = component.getUtility(IFileService)
|
||||||
|
file_srv.delete_home_dir(self)
|
||||||
|
|
||||||
def subscribe_to_mailing_list(self, mailing_list: str):
|
def subscribe_to_mailing_list(self, mailing_list: str):
|
||||||
component.getUtility(IMailmanService).subscribe(self.uid, mailing_list)
|
component.getUtility(IMailmanService).subscribe(self.uid, mailing_list)
|
||||||
|
@ -163,7 +164,9 @@ class User:
|
||||||
self.positions = positions
|
self.positions = positions
|
||||||
|
|
||||||
def get_forwarding_addresses(self) -> List[str]:
|
def get_forwarding_addresses(self) -> List[str]:
|
||||||
return self.file_srv.get_forwarding_addresses(self)
|
file_srv = component.getUtility(IFileService)
|
||||||
|
return file_srv.get_forwarding_addresses(self)
|
||||||
|
|
||||||
def set_forwarding_addresses(self, addresses: List[str]):
|
def set_forwarding_addresses(self, addresses: List[str]):
|
||||||
self.file_srv.set_forwarding_addresses(self, addresses)
|
file_srv = component.getUtility(IFileService)
|
||||||
|
file_srv.set_forwarding_addresses(self, addresses)
|
||||||
|
|
|
@ -5,6 +5,7 @@ uw_domain = uwaterloo.internal
|
||||||
[ceod]
|
[ceod]
|
||||||
# this is the host with the ceod/admin Kerberos key
|
# this is the host with the ceod/admin Kerberos key
|
||||||
admin_host = phosphoric-acid
|
admin_host = phosphoric-acid
|
||||||
|
database_host = coffee
|
||||||
use_https = false
|
use_https = false
|
||||||
port = 9987
|
port = 9987
|
||||||
|
|
||||||
|
@ -12,3 +13,9 @@ port = 9987
|
||||||
required = president,vice-president,sysadmin
|
required = president,vice-president,sysadmin
|
||||||
available = president,vice-president,treasurer,secretary,
|
available = president,vice-president,treasurer,secretary,
|
||||||
sysadmin,cro,librarian,imapd,webmaster,offsck
|
sysadmin,cro,librarian,imapd,webmaster,offsck
|
||||||
|
|
||||||
|
[mysql]
|
||||||
|
host = coffee
|
||||||
|
|
||||||
|
[postgresql]
|
||||||
|
host = coffee
|
||||||
|
|
|
@ -13,7 +13,7 @@ port = 9987
|
||||||
|
|
||||||
[ldap]
|
[ldap]
|
||||||
admin_principal = ceod/admin
|
admin_principal = ceod/admin
|
||||||
server_url = ldap://ldap-master.csclub.internal
|
server_url = ldap://auth1.csclub.internal
|
||||||
sasl_realm = CSCLUB.INTERNAL
|
sasl_realm = CSCLUB.INTERNAL
|
||||||
users_base = ou=People,dc=csclub,dc=internal
|
users_base = ou=People,dc=csclub,dc=internal
|
||||||
groups_base = ou=Group,dc=csclub,dc=internal
|
groups_base = ou=Group,dc=csclub,dc=internal
|
||||||
|
|
Loading…
Reference in New Issue