diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7fe16ac --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +# exclude everything +* + +# include source files and configurations +!/src +!/config +!/fixture +!Cargo.lock +!Cargo.toml + +# include test files so that we can run test if we want during the build step in Dockerfile +!/tests diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..1810f17 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,51 @@ +name: cd + +on: + push: + tags: + - "[v]?[0-9]+.[0-9]+.[0-9]+*" + +env: + CONTAINER_REGISTRY: ghcr.io + CONTAINER_IMAGE_NAME: ${{ github.repository }} + +jobs: + build_and_publish_image: + name: Build and publish image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Wait for test workflow to succeed + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.ref }} + check-name: 'Build and test' + repo-token: ${{ secrets.GITHUB_TOKEN }} + # How frequent (in seconds) this job will call GitHub API to check the status of the job specified at 'check-name' + wait-interval: 60 + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.CONTAINER_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker image + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.CONTAINER_REGISTRY }}/${{ env.CONTAINER_IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/rust.yml b/.github/workflows/ci.yml similarity index 64% rename from .github/workflows/rust.yml rename to .github/workflows/ci.yml index 645fa6b..fc48487 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,14 @@ -name: Rust +name: ci on: push: - branches: [main] + branches: + - main + tags: + - "[v]?[0-9]+.[0-9]+.[0-9]+*" pull_request: - branches: [main] + branches: + - main env: CARGO_TERM_COLOR: always @@ -12,18 +16,20 @@ env: jobs: build_and_test: + name: Build and test if: ( ! github.event.pull_request.draft ) runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 - - name: Stable + - name: Install stable rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable - - uses: Swatinem/rust-cache@v2.5.0 + - name: Use caching + uses: Swatinem/rust-cache@v2.5.0 - name: "Build" run: cargo build @@ -35,9 +41,10 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 - - name: Nightly with rustfmt + - name: Install nightly rust toolchain with rustfmt uses: dtolnay/rust-toolchain@stable with: toolchain: nightly @@ -50,9 +57,10 @@ jobs: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 - - name: Stable + - name: Install stable rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable diff --git a/.gitignore b/.gitignore index 77a490c..4a12185 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ Cargo.lock # vscode project specific settings .vscode/ + +# logs +*.log diff --git a/Cargo.toml b/Cargo.toml index 038077f..4ca08e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] async-trait = "0.1.67" -async-tungstenite = { version = "0.22.2", features = ["tokio-runtime", "tokio-native-tls"] } +async-tungstenite = { version = "0.22.2", features = ["tokio-native-tls"] } axum = { version = "0.6.18", features = ["ws"]} axum-core = "0.3.4" axum-macros = "0.3.8" @@ -27,8 +27,8 @@ serde_yaml = "0.9.21" sha1 = "0.10" structopt = "0.3.26" thiserror = "1" -tlsn-notary = { git = "https://github.com/tlsnotary/tlsn" } -tlsn-tls-core = { git = "https://github.com/tlsnotary/tlsn" } +tlsn-notary = { git = "https://github.com/tlsnotary/tlsn", rev = "aaea854" } +tlsn-tls-core = { git = "https://github.com/tlsnotary/tlsn", rev = "aaea854" } tokio = { version = "1", features = ["full"] } tokio-rustls = { version = "0.24.1" } tokio-util = { version = "0.7", features = ["compat"] } @@ -40,7 +40,8 @@ uuid = { version = "1.4.1", features = ["v4", "fast-rng"] } ws_stream_tungstenite = { version = "0.10.0", features = ["tokio_io"] } [dev-dependencies] -hyper-tls = "0.5.0" -tls-server-fixture = { git = "https://github.com/tlsnotary/tlsn" } -tlsn-prover = { git = "https://github.com/tlsnotary/tlsn" } -tokio-native-tls = "0.3.1" +# specify vendored feature to use statically linked copy of OpenSSL +hyper-tls = { version = "0.5.0", features = ["vendored"] } +tls-server-fixture = { git = "https://github.com/tlsnotary/tlsn", rev = "aaea854" } +tlsn-prover = { git = "https://github.com/tlsnotary/tlsn", rev = "aaea854" } +tokio-native-tls = { version = "0.3.1", features = ["vendored"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..19020b6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Using rust:bookworm so that the builder image has OpenSSL 3.0 which is required by async-tungstenite, because +# +# (1) async-tungstenite dynamically links to the OS' OpenSSL by using openssl-sys crate (https://docs.rs/openssl/0.10.56/openssl/#automatic) +# +# (2) async-tungstenite does not utilise the "vendored" feature for its dependency crates, i.e. +# tokio-native-tls, tungstenite and native-tls. The "vendored" feature would have statically linked +# to a OpenSSL copy instead of dynamically link to the OS' OpenSSL (https://docs.rs/openssl/0.10.56/openssl/#vendored) +# — reported an issue here (https://github.com/sdroege/async-tungstenite/issues/119) +# +# (3) We want to use ubuntu:latest (22.04) as the runner image, which (only) has OpenSSL 3.0, because +# OpenSSL 1.1.1 is reaching EOL in Sept 2023 (https://www.openssl.org/blog/blog/2023/03/28/1.1.1-EOL/) +# +# (4) Therefore, we need the builder image to have the same OpenSSL version, else the built binary will +# try to dynamically link to a different (non-existing) version in the runner image +# +# (5) rust:latest is still using bullseye somehow which only has OpenSSL 1.1.1 +FROM rust:bookworm AS builder +WORKDIR /usr/src/notary-server +COPY . . +RUN cargo install --path . + +FROM ubuntu:latest +WORKDIR /root/.notary-server +# Install pkg-config and libssl-dev for async-tungstenite to use (as explained above) +RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends \ + pkg-config \ + libssl-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +# Copy default fixture folder for default usage +COPY --from=builder /usr/src/notary-server/fixture ./fixture +# Copy default config folder for default usage +COPY --from=builder /usr/src/notary-server/config ./config +COPY --from=builder /usr/local/cargo/bin/notary-server /usr/local/bin/notary-server +# Label to link this image with the repository in Github Container Registry (https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package#connecting-a-repository-to-a-container-image-using-the-command-line) +LABEL org.opencontainers.image.source=https://github.com/tlsnotary/notary-server +CMD [ "notary-server" ] diff --git a/README.md b/README.md index cc65942..26758de 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,59 @@ This project is currently under active development and should not be used in pro --- ## Running the server -1. Configure the server setting in this [file](./config.yaml) — refer [here](./src/config.rs) for more information on the definition of the setting parameters. -2. Start the server by running following in a terminal at the top level of this project. +### Using Cargo +1. Configure the server setting in this config [file](./config/config.yaml) — refer [here](./src/config.rs) for more information on the definition of the setting parameters. +2. Start the server by running the following in a terminal at the root of this repository. ```bash -cargo run +cargo run --release ``` 3. To use a config file from a different location, run the following command to override the default config file location. ```bash -cargo run -- --config-file +cargo run --release -- --config-file ``` +### Using Docker +There are two ways to obtain the notary server's Docker image: +- [GitHub](#obtaining-the-image-via-github) +- [Building from source](#building-from-source) + +#### GitHub +1. Obtain the latest image with: +```bash +docker pull ghcr.io/tlsnotary/notary-server:latest +``` +2. Run the docker container with: +```bash +docker run --init -p 127.0.0.1:7047:7047 ghcr.io/tlsnotary/notary-server:latest +``` +3. If you want to change the default configuration, create a `config` folder locally, that contains a `config.yaml`, whose content follows the format of the default config file [here](./config/config.yaml). +4. Instead of step 2, run the docker container with the following (remember to change the port mapping if you have changed that in the config): +```bash +docker run --init -p 127.0.0.1:7047:7047 -v :/root/.notary-server/config ghcr.io/tlsnotary/notary-server:latest +``` + +#### Building from source +1. Configure the server setting in this config [file](./config/config.yaml). +2. Build the docker image by running the following in a terminal at the root of this repository. +```bash +docker build . -t notary-server:local +``` +3. Run the docker container and specify the port specified in the config file, e.g. for the default port 7047 +```bash +docker run --init -p 127.0.0.1:7047:7047 notary-server:local +``` + +### Using different key/cert for TLS or/and notarization with Docker +1. Instead of changing the key/cert file path(s) in the config file, create a folder containing your key/cert by following the folder structure [here](./fixture/). +2. When launching the docker container, mount your folder onto the docker container at the relevant path prefixed by `/root/.notary-server`. +- Example 1: Using different key/cert for both TLS and notarization: +```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`): +```bash +docker run --init -p 127.0.0.1:7047:7047 -v :/root/.notary-server/fixture/notary notary-server:local +``` --- ## API All APIs are TLS-protected, hence please use `https://` or `wss://`. diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 1608b0f..0000000 --- a/config.yaml +++ /dev/null @@ -1,17 +0,0 @@ -server: - name: "notary-server" - domain: "127.0.0.1" - port: 7047 - -notarization: - max-transcript-size: 16384 - -tls-signature: - private-key-pem-path: "./src/fixture/tls/notary.key" - certificate-pem-path: "./src/fixture/tls/notary.crt" - -notary-signature: - private-key-pem-path: "./src/fixture/notary/notary.key" - -tracing: - default-level: DEBUG diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..4fc54a8 --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,17 @@ +server: + name: "notary-server" + host: "0.0.0.0" + port: 7047 + +notarization: + max-transcript-size: 16384 + +tls-signature: + private-key-pem-path: "./fixture/tls/notary.key" + certificate-pem-path: "./fixture/tls/notary.crt" + +notary-signature: + private-key-pem-path: "./fixture/notary/notary.key" + +tracing: + default-level: DEBUG diff --git a/src/fixture/notary/notary.key b/fixture/notary/notary.key similarity index 100% rename from src/fixture/notary/notary.key rename to fixture/notary/notary.key diff --git a/src/fixture/tls/notary.crt b/fixture/tls/notary.crt similarity index 100% rename from src/fixture/tls/notary.crt rename to fixture/tls/notary.crt diff --git a/src/fixture/tls/notary.csr b/fixture/tls/notary.csr similarity index 100% rename from src/fixture/tls/notary.csr rename to fixture/tls/notary.csr diff --git a/src/fixture/tls/notary.ext b/fixture/tls/notary.ext similarity index 100% rename from src/fixture/tls/notary.ext rename to fixture/tls/notary.ext diff --git a/src/fixture/tls/notary.key b/fixture/tls/notary.key similarity index 100% rename from src/fixture/tls/notary.key rename to fixture/tls/notary.key diff --git a/src/fixture/tls/rootCA.crt b/fixture/tls/rootCA.crt similarity index 100% rename from src/fixture/tls/rootCA.crt rename to fixture/tls/rootCA.crt diff --git a/src/fixture/tls/rootCA.key b/fixture/tls/rootCA.key similarity index 100% rename from src/fixture/tls/rootCA.key rename to fixture/tls/rootCA.key diff --git a/src/fixture/tls/rootCA.srl b/fixture/tls/rootCA.srl similarity index 100% rename from src/fixture/tls/rootCA.srl rename to fixture/tls/rootCA.srl diff --git a/src/config.rs b/src/config.rs index fb0d36b..77b6bfe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ pub struct NotaryServerProperties { pub notarization: NotarizationProperties, /// File path of private key and certificate (in PEM format) used for establishing TLS with prover pub tls_signature: TLSSignatureProperties, - /// File path of private key (in PEM format) used to sign the notarisation + /// File path of private key (in PEM format) used to sign the notarization pub notary_signature: NotarySignatureProperties, /// Setting for logging/tracing pub tracing: TracingProperties, @@ -27,7 +27,7 @@ pub struct NotarizationProperties { pub struct ServerProperties { /// Used for testing purpose pub name: String, - pub domain: String, + pub host: String, pub port: u16, } diff --git a/src/domain/cli.rs b/src/domain/cli.rs index ceb7431..83a03e9 100644 --- a/src/domain/cli.rs +++ b/src/domain/cli.rs @@ -5,6 +5,6 @@ use structopt::StructOpt; #[structopt(name = "Notary Server")] pub struct CliFields { /// Configuration file location - #[structopt(long, default_value = "./config.yaml")] + #[structopt(long, default_value = "./config/config.yaml")] pub config_file: String, } diff --git a/src/server.rs b/src/server.rs index c167555..e8dacce 100644 --- a/src/server.rs +++ b/src/server.rs @@ -52,7 +52,7 @@ pub async fn run_server(config: &NotaryServerProperties) -> Result<(), NotarySer let tls_config = Arc::new(server_config); let notary_address = SocketAddr::new( - IpAddr::V4(config.server.domain.parse().map_err(|err| { + IpAddr::V4(config.server.host.parse().map_err(|err| { eyre!("Failed to parse notary host address from server config: {err}") })?), config.server.port, @@ -176,8 +176,8 @@ mod test { #[tokio::test] async fn test_load_notary_key_and_cert() { let config = TLSSignatureProperties { - private_key_pem_path: "./src/fixture/tls/notary.key".to_string(), - certificate_pem_path: "./src/fixture/tls/notary.crt".to_string(), + private_key_pem_path: "./fixture/tls/notary.key".to_string(), + certificate_pem_path: "./fixture/tls/notary.crt".to_string(), }; let result: Result<(PrivateKey, Vec)> = load_tls_key_and_cert(&config).await; assert!(result.is_ok(), "Could not load tls private key and cert"); @@ -186,7 +186,7 @@ mod test { #[tokio::test] async fn test_load_notary_signing_key() { let config = NotarySignatureProperties { - private_key_pem_path: "./src/fixture/notary/notary.key".to_string(), + private_key_pem_path: "./fixture/notary/notary.key".to_string(), }; let result: Result = load_notary_signing_key(&config).await; assert!(result.is_ok(), "Could not load notary private key"); diff --git a/src/util.rs b/src/util.rs index c96c27f..55bf3d1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -17,7 +17,7 @@ mod test { #[test] fn test_parse_config_file() { - let location = "./config.yaml"; + let location = "./config/config.yaml"; let config: Result = parse_config_file(location); assert!( config.is_ok(), diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 30e0278..9ebbe9c 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -27,25 +27,25 @@ use notary_server::{ ServerProperties, TLSSignatureProperties, TracingProperties, }; -const NOTARY_CA_CERT_PATH: &str = "./src/fixture/tls/rootCA.crt"; -const NOTARY_CA_CERT_BYTES: &[u8] = include_bytes!("../src/fixture/tls/rootCA.crt"); +const NOTARY_CA_CERT_PATH: &str = "./fixture/tls/rootCA.crt"; +const NOTARY_CA_CERT_BYTES: &[u8] = include_bytes!("../fixture/tls/rootCA.crt"); async fn setup_config_and_server(sleep_ms: u64, port: u16) -> NotaryServerProperties { let notary_config = NotaryServerProperties { server: ServerProperties { name: "tlsnotaryserver.io".to_string(), - domain: "127.0.0.1".to_string(), + host: "127.0.0.1".to_string(), port, }, notarization: NotarizationProperties { max_transcript_size: 1 << 14, }, tls_signature: TLSSignatureProperties { - private_key_pem_path: "./src/fixture/tls/notary.key".to_string(), - certificate_pem_path: "./src/fixture/tls/notary.crt".to_string(), + private_key_pem_path: "./fixture/tls/notary.key".to_string(), + certificate_pem_path: "./fixture/tls/notary.crt".to_string(), }, notary_signature: NotarySignatureProperties { - private_key_pem_path: "./src/fixture/notary/notary.key".to_string(), + private_key_pem_path: "./fixture/notary/notary.key".to_string(), }, tracing: TracingProperties { default_level: "DEBUG".to_string(), @@ -90,10 +90,10 @@ async fn test_tcp_prover() { .with_no_client_auth(); let notary_connector = TlsConnector::from(Arc::new(client_notary_config)); - let notary_domain = notary_config.server.domain.clone(); + let notary_host = notary_config.server.host.clone(); let notary_port = notary_config.server.port; let notary_socket = tokio::net::TcpStream::connect(SocketAddr::new( - IpAddr::V4(notary_domain.parse().unwrap()), + IpAddr::V4(notary_host.parse().unwrap()), notary_port, )) .await @@ -122,9 +122,9 @@ async fn test_tcp_prover() { }) .unwrap(); let request = Request::builder() - .uri(format!("https://{notary_domain}:{notary_port}/session")) + .uri(format!("https://{notary_host}:{notary_port}/session")) .method("POST") - .header("Host", notary_domain.clone()) + .header("Host", notary_host.clone()) // Need to specify application/json for axum to parse it as json .header("Content-Type", "application/json") .body(Body::from(payload)) @@ -150,9 +150,9 @@ async fn test_tcp_prover() { // Send notarization request via HTTP, where the underlying TCP connection will be extracted later let request = Request::builder() - .uri(format!("https://{notary_domain}:{notary_port}/notarize")) + .uri(format!("https://{notary_host}:{notary_port}/notarize")) .method("GET") - .header("Host", notary_domain) + .header("Host", notary_host) .header("Connection", "Upgrade") // Need to specify this upgrade header for server to extract tcp connection later .header("Upgrade", "TCP") @@ -259,7 +259,7 @@ async fn test_tcp_prover() { async fn test_websocket_prover() { // Notary server configuration setup let notary_config = setup_config_and_server(100, 7049).await; - let notary_domain = notary_config.server.domain.clone(); + let notary_host = notary_config.server.host.clone(); let notary_port = notary_config.server.port; // Connect to the notary server via TLS-WebSocket @@ -291,9 +291,9 @@ async fn test_websocket_prover() { .unwrap(); let request = Request::builder() - .uri(format!("https://{notary_domain}:{notary_port}/session")) + .uri(format!("https://{notary_host}:{notary_port}/session")) .method("POST") - .header("Host", notary_domain.clone()) + .header("Host", notary_host.clone()) // Need to specify application/json for axum to parse it as json .header("Content-Type", "application/json") .body(Body::from(payload)) @@ -324,8 +324,8 @@ async fn test_websocket_prover() { // client while using its high level request function — there does not seem to have a crate that can let you // make a request without establishing TCP connection where you can claim the TCP connection later after making the request let request = http::Request::builder() - .uri(format!("wss://{notary_domain}:{notary_port}/notarize")) - .header("Host", notary_domain.clone()) + .uri(format!("wss://{notary_host}:{notary_port}/notarize")) + .header("Host", notary_host.clone()) .header("Sec-WebSocket-Key", uuid::Uuid::new_v4().to_string()) .header("Sec-WebSocket-Version", "13") .header("Connection", "Upgrade")