diff --git a/docs/web/permissions.md b/docs/web/permissions.md index 7152c301da..69a2683e1e 100644 --- a/docs/web/permissions.md +++ b/docs/web/permissions.md @@ -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) -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 ![Global permission manager](images/permissions.png) @@ -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. \ No newline at end of file +Users need the `PRODUCT_VIEW` permission to `view` analysis runs without modifying any properties of the runs. \ No newline at end of file diff --git a/docs/web/user_guide.md b/docs/web/user_guide.md index 846599b76a..c5ca9b9004 100644 --- a/docs/web/user_guide.md +++ b/docs/web/user_guide.md @@ -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) @@ -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}] @@ -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) diff --git a/web/server/codechecker_server/cmd/server.py b/web/server/codechecker_server/cmd/server.py index 33bbbd20f1..c7781250b8 100644 --- a/web/server/codechecker_server/cmd/server.py +++ b/web/server/codechecker_server/cmd/server.py @@ -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', @@ -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!") diff --git a/web/server/codechecker_server/server.py b/web/server/codechecker_server/server.py index 40bdf6db4d..36f2713bd2 100644 --- a/web/server/codechecker_server/server.py +++ b/web/server/codechecker_server/server.py @@ -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 @@ -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') @@ -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): @@ -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): @@ -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) @@ -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.") diff --git a/web/server/codechecker_server/session_manager.py b/web/server/codechecker_server/session_manager.py index 276af909cd..2bcae3cbc9 100644 --- a/web/server/codechecker_server/session_manager.py +++ b/web/server/codechecker_server/session_manager.py @@ -9,7 +9,6 @@ Handles the management of authentication sessions on the server's side. """ -import hashlib import json import os import re @@ -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. """ @@ -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' @@ -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): """ @@ -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: @@ -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 @@ -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 diff --git a/web/server/vue-cli/e2e/init.workspace.js b/web/server/vue-cli/e2e/init.workspace.js index 9ca21579f5..c1e43a5453 100644 --- a/web/server/vue-cli/e2e/init.workspace.js +++ b/web/server/vue-cli/e2e/init.workspace.js @@ -7,20 +7,19 @@ const WORKSPACE_DIR = path.join(CC_DIR, "workspace"); const SERVER_CONFIG = { authentication: { enabled : true, + "super_user" : "root", session_lifetime : 60000, refresh_time : 60, logins_until_cleanup : 30, method_dictionary: { enabled : true, - auths : [ "cc:admin" ], + auths : [ "cc:admin", + "root:S3cr3t" ], groups : {} } } }; -const ROOT_USER = - "root:2691b13e4c5eadd0adad38983e611b2caa19caaa3476ccf31cbcadddf65c321c"; - // Create workspace directory if it does not exists. if (!fs.existsSync(WORKSPACE_DIR)) { fs.mkdirSync(WORKSPACE_DIR); @@ -29,10 +28,4 @@ if (!fs.existsSync(WORKSPACE_DIR)) { // Create server configuration file and enable authentication. const serverConfigFile = path.join(WORKSPACE_DIR, "server_config.json"); const data = JSON.stringify(SERVER_CONFIG, null, " "); -fs.writeFileSync(serverConfigFile, data); - -// Generate initial root credentials. -// - username: root -// - password: S3cr3t -const rootUserFile = path.join(WORKSPACE_DIR, "root.user"); -fs.writeFileSync(rootUserFile, ROOT_USER); +fs.writeFileSync(serverConfigFile, data); \ No newline at end of file diff --git a/web/tests/libtest/env.py b/web/tests/libtest/env.py index 1610db8bef..725fb0d177 100644 --- a/web/tests/libtest/env.py +++ b/web/tests/libtest/env.py @@ -10,13 +10,11 @@ """ -from hashlib import sha256 import os import json import tempfile import shutil import socket -import stat import subprocess from codechecker_common.util import load_json @@ -350,11 +348,12 @@ def enable_auth(workspace): scfg_dict = load_json(server_cfg_file, {}) scfg_dict["authentication"]["enabled"] = True + scfg_dict["authentication"]["super_user"] = "root" scfg_dict["authentication"]["method_dictionary"]["enabled"] = True scfg_dict["authentication"]["method_dictionary"]["auths"] = \ ["cc:test", "john:doe", "admin:admin123", "colon:my:password", "admin_group_user:admin123", "regex_admin:blah", - "permission_view_user:pvu"] + "permission_view_user:pvu", "root:root"] scfg_dict["authentication"]["method_dictionary"]["groups"] = \ {"admin_group_user": ["admin_GROUP"]} scfg_dict["authentication"]["regex_groups"]["enabled"] = True @@ -363,13 +362,6 @@ def enable_auth(workspace): encoding="utf-8", errors="ignore") as scfg: json.dump(scfg_dict, scfg, indent=2, sort_keys=True) - # Create a root user. - root_file = os.path.join(workspace, 'root.user') - with open(root_file, 'w', - encoding='utf-8', errors='ignore') as rootf: - rootf.write(f"root:{sha256(b'root:root').hexdigest()}") - os.chmod(root_file, stat.S_IRUSR | stat.S_IWUSR) - def enable_storage_of_analysis_statistics(workspace): """