Skip to content

[Merged by Bors] - Add common datastructures shared between the operators #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,12 +7,14 @@ All notable changes to this project will be documented in this file.
### Added

- Export logs to Jaeger ([#360]).
- Added common datastructures shared between all operators like `Tls` oder `AuthenticationClass` ([#366]).

### Changed

- BREAKING: `initialize_logging` now takes an app name and tracing target ([#360]).

[#360]: https://github.com/stackabletech/operator-rs/pull/360
[#366]: https://github.com/stackabletech/operator-rs/pull/366

## [0.15.0] - 2022.03.21

29 changes: 29 additions & 0 deletions src/commons/authentication.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use serde::{Deserialize, Serialize};

use crate::commons::ldap::LdapAuthenticationProvider;
use kube::CustomResource;
use schemars::JsonSchema;

#[derive(Clone, CustomResource, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[kube(
group = "authentication.stackable.tech",
version = "v1alpha1",
kind = "AuthenticationClass",
plural = "authenticationclasses",
crates(
kube_core = "kube::core",
k8s_openapi = "k8s_openapi",
schemars = "schemars"
)
)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticationClassSpec {
/// Provider used for authentication like LDAP or Kerberos
pub provider: AuthenticationClassProvider,
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum AuthenticationClassProvider {
Ldap(LdapAuthenticationProvider),
}
89 changes: 89 additions & 0 deletions src/commons/ldap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::commons::secret_class::SecretClassVolume;
use crate::commons::tls::Tls;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LdapAuthenticationProvider {
/// Hostname of the LDAP server
pub hostname: String,
/// Port of the LDAP server. If TLS is used defaults to 636 otherwise to 389
pub port: Option<u16>,
/// LDAP search base
#[serde(default)]
pub search_base: String,
/// LDAP query to filter users
#[serde(default)]
pub search_filter: String,
/// The name of the LDAP object fields
#[serde(default)]
pub ldap_field_names: LdapFieldNames,
/// In case you need a special account for searching the LDAP server you can specify it here
pub bind_credentials: Option<SecretClassVolume>,
/// Use a TLS connection. If not specified no TLS will be used
pub tls: Option<Tls>,
}

impl LdapAuthenticationProvider {
pub fn default_port(&self) -> u16 {
match self.tls {
None => 389,
Some(_) => 636,
}
}
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LdapFieldNames {
/// The name of the username field
#[serde(default = "LdapFieldNames::default_uid")]
pub uid: String,
/// The name of the group field
#[serde(default = "LdapFieldNames::default_group")]
pub group: String,
/// The name of the firstname field
#[serde(default = "LdapFieldNames::default_given_name")]
pub given_name: String,
/// The name of the lastname field
#[serde(default = "LdapFieldNames::default_surname")]
pub surname: String,
/// The name of the email field
#[serde(default = "LdapFieldNames::default_email")]
pub email: String,
}

impl LdapFieldNames {
fn default_uid() -> String {
"uid".to_string()
}

fn default_group() -> String {
"memberof".to_string()
}

fn default_given_name() -> String {
"givenName".to_string()
}

fn default_surname() -> String {
"sn".to_string()
}

fn default_email() -> String {
"mail".to_string()
}
}

impl Default for LdapFieldNames {
fn default() -> Self {
LdapFieldNames {
uid: Self::default_uid(),
group: Self::default_group(),
given_name: Self::default_given_name(),
surname: Self::default_surname(),
email: Self::default_email(),
}
}
}
6 changes: 6 additions & 0 deletions src/commons/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! This module provides common datastructures or CRDs shared between all the operators
pub mod authentication;
pub mod ldap;
pub mod secret_class;
pub mod tls;
80 changes: 80 additions & 0 deletions src/commons/secret_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::builder::SecretOperatorVolumeSourceBuilder;
use k8s_openapi::api::core::v1::CSIVolumeSource;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SecretClassVolume {
/// [SecretClass](https://docs.stackable.tech/secret-operator/secretclass.html) containing the LDAP bind credentials
pub secret_class: String,
/// [Scope](https://docs.stackable.tech/secret-operator/scope.html) of the [SecretClass](https://docs.stackable.tech/secret-operator/secretclass.html)
pub scope: Option<SecretClassVolumeScope>,
}

impl SecretClassVolume {
pub fn to_csi_volume(&self) -> CSIVolumeSource {
let mut secret_operator_volume_builder =
SecretOperatorVolumeSourceBuilder::new(&self.secret_class);

if let Some(scope) = &self.scope {
if scope.pod {
secret_operator_volume_builder.with_pod_scope();
}
if scope.node {
secret_operator_volume_builder.with_node_scope();
}
for service in &scope.services {
secret_operator_volume_builder.with_service_scope(service);
}
}

secret_operator_volume_builder.build()
}
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SecretClassVolumeScope {
#[serde(default)]
pub pod: bool,
#[serde(default)]
pub node: bool,
#[serde(default)]
pub services: Vec<String>,
}

#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;

#[test]
fn test_secret_class_volume_to_csi_volume() {
let secret_class_volume = SecretClassVolume {
secret_class: "myclass".to_string(), // pragma: allowlist secret
scope: Some(SecretClassVolumeScope {
pod: true,
node: false,
services: vec!["myservice".to_string()],
}),
}
.to_csi_volume();

let expected_volume_attributes = BTreeMap::from([
(
"secrets.stackable.tech/class".to_string(),
"myclass".to_string(),
),
(
"secrets.stackable.tech/scope".to_string(),
"pod,service=myservice".to_string(),
),
]);

assert_eq!(
expected_volume_attributes,
secret_class_volume.volume_attributes.unwrap()
);
}
}
45 changes: 45 additions & 0 deletions src/commons/tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Tls {
/// The verification method used to verify the certificates of the server and/or the client
pub verification: TlsVerification,
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TlsVerification {
/// Use TLS but don't verify certificates
None {},
/// Use TLS and ca certificate to verify the server
Server(TlsServerVerification),
/// Use TLS and ca certificate to verify the server and the client
Mutual(TlsMutualVerification),
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TlsServerVerification {
/// Ca cert to verify the server
pub ca_cert: CaCert,
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TlsMutualVerification {
/// [SecretClass](https://docs.stackable.tech/secret-operator/secretclass.html) which will provide ca.crt, tls.crt and tls.key
pub cert_secret_class: String,
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum CaCert {
/// Use TLS and the ca certificates trusted by the common web browsers to verify the server.
/// This can be useful when you e.g. use public AWS S3 or other public available services.
WebPki {},
/// Name of the SecretClass which will provide the ca cert.
/// Note that a SecretClass does not need to have a key but can also work with just a ca cert.
/// So if you got provided with a ca cert but don't have access to the key you can still use this method.
SecretClass(String),
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod builder;
pub mod cli;
pub mod client;
pub mod commons;
pub mod crd;
pub mod error;
pub mod label_selector;
4 changes: 2 additions & 2 deletions src/opa.rs
Original file line number Diff line number Diff line change
@@ -203,14 +203,14 @@ impl OpaConfig {
client: &Client,
namespace: Option<&str>,
) -> OperatorResult<String> {
Ok(client
client
.get::<ConfigMap>(&self.config_map_name, namespace)
.await?
.data
.and_then(|mut data| data.remove("OPA"))
.ok_or(error::Error::MissingOpaConnectString {
configmap_name: self.config_map_name.clone(),
})?)
})
}
}