From 6c1bd2fbee63671f9b8cf9fdb6f2832bf9b3aebb Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Thu, 29 Feb 2024 15:21:27 +0100 Subject: [PATCH] "user-ca-bundle" should be consider external regardless of proxy MCO will observe the `user-ca-bundle` configmap regardless of whether the proxy resource points at it or not. So recert should consider all certs inside this configmap to be external. --- src/cluster_crypto/scanning.rs | 91 +----------- src/cluster_crypto/scanning/external_certs.rs | 135 ++++++++++++++++++ src/recert.rs | 2 +- 3 files changed, 138 insertions(+), 90 deletions(-) create mode 100644 src/cluster_crypto/scanning/external_certs.rs diff --git a/src/cluster_crypto/scanning.rs b/src/cluster_crypto/scanning.rs index ea902e48..c6e26dff 100644 --- a/src/cluster_crypto/scanning.rs +++ b/src/cluster_crypto/scanning.rs @@ -7,12 +7,11 @@ use crate::{ cluster_crypto::{crypto_objects::process_unknown_value, json_crawl}, config::ConfigPath, file_utils::{self, read_file_to_string}, - k8s_etcd::{get_etcd_json, InMemoryK8sEtcd}, + k8s_etcd::InMemoryK8sEtcd, recert::timing::RunTime, }; use anyhow::{bail, ensure, Context, Error, Result}; use futures_util::future::join_all; -use itertools::Itertools; use serde_json::Value; use std::{ collections::HashSet, @@ -21,94 +20,8 @@ use std::{ sync::Arc, }; use tokio::task::JoinHandle; -use x509_certificate::X509Certificate; -pub(crate) async fn discover_external_certs(in_memory_etcd_client: Arc) -> Result> { - let trusted_certs = vec![get_openshift_trusted_certs(&in_memory_etcd_client) - .await - .context("openshift trusted certs")?]; - let image_trusted_certs = get_openshift_image_trusted_certs(&in_memory_etcd_client) - .await - .context("image trusted certs")?; - - let all_certs_bundled = trusted_certs.into_iter().chain(image_trusted_certs).join("\n"); - - pem::parse_many(all_certs_bundled) - .context("parsing")? - .into_iter() - .map(|pem| match pem.tag() { - "CERTIFICATE" => Ok({ - let crt = X509Certificate::from_der(pem.contents()).context("from der")?; - let cn = crt.subject_name().user_friendly_str().unwrap_or("undecodable".to_string()); - - log::trace!("Found external certificate: {}", cn); - - cn.to_string() - }), - _ => bail!("unexpected tag"), - }) - .collect::>>() -} - -async fn get_openshift_image_trusted_certs(in_memory_etcd_client: &Arc) -> Result> { - let mut pem_strings = vec![]; - - let image_config = get_etcd_json( - in_memory_etcd_client, - &(K8sResourceLocation::new(None, "Image", "cluster", "config.openshift.io")), - ) - .await - .context("getting image config")? - .context("image config not found")?; - - if let Some(additional_trusted_ca) = image_config.pointer("/spec/additionalTrustedCA/name") { - let user_image_ca_configmap = get_etcd_json( - in_memory_etcd_client, - &(K8sResourceLocation { - namespace: Some("openshift-config".into()), - kind: "ConfigMap".into(), - apiversion: "v1".into(), - name: additional_trusted_ca.as_str().context("must be string")?.into(), - }), - ) - .await - .context("getting user image ca configmap")? - .context("user image ca configmap not found")?; - - for (k, v) in user_image_ca_configmap - .pointer("/data") - .context("parsing registry-cas")? - .as_object() - .context("must be object")? - { - pem_strings.push(v.as_str().context(format!("must be string ({k})"))?.to_string()); - } - } - - Ok(pem_strings) -} - -async fn get_openshift_trusted_certs(in_memory_etcd_client: &Arc) -> Result { - let trusted_ca_bundle_configmap = get_etcd_json( - in_memory_etcd_client, - &(K8sResourceLocation { - namespace: Some("openshift-config-managed".into()), - kind: "ConfigMap".into(), - apiversion: "v1".into(), - name: "trusted-ca-bundle".into(), - }), - ) - .await - .context("getting trusted-ca-bundle")? - .context("trusted-ca-bundle not found")?; - - Ok(trusted_ca_bundle_configmap - .pointer("/data/ca-bundle.crt") - .context("parsing ca-bundle.crt")? - .as_str() - .context("must be string")? - .to_string()) -} +pub(crate) mod external_certs; pub(crate) async fn crypto_scan( in_memory_etcd_client: Arc, diff --git a/src/cluster_crypto/scanning/external_certs.rs b/src/cluster_crypto/scanning/external_certs.rs new file mode 100644 index 00000000..d38adfb0 --- /dev/null +++ b/src/cluster_crypto/scanning/external_certs.rs @@ -0,0 +1,135 @@ +use super::super::locations::K8sResourceLocation; +use crate::k8s_etcd::get_etcd_json; +use crate::k8s_etcd::InMemoryK8sEtcd; +use anyhow::{bail, Context, Result}; +use itertools::Itertools; +use std::collections::HashSet; +use std::sync::Arc; +use x509_certificate::X509Certificate; + +pub(crate) async fn discover_external_certs(in_memory_etcd_client: Arc) -> Result> { + let proxy_trusted_certs = vec![get_openshift_proxy_trusted_certs(&in_memory_etcd_client) + .await + .context("openshift trusted certs")?]; + let ocp_trusted_certs = match get_openshift_user_ca_bundle(&in_memory_etcd_client) + .await + .context("openshift trusted certs")? + { + Some(certs) => vec![certs], + None => vec![], + }; + let image_trusted_certs = get_openshift_image_trusted_certs(&in_memory_etcd_client) + .await + .context("image trusted certs")?; + + let all_certs_bundled = proxy_trusted_certs + .into_iter() + .chain(image_trusted_certs) + .chain(ocp_trusted_certs) + .join("\n"); + + pem::parse_many(all_certs_bundled) + .context("parsing")? + .into_iter() + .map(|pem| match pem.tag() { + "CERTIFICATE" => Ok({ + let crt = X509Certificate::from_der(pem.contents()).context("from der")?; + let cn = crt.subject_name().user_friendly_str().unwrap_or("undecodable".to_string()); + + log::trace!("Found external certificate: {}", cn); + + cn.to_string() + }), + _ => bail!("unexpected tag"), + }) + .collect::>>() +} + +pub(crate) async fn get_openshift_image_trusted_certs(in_memory_etcd_client: &Arc) -> Result> { + let mut pem_strings = vec![]; + + let image_config = get_etcd_json( + in_memory_etcd_client, + &(K8sResourceLocation::new(None, "Image", "cluster", "config.openshift.io")), + ) + .await + .context("getting image config")? + .context("image config not found")?; + + if let Some(additional_trusted_ca) = image_config.pointer("/spec/additionalTrustedCA/name") { + let user_image_ca_configmap = get_etcd_json( + in_memory_etcd_client, + &(K8sResourceLocation { + namespace: Some("openshift-config".into()), + kind: "ConfigMap".into(), + apiversion: "v1".into(), + name: additional_trusted_ca.as_str().context("must be string")?.into(), + }), + ) + .await + .context("getting user image ca configmap")? + .context("user image ca configmap not found")?; + + for (k, v) in user_image_ca_configmap + .pointer("/data") + .context("parsing registry-cas")? + .as_object() + .context("must be object")? + { + pem_strings.push(v.as_str().context(format!("must be string ({k})"))?.to_string()); + } + } + + Ok(pem_strings) +} + +pub(crate) async fn get_openshift_proxy_trusted_certs(in_memory_etcd_client: &Arc) -> Result { + let trusted_ca_bundle_configmap = get_etcd_json( + in_memory_etcd_client, + &(K8sResourceLocation { + namespace: Some("openshift-config-managed".into()), + kind: "ConfigMap".into(), + apiversion: "v1".into(), + name: "trusted-ca-bundle".into(), + }), + ) + .await + .context("getting trusted-ca-bundle")? + .context("trusted-ca-bundle not found")?; + + Ok(trusted_ca_bundle_configmap + .pointer("/data/ca-bundle.crt") + .context("parsing ca-bundle.crt")? + .as_str() + .context("must be string")? + .to_string()) +} + +/// MCO reads the user-ca-bundle from the openshift-config namespace directly regardless of whether +/// the Proxy CR points at it or not, so we should consider the certs in that configmap to be +/// external. +pub(crate) async fn get_openshift_user_ca_bundle(in_memory_etcd_client: &Arc) -> Result> { + let trusted_ca_bundle_configmap = get_etcd_json( + in_memory_etcd_client, + &(K8sResourceLocation { + namespace: Some("openshift-config".into()), + kind: "ConfigMap".into(), + apiversion: "v1".into(), + name: "user-ca-bundle".into(), + }), + ) + .await + .context("getting trusted-ca-bundle")?; + + match trusted_ca_bundle_configmap { + None => Ok(None), + Some(trusted_ca_bundle_configmap) => Ok(Some( + trusted_ca_bundle_configmap + .pointer("/data/ca-bundle.crt") + .context("parsing ca-bundle.crt")? + .as_str() + .context("must be string")? + .to_string(), + )), + } +} diff --git a/src/recert.rs b/src/recert.rs index c5c99cf1..3289a803 100644 --- a/src/recert.rs +++ b/src/recert.rs @@ -63,7 +63,7 @@ async fn recertify( crypto_customizations: &CryptoCustomizations, ) -> Result { let external_certs = if in_memory_etcd_client.etcd_client.is_some() { - scanning::discover_external_certs(Arc::clone(&in_memory_etcd_client)) + scanning::external_certs::discover_external_certs(Arc::clone(&in_memory_etcd_client)) .await .context("discovering external certs to ignore")? } else {