Compare commits

..

2 commits

Author SHA1 Message Date
fa4c2e45ab HTTP 2XX rework 2022-04-07 11:59:42 +00:00
702aefc6e3 CertificateType rework 2022-04-07 11:37:36 +00:00
6 changed files with 48 additions and 35 deletions

View file

@ -102,7 +102,6 @@ class CertificateType(Enum):
Possible types of certificates Possible types of certificates
""" """
ca = auto()
client = auto() client = auto()
server = auto() server = auto()
@ -238,20 +237,16 @@ class EasyRSA:
def get_certificate( def get_certificate(
self, self,
*, *,
cert_type: CertificateType | None = None,
dn: DistinguishedName | None = None, dn: DistinguishedName | None = None,
) -> x509.Certificate | None: ) -> x509.Certificate | None:
""" """
Get a certificate from the PKI directory Get a certificate from the PKI directory
""" """
if cert_type is CertificateType.ca: if dn is None:
cert_filename = self.output_directory.joinpath("ca.crt") cert_filename = self.output_directory.joinpath("ca.crt")
else: else:
if dn is None:
dn = DistinguishedName.build()
cert_filename = (self.output_directory.joinpath("issued") cert_filename = (self.output_directory.joinpath("issued")
.joinpath(f"{dn.common_name}.crt")) .joinpath(f"{dn.common_name}.crt"))
@ -284,7 +279,7 @@ class EasyRSA:
EASYRSA_REQ_CN="kiwi-vpn-ca", EASYRSA_REQ_CN="kiwi-vpn-ca",
) )
cert = self.get_certificate(cert_type=CertificateType.ca) cert = self.get_certificate()
assert cert is not None assert cert is not None
# # this takes long! # # this takes long!
@ -315,10 +310,7 @@ class EasyRSA:
**dn.easyrsa_env, **dn.easyrsa_env,
) )
return self.get_certificate( return self.get_certificate(dn=dn)
cert_type=cert_type,
dn=dn,
)
def renew( def renew(
self, self,

View file

@ -7,7 +7,7 @@ from fastapi.security import OAuth2PasswordBearer
from ..config import SETTINGS, Config from ..config import SETTINGS, Config
from ..db import Device, User from ..db import Device, User
from ..easyrsa import EASYRSA, CertificateType, EasyRSA from ..easyrsa import EASYRSA, EasyRSA
oauth2_scheme = OAuth2PasswordBearer( oauth2_scheme = OAuth2PasswordBearer(
tokenUrl=f"{SETTINGS.api_v1_prefix}/user/authenticate" tokenUrl=f"{SETTINGS.api_v1_prefix}/user/authenticate"
@ -22,8 +22,15 @@ class Responses:
""" """
OK = { OK = {
"description": "Operation successful",
}
OK_NONE = {
"description": "Operation successful",
"content": None, "content": None,
} }
OK_WAIT = {
"description": "Operation successful, waiting for approval",
}
NOT_INSTALLED = { NOT_INSTALLED = {
"description": "kiwi-vpn not installed", "description": "kiwi-vpn not installed",
"content": None, "content": None,
@ -145,7 +152,7 @@ async def get_pki() -> EasyRSA:
- 425: EasyRSA not initialized - 425: EasyRSA not initialized
""" """
if EASYRSA.get_certificate(cert_type=CertificateType.ca) is None: if EASYRSA.get_certificate() is None:
raise HTTPException(status_code=status.HTTP_425_TOO_EARLY) raise HTTPException(status_code=status.HTTP_425_TOO_EARLY)
return EASYRSA return EASYRSA

View file

@ -15,7 +15,7 @@ router = APIRouter(prefix="/admin", tags=["admin"])
@router.put( @router.put(
"/install/config", "/install/config",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS, status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
}, },
) )
@ -43,7 +43,7 @@ async def initial_configure(
@router.put( @router.put(
"/install/admin", "/install/admin",
responses={ responses={
status.HTTP_201_CREATED: Responses.OK, status.HTTP_201_CREATED: Responses.OK_NONE,
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED, status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS, status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
}, },
@ -77,7 +77,7 @@ async def create_initial_admin(
@router.put( @router.put(
"/config", "/config",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,

View file

@ -2,7 +2,7 @@
/device endpoints. /device endpoints.
""" """
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, Response, status
from ..db import Device, DeviceCreate, DeviceRead, DeviceStatus, User from ..db import Device, DeviceCreate, DeviceRead, DeviceStatus, User
from ..easyrsa import DistinguishedName, EasyRSA from ..easyrsa import DistinguishedName, EasyRSA
@ -59,7 +59,7 @@ async def add_device(
@router.delete( @router.delete(
"/{device_id}", "/{device_id}",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
@ -87,10 +87,11 @@ async def remove_device(
device.delete() device.delete()
@router.post( @router.put(
"/{device_id}/issue", "/{device_id}/certificate",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK | {"model": DeviceRead},
status.HTTP_202_ACCEPTED: Responses.OK_WAIT | {"model": DeviceRead},
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
@ -99,17 +100,21 @@ async def remove_device(
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI, status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
}, },
response_model=DeviceRead, response_model=DeviceRead,
status_code=status.HTTP_202_ACCEPTED,
) )
async def request_certificate_issuance( async def request_certificate_issuance(
response: Response,
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
device: Device = Depends(get_device_by_id), device: Device = Depends(get_device_by_id),
pki: EasyRSA = Depends(get_pki), pki: EasyRSA = Depends(get_pki),
) -> Device: ) -> Device:
""" """
POST ./{device_id}/issue: Request certificate issuance for a device. PUT ./{device_id}/certificate: Request certificate issuance for a device.
Status: Status:
- 200: certificate issued
- 202: issuance requested
- 403: no user permission to edit device - 403: no user permission to edit device
- 409: device certificate cannot be "issued" - 409: device certificate cannot be "issued"
""" """
@ -132,15 +137,18 @@ async def request_certificate_issuance(
device.set_status(DeviceStatus.certified) device.set_status(DeviceStatus.certified)
device.expiry = certificate.not_valid_after device.expiry = certificate.not_valid_after
response.status_code = status.HTTP_200_OK
# return updated device # return updated device
device.update() device.update()
return device return device
@router.post( @router.patch(
"/{device_id}/renew", "/{device_id}/certificate",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK | {"model": DeviceRead},
status.HTTP_202_ACCEPTED: Responses.OK_WAIT | {"model": DeviceRead},
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
@ -149,17 +157,21 @@ async def request_certificate_issuance(
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI, status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
}, },
response_model=DeviceRead, response_model=DeviceRead,
status_code=status.HTTP_202_ACCEPTED,
) )
async def request_certificate_renewal( async def request_certificate_renewal(
response: Response,
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
device: Device = Depends(get_device_by_id), device: Device = Depends(get_device_by_id),
pki: EasyRSA = Depends(get_pki), pki: EasyRSA = Depends(get_pki),
) -> Device: ) -> Device:
""" """
POST ./{device_id}/renew: Request certificate renewal for a device. PATCH ./{device_id}/certificate: Request certificate renewal for a device.
Status: Status:
- 200: certificate renewed
- 202: renewal requested
- 403: no user permission to edit device - 403: no user permission to edit device
- 409: device certificate cannot be "renewed" - 409: device certificate cannot be "renewed"
""" """
@ -182,15 +194,17 @@ async def request_certificate_renewal(
device.set_status(DeviceStatus.certified) device.set_status(DeviceStatus.certified)
device.expiry = certificate.not_valid_after device.expiry = certificate.not_valid_after
response.status_code = status.HTTP_200_OK
# return updated device # return updated device
device.update() device.update()
return device return device
@router.post( @router.delete(
"/{device_id}/revoke", "/{device_id}/certificate",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
@ -206,7 +220,7 @@ async def revoke_certificate(
pki: EasyRSA = Depends(get_pki), pki: EasyRSA = Depends(get_pki),
) -> Device: ) -> Device:
""" """
POST ./{device_id}/revoke: Revoke a device certificate. DELETE ./{device_id}/certificate: Revoke a device certificate.
Status: Status:

View file

@ -14,7 +14,7 @@ router = APIRouter(prefix="/service", tags=["service"])
@router.put( @router.put(
"/pki/init", "/pki/init",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,

View file

@ -26,7 +26,7 @@ class Token(BaseModel):
@router.post( @router.post(
"/authenticate", "/authenticate",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
}, },
@ -64,7 +64,7 @@ async def login(
@router.get( @router.get(
"/current", "/current",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_USER, status.HTTP_403_FORBIDDEN: Responses.NEEDS_USER,
@ -127,7 +127,7 @@ async def add_user(
@router.delete( @router.delete(
"/{user_name}", "/{user_name}",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
@ -189,7 +189,7 @@ async def extend_tags(
@router.delete( @router.delete(
"/{user_name}/tags", "/{user_name}/tags",
responses={ responses={
status.HTTP_200_OK: Responses.OK, status.HTTP_200_OK: Responses.OK_NONE,
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_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION, status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,