From 3939caaeeb0599e7f7eef7a92d83809e3a1d5b0d Mon Sep 17 00:00:00 2001 From: Stefano Fontana Date: Sat, 14 Oct 2023 10:22:30 +0200 Subject: [PATCH] Add mtls authentication example. Update RabbitMQ docker configuration for mtls using rabbitmq_auth_mechanism_ssl plugin and start script for client certificate generation adding username DNS SAN --- docker-compose.yml | 1 + examples/Cargo.toml | 6 ++ examples/src/mtls.rs | 120 ++++++++++++++++++++++++++++++++++++++ rabbitmq_conf/custom.conf | 10 +++- start_rabbitmq.sh | 3 +- 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 examples/src/mtls.rs diff --git a/docker-compose.yml b/docker-compose.yml index 7b23557..6032a47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,7 @@ services: - RABBITMQ_USERNAME=user - RABBITMQ_PASSWORD=bitnami - RABBITMQ_VHOST=/ + - RABBITMQ_PLUGINS=rabbitmq_management,rabbitmq_management_agent,rabbitmq_web_dispatch,rabbitmq_auth_mechanism_ssl volumes: - "./rabbitmq_conf/custom.conf:/bitnami/rabbitmq/conf/custom.conf" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 2dcfd0f..058b17c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -19,6 +19,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } # features for example target [features] example-tls = ["amqprs/tls", "amqprs/traces"] +default = ["amqprs/traces"] [[example]] name = "basic_pub_sub" @@ -37,6 +38,11 @@ name = "tls" path = "src/tls.rs" required-features = ["example-tls"] +[[example]] +name = "mtls" +path = "src/mtls.rs" +required-features = ["example-tls"] + [[example]] name = "field_table" path = "src/field_table.rs" diff --git a/examples/src/mtls.rs b/examples/src/mtls.rs new file mode 100644 index 0000000..090940e --- /dev/null +++ b/examples/src/mtls.rs @@ -0,0 +1,120 @@ +use amqprs::{ + callbacks::{DefaultChannelCallback, DefaultConnectionCallback}, + channel::{ + BasicConsumeArguments, BasicPublishArguments, QueueBindArguments, QueueDeclareArguments, + }, + connection::{Connection, OpenConnectionArguments}, + consumer::DefaultConsumer, + BasicProperties, +}; +use tokio::time; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; +use amqprs::security::SecurityCredentials; + +use amqprs::tls::TlsAdaptor; + +#[tokio::main(flavor = "multi_thread", worker_threads = 2)] +async fn main() { + // construct a subscriber that prints formatted traces to stdout + // global subscriber with log level according to RUST_LOG + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_default_env()) + .try_init() + .ok(); + + //////////////////////////////////////////////////////////////// + // TLS specific configuration + let current_dir = std::env::current_dir().unwrap(); + let current_dir = current_dir.join("rabbitmq_conf/client/"); + + let root_ca_cert = current_dir.join("ca_certificate.pem"); + let client_cert = current_dir.join("client_AMQPRS_TEST_certificate.pem"); + let client_private_key = current_dir.join("client_AMQPRS_TEST_key.pem"); + // domain should match the certificate/key files + let domain = "AMQPRS_TEST"; + + let args = OpenConnectionArguments::new("localhost", 5671, "", "") + .tls_adaptor( + TlsAdaptor::with_client_auth( + Some(root_ca_cert.as_path()), + client_cert.as_path(), + client_private_key.as_path(), + domain.to_owned(), + ) + .unwrap(), + ) + .credentials(SecurityCredentials::new_external()) + .finish(); + + //////////////////////////////////////////////////////////////// + // everything below should be the same as regular connection + // open a connection to RabbitMQ server + let connection = Connection::open(&args).await.unwrap(); + connection + .register_callback(DefaultConnectionCallback) + .await + .unwrap(); + + // open a channel on the connection + let channel = connection.open_channel(None).await.unwrap(); + channel + .register_callback(DefaultChannelCallback) + .await + .unwrap(); + + // declare a queue + let (queue_name, _, _) = channel + .queue_declare(QueueDeclareArguments::default()) + .await + .unwrap() + .unwrap(); + + // bind the queue to exchange + let rounting_key = "amqprs.example"; + let exchange_name = "amq.topic"; + channel + .queue_bind(QueueBindArguments::new( + &queue_name, + exchange_name, + rounting_key, + )) + .await + .unwrap(); + + ////////////////////////////////////////////////////////////////////////////// + // start consumer with given name + let args = BasicConsumeArguments::new(&queue_name, "example_basic_pub_sub"); + + channel + .basic_consume(DefaultConsumer::new(args.no_ack), args) + .await + .unwrap(); + + ////////////////////////////////////////////////////////////////////////////// + // publish message + let content = String::from( + r#" + { + "publisher": "example" + "data": "Hello, amqprs!" + } + "#, + ) + .into_bytes(); + + // create arguments for basic_publish + let args = BasicPublishArguments::new(exchange_name, rounting_key); + + channel + .basic_publish(BasicProperties::default(), content, args) + .await + .unwrap(); + + // keep the `channel` and `connection` object from dropping before pub/sub is done. + // channel/connection will be closed when drop. + time::sleep(time::Duration::from_secs(1)).await; + // explicitly close + channel.close().await.unwrap(); + connection.close().await.unwrap(); +} diff --git a/rabbitmq_conf/custom.conf b/rabbitmq_conf/custom.conf index 66c1663..1ad6662 100644 --- a/rabbitmq_conf/custom.conf +++ b/rabbitmq_conf/custom.conf @@ -1,9 +1,12 @@ auth_mechanisms.1 = PLAIN auth_mechanisms.2 = AMQPLAIN -auth_mechanisms.3 = RABBIT-CR-DEMO +auth_mechanisms.3 = EXTERNAL +auth_mechanisms.4 = RABBIT-CR-DEMO -log.default.level = debug +log.console = true +log.console.level = debug +log.default.level = debug listeners.ssl.default = 5671 ssl_options.cacertfile = /bitnami/tls-test/ca_certificate.pem @@ -11,6 +14,9 @@ ssl_options.certfile = /bitnami/tls-test/server_AMQPRS_TEST_certificate.pem ssl_options.keyfile = /bitnami/tls-test/server_AMQPRS_TEST_key.pem ssl_options.verify = verify_peer ssl_options.fail_if_no_peer_cert = true +ssl_cert_login_from = subject_alternative_name +ssl_cert_login_san_type = dns +ssl_cert_login_san_index = 1 # private key password # ssl_options.password = bunnies diff --git a/start_rabbitmq.sh b/start_rabbitmq.sh index 41e0dcb..b66a712 100755 --- a/start_rabbitmq.sh +++ b/start_rabbitmq.sh @@ -1,6 +1,7 @@ #!/bin/bash COMMON_NAME=AMQPRS_TEST +USERNAME=user # Create directories for rabbitmq server and client and alter permissions #------------------------ @@ -15,7 +16,7 @@ sudo chmod 444 rabbitmq_conf/client/* #------------------------ git clone https://github.com/rabbitmq/tls-gen tls-gen cd tls-gen/basic -make CN=$COMMON_NAME +make CN=$COMMON_NAME CLIENT_ALT_NAME=$USERNAME make verify CN=$COMMON_NAME make info CN=$COMMON_NAME ls -lha ./result