#!/usr/bin/env python
# -*- coding: utf-8 -*-
# autosetup.py - Waqas Bhatti (wbhatti@astro.princeton.edu) - Aug 2018
# License: MIT - see the LICENSE file for the full text.
'''This contains functions to set up the authnzerver automatically on first-start.
'''
#############
## LOGGING ##
#############
import logging
# get a logger
LOGGER = logging.getLogger(__name__)
#############
## IMPORTS ##
#############
import os
import os.path
[docs]def autogen_secrets_authdb(basedir,
database_url=None,
interactive=False):
'''This automatically generates secrets files and an authentication DB.
Run this only once on the first start of an authnzerver.
Parameters
----------
basedir : str
The base directory of the authnzerver.
- The authentication database will be written to a file called
``.authdb.sqlite`` in this directory.
- The secret token to authenticate HTTP communications between the
authnzerver and a frontend server will be written to a file called
``.authnzerver-secret-key`` in this directory.
- Credentials for a superuser that can be used to edit various
authnzerver options, and users will be written to
``.authnzerver-admin-credentials`` in this directory.
- A random salt value will be written to ``.authnzerver-random-salt`` in
this directory. This is used to hash user IDs and other PII in logs.
database_url : str or None
If this is a str, must be a valid SQLAlchemy database URL to use to
connect to a database and make the necessary tables for authentication
info. If this is None, will create a new SQLite database in the
``<basedir>/.authdb.sqlite`` file.
interactive : bool
If True, will ask the user for an admin email address and
password. Otherwise, will auto-generate both.
Returns
-------
(authdb_path, creds, secret_file, salt_file) : tuple of str
The names of the files written by this function will be returned as a
tuple of strings.
'''
import getpass
from .authdb import (
create_sqlite_authdb, initial_authdb_inserts, create_authdb
)
from cryptography.fernet import Fernet
#
# get the default permissions JSON
#
mod_dir = os.path.dirname(__file__)
permissions_json = os.path.abspath(
os.path.join(mod_dir, 'default-permissions-model.json')
)
if interactive:
#
# get the auth DB URL
#
LOGGER.warning(
"Enter a valid SQLAlchemy database URL to use for the auth DB."
)
print("If you leave this blank and hit Enter, an SQLite auth DB")
print("will be created in the base directory: %s" % basedir)
input_db_url = input(
"Auth DB URL [default: auto generated]: "
)
if not input_db_url or len(input_db_url) == 0:
database_url = None
LOGGER.warning(
"Enter the path to the permissions policy JSON file to use."
)
print("If you leave this blank and hit Enter, the default permissions")
print("policy JSON shipped with authnzerver will be used: %s" %
permissions_json)
input_permissions_json = input(
"Permission JSON path [default: included permissions JSON]: "
)
if input_permissions_json and len(input_permissions_json) > 0:
permissions_json = input_permissions_json
if database_url is None:
# create our authentication database if it doesn't exist
authdb_path = os.path.join(basedir, '.authdb.sqlite')
if not os.path.exists(authdb_path):
LOGGER.warning('No existing authentication DB was found, '
'making a new SQLite DB in authnzerver basedir: %s'
% authdb_path)
# generate the initial DB
create_sqlite_authdb(authdb_path, echo=False, returnconn=False)
database_url = 'sqlite:///%s' % authdb_path
elif 'sqlite:///' in database_url:
# generate the initial DB
create_sqlite_authdb(authdb_path, echo=False, returnconn=False)
else:
create_authdb(database_url, echo=False, returnconn=False)
# ask the user for their email address and password the default
# email address will be used for the superuser if the email address
# is None, we'll use the user's UNIX ID@localhost if the password is
# None, a random one will be generated
try:
userid = '%s@localhost' % getpass.getuser()
except Exception:
userid = 'serveradmin@localhost'
if interactive:
inp_userid = input(
'\nAdmin email address [default: %s]: ' %
userid
)
if inp_userid and len(inp_userid.strip()) > 0:
userid = inp_userid
inp_pass = getpass.getpass(
'Admin password [default: randomly generated]: '
)
if inp_pass and len(inp_pass.strip()) > 0:
password = inp_pass
else:
password = None
else:
password = None
try:
# generate the admin users and initial DB info
u, p = initial_authdb_inserts(database_url,
permissions_json=permissions_json,
superuser_email=userid,
superuser_pass=password)
if not u and not p:
LOGGER.error("Could not do initial inserts into the auth DB.")
return None, None, None
except Exception:
LOGGER.warning(
"Auth DB is already set up at the provided database URL. "
"Not overwriting..."
)
creds = os.path.join(basedir,
'.authnzerver-admin-credentials')
if os.path.exists(creds):
LOGGER.warning("Admin credentials file already exists. "
"Not overwriting...")
else:
with open(creds,'w') as outfd:
outfd.write('%s %s\n' % (u,p))
os.chmod(creds, 0o100400)
if p:
LOGGER.warning('Generated random admin password, '
'credentials written to: %s\n' %
creds)
# we'll generate the server secrets now so we don't have to deal
# with them later
LOGGER.info('Generating server secret tokens...')
fernet_secret = Fernet.generate_key()
fernet_secret_file = os.path.join(basedir,'.authnzerver-secret-key')
if os.path.exists(fernet_secret_file):
LOGGER.warning("Authnzerver communication secrets file already "
"exists. Not overwriting...")
else:
with open(fernet_secret_file,'wb') as outfd:
outfd.write(fernet_secret)
os.chmod(fernet_secret_file, 0o100400)
# finally, we'll generate the server PII random salt
LOGGER.info('Generating server PII random salt...')
salt = Fernet.generate_key()
salt_file = os.path.join(basedir,'.authnzerver-salt')
if os.path.exists(salt_file):
LOGGER.warning("Authnzerver salt file already "
"exists. Not overwriting...")
else:
with open(salt_file,'wb') as outfd:
outfd.write(salt)
os.chmod(salt_file, 0o100400)
#
# return everything
#
if database_url is not None:
return database_url, creds, fernet_secret_file, salt_file
else:
return authdb_path, creds, fernet_secret_file, salt_file