feat(nodes): Add insecure TLS option for external nodes
This commit is contained in:
17
core/cli.py
17
core/cli.py
@ -329,16 +329,17 @@ def node():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@node.command('add')
|
@node.command('add')
|
||||||
@click.option('--name', required=True, type=str, help='A unique name for the node (e.g., "Node-DE").')
|
@click.option('--name', required=True, type=str, help='Unique name for the node.')
|
||||||
@click.option('--ip', required=True, type=str, help='The public IP address of the node.')
|
@click.option('--ip', required=True, type=str, help='Public IP address of the node.')
|
||||||
@click.option('--port', required=False, type=int, help='Optional: The port of the node.')
|
@click.option('--port', required=False, type=int, help='Optional: Port of the node.')
|
||||||
@click.option('--sni', required=False, type=str, help='Optional: The Server Name Indication.')
|
@click.option('--sni', required=False, type=str, help='Optional: Server Name Indication.')
|
||||||
@click.option('--pinSHA256', required=False, type=str, help='Optional: The public key SHA256 pin.')
|
@click.option('--pinSHA256', required=False, type=str, help='Optional: Public key SHA256 pin.')
|
||||||
@click.option('--obfs', required=False, type=str, help='Optional: The obfuscation key/password.')
|
@click.option('--obfs', required=False, type=str, help='Optional: Obfuscation key.')
|
||||||
def add_node(name, ip, port, sni, pinsha256, obfs):
|
@click.option('--insecure', is_flag=True, default=False, help='Optional: Skip certificate verification.')
|
||||||
|
def add_node(name, ip, port, sni, pinsha256, obfs, insecure):
|
||||||
"""Add a new external node."""
|
"""Add a new external node."""
|
||||||
try:
|
try:
|
||||||
output = cli_api.add_node(name, ip, sni, pinSHA256=pinsha256, port=port, obfs=obfs)
|
output = cli_api.add_node(name, ip, sni, pinSHA256=pinsha256, port=port, obfs=obfs, insecure=insecure)
|
||||||
click.echo(output.strip())
|
click.echo(output.strip())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
click.echo(f'{e}', err=True)
|
click.echo(f'{e}', err=True)
|
||||||
|
|||||||
@ -464,7 +464,7 @@ def edit_ip_address(ipv4: str, ipv6: str):
|
|||||||
if ipv6:
|
if ipv6:
|
||||||
run_cmd(['python3', Command.IP_ADD.value, 'edit', '-6', ipv6])
|
run_cmd(['python3', Command.IP_ADD.value, 'edit', '-6', ipv6])
|
||||||
|
|
||||||
def add_node(name: str, ip: str, sni: Optional[str] = None, pinSHA256: Optional[str] = None, port: Optional[int] = None, obfs: Optional[str] = None):
|
def add_node(name: str, ip: str, sni: Optional[str] = None, pinSHA256: Optional[str] = None, port: Optional[int] = None, obfs: Optional[str] = None, insecure: Optional[bool] = None):
|
||||||
"""
|
"""
|
||||||
Adds a new external node.
|
Adds a new external node.
|
||||||
"""
|
"""
|
||||||
@ -477,6 +477,8 @@ def add_node(name: str, ip: str, sni: Optional[str] = None, pinSHA256: Optional[
|
|||||||
command.extend(['--pinSHA256', pinSHA256])
|
command.extend(['--pinSHA256', pinSHA256])
|
||||||
if obfs:
|
if obfs:
|
||||||
command.extend(['--obfs', obfs])
|
command.extend(['--obfs', obfs])
|
||||||
|
if insecure:
|
||||||
|
command.append('--insecure')
|
||||||
return run_cmd(command)
|
return run_cmd(command)
|
||||||
|
|
||||||
def delete_node(name: str):
|
def delete_node(name: str):
|
||||||
|
|||||||
@ -72,7 +72,7 @@ def write_nodes(nodes):
|
|||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
sys.exit(f"Error writing to {NODES_JSON_PATH}: {e}")
|
sys.exit(f"Error writing to {NODES_JSON_PATH}: {e}")
|
||||||
|
|
||||||
def add_node(name: str, ip: str, sni: str | None = None, pinSHA256: str | None = None, port: int | None = None, obfs: str | None = None):
|
def add_node(name: str, ip: str, sni: str | None = None, pinSHA256: str | None = None, port: int | None = None, obfs: str | None = None, insecure: bool = False):
|
||||||
if not is_valid_ip_or_domain(ip):
|
if not is_valid_ip_or_domain(ip):
|
||||||
print(f"Error: '{ip}' is not a valid IP address or domain name.", file=sys.stderr)
|
print(f"Error: '{ip}' is not a valid IP address or domain name.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@ -89,10 +89,6 @@ def add_node(name: str, ip: str, sni: str | None = None, pinSHA256: str | None =
|
|||||||
print(f"Error: Port '{port}' must be between 1 and 65535.", file=sys.stderr)
|
print(f"Error: Port '{port}' must be between 1 and 65535.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if obfs and not obfs.strip():
|
|
||||||
print(f"Error: OBFS value cannot be empty or just whitespace.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
nodes = read_nodes()
|
nodes = read_nodes()
|
||||||
if any(node['name'] == name for node in nodes):
|
if any(node['name'] == name for node in nodes):
|
||||||
print(f"Error: A node with the name '{name}' already exists.", file=sys.stderr)
|
print(f"Error: A node with the name '{name}' already exists.", file=sys.stderr)
|
||||||
@ -102,14 +98,16 @@ def add_node(name: str, ip: str, sni: str | None = None, pinSHA256: str | None =
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
new_node = {"name": name, "ip": ip}
|
new_node = {"name": name, "ip": ip}
|
||||||
if port:
|
|
||||||
new_node["port"] = port
|
|
||||||
if sni:
|
if sni:
|
||||||
new_node["sni"] = sni.strip()
|
new_node["sni"] = sni.strip()
|
||||||
if pinSHA256:
|
if pinSHA256:
|
||||||
new_node["pinSHA256"] = pinSHA256.strip().upper()
|
new_node["pinSHA256"] = pinSHA256.strip().upper()
|
||||||
|
if port:
|
||||||
|
new_node["port"] = port
|
||||||
if obfs:
|
if obfs:
|
||||||
new_node["obfs"] = obfs.strip()
|
new_node["obfs"] = obfs.strip()
|
||||||
|
if insecure:
|
||||||
|
new_node["insecure"] = insecure
|
||||||
|
|
||||||
nodes.append(new_node)
|
nodes.append(new_node)
|
||||||
write_nodes(nodes)
|
write_nodes(nodes)
|
||||||
@ -133,16 +131,17 @@ def list_nodes():
|
|||||||
print("No nodes configured.")
|
print("No nodes configured.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"{'Name':<15} {'IP / Domain':<25} {'Port':<8} {'OBFS':<15} {'SNI':<20} {'Pin SHA256'}")
|
print(f"{'Name':<15} {'IP / Domain':<25} {'Port':<8} {'SNI':<20} {'Insecure':<10} {'OBFS':<20} {'Pin SHA256'}")
|
||||||
print(f"{'-'*15} {'-'*25} {'-'*8} {'-'*15} {'-'*20} {'-'*30}")
|
print(f"{'-'*15} {'-'*25} {'-'*8} {'-'*20} {'-'*10} {'-'*20} {'-'*30}")
|
||||||
for node in sorted(nodes, key=lambda x: x['name']):
|
for node in sorted(nodes, key=lambda x: x['name']):
|
||||||
name = node['name']
|
name = node['name']
|
||||||
ip = node['ip']
|
ip = node['ip']
|
||||||
port = node.get('port', 'N/A')
|
port = node.get('port', 'N/A')
|
||||||
obfs = node.get('obfs', 'N/A')
|
|
||||||
sni = node.get('sni', 'N/A')
|
sni = node.get('sni', 'N/A')
|
||||||
|
insecure = str(node.get('insecure', 'False'))
|
||||||
|
obfs = node.get('obfs', 'N/A')
|
||||||
pin = node.get('pinSHA256', 'N/A')
|
pin = node.get('pinSHA256', 'N/A')
|
||||||
print(f"{name:<15} {ip:<25} {str(port):<8} {obfs:<15} {sni:<20} {pin}")
|
print(f"{name:<15} {ip:<25} {str(port):<8} {sni:<20} {insecure:<10} {obfs:<20} {pin}")
|
||||||
|
|
||||||
def generate_cert():
|
def generate_cert():
|
||||||
try:
|
try:
|
||||||
@ -203,21 +202,22 @@ def main():
|
|||||||
add_parser.add_argument('--name', type=str, required=True, help='The unique name of the node.')
|
add_parser.add_argument('--name', type=str, required=True, help='The unique name of the node.')
|
||||||
add_parser.add_argument('--ip', type=str, required=True, help='The IP address or domain of the node.')
|
add_parser.add_argument('--ip', type=str, required=True, help='The IP address or domain of the node.')
|
||||||
add_parser.add_argument('--port', type=int, help='Optional: The port of the node.')
|
add_parser.add_argument('--port', type=int, help='Optional: The port of the node.')
|
||||||
add_parser.add_argument('--sni', type=str, help='Optional: The Server Name Indication (e.g., yourdomain.com).')
|
add_parser.add_argument('--sni', type=str, help='Optional: The Server Name Indication.')
|
||||||
add_parser.add_argument('--pinSHA256', type=str, help='Optional: The public key SHA256 pin.')
|
add_parser.add_argument('--pinSHA256', type=str, help='Optional: The public key SHA256 pin.')
|
||||||
add_parser.add_argument('--obfs', type=str, help='Optional: The obfuscation key/password.')
|
add_parser.add_argument('--obfs', type=str, help='Optional: The obfuscation key.')
|
||||||
|
add_parser.add_argument('--insecure', action='store_true', help='Optional: Skip certificate verification.')
|
||||||
|
|
||||||
delete_parser = subparsers.add_parser('delete', help='Delete a node by name.')
|
delete_parser = subparsers.add_parser('delete', help='Delete a node by name.')
|
||||||
delete_parser.add_argument('--name', type=str, required=True, help='The name of the node to delete.')
|
delete_parser.add_argument('--name', type=str, required=True, help='The name of the node to delete.')
|
||||||
|
|
||||||
subparsers.add_parser('list', help='List all configured nodes.')
|
subparsers.add_parser('list', help='List all configured nodes.')
|
||||||
|
|
||||||
subparsers.add_parser('generate-cert', help="Generate blitz.crt and blitz.key if they don't exist or are expiring soon.")
|
subparsers.add_parser('generate-cert', help="Generate blitz.crt and blitz.key.")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.command == 'add':
|
if args.command == 'add':
|
||||||
add_node(args.name, args.ip, args.sni, args.pinSHA256, args.port, args.obfs)
|
add_node(args.name, args.ip, args.sni, args.pinSHA256, args.port, args.obfs, args.insecure)
|
||||||
elif args.command == 'delete':
|
elif args.command == 'delete':
|
||||||
delete_node(args.name)
|
delete_node(args.name)
|
||||||
elif args.command == 'list':
|
elif args.command == 'list':
|
||||||
|
|||||||
@ -286,6 +286,7 @@ $(document).ready(function () {
|
|||||||
<td>${escapeHtml(node.port || 'N/A')}</td>
|
<td>${escapeHtml(node.port || 'N/A')}</td>
|
||||||
<td>${escapeHtml(node.sni || 'N/A')}</td>
|
<td>${escapeHtml(node.sni || 'N/A')}</td>
|
||||||
<td>${escapeHtml(node.obfs || 'N/A')}</td>
|
<td>${escapeHtml(node.obfs || 'N/A')}</td>
|
||||||
|
<td>${escapeHtml(node.insecure ? 'True' : 'False')}</td>
|
||||||
<td>${escapeHtml(node.pinSHA256 || 'N/A')}</td>
|
<td>${escapeHtml(node.pinSHA256 || 'N/A')}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-xs btn-danger delete-node-btn" data-name="${escapeHtml(node.name)}">
|
<button class="btn btn-xs btn-danger delete-node-btn" data-name="${escapeHtml(node.name)}">
|
||||||
@ -310,8 +311,9 @@ $(document).ready(function () {
|
|||||||
const sni = $("#node_sni").val().trim();
|
const sni = $("#node_sni").val().trim();
|
||||||
const obfs = $("#node_obfs").val().trim();
|
const obfs = $("#node_obfs").val().trim();
|
||||||
const pinSHA256 = $("#node_pin").val().trim();
|
const pinSHA256 = $("#node_pin").val().trim();
|
||||||
|
const insecure = $("#node_insecure").is(':checked');
|
||||||
|
|
||||||
const data = { name: name, ip: ip };
|
const data = { name: name, ip: ip, insecure: insecure };
|
||||||
if (port) data.port = parseInt(port);
|
if (port) data.port = parseInt(port);
|
||||||
if (sni) data.sni = sni;
|
if (sni) data.sni = sni;
|
||||||
if (obfs) data.obfs = obfs;
|
if (obfs) data.obfs = obfs;
|
||||||
@ -326,12 +328,7 @@ $(document).ready(function () {
|
|||||||
"#add_node_btn",
|
"#add_node_btn",
|
||||||
false,
|
false,
|
||||||
function() {
|
function() {
|
||||||
$("#node_name").val('');
|
$("#add_node_form")[0].reset();
|
||||||
$("#node_ip").val('');
|
|
||||||
$("#node_port").val('');
|
|
||||||
$("#node_sni").val('');
|
|
||||||
$("#node_obfs").val('');
|
|
||||||
$("#node_pin").val('');
|
|
||||||
$("#add_node_form .form-control").removeClass('is-invalid');
|
$("#add_node_form .form-control").removeClass('is-invalid');
|
||||||
fetchNodes();
|
fetchNodes();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,7 +90,7 @@ async def add_node(body: AddNodeBody):
|
|||||||
Adds a new external node to the configuration.
|
Adds a new external node to the configuration.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
body: Request body containing the name and IP of the node.
|
body: Request body containing the full details of the node.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cli_api.add_node(
|
cli_api.add_node(
|
||||||
@ -99,7 +99,8 @@ async def add_node(body: AddNodeBody):
|
|||||||
port=body.port,
|
port=body.port,
|
||||||
sni=body.sni,
|
sni=body.sni,
|
||||||
pinSHA256=body.pinSHA256,
|
pinSHA256=body.pinSHA256,
|
||||||
obfs=body.obfs
|
obfs=body.obfs,
|
||||||
|
insecure=body.insecure
|
||||||
)
|
)
|
||||||
return DetailResponse(detail=f"Node '{body.name}' added successfully.")
|
return DetailResponse(detail=f"Node '{body.name}' added successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class Node(BaseModel):
|
|||||||
sni: Optional[str] = None
|
sni: Optional[str] = None
|
||||||
pinSHA256: Optional[str] = None
|
pinSHA256: Optional[str] = None
|
||||||
obfs: Optional[str] = None
|
obfs: Optional[str] = None
|
||||||
|
insecure: Optional[bool] = False
|
||||||
|
|
||||||
@field_validator('ip', mode='before')
|
@field_validator('ip', mode='before')
|
||||||
def check_node_ip(cls, v: str | None):
|
def check_node_ip(cls, v: str | None):
|
||||||
|
|||||||
@ -228,6 +228,7 @@
|
|||||||
<th>Port</th>
|
<th>Port</th>
|
||||||
<th>SNI</th>
|
<th>SNI</th>
|
||||||
<th>OBFS</th>
|
<th>OBFS</th>
|
||||||
|
<th>Insecure</th>
|
||||||
<th>Pin SHA256</th>
|
<th>Pin SHA256</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -276,6 +277,14 @@
|
|||||||
<input type="text" class="form-control" id="node_pin" placeholder="5D:23:0E:E9:10:AB:96:E0:43...">
|
<input type="text" class="form-control" id="node_pin" placeholder="5D:23:0E:E9:10:AB:96:E0:43...">
|
||||||
<div class="invalid-feedback">Invalid SHA256 pin format.</div>
|
<div class="invalid-feedback">Invalid SHA256 pin format.</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="node_insecure">
|
||||||
|
<label class="form-check-label" for="node_insecure">
|
||||||
|
Insecure
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="button" id="add_node_btn" class="btn btn-success">
|
<button type="button" id="add_node_btn" class="btn btn-success">
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||||
Add Node
|
Add Node
|
||||||
|
|||||||
Reference in New Issue
Block a user