Skip to content

Commit

Permalink
feat: support adding extra RBAC rules via annotations (#701)
Browse files Browse the repository at this point in the history
* chore: remove unneeded dependency

* feat: add annotations module with RBAC-related annotations

* refactor: move verb constants to new Verbs class in annotations module

* refactor: rename constants more appropriately, add more

* refactor: rename Verbs to more precise RBACVerbs

* feat: make RBACRule repeatable, add ALL constant

* feat: support adding extra RBAC rules via annotations

See SimpleReconciler for a usage example.
Fixes #696

* fix: properly handle case where there's only one RBACRule
  • Loading branch information
metacosm authored Sep 2, 2023
1 parent 571e558 commit 209caba
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 55 deletions.
15 changes: 15 additions & 0 deletions annotations/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkiverse.operatorsdk</groupId>
<artifactId>quarkus-operator-sdk-parent</artifactId>
<version>6.3.1-SNAPSHOT</version>
</parent>

<artifactId>quarkus-operator-sdk-annotations</artifactId>
<name>Quarkus - Operator SDK - Annotations</name>

</project>
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 AdditionalRBACRules {
RBACRule[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkiverse.operatorsdk.annotations;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Repeatable(AdditionalRBACRules.class)
@SuppressWarnings("unused")
public @interface RBACRule {
/**
* Represents a wildcard string that matches any RBAC-related value (verb, resource, etc…).
*/
String ALL = "*";

String[] apiGroups() default {};

String[] verbs();

String[] resources() default {};

String[] resourceNames() default {};

String[] nonResourceURLs() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkiverse.operatorsdk.annotations;

import java.util.ArrayList;
import java.util.Arrays;

public class RBACVerbs {
public static final String CREATE = "create";
public static final String PATCH = "patch";
public static final String UPDATE = "update";
public static final String GET = "get";
public static final String LIST = "list";
public static final String WATCH = "watch";
public static final String DELETE = "delete";
public static final String[] UPDATE_VERBS = new String[] { PATCH, UPDATE };
public static final String[] READ_VERBS = new String[] { GET, LIST, WATCH };
public static final String[] ALL_COMMON_VERBS;

static {
final var verbs = new ArrayList<String>(READ_VERBS.length + UPDATE_VERBS.length + 2);
verbs.addAll(Arrays.asList(READ_VERBS));
verbs.addAll(Arrays.asList(UPDATE_VERBS));
verbs.add(CREATE);
verbs.add(DELETE);
ALL_COMMON_VERBS = verbs.toArray(new String[0]);
}

private RBACVerbs() {
}
}
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@
<artifactId>quarkus-operator-sdk-common-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.operatorsdk</groupId>
<artifactId>quarkus-operator-sdk-annotations</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.quarkiverse.operatorsdk.bundle.deployment;

import static io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator.ALL_VERBS;
import static io.quarkiverse.operatorsdk.annotations.RBACVerbs.ALL_COMMON_VERBS;

import java.io.IOException;
import java.nio.file.Path;
Expand Down Expand Up @@ -401,7 +401,7 @@ private CSVMetadataHolder createMetadataHolder(AnnotationInstance csvMetadata, C
ConfigurationUtils.annotationValueOrDefault(permissionsAnn[i], "resources",
AnnotationValue::asStringArray, () -> null),
ConfigurationUtils.annotationValueOrDefault(permissionsAnn[i], "verbs",
AnnotationValue::asStringArray, () -> ALL_VERBS),
AnnotationValue::asStringArray, () -> ALL_COMMON_VERBS),
ConfigurationUtils.annotationValueOrDefault(permissionsAnn[i], "serviceAccountName",
AnnotationValue::asString, () -> null));
}
Expand Down
5 changes: 5 additions & 0 deletions common-deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkiverse.operatorsdk</groupId>
<artifactId>quarkus-operator-sdk-annotations</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
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.AdditionalRBACRules;
import io.quarkiverse.operatorsdk.annotations.RBACRule;

public class Constants {
private Constants() {
Expand All @@ -24,4 +26,6 @@ private Constants() {
public static final DotName CONFIGURED = DotName.createSimple(Configured.class.getName());
public static final DotName ANNOTATION_CONFIGURABLE = DotName.createSimple(AnnotationConfigurable.class.getName());
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());
}
7 changes: 0 additions & 7 deletions common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,4 @@

<name>Quarkus - Operator SDK - Common</name>
<artifactId>quarkus-operator-sdk-common</artifactId>

<dependencies>
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkiverse.operatorsdk.deployment;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

Expand All @@ -14,28 +12,12 @@
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
import io.javaoperatorsdk.operator.processing.dependent.Creator;
import io.javaoperatorsdk.operator.processing.dependent.Updater;
import io.quarkiverse.operatorsdk.annotations.RBACVerbs;
import io.quarkiverse.operatorsdk.runtime.DependentResourceSpecMetadata;
import io.quarkiverse.operatorsdk.runtime.QuarkusControllerConfiguration;

public class AddClusterRolesDecorator extends ResourceProvidingDecorator<KubernetesListBuilder> {

public static final String CREATE_VERB = "create";
public static final String PATCH_VERB = "patch";
public static final String DELETE_VERB = "delete";

public static final String[] READ_VERBS = new String[] { "get", "list", "watch" };
public static final String[] UPDATE_VERBS = new String[] { PATCH_VERB, "update" };

public static final String[] ALL_VERBS;
static {
final var verbs = new ArrayList<String>(READ_VERBS.length + UPDATE_VERBS.length + 2);
verbs.addAll(Arrays.asList(READ_VERBS));
verbs.addAll(Arrays.asList(UPDATE_VERBS));
verbs.add(CREATE_VERB);
verbs.add(DELETE_VERB);
ALL_VERBS = verbs.toArray(new String[0]);
}

static final String JOSDK_CRD_VALIDATING_CLUSTER_ROLE = "josdk-crd-validating-cluster-role";
@SuppressWarnings("rawtypes")
private final Collection<QuarkusControllerConfiguration> configs;
Expand Down Expand Up @@ -86,7 +68,7 @@ public static ClusterRole createClusterRole(QuarkusControllerConfiguration<?> cr
rule.addToResources(plural + "/finalizers");

rule.addToApiGroups(HasMetadata.getGroup(resourceClass))
.addToVerbs(ALL_VERBS)
.addToVerbs(RBACVerbs.ALL_COMMON_VERBS)
.build();

final var clusterRoleBuilder = new ClusterRoleBuilder()
Expand All @@ -105,23 +87,27 @@ public static ClusterRole createClusterRole(QuarkusControllerConfiguration<?> cr
final var dependentRule = new PolicyRuleBuilder()
.addToApiGroups(HasMetadata.getGroup(associatedResourceClass))
.addToResources(HasMetadata.getPlural(associatedResourceClass))
.addToVerbs(READ_VERBS);
.addToVerbs(RBACVerbs.READ_VERBS);
if (Updater.class.isAssignableFrom(dependentResourceClass)) {
dependentRule.addToVerbs(UPDATE_VERBS);
dependentRule.addToVerbs(RBACVerbs.UPDATE_VERBS);
}
if (Deleter.class.isAssignableFrom(dependentResourceClass)) {
dependentRule.addToVerbs(DELETE_VERB);
dependentRule.addToVerbs(RBACVerbs.DELETE);
}
if (Creator.class.isAssignableFrom(dependentResourceClass)) {
dependentRule.addToVerbs(CREATE_VERB);
if (!dependentRule.getVerbs().contains(PATCH_VERB)) {
dependentRule.addToVerbs(PATCH_VERB);
dependentRule.addToVerbs(RBACVerbs.CREATE);
if (!dependentRule.getVerbs().contains(RBACVerbs.PATCH)) {
dependentRule.addToVerbs(RBACVerbs.PATCH);
}
}
clusterRoleBuilder.addToRules(dependentRule.build());
}

});

// add additional RBAC rules
clusterRoleBuilder.addAllToRules(cri.getAdditionalRBACRules());

return clusterRoleBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

import static io.quarkiverse.operatorsdk.common.ClassLoadingUtils.instantiate;
import static io.quarkiverse.operatorsdk.common.ClassLoadingUtils.loadClass;
import static io.quarkiverse.operatorsdk.common.Constants.CONTROLLER_CONFIGURATION;
import static io.quarkiverse.operatorsdk.common.Constants.*;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Type;
import org.jboss.jandex.*;
import org.jboss.logging.Logger;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.rbac.PolicyRule;
import io.fabric8.kubernetes.api.model.rbac.PolicyRuleBuilder;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
Expand Down Expand Up @@ -208,6 +207,9 @@ static QuarkusControllerConfiguration createConfiguration(
() -> null);
}

// check if we have additional RBAC rules to handle
final var additionalRBACRules = extractAdditionalRBACRules(info);

// extract the namespaces
// first check if we explicitly set the namespaces via the annotations
Set<String> namespaces = null;
Expand Down Expand Up @@ -261,9 +263,8 @@ static QuarkusControllerConfiguration createConfiguration(
primaryAsResource.hasNonVoidStatus(),
finalFilter,
maxReconciliationInterval,
onAddFilter, onUpdateFilter, genericFilter, retryClass, retryConfigurationClass,
rateLimiterClass,
rateLimiterConfigurationClass, dependentResources, null);
onAddFilter, onUpdateFilter, genericFilter, retryClass, retryConfigurationClass, rateLimiterClass,
rateLimiterConfigurationClass, dependentResources, null, additionalRBACRules);

if (hasDependents) {
dependentResourceInfos.forEach(dependent -> {
Expand Down Expand Up @@ -291,6 +292,61 @@ static QuarkusControllerConfiguration createConfiguration(
return configuration;
}

private static List<PolicyRule> extractAdditionalRBACRules(ClassInfo info) {
// if there are multiple annotations they should be found under an automatically generated AdditionalRBACRules
final var additionalRuleAnnotations = ConfigurationUtils.annotationValueOrDefault(
info.declaredAnnotation(ADDITIONAL_RBAC_RULES),
"value",
AnnotationValue::asNestedArray,
() -> null);
List<PolicyRule> additionalRBACRules = Collections.emptyList();
if (additionalRuleAnnotations != null && additionalRuleAnnotations.length > 0) {
additionalRBACRules = new ArrayList<>(additionalRuleAnnotations.length);
for (AnnotationInstance ruleAnnotation : additionalRuleAnnotations) {
additionalRBACRules.add(extractRule(ruleAnnotation));
}
}

// if there's only one, it will be found under RBACRule annotation
final var rbacRuleAnnotation = info.declaredAnnotation(RBAC_RULE);
if (rbacRuleAnnotation != null) {
additionalRBACRules = List.of(extractRule(rbacRuleAnnotation));
}

return additionalRBACRules;
}

private static PolicyRule extractRule(AnnotationInstance ruleAnnotation) {
final var builder = new PolicyRuleBuilder();

builder.withApiGroups(ConfigurationUtils.annotationValueOrDefault(ruleAnnotation,
"apiGroups",
AnnotationValue::asStringArray,
() -> null));

builder.withVerbs(ConfigurationUtils.annotationValueOrDefault(ruleAnnotation,
"verbs",
AnnotationValue::asStringArray,
() -> null));

builder.withResources(ConfigurationUtils.annotationValueOrDefault(ruleAnnotation,
"resources",
AnnotationValue::asStringArray,
() -> null));

builder.withResourceNames(ConfigurationUtils.annotationValueOrDefault(ruleAnnotation,
"resourceNames",
AnnotationValue::asStringArray,
() -> null));

builder.withNonResourceURLs(ConfigurationUtils.annotationValueOrDefault(ruleAnnotation,
"nonResourceURLs",
AnnotationValue::asStringArray,
() -> null));

return builder.build();
}

private static Class<?> getConfigurationAnnotationClass(SelectiveAugmentedClassInfo configurationTargetInfo,
AnnotationConfigurableAugmentedClassInfo configurableInfo) {
if (configurableInfo != null) {
Expand Down
Loading

0 comments on commit 209caba

Please sign in to comment.