Dockerize server. (#9)

* Dockerize server.

* Modify tag push condition.

* Move build image to cd workflow and change tag syntax.
This commit is contained in:
Christopher Chong
2023-09-05 10:49:09 +08:00
committed by GitHub
parent 9a83d96cf3
commit 46acc04f57
22 changed files with 219 additions and 64 deletions

12
.dockerignore Normal file
View File

@@ -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

51
.github/workflows/cd.yml vendored Normal file
View File

@@ -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 }}

View File

@@ -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

3
.gitignore vendored
View File

@@ -25,3 +25,6 @@ Cargo.lock
# vscode project specific settings
.vscode/
# logs
*.log

View File

@@ -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"] }

37
Dockerfile Normal file
View File

@@ -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" ]

View File

@@ -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 <path-to-new-config-file>
cargo run --release -- --config-file <path-of-new-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 <your config folder path>:/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 <your folder path>:/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 <your folder path>:/root/.notary-server/fixture/notary notary-server:local
```
---
## API
All APIs are TLS-protected, hence please use `https://` or `wss://`.

View File

@@ -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

17
config/config.yaml Normal file
View File

@@ -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

View File

@@ -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,
}

View File

@@ -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,
}

View File

@@ -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<Certificate>)> = 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<SigningKey> = load_notary_signing_key(&config).await;
assert!(result.is_ok(), "Could not load notary private key");

View File

@@ -17,7 +17,7 @@ mod test {
#[test]
fn test_parse_config_file() {
let location = "./config.yaml";
let location = "./config/config.yaml";
let config: Result<NotaryServerProperties> = parse_config_file(location);
assert!(
config.is_ok(),

View File

@@ -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")