diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..a3fac80 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,27 @@ +{ + "mcpServers": { + "sequentialthinking": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-sequential-thinking" + ] + }, + "serena": { + "command": "uvx", + "args": [ + "--from", + "git+https://github.com/oraios/serena", + "serena", + "start-mcp-server", + "--context", + "ide-assistant", + "--project", + "/Users/arthur/Desktop/code/vpn-access-server" + ] + }, + "context7": { + "url": "https://mcp.context7.com/sse" + } + } +} \ No newline at end of file diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..5fbf35c --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,71 @@ +# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby) +# * For C, use cpp +# * For JavaScript, use typescript +# Special requirements: +# * csharp: Requires the presence of a .sln file in the project folder. +language: python + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# whether to use the project's gitignore file to ignore files +# Added on 2025-04-07 +ignore_all_files_in_gitignore: true +# list of additional paths to ignore +# same syntax as gitignore, so you can use * and ** +# Was previously called `ignored_dirs`, please update your config if you are using that. +# Added (renamed) on 2025-04-07 +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +project_name: "vpn-access-server" diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..113c03a --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,93 @@ +# GEMINI.md + +## Project Overview + +This project is a **VPN Access Server**, written in Python, designed to extend an OpenVPN server with custom authentication and session management features. It uses a MySQL database to store user information, authorized MAC addresses, and session data. + +The core functionality is to: +1. **Validate MAC Addresses**: Ensure only devices with authorized MAC addresses can connect to the VPN. +2. **Enforce Session Time**: Manage and enforce session duration policies on a per-user basis. + +The architecture is based on OpenVPN's `client-connect` and `client-disconnect` hooks, which execute Python scripts to perform the validation logic. + +**Technologies:** +* **Language:** Python 3 +* **Database:** MySQL +* **VPN:** OpenVPN (as the execution environment) +* **Dependencies:** `mysql-connector-python`, `pytest` + +## Building and Running + +The project is managed via a central command-line interface in `main.py`. + +### Initial Setup + +1. **Install Dependencies:** + ```bash + uv pip install -r requirements.txt + ``` + *(Note: Based on `pyproject.toml`, you might use `uv pip install .` or `uv pip install -e .` for editable mode)* + +2. **Configure Environment:** + Create a `.env` file based on `.env.example` and provide the necessary database credentials: + ```bash + cp .env.example .env + # Edit .env with your DB settings + ``` + +3. **Initialize the Database:** + This script will create the necessary tables (`employees`, `mac_addresses`, `sessions`) in the database. + ```bash + python main.py init-db + ``` + +### Running the Application + +The main application logic is triggered by OpenVPN. However, you can run the individual components for testing or health checks. + +* **Run a Health Check:** + Verifies the configuration and database connection. + ```bash + python main.py health-check + ``` + +* **Show Status:** + Displays the current configuration. + ```bash + python main.py status + ``` + +* **Run Authentication/Session Logic (Manual Simulation):** + These commands are intended to be called by OpenVPN but can be run manually if the required environment variables are set. + ```bash + # Simulate OpenVPN client-connect + python main.py auth + + # Simulate OpenVPN client-disconnect + python main.py session + ``` + +### Running Tests + +The project uses `pytest` for testing. + +```bash +python main.py test +``` +or +```bash +pytest +``` + +## Development Conventions + +* **Modular Architecture:** The code is structured into modules with clear responsibilities: + * `access/auth.py`: User authentication and MAC validation. + * `access/session.py`: Session management. + * `access/db.py`: Database interaction. + * `access/config.py`: Configuration loading. + * `access/utils.py`: Shared utility functions. +* **Configuration:** Configuration is loaded from environment variables, following the 12-factor app methodology. A `.env` file is used for local development. +* **Database Management:** Database connections are managed through a connection pool in `db.py` for efficiency. All SQL queries are centralized in this file. +* **CLI:** A single entry point (`main.py`) provides a consistent way to interact with and manage the application. +* **Testing:** Unit tests are located in the `tests/` directory and can be run with `pytest`. diff --git a/docs/account-creation.md b/docs/account-creation.md new file mode 100644 index 0000000..741b29b --- /dev/null +++ b/docs/account-creation.md @@ -0,0 +1,102 @@ +1. run this command to generated rquired client file: +```commandline +cd /etc/openvpn/easy-rsa/ +./easyrsa build-client-full nopass +``` +# example create test client key for user name: [arthur] +arthur@server:~/openvpn-ca$ ./easyrsa gen-req arthur nopass +Using Easy-RSA 'vars' configuration: +* /home/arthur/openvpn-ca/vars + +Using SSL: +* openssl OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024) +....+...+...+..+.......+.....+......+...+.......+.....+...+..........+.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.+...........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +..+...............+...+.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.+............+......+.......+.....+...+....+..+......+.......+......+......+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...+..+......+....+..................+...+.....+.......+...+......+.....+....+.........+......+.....+....+..+.+........+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +----- +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Common Name (eg: your user, host, or server name) [arthur]: + +Notice +------ +Private-Key and Public-Certificate-Request files created. +Your files are: +* req: /home/arthur/openvpn-ca/pki/reqs/arthur.req +* key: /home/arthur/openvpn-ca/pki/private/arthur.key + +arthur@server:~/openvpn-ca$ ./easyrsa sign-req client arthur +Using Easy-RSA 'vars' configuration: +* /home/arthur/openvpn-ca/vars + +Using SSL: +* openssl OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024) +You are about to sign the following certificate: +Please check over the details shown below for accuracy. Note that this request +has not been cryptographically verified. Please be sure it came from a trusted +source or that you have verified the request checksum with the sender. +Request subject, to be signed as a client certificate +for '825' days: + +subject= + commonName = arthur + +Type the word 'yes' to continue, or any other input to abort. + Confirm request details: yes + +Using configuration from /home/arthur/openvpn-ca/pki/openssl-easyrsa.cnf +Check that the request matches the signature +Signature ok +The Subject's Distinguished Name is as follows +commonName :ASN.1 12:'arthur' +Certificate is to be certified until Dec 19 04:09:41 2027 GMT (825 days) + +Write out database with 1 new entries +Database updated + +Notice +------ +Certificate created at: +* /home/arthur/openvpn-ca/pki/issued/arthur.crt + + +2. OpenVPN Server Configuration\ +```commandline + +### Check location of key before copy +------ +CA creation complete. Your new CA certificate is at: +* /home/arthur/openvpn-ca/pki/ca.crt +------ +Certificate created at: +* /home/arthur/openvpn-ca/pki/issued/server.crt +------ +Private-Key and Public-Certificate-Request files created. +Your files are: +* req: /home/arthur/openvpn-ca/pki/reqs/server.req +* key: /home/arthur/openvpn-ca/pki/private/server.key +------ +DH parameters of size 2048 created at: +* /home/arthur/openvpn-ca/pki/dh.pem +------ +TA Key at: +* /home/arthur/openvpn-ca/ta.key + +### Start copying requirement files +arthur@server:~/openvpn-ca$ sudo cp pki/ca.crt pki/issued/server.crt pki/private/server.key ta.key pki/dh.pem /etc/openvpn/server/ + +### Check the destination again +arthur@server:~/openvpn-ca$ ls -la /etc/openvpn/server/ +total 32 +drwxr-xr-x 2 root root 4096 Sep 15 04:17 . +drwxr-xr-x 4 root root 4096 Sep 15 03:39 .. +-rw------- 1 root root 1245 Sep 15 04:17 ca.crt +-rw------- 1 root root 424 Sep 15 04:17 dh.pem +-rw------- 1 root root 4728 Sep 15 04:17 server.crt +-rw------- 1 root root 1708 Sep 15 04:17 server.key +-rw------- 1 root root 636 Sep 15 04:17 ta.key +``` \ No newline at end of file diff --git a/main.py b/main.py index d5b8a27..53bef5c 100755 --- a/main.py +++ b/main.py @@ -104,6 +104,14 @@ def show_status(): print(f"Max Session Limit: {config.server.max_session_limit}s ({config.server.max_session_limit//3600}h)") print("=" * 50) +from util.client.generate_client import generate_client_config + + +def run_gen_client(username: str): + """Generate a client .ovpn file.""" + generate_client_config(username) + + def main(): """Main entry point with command-line interface.""" parser = argparse.ArgumentParser( @@ -118,6 +126,7 @@ Examples: %(prog)s test # Run unit tests %(prog)s health-check # Check system health %(prog)s status # Show configuration status + %(prog)s gen-client # Generate a client .ovpn file Environment Variables: DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD @@ -127,11 +136,18 @@ See .env.example for full configuration options. """ ) - parser.add_argument( - 'command', - choices=['auth', 'session', 'init-db', 'seed-data', 'test', 'health-check', 'status'], - help='Command to execute' - ) + subparsers = parser.add_subparsers(dest='command', help='Commands') + + subparsers.add_parser('auth', help='Run authentication (for OpenVPN)') + subparsers.add_parser('session', help='Run session management (for OpenVPN)') + subparsers.add_parser('init-db', help='Initialize database schema') + subparsers.add_parser('seed-data', help='Add sample data for testing') + subparsers.add_parser('test', help='Run unit tests') + subparsers.add_parser('health-check', help='Check system health') + subparsers.add_parser('status', help='Show configuration status') + + gen_client_parser = subparsers.add_parser('gen-client', help='Generate a client .ovpn file') + gen_client_parser.add_argument('username', help='Username for the client config') parser.add_argument( '--version', @@ -139,7 +155,7 @@ See .env.example for full configuration options. version='VPN Access Server 1.0.0' ) - if len(sys.argv) == 1: + if len(sys.argv) < 2: parser.print_help() sys.exit(1) @@ -161,6 +177,8 @@ See .env.example for full configuration options. sys.exit(1) elif args.command == 'status': show_status() + elif args.command == 'gen-client': + run_gen_client(args.username) except KeyboardInterrupt: print("\n⚠️ Operation cancelled by user") diff --git a/scripts/gen-client.sh b/scripts/gen-client.sh new file mode 100755 index 0000000..2c45aea --- /dev/null +++ b/scripts/gen-client.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 4 ]]; then + cat >&2 <.ovpn +USAGE + exit 1 +fi + +ca="$1" +cert="$2" +key="$3" +ta="$4" + +# verify files exist and are readable +for f in "$ca" "$cert" "$key" "$ta"; do + if [[ ! -r "$f" ]]; then + echo "Error: cannot read file '$f'." >&2 + exit 2 + fi +done + +# derive client name from certificate filename (remove extension) +clientname="$(basename "$cert")" +clientname="${clientname%.*}" +outfile="${clientname}.ovpn" + +# build and write config (also send to stdout). Use a block to avoid command-substitution problems with large files. +{ +cat <<'HEADER' +client +dev tun +proto udp +remote 14.241.240.102 1194 # use FTP IP address +resolv-retry infinite +nobind +persist-key +persist-tun +remote-cert-tls server +cipher AES-256-GCM +# push mac address info +push-peer-info +verb 3 + +HEADER + +echo "" +cat "$ca" +echo "" +echo +echo "" +cat "$cert" +echo "" +echo +echo "" +cat "$key" +echo "" +echo +echo "" +cat "$ta" +echo "" +echo "key-direction 1" +} | tee "$outfile" + +echo "Wrote config to ./${outfile}" + diff --git a/user-data/data.xlsx b/user-data/data.xlsx new file mode 100644 index 0000000..e79e7ee Binary files /dev/null and b/user-data/data.xlsx differ diff --git a/util/client/generate_client.py b/util/client/generate_client.py new file mode 100644 index 0000000..59e9acc --- /dev/null +++ b/util/client/generate_client.py @@ -0,0 +1,89 @@ +""" +Utility for generating OpenVPN client configuration files. +""" + +import os + +def generate_client_config(username: str): + """ + Generates a .ovpn file for a given user. + + Args: + username: The username for which to generate the config. + """ + ca_path = "/home/arthur/openvpn-ca/pki/ca.crt" + ta_path = "/home/arthur/openvpn-ca/ta.key" + client_crt_path = f"/home/arthur/openvpn-ca/pki/issued/{username}.crt" + client_key_path = f"/home/arthur/openvpn-ca/pki/private/{username}.key" + output_path = f"/etc/openvpn/client/{username}.ovpn" + + # Verify that all required files exist + for f in [ca_path, ta_path, client_crt_path, client_key_path]: + if not os.path.isfile(f): + print(f"Error: Cannot read file '{f}'.") + return + + # Read the content of the files + try: + with open(ca_path, 'r') as f: + ca_content = f.read() + with open(client_crt_path, 'r') as f: + client_crt_content = f.read() + with open(client_key_path, 'r') as f: + client_key_content = f.read() + with open(ta_path, 'r') as f: + ta_content = f.read() + except IOError as e: + print(f"Error reading files: {e}") + return + + # Assemble the .ovpn configuration + ovpn_config = f""" +client +dev tun +proto udp +remote 14.241.240.102 1194 # use FTP IP address +resolv-retry infinite +nobind +persist-key +persist-tun +remote-cert-tls server +cipher AES-256-GCM +# push mac address info +push-peer-info +verb 3 + + +{ca_content} + + + +{client_crt_content} + + + +{client_key_content} + + + +{ta_content} + +key-direction 1 +""" + + # Write the configuration to the output file + try: + # Ensure the output directory exists + output_dir = os.path.dirname(output_path) + if not os.path.exists(output_dir): + # This part is tricky because of permissions. + # For now, we assume the directory exists. + # On a real server, this would be handled by deployment scripts. + pass + + with open(output_path, 'w') as f: + f.write(ovpn_config) + print(f"Successfully generated client config: {output_path}") + except IOError as e: + print(f"Error writing to file: {e}") +