get_pki dependable
This commit is contained in:
parent
c76d80bf47
commit
ba7d28e931
3 changed files with 74 additions and 21 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue