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

feat(rust): implemented control api http server #8776

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

davide-baldo
Copy link
Member

@davide-baldo davide-baldo commented Jan 24, 2025

This PR implements CRUD for portals over a brand-new HTTP service. The temporary name I've given to this API is "Control API".

This service is split into two pieces:

  • frontend: reverse proxy that routes the request to the selected node and performs authentication
  • backend: the concrete API implementation, similarly to the CLI APIs

The API is fully defined within the protocol module, this is to avoid any accidental breakage when changing other data structures and also to have the full flexibility to adjust data structure as needed. The API schema will also be exported using OpenAPI in a future PR, and the schema will be used to verify back-compatibility.

The frontend uses a hyper server and converts HTTP requests into a single ockam message (up to 256kb for now) and sends it to the backend. The backend replies with a single message containing the result, which is converted back into HTTP. Two mechanisms are available to resolve a node name into a node: either the node name is converted into a DNS address and a connection is performed, or the node is converted into a worker address (assuming a relay was created by the node).

The authentication is performed on the frontend level and, for now, only implements a basic "Bearer" with a constant time comparison. Frontend and backend are mutually authenticate via credential policy, which by default are control_api_frontend and control_api_backend.

To start the services it is necessary to use the --launch-configuration, with this PR everything is disabled by default.

ockam project ticket \
  --attribute control_api_frontend=true \
  --attribute control_api_backend=true \
  --relay node1
OCKAM_CONTROL_API_AUTHENTICATION_TOKEN=token ockam node create frontend \
  --foreground \
  --launch-configuration \
    '{"start_default_services": true, "startup_services":{"control_api":{"frontend":true}}}'
ockam node create backend \
  --foreground \
  --launch-configuration \
    '{"start_default_services": true, "startup_services":{"control_api":{"backend":true}}}' 
ockam relay create --at /node/frontend/secure/api --to backend node1
curl -v --header 'Authorization: Bearer token' 'http://localhost:4080/node1/tcp-inlet' --output -

OpenAPI schema is generated via utoipa library. I'm planning to add the schema to the repository and fail the CI whenever differs from the generated one, but in a different PR. To generate the OpenAPI schema:

cargo run --bin=node_control_api_schema > schema.yaml

Current schema:

openapi: 3.1.0
info:
  title: Ockam Control API
  description: API to control Ockam nodes
  contact:
    name: Ockam Developers
  license:
    name: Apache-2.0
    identifier: Apache-2.0
  version: 0.1.0
paths:
  /{node}/tcp-inlet:
    get:
      tags:
      - portal
      - tcp-inlet
      summary: List all TCP Inlets
      operationId: list_tcp_inlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Successfully listed
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/InletStatus'
    put:
      tags:
      - portal
      - tcp-inlet
      summary: Create a new TCP Inlet
      operationId: create_tcp_inlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      requestBody:
        description: Creation request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateInletRequest'
        required: true
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InletStatus'
  /{node}/tcp-inlet/{resource_id}:
    get:
      tags:
      - portal
      - tcp-inlet
      summary: Get a TCP Inlet
      operationId: get_tcp_inlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      - name: resource_id
        in: path
        description: Resource ID
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Successfully retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InletStatus'
        '404':
          description: Resource not found
    delete:
      tags:
      - portal
      - tcp-inlet
      summary: Delete a TCP Inlet
      operationId: delete_tcp_inlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      - name: resource_id
        in: path
        description: Resource ID
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Successfully deleted
    patch:
      tags:
      - portal
      - tcp-inlet
      summary: Update a TCP Inlet
      operationId: update_tcp_inlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      - name: resource_id
        in: path
        description: Resource ID
        required: true
        schema:
          type: string
      requestBody:
        description: Update request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateInletRequest'
        required: true
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InletStatus'
        '404':
          description: Not found
  /{node}/tcp-outlet:
    get:
      tags:
      - portal
      - tcp-outlet
      summary: List all TCP Outlets
      operationId: list_tcp_outlets
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Successfully listed
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/OutletStatus'
    put:
      tags:
      - portal
      - tcp-outlet
      summary: Create a TCP Outlet
      operationId: create_tcp_outlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      requestBody:
        description: Creation request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOutletRequest'
        required: true
      responses:
        '201':
          description: Successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutletStatus'
        '409':
          description: Already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /{node}/tcp-outlet/{resource_id}:
    get:
      tags:
      - portal
      - tcp-outlet
      summary: Get a TCP Outlet
      operationId: get_tcp_outlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      - name: resource_id
        in: path
        description: Outlet address
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Successfully retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutletStatus'
        '404':
          description: Not found
    delete:
      tags:
      - portal
      - tcp-outlet
      summary: Delete a TCP Outlet
      operationId: delete_tcp_outlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      - name: resource_id
        in: path
        description: Outlet address
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Successfully deleted
        '404':
          description: Not found
    patch:
      tags:
      - portal
      - tcp-outlet
      summary: Update a TCP Outlet
      operationId: update_tcp_outlet
      parameters:
      - name: node
        in: path
        description: Destination node name
        required: true
        schema:
          type: string
      - name: resource_id
        in: path
        description: Resource ID
        required: true
        schema:
          type: string
      requestBody:
        description: Update request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateOutletRequest'
        required: true
      responses:
        '200':
          description: Successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutletStatus'
        '404':
          description: Not found
components:
  schemas:
    ConnectionStatus:
      type: string
      enum:
      - up
      - down
    CreateInletRequest:
      type: object
      required:
      - to
      properties:
        allow:
          type:
          - string
          - 'null'
          description: |-
            Policy expression that will be used for access control to the TCP Inlet;
            When omitted, the policy set for the "tcp-inlet" resource type will be used
        authorized:
          type:
          - string
          - 'null'
          description: |-
            Restrict access to the TCP Inlet to the provided identity;
            When omitted, all identities are allowed;
          example: Id3b788c6a89de8b1f2fd13743eb3123178cf6ec7c9253be8ddcf7e154abe016a
        from:
          oneOf:
          - $ref: '#/components/schemas/HostnamePort'
            description: Bind address for the TCP Inlet
          default:
            hostname: 127.0.0.1
            port: 0
        identity:
          type:
          - string
          - 'null'
          description: |-
            Identity to be used to create the secure channel;
            When omitted, the node's identity will be used
        kind:
          $ref: '#/components/schemas/InletKind'
          description: Kind of the Portal
        name:
          type:
          - string
          - 'null'
          description: |-
            Name of the TCP Inlet;
            Whe omitted, a random name will be generated
        retry_wait:
          type: integer
          format: int64
          description: |-
            When connection is lost, how long to wait before retrying to connect to the TCP Outlet;
            In milliseconds;
          default: 20000
          minimum: 0
        tls:
          oneOf:
          - $ref: '#/components/schemas/InletTls'
            description: TLS Inlet implementation
          default: none
        to:
          type: string
          description: Multiaddress to a TCP Outlet
          example: /project/default/service/forward_to_node1/secure/api/service/outlet
    CreateOutletRequest:
      type: object
      required:
      - kind
      - to
      - tls
      properties:
        address:
          type:
          - string
          - 'null'
          description: The address of the outlet, also acts as an identifier for the resource
        allow:
          type:
          - string
          - 'null'
          description: |-
            Policy expression that will be used for access control to the TCP Outlet;
            by default the policy set for the "tcp-outlet" resource type will be used
        kind:
          $ref: '#/components/schemas/OutletKind'
          description: The kind of the outlet
        tls:
          $ref: '#/components/schemas/OutletTls'
          description: The TLS configuration for the outlet
        to:
          $ref: '#/components/schemas/HostnamePort'
          description: The destination address of the TCP connection
    ErrorResponse:
      type: object
      required:
      - message
      properties:
        message:
          type: string
    HostnamePort:
      type: object
      required:
      - hostname
      - port
      properties:
        hostname:
          type: string
        port:
          type: integer
          format: int32
          minimum: 0
    InletKind:
      type: string
      enum:
      - regular
      - udp-pucture
      - only-udp-pucture
      - privileged
      - privileged-udp-puncture
      - privileged-only-udp-puncture
    InletStatus:
      type: object
      required:
      - name
      - status
      - bind-address
      - to
      - privileged
      properties:
        bind-address:
          $ref: '#/components/schemas/HostnamePort'
        current-route:
          type:
          - string
          - 'null'
        name:
          type: string
        privileged:
          type: boolean
        status:
          $ref: '#/components/schemas/ConnectionStatus'
        to:
          type: string
    InletTls:
      oneOf:
      - type: string
        enum:
        - none
      - type: string
        enum:
        - project-tls
      - type: object
        required:
        - custom-tls-provider
        properties:
          custom-tls-provider:
            type: object
            required:
            - tls-certificate-provider
            properties:
              tls-certificate-provider:
                type: string
                description: |-
                  Multiaddress to a certificate provider;
                  Typical: /project/default/service/tls_certificate_provider
    OutletKind:
      type: string
      enum:
      - regular
      - privileged
    OutletStatus:
      type: object
      required:
      - to
      - address
      - privileged
      properties:
        address:
          type: string
        privileged:
          type: boolean
        to:
          $ref: '#/components/schemas/HostnamePort'
    OutletTls:
      type: string
      enum:
      - None
      - Validate
    UpdateInletRequest:
      type: object
      properties:
        allow:
          type:
          - string
          - 'null'
          description: Policy expression that will be used for access control to the TCP Inlet;
    UpdateOutletRequest:
      type: object
      properties:
        allow:
          type:
          - string
          - 'null'
          description: Policy expression that will be used for access control to the TCP Outlet;
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: Plaintext
security:
- bearer: []
externalDocs:
  url: https://docs.ockam.io/
  description: Ockam documentation

@davide-baldo davide-baldo force-pushed the davide-baldo/http-api-server branch 3 times, most recently from 74372a1 to 7ecee78 Compare January 24, 2025 19:02
@davide-baldo davide-baldo marked this pull request as ready for review January 26, 2025 22:41
@davide-baldo davide-baldo requested a review from a team as a code owner January 26, 2025 22:41
@davide-baldo davide-baldo force-pushed the davide-baldo/http-api-server branch from 7ecee78 to 1b36521 Compare February 3, 2025 17:34
@davide-baldo davide-baldo force-pushed the davide-baldo/http-api-server branch 5 times, most recently from 9c15258 to 6645a26 Compare February 5, 2025 11:23
@davide-baldo davide-baldo force-pushed the davide-baldo/http-api-server branch from 6645a26 to 157bee1 Compare February 5, 2025 14:50
@davide-baldo davide-baldo marked this pull request as draft February 7, 2025 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant