Skip to content

Latest commit

 

History

History

agent

agent

A Go implementation of a CDPI agent.

Overview

The Control-to-Data-Plane Interface (CDPI) works by exchanging Protocol Buffers, a language-agnostic format and toolchain for serializing messages, transmitted using gRPC, a performant RPC framework with many sophisticated features.

This directory provides two important pieces for interacting with the Spacetime CDPI:

  • The agent (//agent/cmd/agent), a Go binary that handle the CDPI protocol and authentication details while delegating the actual implementation of enactments to a user-configured external process. While the details of the CDPI protocol are still subject to change, the command line interface for these binaries is intended to be significantly more stable and provide an easy to develop against abstraction that insulates platform integrators from the majority of those changes.

  • The agent library (//agent), a Go package that provides a growing set of abstractions for writing a new CDPI agent. This library is subject to change alongside the CDPI protocol, so platform integrators are encouraged to use the agent binary until the underlying APIs reach a stable milestone.

Building

This repo uses the bazel build system. Once you have a copy of bazel in your $PATH, running bazel build //agent/cmd/agent will build the Go binary. Similarly, running bazel build //agent will build the Go library.

For a full list of available build targets, you can use bazel query:

bazel query //agent/...:all

Getting started with the agent binary

Configuration

The agent binary accepts configuration in the form of a protobuf message, documented in config.proto. The message can be encoded in prototext format (human readable and writable), json, or the binary proto format. Most users will find the prototext format the easiest to use, and as such it's the default.

More details for each aspect of the configuration are provided below, but a simple configuration file might look like this:

# each network_node is configured with a stanza like so:
network_nodes: {
  id: "node-a"
  enactment_driver: {
    connection_params: {
      # TODO: change to point to the domain of your spacetime instance
      endpoint_uri: "scheduling.my_instance.spacetime.aalyria.com"

      transport_security: {
        system_cert_pool: {}
      }

      auth_strategy: {
        jwt: {
          # TODO: change to the domain of your spacetime instance
          audience: "scheduling.my_instance.spacetime.aalyria.com"
          # TODO: use the email your Aalyria representative will share with you
          email: "[email protected]"
          # TODO: use the private key ID your Aalyria representative will share with you
          private_key_id: "BADDB0BACAFE"
          signing_strategy: {
	          # TODO: change to the path of your PEM-encoded RSA private key
            private_key_file: "/path/to/agent/private/key.pem"
          }
        }
      }
    }

    external_command: {
      # while each command invocation will receive a node ID as part of the
      # enactment request, you can also pass additional arguments here to help
      # integrate with your own systems
      args: "/usr/local/bin/do_enactments"
      args: "--node=a"
      args: "--format=json"
      # Encode enactment requests as JSON to the process's standard input and
      # expect any new state messages to be written as JSON to the process's
      # standard output (this is the default)
      proto_format: JSON
    }
  }
}

network_nodes: {
  id: "node-b"
  enactment_driver: {
    connection_params: {
      # TODO: change to point to the domain of your spacetime instance
      endpoint_uri: "scheduling.my_instance.spacetime.aalyria.com"

      transport_security: {
        system_cert_pool: {}
      }

      auth_strategy: {
        jwt: {
          # TODO: change to the domain of your spacetime instance
          audience: "scheduling.my_instance.spacetime.aalyria.com"
          # TODO: use the email your Aalyria representative will share with you
          email: "[email protected]"
          # TODO: use the private key ID your Aalyria representative will share with you
          private_key_id: "BADDB0BACAFE"
          signing_strategy: {
	          # TODO: change to the path of your PEM-encoded RSA private key
            private_key_file: "/path/to/agent/private/key.pem"
          }
        }
      }
    }

    external_command: {
      args: "/usr/local/bin/some_other_enactment_cmd"
      # Use the protobuf binary format for encoding both stdin and stdout messages.
      proto_format: WIRE
    }
  }
}

See the documentation in config.proto for more details on the available options. You can use the --dry-run flag to check that your configuration is valid:

bazel run //agent/cmd/agent -- --log-level trace --config "$PWD/config.textproto" --dry-run
INFO: Invocation ID: d8a4b02f-1e01-47cb-bd26-5366704165af
INFO: Analyzed target //agent/cmd/agent:agent (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //agent/cmd/agent:agent up-to-date:
  bazel-bin/agent/cmd/agent/agent_/agent
INFO: Elapsed time: 1.768s, Critical Path: 1.56s
INFO: 3 processes: 1 internal, 2 linux-sandbox.
INFO: Build completed successfully, 3 total actions
INFO: Running command line: bazel-bin/agent/cmd/agent/agent_/agent --log-level trace --config /path/to/config.textproto --format text --dry-run
2023-04-19 11:57:01AM INF config is valid

Authentication

The agent uses signed JSON Web Tokens (JWTs) to authenticate with the CDPI service. The JWT needs to be signed using an RSA private key with a corresponding public key that's been shared - inside of a self-signed x509 certificate - with the Aalyria team.

Creating a test keypair

For testing purposes, you can generate a valid key using the openssl tool:

# generate a private key of size 4096 and save it to agent_priv_key.pem
openssl genrsa -out agent_priv_key.pem 4096
# extract the public key and save it to an x509 certificate named
# agent_pub_key.cer (with an expiration far into the future)
openssl req -new -x509 -key agent_priv_key.pem -out agent_pub_key.cer -days 36500

Starting the agent

Assuming you've saved your configuration in a file called config.textproto, you can use the following command to start the agent:

bazel run //agent/cmd/agent -- --config "$PWD/my_config.textproto" --log-level debug

NOTE: bazel run changes the working directory of the process, so you'll need to use absolute paths to point to the config file.

If the agent was able to authenticate correctly, you should see something like this appear as output (requires --log-level be "debug" or "trace"):

2023-04-18 08:44:48PM INF starting agent
2023-04-18 08:44:48PM DBG node controller starting nodeID=Atlantis-groundstation

Next steps

Writing a custom extproc enactment backend

Writing a custom enactment backend using the agent is relatively simple as the agent takes care of the CDPI protocol details, including timing and error reporting. When the agent receives a scheduled control update, it invokes the configured external process, writes the incoming ScheduledControlUpdate message in the encoding format of your choice to the process's stdin, and optionally reads a new ControlPlaneState message (using the same encoding format) from the process's stdout.

  • If nothing is written to stdout and the process terminates with an exit code of 0, the enactment is considered successful and the node state is assumed to have been updated to match the change.

  • If anything goes wrong during the enactment (indicated by a non-zero exit code), the process's stderr and exit code are combined to form a gRPC status which is conveyed back to the CDPI endpoint as the (failing) result of the enactment.

Since the external process only needs to be able to encode and decode JSON, it's trivial to write the platform-specific logic in whatever language best suits the task. Included in this repo are some sample programs that demonstrate basic error handling and message parsing in different languages:

  • examples/enact_flow_forward_updates.py: A python script that reads the input messages as ad-hoc JSON, implements some basic error handling, and demonstrates how one might go about enacting flow updates (the actual logic for forwarding packets is left as an exercise for the reader).

Operational notes

Time synchronization

All agent implementations and instantiations MUST have some means to maintain time synchronization. Ideally an agent's time would be synchronzied to the same ultimate source(s) used by Spacetime itself, though this is not a strict requirement.

RECOMMENDED time synchronization mechanisms include:

  • GPS or another Global Navigation Satellite System (GNSS) PNT service
  • Network Time Protocol v4 (NTPv4), ideally with Network Time Security (NTS)
  • IEEE Precision Time Protocol (IEEE 1588-2019)

Some experimental time synchronization mechanisms include:

  • Network Time Protocol v5 (draft)
  • roughtime (draft)

An agent SHOULD maintain a sub-second difference from its chosen time source(s), and all RECOMMENDED time synchronizations can in principle achieve much better accuracies. The exact requirements and realistically achievable accuracies, however, are specific to a given deployment scenario.

If an agent instance or its underlying platform cannot maintain adequate time synchronization, for a mission-specific definition of "adequate", then it MUST do one of the following:

  • include notification of the loss of adequate time synchronization with any reported telemetry, or
  • not report telemetry (information is too untrustworthy to be useful).

An agent MAY continue to enact Scheduling API commands, especially if doing so might lead to restoring adequate time synchronization (e.g. restoration of a data plane connection over which a networked time protocol's messages are forwarded).