From 61b675c00b3c421cbbd702fe756ca8bba17e6abb Mon Sep 17 00:00:00 2001 From: 82marbag <69267416+82marbag@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:21:55 +0100 Subject: [PATCH] TLS tests in CI (#2886) ## Motivation and Context This PR adds a CI workflow to verify the TLS configuration of the smithy-rs client. ## Checklist - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --------- Signed-off-by: Daniele Ahmed Co-authored-by: Daniele Ahmed --- .github/workflows/ci-tls.yml | 81 +++++++++ ci.mk | 4 + tools/ci-resources/tls-stub/Cargo.toml | 23 +++ tools/ci-resources/tls-stub/README.md | 8 + tools/ci-resources/tls-stub/src/main.rs | 160 ++++++++++++++++++ .../ci-scripts/configure-tls/configure-badssl | 25 +++ .../ci-scripts/configure-tls/configure-badtls | 17 ++ .../ci-scripts/configure-tls/configure-trytls | 14 ++ tools/ci-scripts/configure-tls/update-certs | 10 ++ 9 files changed, 342 insertions(+) create mode 100644 .github/workflows/ci-tls.yml create mode 100644 tools/ci-resources/tls-stub/Cargo.toml create mode 100644 tools/ci-resources/tls-stub/README.md create mode 100644 tools/ci-resources/tls-stub/src/main.rs create mode 100755 tools/ci-scripts/configure-tls/configure-badssl create mode 100755 tools/ci-scripts/configure-tls/configure-badtls create mode 100755 tools/ci-scripts/configure-tls/configure-trytls create mode 100755 tools/ci-scripts/configure-tls/update-certs diff --git a/.github/workflows/ci-tls.yml b/.github/workflows/ci-tls.yml new file mode 100644 index 000000000..324b23383 --- /dev/null +++ b/.github/workflows/ci-tls.yml @@ -0,0 +1,81 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# This workflow tests the TLS configuration of the smithy-rs client +# To run on an Ubuntu machine, run each step in this order. +# Each script can be run on your Ubuntu host. +# You will have to install Docker and rustc/cargo manually. + +env: + rust_version: 1.68.2 + +name: Verify client TLS configuration +on: + pull_request: + push: + branches: [main] + +jobs: + verify-tls-config: + name: Verify TLS configuration + runs-on: ubuntu-latest + steps: + - name: Install packages + shell: bash + run: | + sudo apt-get update + sudo apt-get -y install gcc make python3-pip nginx git ruby openjdk-17-jre pkg-config libssl-dev faketime + pip3 install certbuilder crlbuilder + - name: Stop nginx + run: sudo systemctl stop nginx + - name: Checkout smithy-rs + uses: actions/checkout@v3 + with: + path: ./smithy-rs + - name: Checkout trytls + uses: actions/checkout@v3 + with: + repository: ouspg/trytls + path: ./trytls + - name: Checkout badtls + uses: actions/checkout@v3 + with: + repository: wbond/badtls.io + path: ./badtls.io + - name: Checkout badssl + uses: actions/checkout@v3 + with: + repository: chromium/badssl.com + path: ./badssl.com + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.rust_version }} + - name: Build badssl.com + shell: bash + working-directory: badssl.com + env: + DOCKER_BUILDKIT: 1 + run: ../smithy-rs/tools/ci-scripts/configure-tls/configure-badssl + - name: Build SDK + working-directory: smithy-rs + run: ./gradlew :aws:sdk:assemble -Paws.services=+sts,+sso + - name: Build trytls + shell: bash + working-directory: trytls + run: ../smithy-rs/tools/ci-scripts/configure-tls/configure-trytls + - name: Build badtls.io + working-directory: badtls.io + shell: bash + run: ../smithy-rs/tools/ci-scripts/configure-tls/configure-badtls + - name: Update TLS configuration + shell: bash + run: smithy-rs/tools/ci-scripts/configure-tls/update-certs + - name: Build TLS stub + working-directory: smithy-rs/tools/ci-resources/tls-stub + shell: bash + run: cargo build + - name: Test TLS configuration + working-directory: smithy-rs/tools + shell: bash + run: trytls https target/debug/stub diff --git a/ci.mk b/ci.mk index a7022355e..ddbe1013f 100644 --- a/ci.mk +++ b/ci.mk @@ -131,3 +131,7 @@ check-semver: .PHONY: generate-smithy-rs-release generate-smithy-rs-release: $(CI_ACTION) $@ $(ARGS) + +.PHONY: verify-tls-config +verify-tls-config: + $(CI_ACTION) $@ $(ARGS) diff --git a/tools/ci-resources/tls-stub/Cargo.toml b/tools/ci-resources/tls-stub/Cargo.toml new file mode 100644 index 000000000..aea6e0a76 --- /dev/null +++ b/tools/ci-resources/tls-stub/Cargo.toml @@ -0,0 +1,23 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +[package] +name = "stub" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +aws-config = {path = "../../../aws/sdk/build/aws-sdk/sdk/aws-config", features = ["client-hyper"] } +aws-credential-types = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-credential-types", features = ["hardcoded-credentials"] } +aws-sdk-sts = { path = "../../../aws/sdk/build/aws-sdk/sdk/sts" } +aws-smithy-client = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-smithy-client", features = ["client-hyper", "rustls"] } +exitcode = "1" +hyper-rustls = { version = "0.24", features = ["rustls-native-certs", "http2"] } +rustls = "0.21" +rustls-native-certs = "0.6" +rustls-pemfile = "1" +tokio = { version = "1", features = ["full"] } +x509-parser = "0.15" diff --git a/tools/ci-resources/tls-stub/README.md b/tools/ci-resources/tls-stub/README.md new file mode 100644 index 000000000..7d3f291e7 --- /dev/null +++ b/tools/ci-resources/tls-stub/README.md @@ -0,0 +1,8 @@ +# TLS Stub + +This package is used to verify the client's TLS configuration. + +It is used in a CI test. See `ci-tls.yml`, "Verify client TLS configuration". + +The stub loads a root certificate authority and uses it to connect to a supplied port on localhost. +`trytls` reads the output on the console and uses the exit code of the stub to pass or fail a test case. diff --git a/tools/ci-resources/tls-stub/src/main.rs b/tools/ci-resources/tls-stub/src/main.rs new file mode 100644 index 000000000..8daebc5a7 --- /dev/null +++ b/tools/ci-resources/tls-stub/src/main.rs @@ -0,0 +1,160 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::time::Duration; + +use aws_config::timeout::TimeoutConfig; +use aws_credential_types::Credentials; +use aws_sdk_sts::error::SdkError; + +#[cfg(debug_assertions)] +use x509_parser::prelude::*; + +const OPERATION_TIMEOUT: u64 = 5; + +fn unsupported() { + println!("UNSUPPORTED"); + std::process::exit(exitcode::OK); +} + +fn get_credentials() -> Credentials { + Credentials::from_keys( + "AKIAIOSFODNN7EXAMPLE", + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + None, + ) +} + +#[cfg(debug_assertions)] +fn debug_cert(cert: &[u8]) { + let x509 = X509Certificate::from_der(cert).unwrap(); + let subject = x509.1.subject(); + let serial = x509.1.raw_serial_as_string(); + println!("Adding root CA: {subject} ({serial})"); +} + +fn add_cert_to_store(cert: &[u8], store: &mut rustls::RootCertStore) { + let cert = rustls::Certificate(cert.to_vec()); + #[cfg(debug_assertions)] + debug_cert(&cert.0); + if let Err(e) = store.add(&cert) { + println!("Error adding root certificate: {e}"); + unsupported(); + } +} + +fn load_ca_bundle(filename: &String, roots: &mut rustls::RootCertStore) { + match File::open(filename) { + Ok(f) => { + let mut f = BufReader::new(f); + match rustls_pemfile::certs(&mut f) { + Ok(certs) => { + for cert in certs { + add_cert_to_store(&cert, roots); + } + } + Err(e) => { + println!("Error reading PEM file: {e}"); + unsupported(); + } + } + } + Err(e) => { + println!("Error opening file '{filename}': {e}"); + unsupported(); + } + } +} + +fn load_native_certs(roots: &mut rustls::RootCertStore) { + let certs = rustls_native_certs::load_native_certs(); + if let Err(ref e) = certs { + println!("Error reading native certificates: {e}"); + unsupported(); + } + for cert in certs.unwrap() { + add_cert_to_store(&cert.0, roots); + } + let mut pem_ca_cert = b"\ +-----BEGIN CERTIFICATE----- +-----END CERTIFICATE-----\ +" as &[u8]; + let certs = rustls_pemfile::certs(&mut pem_ca_cert).unwrap(); + for cert in certs { + add_cert_to_store(&cert, roots); + } +} + +async fn create_client( + roots: rustls::RootCertStore, + host: &String, + port: &String, +) -> aws_sdk_sts::Client { + let credentials = get_credentials(); + let tls_config = rustls::client::ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots) + .with_no_client_auth(); + let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(tls_config) + .https_only() + .enable_http1() + .enable_http2() + .build(); + let smithy_connector = aws_smithy_client::hyper_ext::Adapter::builder().build(https_connector); + let sdk_config = aws_config::from_env() + .http_connector(smithy_connector) + .credentials_provider(credentials) + .region("us-nether-1") + .endpoint_url(format!("https://{host}:{port}")) + .timeout_config( + TimeoutConfig::builder() + .operation_timeout(Duration::from_secs(OPERATION_TIMEOUT)) + .build(), + ) + .load() + .await; + aws_sdk_sts::Client::new(&sdk_config) +} + +#[tokio::main] +async fn main() -> Result<(), aws_sdk_sts::Error> { + let argv: Vec = env::args().collect(); + if argv.len() < 3 || argv.len() > 4 { + eprintln!("Syntax: {} [ca-file]", argv[0]); + std::process::exit(exitcode::USAGE); + } + let mut roots = rustls::RootCertStore::empty(); + if argv.len() == 4 { + print!( + "Connecting to https://{}:{} with root CA bundle from {}: ", + &argv[1], &argv[2], &argv[3] + ); + load_ca_bundle(&argv[3], &mut roots); + } else { + print!( + "Connecting to https://{}:{} with native roots: ", + &argv[1], &argv[2] + ); + load_native_certs(&mut roots); + } + let sts_client = create_client(roots, &argv[1], &argv[2]).await; + match sts_client.get_caller_identity().send().await { + Ok(_) => println!("\nACCEPT"), + Err(SdkError::DispatchFailure(e)) => println!("{e:?}\nREJECT"), + Err(SdkError::ServiceError(e)) => println!("{e:?}\nACCEPT"), + Err(e) => { + println!("Unexpected error: {e:#?}"); + std::process::exit(exitcode::SOFTWARE); + } + } + Ok(()) +} diff --git a/tools/ci-scripts/configure-tls/configure-badssl b/tools/ci-scripts/configure-tls/configure-badssl new file mode 100755 index 000000000..f0b04951c --- /dev/null +++ b/tools/ci-scripts/configure-tls/configure-badssl @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +set -euxo pipefail + +perl -p -i -e 's/ruby2\.4/ruby2.6/' Dockerfile +grep -q 'start of badssl\.test hosts' /etc/hosts || make list-hosts | sudo tee -a /etc/hosts +# badssl fails to create dh480.pem on our Ubuntu host. +# Create it manually inside the docker container. +sed -i '/CMD /i \ +RUN echo "-----BEGIN DH PARAMETERS-----" >/var/www/badssl/_site/certs/sets/current/gen/dhparam/dh480.pem \ +RUN echo "MEICPQDZ/YFp3iEs3/k9iRGoC/5/To2+5pUF/C6GkO6VjXHHyRVy68I0rI0q7IAq" >>/var/www/badssl/_site/certs/sets/current/gen/dhparam/dh480.pem \ +RUN echo "VyyGQ7/5Q/Iu0QQnHT4X9uMCAQI=" >>/var/www/badssl/_site/certs/sets/current/gen/dhparam/dh480.pem \ +RUN echo "-----END DH PARAMETERS-----" >>/var/www/badssl/_site/certs/sets/current/gen/dhparam/dh480.pem \ +' Dockerfile +sed -i '/ 480/c \\ttrue' certs/Makefile +# badssl does not create an expired certificate; +# it creates a certificate that expires after 1 day and waits for 1 day to run the "expired certificate" test. +# This command patches this behavior to run the test immediately. +# See: https://github.com/chromium/badssl.com/blob/df8d5a9d062f4b99fc19d8aacdea5333b399d624/certs/Makefile#L177 +sed -i 's%./tool sign $@ $(D) 1 sha256 req_v3_usr $^%faketime -f "-2d" ./tool sign $@ $(D) 1 sha256 req_v3_usr $^%' certs/Makefile +screen -dmS badssl sudo make serve diff --git a/tools/ci-scripts/configure-tls/configure-badtls b/tools/ci-scripts/configure-tls/configure-badtls new file mode 100755 index 000000000..6828fce1a --- /dev/null +++ b/tools/ci-scripts/configure-tls/configure-badtls @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +set -euxo pipefail + +python3 scripts/generate.py badtls.test +sudo mkdir /etc/nginx/tls || true +sudo mkdir /var/www || true +sudo python3 scripts/install.py /etc/nginx/conf.d /etc/nginx/tls /var/www +sudo rm /etc/nginx/sites-enabled/default +echo '#### start of badtls.test hosts ####' | sudo tee -a /etc/hosts +echo '127.0.0.1 domain-match.badtls.test wildcard-match.badtls.test san-match.badtls.test dh1024.badtls.test expired-1963.badtls.test future.badtls.test domain-mismatch.badtls.test san-mismatch.badtls.test bad-key-usage.badtls.test expired.badtls.test wildcard.mismatch.badtls.test rc4.badtls.test weak-sig.badtls.test rc4-md5.badtls.test' | sudo tee -a /etc/hosts +echo '#### end of badtls.test hosts ####' | sudo tee -a /etc/hosts +screen -dmS badtls sudo bash ./scripts/local.sh diff --git a/tools/ci-scripts/configure-tls/configure-trytls b/tools/ci-scripts/configure-tls/configure-trytls new file mode 100755 index 000000000..0006092e0 --- /dev/null +++ b/tools/ci-scripts/configure-tls/configure-trytls @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +set -euxo pipefail + +perl -p -i -e 's!\./runners!runners!' setup.py +sed -i '/import platform/a import distro' runners/trytls/utils.py +sed -i 's/platform.linux_distribution()/distro.name(), distro.version(), distro.id()/' runners/trytls/utils.py +sed -i 's/break//' runners/trytls/bundles/https.py +perl -p -i -e 's/badssl\.com/badssl.test/g; s/badtls\.io/badtls.test/g;' runners/trytls/bundles/https.py +pip3 install -e . diff --git a/tools/ci-scripts/configure-tls/update-certs b/tools/ci-scripts/configure-tls/update-certs new file mode 100755 index 000000000..3ccab98e6 --- /dev/null +++ b/tools/ci-scripts/configure-tls/update-certs @@ -0,0 +1,10 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +set -euxo pipefail + +sed -i -e '/BEGIN CERTIFICATE/,/CERTIFICATE/!b' -e '/END CERTIFICATE/!d;r badtls.io/certs/ca.crt' -e 'd' trytls/runners/trytls/bundles/https.py +sed -i -e '/BEGIN CERTIFICATE/,/CERTIFICATE/!b' -e '/END CERTIFICATE/!d;r badssl.com/certs/sets/test/gen/crt/ca-root.crt' -e 'd' smithy-rs/tools/ci-resources/tls-stub/src/main.rs