Proxy: Make TLS server aware of its own identity. (#1148)

* Proxy: Make TLS server aware of its own identity.

When validating the TLS configuration, make sure the certificate is
valid for the current pod. Make the pod's identity available at that
point in time so it can do so. Since the identity is available now,
simplify the validation of our own certificate by using Rustls's API
instead of dropping down to the lower-level webpli API.

This is a step towards the server differentiating between TLS
handshakes it is supposed to terminate vs. TLS handshakes it is
supposed to pass through.

This is also a step toward the client side (connect) of TLS, which will
reuse much of the configuration logic.

Signed-off-by: Brian Smith <brian@briansmith.org>
This commit is contained in:
Brian Smith 2018-06-18 12:53:10 -10:00 committed by GitHub
parent 13716cd269
commit f82d16f50e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 307 additions and 111 deletions

View File

@ -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?,

View File

@ -97,7 +97,7 @@ impl BoundPort {
// TLS when needed.
pub fn listen_and_fold<T, F, Fut>(
self,
tls_config: tls::ServerConfigWatch,
tls: Option<(tls::Identity, tls::ServerConfigWatch)>,
initial: T,
f: F)
-> impl Future<Item = (), Error = io::Error> + 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 {

View File

@ -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);

View File

@ -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<R, B, E, F, G>(
bound_port: BoundPort,
tls_config: tls::ServerConfigWatch,
tls_config: Option<(tls::Identity, tls::ServerConfigWatch)>,
router: Router<R>,
tcp_connect_timeout: Duration,
disable_protocol_detection_ports: IndexSet<u16>,
@ -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);

View File

@ -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();

View File

@ -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<rustls::Certificate>,
private_key: untrusted::Input)
-> Result<Self, config::Error>
{
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::<Vec<_>>();
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;

View File

@ -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<CertResolver>,
}
@ -64,9 +70,8 @@ pub type ServerConfigWatch = Watch<Option<ServerConfig>>;
pub enum Error {
Io(PathBuf, io::Error),
FailedToParseTrustAnchors(Option<webpki::Error>),
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<Item = CommonConfig, Error = ()>
{
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<Vec<u8>, Error> {
@ -258,3 +280,100 @@ fn set_common_settings(versions: &mut Vec<rustls::ProtocolVersion>) {
// 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),
}
}
}

View File

@ -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> {

View File

@ -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<DnsName>);
pub struct Identity(pub(super) Arc<DnsName>);
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<Option<Self>, ()>
{
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<Self, ()> {
// 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()
}
}

Binary file not shown.

Binary file not shown.

View File

@ -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-----

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBYTCCAQigAwIBAgIUeFHfUb7rtx5XqLkZCvGtBWl9dBwwCgYIKoZIzj0EAwIw
DzENMAsGA1UECxMETm9uZTAeFw0xODA2MTgyMDQ3MDBaFw0yMzA2MTcyMDQ3MDBa
MA8xDTALBgNVBAsTBE5vbmUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASeliXf
Pezb5p/uCv2vHKyTcGY4fIJ/3EZNmImCoKsR2KBjNhOZY5hJNC0XIXscPFwuUKpe
IkYBxLaz1N72VmnGo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUY9VEJcxsPngQJsJ0VC7ZDhjbWAgwCgYIKoZIzj0EAwIDRwAw
RAIgS9lRS+ZSM5xCRx3V57zutoPrxaLqZjO3T7WxbgZXesQCIDpLonnDBl9lApMV
X6SzSOuotZ6t2dz2bHSxw2KYLPK/
-----END CERTIFICATE-----

Binary file not shown.

Binary file not shown.

View File

@ -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