Per-Arne Andersen
4 years ago
committed by
GitHub
60 changed files with 7411 additions and 325 deletions
@ -0,0 +1,15 @@ |
|||
dist: xenial |
|||
sudo: required |
|||
env: |
|||
global: |
|||
- DOCKER_REPO=perara/wg-manager |
|||
before_install: |
|||
- curl -fsSL https://get.docker.com | sh |
|||
- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json |
|||
- mkdir -p $HOME/.docker |
|||
- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json |
|||
- sudo service docker start |
|||
install: |
|||
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes |
|||
- docker buildx create --name xbuilder --use |
|||
script: bash ci.sh |
@ -0,0 +1,16 @@ |
|||
import requests |
|||
|
|||
if __name__ == "__main__": |
|||
sess = requests.Session() |
|||
|
|||
resp = sess.post("http://localhost:8888/api/v1/login", data={ |
|||
"username": "admin", |
|||
"password": "admin" |
|||
}) |
|||
print(resp.json()) |
|||
sess.headers.update({ |
|||
"Authorization": f"Bearer {resp.json()['access_token']}" |
|||
}) |
|||
|
|||
for _ in range(20): |
|||
print(sess.get("http://localhost:8888/api/v1/wg/generate_psk").json()) |
@ -0,0 +1,62 @@ |
|||
version: "2.1" |
|||
services: |
|||
|
|||
server: |
|||
container_name: wg-manager |
|||
build: . |
|||
restart: always |
|||
sysctls: |
|||
net.ipv6.conf.all.disable_ipv6: 0 |
|||
cap_add: |
|||
- NET_ADMIN |
|||
#network_mode: host # Alternatively |
|||
ports: |
|||
- 11820:11820/udp |
|||
- 51800-51900:51800-51900/udp |
|||
- 8888:8888 |
|||
environment: |
|||
HOST: 0.0.0.0 |
|||
PORT: 8888 |
|||
ADMIN_USERNAME: admin |
|||
ADMIN_PASSWORD: admin |
|||
WEB_CONCURRENCY: 2 |
|||
SERVER_INIT_INTERFACE_START: 1 |
|||
|
|||
#endpoint dynamic variables: ||external|| , ||internal|| |
|||
SERVER_INIT_INTERFACE: '{"address":"10.0.200.1","v6_address":"fd42:42:42::1","subnet":24,"v6_subnet":64,"interface":"wg0","listen_port":"51820","endpoint":"server","dns":"10.0.200.1,8.8.8.8","private_key":"","public_key":"","post_up":"","post_down":"","configuration":"","is_running":false,"peers":[]}' |
|||
SERVER_STARTUP_API_KEY: thisisasecretkeythatnobodyknows |
|||
networks: |
|||
- wg-manager-net |
|||
|
|||
client: |
|||
container_name: wg-manager-server-with-client |
|||
build: . |
|||
restart: always |
|||
sysctls: |
|||
net.ipv6.conf.all.disable_ipv6: 0 |
|||
cap_add: |
|||
- NET_ADMIN |
|||
ports: |
|||
- 8889:8889 |
|||
privileged: true |
|||
environment: |
|||
HOST: 0.0.0.0 # Optional (For Accessing WEB-Gui) |
|||
PORT: 8889 # Optional (Web-GUI Listen Port) |
|||
WEB_CONCURRENCY: 1 # Optional |
|||
ADMIN_USERNAME: admin |
|||
ADMIN_PASSWORD: admin |
|||
INIT_SLEEP: 5 # If you run into concurrency issues |
|||
SERVER: 0 # If you want to host a server as well |
|||
CLIENT: 1 # If you want to connect to servers |
|||
CLIENT_START_AUTOMATICALLY: 1 # If you want the client to start automatically |
|||
CLIENT_1_NAME: "client-1" # Name of first client |
|||
CLIENT_1_ROUTES: "10.0.200.0/24" |
|||
CLIENT_1_SERVER_HOST: "http://server:8888" # Endpoint of first server |
|||
CLIENT_1_SERVER_INTERFACE: "wg0" # Interface of first server (to get config) |
|||
CLIENT_1_API_KEY: "thisisasecretkeythatnobodyknows" # API-Key of first server (to get config) |
|||
networks: |
|||
- wg-manager-net |
|||
|
|||
networks: |
|||
wg-manager-net: |
|||
driver: bridge |
@ -1,5 +1,6 @@ |
|||
#!/usr/bin/env bash |
|||
cd .. |
|||
docker login |
|||
|
|||
docker build -t perara/wg-manager . |
|||
docker push perara/wg-manager |
|||
docker build -t perara/wg-manager:dev . |
|||
docker push perara/wg-manager:dev |
|||
|
File diff suppressed because it is too large
@ -0,0 +1,27 @@ |
|||
from sqlalchemy.orm import Session |
|||
|
|||
import models |
|||
|
|||
|
|||
def add_initial_api_key_for_admin(sess: Session, api_key, ADMIN_USERNAME): |
|||
|
|||
db_user = sess.query(models.User)\ |
|||
.filter_by(username=ADMIN_USERNAME)\ |
|||
.one() |
|||
|
|||
exists_api_key = sess.query(models.UserAPIKey)\ |
|||
.filter_by( |
|||
user_id=db_user.id, |
|||
key=api_key |
|||
)\ |
|||
.count() |
|||
|
|||
if exists_api_key == 0: |
|||
db_api_key = models.UserAPIKey() |
|||
db_api_key.key = api_key |
|||
db_api_key.user_id = db_user.id |
|||
|
|||
sess.add(db_api_key) |
|||
sess.commit() |
|||
|
|||
return True |
@ -0,0 +1,21 @@ |
|||
from sqlalchemy import * |
|||
from migrate import * |
|||
|
|||
|
|||
def upgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('server', meta, autoload=True) |
|||
subnet = Column('subnet', Integer, nullable=False) |
|||
subnet.create(server) |
|||
except: |
|||
pass |
|||
|
|||
|
|||
def downgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('server', meta, autoload=True) |
|||
server.c.subnet.drop() |
|||
except: |
|||
pass |
@ -0,0 +1,32 @@ |
|||
from sqlalchemy import * |
|||
from migrate import * |
|||
|
|||
|
|||
def upgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('server', meta, autoload=True) |
|||
v6_address_server = Column('v6_address', VARCHAR, unique=True, nullable=True) |
|||
v6_address_server.create(server) |
|||
|
|||
meta = MetaData(bind=migrate_engine) |
|||
peer = Table('peer', meta, autoload=True) |
|||
v6_address_peer = Column('v6_address', VARCHAR, nullable=True) |
|||
v6_address_peer.create(peer) |
|||
except: |
|||
pass |
|||
|
|||
|
|||
def downgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('server', meta, autoload=True) |
|||
server.c.v6_address.drop() |
|||
|
|||
meta = MetaData(bind=migrate_engine) |
|||
peer = Table('peer', meta, autoload=True) |
|||
peer.c.v6_address.drop() |
|||
except: |
|||
pass |
|||
|
|||
|
@ -0,0 +1,21 @@ |
|||
from sqlalchemy import * |
|||
from migrate import * |
|||
|
|||
|
|||
def upgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
peer = Table('server', meta, autoload=True) |
|||
v6_subnet = Column('v6_subnet', INTEGER) |
|||
v6_subnet.create(peer) |
|||
except: |
|||
pass |
|||
|
|||
|
|||
def downgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
peer = Table('server', meta, autoload=True) |
|||
peer.c.v6_subnet.drop() |
|||
except: |
|||
pass |
@ -0,0 +1,34 @@ |
|||
from sqlalchemy import * |
|||
from migrate import * |
|||
|
|||
|
|||
def upgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('server', meta, autoload=True) |
|||
read_only = Column('read_only', INTEGER, default=0) |
|||
read_only.create(server) |
|||
except: |
|||
pass |
|||
|
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
peer = Table('peer', meta, autoload=True) |
|||
read_only = Column('read_only', INTEGER, default=0) |
|||
read_only.create(peer) |
|||
except: |
|||
pass |
|||
|
|||
def downgrade(migrate_engine): |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('server', meta, autoload=True) |
|||
server.c.read_only.drop() |
|||
except: |
|||
pass |
|||
try: |
|||
meta = MetaData(bind=migrate_engine) |
|||
server = Table('peer', meta, autoload=True) |
|||
server.c.read_only.drop() |
|||
except: |
|||
pass |
@ -0,0 +1,56 @@ |
|||
import abc |
|||
from pathlib import Path |
|||
import subprocess |
|||
import shlex |
|||
|
|||
|
|||
class BaseObfuscation(abc.ABC): |
|||
|
|||
def __init__(self, binary_name=None, binary_path=None, algorithm=None): |
|||
|
|||
assert binary_name is not None or binary_path is not None |
|||
self.binary_name = binary_name if binary_name is not None else Path(self.binary_path).name |
|||
self.binary_path = binary_path if binary_path else "" |
|||
self.algorithm = algorithm |
|||
|
|||
def ensure_installed(self): |
|||
|
|||
# Attempt to find process by path |
|||
binary = Path(self.binary_path) |
|||
if not binary.is_file(): |
|||
# Did not find by path, attempt to find using which |
|||
proc_which = subprocess.Popen(["which", self.binary_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|||
data = [x.decode().strip() for x in proc_which.communicate() if x != b''][0] |
|||
|
|||
if proc_which.returncode != 0: |
|||
raise RuntimeError("Could not find binary '%s'" % data) |
|||
|
|||
self.binary_path = data |
|||
|
|||
def execute(self, *args, kill_first=False, override_command=None): |
|||
|
|||
if kill_first: |
|||
# TODO try to delete by full name as we dont want to kill other processes. |
|||
pattern = self.binary_name |
|||
self.execute(*[pattern], override_command="pkill") |
|||
#pattern = self.binary_path + " " + ' '.join(args) |
|||
#print(pattern) |
|||
#kill_output, kill_code = self.execute(*[pattern], override_command="pkill") |
|||
|
|||
command = override_command if override_command is not None else self.binary_path |
|||
print(shlex.join([command] + list(args))) |
|||
proc_which = subprocess.Popen([command] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|||
raw_data = proc_which.communicate() |
|||
|
|||
data = [x.decode().strip() for x in raw_data if x != b''] |
|||
if len(data) == 0: |
|||
data = "" |
|||
else: |
|||
data = data[0] |
|||
return data, proc_which.returncode |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,30 @@ |
|||
from script.obfuscate import BaseObfuscation |
|||
import re |
|||
|
|||
|
|||
class ObfuscateOBFS4(BaseObfuscation): |
|||
|
|||
def __init__(self): |
|||
super().__init__( |
|||
binary_name="obfs4proxy", |
|||
binary_path="/usr/bin/obfs4proxy", |
|||
algorithm="obfs4" |
|||
) |
|||
|
|||
self.ensure_installed() |
|||
|
|||
def ensure_installed(self): |
|||
super().ensure_installed() |
|||
|
|||
output, code = self.execute("-version") |
|||
|
|||
if re.match(f'{self.binary_name}-[0-9]+.[0-9]+.[0-9]+', output) and code == 0: |
|||
return True |
|||
else: |
|||
raise RuntimeError(f"Could not verify that {self.binary_name} is installed correctly.") |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
|
|||
x = ObfuscateOBFS4() |
|||
x.ensure_installed() |
@ -0,0 +1,119 @@ |
|||
from pathlib import Path |
|||
|
|||
import requests |
|||
|
|||
import const |
|||
from script.obfuscate import BaseObfuscation |
|||
import re |
|||
import os |
|||
import qrcode |
|||
import socket |
|||
|
|||
from script.obfuscate.obfs4 import ObfuscateOBFS4 |
|||
|
|||
|
|||
class ObfuscationViaTOR(BaseObfuscation): |
|||
|
|||
def __init__(self, algorithm: BaseObfuscation): |
|||
super().__init__( |
|||
binary_name="tor" |
|||
) |
|||
self.algorithm = algorithm |
|||
self.tor_data_dir = "/tmp/wg-manager-tor-proxy" |
|||
self.tor_config_file = "/tmp/wg-manager-tor-proxy/torrc" |
|||
self.tor_fingerprint_file = f"{self.tor_data_dir}/fingerprint" |
|||
self.tor_bridge_file = f"{self.tor_data_dir}/pt_state/obfs4_bridgeline.txt" |
|||
|
|||
Path(self.tor_config_file).touch() |
|||
os.makedirs(self.tor_data_dir, exist_ok=True) |
|||
|
|||
def __del__(self): |
|||
pass |
|||
|
|||
def ensure_installed(self): |
|||
super().ensure_installed() |
|||
output, code = self.execute("--version") |
|||
|
|||
if re.match(f'Tor version .*', output) and code == 0: |
|||
return True |
|||
else: |
|||
raise RuntimeError(f"Could not verify that {self.binary_name} is installed correctly.") |
|||
|
|||
def start(self): |
|||
|
|||
output, code = self.execute( |
|||
"-f", self.tor_config_file, |
|||
"--DataDirectory", self.tor_data_dir, |
|||
"--RunAsDaemon", "1", |
|||
"--ExitPolicy", "reject *:*", |
|||
"--ORPort", str(const.OBFUSCATE_SOCKS_TOR_PORT), |
|||
"--BridgeRelay", "1", |
|||
"--PublishServerDescriptor", "0", |
|||
"--ServerTransportPlugin", f"{self.algorithm.algorithm} exec {self.algorithm.binary_path}", |
|||
"--ServerTransportListenAddr", f"{self.algorithm.algorithm} 0.0.0.0:{const.OBFUSCATE_TOR_LISTEN_ADDR}", |
|||
"--ExtORPort", "auto", |
|||
"--ContactInfo", "wg-manager@github.com", |
|||
"--Nickname", "wgmanager", |
|||
kill_first=True |
|||
) |
|||
|
|||
print(output) |
|||
|
|||
def generate_bridge_line(self, local=False): |
|||
|
|||
if local: |
|||
ip_address = socket.gethostbyname(socket.gethostname()) |
|||
else: |
|||
ip_address = requests.get("https://api.ipify.org").text |
|||
|
|||
with open(self.tor_fingerprint_file, "r") as f: |
|||
fingerprint = f.read().split(" ") |
|||
assert len(fingerprint) == 2, "Could not load fingerprint correctly. " \ |
|||
"Should be a list of 2 items (name, fingerprint)" |
|||
fingerprint = fingerprint[1] |
|||
|
|||
with open(self.tor_bridge_file, "r") as f: |
|||
bridge_line_raw = f.read() |
|||
|
|||
bridge_line = re.search(r"^Bridge .*", bridge_line_raw, re.MULTILINE).group(0) |
|||
bridge_line = bridge_line\ |
|||
.replace("<IP ADDRESS>", ip_address)\ |
|||
.replace("<PORT>", str(const.OBFUSCATE_TOR_LISTEN_ADDR))\ |
|||
.replace("<FINGERPRINT>", fingerprint)\ |
|||
.replace("Bridge ", "bridge://")\ |
|||
.replace("\n", "") |
|||
#bridge_line = f"bridge://{self.algorithm.algorithm} {ip_address}:{const.OBFUSCATE_SOCKS_TOR_PORT} {fingerprint}" |
|||
print(bridge_line) |
|||
return bridge_line |
|||
|
|||
def output_qr(self, text, image=False): |
|||
|
|||
qr = qrcode.QRCode( |
|||
version=10, |
|||
error_correction=qrcode.constants.ERROR_CORRECT_L, |
|||
box_size=10, |
|||
border=4, |
|||
) |
|||
qr.add_data(text) |
|||
qr.make(fit=True) |
|||
|
|||
if image: |
|||
img = qr.make_image(fill_color="black", back_color="white") |
|||
img.show() |
|||
else: |
|||
try: |
|||
qr.print_tty() |
|||
except: |
|||
qr.print_ascii() |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
|
|||
x = ObfuscationViaTOR( |
|||
algorithm=ObfuscateOBFS4() |
|||
) |
|||
x.ensure_installed() |
|||
x.start() |
|||
bridge_line = x.generate_bridge_line(local=False) |
|||
x.output_qr(bridge_line, image=True) |
|||
#x.generate_bridge_line(local=False) |
File diff suppressed because it is too large
@ -0,0 +1,43 @@ |
|||
<mat-card> |
|||
<mat-card-title> |
|||
API Keys |
|||
</mat-card-title> |
|||
|
|||
<mat-card-content> |
|||
You can use API-Keys to perform authenticated actions. These are less secure than using OAuth2, but at the gain for increased convenience. |
|||
<br><b>Note:</b> A newly created API Key will only show <b>once</b>. This means that you have to take note of the key and safe it somewhere safe. |
|||
|
|||
<table mat-table [dataSource]="dataSource" style="width: 100%"> |
|||
|
|||
<!-- Id Column --> |
|||
<ng-container matColumnDef="id"> |
|||
<th mat-header-cell *matHeaderCellDef> ID. </th> |
|||
<td mat-cell *matCellDef="let element"> {{element.id}} </td> |
|||
</ng-container> |
|||
|
|||
<!-- Key Column --> |
|||
<ng-container matColumnDef="key"> |
|||
<th mat-header-cell *matHeaderCellDef> API-Key </th> |
|||
<td mat-cell *matCellDef="let element"> {{(element.key) ? element.key : "[HIDDEN]"}} </td> |
|||
</ng-container> |
|||
|
|||
<!-- Created_At Column --> |
|||
<ng-container matColumnDef="created_at"> |
|||
<th mat-header-cell *matHeaderCellDef> Creation Date </th> |
|||
<td mat-cell *matCellDef="let element"> {{element.created_date | date:'medium'}} </td> |
|||
</ng-container> |
|||
|
|||
<!-- Delete Column --> |
|||
<ng-container matColumnDef="delete"> |
|||
<th mat-header-cell *matHeaderCellDef> Delete </th> |
|||
<td mat-cell *matCellDef="let element"> <button mat-flat-button color="warn" (click)="deleteAPIKey(element)">Delete</button></td> |
|||
</ng-container> |
|||
|
|||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
|||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> |
|||
|
|||
</table> |
|||
|
|||
<button mat-flat-button color="primary" (click)="createAPIKey()">New Key</button> |
|||
</mat-card-content> |
|||
</mat-card> |
@ -0,0 +1,25 @@ |
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { ApiKeyComponent } from './api-key.component'; |
|||
|
|||
describe('ApiKeyComponent', () => { |
|||
let component: ApiKeyComponent; |
|||
let fixture: ComponentFixture<ApiKeyComponent>; |
|||
|
|||
beforeEach(async(() => { |
|||
TestBed.configureTestingModule({ |
|||
declarations: [ ApiKeyComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
})); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(ApiKeyComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should create', () => { |
|||
expect(component).toBeTruthy(); |
|||
}); |
|||
}); |
@ -0,0 +1,45 @@ |
|||
import {Component, OnInit} from '@angular/core'; |
|||
import {ServerService} from "../../../../services/server.service"; |
|||
|
|||
@Component({ |
|||
selector: 'app-api-key', |
|||
templateUrl: './api-key.component.html', |
|||
styleUrls: ['./api-key.component.scss'] |
|||
}) |
|||
export class ApiKeyComponent implements OnInit { |
|||
|
|||
displayedColumns: string[] = ['id', 'key', 'created_at', 'delete']; |
|||
dataSource = []; |
|||
|
|||
constructor(private serverService: ServerService |
|||
) { } |
|||
|
|||
ngOnInit(): void { |
|||
|
|||
|
|||
this.serverService.getAPIKeys().subscribe((apiKeys: Array<any>) => { |
|||
this.dataSource = [...apiKeys] |
|||
|
|||
console.log(this.dataSource) |
|||
}) |
|||
} |
|||
|
|||
deleteAPIKey(elem){ |
|||
let idx = this.dataSource.indexOf(elem); |
|||
this.serverService.deleteAPIKey(elem.id).subscribe(x => { |
|||
this.dataSource.splice(idx, 1); |
|||
this.dataSource = [...this.dataSource] |
|||
}) |
|||
} |
|||
|
|||
createAPIKey(){ |
|||
|
|||
this.serverService.addAPIKey().subscribe(key => { |
|||
this.dataSource.push(key) |
|||
this.dataSource = [...this.dataSource] |
|||
|
|||
}) |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,3 @@ |
|||
.user-edit-component{ |
|||
padding: 20px; |
|||
} |
@ -0,0 +1,3 @@ |
|||
{ |
|||
"language_tabs": [{ "python": "Python" }] |
|||
} |
Loading…
Reference in new issue