You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

511 lines
15 KiB

import logging
import os
from sqlalchemy_utils import database_exists
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
if not logger.hasHandlers():
sh = logging.StreamHandler()
fmt = logging.Formatter(fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s")
sh.setFormatter(fmt)
logger.addHandler(sh)
import pkg_resources
import uvicorn as uvicorn
from fastapi.staticfiles import StaticFiles
import databases
from sqlalchemy.orm import sessionmaker, Session
from starlette.responses import FileResponse, JSONResponse
import sqlalchemy
import const
from datetime import datetime, timedelta
import db.wireguard
import db.user
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt import PyJWTError
import script.wireguard
import typing
import models
import schemas
database = databases.Database(const.DATABASE_URL)
engine = sqlalchemy.create_engine(
const.DATABASE_URL, connect_args={"check_same_thread": False}
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
if not database_exists(engine.url):
models.Base.metadata.create_all(engine)
# Create default user
_db: Session = SessionLocal()
_db.add(models.User(
username=os.getenv("ADMIN_USERNAME", "admin"),
password=db.user.get_password_hash(os.getenv("ADMIN_PASSWORD", "admin")),
full_name="Admin",
role="admin",
email=""
))
_db.commit()
_db.close()
app = FastAPI()
# Dependency
def get_db():
try:
db = SessionLocal()
yield db
finally:
db.close()
def create_access_token(*, data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, const.SECRET_KEY, algorithm=const.ALGORITHM)
return encoded_jwt
def auth(token: str = Depends(oauth2_scheme), sess: Session = Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, const.SECRET_KEY, algorithms=[const.ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except PyJWTError:
raise credentials_exception
user = db.user.get_user_by_name(sess, username)
if user is None:
raise credentials_exception
return user
@app.get("/api/logout")
def logout(user: schemas.User = Depends(auth)):
# TODO
return {}
@app.post("/api/user/edit", response_model=schemas.User)
def edit(form_data: schemas.UserInDB, user: schemas.User = Depends(auth), sess: Session = Depends(get_db)):
form_data.password = db.user.get_password_hash(form_data.password)
db_user = db.user.update_user(sess, form_data)
return schemas.User.from_orm(db_user)
@app.post("/api/login", response_model=schemas.Token)
def login(form_data: OAuth2PasswordRequestForm = Depends(), sess: Session = Depends(get_db)):
user = db.user.authenticate_user(sess, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Create token
access_token_expires = timedelta(minutes=const.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer", "user": schemas.User.from_orm(user)}
# @app.post("/wg/update/", response_model=List[schemas.WireGuard])
@app.get("/api/wg/server/all", response_model=typing.List[schemas.WGServer])
def get_interfaces(
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
interfaces = db.wireguard.server_get_all(sess)
for iface in interfaces:
iface.is_running = script.wireguard.is_running(iface)
return interfaces
@app.post("/api/wg/server/add", response_model=schemas.WGServer)
def add_interface(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
if form_data.interface is None or form_data.listen_port is None or form_data.address is None:
raise HTTPException(status_code=400,
detail="Interface, Listen-Port and Address must be included in the schema.")
try:
wg_server = db.wireguard.server_add(sess, form_data)
# Public/Private key
private_key, public_key = script.wireguard.generate_keys()
wg_server.private_key = private_key
wg_server.public_key = public_key
db.wireguard.server_key_pair_set(sess, wg_server)
db.wireguard.server_generate_config(sess, wg_server)
return wg_server
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/wg/server/edit", response_model=schemas.WGServer)
def edit_server(
data: dict, sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
interface = data["interface"]
server = schemas.WGServer(**data["server"])
# Stop if running
old = schemas.WGServer(interface=interface)
if script.wireguard.is_running(old):
script.wireguard.stop_interface(old)
fields = set(old.__fields__) - {"peers", "is_running"}
if not db.wireguard.server_update_field(sess, interface, server, fields):
raise HTTPException(400, detail="Were not able to edit %s" % old.interface)
script.wireguard.start_interface(server)
return server
@app.get("/api/wg/generate_keypair", response_model=schemas.KeyPair)
def generate_key_pair(user: schemas.User = Depends(auth)):
private_key, public_key = script.wireguard.generate_keys()
return schemas.KeyPair(
private_key=private_key,
public_key=public_key
)
@app.get("/api/wg/generate_psk", response_model=schemas.PSK)
def generate_psk(user: schemas.User = Depends(auth)):
return schemas.PSK(
psk=script.wireguard.generate_psk()
)
@app.post("/api/wg/server/stop", response_model=schemas.WGServer)
def start_server(
form_data: schemas.WGServer,
user: schemas.User = Depends(auth)
):
script.wireguard.stop_interface(form_data)
form_data.is_running = script.wireguard.is_running(form_data)
return form_data
@app.post("/api/wg/server/start", response_model=schemas.WGServer)
def start_server(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
db.wireguard.server_generate_config(sess, form_data)
script.wireguard.start_interface(form_data)
form_data.is_running = script.wireguard.is_running(form_data)
return form_data
@app.post("/api/wg/server/restart", response_model=schemas.WGServer)
def start_server(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
db.wireguard.server_generate_config(sess, form_data)
script.wireguard.restart_interface(form_data)
form_data.is_running = script.wireguard.is_running(form_data)
return form_data
@app.post("/api/wg/server/delete", response_model=schemas.WGServer)
def delete_server(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
# Stop if running
if script.wireguard.is_running(form_data):
script.wireguard.stop_interface(form_data)
if not db.wireguard.server_remove(sess, form_data):
raise HTTPException(400, detail="Were not able to delete %s" % form_data.interface)
return form_data
@app.post("/api/wg/server/peer/add", response_model=schemas.WGPeer)
def add_peer(
form_data: schemas.WGServer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
wg_peer = schemas.WGPeer(server=form_data.interface)
# Insert initial peer
wg_peer = db.wireguard.peer_insert(sess, wg_peer)
# If server is running. Add peer
if script.wireguard.is_running(form_data):
script.wireguard.add_peer(form_data, wg_peer)
db.wireguard.peer_generate_config(sess, wg_peer)
return wg_peer
@app.post("/api/wg/server/peer/delete", response_model=schemas.WGPeer)
def delete_peer(
form_data: schemas.WGPeer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
if not db.wireguard.peer_remove(sess, form_data):
raise HTTPException(400, detail="Were not able to delete peer %s (%s)" % (form_data.name, form_data.public_key))
server = schemas.WGServer(interface=form_data.server)
if script.wireguard.is_running(server):
script.wireguard.remove_peer(server, form_data)
return form_data
@app.post("/api/wg/server/peer/edit", response_model=schemas.WGPeer)
def edit_peer(
form_data: schemas.WGPeer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
wg_peer = db.wireguard.peer_update(sess, form_data)
db.wireguard.peer_generate_config(sess, wg_peer)
return wg_peer
@app.post("/api/wg/server/stats")
def edit_peer(
form_data: schemas.WGServer,
user: schemas.User = Depends(auth)
):
stats = script.wireguard.get_stats(form_data)
return JSONResponse(content=stats)
@app.post("/api/wg/server/peer/config", response_model=schemas.WGPeerConfig)
def config_peer(
form_data: schemas.WGPeer,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
db_peer = db.wireguard.peer_query_get_by_address(sess, form_data.address, form_data.server).one()
with open(const.PEER_FILE(db_peer), "r") as f:
conf_file = f.read()
return schemas.WGPeerConfig(config=conf_file)
@app.post("/api/wg/server/config", response_model=schemas.WGPeerConfig)
def config_server(
form_data: schemas.WGServer,
user: schemas.User = Depends(auth)
):
with open(const.SERVER_FILE(form_data.interface), "r") as f:
conf_file = f.read()
return schemas.WGPeerConfig(config=conf_file)
@app.post("/api/users/create/")
def create_user(
form_data: schemas.UserInDB,
sess: Session = Depends(get_db),
user: schemas.User = Depends(auth)
):
user = db.user.get_user_by_name(sess, form_data.username)
# User already exists
if user:
if not db.user.authenticate_user(sess, form_data.username, form_data.password):
raise HTTPException(status_code=401, detail="Incorrect password")
else:
# Create the user
if not db.user.create_user(sess, models.User(
username=form_data.username,
password=form_data.password,
full_name=form_data.full_name,
email=form_data.email,
role=form_data.role,
)):
raise HTTPException(status_code=400, detail="Could not create user")
return login_for_access_token(OAuth2PasswordRequestForm(
username=form_data.username,
password=form_data.password,
scope=""
), sess)
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/", include_in_schema=True)
def root():
return FileResponse('build/index.html')
app.mount("/", StaticFiles(directory=pkg_resources.resource_filename(__name__, 'build')), name="static")
# @app.get("/")
# async def read_root():
# return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
if __name__ == "__main__":
async def main():
if not script.wireguard.is_installed():
print("NOT INSTALLED!")
exit(0)
await database.connect()
# TODO - GUI EVENT - Create new WG interface and post to backend
wg_interface_in = dict(
address="10.0.100.1/24",
listen_port=5192,
interface="wg0",
endpoint=None,
private_key=None,
public_key=None,
shared_key=None,
post_up=None,
post_down=None,
peers=[]
)
wg_interface = schemas.WGServer(**wg_interface_in)
sess = SessionLocal()
if not db.wireguard.server_add(sess, wg_interface):
print("Already exists. error")
# Public/Private key
private_key, public_key = script.wireguard.generate_keys()
wg_interface.private_key = private_key
wg_interface.public_key = public_key
db.wireguard.server_key_pair_set(sess, wg_interface)
# Shared key
shared_key = script.wireguard.generate_psk()
wg_interface.shared_key = shared_key
db.wireguard.server_preshared_key(sess, wg_interface)
# Post UP
wg_interface.post_up = "echo 'LOL2'"
db.wireguard.server_post_up_set(sess, wg_interface)
# Post DOWN
wg_interface.post_down = "echo 'LOL'"
db.wireguard.server_post_down_set(sess, wg_interface)
# Listen port
wg_interface.listen_port = 5192
if not db.wireguard.server_listen_port_set(sess, wg_interface):
print("FAILED!")
# Address
wg_interface.address = "10.0.100.1/24"
db.wireguard.server_ip_address_set(sess, wg_interface)
# Endpoint
wg_interface.endpoint = "10.0.0.135"
db.wireguard.server_endpoint_set(sess, wg_interface)
# TODO - Creates peer
wg_peer_in = dict(
server="wg0"
)
# CReate schema instance
wg_peer = schemas.WGPeer(**wg_peer_in)
# Insert initial peer
wg_peer = db.wireguard.peer_insert(sess, wg_peer)
print(wg_peer)
# Set DNS of peer
wg_peer.dns = ["sysx.no"]
wg_peer = db.wireguard.peer_dns_set(sess, wg_peer)
# Update priv_pub key
wg_peer = db.wireguard.peer_key_pair_generate(sess, wg_peer)
print(wg_peer)
all_peers = db.wireguard.peer_get(sess, wg_interface)
print(all_peers)
db.wireguard.peer_generate_config(sess, wg_peer)
print("-----")
db_interface = db.wireguard.server_query_get_by_interface(sess, wg_interface.interface).one()
db.wireguard.server_generate_config(sess, wg_interface.from_orm(db_interface))
if script.wireguard.is_running(db_interface):
script.wireguard.stop_interface(db_interface)
script.wireguard.start_interface(db_interface)
# script.wireguard.stop_interface(db_interface)
if script.wireguard.is_running(db_interface):
script.wireguard.add_peer(wg_interface, wg_peer)
script.wireguard.remove_peer(wg_interface, wg_peer)
db.wireguard.start_client(sess, wg_peer)
# loop = asyncio.get_event_loop()
# loop.create_task(main())
# asyncio.get_event_loop().run_forever()
uvicorn.run("__main__:app", reload=True)