Skip to content

Commit

Permalink
Removing the root user creation
Browse files Browse the repository at this point in the history
Instead the user can assign
the super user role to a user
  • Loading branch information
dkrupp authored and bruntib committed Oct 18, 2024
1 parent a48e4ae commit 866f379
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 189 deletions.
61 changes: 28 additions & 33 deletions docs/web/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,40 @@ on the product level.

Table of Contents
=================
* [The master superuser (root)](#the-master-superuser)
* [Managing permissions](#managing-permissions)
* [Permission concepts](#permission-concepts)
* [Default value](#default-value)
* [Permission inheritance](#permission-inheritance)
* [Permission manager](#permission-manager)
* [Available permissions](#available-permissions)
* [Server-wide (global) permissions](#global-permissions)
* [`SUPERUSER`](#superuser)
* [`PERMISSION_VIEW`](#permission-view)
* [Product-level permissions](#product-level-permissions)
* [`PRODUCT_ADMIN`](#product-admin)
* [`PRODUCT_ACCESS`](#product-access)
* [`PRODUCT_STORE`](#product-store)
* [`PRODUCT_VIEW`](#product-view)
- [Permission subsystem](#permission-subsystem)
- [Table of Contents](#table-of-contents)
- [The master superuser (root) ](#the-master-superuser-root-)
- [Managing permissions ](#managing-permissions-)
- [Permission concepts ](#permission-concepts-)
- [Default value ](#default-value-)
- [Permission inheritance ](#permission-inheritance-)
- [Permission manager ](#permission-manager-)
- [Available permissions ](#available-permissions-)
- [Server-wide (global) permissions ](#server-wide-global-permissions-)
- [`SUPERUSER` ](#superuser-)
- [`PERMISSION_VIEW`](#permission_view)
- [Product-level permissions ](#product-level-permissions-)
- [`PRODUCT_ADMIN` ](#product_admin-)
- [`PRODUCT_ACCESS` ](#product_access-)
- [`PRODUCT_STORE` ](#product_store-)
- [`PRODUCT_VIEW` ](#product_view-)

# The master superuser (root) <a name="the-master-superuser"></a>

Each CodeChecker server at its first start generates a master superuser
(*root*) access credential which it prints into its standard output:

At the first CodeChecker startup it is recommended that
you set up a single user with `SUPERUSER` permission.
Then with this user you will be able to configure additional permissions
for other users in the WEB GUI.
Let's say you want to give `SUPERUSER` permission to user `admin`.
Then set `super_user` field in the `server_config.json` configuration file:
```sh
[WARNING] Server started without 'root.user' present in CONFIG_DIRECTORY!
A NEW superuser credential was generated for the server. This information IS
SAVED, thus subsequent server starts WILL use these credentials. You WILL NOT
get to see the credentials again, so MAKE SURE YOU REMEMBER THIS LOGIN!
-----------------------------------------------------------------
The superuser's username is 'AAAAAA' with the password 'aaaa0000'
-----------------------------------------------------------------
"authentication": {
"enabled" : true,
"super_user" : "admin",
...
```
These credentials can be deleted and new ones can be requested by starting
CodeChecker server with the `--reset-root` flag. The credentials are always
**randomly generated**.
If the server has authentication enabled, the *root* user will **always have
access** despite of the configured authentication backends' decision, and
will automatically **have the `SUPERUSER` permission**.

# Managing permissions <a name="managing-permissions"></a>
![Global permission manager](images/permissions.png)
Expand Down Expand Up @@ -184,4 +179,4 @@ delete existing analysis runs from the server.
|---------|-----------------|-----------------|
| Granted | `PRODUCT_ADMIN` | `PRODUCT_ADMIN`, `PRODUCT_STORE`, `PRODUCT_ACCESS` |
Users need the `PRODUCT_VIEW` permission to `view` analysis runs without modifying any properties of the runs.
Users need the `PRODUCT_VIEW` permission to `view` analysis runs without modifying any properties of the runs.
41 changes: 18 additions & 23 deletions docs/web/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
- [Configuring database and server settings location](#configuring-database-and-server-settings-location)
- [Server Configuration (Authentication and Server Limits)](#server-configuration-authentication-and-server-limits)
- [Database Configuration](#database-configuration)
- [Master superuser and authentication forcing](#master-superuser-and-authentication-forcing)
- [Enfore secure socket (TLS/SSL)](#enfore-secure-socket-ssl)
- [Initial super-user](#initial-super-user)
- [Enfore secure socket (TLS/SSL)](#enfore-secure-socket-tlsssl)
- [Managing running servers](#managing-running-servers)
- [Manage server database upgrades](#manage-server-database-upgrades)
- [`store`](#store)
Expand Down Expand Up @@ -150,7 +150,7 @@ usage: CodeChecker server [-h] [-w WORKSPACE] [-f CONFIG_DIRECTORY]
[--sqlite SQLITE_FILE | --postgresql]
[--dbaddress DBADDRESS] [--dbport DBPORT]
[--dbusername DBUSERNAME] [--dbname DBNAME]
[--reset-root] [--force-authentication]
[--force-authentication]
[-l | -r | -s | --stop-all]
[--db-status STATUS | --db-upgrade-schema PRODUCT_TO_UPGRADE | --db-force-upgrade]
[--verbose {info,debug,debug_analyzer}]
Expand Down Expand Up @@ -309,26 +309,21 @@ project.
**It is recommended to use only the Postgresql databse for production
deployments!**

#### Master superuser and authentication forcing

```
root account arguments:
Servers automatically create a root user to access the server's
configuration via the clients. This user is created at first start and
saved in the CONFIG_DIRECTORY, and the credentials are printed to the
server's standard output. The plaintext credentials are NEVER accessible
again.
--reset-root Force the server to recreate the master superuser
(root) account name and password. The previous
credentials will be invalidated, and the new ones will
be printed to the standard output.
--force-authentication
Force the server to run in authentication requiring
mode, despite the configuration value in
'session_config.json'. This is needed if you need to
edit the product configuration of a server that would
not require authentication otherwise.
#### Initial super-user

You can give a single user SUPER_USER permission
by setting the `super_user` field in the `authentication`
section of the `server_config.json`.
The user which is set here, must be an existing user.
For example it should be a user
with dictionary authentication method.

```
"authentication": {
"enabled" : true,
"super_user" : "admin",
...
```

#### Enfore secure socket (TLS/SSL)
Expand Down
20 changes: 0 additions & 20 deletions web/server/codechecker_server/cmd/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,6 @@ def add_arguments_to_parser(parser):
CONFIG_DIRECTORY, and the credentials are printed to the server's standard
output. The plaintext credentials are NEVER accessible again.""")

root_account.add_argument('--reset-root',
dest="reset_root",
action='store_true',
default=argparse.SUPPRESS,
required=False,
help="Force the server to recreate the master "
"superuser (root) account name and "
"password. The previous credentials will "
"be invalidated, and the new ones will be "
"printed to the standard output.")

root_account.add_argument('--force-authentication',
dest="force_auth",
action='store_true',
Expand Down Expand Up @@ -932,15 +921,6 @@ def server_init_start(args):
not os.path.isdir(os.path.dirname(args.sqlite)):
os.makedirs(os.path.dirname(args.sqlite))

if 'reset_root' in args:
try:
os.remove(os.path.join(args.config_directory, 'root.user'))
LOG.info("Master superuser (root) credentials invalidated and "
"deleted. New ones will be generated...")
except OSError:
# File doesn't exist.
pass

if 'force_auth' in args:
LOG.info("'--force-authentication' was passed as a command-line "
"option. The server will ask for users to authenticate!")
Expand Down
70 changes: 11 additions & 59 deletions web/server/codechecker_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@
import atexit
import datetime
from functools import partial
from hashlib import sha256
from http.server import HTTPServer, SimpleHTTPRequestHandler
import os
import posixpath
from random import sample
import shutil
import signal
import socket
import ssl
import sys
import stat
from typing import List, Optional, Tuple
import urllib

Expand Down Expand Up @@ -68,8 +65,6 @@
Configuration as ORMConfiguration
from .database.database import DBSession
from .database.run_db_model import IDENTIFIER as RUN_META, Run, RunLock
from .tmp import get_tmp_dir_hash


LOG = get_logger('server')

Expand Down Expand Up @@ -991,43 +986,6 @@ class CCSimpleHttpServerIPv6(CCSimpleHttpServer):
address_family = socket.AF_INET6


def __make_root_file(root_file):
"""
Generate a root username and password SHA. This hash is saved to the
given file path, and is also returned.
"""

LOG.debug("Generating initial superuser (root) credentials...")

username = ''.join(sample("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6))
password = get_tmp_dir_hash()[:8]

LOG.info("A NEW superuser credential was generated for the server. "
"This information IS SAVED, thus subsequent server starts "
"WILL use these credentials. You WILL NOT get to see "
"the credentials again, so MAKE SURE YOU REMEMBER THIS "
"LOGIN!")

# Highlight the message a bit more, as the server owner configuring the
# server must know this root access initially.
credential_msg = f"The superuser's username is '{username}' with the " \
f"password '{password}'"
LOG.info("-" * len(credential_msg))
LOG.info(credential_msg)
LOG.info("-" * len(credential_msg))

sha = sha256((username + ':' + password).encode('utf-8')).hexdigest()
secret = f"{username}:{sha}"
with open(root_file, 'w', encoding="utf-8", errors="ignore") as f:
LOG.debug("Save root SHA256 '%s'", secret)
f.write(secret)

# This file should be only readable by the process owner, and noone else.
os.chmod(root_file, stat.S_IRUSR)

return secret


def start_server(config_directory, package_data, port, config_sql_server,
listen_address, force_auth, skip_db_cleanup: bool,
context, check_env):
Expand All @@ -1038,22 +996,17 @@ def start_server(config_directory, package_data, port, config_sql_server,

server_addr = (listen_address, port)

# The root user file is DEPRECATED AND IGNORED
root_file = os.path.join(config_directory, 'root.user')
if not os.path.exists(root_file):
LOG.warning("Server started without 'root.user' present in "
"CONFIG_DIRECTORY!")
root_sha = __make_root_file(root_file)
else:
LOG.debug("Root file was found. Loading...")
try:
with open(root_file, 'r', encoding="utf-8", errors="ignore") as f:
root_sha = f.read()
LOG.debug("Root digest is '%s'", root_sha)
except IOError:
LOG.info("Cannot open root file '%s' even though it exists",
root_file)
root_sha = __make_root_file(root_file)

if os.path.exists(root_file):
LOG.warning("The 'root.user' file: %s"
" is deprecated and ignored. If you want to"
" setup an initial user with SUPER_USER permission,"
" configure the super_user field in the server_config.json"
" as described in the documentation."
" To get rid off this warning,"
" simply delete the root.user file.",
root_file)
# Check whether configuration file exists, create an example if not.
server_cfg_file = os.path.join(config_directory, 'server_config.json')
if not os.path.exists(server_cfg_file):
Expand All @@ -1077,7 +1030,6 @@ def start_server(config_directory, package_data, port, config_sql_server,
try:
manager = session_manager.SessionManager(
server_cfg_file,
root_sha,
force_auth)
except IOError as ioerr:
LOG.debug(ioerr)
Expand All @@ -1098,7 +1050,7 @@ def start_server(config_directory, package_data, port, config_sql_server,
"Earlier logs might contain additional detailed "
"reasoning.\n\t* %s", len(fails),
"\n\t* ".join(
(f"'{ep}' ({reason})" for (ep, reason) in fails)
(f"'{ep}' ({reason})" for (ep, reason) in fails)
))
else:
LOG.debug("Skipping db_cleanup, as requested.")
Expand Down
43 changes: 10 additions & 33 deletions web/server/codechecker_server/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
Handles the management of authentication sessions on the server's side.
"""

import hashlib
import json
import os
import re
Expand Down Expand Up @@ -161,13 +160,12 @@ class SessionManager:
CodeChecker server.
"""

def __init__(self, configuration_file, root_sha, force_auth=False):
def __init__(self, configuration_file, force_auth=False):
"""
Initialise a new Session Manager on the server.
:param configuration_file: The configuration file to read
authentication backends from.
:param root_sha: The SHA-256 hash of the root user's authentication.
:param force_auth: If True, the manager will be enabled even if the
configuration file disables authentication.
"""
Expand Down Expand Up @@ -199,9 +197,6 @@ def __init__(self, configuration_file, root_sha, force_auth=False):
self.__refresh_time = self.__auth_config['refresh_time'] \
if 'refresh_time' in self.__auth_config else None

# Save the root SHA into the configuration (but only in memory!)
self.__auth_config['method_root'] = root_sha

self.__regex_groups_enabled = False

# Pre-compile the regular expressions of 'regex_groups'
Expand Down Expand Up @@ -334,17 +329,16 @@ def get_realm(self):
"error": self.__auth_config.get('realm_error')
}

@property
def get_super_user(self):
return {
"super_user": self.__auth_config.get('super_user'),
}

@property
def default_superuser_name(self) -> Optional[str]:
""" Get default superuser name. """
root = self.__auth_config['method_root'].split(":")

# Previously the root file doesn't contain the user name. In this case
# we will return with no user name.
if len(root) <= 1:
return None

return root[0]
return self.__auth_config['super_user']

def set_database_connection(self, connection):
"""
Expand All @@ -365,8 +359,7 @@ def __handle_validation(self, auth_string):
This validation object contains two keys: username and groups.
"""
validation = self.__try_auth_root(auth_string) \
or self.__try_auth_dictionary(auth_string) \
validation = self.__try_auth_dictionary(auth_string) \
or self.__try_auth_pam(auth_string) \
or self.__try_auth_ldap(auth_string)
if not validation:
Expand All @@ -387,22 +380,6 @@ def __is_method_enabled(self, method):
'method_' + method in self.__auth_config and \
self.__auth_config['method_' + method].get('enabled')

def __try_auth_root(self, auth_string):
"""
Try to authenticate the user against the root username:password's hash.
"""
user_name = SessionManager.get_user_name(auth_string)
sha = hashlib.sha256(auth_string.encode('utf8')).hexdigest()

if f"{user_name}:{sha}" == self.__auth_config['method_root']:
return {
'username': SessionManager.get_user_name(auth_string),
'groups': [],
'root': True
}

return False

def __try_auth_token(self, auth_string):
if not self.__database_connection:
return None
Expand Down Expand Up @@ -562,7 +539,7 @@ def get_db_auth_session_tokens(self, user_name):

def __is_root_user(self, user_name):
""" Return True if the given user has system permissions. """
if self.__auth_config['method_root'].split(":")[0] == user_name:
if self.__auth_config['super_user'] == user_name:
return True

transaction = None
Expand Down
Loading

0 comments on commit 866f379

Please sign in to comment.