Skip to content

Commit d45f8fb

Browse files
committed
wip: add run-securityadmin job
1 parent 560695b commit d45f8fb

4 files changed

Lines changed: 204 additions & 1 deletion

File tree

rust/operator-binary/src/controller.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ use build::build;
55
use snafu::{ResultExt, Snafu};
66
use stackable_operator::{
77
cluster_resources::ClusterResourceApplyStrategy,
8-
commons::{affinity::StackableAffinity, product_image_selection::ProductImage},
8+
commons::{
9+
affinity::StackableAffinity, networking::DomainName, product_image_selection::ProductImage,
10+
},
911
crd::listener::v1alpha1::Listener,
1012
k8s_openapi::api::{
1113
apps::v1::StatefulSet,
14+
batch::v1::Job,
1215
core::v1::{ConfigMap, Service, ServiceAccount},
1316
policy::v1::PodDisruptionBudget,
1417
rbac::v1::RoleBinding,
@@ -43,6 +46,7 @@ pub struct ContextNames {
4346
pub product_name: ProductName,
4447
pub operator_name: OperatorName,
4548
pub controller_name: ControllerName,
49+
pub cluster_domain_name: DomainName,
4650
}
4751

4852
pub struct Context {
@@ -52,6 +56,7 @@ pub struct Context {
5256

5357
impl Context {
5458
pub fn new(client: stackable_operator::client::Client, operator_name: OperatorName) -> Self {
59+
let cluster_domain_name = client.kubernetes_cluster_info.cluster_domain.clone();
5560
Context {
5661
client,
5762
names: ContextNames {
@@ -60,6 +65,7 @@ impl Context {
6065
operator_name,
6166
controller_name: ControllerName::from_str("opensearchcluster")
6267
.expect("should be a valid controller name"),
68+
cluster_domain_name,
6369
},
6470
}
6571
}
@@ -282,5 +288,6 @@ struct KubernetesResources<T> {
282288
service_accounts: Vec<ServiceAccount>,
283289
role_bindings: Vec<RoleBinding>,
284290
pod_disruption_budgets: Vec<PodDisruptionBudget>,
291+
jobs: Vec<Job>,
285292
status: PhantomData<T>,
286293
}

rust/operator-binary/src/controller/apply.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ impl<'a> Applier<'a> {
7272

7373
let pod_disruption_budgets = self.add_resources(resources.pod_disruption_budgets).await?;
7474

75+
let jobs = self.add_resources(resources.jobs).await?;
76+
7577
self.cluster_resources
7678
.delete_orphaned_resources(self.client)
7779
.await
@@ -85,6 +87,7 @@ impl<'a> Applier<'a> {
8587
service_accounts,
8688
role_bindings,
8789
pod_disruption_budgets,
90+
jobs,
8891
status: PhantomData,
8992
})
9093
}

rust/operator-binary/src/controller/build.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use std::marker::PhantomData;
22

33
use role_builder::RoleBuilder;
44

5+
use crate::controller::build::job_builder::JobBuilder;
6+
57
use super::{ContextNames, KubernetesResources, Prepared, ValidatedCluster};
68

9+
pub mod job_builder;
710
pub mod node_config;
811
pub mod role_builder;
912
pub mod role_group_builder;
@@ -13,8 +16,10 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResou
1316
let mut stateful_sets = vec![];
1417
let mut services = vec![];
1518
let mut listeners = vec![];
19+
let mut jobs = vec![];
1620

1721
let role_builder = RoleBuilder::new(cluster.clone(), names);
22+
let job_builder = JobBuilder::new(cluster.clone(), names);
1823

1924
for role_group_builder in role_builder.role_group_builders() {
2025
config_maps.push(role_group_builder.build_config_map());
@@ -32,6 +37,8 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResou
3237

3338
let pod_disruption_budgets = role_builder.build_pdb().into_iter().collect();
3439

40+
jobs.push(job_builder.build_run_securityadmin_job());
41+
3542
KubernetesResources {
3643
stateful_sets,
3744
services,
@@ -40,6 +47,7 @@ pub fn build(names: &ContextNames, cluster: ValidatedCluster) -> KubernetesResou
4047
service_accounts,
4148
role_bindings,
4249
pod_disruption_budgets,
50+
jobs,
4351
status: PhantomData,
4452
}
4553
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use stackable_operator::{
2+
builder::{
3+
meta::ObjectMetaBuilder,
4+
pod::{container::ContainerBuilder, resources::ResourceRequirementsBuilder},
5+
},
6+
k8s_openapi::api::{
7+
batch::v1::{Job, JobSpec},
8+
core::v1::{
9+
PodSecurityContext, PodSpec, PodTemplateSpec, SecretVolumeSource, Volume, VolumeMount,
10+
},
11+
},
12+
kube::api::ObjectMeta,
13+
kvp::{
14+
Label, Labels,
15+
consts::{STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE},
16+
},
17+
};
18+
19+
use crate::{
20+
controller::{ContextNames, ValidatedCluster},
21+
framework::{
22+
IsLabelValue, builder::meta::ownerreference_from_resource, role_utils::ResourceNames,
23+
},
24+
};
25+
26+
const RUN_SECURITYADMIN_CERT_VOLUME_NAME: &str = "tls";
27+
const RUN_SECURITYADMIN_CERT_VOLUME_MOUNT: &str = "/stackable/cert";
28+
const SECURITY_CONFIG_VOLUME_NAME: &str = "security-config";
29+
const SECURITY_CONFIG_VOLUME_MOUNT: &str = "/stackable/opensearch/config/opensearch-security";
30+
const RUN_SECURITYADMIN_CONTAINER_NAME: &str = "run-securityadmin";
31+
32+
pub struct JobBuilder<'a> {
33+
cluster: ValidatedCluster,
34+
context_names: &'a ContextNames,
35+
resource_names: ResourceNames,
36+
}
37+
38+
impl<'a> JobBuilder<'a> {
39+
pub fn new(cluster: ValidatedCluster, context_names: &'a ContextNames) -> JobBuilder<'a> {
40+
JobBuilder {
41+
cluster: cluster.clone(),
42+
context_names,
43+
resource_names: ResourceNames {
44+
cluster_name: cluster.name.clone(),
45+
product_name: context_names.product_name.clone(),
46+
},
47+
}
48+
}
49+
50+
pub fn build_run_securityadmin_job(&self) -> Job {
51+
let product_image = self
52+
.cluster
53+
.image
54+
.resolve("opensearch", crate::built_info::PKG_VERSION);
55+
// Maybe add a suffix for consecutive
56+
let metadata = self.common_metadata(format!(
57+
"{}-run-securityadmin",
58+
self.resource_names.cluster_name,
59+
));
60+
61+
let args = [
62+
"plugins/opensearch-security/tools/securityadmin.sh".to_string(),
63+
"-cacert".to_string(),
64+
"config/tls-client/ca.crt".to_string(),
65+
"-cert".to_string(),
66+
"config/tls-client/tls.crt".to_string(),
67+
"-key".to_string(),
68+
"config/tls-client/tls.key".to_string(),
69+
"--hostname".to_string(),
70+
self.opensearch_master_fqdn(),
71+
"--configdir".to_string(),
72+
"config/opensearch-security/".to_string(),
73+
];
74+
let mut cb = ContainerBuilder::new(RUN_SECURITYADMIN_CONTAINER_NAME)
75+
.expect("should be a valid container name");
76+
let container = cb
77+
.image_from_product_image(&product_image)
78+
.command(vec!["sh".to_string(), "-c".to_string()])
79+
.args(vec![args.join(" ")])
80+
// The VolumeMount for the secret operator key store certificates
81+
.add_volume_mounts([
82+
VolumeMount {
83+
mount_path: RUN_SECURITYADMIN_CERT_VOLUME_MOUNT.to_owned(),
84+
name: RUN_SECURITYADMIN_CERT_VOLUME_NAME.to_owned(),
85+
..VolumeMount::default()
86+
},
87+
VolumeMount {
88+
mount_path: SECURITY_CONFIG_VOLUME_MOUNT.to_owned(),
89+
name: SECURITY_CONFIG_VOLUME_NAME.to_owned(),
90+
..VolumeMount::default()
91+
},
92+
])
93+
.expect("the mount paths are statically defined and there should be no duplicates")
94+
.resources(
95+
ResourceRequirementsBuilder::new()
96+
.with_cpu_request("100m")
97+
.with_cpu_limit("400m")
98+
.with_memory_request("128Mi")
99+
.with_memory_limit("512Mi")
100+
.build(),
101+
)
102+
.build();
103+
104+
let pod_template = PodTemplateSpec {
105+
metadata: Some(metadata.clone()),
106+
spec: Some(PodSpec {
107+
containers: vec![container],
108+
109+
security_context: Some(PodSecurityContext {
110+
fs_group: Some(1000),
111+
..PodSecurityContext::default()
112+
}),
113+
service_account_name: Some(self.resource_names.service_account_name()),
114+
volumes: Some(vec![Volume {
115+
name: SECURITY_CONFIG_VOLUME_NAME.to_owned(),
116+
secret: Some(SecretVolumeSource {
117+
secret_name: Some("opensearch-security-config".to_string()),
118+
..Default::default()
119+
}),
120+
..Volume::default()
121+
}]),
122+
..PodSpec::default()
123+
}),
124+
};
125+
126+
Job {
127+
metadata,
128+
spec: Some(JobSpec {
129+
backoff_limit: Some(100),
130+
ttl_seconds_after_finished: Some(120),
131+
template: pod_template,
132+
..JobSpec::default()
133+
}),
134+
..Job::default()
135+
}
136+
}
137+
138+
fn opensearch_master_fqdn(&self) -> String {
139+
let cluster_manager_service_name = self.resource_names.discovery_service_name();
140+
let namespace = &self.cluster.namespace;
141+
let cluster_domain = &self.context_names.cluster_domain_name;
142+
format!("{cluster_manager_service_name}.{namespace}.svc.{cluster_domain}")
143+
}
144+
145+
fn common_metadata(&self, resource_name: impl Into<String>) -> ObjectMeta {
146+
ObjectMetaBuilder::new()
147+
.name(resource_name)
148+
.namespace(&self.cluster.namespace)
149+
.ownerreference(ownerreference_from_resource(
150+
&self.cluster,
151+
None,
152+
Some(true),
153+
))
154+
.with_labels(self.labels())
155+
.build()
156+
}
157+
158+
/// Labels on role resources
159+
fn labels(&self) -> Labels {
160+
// Well-known Kubernetes labels
161+
let mut labels = Labels::role_selector(
162+
&self.cluster,
163+
&self.context_names.product_name.to_label_value(),
164+
&ValidatedCluster::role_name().to_label_value(),
165+
)
166+
.unwrap();
167+
168+
let managed_by = Label::managed_by(
169+
&self.context_names.operator_name.to_string(),
170+
&self.context_names.controller_name.to_string(),
171+
)
172+
.unwrap();
173+
let version = Label::version(&self.cluster.product_version.to_string()).unwrap();
174+
175+
labels.insert(managed_by);
176+
labels.insert(version);
177+
178+
// Stackable-specific labels
179+
labels
180+
.parse_insert((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE))
181+
.unwrap();
182+
183+
labels
184+
}
185+
}

0 commit comments

Comments
 (0)