Skip to content

Commit

Permalink
feat: Certificate Authorities to wasmcloud host (#72)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Lucas Fontes <lucas@cosmonic.com>
  • Loading branch information
lxfontes committed Jul 17, 2024
1 parent 7c70946 commit 97f0c12
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 17 deletions.
9 changes: 8 additions & 1 deletion crates/types/src/v1alpha1/wasmcloud_host_config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use k8s_openapi::api::core::v1::{PodSpec, ResourceRequirements};
use k8s_openapi::api::core::v1::{PodSpec, ResourceRequirements, Volume};
use kube::CustomResource;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -78,6 +78,8 @@ pub struct WasmCloudHostConfigSpec {
pub scheduling_options: Option<KubernetesSchedulingOptions>,
/// Observability options for configuring the OpenTelemetry integration
pub observability: Option<ObservabilityConfiguration>,
/// Certificates: Authorities, client certificates
pub certificates: Option<WasmCloudHostCertificates>,
}

#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
Expand Down Expand Up @@ -187,6 +189,11 @@ fn default_nats_leafnode_port() -> u16 {
7422
}

#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
pub struct WasmCloudHostCertificates {
pub authorities: Option<Vec<Volume>>,
}

#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
pub struct WasmCloudHostConfigResources {
pub nats: Option<ResourceRequirements>,
Expand Down
177 changes: 161 additions & 16 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ async fn cleanup(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Result<Acti
Ok(Action::await_change())
}

fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplateSpec {
async fn pod_template(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Result<PodTemplateSpec> {
let labels = pod_labels(config);

let mut wasmcloud_env = vec![
Expand Down Expand Up @@ -373,7 +373,7 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
}
}

let wasmcloud_args = configure_observability(&config.spec);
let mut wasmcloud_args = configure_observability(&config.spec);

let mut nats_resources: Option<k8s_openapi::api::core::v1::ResourceRequirements> = None;
let mut wasmcloud_resources: Option<k8s_openapi::api::core::v1::ResourceRequirements> = None;
Expand Down Expand Up @@ -403,14 +403,111 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
..Default::default()
}];

let mut volume_mounts = vec![VolumeMount {
let mut nats_volume_mounts = vec![VolumeMount {
name: "nats-config".to_string(),
mount_path: "/nats/nats.conf".to_string(),
read_only: Some(true),
sub_path: Some("nats.conf".to_string()),
..Default::default()
}];

let mut wasm_volume_mounts: Vec<VolumeMount> = vec![];
if let Some(authorities) = &config
.spec
.certificates
.clone()
.and_then(|certificates| certificates.authorities)
{
for authority in authorities.iter() {
let authority_name = authority.name.clone();
let volume_name = format!("ca-{authority_name}");
let volume_path = format!("/wasmcloud/certificates/{volume_name}");
let mut items: Vec<String> = vec![];

// The Volume interface is quite broad. Permit only known types.
if authority.secret.is_none() && authority.config_map.is_none() {
return Err(Error::CertificateError(format!(
"'{authority_name}' has to be a Configmap or Secret"
)));
}

// secrets
if let Some(secret_ref) = &authority.secret {
let secret_name = match &secret_ref.secret_name {
Some(s) => s,
None => {
return Err(Error::CertificateError(format!(
"Missing secret name for authority '{authority_name}'"
)))
}
};

items = discover_secret_certificates(
&config.namespace().unwrap_or_default(),
secret_name,
&ctx,
)
.await?;

if items.is_empty() {
continue;
}

volumes.push(Volume {
name: volume_name.clone(),
secret: Some(SecretVolumeSource {
secret_name: Some(secret_name.to_string()),
..Default::default()
}),
..Default::default()
});
}

// configmaps
if let Some(configmap_ref) = &authority.config_map {
let configmap_name = match &configmap_ref.name {
Some(s) => s,
None => {
return Err(Error::CertificateError(format!(
"Missing configmap name for authority '{authority_name}'"
)))
}
};
items = discover_configmap_certificates(
&config.namespace().unwrap_or_default(),
configmap_name,
&ctx,
)
.await?;

if items.is_empty() {
continue;
}

volumes.push(Volume {
name: volume_name.clone(),
config_map: Some(ConfigMapVolumeSource {
name: Some(configmap_name.to_string()),
..Default::default()
}),
..Default::default()
});
}

wasm_volume_mounts.push(VolumeMount {
name: volume_name.clone(),
read_only: Some(true),
mount_path: volume_path.clone(),
..Default::default()
});

for item in items {
wasmcloud_args.push("--tls-ca-path".to_string());
wasmcloud_args.push(format!("{volume_path}/{item}"));
}
}
}

if let Some(secret_name) = &config.spec.secret_name {
volumes.push(Volume {
name: "nats-creds".to_string(),
Expand All @@ -421,7 +518,7 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
..Default::default()
});

volume_mounts.push(VolumeMount {
nats_volume_mounts.push(VolumeMount {
name: "nats-creds".to_string(),
mount_path: "/nats/nats.creds".to_string(),
sub_path: Some("nats.creds".to_string()),
Expand Down Expand Up @@ -455,7 +552,7 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
..Default::default()
}),
resources: nats_resources,
volume_mounts: Some(volume_mounts),
volume_mounts: Some(nats_volume_mounts),
..Default::default()
},
Container {
Expand All @@ -465,6 +562,7 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
args: Some(wasmcloud_args),
env: Some(wasmcloud_env),
resources: wasmcloud_resources,
volume_mounts: Some(wasm_volume_mounts),
..Default::default()
},
];
Expand Down Expand Up @@ -495,7 +593,51 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
template.spec = Some(overrides);
}
};
template
Ok(template)
}

async fn discover_configmap_certificates(
namespace: &str,
name: &str,
ctx: &Context,
) -> Result<Vec<String>> {
let kube_client = ctx.client.clone();
let api = Api::<ConfigMap>::namespaced(kube_client, namespace);
let mut certs = Vec::new();

let raw_config_map = api.get(name).await?;

if let Some(raw_data) = raw_config_map.data {
for (key, _) in raw_data {
if key.ends_with(".crt") {
certs.push(key)
}
}
}

Ok(certs)
}

async fn discover_secret_certificates(
namespace: &str,
name: &str,
ctx: &Context,
) -> Result<Vec<String>> {
let kube_client = ctx.client.clone();
let api = Api::<Secret>::namespaced(kube_client, namespace);
let mut certs = Vec::new();

let raw_config_map = api.get(name).await?;

if let Some(raw_data) = raw_config_map.data {
for (key, _) in raw_data {
if key.ends_with(".crt") {
certs.push(key)
}
}
}

Ok(certs)
}

fn configure_observability(spec: &WasmCloudHostConfigSpec) -> Vec<String> {
Expand Down Expand Up @@ -543,19 +685,22 @@ fn configure_observability(spec: &WasmCloudHostConfigSpec) -> Vec<String> {
args
}

fn deployment_spec(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> DeploymentSpec {
let pod_template = pod_template(config, ctx);
async fn deployment_spec(
config: &WasmCloudHostConfig,
ctx: Arc<Context>,
) -> Result<DeploymentSpec> {
let pod_template = pod_template(config, ctx).await?;
let ls = LabelSelector {
match_labels: Some(selector_labels(config)),
..Default::default()
};

DeploymentSpec {
Ok(DeploymentSpec {
replicas: Some(config.spec.host_replicas as i32),
selector: ls,
template: pod_template,
..Default::default()
}
})
}

fn pod_labels(config: &WasmCloudHostConfig) -> BTreeMap<String, String> {
Expand All @@ -582,18 +727,18 @@ fn selector_labels(config: &WasmCloudHostConfig) -> BTreeMap<String, String> {
labels
}

fn daemonset_spec(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> DaemonSetSpec {
let pod_template = pod_template(config, ctx);
async fn daemonset_spec(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Result<DaemonSetSpec> {
let pod_template = pod_template(config, ctx).await?;
let ls = LabelSelector {
match_labels: Some(selector_labels(config)),
..Default::default()
};

DaemonSetSpec {
Ok(DaemonSetSpec {
selector: ls,
template: pod_template,
..Default::default()
}
})
}

async fn configure_hosts(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Result<()> {
Expand Down Expand Up @@ -665,7 +810,7 @@ async fn configure_hosts(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Res

if let Some(scheduling_options) = &config.spec.scheduling_options {
if scheduling_options.daemonset {
let mut spec = daemonset_spec(config, ctx.clone());
let mut spec = daemonset_spec(config, ctx.clone()).await?;
spec.template.spec.as_mut().unwrap().containers[1]
.env
.as_mut()
Expand Down Expand Up @@ -693,7 +838,7 @@ async fn configure_hosts(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Res
.await?;
}
} else {
let mut spec = deployment_spec(config, ctx.clone());
let mut spec = deployment_spec(config, ctx.clone()).await?;
spec.template.spec.as_mut().unwrap().containers[1]
.env
.as_mut()
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub enum Error {
#[error("Error retrieving secrets: {0}")]
SecretError(String),

#[error("Certificate error: {0}")]
CertificateError(String),

#[error("Error rendering template: {0}")]
RenderError(#[from] RenderError),
}
Expand Down

0 comments on commit 97f0c12

Please sign in to comment.