Skip to content

Commit 99b2e30

Browse files
committed
Initial prototype for cert-manager integration
See #474
1 parent eb757ef commit 99b2e30

File tree

15 files changed

+311
-25
lines changed

15 files changed

+311
-25
lines changed

deploy/helm/secret-operator/templates/roles.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ rules:
6666
- podlisteners
6767
verbs:
6868
- get
69+
- apiGroups:
70+
- cert-manager.io
71+
resources:
72+
- certificates
73+
verbs:
74+
- patch
75+
- create
6976
- apiGroups:
7077
- security.openshift.io
7178
resourceNames:

rust/operator-binary/src/backend/dynamic.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,14 @@ pub async fn from_class(
111111
class: SecretClass,
112112
) -> Result<Box<Dynamic>, FromClassError> {
113113
Ok(match class.spec.backend {
114-
crd::SecretClassBackend::K8sSearch(crd::K8sSearchBackend { search_namespace }) => {
115-
from(super::K8sSearch {
116-
client: Unloggable(client.clone()),
117-
search_namespace,
118-
})
119-
}
114+
crd::SecretClassBackend::K8sSearch(crd::K8sSearchBackend {
115+
search_namespace,
116+
cert_manager_issuer,
117+
}) => from(super::K8sSearch {
118+
client: Unloggable(client.clone()),
119+
search_namespace,
120+
cert_manager_issuer,
121+
}),
120122
crd::SecretClassBackend::AutoTls(crd::AutoTlsBackend {
121123
ca,
122124
max_certificate_lifetime,

rust/operator-binary/src/backend/k8s_search.rs

+85-19
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@ use stackable_operator::{
88
k8s_openapi::{
99
api::core::v1::Secret, apimachinery::pkg::apis::meta::v1::LabelSelector, ByteString,
1010
},
11-
kube::api::ListParams,
11+
kube::api::{ListParams, ObjectMeta},
1212
kvp::{LabelError, LabelSelectorExt, Labels},
1313
};
1414

15-
use crate::{crd::SearchNamespace, format::SecretData, utils::Unloggable};
15+
use crate::{
16+
cert_manager,
17+
crd::{CertManagerIssuer, SearchNamespace},
18+
format::SecretData,
19+
utils::Unloggable,
20+
};
1621

1722
use super::{
18-
pod_info::{PodInfo, SchedulingPodInfo},
23+
pod_info::{Address, PodInfo, SchedulingPodInfo},
1924
scope::SecretScope,
20-
SecretBackend, SecretBackendError, SecretContents, SecretVolumeSelector,
25+
ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents, SecretVolumeSelector,
2126
};
2227

2328
const LABEL_CLASS: &str = "secrets.stackable.tech/class";
@@ -28,6 +33,17 @@ const LABEL_SCOPE_LISTENER: &str = "secrets.stackable.tech/listener";
2833

2934
#[derive(Debug, Snafu)]
3035
pub enum Error {
36+
#[snafu(display("failed to get addresses for scope {scope}"))]
37+
ScopeAddresses {
38+
source: ScopeAddressesError,
39+
scope: SecretScope,
40+
},
41+
42+
#[snafu(display("failed to apply cert-manager Certificate for volume"))]
43+
ApplyCertManagerCertificate {
44+
source: stackable_operator::client::Error,
45+
},
46+
3147
#[snafu(display("failed to build Secret selector"))]
3248
SecretSelector {
3349
source: stackable_operator::kvp::SelectorError,
@@ -51,8 +67,10 @@ pub enum Error {
5167
impl SecretBackendError for Error {
5268
fn grpc_code(&self) -> tonic::Code {
5369
match self {
70+
Error::ScopeAddresses { .. } => tonic::Code::Unavailable,
5471
Error::SecretSelector { .. } => tonic::Code::FailedPrecondition,
5572
Error::SecretQuery { .. } => tonic::Code::FailedPrecondition,
73+
Error::ApplyCertManagerCertificate { .. } => tonic::Code::Unavailable,
5674
Error::NoSecret { .. } => tonic::Code::FailedPrecondition,
5775
Error::NoListener { .. } => tonic::Code::FailedPrecondition,
5876
Error::BuildLabel { .. } => tonic::Code::FailedPrecondition,
@@ -65,6 +83,7 @@ pub struct K8sSearch {
6583
// Not secret per se, but isn't Debug: https://github.com/stackabletech/secret-operator/issues/411
6684
pub client: Unloggable<stackable_operator::client::Client>,
6785
pub search_namespace: SearchNamespace,
86+
pub cert_manager_issuer: Option<CertManagerIssuer>,
6887
}
6988

7089
impl K8sSearch {
@@ -85,8 +104,53 @@ impl SecretBackend for K8sSearch {
85104
selector: &SecretVolumeSelector,
86105
pod_info: PodInfo,
87106
) -> Result<SecretContents, Self::Error> {
88-
let label_selector =
89-
build_label_selector_query(selector, LabelSelectorPodInfo::Scheduled(&pod_info))?;
107+
let labels = build_selector_labels(selector, LabelSelectorPodInfo::Scheduled(&pod_info))?;
108+
if let Some(cert_manager_issuer) = &self.cert_manager_issuer {
109+
let cert_name = &selector.pod;
110+
let mut dns_names = Vec::new();
111+
let mut ip_addresses = Vec::new();
112+
for scope in &selector.scope {
113+
for address in selector
114+
.scope_addresses(&pod_info, scope)
115+
.context(ScopeAddressesSnafu { scope })?
116+
{
117+
match address {
118+
Address::Dns(name) => dns_names.push(name),
119+
Address::Ip(addr) => ip_addresses.push(addr.to_string()),
120+
}
121+
}
122+
}
123+
let cert = cert_manager::Certificate {
124+
metadata: ObjectMeta {
125+
name: Some(cert_name.clone()),
126+
namespace: Some(self.search_ns_for_pod(selector).to_string()),
127+
..Default::default()
128+
},
129+
spec: cert_manager::CertificateSpec {
130+
secret_name: cert_name.clone(),
131+
secret_template: cert_manager::SecretTemplate {
132+
annotations: BTreeMap::new(),
133+
labels: labels.clone().into(),
134+
},
135+
dns_names,
136+
ip_addresses,
137+
issuer_ref: cert_manager::IssuerRef {
138+
name: cert_manager_issuer.name.clone(),
139+
kind: cert_manager_issuer.kind,
140+
},
141+
},
142+
};
143+
self.client
144+
.apply_patch("k8s-search-cert-manager", &cert, &cert)
145+
.await
146+
.context(ApplyCertManagerCertificateSnafu)?;
147+
}
148+
let label_selector = LabelSelector {
149+
match_expressions: None,
150+
match_labels: Some(labels.into()),
151+
}
152+
.to_query_string()
153+
.context(SecretSelectorSnafu)?;
90154
let secret = self
91155
.client
92156
.list::<Secret>(
@@ -113,9 +177,18 @@ impl SecretBackend for K8sSearch {
113177
selector: &SecretVolumeSelector,
114178
pod_info: SchedulingPodInfo,
115179
) -> Result<Option<HashSet<String>>, Self::Error> {
116-
if pod_info.has_node_scope {
117-
let label_selector =
118-
build_label_selector_query(selector, LabelSelectorPodInfo::Scheduling(&pod_info))?;
180+
if pod_info.has_node_scope
181+
// FIXME: how should node selection interact with cert manager?
182+
&& self.cert_manager_issuer.is_none()
183+
{
184+
let labels =
185+
build_selector_labels(selector, LabelSelectorPodInfo::Scheduling(&pod_info))?;
186+
let label_selector = LabelSelector {
187+
match_expressions: None,
188+
match_labels: Some(labels.into()),
189+
}
190+
.to_query_string()
191+
.context(SecretSelectorSnafu)?;
119192
Ok(Some(
120193
self.client
121194
.list::<Secret>(
@@ -139,10 +212,10 @@ enum LabelSelectorPodInfo<'a> {
139212
Scheduled(&'a PodInfo),
140213
}
141214

142-
fn build_label_selector_query(
215+
fn build_selector_labels(
143216
vol_selector: &SecretVolumeSelector,
144217
pod_info: LabelSelectorPodInfo,
145-
) -> Result<String, Error> {
218+
) -> Result<Labels, Error> {
146219
let mut labels: Labels =
147220
BTreeMap::from([(LABEL_CLASS.to_string(), vol_selector.class.to_string())])
148221
.try_into()
@@ -195,12 +268,5 @@ fn build_label_selector_query(
195268
}
196269
}
197270
}
198-
let label_selector = LabelSelector {
199-
match_expressions: None,
200-
match_labels: Some(labels.into()),
201-
};
202-
203-
label_selector
204-
.to_query_string()
205-
.context(SecretSelectorSnafu)
271+
Ok(labels)
206272
}
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::collections::BTreeMap;
2+
3+
use serde::{Deserialize, Serialize};
4+
use stackable_operator::{
5+
kube::CustomResource,
6+
schemars::{self, JsonSchema},
7+
};
8+
9+
use crate::crd::CertManagerIssuerKind;
10+
11+
#[derive(CustomResource, Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12+
#[kube(
13+
group = "cert-manager.io",
14+
version = "v1",
15+
kind = "Certificate",
16+
namespaced,
17+
crates(
18+
kube_core = "stackable_operator::kube::core",
19+
k8s_openapi = "stackable_operator::k8s_openapi",
20+
schemars = "stackable_operator::schemars"
21+
)
22+
)]
23+
#[serde(rename_all = "camelCase")]
24+
pub struct CertificateSpec {
25+
pub secret_name: String,
26+
pub secret_template: SecretTemplate,
27+
pub dns_names: Vec<String>,
28+
pub ip_addresses: Vec<String>,
29+
pub issuer_ref: IssuerRef,
30+
}
31+
32+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
33+
#[serde(rename_all = "camelCase")]
34+
pub struct SecretTemplate {
35+
pub annotations: BTreeMap<String, String>,
36+
pub labels: BTreeMap<String, String>,
37+
}
38+
39+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
40+
#[serde(rename_all = "camelCase")]
41+
pub struct IssuerRef {
42+
pub name: String,
43+
pub kind: CertManagerIssuerKind,
44+
}

rust/operator-binary/src/crd.rs

+15
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub enum SecretClassBackend {
5858
pub struct K8sSearchBackend {
5959
/// Configures the namespace searched for Secret objects.
6060
pub search_namespace: SearchNamespace,
61+
62+
pub cert_manager_issuer: Option<CertManagerIssuer>,
6163
}
6264

6365
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -72,6 +74,19 @@ pub enum SearchNamespace {
7274
Name(String),
7375
}
7476

77+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
78+
#[serde(rename_all = "camelCase")]
79+
pub struct CertManagerIssuer {
80+
pub kind: CertManagerIssuerKind,
81+
pub name: String,
82+
}
83+
84+
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, JsonSchema)]
85+
pub enum CertManagerIssuerKind {
86+
Issuer,
87+
ClusterIssuer,
88+
}
89+
7590
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
7691
#[serde(rename_all = "camelCase")]
7792
pub struct AutoTlsBackend {

rust/operator-binary/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use tonic::transport::Server;
1616
use utils::{uds_bind_private, TonicUnixStream};
1717

1818
mod backend;
19+
mod cert_manager;
1920
mod crd;
2021
mod csi_server;
2122
mod format;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% if test_scenario['values']['openshift'] == 'true' %}
2+
# see https://github.com/stackabletech/issues/issues/566
3+
---
4+
apiVersion: kuttl.dev/v1beta1
5+
kind: TestStep
6+
commands:
7+
- script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}'
8+
timeout: 120
9+
{% endif %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
apiVersion: cert-manager.io/v1
3+
kind: Issuer
4+
metadata:
5+
name: secret-operator-demonstration
6+
spec:
7+
ca:
8+
secretName: secret-operator-demonstration-ca
9+
---
10+
apiVersion: cert-manager.io/v1
11+
kind: Certificate
12+
metadata:
13+
name: secret-operator-demonstration-ca
14+
spec:
15+
secretName: secret-operator-demonstration-ca
16+
isCA: true
17+
commonName: Stackable Secret Operator/Cert-Manager Demonstration CA
18+
issuerRef:
19+
kind: Issuer
20+
name: secret-operator-demonstration-ca
21+
---
22+
apiVersion: cert-manager.io/v1
23+
kind: Issuer
24+
metadata:
25+
name: secret-operator-demonstration-ca
26+
spec:
27+
selfSigned: {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
apiVersion: kuttl.dev/v1beta1
3+
kind: TestStep
4+
commands:
5+
- script: envsubst '$NAMESPACE' < secretclass.yaml | kubectl apply -f -
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
kind: Role
3+
apiVersion: rbac.authorization.k8s.io/v1
4+
metadata:
5+
name: use-integration-tests-scc
6+
rules:
7+
{% if test_scenario['values']['openshift'] == "true" %}
8+
- apiGroups: ["security.openshift.io"]
9+
resources: ["securitycontextconstraints"]
10+
resourceNames: ["privileged"]
11+
verbs: ["use"]
12+
{% endif %}
13+
---
14+
apiVersion: v1
15+
kind: ServiceAccount
16+
metadata:
17+
name: integration-tests-sa
18+
---
19+
kind: RoleBinding
20+
apiVersion: rbac.authorization.k8s.io/v1
21+
metadata:
22+
name: use-integration-tests-scc
23+
subjects:
24+
- kind: ServiceAccount
25+
name: integration-tests-sa
26+
roleRef:
27+
kind: Role
28+
name: use-integration-tests-scc
29+
apiGroup: rbac.authorization.k8s.io
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
apiVersion: kuttl.dev/v1beta1
3+
kind: TestAssert
4+
timeout: 300
5+
---
6+
apiVersion: batch/v1
7+
kind: Job
8+
metadata:
9+
name: tls-consumer
10+
status:
11+
succeeded: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
apiVersion: kuttl.dev/v1beta1
3+
kind: TestStep
4+
commands:
5+
- script: envsubst '$NAMESPACE' < consumer.yaml | kubectl apply -n $NAMESPACE -f -

0 commit comments

Comments
 (0)