Rust Rest API Stack with User Management, Kafka Message Publishing, S3 uploads/downloads, and Prometheus for Monitoring

A secure-by-default Rest API using hyper, tokio, bb8, kafka-threadpool, postgres, and prometheus for monitoring.

Features

  1. User management and authentication stored in postgres
  2. Async s3 uploading and downloading (to/from local files or to/from memory)
  3. Decoupled, async kafka threadpool that uses environment variables to connect to a kafka cluster with client mtls for authentication and encryption in transit
  4. Async publishing for all successful user events to a kafka topic (topic default: user.events) and partition key (key default: user-{user.id})
  5. Async kafka messaging for one-off messages using custom kafka topic(s), partition key(s) and custom header(s).

Overview

User

Auth

Database

TLS Encryption

Ingress

Component | Status ---------------- | ------ Rest API Server | Listening for encrypted client connections on tcp port 3000 Postgres | Listening for encrypted client connections on tcp port 5432 (tls Certificate Authority required) pgAdmin | Listening for encrypted HTTP client connections on tcp port 5433

Getting Started

Clone the repo

bash git clone https://github.com/jay-johnson/restapi cd restapi

Generate TLS Assets and a Private Certificate Authority (CA) using CFSSL

Generate new tls assets under the ./tls directory with these commands:

bash cd tls ./create-tls-assets.sh cd ..

Please refer to the Generating TLS Assets with CFSSL for more information.

Generate JWT Private and Public Signing Keys

Generate new signing JWT keys under the ./jwt directory with these commands:

bash cd jwt ./recreate-jwt.sh cd ..

Please refer to the How to build JWT private and public keys for the jsonwebtokens crate doc for more information.

Deploy Postgres and pgAdmin using Podman

Please refer to the Build and Deploy a Secured Postgres backend doc for more information.

Build API Server

bash cargo build --example server

Run API Server

bash export RUST_BACKTRACE=1 && export RUST_LOG=info,kafka_threadpool=info && ./target/debug/examples/server

Environment Variables

Rest API

Environment Variable | Default --------------------- | ------- SERVERNAMEAPI | api SERVERNAMELABEL | rust-restapi APIENDPOINT | 0.0.0.0:3000 APITLSDIR | ./tls/api APITLSCA | ./tls/ca/ca.pem APITLSCERT | ./tls/api/server.pem APITLS_KEY | ./tls/api/server-key.pem

User Email Verification

Environment Variable | Default -------------------------------------- | ------- USEREMAILVERIFICATIONREQUIRED | "0" USEREMAILVERIFICATIONENABLED | "1" USEREMAILVERIFICATIONEXPIN_SECONDS | "2592000"

User One-Time-Use Token Expiration for Password Recovery

Environment Variable | Default ----------------------- | ------- USEROTPEXPINSECONDS | "2592000"

Postgres Database

Environment Variable | Default --------------------- | ------- POSTGRESUSERNAME | datawriter POSTGRESPASSWORD | "123321" POSTGRESENDPOINT | 0.0.0.0:5432 POSTGRESTLSDIR | ./tls/postgres POSTGRESTLSCA | ./tls/ca/ca.pem POSTGRESTLSCERT | ./tls/postgres/client.pem POSTGRESTLSKEY | ./tls/postgres/client-key.pem POSTGRESDBCONNTYPE | postgresql

Kafka Cluster

Please refer to the kafka_threadpool docs for more information.

Environment Variable | Purpose / Value -------------------------------- | --------------- KAFKAPUBLISHEVENTS | if set to true or 1 publish all user events to kafka KAFKAENABLED | toggle the kafkathreadpool on with: true or 1 anything else disables the threadpool KAFKALOGLABEL | tracking label that shows up in all crate logs KAFKABROKERS | comma-delimited list of brokers (host1:port,host2:port,host3:port) KAFKATOPICS | comma-delimited list of supported topics KAFKAPUBLISHRETRYINTERVALSEC | number of seconds to sleep before each publish retry KAFKAPUBLISHIDLEINTERVALSEC | number of seconds to sleep if there are no message to process KAFKANUMTHREADS | number of threads for the threadpool KAFKATLSCLIENTKEY | optional - path to the kafka mTLS key (./tls/kafka-cluster-0/client-key.pem) KAFKATLSCLIENTCERT | optional - path to the kafka mTLS certificate (./tls/kafka-cluster-0/client.pem) KAFKATLSCLIENTCA | optional - path to the kafka mTLS certificate authority (CA) (./tls/ca/ca.pem) KAFKAMETADATACOUNTMSG_OFFSETS | optional - set to anything but true to bypass counting the offsets

Sample kafka.env file

```bash

enable the cluster

export KAFKAENABLED=1 export KAFKALOGLABEL="ktp" export KAFKABROKERS="host1:port,host2:port,host3:port" export KAFKATOPICS="testing" export KAFKAPUBLISHRETRYINTERVALSEC="1.0" export KAFKANUMTHREADS="5" export KAFKATLSCLIENTCA="./tls/ca/ca.pem" export KAFKATLSCLIENTCERT="./tls/kafka-cluster-0/client.pem" export KAFKATLSCLIENTKEY="./tls/kafka-cluster-0/client-key.pem"

the KafkaPublisher can count the offsets for each topic with "true" or "1"

export KAFKAMETADATACOUNTMSGOFFSETS="true" ```

S3

Environment Variable | Default -------------------- | ------- S3DATABUCKET | YOURBUCKET S3DATAPREFIX | /rust-restapi/tests S3STORAGECLASS | STANDARD S3DATAUPLOADTO_S3 | "0"

JWT

Environment Variable | Default ------------------------------------ | ------- TOKENEXPIRATIONSECONDSINTOFUTURE | "2592000" TOKENORG | example.org TOKENHEADER | Bearer TOKENALGOPRIVATEKEY | ./jwt/private-key-pkcs8.pem TOKENALGOPUBLICKEY | ./jwt/public-key.pem SERVERPKIDIRJWT | ./jwt SERVERPASSWORD_SALT | 78197b60-c950-4339-a52c-053165a04764

Rust

Environment Variable | Default -------------------- | ------- RUSTBACKTRACE | "1" RUSTLOG | info

Debug

Environment Variable | Default -------------------- | ------- DEBUG | "1"

Docker Builds

Build Base Image

This will build an initial base image using podman. Note: this base image will not work on a different cpu chipset because the openssl libraries are compiled within the image for this base image.

bash ./build-base.sh

Build Derived Image

By reusing the base image, this derived image only needs to recompile the server. With minimal code changes, this is a much faster build than the base image build.

bash ./build-derived.sh

Kubernetes

Start Kafka

If you do not have a running Kafka cluster, you can deploy your own with:

https://github.com/jay-johnson/rust-with-strimzi-kafka-and-tls

Helm Chart

Deploy TLS Assets into Kubernetes

This command will deploy all jwt keys, tls assets and credentials into the dev namespace:

bash ./deploy-kubernetes-assets.sh -e dev

Deploy the Rust Rest API into Kubernetes

Please refer to the Deploying the Rust Rest API helm chart into kubernetes guide for deploying the example helm chart into a kubernetes cluster.

By default this uses the jayjohnson/rust-restapi container image

bash helm upgrade --install -n dev dev-api ./charts/rust-restapi -f ./charts/rust-restapi/values.yaml

Monitoring

Prometheus

This section assumes you have a working prometheus instance already running inside kubernetes. Below is the Prometheus scrape_config to monitor the rest api deployment replica(s) within kubernetes. Note this config also assumes the api chart is running in the dev namespace:

yaml scrape_configs: - job_name: rust-restapi scrape_interval: 10s scrape_timeout: 5s metrics_path: /metrics scheme: https tls_config: insecure_skip_verify: true static_configs: - targets: - dev-api.dev.svc.cluster.local:3000

Supported APIs

Here are the supported json contracts for each Request and Response based off the url. Each client request is handled by the ./src/handle_requests.rs module and returned as a response back to the client (serialization using serde_json)

User APIs

Create User

Create a single users record for the new user

Update User

Update supported users fields (including change user email and password)

Get User

Get a single user by users.id - by default, a user can only get their own account details

Delete User

Delete a single users record (note: this does not delete the db record, just sets the users.state to inactive 1)

Search Users in the db

Search for matching users records in the db

Create One-Time-Use Password Reset Token (OTP)

Create a one-time-use password reset token that allows a user to change their users.password value by presenting the token

Consume a One-Time-Use Password Reset Token (OTP)

Consume a one-time-use password and change the user's users.password value to the new argon2-hashed password

Verify a User's email

Consume a one-time-use verification token and change the user's users.verified value verified (1)

User S3 APIs

Upload a file asynchronously to AWS S3 and store a tracking record in the db

Upload a local file on disk to AWS S3 asynchronously and store a tracking record in the users_data table. The documentation refers to this as a user data or user data file record.

Update an existing user data file record for a file stored in AWS S3

Update the users_data tracking record for a file that exists in AWS S3

Search for existing user data files from the db

Search for matching records in the users_data db based off the request's values

User Authentication APIs

User Login

Log the user in and get a json web token (jwt) back for authentication on subsequent client requests

Integration Tests

This project focused on integration tests for v1 instead of only rust tests (specifically everything has been tested with curl):

Please refer to the Integration Tests Using curl Guide

Podman Image Push

bash cur_tag=$(cat Cargo.toml | grep version | head -1 | sed -e 's/"//g' | awk '{print $NF}') podman tag IMAGE_ID "docker://docker.io/jayjohnson/rust-restapi:${cur_tag}" podman tag IMAGE_ID "docker://docker.io/jayjohnson/rust-restapi:latest" podman push "docker.io/jayjohnson/rust-restapi:${cur_tag}" podman push "docker.io/jayjohnson/rust-restapi:latest"

Build Docs

bash cargo doc --example server

Start Development Server

bash source ./env/api.env && source ./env/kafka.env && source ./env/postgres.env && cargo build --example server && export RUST_BACKTRACE=1 && export RUST_LOG=info,kafka_threadpool=info,rdkafka=error && ./target/debug/examples/server