Skip to content

Commit

Permalink
feat: @RBACRoleRef to assign existing cluster roles (#865)
Browse files Browse the repository at this point in the history
Added new annotation @RBACRoleRef to assign an existing cluster role to the controllers

---------

Signed-off-by: Chris Laprun <claprun@redhat.com>
Co-authored-by: domenico.loiaconoa <domenico.loiacono@cgm.com>
Co-authored-by: Chris Laprun <metacosm@users.noreply.github.com>
Co-authored-by: Chris Laprun <claprun@redhat.com>
  • Loading branch information
4 people committed Apr 25, 2024
1 parent 40fd75c commit cae40e9
Show file tree
Hide file tree
Showing 18 changed files with 351 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.quarkiverse.operatorsdk.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@SuppressWarnings("unused")
public @interface AdditionalRBACRoleRefs {
RBACCRoleRef[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkiverse.operatorsdk.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Repeatable(AdditionalRBACRoleRefs.class)
public @interface RBACCRoleRef {
String RBAC_API_GROUP = "rbac.authorization.k8s.io";

RoleKind kind() default RoleKind.Role;

String name() default "";

enum RoleKind {

ClusterRole,
Role;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.quarkiverse.operatorsdk.annotations.AdditionalRBACRoleRefs;
import io.quarkiverse.operatorsdk.annotations.AdditionalRBACRules;
import io.quarkiverse.operatorsdk.annotations.RBACCRoleRef;
import io.quarkiverse.operatorsdk.annotations.RBACRule;

public class Constants {
Expand All @@ -28,4 +30,7 @@ private Constants() {
public static final DotName OBJECT = DotName.createSimple(Object.class.getName());
public static final DotName ADDITIONAL_RBAC_RULES = DotName.createSimple(AdditionalRBACRules.class.getName());
public static final DotName RBAC_RULE = DotName.createSimple(RBACRule.class.getName());
public static final DotName RBAC_ROLE_REF = DotName.createSimple(RBACCRoleRef.class.getName());
public static final DotName ADDITIONAL_RBAC_ROLE_REFS = DotName.createSimple(AdditionalRBACRoleRefs.class.getName());

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@

public class AddClusterRolesDecorator extends ResourceProvidingDecorator<KubernetesListBuilder> {

static final String JOSDK_CRD_VALIDATING_CLUSTER_ROLE = "josdk-crd-validating-cluster-role";
@SuppressWarnings("rawtypes")
private final Collection<QuarkusControllerConfiguration> configs;
public static final String JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME = "josdk-crd-validating-cluster-role";
private static final ClusterRoleBuilder CRD_VALIDATING_CLUSTER_ROLE_BUILDER = new ClusterRoleBuilder().withNewMetadata()
.withName(JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME).endMetadata()
.addToRules(new PolicyRuleBuilder()
.addToApiGroups("apiextensions.k8s.io")
.addToResources("customresourcedefinitions")
.addToVerbs("get", "list")
.build());
private static final String CR_API_VERSION = HasMetadata.getApiVersion(ClusterRole.class);
private static final String CR_KIND = HasMetadata.getKind(ClusterRole.class);
private final Collection<QuarkusControllerConfiguration<?>> configs;

private final boolean validateCRDs;

@SuppressWarnings("rawtypes")
public AddClusterRolesDecorator(Collection<QuarkusControllerConfiguration> configs, boolean validateCRDs) {
public AddClusterRolesDecorator(Collection<QuarkusControllerConfiguration<?>> configs, boolean validateCRDs) {
this.configs = configs;
this.validateCRDs = validateCRDs;
}
Expand All @@ -39,15 +46,8 @@ public void visit(KubernetesListBuilder list) {

// if we're asking to validate the CRDs, also add CRDs permissions, once
if (validateCRDs) {
final var crName = JOSDK_CRD_VALIDATING_CLUSTER_ROLE;

if (!contains(list, HasMetadata.getApiVersion(ClusterRole.class), HasMetadata.getKind(ClusterRole.class), crName)) {
list.addToItems(new ClusterRoleBuilder().withNewMetadata().withName(crName).endMetadata()
.addToRules(new PolicyRuleBuilder()
.addToApiGroups("apiextensions.k8s.io")
.addToResources("customresourcedefinitions")
.addToVerbs("get", "list")
.build()));
if (!contains(list, CR_API_VERSION, CR_KIND, JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME)) {
list.addToItems(CRD_VALIDATING_CLUSTER_ROLE_BUILDER);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkiverse.operatorsdk.deployment;

import static io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator.JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME;
import static io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator.getClusterRoleName;

import java.util.ArrayList;
Expand All @@ -19,6 +20,8 @@
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBindingBuilder;
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
import io.fabric8.kubernetes.api.model.rbac.RoleRef;
import io.fabric8.kubernetes.api.model.rbac.RoleRefBuilder;
import io.quarkiverse.operatorsdk.runtime.BuildTimeOperatorConfiguration;
import io.quarkiverse.operatorsdk.runtime.QuarkusControllerConfiguration;

Expand All @@ -27,15 +30,17 @@ public class AddRoleBindingsDecorator extends ResourceProvidingDecorator<Kuberne

private static final Logger log = Logger.getLogger(AddRoleBindingsDecorator.class);
protected static final String RBAC_AUTHORIZATION_GROUP = "rbac.authorization.k8s.io";
protected static final String CLUSTER_ROLE = "ClusterRole";
public static final String CLUSTER_ROLE = "ClusterRole";
protected static final String SERVICE_ACCOUNT = "ServiceAccount";
private final Collection<QuarkusControllerConfiguration> configs;
private final Collection<QuarkusControllerConfiguration<?>> configs;
private final BuildTimeOperatorConfiguration operatorConfiguration;
private static final ConcurrentMap<QuarkusControllerConfiguration, List<HasMetadata>> cachedBindings = new ConcurrentHashMap<>();
private static final Optional<String> deployNamespace = ConfigProvider.getConfig()
.getOptionalValue("quarkus.kubernetes.namespace", String.class);
public static final RoleRef CRD_VALIDATING_ROLE_REF = new RoleRef(RBAC_AUTHORIZATION_GROUP, CLUSTER_ROLE,
JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME);

public AddRoleBindingsDecorator(Collection<QuarkusControllerConfiguration> configs,
public AddRoleBindingsDecorator(Collection<QuarkusControllerConfiguration<?>> configs,
BuildTimeOperatorConfiguration operatorConfiguration) {
this.configs = configs;
this.operatorConfiguration = operatorConfiguration;
Expand All @@ -60,10 +65,9 @@ private List<HasMetadata> bindingsFor(QuarkusControllerConfiguration<?> controll
// if we validate the CRDs, also create a binding for the CRD validating role
List<HasMetadata> itemsToAdd;
if (operatorConfiguration.crd.validate) {
final var crBindingName = controllerName + "-crd-validating-role-binding";
final var crBindingName = getCRDValidatingBindingName(controllerName);
final var crdValidatorRoleBinding = createClusterRoleBinding(serviceAccountName, controllerName,
crBindingName, "validate CRDs",
AddClusterRolesDecorator.JOSDK_CRD_VALIDATING_CLUSTER_ROLE);
crBindingName, "validate CRDs", CRD_VALIDATING_ROLE_REF);
itemsToAdd = new ArrayList<>(desiredWatchedNamespaces.size() + 1);
itemsToAdd.add(crdValidatorRoleBinding);
} else {
Expand All @@ -73,49 +77,103 @@ private List<HasMetadata> bindingsFor(QuarkusControllerConfiguration<?> controll
final var roleBindingName = getRoleBindingName(controllerName);
if (controllerConfiguration.watchCurrentNamespace()) {
// create a RoleBinding that will be applied in the current namespace if watching only the current NS
itemsToAdd.add(createRoleBinding(roleBindingName, controllerName, serviceAccountName, null));
itemsToAdd.add(createRoleBinding(roleBindingName, serviceAccountName, null,
createDefaultRoleRef(getClusterRoleName(controllerName))));
// add additional Role Bindings
controllerConfiguration.getAdditionalRBACRoleRefs().forEach(
roleRef -> {
final var specificRoleBindingName = getSpecificRoleBindingName(controllerName, roleRef);
itemsToAdd.add(createRoleBinding(specificRoleBindingName, serviceAccountName, null, roleRef));
});
} else if (controllerConfiguration.watchAllNamespaces()) {
itemsToAdd.add(createClusterRoleBinding(serviceAccountName, controllerName,
controllerName + "-cluster-role-binding", "watch all namespaces",
getClusterRoleName(controllerName)));
getClusterRoleBindingName(controllerName), "watch all namespaces",
null));
// add additional cluster role bindings only if they target cluster roles
controllerConfiguration.getAdditionalRBACRoleRefs().forEach(
roleRef -> {
if (!CLUSTER_ROLE.equals(roleRef.getKind())) {
log.warnv("Cannot create a ClusterRoleBinding for RoleRef ''{0}'' because it's not a ClusterRole",
roleRef);
} else {
itemsToAdd.add(createClusterRoleBinding(serviceAccountName, controllerName,
roleRef.getName() + "-" + getClusterRoleBindingName(controllerName),
"watch all namespaces", roleRef));
}
});
} else {
// create a RoleBinding using either the provided deployment namespace or the desired watched namespace name
desiredWatchedNamespaces
.forEach(ns -> itemsToAdd.add(createRoleBinding(roleBindingName, controllerName, serviceAccountName, ns)));
.forEach(ns -> {
itemsToAdd.add(createRoleBinding(roleBindingName, serviceAccountName, ns,
createDefaultRoleRef(getClusterRoleName(controllerName))));
//add additional Role Bindings
controllerConfiguration.getAdditionalRBACRoleRefs()
.forEach(roleRef -> {
final var specificRoleBindingName = getSpecificRoleBindingName(controllerName, roleRef);
itemsToAdd.add(createRoleBinding(specificRoleBindingName, serviceAccountName,
ns, roleRef));
});
});
}

return itemsToAdd;
}

public static String getCRDValidatingBindingName(String controllerName) {
return controllerName + "-crd-validating-role-binding";
}

private static String getClusterRoleBindingName(String controllerName) {
return controllerName + "-cluster-role-binding";
}

public static String getRoleBindingName(String controllerName) {
return controllerName + "-role-binding";
}

private static RoleBinding createRoleBinding(String roleBindingName, String controllerName,
String serviceAccountName, String namespace) {
final var nsMsg = (namespace == null ? "current" : "'" + namespace + "'") + " namespace";
public static String getSpecificRoleBindingName(String controllerName, String roleRefName) {
return roleRefName + "-" + getRoleBindingName(controllerName);
}

public static String getSpecificRoleBindingName(String controllerName, RoleRef roleRef) {
return getSpecificRoleBindingName(controllerName, roleRef.getName());
}

private static RoleRef createDefaultRoleRef(String controllerName) {
return new RoleRefBuilder()
.withApiGroup(RBAC_AUTHORIZATION_GROUP).withKind(CLUSTER_ROLE).withName(controllerName)
.build();
}

private static RoleBinding createRoleBinding(String roleBindingName,
String serviceAccountName,
String targetNamespace,
RoleRef roleRef) {
final var nsMsg = (targetNamespace == null ? "current" : "'" + targetNamespace + "'") + " namespace";
log.infov("Creating ''{0}'' RoleBinding to be applied to {1}", roleBindingName, nsMsg);
return new RoleBindingBuilder()
.withNewMetadata()
.withName(roleBindingName)
.withNamespace(deployNamespace.orElse(namespace))
.withNamespace(targetNamespace)
.endMetadata()
.withNewRoleRef(RBAC_AUTHORIZATION_GROUP, CLUSTER_ROLE, getClusterRoleName(controllerName))
.withRoleRef(roleRef)
.addNewSubject(null, SERVICE_ACCOUNT, serviceAccountName,
deployNamespace.orElse(null))
.build();
}

private static ClusterRoleBinding createClusterRoleBinding(String serviceAccountName,
String controllerName, String bindingName, String controllerConfMessage,
String clusterRoleName) {
RoleRef roleRef) {
outputWarningIfNeeded(controllerName, bindingName, controllerConfMessage);
roleRef = roleRef == null ? createDefaultRoleRef(serviceAccountName) : roleRef;
final var ns = deployNamespace.orElse(null);
log.infov("Creating ''{0}'' ClusterRoleBinding to be applied to ''{1}'' namespace", bindingName, ns);
return new ClusterRoleBindingBuilder()
.withNewMetadata().withName(bindingName)
.endMetadata()
.withNewRoleRef(RBAC_AUTHORIZATION_GROUP, CLUSTER_ROLE, clusterRoleName)
.withRoleRef(roleRef)
.addNewSubject()
.withKind(SERVICE_ACCOUNT).withName(serviceAccountName).withNamespace(ns)
.endSubject()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
@SuppressWarnings("rawtypes")
public final class ControllerConfigurationsBuildItem extends SimpleBuildItem {

private final Map<String, QuarkusControllerConfiguration> controllerConfigs;
private final Map<String, QuarkusControllerConfiguration<?>> controllerConfigs;

public ControllerConfigurationsBuildItem(List<QuarkusControllerConfiguration> controllerConfigs) {
this.controllerConfigs = new HashMap<>(controllerConfigs.size());

controllerConfigs.forEach(c -> this.controllerConfigs.put(c.getName(), c));
}

public Map<String, QuarkusControllerConfiguration> getControllerConfigs() {
public Map<String, QuarkusControllerConfiguration<?>> getControllerConfigs() {
return controllerConfigs;
}
}
Loading

0 comments on commit cae40e9

Please sign in to comment.