Skip to content
58 changes: 42 additions & 16 deletions crates/stackable-operator/src/commons/rbac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,24 @@ pub enum Error {
}

/// Build RBAC objects for the product workloads.
/// The `rbac_prefix` is meant to be the product name, for example: zookeeper, airflow, etc.
/// and it is a assumed that a ClusterRole named `{rbac_prefix}-clusterrole` exists.
/// The `product_name` is meant to be the product name, for example: zookeeper, airflow, etc.
/// and it is a assumed that a ClusterRole named `{product_name}-clusterrole` exists.

pub fn build_rbac_resources<T: Clone + Resource<DynamicType = ()>>(
resource: &T,
rbac_prefix: &str,
// 'product_name' is not used to build the names of the serviceAccount and roleBinding objects,
// as this caused problems with multiple clusters of the same product within the same namespace
// see <https://stackable.atlassian.net/browse/SUP-148> for more details.
// Instead the names for these objects are created by reading the name from the cluster object
// and appending [-rolebinding|-serviceaccount] to create unique names instead of using the
// same objects for multiple clusters.
product_name: &str,
labels: Labels,
) -> Result<(ServiceAccount, RoleBinding)> {
let sa_name = service_account_name(rbac_prefix);
let sa_name = service_account_name(&resource.name_any());
// We add the legacy serviceAccount name to the binding here for at least one
// release cycle, so that the switchover during the upgrade can be smoother.
let legacy_sa_name = service_account_name(product_name);
let service_account = ServiceAccount {
metadata: ObjectMetaBuilder::new()
.name_and_namespace(resource)
Expand All @@ -52,7 +62,7 @@ pub fn build_rbac_resources<T: Clone + Resource<DynamicType = ()>>(
let role_binding = RoleBinding {
metadata: ObjectMetaBuilder::new()
.name_and_namespace(resource)
.name(role_binding_name(rbac_prefix))
.name(role_binding_name(&resource.name_any()))
.ownerreference_from_resource(resource, None, Some(true))
.context(RoleBindingOwnerReferenceFromResourceSnafu {
name: resource.name_any(),
Expand All @@ -61,29 +71,45 @@ pub fn build_rbac_resources<T: Clone + Resource<DynamicType = ()>>(
.build(),
role_ref: RoleRef {
kind: "ClusterRole".to_string(),
name: format!("{rbac_prefix}-clusterrole"),
name: format!("{product_name}-clusterrole"),
api_group: "rbac.authorization.k8s.io".to_string(),
},
subjects: Some(vec![Subject {
kind: "ServiceAccount".to_string(),
name: sa_name,
namespace: resource.namespace(),
..Subject::default()
}]),
subjects: Some(vec![
Subject {
kind: "ServiceAccount".to_string(),
name: sa_name,
namespace: resource.namespace(),
..Subject::default()
},
// We add the legacy serviceAccount name to the binding here for at least one
// release cycle, so that the switchover during the upgrade can be smoother.
Subject {
kind: "ServiceAccount".to_string(),
name: legacy_sa_name,
namespace: resource.namespace(),
..Subject::default()
},
]),
};

Ok((service_account, role_binding))
}

/// Generate the service account name.
/// The `rbac_prefix` is meant to be the product name, for example: zookeeper, airflow, etc.
pub fn service_account_name(rbac_prefix: &str) -> String {
/// This is private because operators should not use this function to calculate names for
/// serviceAccount objects, but rather read the name from the objects returned by
/// `build_rbac_resources` if they need the name.
fn service_account_name(rbac_prefix: &str) -> String {
format!("{rbac_prefix}-serviceaccount")
}

/// Generate the role binding name.
/// The `rbac_prefix` is meant to be the product name, for example: zookeeper, airflow, etc.
pub fn role_binding_name(rbac_prefix: &str) -> String {
/// This is private because operators should not use this function to calculate names for
/// roleBinding objects, but rather read the name from the objects returned by
/// `build_rbac_resources` if they need the name.
fn role_binding_name(rbac_prefix: &str) -> String {
format!("{rbac_prefix}-rolebinding")
}

Expand Down Expand Up @@ -130,7 +156,7 @@ mod tests {
build_rbac_resources(&cluster, RESOURCE_NAME, Labels::new()).unwrap();

assert_eq!(
Some(service_account_name(RESOURCE_NAME)),
Some(service_account_name(CLUSTER_NAME)),
rbac_sa.metadata.name,
"service account does not match"
);
Expand All @@ -141,7 +167,7 @@ mod tests {
);

assert_eq!(
Some(role_binding_name(RESOURCE_NAME)),
Some(role_binding_name(CLUSTER_NAME)),
rbac_rolebinding.metadata.name,
"rolebinding does not match"
);
Expand Down
Loading