diff --git a/proxy/src/config.rs b/proxy/src/config.rs index 6828d6d4f..9e6a99e04 100644 --- a/proxy/src/config.rs +++ b/proxy/src/config.rs @@ -188,6 +188,7 @@ pub const ENV_TLS_CERT: &str = "CONDUIT_PROXY_TLS_CERT"; pub const ENV_TLS_PRIVATE_KEY: &str = "CONDUIT_PROXY_TLS_PRIVATE_KEY"; pub const ENV_CONTROLLER_NAMESPACE: &str = "CONDUIT_PROXY_CONTROLLER_NAMESPACE"; +pub const ENV_POD_NAME: &str = "CONDUIT_PROXY_POD_NAME"; pub const ENV_POD_NAMESPACE: &str = "CONDUIT_PROXY_POD_NAMESPACE"; pub const ENV_CONTROL_URL: &str = "CONDUIT_PROXY_CONTROL_URL"; @@ -275,6 +276,7 @@ impl<'a> TryFrom<&'a Strings> for Config { let metrics_retain_idle = parse(strings, ENV_METRICS_RETAIN_IDLE, parse_duration); let dns_min_ttl = parse(strings, ENV_DNS_MIN_TTL, parse_duration); let dns_max_ttl = parse(strings, ENV_DNS_MAX_TTL, parse_duration); + let pod_name = strings.get(ENV_POD_NAME); let pod_namespace = strings.get(ENV_POD_NAMESPACE).and_then(|maybe_value| { // There cannot be a default pod namespace, and the pod namespace is required. maybe_value.ok_or_else(|| { @@ -288,41 +290,51 @@ impl<'a> TryFrom<&'a Strings> for Config { // too easy to connect to the wrong controller, which would be dangerous. let control_host_and_port = parse(strings, ENV_CONTROL_URL, parse_url); - let tls_settings = match (tls_trust_anchors?, tls_end_entity_cert?, tls_private_key?) { - (Some(trust_anchors), Some(end_entity_cert), Some(private_key)) => + let namespaces = Namespaces { + pod: pod_namespace?, + tls_controller: controller_namespace?, + }; + + let tls_settings = match (tls_trust_anchors?, + tls_end_entity_cert?, + tls_private_key?, + pod_name?.as_ref()) + { + (Some(trust_anchors), + Some(end_entity_cert), + Some(private_key), + Some(pod_name)) => { + let service_identity = tls::Identity::try_from_pod_name(&namespaces, pod_name) + .map_err(|_| Error::InvalidEnvVar)?; // Already logged. Ok(Some(tls::CommonSettings { trust_anchors, end_entity_cert, private_key, - })), - (_, None, None) => Ok(None), // No TLS in server role. - (trust_anchors, end_entity_cert, private_key) => { + service_identity, + })) + }, + (None, None, None, _) => Ok(None), // No TLS. + (trust_anchors, end_entity_cert, private_key, pod_name) => { if trust_anchors.is_none() { error!("{} is not set; it is required when {} and {} are set.", ENV_TLS_TRUST_ANCHORS, ENV_TLS_CERT, ENV_TLS_PRIVATE_KEY); } if end_entity_cert.is_none() { error!("{} is not set; it is required when {} are set.", - ENV_TLS_CERT, ENV_TLS_PRIVATE_KEY); + ENV_TLS_CERT, ENV_TLS_TRUST_ANCHORS); } if private_key.is_none() { error!("{} is not set; it is required when {} are set.", - ENV_TLS_PRIVATE_KEY, ENV_TLS_CERT); + ENV_TLS_PRIVATE_KEY, ENV_TLS_TRUST_ANCHORS); + } + if pod_name.is_none() { + error!("{} is not set; it is required when {} are set.", + ENV_POD_NAME, ENV_TLS_CERT); } Err(Error::InvalidEnvVar) }, }?; - let tls_controller_namespace = match (&tls_settings, controller_namespace?) { - (Some(_), Some(ns)) => Some(ns), - (Some(_), None) => { - error!("{} is not set; it is required when {} are set.", - ENV_CONTROLLER_NAMESPACE, ENV_TLS_TRUST_ANCHORS); - return Err(Error::InvalidEnvVar); - }, - _ => None, - }; - Ok(Config { private_listener: Listener { addr: private_listener_addr? @@ -374,10 +386,7 @@ impl<'a> TryFrom<&'a Strings> for Config { bind_timeout: bind_timeout?.unwrap_or(DEFAULT_BIND_TIMEOUT), - namespaces: Namespaces { - pod: pod_namespace?, - tls_controller: tls_controller_namespace, - }, + namespaces, dns_min_ttl: dns_min_ttl?, diff --git a/proxy/src/connection.rs b/proxy/src/connection.rs index 5e374eeb7..4b51357af 100644 --- a/proxy/src/connection.rs +++ b/proxy/src/connection.rs @@ -97,7 +97,7 @@ impl BoundPort { // TLS when needed. pub fn listen_and_fold( self, - tls_config: tls::ServerConfigWatch, + tls: Option<(tls::Identity, tls::ServerConfigWatch)>, initial: T, f: F) -> impl Future + Send + 'static @@ -126,14 +126,18 @@ impl BoundPort { // libraries don't have the necessary API for that, so just // do it here. set_nodelay_or_warn(&socket); - match tls_config.borrow().as_ref() { - Some(tls_config) => { - Either::A( - tls::Connection::accept(socket, tls_config.clone()) - .map(move |tls| (Connection::new(Box::new(tls)), remote_addr))) - }, - None => Either::B(future::ok((Connection::new(Box::new(socket)), remote_addr))), + if let Some((_identity, config_watch)) = &tls { + // TODO: use `identity` to differentiate between TLS + // that the proxy should terminate vs. TLS that should + // be passed through. + if let Some(config) = &*config_watch.borrow() { + return Either::A( + tls::Connection::accept(socket, config.clone()) + .map(move |tls| (Connection::new(Box::new(tls)), remote_addr))); + } } + + Either::B(future::ok((Connection::new(Box::new(socket)), remote_addr))) }) .then(|r| { future::ok(match r { diff --git a/proxy/src/control/destination/background.rs b/proxy/src/control/destination/background.rs index 8580c8d09..cba981577 100644 --- a/proxy/src/control/destination/background.rs +++ b/proxy/src/control/destination/background.rs @@ -582,7 +582,7 @@ fn pb_to_addr_meta( labels.sort_by(|(k0, _), (k1, _)| k0.cmp(k1)); let tls_identity = pb.tls_identity.and_then(|pb| { - match tls::Identity::maybe_from(pb, tls_controller_namespace) { + match tls::Identity::maybe_from_protobuf(tls_controller_namespace, pb) { Ok(maybe_tls) => maybe_tls, Err(e) => { error!("Failed to parse TLS identity: {:?}", e); diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index 6cd116a15..e8a5e225e 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -245,9 +245,7 @@ where // to the managed application (private destination). let inbound = { let ctx = ctx::Proxy::inbound(&process_ctx); - let bind = bind.clone().with_ctx(ctx.clone()); - let default_addr = config.private_forward.map(|a| a.into()); let router = Router::new( @@ -257,7 +255,7 @@ where ); serve( inbound_listener, - tls_server_config, + config.tls_settings.map(|settings| (settings.service_identity, tls_server_config)), router, config.private_connect_timeout, config.inbound_ports_disable_protocol_detection, @@ -281,7 +279,7 @@ where ); serve( outbound_listener, - tls::ServerConfig::no_tls(), // No TLS between service & proxy. + None, // No TLS between service & proxy. router, config.public_connect_timeout, config.outbound_ports_disable_protocol_detection, @@ -348,7 +346,7 @@ where fn serve( bound_port: BoundPort, - tls_config: tls::ServerConfigWatch, + tls_config: Option<(tls::Identity, tls::ServerConfigWatch)>, router: Router, tcp_connect_timeout: Duration, disable_protocol_detection_ports: IndexSet, @@ -507,7 +505,7 @@ where let fut = { let log = log.clone(); bound_port.listen_and_fold( - tls::ServerConfig::no_tls(), // TODO: serve over TLS. + None, // TODO: serve over TLS. server, move |server, (session, remote)| { let log = log.clone().with_remote(remote); diff --git a/proxy/src/telemetry/control.rs b/proxy/src/telemetry/control.rs index b521d3cee..44f910007 100644 --- a/proxy/src/telemetry/control.rs +++ b/proxy/src/telemetry/control.rs @@ -102,7 +102,7 @@ impl Control { let fut = { let log = log.clone(); bound_port.listen_and_fold( - ::tls::ServerConfig::no_tls(), // TODO: Serve over TLS. + None, // TODO: Serve over TLS. hyper::server::conn::Http::new(), move |hyper, (conn, remote)| { let service = service.clone(); diff --git a/proxy/src/transport/tls/cert_resolver.rs b/proxy/src/transport/tls/cert_resolver.rs index 23ed5ddfe..52d56bbd6 100755 --- a/proxy/src/transport/tls/cert_resolver.rs +++ b/proxy/src/transport/tls/cert_resolver.rs @@ -1,6 +1,6 @@ use std::{ + fmt, sync::Arc, - time::SystemTime, }; use super::{ @@ -16,6 +16,13 @@ pub struct CertResolver { certified_key: rustls::sign::CertifiedKey, } +impl fmt::Debug for CertResolver { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + f.debug_struct("CertResolver") + .finish() + } +} + struct SigningKey { signer: Signer, } @@ -29,33 +36,18 @@ impl CertResolver { /// Returns a new `CertResolver` that has a certificate (chain) verified to /// have been issued by one of the given trust anchors. /// + /// TODO: Have the caller pass in a `rustls::ServerCertVerified` as evidence + /// that the certificate chain was validated, once Rustls's (safe) API + /// supports that. + /// /// TODO: Verify that the public key of the certificate matches the private /// key. pub fn new( - root_cert_store: &rustls::RootCertStore, + _certificate_was_validated: (), // TODO: `rustls::ServerCertVerified`. cert_chain: Vec, private_key: untrusted::Input) -> Result { - let now = webpki::Time::try_from(SystemTime::now()) - .map_err(|ring::error::Unspecified| config::Error::TimeConversionFailed)?; - - let trust_anchors = root_cert_store.roots.iter() - .map(|owned_trust_anchor| owned_trust_anchor.to_trust_anchor()) - .collect::>(); - let trust_anchors = webpki::TLSServerTrustAnchors(&trust_anchors); - - // Verify that we were given a valid TLS certificate that was issued by - // our CA. - parse_end_entity_cert(&cert_chain) - .and_then(|cert| { - cert.verify_is_valid_tls_server_cert( - &[SIGNATURE_ALG_WEBPKI], - &trust_anchors, - &[], // No intermediate certificates - now) - }).map_err(config::Error::EndEntityCertIsNotValid)?; - let private_key = signature::key_pair_from_pkcs8(SIGNATURE_ALG_RING_SIGNING, private_key) .map_err(|ring::error::Unspecified| config::Error::InvalidPrivateKey)?; @@ -139,4 +131,3 @@ const SIGNATURE_ALG_RUSTLS_SCHEME: rustls::SignatureScheme = rustls::SignatureScheme::ECDSA_NISTP256_SHA256; const SIGNATURE_ALG_RUSTLS_ALGORITHM: rustls::internal::msgs::enums::SignatureAlgorithm = rustls::internal::msgs::enums::SignatureAlgorithm::ECDSA; -static SIGNATURE_ALG_WEBPKI: &webpki::SignatureAlgorithm = &webpki::ECDSA_P256_SHA256; diff --git a/proxy/src/transport/tls/config.rs b/proxy/src/transport/tls/config.rs index f0a20cc2b..fd7ecc097 100644 --- a/proxy/src/transport/tls/config.rs +++ b/proxy/src/transport/tls/config.rs @@ -8,6 +8,7 @@ use std::{ use super::{ cert_resolver::CertResolver, + Identity, rustls, untrusted, @@ -39,10 +40,15 @@ pub struct CommonSettings { /// The private key in DER-encoded PKCS#8 form. pub private_key: PathBuf, + + /// The identity we use to identify the service being proxied (as opposed + /// to the psuedo-service exposed on the proxy's control port). + pub service_identity: Identity, } /// Validated configuration common between TLS clients and TLS servers. -pub struct CommonConfig { +#[derive(Debug)] +struct CommonConfig { root_cert_store: rustls::RootCertStore, cert_resolver: Arc, } @@ -64,9 +70,8 @@ pub type ServerConfigWatch = Watch>; pub enum Error { Io(PathBuf, io::Error), FailedToParseTrustAnchors(Option), - EndEntityCertIsNotValid(webpki::Error), + EndEntityCertIsNotValid(rustls::TLSError), InvalidPrivateKey, - TimeConversionFailed, } impl CommonSettings { @@ -83,7 +88,7 @@ impl CommonSettings { /// The returned stream consists of each subsequent successfully loaded /// `CommonSettings` after each change. If the settings could not be /// reloaded (i.e., they were malformed), nothing is sent. - pub fn stream_changes(self, interval: Duration) + fn stream_changes(self, interval: Duration) -> impl Stream { let paths = self.paths().iter() @@ -143,8 +148,30 @@ impl CommonConfig { let private_key = load_file_contents(&settings.private_key)?; let private_key = untrusted::Input::from(&private_key); - // `CertResolver::new` is responsible for the consistency check. - let cert_resolver = CertResolver::new(&root_cert_store, cert_chain, private_key)?; + // Ensure the certificate is valid for the services we terminate for + // TLS. This assumes that server cert validation does the same or + // more validation than client cert validation. + // + // XXX: Rustls currently only provides access to a + // `ServerCertVerifier` through + // `rustls::ClientConfig::get_verifier()`. + // + // XXX: Once `rustls::ServerCertVerified` is exposed in Rustls's + // safe API, remove the `map(|_| ())` below. + // + // TODO: Restrict accepted signatutre algorithms. + let certificate_was_validated = + rustls::ClientConfig::new().get_verifier().verify_server_cert( + &root_cert_store, + &cert_chain, + settings.service_identity.as_dns_name_ref(), + &[]) // No OCSP + .map(|_| ()) + .map_err(Error::EndEntityCertIsNotValid)?; + + // `CertResolver::new` is responsible for verifying that the + // private key is the right one for the certificate. + let cert_resolver = CertResolver::new(certificate_was_validated, cert_chain, private_key)?; Ok(Self { root_cert_store, @@ -221,11 +248,6 @@ impl ServerConfig { config.cert_resolver = common.cert_resolver.clone(); ServerConfig(Arc::new(config)) } - - pub fn no_tls() -> ServerConfigWatch { - let (watch, _) = Watch::new(None); - watch - } } fn load_file_contents(path: &PathBuf) -> Result, Error> { @@ -258,3 +280,100 @@ fn set_common_settings(versions: &mut Vec) { // Only enable TLS 1.2 until TLS 1.3 is stable. *versions = vec![rustls::ProtocolVersion::TLSv1_2] } + + +#[cfg(test)] +mod tests { + use tls::{CommonSettings, Identity, ServerConfig}; + use super::{CommonConfig, Error}; + use config::Namespaces; + use std::path::PathBuf; + + struct Strings { + pod_name: &'static str, + pod_ns: &'static str, + controller_ns: &'static str, + trust_anchors: &'static str, + end_entity_cert: &'static str, + private_key: &'static str, + } + + fn settings(s: &Strings) -> CommonSettings { + let dir = PathBuf::from("src/transport/tls/testdata"); + let namespaces = Namespaces { + pod: s.pod_ns.into(), + tls_controller: Some(s.controller_ns.into()), + }; + let service_identity = Identity::try_from_pod_name(&namespaces, s.pod_name).unwrap(); + CommonSettings { + trust_anchors: dir.join(s.trust_anchors), + end_entity_cert: dir.join(s.end_entity_cert), + private_key: dir.join(s.private_key), + service_identity, + } + } + + #[test] + fn can_construct_server_config_from_valid_settings() { + let settings = settings(&Strings { + pod_name: "foo", + pod_ns: "ns1", + controller_ns: "conduit", + trust_anchors: "ca1.pem", + end_entity_cert: "foo-ns1-ca1.crt", + private_key: "foo-ns1-ca1.p8", + }); + let config = CommonConfig::load_from_disk(&settings).unwrap(); + let _: ServerConfig = ServerConfig::from(&config); // Infallible. + } + + #[test] + fn recognize_ca_did_not_issue_cert() { + let settings = settings(&Strings { + pod_name: "foo", + pod_ns: "ns1", + controller_ns: "conduit", + trust_anchors: "ca2.pem", // Mismatch + end_entity_cert: "foo-ns1-ca1.crt", + private_key: "foo-ns1-ca1.p8", + }); + match CommonConfig::load_from_disk(&settings) { + Err(Error::EndEntityCertIsNotValid(_)) => (), + r => unreachable!("CommonConfig::load_from_disk returned {:?}", r), + } + } + + #[test] + fn recognize_cert_is_not_valid_for_identity() { + let settings = settings(&Strings { + pod_name: "foo", // Mismatch + pod_ns: "ns1", + controller_ns: "conduit", + trust_anchors: "ca1.pem", + end_entity_cert: "bar-ns1-ca1.crt", + private_key: "bar-ns1-ca1.p8", + }); + match CommonConfig::load_from_disk(&settings) { + Err(Error::EndEntityCertIsNotValid(_)) => (), + r => unreachable!("CommonConfig::load_from_disk returned {:?}", r), + } + } + + // XXX: The check that this tests hasn't been implemented yet. + #[test] + #[should_panic] + fn recognize_private_key_is_not_valid_for_cert() { + let settings = settings(&Strings { + pod_name: "foo", + pod_ns: "ns1", + controller_ns: "conduit", + trust_anchors: "ca1.pem", + end_entity_cert: "foo-ns1-ca1.crt", + private_key: "bar-ns1-ca1.p8", // Mismatch + }); + match CommonConfig::load_from_disk(&settings) { + Err(_) => (), // // TODO: Err(Error::InvalidPrivateKey) > (), + r => unreachable!("CommonConfig::load_from_disk returned {:?}", r), + } + } +} diff --git a/proxy/src/transport/tls/dns_name.rs b/proxy/src/transport/tls/dns_name.rs index a82cae59d..3a141bc9a 100644 --- a/proxy/src/transport/tls/dns_name.rs +++ b/proxy/src/transport/tls/dns_name.rs @@ -6,7 +6,7 @@ use convert::TryFrom; /// are specified in [RFC 5280 Section 7.2], except that underscores are also /// allowed. #[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct DnsName(webpki::DNSName); +pub struct DnsName(pub(super) webpki::DNSName); impl fmt::Display for DnsName { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { diff --git a/proxy/src/transport/tls/identity.rs b/proxy/src/transport/tls/identity.rs index 66ffbb976..b9c523d68 100644 --- a/proxy/src/transport/tls/identity.rs +++ b/proxy/src/transport/tls/identity.rs @@ -1,11 +1,12 @@ use conduit_proxy_controller_grpc; use convert::TryFrom; -use super::{DnsName, InvalidDnsName}; +use super::{DnsName, InvalidDnsName, webpki}; use std::sync::Arc; +use config::Namespaces; /// An endpoint's identity. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Identity(Arc); +pub struct Identity(pub(super) Arc); impl Identity { /// Parses the given TLS identity, if provided. @@ -14,11 +15,33 @@ impl Identity { /// /// In the event of an error, the error is logged, so no detailed error /// information is returned. - pub fn maybe_from( - pb: conduit_proxy_controller_grpc::destination::TlsIdentity, - controller_namespace: Option<&str>) + pub fn maybe_from_protobuf( + controller_namespace: Option<&str>, + pb: conduit_proxy_controller_grpc::destination::TlsIdentity) -> Result, ()> { + use conduit_proxy_controller_grpc::destination::tls_identity::Strategy; + match pb.strategy { + Some(Strategy::K8sPodNamespace(i)) => { + // XXX: If we don't know the controller's namespace or we don't + // share the same controller then we won't be able to validate + // the certificate yet. TODO: Support cross-controller + // certificate validation and lock this down. + if controller_namespace != Some(i.controller_ns.as_ref()) { + return Ok(None); + } + + let namespaces = Namespaces { + pod: i.pod_ns, + tls_controller: Some(i.controller_ns), + }; + Self::try_from_pod_name(&namespaces, &i.pod_name).map(Some) + }, + None => Ok(None), // No TLS. + } + } + + pub fn try_from_pod_name(namespaces: &Namespaces, pod_name: &str) -> Result { // Verifies that the string doesn't contain '.' so that it is safe to // join it using '.' to try to form a DNS name. The rest of the DNS // name rules will be enforced by `DnsName::try_from`. @@ -31,42 +54,39 @@ impl Identity { } } - use conduit_proxy_controller_grpc::destination::tls_identity::Strategy; - match pb.strategy { - Some(Strategy::K8sPodNamespace(i)) => { - // Log any/any per-component errors before returning. - let controller_ns_check = check_single_label(&i.controller_ns, "controller_ms"); - let pod_ns_check = check_single_label(&i.pod_ns, "pod_ns"); - let pod_name_check = check_single_label(&i.pod_name, "pod_name"); - if controller_ns_check.is_err() || pod_ns_check.is_err() || pod_name_check.is_err() { - return Err(()); - } + let controller_ns = if let Some(controller_ns) = &namespaces.tls_controller { + controller_ns + } else { + error!("controller namespace not provided"); + return Err(()); + }; - // XXX: If we don't know the controller's namespace or we don't - // share the same controller then we won't be able to validate - // the certificate yet. TODO: Support cross-controller - // certificate validation and lock this down. - if controller_namespace != Some(i.controller_ns.as_ref()) { - return Ok(None) - } - - // We reserve all names under a fake "managed-pods" service in - // our namespace for identifying pods by name. - let name = format!( - "{pod}.{pod_ns}.conduit-managed-pods.{controller_ns}.svc.cluster.local.", - pod = i.pod_name, - pod_ns = i.pod_ns, - controller_ns = i.controller_ns, - ); - - DnsName::try_from(&name) - .map(|name| Some(Identity(Arc::new(name)))) - .map_err(|InvalidDnsName| { - error!("Invalid DNS name: {:?}", name); - () - }) - }, - None => Ok(None), // No TLS. + // Log any/any per-component errors before returning. + let controller_ns_check = check_single_label(controller_ns, "controller namespace"); + let pod_ns_check = check_single_label(&namespaces.pod, "pod namespace"); + let pod_name_check = check_single_label(pod_name, "pod name"); + if controller_ns_check.is_err() || pod_ns_check.is_err() || pod_name_check.is_err() { + return Err(()); } + + // We reserve all names under a fake "managed-pods" service in + // our namespace for identifying pods by name. + let name = format!( + "{pod}.{pod_ns}.conduit-managed-pods.{controller_ns}.svc.cluster.local.", + pod = pod_name, + pod_ns = &namespaces.pod, + controller_ns = controller_ns, + ); + + DnsName::try_from(&name) + .map(|name| Identity(Arc::new(name))) + .map_err(|InvalidDnsName| { + error!("Invalid DNS name: {:?}", name); + () + }) + } + + pub(super) fn as_dns_name_ref(&self) -> webpki::DNSNameRef { + (self.0).0.as_ref() } } diff --git a/proxy/src/transport/tls/testdata/bar-ns1-ca1.crt b/proxy/src/transport/tls/testdata/bar-ns1-ca1.crt new file mode 100644 index 000000000..48b53dcd6 Binary files /dev/null and b/proxy/src/transport/tls/testdata/bar-ns1-ca1.crt differ diff --git a/proxy/src/transport/tls/testdata/bar-ns1-ca1.p8 b/proxy/src/transport/tls/testdata/bar-ns1-ca1.p8 new file mode 100644 index 000000000..3c6aa8e3c Binary files /dev/null and b/proxy/src/transport/tls/testdata/bar-ns1-ca1.p8 differ diff --git a/proxy/src/transport/tls/testdata/ca1.pem b/proxy/src/transport/tls/testdata/ca1.pem new file mode 100644 index 000000000..03a74a3fb --- /dev/null +++ b/proxy/src/transport/tls/testdata/ca1.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYjCCAQigAwIBAgIUeNut9wWIX23pQjr5vp9CVjT5ckYwCgYIKoZIzj0EAwIw +DzENMAsGA1UECxMETm9uZTAeFw0xODA2MTgyMDQ3MDBaFw0yMzA2MTcyMDQ3MDBa +MA8xDTALBgNVBAsTBE5vbmUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATpX00P +uIzqsJOyfF8j5KQxZwl7z8gwFRUOKS5pGYToLZRHknDCAM6+8atts+rlOCbRx3Ip +BOE6/zl8mmgwDtHho0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUd6BIvpxr8rUL9quQelM8GiTv2h0wCgYIKoZIzj0EAwIDSAAw +RQIhANh+WhsYerczLUi5fsZNOFVA+gTaYLaPfjkZ+xduaFnDAiBor7lB9XUt/1Xn +J+2gLEkKlZnmoDx7EWOlnfK5/zkVMg== +-----END CERTIFICATE----- diff --git a/proxy/src/transport/tls/testdata/ca2.pem b/proxy/src/transport/tls/testdata/ca2.pem new file mode 100644 index 000000000..0f8121387 --- /dev/null +++ b/proxy/src/transport/tls/testdata/ca2.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYTCCAQigAwIBAgIUeFHfUb7rtx5XqLkZCvGtBWl9dBwwCgYIKoZIzj0EAwIw +DzENMAsGA1UECxMETm9uZTAeFw0xODA2MTgyMDQ3MDBaFw0yMzA2MTcyMDQ3MDBa +MA8xDTALBgNVBAsTBE5vbmUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASeliXf +Pezb5p/uCv2vHKyTcGY4fIJ/3EZNmImCoKsR2KBjNhOZY5hJNC0XIXscPFwuUKpe +IkYBxLaz1N72VmnGo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUY9VEJcxsPngQJsJ0VC7ZDhjbWAgwCgYIKoZIzj0EAwIDRwAw +RAIgS9lRS+ZSM5xCRx3V57zutoPrxaLqZjO3T7WxbgZXesQCIDpLonnDBl9lApMV +X6SzSOuotZ6t2dz2bHSxw2KYLPK/ +-----END CERTIFICATE----- diff --git a/proxy/src/transport/tls/testdata/foo-ns1-ca1.crt b/proxy/src/transport/tls/testdata/foo-ns1-ca1.crt new file mode 100644 index 000000000..72e5e168f Binary files /dev/null and b/proxy/src/transport/tls/testdata/foo-ns1-ca1.crt differ diff --git a/proxy/src/transport/tls/testdata/foo-ns1-ca1.p8 b/proxy/src/transport/tls/testdata/foo-ns1-ca1.p8 new file mode 100644 index 000000000..05adfde59 Binary files /dev/null and b/proxy/src/transport/tls/testdata/foo-ns1-ca1.p8 differ diff --git a/proxy/src/transport/tls/testdata/gen-certs.sh b/proxy/src/transport/tls/testdata/gen-certs.sh new file mode 100644 index 000000000..f7376cbd4 --- /dev/null +++ b/proxy/src/transport/tls/testdata/gen-certs.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -euox pipefail + +ca() { + filename=$1 + name=$2 + echo '{"names":[{"CN": "${name}","OU":"None"}]}' \ + | cfssl genkey -initca - \ + | cfssljson -bare ${name} +} + +ee() { + ca_name=$1 + ee_name=$2 + hostname=$3 + echo '{}' \ + | cfssl gencert -ca ${ca_name}.pem -ca-key ${ca_name}-key.pem -hostname=${hostname} - \ + | cfssljson -bare ${ee_name} + openssl pkcs8 -topk8 -nocrypt -inform pem -outform der \ + -in ${ee_name}-key.pem \ + -out ${ee_name}-${ca_name}.p8 + openssl x509 -inform pem -outform der \ + -in ${ee_name}.pem \ + -out ${ee_name}-${ca_name}.crt + rm ${ee_name}.pem +} + +ca "Cluster-local CA 1" ca1 +ca "Cluster-local CA 1" ca2 # Same name, different key pair. +ee ca1 foo-ns1 foo.ns1.conduit-managed-pods.conduit.svc.cluster.local +ee ca2 foo-ns1 foo.ns1.conduit-managed-pods.conduit.svc.cluster.local # Same, but different CA +ee ca1 bar-ns1 bar.ns1.conduit-managed-pods.conduit.svc.cluster.local # Different service. + +rm *-key.pem *.csr +