Unverified Commit 61b675c0 authored by 82marbag's avatar 82marbag Committed by GitHub
Browse files

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
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [ ] 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: default avatarDaniele Ahmed <ahmeddan@amazon.de>
Co-authored-by: default avatarDaniele Ahmed <ahmeddan@amazon.de>
parent 5675a69d
Loading
Loading
Loading
Loading
+81 −0
Original line number Diff line number Diff line
# 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
+4 −0
Original line number Diff line number Diff line
@@ -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)
+23 −0
Original line number Diff line number Diff line
#
# 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"
+8 −0
Original line number Diff line number Diff line
# 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.
+160 −0
Original line number Diff line number Diff line
/*
 * 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<String> = env::args().collect();
    if argv.len() < 3 || argv.len() > 4 {
        eprintln!("Syntax: {} <hostname> <port> [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(())
}
Loading