diff --git a/nodeman/client.py b/nodeman/client.py index b12eef0..e26f481 100644 --- a/nodeman/client.py +++ b/nodeman/client.py @@ -152,10 +152,11 @@ def command_create(args: argparse.Namespace) -> NodeBootstrapInformation: create_response = response.json() name = create_response["name"] - secret = create_response["secret"] - logging.info("Created node with name=%s secret=%s", name, secret) - return NodeBootstrapInformation(name=name, secret=secret) + logging.debug("Response: %s", json.dumps(create_response, indent=4)) + logging.info("Created node with name=%s", name) + + return NodeBootstrapInformation(name=name, key=create_response["key"]) def command_delete(args: argparse.Namespace) -> None: @@ -209,7 +210,7 @@ def command_enroll(args: argparse.Namespace) -> NodeConfiguration: if args.create: node_bootstrap_information = command_create(args) name = node_bootstrap_information.name - secret = node_bootstrap_information.secret + secret = node_bootstrap_information.key.k else: name = args.name secret = args.secret diff --git a/nodeman/jose.py b/nodeman/jose.py index e79b3d7..50598ef 100644 --- a/nodeman/jose.py +++ b/nodeman/jose.py @@ -38,6 +38,14 @@ class PublicOKP(BaseJWK): x: Base64UrlString +class PrivateSymmetric(BaseJWK): + """JWK: Private symmetric key""" + + kty: Annotated[str, StringConstraints(pattern=r"^oct$")] + k: Base64UrlString + alg: str | None = None + + PublicJwk = PublicRSA | PublicEC | PublicOKP diff --git a/nodeman/models.py b/nodeman/models.py index 61c3f6a..f0d217f 100644 --- a/nodeman/models.py +++ b/nodeman/models.py @@ -7,7 +7,7 @@ from pydantic.types import AwareDatetime from .db_models import TapirNode -from .jose import PublicJwk, public_key_factory +from .jose import PrivateSymmetric, PublicJwk, public_key_factory from .settings import MqttUrl MAX_REQUEST_AGE = 300 @@ -65,7 +65,7 @@ class NodeCollection(BaseModel): class NodeBootstrapInformation(BaseModel): name: str = Field(title="Node name") - secret: str = Field(title="Enrollment secret") + key: PrivateSymmetric = Field(title="Enrollment JWK") class NodeCertificate(BaseModel): diff --git a/nodeman/nodes.py b/nodeman/nodes.py index cf413ab..5dc5d37 100644 --- a/nodeman/nodes.py +++ b/nodeman/nodes.py @@ -55,11 +55,14 @@ def find_node(name: str) -> TapirNode: status.HTTP_201_CREATED: {"model": NodeBootstrapInformation}, }, tags=["backend"], + response_model_exclude_none=True, ) async def create_node( request: Request, username: Annotated[str, Depends(get_current_username)], name: str | None = None ) -> NodeBootstrapInformation: - secret = JWK.generate(kty="oct", size=256).k + """Create node""" + + node_enrollment_key = JWK.generate(kty="oct", size=256, alg="HS256") domain = request.app.settings.nodes.domain if name is None: @@ -71,12 +74,13 @@ async def create_node( logging.warning("Explicit node name %s not acceptable", name, extra={"nodename": name}) raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Invalid node name") - node_secret = TapirNodeSecret(name=node.name, secret=secret).save() + TapirNodeSecret(name=node.name, secret=node_enrollment_key.k).save() nodes_created.add(1, {"creator": username}) logging.info("%s created node %s", username, node.name, extra={"username": username, "nodename": node.name}) - return NodeBootstrapInformation(name=node.name, secret=node_secret.secret) + + return NodeBootstrapInformation(name=node.name, key=node_enrollment_key.export(as_dict=True, private_key=True)) @router.get( diff --git a/tests/test_api.py b/tests/test_api.py index 2df25e3..ee1743e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -80,7 +80,7 @@ def _test_enroll(data_key: JWK, x509_key: PrivateKey, requested_name: str | None assert response.status_code == status.HTTP_201_CREATED create_response = response.json() name = create_response["name"] - secret = create_response["secret"] + secret = create_response["key"]["k"] if requested_name: assert name == requested_name logging.info("Got name=%s secret=%s", name, secret) @@ -99,8 +99,8 @@ def _test_enroll(data_key: JWK, x509_key: PrivateKey, requested_name: str | None ##################### # Enroll created node - hmac_key = JWK(kty="oct", k=secret) - hmac_alg = "HS256" + hmac_key = JWK(**create_response["key"]) + hmac_alg = hmac_key.alg data_alg = jwk_to_alg(data_key) @@ -276,8 +276,10 @@ def test_enroll_bad_hmac_signature() -> None: create_response = response.json() name = create_response["name"] - hmac_key = JWK.generate(kty="oct") - hmac_alg = "HS256" + hmac_key = JWK.generate(kty="oct", size=256, alg="HS256") + hmac_alg = hmac_key.alg + + assert hmac_alg == "HS256" data_key = JWK.generate(kty=kty, crv=crv) data_alg = jwk_to_alg(data_key) @@ -321,10 +323,11 @@ def test_enroll_bad_data_signature() -> None: assert response.status_code == status.HTTP_201_CREATED create_response = response.json() name = create_response["name"] - secret = create_response["secret"] - hmac_key = JWK(kty="oct", k=secret) - hmac_alg = "HS256" + hmac_key = JWK(**create_response["key"]) + hmac_alg = hmac_key.alg + + assert hmac_alg == "HS256" data_key = JWK.generate(kty=kty, crv=crv) bad_data_key = JWK.generate(kty=kty, crv=crv)