get_pki dependable

This commit is contained in:
Jörn-Michael Miehe 2022-04-07 09:43:58 +00:00
parent c76d80bf47
commit ba7d28e931
3 changed files with 74 additions and 21 deletions

View file

@ -199,10 +199,9 @@ class EasyRSA:
def __build_cert(
self,
cert_filename: Path,
*easyrsa_cmd: str,
**easyrsa_env: str,
) -> x509.Certificate | None:
) -> None:
"""
Create an X.509 certificate
"""
@ -230,16 +229,35 @@ class EasyRSA:
**easyrsa_env,
)
# parse the new certificate
with open(
self.output_directory.joinpath(cert_filename), "rb"
) as cert_file:
except (subprocess.CalledProcessError):
# certificate couldn't be built
pass
return None
def get_certificate(
self,
cert_type: CertificateType | None,
dn: DistinguishedName | None = None,
) -> x509.Certificate | None:
if cert_type is CertificateType.ca:
cert_filename = self.output_directory.joinpath("ca.crt")
else:
if dn is None:
dn = DistinguishedName.build()
cert_filename = (self.output_directory.joinpath("issued")
.joinpath(f"{dn.common_name}.crt"))
try:
# parse the certificate
with open(cert_filename, "rb") as cert_file:
return x509.load_pem_x509_certificate(
cert_file.read()
)
except (subprocess.CalledProcessError, FileNotFoundError):
# certificate couldn't be built
except FileNotFoundError:
return None
def init_pki(self) -> None:
@ -254,14 +272,17 @@ class EasyRSA:
Build the CA certificate
"""
cert = self.__build_cert(
Path("ca.crt"),
self.__build_cert(
"build-ca",
EASYRSA_DN="cn_only",
EASYRSA_REQ_CN="kiwi-vpn-ca",
)
cert = self.get_certificate(
cert_type=CertificateType.ca,
dn=None,
)
assert cert is not None
# # this takes long!
@ -284,9 +305,7 @@ class EasyRSA:
or cert_type is CertificateType.server):
return None
return self.__build_cert(
Path("issued").joinpath(f"{dn.common_name}.crt"),
self.__build_cert(
f"build-{cert_type}-full",
dn.common_name,
"nopass",
@ -294,6 +313,11 @@ class EasyRSA:
**dn.easyrsa_env,
)
return self.get_certificate(
cert_type=cert_type,
dn=dn,
)
def renew(
self,
dn: DistinguishedName | None = None,
@ -305,9 +329,7 @@ class EasyRSA:
if dn is None:
dn = DistinguishedName.build()
return self.__build_cert(
Path("issued").joinpath(f"{dn.common_name}.crt"),
self.__build_cert(
"renew",
dn.common_name,
"nopass",
@ -318,6 +340,11 @@ class EasyRSA:
**dn.easyrsa_env,
)
return self.get_certificate(
cert_type=None,
dn=dn,
)
def revoke(
self,
dn: DistinguishedName | None = None,

View file

@ -7,6 +7,7 @@ from fastapi.security import OAuth2PasswordBearer
from ..config import SETTINGS, Config
from ..db import Device, User
from ..easyrsa import EASYRSA, CertificateType, EasyRSA
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl=f"{SETTINGS.api_v1_prefix}/user/authenticate"
@ -35,6 +36,10 @@ class Responses:
"description": "Operation not permitted",
"content": None,
}
NEEDS_PKI = {
"description": "PKI hasn't been initialized",
"content": None,
}
ENTRY_ADDED = {
"description": "Entry added to database",
"content": None,
@ -129,3 +134,18 @@ async def get_device_by_id(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return device
async def get_pki() -> EasyRSA:
"""
Get the EasyRSA object if the CA has been built.
Status:
- 425: EasyRSA not initialized
"""
if EASYRSA.get_certificate(CertificateType.ca) is None:
raise HTTPException(status_code=status.HTTP_425_TOO_EARLY)
return EASYRSA

View file

@ -5,8 +5,8 @@
from fastapi import APIRouter, Depends, HTTPException, status
from ..db import Device, DeviceCreate, DeviceRead, DeviceStatus, User
from ..easyrsa import EASYRSA, DistinguishedName
from ._common import (Responses, get_current_user, get_device_by_id,
from ..easyrsa import DistinguishedName, EasyRSA
from ._common import (Responses, get_current_user, get_device_by_id, get_pki,
get_user_by_name)
router = APIRouter(prefix="/device", tags=["device"])
@ -96,12 +96,14 @@ async def remove_device(
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
},
response_model=DeviceRead,
)
async def request_certificate_issuance(
current_user: User = Depends(get_current_user),
device: Device = Depends(get_device_by_id),
pki: EasyRSA = Depends(get_pki),
) -> Device:
"""
POST ./{device_id}/issue: Request certificate issuance for a device.
@ -124,7 +126,7 @@ async def request_certificate_issuance(
# check if we can issue the certificate immediately
if current_user.can_issue:
if (certificate := EASYRSA.issue(
if (certificate := pki.issue(
dn=DistinguishedName.build(device)
)) is not None:
device.set_status(DeviceStatus.certified)
@ -144,12 +146,14 @@ async def request_certificate_issuance(
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
},
response_model=DeviceRead,
)
async def request_certificate_renewal(
current_user: User = Depends(get_current_user),
device: Device = Depends(get_device_by_id),
pki: EasyRSA = Depends(get_pki),
) -> Device:
"""
POST ./{device_id}/renew: Request certificate renewal for a device.
@ -172,7 +176,7 @@ async def request_certificate_renewal(
# check if we can renew the certificate immediately
if current_user.can_renew:
if (certificate := EASYRSA.renew(
if (certificate := pki.renew(
dn=DistinguishedName.build(device)
)) is not None:
device.set_status(DeviceStatus.certified)
@ -192,12 +196,14 @@ async def request_certificate_renewal(
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
},
response_model=DeviceRead,
)
async def revoke_certificate(
current_user: User = Depends(get_current_user),
device: Device = Depends(get_device_by_id),
pki: EasyRSA = Depends(get_pki),
) -> Device:
"""
POST ./{device_id}/revoke: Revoke a device certificate.
@ -217,7 +223,7 @@ async def revoke_certificate(
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
# revoke the device certificate
EASYRSA.revoke(dn=DistinguishedName.build(device))
pki.revoke(dn=DistinguishedName.build(device))
# reset the device
device.set_status(DeviceStatus.uncertified)