Skip to content
Draft
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions crates/stackable-operator/crds/DummyCluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ spec:

Since git-sync v4.x.x this field is mapped to the flag `--ref`.
type: string
caCertSecretName:
description: |-
An optional secret used for holding CA certificates that will be used to verify the git server's TLS certificate by passing it to the git config option `http.sslCAInfo` passed with the gitsync command. The secret must have a key named `ca.crt` whose value is the PEM-encoded certificate bundle.
If `http.sslCAInfo` is also set via `gitSyncConf` (the `--git-config` option) then a warning will be logged.
nullable: true
type: string
credentials:
description: An optional secret used for git access.
nullable: true
Expand Down
5 changes: 5 additions & 0 deletions crates/stackable-operator/src/crd/git_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ pub mod versioned {
downgrade_with = credentials_to_secret
))]
pub credentials: Option<Credentials>,

/// An optional secret used for holding CA certificates that will be used to verify the git server's TLS certificate by passing it to the git config option `http.sslCAInfo` passed with the gitsync command. The secret must have a key named `ca.crt` whose value is the PEM-encoded certificate bundle.
/// If `http.sslCAInfo` is also set via `gitSyncConf` (the `--git-config` option) then a warning will be logged.
#[versioned(added(since = "v1alpha2"))]
pub ca_cert_secret_name: Option<String>,
}

#[derive(strum::Display, Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
Expand Down
237 changes: 236 additions & 1 deletion crates/stackable-operator/src/crd/git_sync/v1alpha2_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub const SSH_MOUNT_PATH_PREFIX: &str = "/stackable/gitssh";
pub const GIT_SYNC_SAFE_DIR_OPTION: &str = "safe.directory";
pub const GIT_SYNC_ROOT_DIR: &str = "/tmp/git";
pub const GIT_SYNC_LINK: &str = "current";
pub const CA_CERT_VOLUME_NAME_PREFIX: &str = "ca-cert";
pub const CA_CERT_MOUNT_PATH_PREFIX: &str = "/stackable/gitca";
pub const GIT_SSL_CA_INFO_CONFIG_KEY: &str = "http.sslCAInfo";

#[derive(Snafu, Debug, EnumDiscriminants)]
#[strum_discriminants(derive(IntoStaticStr))]
Expand Down Expand Up @@ -65,6 +68,9 @@ pub struct GitSyncResources {

/// GitSync volumes containing the synchronized repository
pub git_ssh_volumes: Vec<Volume>,

// GitSync volumes containing Ca certificates
pub git_ca_cert_volumes: Vec<Volume>,
}

impl GitSyncResources {
Expand Down Expand Up @@ -155,6 +161,21 @@ impl GitSyncResources {
git_sync_container_volume_mounts.push(ssh_volume_mount);
}

if git_sync.ca_cert_secret_name.is_some() {
let ca_cert_secret_mount_path = format!("{CA_CERT_MOUNT_PATH_PREFIX}-{i}");
let ca_cert_secret_volume_name = format!("{CA_CERT_VOLUME_NAME_PREFIX}-{i}");

let ca_cert_volume_mount =
VolumeMountBuilder::new(ca_cert_secret_volume_name, ca_cert_secret_mount_path)
.build();
git_sync_container_volume_mounts.push(ca_cert_volume_mount);
}

let ca_cert_path: Option<String> = git_sync
.ca_cert_secret_name
.as_ref()
.map(|_| format!("{CA_CERT_MOUNT_PATH_PREFIX}-{i}/ca.crt"));

let container = Self::create_git_sync_container(
&format!("{CONTAINER_NAME_PREFIX}-{i}"),
resolved_product_image,
Expand All @@ -163,6 +184,7 @@ impl GitSyncResources {
&env_vars,
&git_sync_container_volume_mounts,
container_log_config,
ca_cert_path.as_deref(),
)?;

let init_container = Self::create_git_sync_container(
Expand All @@ -173,6 +195,7 @@ impl GitSyncResources {
&env_vars,
&git_sync_container_volume_mounts,
container_log_config,
ca_cert_path.as_deref(),
)?;

let volume = VolumeBuilder::new(volume_name.clone())
Expand Down Expand Up @@ -211,11 +234,20 @@ impl GitSyncResources {
.build();
resources.git_ssh_volumes.push(ssh_secret_volume);
}

if let Some(ca_cert_secret_name) = &git_sync.ca_cert_secret_name {
let ca_cert_secret_volume_name = format!("{CA_CERT_VOLUME_NAME_PREFIX}-{i}");
let ca_cert_secret_volume = VolumeBuilder::new(&ca_cert_secret_volume_name)
.with_secret(ca_cert_secret_name, false)
.build();
resources.git_ca_cert_volumes.push(ca_cert_secret_volume);
}
}

Ok(resources)
}

#[allow(clippy::too_many_arguments)]
fn create_git_sync_container(
container_name: &str,
resolved_product_image: &ResolvedProductImage,
Expand All @@ -224,6 +256,7 @@ impl GitSyncResources {
env_vars: &[EnvVar],
volume_mounts: &[VolumeMount],
container_log_config: &ContainerLogConfig,
ca_cert_path: Option<&str>,
) -> Result<k8s_openapi::api::core::v1::Container, Error> {
let container = ContainerBuilder::new(container_name)
.context(InvalidContainerNameSnafu)?
Expand All @@ -240,6 +273,7 @@ impl GitSyncResources {
git_sync,
one_time,
container_log_config,
ca_cert_path,
)])
.add_env_vars(env_vars.into())
.add_volume_mounts(volume_mounts.to_vec())
Expand All @@ -261,6 +295,7 @@ impl GitSyncResources {
git_sync: &GitSync,
one_time: bool,
container_log_config: &ContainerLogConfig,
ca_cert_path: Option<&str>,
) -> String {
let internal_args = BTreeMap::from([
("--repo".to_string(), git_sync.repo.as_str().to_owned()),
Expand All @@ -275,11 +310,15 @@ impl GitSyncResources {
("--one-time".to_string(), one_time.to_string()),
]);

let internal_git_config = BTreeMap::from([(
let mut internal_git_config = BTreeMap::from([(
GIT_SYNC_SAFE_DIR_OPTION.to_owned(),
GIT_SYNC_ROOT_DIR.to_owned(),
)]);

if let Some(path) = ca_cert_path {
internal_git_config.insert(GIT_SSL_CA_INFO_CONFIG_KEY.to_owned(), path.to_owned());
}

let mut git_sync_config = git_sync.git_sync_conf.clone();

// The key and value in Git configs are separated by a colon, but both can contain either
Expand Down Expand Up @@ -1108,4 +1147,200 @@ secret:
serde_yaml::to_string(&git_sync_resources.git_ssh_volumes.first()).unwrap()
);
}

#[test]
fn test_git_sync_ca_cert() {
let git_sync_spec = r#"
# GitSync using SSH
- repo: ssh://git@github.com/stackabletech/repo.git
branch: trunk
gitFolder: ""
depth: 3
wait: 1m
caCertSecretName: git-ca-cert
gitSyncConf:
--rev: HEAD
"#;

let git_syncs: Vec<GitSync> = yaml_from_str_singleton_map(git_sync_spec).unwrap();

let resolved_product_image = ResolvedProductImage {
image: "oci.stackable.tech/sdp/product:latest".to_string(),
app_version_label_value: "1.0.0-latest"
.parse()
.expect("static app version label is always valid"),
product_version: "1.0.0".to_string(),
image_pull_policy: "Always".to_string(),
pull_secrets: None,
};

let extra_env_vars = env_vars_from([("VAR1", "value1")]);

let extra_volume_mounts = [VolumeMount {
name: "extra-volume".to_string(),
mount_path: "/mnt/extra-volume".to_string(),
..VolumeMount::default()
}];

let git_sync_resources = GitSyncResources::new(
&git_syncs,
&resolved_product_image,
&extra_env_vars,
&extra_volume_mounts,
"log-volume",
&validate(default_container_log_config()).unwrap(),
)
.unwrap();

assert!(git_sync_resources.is_git_sync_enabled());

assert_eq!(1, git_sync_resources.git_sync_containers.len());

assert_eq!(
r#"args:
- |-
mkdir --parents /stackable/log/git-sync-0 && exec > >(tee /stackable/log/git-sync-0/container.stdout.log) 2> >(tee /stackable/log/git-sync-0/container.stderr.log >&2)

prepare_signal_handlers()
{
unset term_child_pid
unset term_kill_needed
trap 'handle_term_signal' TERM
}

handle_term_signal()
{
if [ "${term_child_pid}" ]; then
kill -TERM "${term_child_pid}" 2>/dev/null
else
term_kill_needed="yes"
fi
}

wait_for_termination()
{
set +e
term_child_pid=$1
if [[ -v term_kill_needed ]]; then
kill -TERM "${term_child_pid}" 2>/dev/null
fi
wait ${term_child_pid} 2>/dev/null
trap - TERM
wait ${term_child_pid} 2>/dev/null
set -e
}

prepare_signal_handlers
/stackable/git-sync --depth=3 --git-config='http.sslCAInfo:/stackable/gitca-0/ca.crt,safe.directory:/tmp/git' --link=current --one-time=false --period=60s --ref=trunk --repo=ssh://git@github.com/stackabletech/repo.git --rev=HEAD --root=/tmp/git &
wait_for_termination $!
command:
- /bin/bash
- -x
- -euo
- pipefail
- -c
env:
- name: VAR1
value: value1
image: oci.stackable.tech/sdp/product:latest
imagePullPolicy: Always
name: git-sync-0
resources:
limits:
cpu: 200m
memory: 64Mi
requests:
cpu: 100m
memory: 64Mi
volumeMounts:
- mountPath: /tmp/git
name: content-from-git-0
- mountPath: /stackable/log
name: log-volume
- mountPath: /mnt/extra-volume
name: extra-volume
- mountPath: /stackable/gitca-0
name: ca-cert-0
"#,
serde_yaml::to_string(&git_sync_resources.git_sync_containers.first()).unwrap()
);

assert_eq!(1, git_sync_resources.git_sync_init_containers.len());

assert_eq!(
r#"args:
- |-
mkdir --parents /stackable/log/git-sync-0-init && exec > >(tee /stackable/log/git-sync-0-init/container.stdout.log) 2> >(tee /stackable/log/git-sync-0-init/container.stderr.log >&2)
/stackable/git-sync --depth=3 --git-config='http.sslCAInfo:/stackable/gitca-0/ca.crt,safe.directory:/tmp/git' --link=current --one-time=true --period=60s --ref=trunk --repo=ssh://git@github.com/stackabletech/repo.git --rev=HEAD --root=/tmp/git
command:
- /bin/bash
- -x
- -euo
- pipefail
- -c
env:
- name: VAR1
value: value1
image: oci.stackable.tech/sdp/product:latest
imagePullPolicy: Always
name: git-sync-0-init
resources:
limits:
cpu: 200m
memory: 64Mi
requests:
cpu: 100m
memory: 64Mi
volumeMounts:
- mountPath: /tmp/git
name: content-from-git-0
- mountPath: /stackable/log
name: log-volume
- mountPath: /mnt/extra-volume
name: extra-volume
- mountPath: /stackable/gitca-0
name: ca-cert-0
"#,
serde_yaml::to_string(&git_sync_resources.git_sync_init_containers.first()).unwrap()
);

assert_eq!(1, git_sync_resources.git_content_volumes.len());

assert_eq!(
"emptyDir: {}
name: content-from-git-0
",
serde_yaml::to_string(&git_sync_resources.git_content_volumes.first()).unwrap()
);

assert_eq!(1, git_sync_resources.git_content_volume_mounts.len());

assert_eq!(
"mountPath: /stackable/app/git-0
name: content-from-git-0
",
serde_yaml::to_string(&git_sync_resources.git_content_volume_mounts.first()).unwrap()
);

assert_eq!(1, git_sync_resources.git_content_folders.len());

assert_eq!(
"/stackable/app/git-0/current/",
git_sync_resources
.git_content_folders_as_string()
.first()
.unwrap()
);

assert_eq!(1, git_sync_resources.git_ca_cert_volumes.len());

assert_eq!(
"name: ca-cert-0
secret:
optional: false
secretName: git-ca-cert
",
serde_yaml::to_string(&git_sync_resources.git_ca_cert_volumes.first()).unwrap()
);
}
}
Loading