Skip to content
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

feat: support adding extra RBAC rules via annotations #701

Merged
merged 8 commits into from
Sep 2, 2023
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