Skip to content

Commit 20457c8

Browse files
committed
feat: allow product to request interop privileges
1 parent 13141f8 commit 20457c8

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

src/rasenmaeher_api/web/api/product/schema.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,24 @@ class Config: # pylint: disable=too-few-public-methods
9595
},
9696
]
9797
}
98+
99+
100+
# FIXME: Move to libpvarki
101+
class ProductAddRequest(BaseModel): # pylint: disable=too-few-public-methods
102+
"""Request to add product interoperability."""
103+
104+
certcn: str = Field(description="CN of the certificate")
105+
x509cert: str = Field(description="Certificate encoded with CFSSL conventions (newlines escaped)")
106+
107+
class Config: # pylint: disable=too-few-public-methods
108+
"""Example values for schema"""
109+
110+
extra = Extra.forbid
111+
schema_extra = {
112+
"examples": [
113+
{
114+
"certcn": "product.deployment.tld",
115+
"x509cert": "-----BEGIN CERTIFICATE-----\\nMIIEwjCC...\\n-----END CERTIFICATE-----\\n",
116+
},
117+
],
118+
}

src/rasenmaeher_api/web/api/product/views.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Product registration API views."""
22

3+
from typing import cast
34
import logging
45

56
from fastapi import APIRouter, Depends, HTTPException, Request
@@ -11,14 +12,15 @@
1112
from OpenSSL.crypto import load_certificate_request, FILETYPE_PEM # FIXME: use cryptography instead of pyOpenSSL
1213

1314

14-
from .schema import CertificatesResponse, CertificatesRequest, RevokeRequest, KCClientToken
15+
from .schema import CertificatesResponse, CertificatesRequest, RevokeRequest, KCClientToken, ProductAddRequest
1516
from ....db.nonces import SeenToken
1617
from ....db.errors import NotFound
1718
from ....cfssl.public import get_ca, get_bundle
1819
from ....cfssl.private import sign_csr, revoke_pem
1920
from ....cfssl.base import CFSSLError
2021
from ....rmsettings import RMSettings
2122
from ....kchelpers import KCClient
23+
from ....productapihelpers import post_to_product
2224

2325

2426
router = APIRouter()
@@ -138,3 +140,29 @@ async def get_kc_token(
138140
raise HTTPException(403, detail="KC is not enabled")
139141
data = await KCClient.singleton().client_access_token()
140142
return KCClientToken.parse_obj(data)
143+
144+
145+
@router.post("/interop/{tgtproduct}", dependencies=[Depends(MTLSHeader(auto_error=True))])
146+
async def add_interop(
147+
srcproduct: ProductAddRequest,
148+
tgtproduct: str,
149+
request: Request,
150+
) -> OperationResultResponse:
151+
"""Product needs interop privileges with another"""
152+
payload = request.state.mtlsdn
153+
if payload.get("CN") not in RMSettings.singleton().valid_product_cns:
154+
raise HTTPException(status_code=403)
155+
156+
# TODO: Verify that srcproduct certcn and actual cert contents match
157+
158+
manifest = RMSettings.singleton().kraftwerk_manifest_dict
159+
if "products" not in manifest:
160+
LOGGER.error("Manifest does not have products key")
161+
raise HTTPException(status_code=500, detail="Manifest does not have products key")
162+
if not tgtproduct in manifest["products"]:
163+
raise HTTPException(status_code=404, detail=f"Unknown product {tgtproduct}")
164+
resp = await post_to_product(tgtproduct, "/api/v1/interop/add", srcproduct.dict(), OperationResultResponse)
165+
if resp is None:
166+
return OperationResultResponse(success=False, error="post_to_product returned None")
167+
resp = cast(OperationResultResponse, resp)
168+
return resp

tests/ptfpapi/fprun.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
UserCRUDRequest,
2020
)
2121

22+
23+
from rasenmaeher_api.web.api.product.schema import ProductAddRequest
24+
25+
2226
LOGGER = logging.getLogger(__name__)
2327

2428

@@ -128,6 +132,13 @@ async def handle_admin_fragment(request: web.Request) -> web.Response:
128132
return web.json_response(resp.dict())
129133

130134

135+
async def handle_interop_add(request: web.Request) -> web.Response:
136+
"""Respond to additions"""
137+
_req = ProductAddRequest.parse_raw(await request.text())
138+
resp = OperationResultResponse(success=True, extra="Nothing was actually done, this is a fake endpoint for testing")
139+
return web.json_response(resp.dict())
140+
141+
131142
def main() -> int:
132143
"""Main entrypoint, return exit code"""
133144
LOGGER.debug("Called")
@@ -146,6 +157,7 @@ def main() -> int:
146157
[
147158
web.get("/", handle_get_hello),
148159
web.get("/{name}", handle_get_hello),
160+
web.post("/api/v1/interop/add", handle_user_crud),
149161
web.post("/api/v1/users/created", handle_user_crud),
150162
web.post("/api/v1/users/revoked", handle_user_crud),
151163
web.post("/api/v1/users/promoted", handle_user_crud),

0 commit comments

Comments
 (0)