Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement GET /api/v1/node/{name}/configuration #46

Merged
merged 6 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions nodeman/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
from pydantic.types import AwareDatetime

from .db_models import TapirNode
from .jose import PrivateJwk, PrivateSymmetric, PublicJwk, PublicJwks, public_key_factory
from .jose import (
PrivateJwk,
PrivateSymmetric,
PublicJwk,
PublicJwks,
public_key_factory,
)
from .settings import MqttUrl

MAX_REQUEST_AGE = 300
Expand Down Expand Up @@ -81,7 +87,7 @@ def validate_pem_bundle(cls, v: str):
return v


class NodeConfiguration(NodeCertificate):
class NodeConfiguration(BaseModel):
name: str = Field(title="Node name", examples=["node.example.com"])
mqtt_broker: MqttUrl = Field(title="MQTT Broker", examples=["mqtts://broker.example.com"])
mqtt_topics: dict[str, str] = Field(
Expand All @@ -90,3 +96,7 @@ class NodeConfiguration(NodeCertificate):
examples=[{"edm": "configuration/node.example.com/edm", "pop": "configuration/node.example.com/pop"}],
)
trusted_jwks: PublicJwks = Field(title="Trusted JWKS")


class NodeEnrollmentResult(NodeConfiguration, NodeCertificate):
pass
45 changes: 38 additions & 7 deletions nodeman/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
NodeCertificate,
NodeCollection,
NodeConfiguration,
NodeEnrollmentResult,
NodeInformation,
PublicKeyFormat,
RenewalRequest,
Expand Down Expand Up @@ -48,6 +49,15 @@ def find_node(name: str) -> TapirNode:
raise HTTPException(status.HTTP_404_NOT_FOUND)


def create_node_configuration(name: str, request: Request) -> NodeConfiguration:
return NodeConfiguration(
name=name,
mqtt_broker=request.app.settings.nodes.mqtt_broker,
mqtt_topics=request.app.settings.nodes.mqtt_topics,
trusted_jwks=request.app.trusted_jwks,
)


@router.post(
"/api/v1/node",
status_code=status.HTTP_201_CREATED,
Expand Down Expand Up @@ -199,7 +209,7 @@ def delete_node(name: str, username: Annotated[str, Depends(get_current_username
@router.post(
"/api/v1/node/{name}/enroll",
responses={
status.HTTP_200_OK: {"model": NodeConfiguration},
status.HTTP_200_OK: {"model": NodeEnrollmentResult},
status.HTTP_404_NOT_FOUND: {},
},
tags=["client"],
Expand All @@ -208,7 +218,7 @@ def delete_node(name: str, username: Annotated[str, Depends(get_current_username
async def enroll_node(
name: str,
request: Request,
) -> NodeConfiguration:
) -> NodeEnrollmentResult:
"""Enroll new node"""

node = find_node(name)
Expand Down Expand Up @@ -269,11 +279,8 @@ async def enroll_node(

nodes_enrolled.add(1)

return NodeConfiguration(
name=name,
mqtt_broker=request.app.settings.nodes.mqtt_broker,
mqtt_topics=request.app.settings.nodes.mqtt_topics,
trusted_jwks=request.app.trusted_jwks,
return NodeEnrollmentResult(
**create_node_configuration(name=name, request=request).model_dump(),
x509_certificate=node_certificate.x509_certificate,
x509_ca_certificate=node_certificate.x509_ca_certificate,
x509_certificate_serial_number=node_certificate.x509_certificate_serial_number,
Expand Down Expand Up @@ -332,3 +339,27 @@ async def renew_node(
nodes_renewed.add(1)

return res


@router.get(
"/api/v1/node/{name}/configuration",
responses={
status.HTTP_200_OK: {"model": NodeConfiguration},
status.HTTP_404_NOT_FOUND: {},
},
tags=["client"],
response_model_exclude_none=True,
)
async def get_node_configuration(
name: str,
request: Request,
) -> NodeConfiguration:
"""Enroll new node"""
jschlyter marked this conversation as resolved.
Show resolved Hide resolved

node = find_node(name)

if not node.activated:
logging.debug("Node %s not activated", name, extra={"nodename": name})
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Node not activated")

return create_node_configuration(name=name, request=request)
jschlyter marked this conversation as resolved.
Show resolved Hide resolved
16 changes: 15 additions & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@
from nodeman.models import PublicKeyFormat
from nodeman.server import NodemanServer
from nodeman.settings import Settings
from nodeman.x509 import RSA_EXPONENT, CertificateAuthorityClient, generate_ca_certificate, generate_x509_csr
from nodeman.x509 import (
RSA_EXPONENT,
CertificateAuthorityClient,
generate_ca_certificate,
generate_x509_csr,
)

ADMIN_TEST_NODE_COUNT = 100
BACKEND_CREDENTIALS = ("username", "password")
Expand Down Expand Up @@ -140,6 +145,15 @@ def _test_enroll(data_key: JWK, x509_key: PrivateKey, requested_name: str | None
assert node_information["name"] == name
assert node_information["activated"] is not None

#########################
# Get node configuration

response = client.get(f"{node_url}/configuration")
assert response.status_code == status.HTTP_200_OK
node_information = response.json()
print(json.dumps(node_information, indent=4))
assert node_information["name"] == name

#####################
# Get node public key

Expand Down
Loading