Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add files for psql ssl support #317

Merged
merged 1 commit into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
377 changes: 377 additions & 0 deletions bin/config_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
import json
import os
import copy
import argparse
import re
import types

#
# make it easy to change this for testing
XDG_DATA_HOME = os.getenv("XDG_DATA_HOME", "/usr/share/")


def default_search_folders(app_name):
"""
Return the list of folders to search for configuration files
"""
return [
"%s/cdis/%s" % (XDG_DATA_HOME, app_name),
"/usr/share/cdis/%s" % app_name,
"%s/gen3/%s" % (XDG_DATA_HOME, app_name),
"/usr/share/gen3/%s" % app_name,
"/var/www/%s" % app_name,
"/etc/gen3/%s" % app_name,
os.getcwd(),
os.path.dirname(__file__),
]


def find_paths(file_name, app_name, search_folders=None):
"""
Search the given folders for file_name
search_folders defaults to default_search_folders if not specified
return the first path to file_name found
"""
search_folders = search_folders or default_search_folders(app_name)
possible_files = [os.path.join(folder, file_name) for folder in search_folders]
return [path for path in possible_files if os.path.exists(path)]


def load_json(file_name, app_name, search_folders=None):
"""
json.load(file_name) after finding file_name in search_folders
return the loaded json data or None if file not found
"""
actual_files = find_paths(file_name, app_name, search_folders)
if not actual_files:
return None
with open(actual_files[0], "r") as reader:
return json.load(reader)


def inject_creds_into_fence_config(creds_file_path, config_file_path):
creds_file = open(creds_file_path, "r")
creds = json.load(creds_file)
creds_file.close()

# get secret values from creds.json file
db_host = _get_nested_value(creds, "db_host")
db_username = _get_nested_value(creds, "db_username")
db_password = _get_nested_value(creds, "db_password")
db_database = _get_nested_value(creds, "db_database")
hostname = _get_nested_value(creds, "hostname")
indexd_password = _get_nested_value(creds, "indexd_password")
google_client_secret = _get_nested_value(creds, "google_client_secret")
google_client_id = _get_nested_value(creds, "google_client_id")
hmac_key = _get_nested_value(creds, "hmac_key")
db_path = "postgresql://{}:{}@{}:5432/{}".format(
db_username, db_password, db_host, db_database
)

config_file = open(config_file_path, "r").read()

print(" DB injected with value(s) from creds.json")
config_file = _replace(config_file, "DB", db_path)

print(" BASE_URL injected with value(s) from creds.json")
config_file = _replace(config_file, "BASE_URL", "https://{}/user".format(hostname))

print(" INDEXD_PASSWORD injected with value(s) from creds.json")
config_file = _replace(config_file, "INDEXD_PASSWORD", indexd_password)
config_file = _replace(config_file, "INDEXD_USERNAME", "fence")

print(" ENCRYPTION_KEY injected with value(s) from creds.json")
config_file = _replace(config_file, "ENCRYPTION_KEY", hmac_key)

print(
" OPENID_CONNECT/google/client_secret injected with value(s) "
"from creds.json"
)
config_file = _replace(
config_file,
"OPENID_CONNECT/google/client_secret",
google_client_secret,
)

print(" OPENID_CONNECT/google/client_id injected with value(s) from creds.json")
config_file = _replace(
config_file, "OPENID_CONNECT/google/client_id", google_client_id
)

open(config_file_path, "w+").write(config_file)


def set_prod_defaults(config_file_path):
config_file = open(config_file_path, "r").read()

print(
" CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS set as "
"var/www/fence/fence_google_app_creds_secret.json"
)
config_file = _replace(
config_file,
"CIRRUS_CFG/GOOGLE_APPLICATION_CREDENTIALS",
"/var/www/fence/fence_google_app_creds_secret.json",
)

print(
" CIRRUS_CFG/GOOGLE_STORAGE_CREDS set as "
"var/www/fence/fence_google_storage_creds_secret.json"
)
config_file = _replace(
config_file,
"CIRRUS_CFG/GOOGLE_STORAGE_CREDS",
"/var/www/fence/fence_google_storage_creds_secret.json",
)

print(" INDEXD set as http://indexd-service/")
config_file = _replace(config_file, "INDEXD", "http://indexd-service/")

print(" ARBORIST set as http://arborist-service/")
config_file = _replace(config_file, "ARBORIST", "http://arborist-service/")

print(" HTTP_PROXY/host set as cloud-proxy.internal.io")
config_file = _replace(config_file, "HTTP_PROXY/host", "cloud-proxy.internal.io")

print(" HTTP_PROXY/port set as 3128")
config_file = _replace(config_file, "HTTP_PROXY/port", 3128)

print(" DEBUG set to false")
config_file = _replace(config_file, "DEBUG", False)

print(" MOCK_AUTH set to false")
config_file = _replace(config_file, "MOCK_AUTH", False)

print(" MOCK_GOOGLE_AUTH set to false")
config_file = _replace(config_file, "MOCK_GOOGLE_AUTH", False)

print(" AUTHLIB_INSECURE_TRANSPORT set to true")
config_file = _replace(config_file, "AUTHLIB_INSECURE_TRANSPORT", True)

print(" SESSION_COOKIE_SECURE set to true")
config_file = _replace(config_file, "SESSION_COOKIE_SECURE", True)

print(" ENABLE_CSRF_PROTECTION set to true")
config_file = _replace(config_file, "ENABLE_CSRF_PROTECTION", True)

open(config_file_path, "w+").write(config_file)


def inject_other_files_into_fence_config(other_files, config_file_path):
additional_cfgs = _get_all_additional_configs(other_files)

config_file = open(config_file_path, "r").read()

for key, value in additional_cfgs.iteritems():
print(" {} set to {}".format(key, value))
config_file = _nested_replace(config_file, key, value)

open(config_file_path, "w+").write(config_file)


def _get_all_additional_configs(other_files):
"""
Attempt to parse given list of files
and extract configuration variables and values
"""
additional_configs = dict()
for file_path in other_files:
try:
file_ext = file_path.strip().split(".")[-1]
if file_ext == "json":
json_file = open(file_path, "r")
configs = json.load(json_file)
json_file.close()
elif file_ext == "py":
configs = from_pyfile(file_path)
else:
print(
"Cannot load config vars from a file with extention: {}".format(
file_ext
)
)
except Exception as exc:
# if there's any issue reading the file, exit
print(
"Error reading {}. Cannot get configuration. Skipping this file. "
"Details: {}".format(other_files, str(exc))
)
continue

if configs:
additional_configs.update(configs)

return additional_configs


def _nested_replace(config_file, key, value, replacement_path=None):
replacement_path = replacement_path or key
try:
for inner_key, inner_value in value.iteritems():
temp_path = replacement_path
temp_path = temp_path + "/" + inner_key
config_file = _nested_replace(
config_file, inner_key, inner_value, temp_path
)
except AttributeError:
# not a dict so replace
if value is not None:
config_file = _replace(config_file, replacement_path, value)

return config_file


def _replace(yaml_config, path_to_key, replacement_value, start=0, nested_level=0):
"""
Replace a nested value in a YAML file string with the given value without
losing comments. Uses a regex to do the replacement.
Args:
yaml_config (str): a string representing a full configuration file
path_to_key (str): nested/path/to/key. The value of this key will be
replaced
replacement_value (str): Replacement value for the key from
path_to_key
"""
nested_path_to_replace = path_to_key.split("/")

# our regex looks for a specific number of spaces to ensure correct
# level of nesting. It matches to the end of the line
search_string = (
" " * nested_level + ".*" + nested_path_to_replace[0] + "(')?(\")?:.*\n"
)
matches = re.search(search_string, yaml_config[start:])

# early return if we haven't found anything
if not matches:
return yaml_config

# if we're on the last item in the path, we need to get the value and
# replace it in the original file
if len(nested_path_to_replace) == 1:
# replace the current key:value with the new replacement value
match_start = start + matches.start(0) + len(" " * nested_level)
match_end = start + matches.end(0)
yaml_config = (
yaml_config[:match_start]
+ "{}: {}\n".format(
nested_path_to_replace[0],
_get_yaml_replacement_value(replacement_value, nested_level),
)
+ yaml_config[match_end:]
)

return yaml_config

# set new start point to past current match and move on to next match
start = matches.end(0)
nested_level += 1
del nested_path_to_replace[0]

return _replace(
yaml_config,
"/".join(nested_path_to_replace),
replacement_value,
start,
nested_level,
)


def from_pyfile(filename, silent=False):
"""
Modeled after flask's ability to load in python files:
https://github.com/pallets/flask/blob/master/flask/config.py
Some alterations were made but logic is essentially the same
"""
filename = os.path.abspath(filename)
d = types.ModuleType("config")
d.__file__ = filename
try:
with open(filename, mode="rb") as config_file:
exec( # nosec pylint: disable=W0122
compile(config_file.read(), filename, "exec"), d.__dict__
)
except IOError as e:
print("Unable to load configuration file ({})".format(e.strerror))
if silent:
return False
raise
return _from_object(d)


def _from_object(obj):
configs = {}
for key in dir(obj):
if key.isupper():
configs[key] = getattr(obj, key)
return configs


def _get_yaml_replacement_value(value, nested_level=0):
if isinstance(value, str):
return "'" + value + "'"
elif isinstance(value, bool):
return str(value).lower()
elif isinstance(value, list) or isinstance(value, set):
output = ""
for item in value:
# spaces for nested level then spaces and hyphen for each list item
output += (
"\n"
+ " " * nested_level
+ " - "
+ _get_yaml_replacement_value(item)
+ ""
)
return output
else:
return value


def _get_nested_value(dictionary, nested_path):
"""
Return a value from a dictionary given a path-like nesting of keys.
Will default to an empty string if value cannot be found.
Args:
dictionary (dict): a dictionary
nested_path (str): nested/path/to/key
Returns:
?: Value from dict
"""
replacement_value_path = nested_path.split("/")
replacement_value = copy.deepcopy(dictionary)

for item in replacement_value_path:
replacement_value = replacement_value.get(item, {})

if replacement_value == {}:
replacement_value = ""

return replacement_value


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--creds_file_to_inject",
default="creds.json",
help="creds file to inject into the configuration yaml",
)
parser.add_argument(
"--other_files_to_inject",
nargs="+",
help="fence_credentials.json, local_settings.py, fence_settings.py file(s) to "
"inject into the configuration yaml",
)
parser.add_argument(
"-c", "--config_file", default="config.yaml", help="configuration yaml"
)
args = parser.parse_args()

inject_creds_into_fence_config(args.creds_file_to_inject, args.config_file)
set_prod_defaults(args.config_file)

if args.other_files_to_inject:
inject_other_files_into_fence_config(
args.other_files_to_inject, args.config_file
)
7 changes: 7 additions & 0 deletions bin/creds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"db_host": "localhost",
"db_username": "indexduser",
"db_password": "indexdpassword",
"db_database": "indexd_db",
"fence_database": "fence_db"
}
Loading