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(
|
def __build_cert(
|
||||||
self,
|
self,
|
||||||
cert_filename: Path,
|
|
||||||
*easyrsa_cmd: str,
|
*easyrsa_cmd: str,
|
||||||
**easyrsa_env: str,
|
**easyrsa_env: str,
|
||||||
) -> x509.Certificate | None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create an X.509 certificate
|
Create an X.509 certificate
|
||||||
"""
|
"""
|
||||||
|
@ -230,16 +229,35 @@ class EasyRSA:
|
||||||
**easyrsa_env,
|
**easyrsa_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
# parse the new certificate
|
except (subprocess.CalledProcessError):
|
||||||
with open(
|
# certificate couldn't be built
|
||||||
self.output_directory.joinpath(cert_filename), "rb"
|
pass
|
||||||
) as cert_file:
|
|
||||||
|
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(
|
return x509.load_pem_x509_certificate(
|
||||||
cert_file.read()
|
cert_file.read()
|
||||||
)
|
)
|
||||||
|
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
except FileNotFoundError:
|
||||||
# certificate couldn't be built
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def init_pki(self) -> None:
|
def init_pki(self) -> None:
|
||||||
|
@ -254,14 +272,17 @@ class EasyRSA:
|
||||||
Build the CA certificate
|
Build the CA certificate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cert = self.__build_cert(
|
self.__build_cert(
|
||||||
Path("ca.crt"),
|
|
||||||
"build-ca",
|
"build-ca",
|
||||||
|
|
||||||
EASYRSA_DN="cn_only",
|
EASYRSA_DN="cn_only",
|
||||||
EASYRSA_REQ_CN="kiwi-vpn-ca",
|
EASYRSA_REQ_CN="kiwi-vpn-ca",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cert = self.get_certificate(
|
||||||
|
cert_type=CertificateType.ca,
|
||||||
|
dn=None,
|
||||||
|
)
|
||||||
assert cert is not None
|
assert cert is not None
|
||||||
|
|
||||||
# # this takes long!
|
# # this takes long!
|
||||||
|
@ -284,9 +305,7 @@ class EasyRSA:
|
||||||
or cert_type is CertificateType.server):
|
or cert_type is CertificateType.server):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.__build_cert(
|
self.__build_cert(
|
||||||
Path("issued").joinpath(f"{dn.common_name}.crt"),
|
|
||||||
|
|
||||||
f"build-{cert_type}-full",
|
f"build-{cert_type}-full",
|
||||||
dn.common_name,
|
dn.common_name,
|
||||||
"nopass",
|
"nopass",
|
||||||
|
@ -294,6 +313,11 @@ class EasyRSA:
|
||||||
**dn.easyrsa_env,
|
**dn.easyrsa_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return self.get_certificate(
|
||||||
|
cert_type=cert_type,
|
||||||
|
dn=dn,
|
||||||
|
)
|
||||||
|
|
||||||
def renew(
|
def renew(
|
||||||
self,
|
self,
|
||||||
dn: DistinguishedName | None = None,
|
dn: DistinguishedName | None = None,
|
||||||
|
@ -305,9 +329,7 @@ class EasyRSA:
|
||||||
if dn is None:
|
if dn is None:
|
||||||
dn = DistinguishedName.build()
|
dn = DistinguishedName.build()
|
||||||
|
|
||||||
return self.__build_cert(
|
self.__build_cert(
|
||||||
Path("issued").joinpath(f"{dn.common_name}.crt"),
|
|
||||||
|
|
||||||
"renew",
|
"renew",
|
||||||
dn.common_name,
|
dn.common_name,
|
||||||
"nopass",
|
"nopass",
|
||||||
|
@ -318,6 +340,11 @@ class EasyRSA:
|
||||||
**dn.easyrsa_env,
|
**dn.easyrsa_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return self.get_certificate(
|
||||||
|
cert_type=None,
|
||||||
|
dn=dn,
|
||||||
|
)
|
||||||
|
|
||||||
def revoke(
|
def revoke(
|
||||||
self,
|
self,
|
||||||
dn: DistinguishedName | None = None,
|
dn: DistinguishedName | None = None,
|
||||||
|
|
|
@ -7,6 +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
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(
|
oauth2_scheme = OAuth2PasswordBearer(
|
||||||
tokenUrl=f"{SETTINGS.api_v1_prefix}/user/authenticate"
|
tokenUrl=f"{SETTINGS.api_v1_prefix}/user/authenticate"
|
||||||
|
@ -35,6 +36,10 @@ class Responses:
|
||||||
"description": "Operation not permitted",
|
"description": "Operation not permitted",
|
||||||
"content": None,
|
"content": None,
|
||||||
}
|
}
|
||||||
|
NEEDS_PKI = {
|
||||||
|
"description": "PKI hasn't been initialized",
|
||||||
|
"content": None,
|
||||||
|
}
|
||||||
ENTRY_ADDED = {
|
ENTRY_ADDED = {
|
||||||
"description": "Entry added to database",
|
"description": "Entry added to database",
|
||||||
"content": None,
|
"content": None,
|
||||||
|
@ -129,3 +134,18 @@ async def get_device_by_id(
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
return device
|
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 fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
|
||||||
from ..db import Device, DeviceCreate, DeviceRead, DeviceStatus, User
|
from ..db import Device, DeviceCreate, DeviceRead, DeviceStatus, User
|
||||||
from ..easyrsa import EASYRSA, DistinguishedName
|
from ..easyrsa import 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_pki,
|
||||||
get_user_by_name)
|
get_user_by_name)
|
||||||
|
|
||||||
router = APIRouter(prefix="/device", tags=["device"])
|
router = APIRouter(prefix="/device", tags=["device"])
|
||||||
|
@ -96,12 +96,14 @@ async def remove_device(
|
||||||
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
||||||
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
|
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
|
||||||
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
||||||
|
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
|
||||||
},
|
},
|
||||||
response_model=DeviceRead,
|
response_model=DeviceRead,
|
||||||
)
|
)
|
||||||
async def request_certificate_issuance(
|
async def request_certificate_issuance(
|
||||||
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),
|
||||||
) -> Device:
|
) -> Device:
|
||||||
"""
|
"""
|
||||||
POST ./{device_id}/issue: Request certificate issuance for a 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
|
# check if we can issue the certificate immediately
|
||||||
if current_user.can_issue:
|
if current_user.can_issue:
|
||||||
if (certificate := EASYRSA.issue(
|
if (certificate := pki.issue(
|
||||||
dn=DistinguishedName.build(device)
|
dn=DistinguishedName.build(device)
|
||||||
)) is not None:
|
)) is not None:
|
||||||
device.set_status(DeviceStatus.certified)
|
device.set_status(DeviceStatus.certified)
|
||||||
|
@ -144,12 +146,14 @@ async def request_certificate_issuance(
|
||||||
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
||||||
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
|
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
|
||||||
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
||||||
|
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
|
||||||
},
|
},
|
||||||
response_model=DeviceRead,
|
response_model=DeviceRead,
|
||||||
)
|
)
|
||||||
async def request_certificate_renewal(
|
async def request_certificate_renewal(
|
||||||
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),
|
||||||
) -> Device:
|
) -> Device:
|
||||||
"""
|
"""
|
||||||
POST ./{device_id}/renew: Request certificate renewal for a 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
|
# check if we can renew the certificate immediately
|
||||||
if current_user.can_renew:
|
if current_user.can_renew:
|
||||||
if (certificate := EASYRSA.renew(
|
if (certificate := pki.renew(
|
||||||
dn=DistinguishedName.build(device)
|
dn=DistinguishedName.build(device)
|
||||||
)) is not None:
|
)) is not None:
|
||||||
device.set_status(DeviceStatus.certified)
|
device.set_status(DeviceStatus.certified)
|
||||||
|
@ -192,12 +196,14 @@ async def request_certificate_renewal(
|
||||||
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
||||||
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
|
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
|
||||||
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
||||||
|
status.HTTP_425_TOO_EARLY: Responses.NEEDS_PKI,
|
||||||
},
|
},
|
||||||
response_model=DeviceRead,
|
response_model=DeviceRead,
|
||||||
)
|
)
|
||||||
async def revoke_certificate(
|
async def revoke_certificate(
|
||||||
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),
|
||||||
) -> Device:
|
) -> Device:
|
||||||
"""
|
"""
|
||||||
POST ./{device_id}/revoke: Revoke a device certificate.
|
POST ./{device_id}/revoke: Revoke a device certificate.
|
||||||
|
@ -217,7 +223,7 @@ async def revoke_certificate(
|
||||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||||
|
|
||||||
# revoke the device certificate
|
# revoke the device certificate
|
||||||
EASYRSA.revoke(dn=DistinguishedName.build(device))
|
pki.revoke(dn=DistinguishedName.build(device))
|
||||||
|
|
||||||
# reset the device
|
# reset the device
|
||||||
device.set_status(DeviceStatus.uncertified)
|
device.set_status(DeviceStatus.uncertified)
|
||||||
|
|
Loading…
Reference in a new issue