From 52d6bb20dbae0edd45366f0cb36c953489897288 Mon Sep 17 00:00:00 2001 From: Christopher Chong Date: Tue, 12 Dec 2023 06:55:40 +0300 Subject: [PATCH] Add git commit hash and timestamp to info endpoint (#392) * Add commit hash and timestamp * Fixed Docker build problem #392 * Fix error message and docker build. * Update README and openapi yaml. * Formatting. --------- Co-authored-by: Hendrik Eeckhaut --- notary-server/README.md | 25 ++++-- notary-server/build.rs | 21 +++++ notary-server/notary-server.Dockerfile | 2 +- .../notary-server.Dockerfile.dockerignore | 3 + notary-server/openapi.yaml | 90 ++++++++++++++++++- notary-server/src/domain.rs | 4 + notary-server/src/server.rs | 5 ++ 7 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 notary-server/build.rs diff --git a/notary-server/README.md b/notary-server/README.md index a3fb3e4838..6969eb85ad 100644 --- a/notary-server/README.md +++ b/notary-server/README.md @@ -58,7 +58,7 @@ docker run --init -p 127.0.0.1:7047:7047 notary-server:local ```bash docker run --init -p 127.0.0.1:7047:7047 -v :/root/.notary-server/fixture notary-server:local ``` -- Example 2: Using different key for notarization (your folder should only contain `notary.key`): +- Example 2: Using different key for notarization: ```bash docker run --init -p 127.0.0.1:7047:7047 -v :/root/.notary-server/fixture/notary notary-server:local ``` @@ -71,7 +71,7 @@ Defined in the [OpenAPI specification](./openapi.yaml). ### WebSocket APIs #### /notarize ##### Description -To perform notarization using the session id (unique id returned upon calling the `/session` endpoint successfully) submitted as a custom header. +To perform notarization using the session id (unique id returned upon calling the `/session` endpoint successfully). ##### Query Parameter `sessionId` @@ -86,13 +86,7 @@ The main objective of a notary server is to perform notarization together with a 1. TCP client — which has access and control over the transport layer, i.e. TCP 2. WebSocket client — which has no access over TCP and instead uses WebSocket for notarization -### Design Choices -#### Web Framework -Axum is chosen as the framework to serve HTTP and WebSocket requests from the prover clients due to its rich and well supported features, e.g. native integration with Tokio/Hyper/Tower, customizable middleware, ability to support lower level integration of TLS ([example](https://github.com/tokio-rs/axum/blob/main/examples/low-level-rustls/src/main.rs)). To simplify the notary server setup, a single Axum router is used to support both HTTP and WebSocket connections, i.e. all requests can be made to the same port of the notary server. - -#### WebSocket -Axum's internal implementation of WebSocket uses [tokio_tungstenite](https://docs.rs/tokio-tungstenite/latest/tokio_tungstenite/), which provides a WebSocket struct that doesn't implement [AsyncRead](https://docs.rs/futures/latest/futures/io/trait.AsyncRead.html) and [AsyncWrite](https://docs.rs/futures/latest/futures/io/trait.AsyncWrite.html). Both these traits are required by TLSN core libraries for prover and notary. To overcome this, a [slight modification](./src/service/axum_websocket.rs) of Axum's implementation of WebSocket is used, where [async_tungstenite](https://docs.rs/async-tungstenite/latest/async_tungstenite/) is used instead so that [ws_stream_tungstenite](https://docs.rs/ws_stream_tungstenite/latest/ws_stream_tungstenite/index.html) can be used to wrap on top of the WebSocket struct to get AsyncRead and AsyncWrite implemented. - +### Features #### Notarization Configuration To perform notarization, some parameters need to be configured by the prover and notary server (more details in the [OpenAPI specification](./openapi.yaml)), i.e. - maximum transcript size @@ -105,3 +99,16 @@ After calling the configuration endpoint above, prover can proceed to start nota #### Signatures Currently, both the private key (and cert) used to establish TLS connection with prover, and the private key used by notary server to sign the notarized transcript, are hardcoded PEM keys stored in this repository. Though the paths of these keys can be changed in the config to use different keys instead. + +#### Authorization +An optional authorization module is available to only allow requests with valid API key attached in the authorization header. The API key whitelist path (as well as the flag to enable/disable this module) is configurable [here](./config/config.yaml). + +#### Optional TLS +TLS between prover and notary is currently manually handled in the server, though it can be turned off if TLS is to be handled by an external environment, e.g. reverse proxy, cloud setup (configurable [here](./config/config.yaml)). + +### Design Choices +#### Web Framework +Axum is chosen as the framework to serve HTTP and WebSocket requests from the prover clients due to its rich and well supported features, e.g. native integration with Tokio/Hyper/Tower, customizable middleware, ability to support lower level integration of TLS ([example](https://github.com/tokio-rs/axum/blob/main/examples/low-level-rustls/src/main.rs)). To simplify the notary server setup, a single Axum router is used to support both HTTP and WebSocket connections, i.e. all requests can be made to the same port of the notary server. + +#### WebSocket +Axum's internal implementation of WebSocket uses [tokio_tungstenite](https://docs.rs/tokio-tungstenite/latest/tokio_tungstenite/), which provides a WebSocket struct that doesn't implement [AsyncRead](https://docs.rs/futures/latest/futures/io/trait.AsyncRead.html) and [AsyncWrite](https://docs.rs/futures/latest/futures/io/trait.AsyncWrite.html). Both these traits are required by TLSN core libraries for prover and notary. To overcome this, a [slight modification](./src/service/axum_websocket.rs) of Axum's implementation of WebSocket is used, where [async_tungstenite](https://docs.rs/async-tungstenite/latest/async_tungstenite/) is used instead so that [ws_stream_tungstenite](https://docs.rs/ws_stream_tungstenite/latest/ws_stream_tungstenite/index.html) can be used to wrap on top of the WebSocket struct to get AsyncRead and AsyncWrite implemented. diff --git a/notary-server/build.rs b/notary-server/build.rs new file mode 100644 index 0000000000..766117236d --- /dev/null +++ b/notary-server/build.rs @@ -0,0 +1,21 @@ +use std::process::Command; + +fn main() { + // Used to extract latest HEAD commit hash and timestamp for the /info endpoint + let output = Command::new("git") + .args(["show", "HEAD", "-s", "--format=%H,%cI"]) + .output() + .expect("Git command to get commit hash and timestamp should work during build process"); + + let output_string = + String::from_utf8(output.stdout).expect("Git command should produce valid string output"); + + let (commit_hash, commit_timestamp) = output_string + .as_str() + .split_once(',') + .expect("Git commit hash and timestamp string output should be comma separated"); + + // Pass these 2 values as env var to the program + println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_hash); + println!("cargo:rustc-env=GIT_COMMIT_TIMESTAMP={}", commit_timestamp); +} diff --git a/notary-server/notary-server.Dockerfile b/notary-server/notary-server.Dockerfile index 964977a29c..077f3ed0a0 100644 --- a/notary-server/notary-server.Dockerfile +++ b/notary-server/notary-server.Dockerfile @@ -19,7 +19,7 @@ FROM rust:bookworm AS builder WORKDIR /usr/src/tlsn COPY . . -RUN cd notary-server; cargo install --path . +RUN cargo install --path notary-server FROM ubuntu:latest WORKDIR /root/.notary-server diff --git a/notary-server/notary-server.Dockerfile.dockerignore b/notary-server/notary-server.Dockerfile.dockerignore index a18a55133f..b326c7e88d 100644 --- a/notary-server/notary-server.Dockerfile.dockerignore +++ b/notary-server/notary-server.Dockerfile.dockerignore @@ -8,5 +8,8 @@ !/tlsn !/components +# include .git for program to get git info +!/.git + # exclude any /target folders inside the included folders above **/target* diff --git a/notary-server/openapi.yaml b/notary-server/openapi.yaml index 21e58bb763..fadff3ccc6 100644 --- a/notary-server/openapi.yaml +++ b/notary-server/openapi.yaml @@ -3,12 +3,66 @@ openapi: 3.0.0 info: title: Notary Server description: Notary server written in Rust to provide notarization service. - version: 0.1.0 + version: 0.1.0-alpha.2 tags: + - name: General - name: Notarization paths: + /healthcheck: + get: + tags: + - General + description: Healthcheck endpoint + parameters: + - in: header + name: Authorization + description: Whitelisted API key if auth module is turned on + schema: + type: string + required: false + responses: + "200": + description: Ok response from server + content: + text/plain: + schema: + type: string + example: "Ok" + "401": + description: API key is invalid + content: + text/plain: + schema: + type: string + example: "Unauthorized request from prover: Invalid API key." + /info: + get: + tags: + - General + description: General information about the notary server + parameters: + - in: header + name: Authorization + description: Whitelisted API key if auth module is turned on + schema: + type: string + required: false + responses: + "200": + description: Info response from server + content: + application/json: + schema: + $ref: "#/components/schemas/InfoResponse" + "401": + description: API key is invalid + content: + text/plain: + schema: + type: string + example: "Unauthorized request from prover: Invalid API key." /session: post: tags: @@ -23,6 +77,12 @@ paths: enum: - "application/json" required: true + - in: header + name: Authorization + description: Whitelisted API key if auth module is turned on + schema: + type: string + required: false requestBody: description: Notarization session request to server required: true @@ -44,6 +104,13 @@ paths: schema: type: string example: "Invalid request from prover: Failed to deserialize the JSON body into the target type" + "401": + description: API key is invalid + content: + text/plain: + schema: + type: string + example: "Unauthorized request from prover: Invalid API key." "500": description: There was some internal error when processing content: @@ -118,6 +185,27 @@ components: type: object properties: sessionId: + description: Unique ID returned from server upon calling POST /session type: string required: - "sessionId" + InfoResponse: + type: object + properties: + version: + description: Current version of notary server + type: string + publicKey: + description: Public key of notary server for its notarization transcript signature + type: string + gitCommitHash: + description: The git commit hash of source code that this notary server is running + type: string + gitCommitTimestamp: + description: The git commit timestamp of source code that this notary server is running + type: string + required: + - "version" + - "publicKey" + - "gitCommitHash" + - "gitCommitTimestamp" diff --git a/notary-server/src/domain.rs b/notary-server/src/domain.rs index c9b35b9032..5485be620c 100644 --- a/notary-server/src/domain.rs +++ b/notary-server/src/domain.rs @@ -12,4 +12,8 @@ pub struct InfoResponse { pub version: String, /// Public key of the notary signing key pub public_key: String, + /// Current git commit hash of notary-server + pub git_commit_hash: String, + /// Current git commit timestamp of notary-server + pub git_commit_timestamp: String, } diff --git a/notary-server/src/server.rs b/notary-server/src/server.rs index 8eb5f7e92b..4b3b5ec8da 100644 --- a/notary-server/src/server.rs +++ b/notary-server/src/server.rs @@ -108,6 +108,9 @@ pub async fn run_server(config: &NotaryServerProperties) -> Result<(), NotarySer let public_key = std::fs::read_to_string(&config.notary_signature.public_key_pem_path) .map_err(|err| eyre!("Failed to load notary public signing key for notarization: {err}"))?; let version = env!("CARGO_PKG_VERSION").to_string(); + let git_commit_hash = env!("GIT_COMMIT_HASH").to_string(); + let git_commit_timestamp = env!("GIT_COMMIT_TIMESTAMP").to_string(); + let router = Router::new() .route( "/healthcheck", @@ -121,6 +124,8 @@ pub async fn run_server(config: &NotaryServerProperties) -> Result<(), NotarySer Json(InfoResponse { version, public_key, + git_commit_hash, + git_commit_timestamp, }), ) .into_response()