From c4d10f0ef8ad98e60a1d436ccf1e258cc148f3b2 Mon Sep 17 00:00:00 2001 From: Christopher <1289128+dragonfire1119@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:16:45 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(big-bear-casaos-user-managemen?= =?UTF-8?q?t):=20Add=20user=20management=20system=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat(big-bear-casaos-user-management): Add user management system This commit introduces a new user management system for the BigBearCasaOS project. The key changes include: - Added a Dockerfile to build a Docker image for the user management system - Created a Docker Compose file to easily deploy the user management service - Implemented a Flask-based web application to manage user accounts - Added an HTML template for the user management interface - Configured environment variables for the admin username and password - Granted the container extended privileges and capabilities for system integration These changes enable the CasaOS platform to manage user accounts and roles, improving the overall security and usability of the system. * 🔧 feat(user-management): Enhance user management service configuration Modify the Docker Compose configuration for the CasaOS user management service: - Expose port 5000 on the host and map it to the container - Mount the cgroup filesystem in read-only mode for system resource monitoring - Persist the CasaOS database files to retain data across container restarts - Mount systemd's runtime directory for integration with the host system - Provide access to the D-Bus system bus for communication with host services - Set the Flask application entry point and production environment - Specify the admin username and password for the service - Grant the container extended privileges and the SYS_ADMIN capability - Disable the default seccomp security profile to allow unrestricted system calls - Rename the service from "big-bear-casaos-user-manager" to "big-bear-casaos-user-management" for consistency --- ...e_for_big_bear_casaos_user_management.yaml | 47 +++++ .gitignore | 4 + big-bear-casaos-user-management/Dockerfile | 27 +++ big-bear-casaos-user-management/VERSION | 1 + big-bear-casaos-user-management/app/app.py | 71 ++++++++ .../app/templates/base.html | 15 ++ .../app/templates/index.html | 128 +++++++++++++ .../app/user_management.py | 170 ++++++++++++++++++ .../docker-compose.yml | 45 +++++ 9 files changed, 508 insertions(+) create mode 100644 .github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml create mode 100644 big-bear-casaos-user-management/Dockerfile create mode 100644 big-bear-casaos-user-management/VERSION create mode 100644 big-bear-casaos-user-management/app/app.py create mode 100644 big-bear-casaos-user-management/app/templates/base.html create mode 100644 big-bear-casaos-user-management/app/templates/index.html create mode 100644 big-bear-casaos-user-management/app/user_management.py create mode 100644 big-bear-casaos-user-management/docker-compose.yml diff --git a/.github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml b/.github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml new file mode 100644 index 0000000..6d37acb --- /dev/null +++ b/.github/workflows/build_and_release_for_big_bear_casaos_user_management.yaml @@ -0,0 +1,47 @@ +name: "Build and release for big-bear-casaos-user-management" + +on: + push: + branches: + - main + paths: + - "big-bear-casaos-user-management/**" + +jobs: + create: + name: "Creates the newest release by version" + runs-on: "ubuntu-latest" + + steps: + - name: Checkout project + uses: actions/checkout@v2.3.4 + + # New step to read the VERSION file and set the version as an output + - name: Get the version + id: get_version + run: echo "big_bear_casaos_user_management_version=$(cat big-bear-casaos-user-management/VERSION)" >> $GITHUB_ENV + + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + + - name: Set up Docker Build + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + platforms: linux/amd64,linux/arm64 + context: ./big-bear-casaos-user-management + file: ./big-bear-casaos-user-management/Dockerfile + tags: | + bigbeartechworld/big-bear-casaos-user-management:latest + bigbeartechworld/big-bear-casaos-user-management:${{ env.big_bear_casaos_user_management_version }} diff --git a/.gitignore b/.gitignore index 9804a16..ed1ad03 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ pihole-unbound/build.log pihole-unbound/build.sh genmon/build.log genmon/build.sh +genmon/build.log +genmon/build.sh +big-bear-casaos-user-management/build.log +big-bear-casaos-user-management/build.sh \ No newline at end of file diff --git a/big-bear-casaos-user-management/Dockerfile b/big-bear-casaos-user-management/Dockerfile new file mode 100644 index 0000000..18dcf4f --- /dev/null +++ b/big-bear-casaos-user-management/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.9-slim + +# Install system dependencies (systemd includes systemctl) +RUN apt-get update && \ + apt-get install -y systemd sudo && \ + rm -rf /var/lib/apt/lists/* + +# Create necessary directories +RUN mkdir -p /var/lib/casaos/db + +# Set working directory +WORKDIR /app + +# Copy all application files +COPY app/ . + +# Install Python dependencies +RUN pip install flask flask-wtf + +# Create a volume for persistent storage +VOLUME /var/lib/casaos/db + +# Expose port for Flask +EXPOSE 5000 + +# Run Flask app +CMD ["python", "app.py"] diff --git a/big-bear-casaos-user-management/VERSION b/big-bear-casaos-user-management/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/big-bear-casaos-user-management/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/big-bear-casaos-user-management/app/app.py b/big-bear-casaos-user-management/app/app.py new file mode 100644 index 0000000..9c20912 --- /dev/null +++ b/big-bear-casaos-user-management/app/app.py @@ -0,0 +1,71 @@ +from flask import Flask, render_template, redirect, url_for, flash, request +from user_management import (list_users, add_user, edit_password, + remove_user, reset_database, hash_password) +from functools import wraps +from flask import request, Response +import os + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'your-secret-key' + +ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME', 'admin') +ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'YOUR_SECURE_PASSWORD') + +def check_auth(username, password): + """Verify admin credentials""" + return username == ADMIN_USERNAME and password == ADMIN_PASSWORD + +def authenticate(): + return Response( + 'Could not verify your access level for that URL.\n' + 'You have to login with proper credentials', 401, + {'WWW-Authenticate': 'Basic realm="Login Required"'} + ) + +def requires_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + auth = request.authorization + if not auth or not check_auth(auth.username, auth.password): + return authenticate() + return f(*args, **kwargs) + return decorated + +@app.route('/') +@requires_auth +def index(): + users = list_users(return_data=True) + return render_template('index.html', users=users) + +@app.route('/add_user', methods=['POST']) +def add_user_route(): + username = request.form.get('username') + password = request.form.get('password') + if add_user(username, password): + flash('User added successfully', 'success') + return redirect(url_for('index')) + +@app.route('/edit_password', methods=['POST']) +def edit_password_route(): + user_id = request.form.get('user_id') + new_password = request.form.get('new_password') + if edit_password(user_id, new_password): + flash('Password updated successfully', 'success') + return redirect(url_for('index')) + +@app.route('/remove_user', methods=['POST']) +def remove_user_route(): + user_id = request.form.get('user_id') + if remove_user(user_id): + flash('User removed successfully', 'success') + return redirect(url_for('index')) + +@app.route('/reset_database', methods=['POST']) +@requires_auth +def reset_database_route(): + if reset_database(): + flash('Database reset successfully', 'success') + return redirect(url_for('index')) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/big-bear-casaos-user-management/app/templates/base.html b/big-bear-casaos-user-management/app/templates/base.html new file mode 100644 index 0000000..5733ef6 --- /dev/null +++ b/big-bear-casaos-user-management/app/templates/base.html @@ -0,0 +1,15 @@ + + + + BigBearCasaOS User Management + + + + + +
{% block content %}{% endblock %}
+ + diff --git a/big-bear-casaos-user-management/app/templates/index.html b/big-bear-casaos-user-management/app/templates/index.html new file mode 100644 index 0000000..1beb4bc --- /dev/null +++ b/big-bear-casaos-user-management/app/templates/index.html @@ -0,0 +1,128 @@ +{% extends "base.html" %} {% block content %} +

BigBearCasaOS User Management

+ + +
+
Users
+
+ + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
IDUsernameRoleActions
{{ user[0] }}{{ user[1] }}{{ user[2] }} + +
+ + +
+
+ + + {% for user in users %} + + {% endfor %} +
+
+ + +
+
Add New User
+
+
+
+ + +
+
+ + +
+ +
+
+
+ +
+
Database Management
+
+
+ +
+
+
+{% endblock %} diff --git a/big-bear-casaos-user-management/app/user_management.py b/big-bear-casaos-user-management/app/user_management.py new file mode 100644 index 0000000..f192a02 --- /dev/null +++ b/big-bear-casaos-user-management/app/user_management.py @@ -0,0 +1,170 @@ +# Based on https://github.com/carlbomsdata/casaos-user-management +# Original discussion: https://community.bigbeartechworld.com/t/casaos-user-management/2225 +# Modified version by bigbeartechworld: Added UI implementation and adapted user_management.py +# to integrate with the UI while maintaining core functionality from the original. + +import sqlite3 +import os +import shutil +import subprocess +import hashlib +import getpass +from datetime import datetime + +# Constants +DB_PATH = "/var/lib/casaos/db/user.db" +BACKUP_PATH = "/var/lib/casaos/db/user_backup.db" +SERVICE_NAME = "casaos-user-service.service" + +# Helper Functions +def check_casaos_installation(): + if not os.path.exists(DB_PATH): + return False + service_status = subprocess.run( + ["systemctl", "status", SERVICE_NAME], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return service_status.returncode == 0 + +def backup_database(): + try: + shutil.copy(DB_PATH, BACKUP_PATH) + return True + except Exception: + return False + +def hash_password(password): + """Hash the password using MD5.""" + return hashlib.md5(password.encode()).hexdigest() + +def connect_db(): + """Connect to the SQLite database.""" + try: + return sqlite3.connect(DB_PATH) + except sqlite3.Error as e: + print(f"Database connection error: {e}") + exit(1) + +def manage_service(action): + """Start, stop, or restart the CasaOS user service on host OS.""" + try: + result = subprocess.run( + ["systemctl", "--system", action, SERVICE_NAME], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True + ) + return True + except subprocess.CalledProcessError as e: + print(f"Service {action} failed: {e.stderr}") + return False + +def list_users(return_data=False): + """List all users in the database.""" + conn = connect_db() + cursor = conn.cursor() + try: + cursor.execute("SELECT id, username, role FROM o_users") + users = cursor.fetchall() + if return_data: + return users + else: + if users: + print("\nUsers in the database:") + for user in users: + print(f"ID: {user[0]}, Username: {user[1]}, Role: {user[2]}") + else: + print("\nNo users found.") + except sqlite3.Error as e: + print(f"Error fetching users: {e}") + return [] if return_data else None + finally: + conn.close() + +def add_user(username, password): + """Add a new user to the database.""" + if not username or not password: + print("Username and password cannot be empty.") + return False + + conn = connect_db() + cursor = conn.cursor() + try: + # Check for duplicate username + cursor.execute("SELECT 1 FROM o_users WHERE username = ?", (username,)) + if cursor.fetchone(): + print(f"Error: A user with the username '{username}' already exists.") + return False + + hashed_password = hash_password(password) + created_at = datetime.now().isoformat() + + # Insert the user into the database + cursor.execute( + "INSERT INTO o_users (username, password, role, created_at) VALUES (?, ?, ?, ?)", + (username, hashed_password, "admin", created_at), + ) + conn.commit() + print(f"User '{username}' added successfully.") + return True + except sqlite3.Error as e: + print(f"Error adding user: {e}") + return False + finally: + conn.close() + +def edit_password(user_id, new_password): + if not user_id or not new_password: + return False + + hashed_password = hash_password(new_password) + updated_at = datetime.now().isoformat() + + conn = connect_db() + cursor = conn.cursor() + try: + cursor.execute( + "UPDATE o_users SET password = ?, updated_at = ? WHERE id = ?", + (hashed_password, updated_at, user_id), + ) + if cursor.rowcount > 0: + conn.commit() + return True + return False + except sqlite3.Error: + return False + finally: + conn.close() + +def remove_user(user_id): + if not user_id: + return False + + conn = connect_db() + cursor = conn.cursor() + try: + cursor.execute("DELETE FROM o_users WHERE id = ?", (user_id,)) + if cursor.rowcount > 0: + conn.commit() + return True + return False + except sqlite3.Error: + return False + finally: + conn.close() + +def reset_database(): + """Reset the database by deleting the user.db file.""" + try: + if os.path.exists(DB_PATH): + os.remove(DB_PATH) + if not manage_service("restart"): + print("Warning: Database reset successful but service restart failed") + return True + return False + except Exception as e: + print(f"Reset failed: {str(e)}") + return False + diff --git a/big-bear-casaos-user-management/docker-compose.yml b/big-bear-casaos-user-management/docker-compose.yml new file mode 100644 index 0000000..03153cb --- /dev/null +++ b/big-bear-casaos-user-management/docker-compose.yml @@ -0,0 +1,45 @@ +services: + big-bear-casaos-user-management: + # The service name for the user management system + image: bigbeartechworld/big-bear-casaos-user-management:0.0.1 + # The Docker image to use for this service, with the specified version tag + container_name: big-bear-casaos-user-management + # A custom name for the container, making it easier to identify + + ports: + # Exposing port 5000 on the host and mapping it to port 5000 in the container + - "5000:5000" + + volumes: + # Mounting the cgroup filesystem in read-only mode for system resource monitoring + - /sys/fs/cgroup:/sys/fs/cgroup:ro + # Persisting CasaOS database files to retain data across container restarts + - /var/lib/casaos/db:/var/lib/casaos/db + # Mounting systemd's runtime directory for integration with the host system + - /run/systemd/system:/run/systemd/system + # Providing access to the D-Bus system bus for communication with host services + - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket + + # Ensures the container restarts automatically unless explicitly stopped + restart: unless-stopped + + environment: + # Sets the Flask application entry point + - FLASK_APP=app.py + # Configures Flask to run in production mode + - FLASK_ENV=production + # Specifies the admin username for the service + - ADMIN_USERNAME=casaos + # Specifies the admin password for the service + - ADMIN_PASSWORD=casaos + + # Grants the container extended privileges, necessary for some operations + privileged: true + + cap_add: + # Adds the SYS_ADMIN capability to the container for advanced administrative actions + - SYS_ADMIN + + security_opt: + # Disables the default seccomp security profile to allow unrestricted system calls + - seccomp:unconfined \ No newline at end of file