add support script for generating client file

This commit is contained in:
arthur 2025-10-18 21:06:35 +07:00
parent 9765095bab
commit 0fe85325fc
9 changed files with 476 additions and 6 deletions

27
.gemini/settings.json Normal file
View File

@ -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"
}
}
}

1
.serena/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/cache

71
.serena/project.yml Normal file
View File

@ -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"

93
GEMINI.md Normal file
View File

@ -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`.

102
docs/account-creation.md Normal file
View File

@ -0,0 +1,102 @@
1. run this command to generated rquired client file:
```commandline
cd /etc/openvpn/easy-rsa/
./easyrsa build-client-full <username> 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
```

30
main.py
View File

@ -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 <user> # 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")

69
scripts/gen-client.sh Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 4 ]]; then
cat >&2 <<USAGE
Usage: $0 /path/to/ca.crt /path/to/clientname.crt /path/to/clientname.key /path/to/ta.key
Example: ./gen-client.sh /etc/openvpn/ca.crt ./client1.crt ./client1.key ./ta.key
This writes output to stdout and also saves to ./<clientname>.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 "<ca>"
cat "$ca"
echo "</ca>"
echo
echo "<cert>"
cat "$cert"
echo "</cert>"
echo
echo "<key>"
cat "$key"
echo "</key>"
echo
echo "<tls-auth>"
cat "$ta"
echo "</tls-auth>"
echo "key-direction 1"
} | tee "$outfile"
echo "Wrote config to ./${outfile}"

BIN
user-data/data.xlsx Normal file

Binary file not shown.

View File

@ -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>
{ca_content}
</ca>
<cert>
{client_crt_content}
</cert>
<key>
{client_key_content}
</key>
<tls-auth>
{ta_content}
</tls-auth>
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}")