Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ These are the attestation type names used in the HTTP headers, and the measureme
- `azure-tdx` - TDX on Azure, with vTPM attestation
- `dcap-tdx` - DCAP TDX (platform not specified)

The HTTP and TCP tunnel client/server commands default to `auto` when the local
attestation type is omitted. If no supported attestation platform is detected,
startup fails. To run without local attestation, explicitly pass
Comment on lines +83 to +85
`--client-attestation-type none` or `--server-attestation-type none` (or set the
corresponding `CLIENT_ATTESTATION_TYPE` / `SERVER_ATTESTATION_TYPE` environment
variable to `none`). This is separate from the remote verification policy.

### Other CLI Options

- `--pccs-url` selects the PCCS used to retrieve collateral when verifying DCAP attestations. It defaults to Intel PCS.
Expand Down Expand Up @@ -264,6 +271,7 @@ docker run --rm \
docker run --rm attested-tls-proxy client \
--listen-addr 0.0.0.0:8080 \
target-server:443 \
--client-attestation-type none \
--allowed-remote-attestation-type none
```

Expand All @@ -273,7 +281,10 @@ use the target container's name instead of `host.docker.internal`.

### Testing with Docker Compose

A `docker-compose.yml` is provided to test the full proxy chain:
A `docker-compose.yml` is provided for local testing of the full proxy chain.
It uses test certificates and explicitly disables local attestation on both
proxies, testing TLS forwarding without attestation. For a CVM deployment,
configure local attestation and remote verification policies for your platform.

1. **Generate test certificates:**
```bash
Expand Down
2 changes: 1 addition & 1 deletion crates/attested-tls-proxy/TCP_TUNNEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ settings follow the [HTTP proxy CLI](../../README.md#measurements-file).
| Client `--tls-ca-certificate` | Trust the first PEM certificate instead of public roots. |
| Client `--allow-self-signed` | Accept a self-signed server certificate; still verify attestation. Cannot combine with `--tls-ca-certificate`. Preserves any supplied client identity. |
| Server `--client-auth` | Require a TLS client certificate authenticated against public roots. Private client CAs can be configured through the Rust API. |
| `--client-attestation-type` / `--server-attestation-type` | Automatic local detection when omitted. |
| `--client-attestation-type` / `--server-attestation-type` | Automatic local detection when omitted or set to `auto`; startup fails if no supported platform is detected. Set `none` explicitly to run without local attestation. |
| `--pccs-url`, `--dev-dummy-dcap` | Same meaning as in the HTTP proxy. |
| `--log-debug`, `--log-json`, `--log-dcap-quote` | Debug logs, structured logs, or DCAP quote dumps in `quotes/`. Logs go to stderr; the default level is info. Payload bytes are never logged. |
| `--override-azure-outdated-tcb` | Same Azure verification override as in the HTTP proxy. |
Expand Down
86 changes: 84 additions & 2 deletions crates/attested-tls-proxy/src/cli/attestation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::anyhow;
use anyhow::{anyhow, ensure};
use attested_tls::attestation::{
AttestationType, AttestationVerifier, CachePolicy, measurements::MeasurementPolicy,
AttestationGenerator, AttestationType, AttestationVerifier, CachePolicy,
measurements::MeasurementPolicy,
};

pub(super) async fn build_verifier(
Expand Down Expand Up @@ -51,3 +52,84 @@ pub(super) async fn build_verifier(
}
Ok(attestation_verifier_builder.build())
}

/// Build a local generator, requiring explicit opt-in to run without attestation.
pub(super) fn build_attestation_generator(
requested_type: Option<String>,
provider_url: Option<String>,
type_option: &str,
) -> anyhow::Result<AttestationGenerator> {
let generator = AttestationGenerator::new_with_detection(requested_type.clone(), provider_url)?;
validate_platform(
generator.attestation_type,
requested_type.as_deref(),
type_option,
)?;
Ok(generator)
}

fn validate_platform(
platform: AttestationType,
requested_type: Option<&str>,
type_option: &str,
) -> anyhow::Result<()> {
let automatic = matches!(requested_type, None | Some("auto"));
ensure!(
!automatic || platform != AttestationType::None,
"No supported attestation platform detected. To proceed without local attestation, explicitly set {type_option} none."
);
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn automatic_detection_requires_an_attested_platform() {
for option in ["--client-attestation-type", "--server-attestation-type"] {
for requested in [None, Some("auto")] {
let error = validate_platform(AttestationType::None, requested, option)
.unwrap_err()
.to_string();
assert!(error.contains("No supported attestation platform detected"));
assert!(error.contains(&format!("{option} none")));
for platform in [
AttestationType::DcapTdx,
AttestationType::GcpTdx,
AttestationType::AzureTdx,
] {
validate_platform(platform, requested, option).unwrap();
}
}
}
}

#[test]
fn explicit_none_is_supported() {
let generator =
build_attestation_generator(Some("none".into()), None, "--client-attestation-type")
.unwrap();
assert_eq!(generator.attestation_type, AttestationType::None);
}

#[test]
fn upstream_configuration_errors_propagate() {
for (requested, provider) in [
(Some("invalid"), None),
(None, Some("http://127.0.0.1:1234")),
(Some("auto"), Some("http://127.0.0.1:1234")),
(Some("dcap-tdx"), Some("not a URL")),
] {
let requested = requested.map(str::to_owned);
let provider = provider.map(str::to_owned);
let expected =
AttestationGenerator::new_with_detection(requested.clone(), provider.clone())
.unwrap_err();
let actual =
build_attestation_generator(requested, provider, "--client-attestation-type")
.unwrap_err();
assert_eq!(actual.to_string(), expected.to_string());
}
}
}
24 changes: 16 additions & 8 deletions crates/attested-tls-proxy/src/cli/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ pub(super) struct ClientArgs {
/// Maximum in-flight requests, including streaming responses
#[arg(long, default_value = "64", value_parser = parse_max_in_flight_requests)]
max_in_flight_requests: NonZeroUsize,
/// Type of attestation to present (dafaults to 'auto' for automatic detection)
/// Local attestation type (defaults to auto; errors if no platform is detected).
/// Set none explicitly to run without local attestation.
/// If other than None, a TLS key and certicate must also be given
#[arg(long, env = "CLIENT_ATTESTATION_TYPE")]
client_attestation_type: Option<String>,
Expand Down Expand Up @@ -84,6 +85,12 @@ impl ClientArgs {
.unwrap_or(&target_addr)
.to_string();

let client_attestation_generator = super::attestation::build_attestation_generator(
client_attestation_type,
dev_dummy_dcap,
"--client-attestation-type",
)?;

if let Some(listen_addr_healthcheck) = listen_addr_healthcheck {
health_check::server(listen_addr_healthcheck).await?;
}
Expand Down Expand Up @@ -112,9 +119,6 @@ impl ClientArgs {
None => None,
};

let client_attestation_generator =
AttestationGenerator::new_with_detection(client_attestation_type, dev_dummy_dcap)?;

let client_tls_config = attested_tls::tls::client_config(
tls_cert_and_chain.as_ref(),
remote_tls_cert,
Expand Down Expand Up @@ -150,7 +154,8 @@ pub(super) struct ServerArgs {
listen_addr: SocketAddr,
/// The hostname:port or ip:port of the target service to forward traffic to
target_addr: String,
/// Type of attestation to present (dafaults to 'auto' for automatic detection)
/// Local attestation type (defaults to auto; errors if no platform is detected).
/// Set none explicitly to run without local attestation.
/// If other than None, a TLS key and certicate must also be given
#[arg(long, env = "SERVER_ATTESTATION_TYPE")]
server_attestation_type: Option<String>,
Expand Down Expand Up @@ -186,6 +191,12 @@ impl ServerArgs {
listen_addr_healthcheck,
} = self;

let local_attestation_generator = super::attestation::build_attestation_generator(
server_attestation_type,
dev_dummy_dcap,
"--server-attestation-type",
)?;

if let Some(listen_addr_healthcheck) = listen_addr_healthcheck {
health_check::server(listen_addr_healthcheck).await?;
}
Expand All @@ -196,9 +207,6 @@ impl ServerArgs {
listen_addr.ip(),
)?;

let local_attestation_generator =
AttestationGenerator::new_with_detection(server_attestation_type, dev_dummy_dcap)?;

let server = ProxyServer::new(
tls_cert_and_chain,
listen_addr,
Expand Down
31 changes: 17 additions & 14 deletions crates/attested-tls-proxy/src/cli/tcp_tunnel.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use anyhow::{anyhow, ensure};
use attested_tls::self_signed::generate_self_signed_cert;
use attested_tls::tls;
use attested_tls::{
TlsCertAndKey,
attestation::{AttestationGenerator, AttestationVerifier},
};
use attested_tls::{TlsCertAndKey, attestation::AttestationVerifier};
use attested_tls_proxy::tcp_tunnel::{TunnelClient, TunnelOptions, TunnelServer};
use clap::Args;
use std::{
Expand All @@ -25,7 +22,8 @@ pub(super) struct ClientArgs {
limits: Limits,
#[command(flatten)]
identity: Identity,
/// Local attestation type (defaults to automatic detection)
/// Local attestation type (defaults to auto; errors if no platform is detected).
/// Set none explicitly to run without local attestation.
#[arg(long, env = "CLIENT_ATTESTATION_TYPE")]
client_attestation_type: Option<String>,
/// CA certificate to trust instead of public roots
Expand All @@ -46,7 +44,8 @@ pub(super) struct ServerArgs {
limits: Limits,
#[command(flatten)]
identity: Identity,
/// Local attestation type (defaults to automatic detection)
/// Local attestation type (defaults to auto; errors if no platform is detected).
/// Set none explicitly to run without local attestation.
#[arg(long, env = "SERVER_ATTESTATION_TYPE")]
server_attestation_type: Option<String>,
/// Require a client TLS certificate authenticated against public roots
Expand Down Expand Up @@ -127,6 +126,12 @@ impl ClientArgs {
allow_self_signed,
} = self;

let generator = super::attestation::build_attestation_generator(
client_attestation_type,
identity.dev_dummy_dcap.clone(),
"--client-attestation-type",
)?;

let credentials = identity.load()?;
let remote_certificate = tls_ca_certificate
.as_deref()
Expand All @@ -135,10 +140,6 @@ impl ClientArgs {
.and_then(|certs| certs.into_iter().next());
let config =
tls::client_config(credentials.as_ref(), remote_certificate, allow_self_signed)?;
let generator = AttestationGenerator::new_with_detection(
client_attestation_type,
identity.dev_dummy_dcap,
)?;
let client = TunnelClient::new_with_tls_config(
listen_addr,
target_addr,
Expand Down Expand Up @@ -167,17 +168,19 @@ impl ServerArgs {
client_auth,
} = self;

let generator = super::attestation::build_attestation_generator(
server_attestation_type,
identity.dev_dummy_dcap.clone(),
"--server-attestation-type",
)?;

let credentials = match identity.load()? {
Some(credentials) => credentials,
None => {
tracing::warn!("No TLS certificate provided; generating self-signed certificate");
generate_self_signed_cert(listen_addr.ip())?
}
};
let generator = AttestationGenerator::new_with_detection(
server_attestation_type,
identity.dev_dummy_dcap,
)?;
let server = TunnelServer::new(
listen_addr,
target_addr,
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ services:
- --listen-addr=0.0.0.0:8443
- --tls-private-key-path=/certs/server.key
- --tls-certificate-path=/certs/server.crt
- --server-attestation-type=none
- --allowed-remote-attestation-type=none
- "172.28.0.10:80"
volumes:
Expand All @@ -34,6 +35,7 @@ services:
- --listen-addr=0.0.0.0:8080
- proxy-server:8443
- --tls-ca-certificate=/certs/ca.crt
- --client-attestation-type=none
- --allowed-remote-attestation-type=none
volumes:
- ./certs:/certs:ro
Expand Down
Loading