Compare commits

...

4 commits

4 changed files with 68 additions and 18 deletions

View file

@ -252,12 +252,15 @@ class EasyRSA:
def issue( def issue(
self, self,
cert_type: CertificateType = CertificateType.client, cert_type: CertificateType = CertificateType.client,
dn: DistinguishedName = DistinguishedName.build(), dn: DistinguishedName | None = None,
) -> crypto.X509 | None: ) -> crypto.X509 | None:
""" """
Issue a client or server certificate Issue a client or server certificate
""" """
if dn is None:
dn = DistinguishedName.build()
if not (cert_type is CertificateType.client if not (cert_type is CertificateType.client
or cert_type is CertificateType.server): or cert_type is CertificateType.server):
return None return None

View file

@ -32,7 +32,7 @@ class Responses:
"content": None, "content": None,
} }
NEEDS_USER = { NEEDS_USER = {
"description": "Must be logged in", "description": "Not logged in",
"content": None, "content": None,
} }
NEEDS_PERMISSION = { NEEDS_PERMISSION = {
@ -58,6 +58,10 @@ async def get_current_config(
) -> Config: ) -> Config:
""" """
Get the current configuration if it exists. Get the current configuration if it exists.
Status:
- 400: `kiwi-vpn` not installed
""" """
# fail if not configured # fail if not configured
@ -73,6 +77,10 @@ async def get_current_user(
) -> User: ) -> User:
""" """
Get the currently logged-in user if it exists. Get the currently logged-in user if it exists.
Status:
- 403: invalid auth token, or user not found
""" """
# don't use error 404 here - possible user enumeration # don't use error 404 here - possible user enumeration
@ -89,23 +97,32 @@ async def get_current_user(
async def get_user_by_name( async def get_user_by_name(
user_name: str, user_name: str,
_: Config = Depends(get_current_config), ) -> User:
) -> User | None:
""" """
Get a user by name. Get a user by name.
Status:
- 404: user not found
""" """
return User.get(user_name) # fail if device doesn't exist
if (user := User.get(user_name)) is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return user
async def get_device_by_id( async def get_device_by_id(
device_id: int, device_id: int,
current_config: Config = Depends(get_current_config), ) -> Device:
) -> Device | None: """
Get a device by ID.
# can't connect to an unconfigured database Status:
if current_config is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) - 404: device not found
"""
# fail if device doesn't exist # fail if device doesn't exist
if (device := Device.get(device_id)) is None: if (device := Device.get(device_id)) is None:

View file

@ -4,7 +4,8 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from ..db import Device, DeviceCreate, DeviceRead, User from ..db import Connection, Device, DeviceCreate, DeviceRead, User
from ..easyrsa import CertificateType, DistinguishedName, EasyRSA
from ._common import (Responses, get_current_user, get_device_by_id, from ._common import (Responses, get_current_user, get_device_by_id,
get_user_by_name) get_user_by_name)
@ -37,10 +38,6 @@ async def add_device(
if not current_user.can_create(Device, owner): if not current_user.can_create(Device, owner):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
# fail if owner doesn't exist
if owner is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
# create the new device # create the new device
new_device = Device.create( new_device = Device.create(
owner=owner, owner=owner,
@ -79,3 +76,37 @@ async def remove_device(
# delete device # delete device
device.delete() device.delete()
@router.post(
"/{device_id}/csr",
responses={
status.HTTP_200_OK: Responses.OK,
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
},
)
async def request_certificate(
current_user: User = Depends(get_current_user),
device: Device = Depends(get_device_by_id),
):
"""
POST ./{device_id}/csr: Request certificate for a device.
"""
# check permission
if not current_user.can_edit(device):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
easy_rsa = EasyRSA()
with Connection.session as db:
db.add(device)
dn = DistinguishedName.build(device)
easy_rsa.issue(
dn=dn,
cert_type=CertificateType.server,
)

View file

@ -4,10 +4,9 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from ..config import Config
from ..db import User from ..db import User
from ..easyrsa import CertificateType, EasyRSA from ..easyrsa import CertificateType, EasyRSA
from ._common import Responses, get_current_config, get_current_user from ._common import Responses, get_current_user
router = APIRouter(prefix="/service", tags=["service"]) router = APIRouter(prefix="/service", tags=["service"])
@ -17,11 +16,11 @@ router = APIRouter(prefix="/service", tags=["service"])
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK,
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED, status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
}, },
) )
async def init_pki( async def init_pki(
_: Config = Depends(get_current_config),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
) -> None: ) -> None: