Skip to content

[Merged by Bors] - Fix/custom s3 certificates #247

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 41 commits into from
Closed
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9c66f66
Removed check for TLS type
Maleware May 16, 2023
6912b5e
WIP adding trust store function and necessary constants
Maleware May 23, 2023
4ba6416
WIP add some more constants for tls
Maleware May 23, 2023
5a57269
WIP create key and trustore if TLS is SecretClass
Maleware May 23, 2023
4105bdd
WIP add truststore from system truststore
Maleware May 23, 2023
5c1486e
WIP remove comments
Maleware May 23, 2023
ed98af7
WIP move tls to init container and alter functions to respect secret …
Maleware May 23, 2023
140b952
WIP clean up
Maleware May 23, 2023
cac5b4f
WIP putting TLS into the init container
Maleware May 31, 2023
b0eb4de
WIP Cleaning up some stuff
Maleware May 31, 2023
b3c3ecd
WIP making tls check more robust
Maleware May 31, 2023
ed11f8b
WIP setting tls directory
Maleware May 31, 2023
9f556e6
WIP removing some checks
Maleware May 31, 2023
c8b53a8
WIP adding s3 to tests
Maleware Jun 1, 2023
eddb4f6
Added tls test
Maleware Jun 2, 2023
15f17e7
Extend test_definition for tls
Maleware Jun 2, 2023
e4f3e4e
Cleaning up
Maleware Jun 2, 2023
6d8eeed
merged main
adwk67 Jun 2, 2023
394d033
fixed linting and clippy warnings
adwk67 Jun 2, 2023
e883ceb
linting
adwk67 Jun 2, 2023
b0c27e8
certificate is added to store via initcontainer but not yet used/dete…
adwk67 Jun 6, 2023
cf7703e
corrected minio name to match certificate
adwk67 Jun 7, 2023
f15d9c6
test fixes for openshift
adwk67 Jun 7, 2023
f5f500d
linting fix
adwk67 Jun 7, 2023
d690e05
added s3-tls note to docs
adwk67 Jun 7, 2023
edc7520
updated changelog
adwk67 Jun 7, 2023
741d876
wip: history server with custom certificate
adwk67 Jun 9, 2023
de5a4bf
Debug certificate problems
razvan Jun 15, 2023
8b75e76
First successful history test.
razvan Jun 15, 2023
8a6594b
More cleanups.
razvan Jun 15, 2023
91394ce
Cleaning up
Maleware Jun 16, 2023
144cab1
Rust fmt
Maleware Jun 16, 2023
884ae33
Rust fmt again
Maleware Jun 16, 2023
718020e
make extraJavaOpts dependent on tls setting
adwk67 Jun 19, 2023
1db426a
use bitnami chart for TLS
adwk67 Jun 19, 2023
66b8112
Regenerate test cert.
razvan Jun 19, 2023
ecec20b
switch history server test to use bitnami chart for minio
adwk67 Jun 20, 2023
849c34b
linting fix
adwk67 Jun 20, 2023
8271f57
test tweaks
adwk67 Jun 20, 2023
96911ea
Merge branch 'main' into fix/custom-s3-certificates
adwk67 Jun 20, 2023
3a28e2b
updated changelog and corrected docs reference
adwk67 Jun 20, 2023
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
Expand Up @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.

- Generate OLM bundle for Release 23.4.0 ([#238]).
- Add support for Spark 3.4.0 ([#243]).
- Add support for using custom certificates when accessing S3 with TLS ([#247]).

### Changed

Expand All @@ -25,6 +26,7 @@ All notable changes to this project will be documented in this file.
[#238]: https://github.com/stackabletech/spark-k8s-operator/pull/238
[#241]: https://github.com/stackabletech/spark-k8s-operator/pull/241
[#243]: https://github.com/stackabletech/spark-k8s-operator/pull/243
[#247]: https://github.com/stackabletech/spark-k8s-operator/pull/247

## [23.4.0] - 2023-04-17

Expand Down
43 changes: 43 additions & 0 deletions docs/modules/spark-k8s/pages/usage-guide/s3.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

You can specify S3 connection details directly inside the `SparkApplication` specification or by referring to an external `S3Bucket` custom resource.

== S3 access using credentials

To specify S3 connection details directly as part of the `SparkApplication` resource you add an inline connection configuration as shown below.

[source,yaml]
Expand Down Expand Up @@ -46,3 +48,44 @@ spec:
----

This has the advantage that one connection configuration can be shared across `SparkApplications` and reduces the cost of updating these details.

== S3 access with TLS

A custom certificate can also be used for S3 access. In the example below, a Secret containing a custom certificate is referenced, which will used a to create a custom truststore which is used by Spark for S3-bucket access:

[source,yaml]
----
---
apiVersion: s3.stackable.tech/v1alpha1
kind: S3Connection
metadata:
name: s3-connection-resource
spec:
host: test-minio
port: 9000
accessStyle: Path
credentials:
secretClass: minio-credentials-class # <1>
tls:
verification:
server:
caCert:
secretClass: minio-tls-certificates # <2>
----
<1> Name of the `Secret` object expected to contain the following keys: `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY` (as in the previous example).
<2> Name of the `Secret` object containing the custom certificate. The certificate should comprise the 3 files named as shown below:

[source,yaml]
----
---
apiVersion: v1
kind: Secret
metadata:
name: minio-tls-certificates
labels:
secrets.stackable.tech/class: minio-tls-certificates
data:
ca.crt: ...
tls.crt: ...
tls.key: ...
----
7 changes: 7 additions & 0 deletions rust/crd/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ pub const LOG4J2_CONFIG_FILE: &str = "log4j2.properties";
pub const ACCESS_KEY_ID: &str = "accessKey";
pub const SECRET_ACCESS_KEY: &str = "secretKey";
pub const S3_SECRET_DIR_NAME: &str = "/stackable/secrets";
pub const SYSTEM_TRUST_STORE: &str = "/etc/pki/java/cacerts";
pub const STACKABLE_TRUST_STORE: &str = "/stackable/truststore";
pub const STACKABLE_TRUST_STORE_NAME: &str = "stackable-truststore";
pub const STACKABLE_TLS_STORE_PASSWORD: &str = "changeit";
pub const SYSTEM_TRUST_STORE_PASSWORD: &str = "changeit";
pub const STACKABLE_MOUNT_PATH_TLS: &str = "/stackable/mount_server_tls";
pub const STACKABLE_MOUNT_NAME_TLS: &str = "servertls";

pub const MIN_MEMORY_OVERHEAD: u32 = 384;
pub const JVM_OVERHEAD_FACTOR: f32 = 0.1;
Expand Down
66 changes: 64 additions & 2 deletions rust/crd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod affinity;
pub mod constants;
pub mod history;
pub mod s3logdir;
pub mod tlscerts;

use std::{
cmp::max,
Expand All @@ -18,7 +19,7 @@ use s3logdir::S3LogDir;
use serde::{Deserialize, Serialize};
use snafu::{OptionExt, ResultExt, Snafu};
use stackable_operator::{
builder::VolumeBuilder,
builder::{SecretOperatorVolumeSourceBuilder, VolumeBuilder},
commons::{
affinity::{StackableAffinity, StackableAffinityFragment},
resources::{
Expand Down Expand Up @@ -332,6 +333,21 @@ impl SparkApplication {
.build(),
);

if let Some(cert_secrets) = tlscerts::tls_secret_names(s3conn, s3logdir) {
result.push(
VolumeBuilder::new(STACKABLE_TRUST_STORE_NAME)
.with_empty_dir(None::<String>, Some(Quantity("5Mi".to_string())))
.build(),
);
for cert_secret in cert_secrets {
result.push(
VolumeBuilder::new(cert_secret)
.ephemeral(SecretOperatorVolumeSourceBuilder::new(cert_secret).build())
.build(),
);
}
}

result
}

Expand Down Expand Up @@ -427,6 +443,22 @@ impl SparkApplication {
..VolumeMount::default()
});

if let Some(cert_secrets) = tlscerts::tls_secret_names(s3conn, s3logdir) {
mounts.push(VolumeMount {
name: STACKABLE_TRUST_STORE_NAME.into(),
mount_path: STACKABLE_TRUST_STORE.into(),
..VolumeMount::default()
});
for cert_secret in cert_secrets {
let secret_dir = format!("{STACKABLE_MOUNT_PATH_TLS}/{cert_secret}");
mounts.push(VolumeMount {
name: cert_secret.to_string(),
mount_path: secret_dir,
..VolumeMount::default()
});
}
}

mounts
}

Expand Down Expand Up @@ -508,6 +540,12 @@ impl SparkApplication {
}
}

// s3 with TLS
if tlscerts::tls_secret_names(s3conn, s3_log_dir).is_some() {
submit_cmd.push(format!("--conf spark.driver.extraJavaOptions=\"-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}/truststore.p12 -Djavax.net.ssl.trustStorePassword={STACKABLE_TLS_STORE_PASSWORD} -Djavax.net.ssl.trustStoreType=pkcs12 -Djavax.net.debug=ssl,handshake\""));
submit_cmd.push(format!("--conf spark.executor.extraJavaOptions=\"-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}/truststore.p12 -Djavax.net.ssl.trustStorePassword={STACKABLE_TLS_STORE_PASSWORD} -Djavax.net.ssl.trustStoreType=pkcs12 -Djavax.net.debug=ssl,handshake\""));
}

// repositories and packages arguments
if let Some(deps) = self.spec.deps.clone() {
submit_cmd.extend(
Expand Down Expand Up @@ -675,7 +713,11 @@ impl SparkApplication {
Ok(format!("{}m", original_memory - deduction))
}

pub fn env(&self) -> Vec<EnvVar> {
pub fn env(
&self,
s3conn: &Option<S3ConnectionSpec>,
s3logdir: &Option<S3LogDir>,
) -> Vec<EnvVar> {
let tmp = self.spec.env.as_ref();
let mut e: Vec<EnvVar> = tmp.iter().flat_map(|e| e.iter()).cloned().collect();
if self.requirements().is_some() {
Expand All @@ -687,6 +729,25 @@ impl SparkApplication {
value_from: None,
});
}
if tlscerts::tls_secret_names(s3conn, s3logdir).is_some() {
e.push(EnvVar {
name: "STACKABLE_TLS_STORE_PASSWORD".to_string(),
value: Some(STACKABLE_TLS_STORE_PASSWORD.to_string()),
value_from: None,
});
}
if let Some(s3logdir) = s3logdir {
if tlscerts::tls_secret_name(&s3logdir.bucket.connection).is_some() {
e.push(EnvVar {
name: "SPARK_DAEMON_JAVA_OPTS".to_string(),
value: Some(format!(
"-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}/truststore.p12 -Djavax.net.ssl.trustStorePassword={STACKABLE_TLS_STORE_PASSWORD} -Djavax.net.ssl.trustStoreType=pkcs12"
)),
value_from: None,
});
}
}

e
}

Expand Down Expand Up @@ -811,6 +872,7 @@ pub enum SparkContainer {
Requirements,
Spark,
Vector,
Tls,
}

#[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)]
Expand Down
36 changes: 33 additions & 3 deletions rust/crd/src/s3logdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use crate::{
LogFileDirectorySpec::{self, S3},
S3LogFileDirectorySpec,
},
tlscerts,
};
use stackable_operator::{
builder::{SecretOperatorVolumeSourceBuilder, VolumeBuilder},
commons::{
s3::{InlinedS3BucketSpec, S3AccessStyle},
secret_class::SecretClassVolume,
Expand Down Expand Up @@ -79,9 +81,7 @@ impl S3LogDir {
TlsVerification::Server(server_verification) => {
match &server_verification.ca_cert {
CaCert::WebPki {} => {}
CaCert::SecretClass(_) => {
return S3TlsCaVerificationNotSupportedSnafu.fail()
}
CaCert::SecretClass(_) => {}
}
}
}
Expand Down Expand Up @@ -120,6 +120,7 @@ impl S3LogDir {
);
}
}

result
}

Expand Down Expand Up @@ -177,6 +178,35 @@ impl S3LogDir {
)
}

pub fn volumes(&self) -> Vec<Volume> {
let mut volumes: Vec<Volume> = self.credentials_volume().into_iter().collect();

if let Some(secret_name) = tlscerts::tls_secret_name(&self.bucket.connection) {
volumes.push(
VolumeBuilder::new(secret_name)
.ephemeral(SecretOperatorVolumeSourceBuilder::new(secret_name).build())
.build(),
);
}
volumes
}

pub fn volume_mounts(&self) -> Vec<VolumeMount> {
let mut volume_mounts: Vec<VolumeMount> =
self.credentials_volume_mount().into_iter().collect();

if let Some(secret_name) = tlscerts::tls_secret_name(&self.bucket.connection) {
let secret_dir = format!("{STACKABLE_MOUNT_PATH_TLS}/{secret_name}");

volume_mounts.push(VolumeMount {
name: secret_name.to_string(),
mount_path: secret_dir,
..VolumeMount::default()
});
}
volume_mounts
}

pub fn credentials_volume(&self) -> Option<Volume> {
self.credentials()
.map(|credentials| credentials.to_volume(credentials.secret_class.as_ref()))
Expand Down
62 changes: 62 additions & 0 deletions rust/crd/src/tlscerts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use stackable_operator::commons::{
s3::S3ConnectionSpec,
tls::{CaCert, TlsVerification},
};

use crate::{
constants::{
STACKABLE_MOUNT_PATH_TLS, STACKABLE_TLS_STORE_PASSWORD, STACKABLE_TRUST_STORE,
SYSTEM_TRUST_STORE, SYSTEM_TRUST_STORE_PASSWORD,
},
s3logdir::S3LogDir,
};

pub fn tls_secret_name(s3conn: &Option<S3ConnectionSpec>) -> Option<&str> {
if let Some(conn) = s3conn.as_ref() {
if let Some(tls) = &conn.tls {
if let TlsVerification::Server(verification) = &tls.verification {
if let CaCert::SecretClass(secret_name) = &verification.ca_cert {
return Some(secret_name);
}
}
}
}
None
}

pub fn tls_secret_names<'a>(
s3conn: &'a Option<S3ConnectionSpec>,
s3logdir: &'a Option<S3LogDir>,
) -> Option<Vec<&'a str>> {
let mut names = Vec::new();

if let Some(secret_name) = tls_secret_name(s3conn) {
names.push(secret_name);
}

if let Some(logdir) = s3logdir {
if let Some(secret_name) = tls_secret_name(&logdir.bucket.connection) {
names.push(secret_name);
}
}
if names.is_empty() {
None
} else {
Some(names)
}
}

pub fn create_key_and_trust_store() -> Vec<String> {
vec![
format!("keytool -importkeystore -srckeystore {SYSTEM_TRUST_STORE} -srcstoretype jks -srcstorepass {SYSTEM_TRUST_STORE_PASSWORD} -destkeystore {STACKABLE_TRUST_STORE}/truststore.p12 -deststoretype pkcs12 -deststorepass {STACKABLE_TLS_STORE_PASSWORD} -noprompt"),
]
}

pub fn add_cert_to_stackable_truststore(secret_name: &str) -> Vec<String> {
vec![
format!("echo [{STACKABLE_MOUNT_PATH_TLS}/{secret_name}/ca.crt] Adding cert..."),
format!("keytool -importcert -file {STACKABLE_MOUNT_PATH_TLS}/{secret_name}/ca.crt -alias stackable-{secret_name} -keystore {STACKABLE_TRUST_STORE}/truststore.p12 -storepass {STACKABLE_TLS_STORE_PASSWORD} -noprompt"),
format!("echo [{STACKABLE_MOUNT_PATH_TLS}/{secret_name}/ca.crt] Checking for cert..."),
format!("keytool -list -keystore {STACKABLE_TRUST_STORE}/truststore.p12 -storepass {STACKABLE_TLS_STORE_PASSWORD} -noprompt | grep stackable"),
]
}
Loading