Skip to content
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
29 changes: 29 additions & 0 deletions docs/web/server_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,32 @@ By default the server will use the value from your host configured by the
## Authentication
For authentication configuration options and which options can be reloaded see
the [Authentication](authentication.md) documentation.

## Secrets

### server_secrets.json
Optionally, one can store sensitive data (e.g., passwords, secret tokens) outside
of `server_config.json`. To do this, create a separate `server_secrets.json` file
in the server's *workspace* folder. In `server_config.json`, replace sensitive data
with `$SECRET:NAME_OF_SECRET$`.
Then, secrets can be defined in `server_secrets.json`, as an example:
```json
{
"NAME_OF_SECRET": "MySecurePassword123"
}
```
Alternatively, one can also define entire sections as a secret, for instance:
```json
{
"NAME_OF_SECRET": {
"enabled" : true,
"client_id" : "<ExampleClientID>",
"client_secret": "<ExampleClientSecret>"
}
}
```

### Environmental variables
Alternatively, CodeChecker can also read sensitive data from environmental variables.
To do this, replace sensitive data in `server_config.json` with `$ENV:VARIABLE_NAME$`.
In this case, value will be read from environmental variable `VARIABLE_NAME`.
6 changes: 4 additions & 2 deletions web/server/codechecker_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,9 +1127,12 @@ def start_server(config_directory, package_data, port, config_sql_server,
"created at '%s'", server_cfg_file)
shutil.copyfile(example_cfg_file, server_cfg_file)

server_secrets_file = os.path.join(config_directory, 'server_secrets.json')

try:
manager = session_manager.SessionManager(
server_cfg_file,
server_secrets_file,
force_auth)

except IOError as ioerr:
Expand All @@ -1138,8 +1141,7 @@ def start_server(config_directory, package_data, port, config_sql_server,
"is missing or can not be read!")
sys.exit(1)
except ValueError as verr:
LOG.debug(verr)
LOG.error("The server's configuration file is invalid!")
LOG.error(verr)
sys.exit(1)

if not skip_db_cleanup:
Expand Down
43 changes: 42 additions & 1 deletion web/server/codechecker_server/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class SessionManager:
CodeChecker server.
"""

def __init__(self, configuration_file, force_auth=False):
def __init__(self, configuration_file, secrets_file, force_auth=False):
"""
Initialise a new Session Manager on the server.

Expand All @@ -176,6 +176,7 @@ def __init__(self, configuration_file, force_auth=False):
self.__logins_since_prune = 0
self.__sessions = []
self.__configuration_file = configuration_file
self.__secrets_file = secrets_file

self.scfg_dict = self.__get_config_dict()

Expand Down Expand Up @@ -409,6 +410,46 @@ def __get_config_dict(self):
# have been parsed from it.
raise ValueError("Server configuration file was invalid, or "
"empty.")

LOG.debug(self.__secrets_file)
if os.path.exists(self.__secrets_file):
secrets_dict = load_json(self.__secrets_file, {})
check_file_owner_rw(self.__secrets_file)
else:
secrets_dict = {}
Comment thread
Discookie marked this conversation as resolved.

secret_re = re.compile(r'^\$SECRET:[a-zA-Z0-9_-]+\$$')
env_re = re.compile(r'^\$ENV:[a-zA-Z0-9_-]+\$$')

def resolve_variables_failed(var):
if (secret_re.search(var) and
not os.path.exists(self.__secrets_file)):
LOG.error("Secrets were used in server configuration file, "
f"but {self.__secrets_file} does not exist!")

raise ValueError(f"Variable '{var}' could not "
"be resolved in server configuration file.")

def resolve_variables(d):
items = d.items() if isinstance(d, dict) else enumerate(d)

for k, v in items:
if isinstance(v, (dict, list)):
resolve_variables(v)
elif isinstance(v, str):
secret_matched = secret_re.search(v)
env_matched = env_re.search(v)

if secret_matched or env_matched:
var_name = v.split(':')[1][:-1]
if secret_matched and var_name in secrets_dict:
d[k] = secrets_dict[var_name]
elif env_matched and var_name in os.environ:
d[k] = os.environ[var_name]
else:
resolve_variables_failed(v)

resolve_variables(cfg_dict)
return cfg_dict

def reload_config(self):
Expand Down
Loading