Skip to content

Commit

Permalink
Support generation of ClusterRoleBinding resources
Browse files Browse the repository at this point in the history
Fix quarkusio#32572

(cherry picked from commit c494ce8)
  • Loading branch information
Sgitario authored and gsmet committed Apr 18, 2023
1 parent 8a44cd5 commit 3c95724
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 67 deletions.
8 changes: 5 additions & 3 deletions docs/src/main/asciidoc/deploying-to-kubernetes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ quarkus.kubernetes.rbac.role-bindings.my-role-binding.role-name=my-role
<2> Also, the service account "my-service-account" will be generated.
<3> And we can configure the generated RoleBinding resource by selecting the role to be used and the subject.

Finally, we can also generate the cluster wide role resource of "ClusterRole" kind as follows:
Finally, we can also generate the cluster wide role resource of "ClusterRole" kind and a "ClusterRoleBinding" resource as follows:

[source,properties]
----
Expand All @@ -917,11 +917,13 @@ quarkus.kubernetes.rbac.cluster-roles.my-cluster-role.policy-rules.0.resources=d
quarkus.kubernetes.rbac.cluster-roles.my-cluster-role.policy-rules.0.verbs=get,watch,list
# Bind the ClusterRole "my-cluster-role" with the application service account
quarkus.kubernetes.rbac.role-bindings.my-role-binding.role-name=my-cluster-role <2>
quarkus.kubernetes.rbac.cluster-role-bindings.my-cluster-role-binding.subjects.manager.kind=Group
quarkus.kubernetes.rbac.cluster-role-bindings.my-cluster-role-binding.subjects.manager.api-group=rbac.authorization.k8s.io
quarkus.kubernetes.rbac.cluster-role-bindings.my-cluster-role-binding.role-name=my-cluster-role <2>
----

<1> In this example, the cluster role "my-cluster-role" will be generated with the specified policy rules.
<2> As we have configured only one role, this property is not really necessary.
<2> The name of the ClusterRole resource to use. Role resources are namespace-based and hence not allowed in ClusterRoleBinding resources.

=== Deploying to Minikube

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,52 +79,4 @@ public RoleRef getRoleRef() {
public Subject[] getSubjects() {
return subjects;
}

public static final class RoleRef {
private final boolean clusterWide;
private final String name;

public RoleRef(String name, boolean clusterWide) {
this.name = name;
this.clusterWide = clusterWide;
}

public boolean isClusterWide() {
return clusterWide;
}

public String getName() {
return name;
}
}

public static final class Subject {
private final String apiGroup;
private final String kind;
private final String name;
private final String namespace;

public Subject(String apiGroup, String kind, String name, String namespace) {
this.apiGroup = apiGroup;
this.kind = kind;
this.name = name;
this.namespace = namespace;
}

public String getApiGroup() {
return apiGroup;
}

public String getKind() {
return kind;
}

public String getName() {
return name;
}

public String getNamespace() {
return namespace;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.kubernetes.spi;

public class RoleRef {
private final boolean clusterWide;
private final String name;

public RoleRef(String name, boolean clusterWide) {
this.name = name;
this.clusterWide = clusterWide;
}

public boolean isClusterWide() {
return clusterWide;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.kubernetes.spi;

public class Subject {
private final String apiGroup;
private final String kind;
private final String name;
private final String namespace;

public Subject(String apiGroup, String kind, String name, String namespace) {
this.apiGroup = apiGroup;
this.kind = kind;
this.name = name;
this.namespace = namespace;
}

public String getApiGroup() {
return apiGroup;
}

public String getKind() {
return kind;
}

public String getName() {
return name;
}

public String getNamespace() {
return namespace;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.quarkus.kubernetes.deployment;

import static io.quarkus.kubernetes.deployment.Constants.CLUSTER_ROLE;
import static io.quarkus.kubernetes.deployment.Constants.CLUSTER_ROLE_BINDING;
import static io.quarkus.kubernetes.deployment.Constants.RBAC_API_GROUP;
import static io.quarkus.kubernetes.deployment.Constants.RBAC_API_VERSION;

import java.util.HashMap;
import java.util.Map;

import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.dekorate.utils.Strings;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBindingBuilder;
import io.quarkus.kubernetes.spi.RoleRef;
import io.quarkus.kubernetes.spi.Subject;

public class AddClusterRoleBindingResourceDecorator extends ResourceProvidingDecorator<KubernetesListBuilder> {

private final String deploymentName;
private final String name;
private final Map<String, String> labels;
private final RoleRef roleRef;
private final Subject[] subjects;

public AddClusterRoleBindingResourceDecorator(String deploymentName, String name, Map<String, String> labels,
RoleRef roleRef,
Subject... subjects) {
this.deploymentName = deploymentName;
this.name = name;
this.labels = labels;
this.roleRef = roleRef;
this.subjects = subjects;
}

public void visit(KubernetesListBuilder list) {
if (contains(list, RBAC_API_VERSION, CLUSTER_ROLE_BINDING, name)) {
return;
}

Map<String, String> clusterRoleBindingLabels = new HashMap<>();
clusterRoleBindingLabels.putAll(labels);
getDeploymentMetadata(list, deploymentName)
.map(ObjectMeta::getLabels)
.ifPresent(clusterRoleBindingLabels::putAll);

ClusterRoleBindingBuilder builder = new ClusterRoleBindingBuilder()
.withNewMetadata()
.withName(name)
.withLabels(clusterRoleBindingLabels)
.endMetadata()
.withNewRoleRef()
.withKind(CLUSTER_ROLE)
.withName(roleRef.getName())
.withApiGroup(RBAC_API_GROUP)
.endRoleRef();

for (Subject subject : subjects) {
builder.addNewSubject()
.withApiGroup(subject.getApiGroup())
.withKind(subject.getKind())
.withName(Strings.defaultIfEmpty(subject.getName(), deploymentName))
.withNamespace(subject.getNamespace())
.endSubject();
}

list.addToItems(builder.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem;
import io.quarkus.kubernetes.spi.RoleRef;
import io.quarkus.kubernetes.spi.Subject;

public class AddRoleBindingResourceDecorator extends ResourceProvidingDecorator<KubernetesListBuilder> {

private final String deploymentName;
private final String name;
private final Map<String, String> labels;
private final KubernetesRoleBindingBuildItem.RoleRef roleRef;
private final KubernetesRoleBindingBuildItem.Subject[] subjects;
private final RoleRef roleRef;
private final Subject[] subjects;

public AddRoleBindingResourceDecorator(String deploymentName, String name, Map<String, String> labels,
KubernetesRoleBindingBuildItem.RoleRef roleRef,
KubernetesRoleBindingBuildItem.Subject... subjects) {
RoleRef roleRef,
Subject... subjects) {
this.deploymentName = deploymentName;
this.name = name;
this.labels = labels;
Expand Down Expand Up @@ -56,7 +57,7 @@ public void visit(KubernetesListBuilder list) {
.withApiGroup(RBAC_API_GROUP)
.endRoleRef();

for (KubernetesRoleBindingBuildItem.Subject subject : subjects) {
for (Subject subject : subjects) {
builder.addNewSubject()
.withApiGroup(subject.getApiGroup())
.withKind(subject.getKind())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.quarkus.kubernetes.deployment;

import java.util.Map;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class ClusterRoleBindingConfig {

/**
* Name of the ClusterRoleBinding resource to be generated. If not provided, it will use the application name plus the role
* ref name.
*/
@ConfigItem
public Optional<String> name;

/**
* Labels to add into the RoleBinding resource.
*/
@ConfigItem
public Map<String, String> labels;

/**
* The name of the ClusterRole resource to use by the RoleRef element in the generated ClusterRoleBinding resource.
*/
@ConfigItem
public String roleName;

/**
* List of subjects elements to use in the generated ClusterRoleBinding resource.
*/
@ConfigItem
public Map<String, SubjectConfig> subjects;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public final class Constants {
public static final String ROLE = "Role";
public static final String CLUSTER_ROLE = "ClusterRole";
public static final String ROLE_BINDING = "RoleBinding";
public static final String CLUSTER_ROLE_BINDING = "ClusterRoleBinding";
public static final String SERVICE_ACCOUNT = "ServiceAccount";
public static final String DEPLOYMENT_GROUP = "apps";
public static final String DEPLOYMENT_VERSION = "v1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem;
import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem;
import io.quarkus.kubernetes.spi.KubernetesServiceAccountBuildItem;
import io.quarkus.kubernetes.spi.RoleRef;
import io.quarkus.kubernetes.spi.Subject;

public class KubernetesCommonHelper {

Expand Down Expand Up @@ -358,17 +360,17 @@ private static Collection<DecoratorBuildItem> createRbacDecorators(String name,
String rbName = rb.getValue().name.orElse(rb.getKey());
RoleBindingConfig roleBinding = rb.getValue();

List<KubernetesRoleBindingBuildItem.Subject> subjects = new ArrayList<>();
List<Subject> subjects = new ArrayList<>();
if (roleBinding.subjects.isEmpty()) {
requiresServiceAccount = true;
subjects.add(new KubernetesRoleBindingBuildItem.Subject(null, SERVICE_ACCOUNT,
subjects.add(new Subject(null, SERVICE_ACCOUNT,
defaultIfEmpty(defaultServiceAccount, config.getServiceAccount().orElse(name)),
defaultServiceAccountNamespace));
} else {
for (Map.Entry<String, SubjectConfig> s : roleBinding.subjects.entrySet()) {
String subjectName = s.getValue().name.orElse(s.getKey());
SubjectConfig subject = s.getValue();
subjects.add(new KubernetesRoleBindingBuildItem.Subject(subject.apiGroup.orElse(null),
subjects.add(new Subject(subject.apiGroup.orElse(null),
subject.kind,
subjectName,
subject.namespace.orElse(null)));
Expand All @@ -384,8 +386,34 @@ private static Collection<DecoratorBuildItem> createRbacDecorators(String name,
result.add(new DecoratorBuildItem(target, new AddRoleBindingResourceDecorator(name,
rbName,
roleBinding.labels,
new KubernetesRoleBindingBuildItem.RoleRef(roleName, clusterWide),
subjects.toArray(new KubernetesRoleBindingBuildItem.Subject[0]))));
new RoleRef(roleName, clusterWide),
subjects.toArray(new Subject[0]))));
}

// Add cluster role bindings from configuration
for (Map.Entry<String, ClusterRoleBindingConfig> rb : config.getRbacConfig().clusterRoleBindings.entrySet()) {
String rbName = rb.getValue().name.orElse(rb.getKey());
ClusterRoleBindingConfig clusterRoleBinding = rb.getValue();

List<Subject> subjects = new ArrayList<>();
if (clusterRoleBinding.subjects.isEmpty()) {
throw new IllegalStateException("No subjects have been set in the ClusterRoleBinding resource!");
}

for (Map.Entry<String, SubjectConfig> s : clusterRoleBinding.subjects.entrySet()) {
String subjectName = s.getValue().name.orElse(s.getKey());
SubjectConfig subject = s.getValue();
subjects.add(new Subject(subject.apiGroup.orElse(null),
subject.kind,
subjectName,
subject.namespace.orElse(null)));
}

result.add(new DecoratorBuildItem(target, new AddClusterRoleBindingResourceDecorator(name,
rbName,
clusterRoleBinding.labels,
new RoleRef(clusterRoleBinding.roleName, true),
subjects.toArray(new Subject[0]))));
}

// if no role bindings were created, then automatically create one if:
Expand All @@ -396,8 +424,8 @@ private static Collection<DecoratorBuildItem> createRbacDecorators(String name,
result.add(new DecoratorBuildItem(target, new AddRoleBindingResourceDecorator(name,
name,
Collections.emptyMap(),
new KubernetesRoleBindingBuildItem.RoleRef(defaultRoleName, defaultClusterWide),
new KubernetesRoleBindingBuildItem.Subject(null, SERVICE_ACCOUNT,
new RoleRef(defaultRoleName, defaultClusterWide),
new Subject(null, SERVICE_ACCOUNT,
defaultIfEmpty(defaultServiceAccount, config.getServiceAccount().orElse(name)),
defaultServiceAccountNamespace))));
} else if (kubernetesClientRequiresRbacGeneration) {
Expand All @@ -407,8 +435,8 @@ private static Collection<DecoratorBuildItem> createRbacDecorators(String name,
result.add(new DecoratorBuildItem(target, new AddRoleBindingResourceDecorator(name,
name + "-" + DEFAULT_ROLE_NAME_VIEW,
Collections.emptyMap(),
new KubernetesRoleBindingBuildItem.RoleRef(DEFAULT_ROLE_NAME_VIEW, true),
new KubernetesRoleBindingBuildItem.Subject(null, SERVICE_ACCOUNT,
new RoleRef(DEFAULT_ROLE_NAME_VIEW, true),
new Subject(null, SERVICE_ACCOUNT,
defaultIfEmpty(defaultServiceAccount, config.getServiceAccount().orElse(name)),
defaultServiceAccountNamespace))));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ public class RbacConfig {
*/
@ConfigItem
Map<String, RoleBindingConfig> roleBindings;

/**
* List of cluster role bindings to generate.
*/
@ConfigItem
Map<String, ClusterRoleBindingConfig> clusterRoleBindings;
}

0 comments on commit 3c95724

Please sign in to comment.