Skip to content

Commit

Permalink
feat: Kubernetes support of SecurityContext configuration
Browse files Browse the repository at this point in the history
Allow to configure the security context section in the pods for Kubernetes, OpenShift and Knative.

Fix #23866
  • Loading branch information
Sgitario committed Mar 4, 2022
1 parent f1586f4 commit d3a366b
Show file tree
Hide file tree
Showing 12 changed files with 392 additions and 5 deletions.
35 changes: 35 additions & 0 deletions docs/src/main/asciidoc/deploying-to-kubernetes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ The table below describe all the available configuration options.
| quarkus.kubernetes.resources.requests.memory | String | |
| quarkus.kubernetes.resources.limits.cpu | String | |
| quarkus.kubernetes.resources.limits.memory | String | |
| quarkus.kubernetes.security-context | SecurityContext | | ( see SecurityContext )
|====

Properties that use non-standard types, can be referenced by expanding the property.
Expand Down Expand Up @@ -796,6 +797,38 @@ Allowed values: `cluster-ip`, `node-port`, `load-balancer`, `external-name`
| hostnames | String[] | list of hostnames |
|====

.SecurityContext
|====
| Property | Type | Description | Default Value
| se-linux-options | SeLinuxOptions | |
| windows-options | WindowsOptions | |
| run-as-user | long | |
| run-as-group | long | |
| run-as-non-root | boolean | |
| supplemental-groups | long[] | |
| fs-group | long | |
| sysctls | Map<String, String> | |
| fs-group-change-policy | String | |
|====

.SeLinuxOptions
|====
| Property | Type | Description | Default Value
| level | String | |
| role | String | |
| user | String | |
| type | String | |
|====

.WindowsOptions
|====
| Property | Type | Description | Default Value
| gmsa-credential-spec-name | String | |
| gmsa-credential-spec | String | |
| run-as-user-name | String | |
| host-process | boolean | |
|====

==== Mounts and Volumes

.Mount
Expand Down Expand Up @@ -994,6 +1027,7 @@ The OpenShift resources can be customized in a similar approach with Kubernetes.
| quarkus.openshift.route.host | String | |
| quarkus.openshift.route.annotations | Map<String, String> | |
| quarkus.openshift.headless | boolean | | false
| quarkus.openshift.security-context | SecurityContext | | ( see SecurityContext )
|====

[#knative]
Expand Down Expand Up @@ -1097,6 +1131,7 @@ The generated service can be customized using the following properties:
| quarkus.knative.scale-to-zero-enabled | boolean | See link:https://knative.dev/docs/serving/autoscaling/scale-to-zero/#enable-scale-to-zero[link] | true
| quarkus.knative.revision-auto-scaling | AutoScalingConfig | | ( see AutoScalingConfig )
| quarkus.knative.global-auto-scaling | GlobalAutoScalingConfig | | ( see GlobalAutoScalingConfig )
| quarkus.knative.security-context | SecurityContext | | ( see SecurityContext )
|====

.Traffic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.quarkus.kubernetes.deployment;

import java.util.Optional;

import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.NamedResourceDecorator;
import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;
import io.fabric8.kubernetes.api.model.PodSpecFluent;
import io.fabric8.kubernetes.api.model.SELinuxOptions;
import io.fabric8.kubernetes.api.model.SELinuxOptionsBuilder;
import io.fabric8.kubernetes.api.model.SysctlBuilder;
import io.fabric8.kubernetes.api.model.WindowsSecurityContextOptions;
import io.fabric8.kubernetes.api.model.WindowsSecurityContextOptionsBuilder;

public class ApplySecuritySettingsDecorator extends NamedResourceDecorator<PodSpecFluent> {

private final SecurityContextConfig securityContext;

public ApplySecuritySettingsDecorator(String resourceName, SecurityContextConfig securityContext) {
super(resourceName);
this.securityContext = securityContext;
}

@Override
public void andThenVisit(PodSpecFluent podSpec, ObjectMeta resourceMeta) {
PodSecurityContextBuilder securityContextBuilder = new PodSecurityContextBuilder();

securityContext.runAsUser.ifPresent(securityContextBuilder::withRunAsUser);
securityContext.runAsGroup.ifPresent(securityContextBuilder::withRunAsGroup);
securityContext.runAsNonRoot.ifPresent(securityContextBuilder::withRunAsNonRoot);
securityContext.supplementalGroups.ifPresent(securityContextBuilder::addAllToSupplementalGroups);
securityContext.fsGroup.ifPresent(securityContextBuilder::withFsGroup);
securityContext.sysctls.ifPresent(map -> map.entrySet().stream()
.map(entry -> new SysctlBuilder().withName(entry.getKey()).withValue(entry.getValue()).build())
.forEach(securityContextBuilder::addToSysctls));
securityContext.fsGroupChangePolicy.map(e -> e.name()).ifPresent(securityContextBuilder::withFsGroupChangePolicy);
buildSeLinuxOptions().ifPresent(securityContextBuilder::withSeLinuxOptions);
buildWindowsOptions().ifPresent(securityContextBuilder::withWindowsOptions);

podSpec.withSecurityContext(securityContextBuilder.build());
}

@Override
public Class<? extends Decorator>[] after() {
return new Class[] { ResourceProvidingDecorator.class };
}

private Optional<WindowsSecurityContextOptions> buildWindowsOptions() {
WindowsSecurityContextOptions item = null;
if (securityContext.windowsOptions.isAnyPropertySet()) {
WindowsSecurityContextOptionsBuilder builder = new WindowsSecurityContextOptionsBuilder();
securityContext.windowsOptions.gmsaCredentialSpec.ifPresent(builder::withGmsaCredentialSpec);
securityContext.windowsOptions.gmsaCredentialSpecName.ifPresent(builder::withGmsaCredentialSpecName);
securityContext.windowsOptions.hostProcess.ifPresent(builder::withHostProcess);
securityContext.windowsOptions.runAsUserName.ifPresent(builder::withRunAsUserName);
item = builder.build();
}

return Optional.ofNullable(item);
}

private Optional<SELinuxOptions> buildSeLinuxOptions() {
SELinuxOptions item = null;
if (securityContext.seLinuxOptions.isAnyPropertySet()) {
SELinuxOptionsBuilder builder = new SELinuxOptionsBuilder();
securityContext.seLinuxOptions.user.ifPresent(builder::withUser);
securityContext.seLinuxOptions.role.ifPresent(builder::withRole);
securityContext.seLinuxOptions.level.ifPresent(builder::withLevel);
securityContext.seLinuxOptions.type.ifPresent(builder::withType);
item = builder.build();
}

return Optional.ofNullable(item);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -435,11 +435,22 @@ public EnvVarsConfig getEnv() {
@ConfigItem
Optional<String> appConfigMap;

/**
* If set, it will copy the security context configuration provided into the generated pod settings.
*/
@ConfigItem
SecurityContextConfig securityContext;

public Optional<String> getAppSecret() {
return this.appSecret;
}

public Optional<String> getAppConfigMap() {
return this.appConfigMap;
}

@Override
public SecurityContextConfig getSecurityContext() {
return securityContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ private static List<DecoratorBuildItem> createPodDecorators(Optional<Project> pr
result.add(new DecoratorBuildItem(target, new ApplyRequestsMemoryDecorator(name, m)));
});

if (config.getSecurityContext().isAnyPropertySet()) {
result.add(new DecoratorBuildItem(target, new ApplySecuritySettingsDecorator(name, config.getSecurityContext())));
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,18 @@ public enum DeploymentResourceKind {
Optional<String> appSecret;

/**
* If set, the config amp will mounted to the application container and its contents will be used for application
* If set, the config map will be mounted to the application container and its contents will be used for application
* configuration.
*/
@ConfigItem
Optional<String> appConfigMap;

/**
* If set, it will copy the security context configuration provided into the generated pod settings.
*/
@ConfigItem
SecurityContextConfig securityContext;

public Optional<String> getPartOf() {
return partOf;
}
Expand Down Expand Up @@ -485,4 +491,9 @@ public Optional<String> getAppConfigMap() {
public Optional<ExpositionConfig> getExposition() {
return Optional.of(ingress);
}

@Override
public SecurityContextConfig getSecurityContext() {
return securityContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@ public EnvVarsConfig getEnv() {
@ConfigItem
Optional<String> appConfigMap;

/**
* If set, it will copy the security context configuration provided into the generated pod settings.
*/
@ConfigItem
SecurityContextConfig securityContext;

public Optional<String> getAppSecret() {
return this.appSecret;
}
Expand All @@ -510,6 +516,11 @@ public Optional<ExpositionConfig> getExposition() {
return Optional.of(route);
}

@Override
public SecurityContextConfig getSecurityContext() {
return securityContext;
}

public static boolean isOpenshiftBuildEnabled(ContainerImageConfig containerImageConfig, Capabilities capabilities) {
boolean implictlyEnabled = ContainerImageCapabilitiesUtil.getActiveContainerImageCapability(capabilities)
.filter(c -> c.contains(OPENSHIFT) || c.contains(S2I)).isPresent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ default String getConfigName() {
return getClass().getSimpleName().replaceAll("Config$", "").toLowerCase();
}

public Optional<String> getAppSecret();
Optional<String> getAppSecret();

public Optional<String> getAppConfigMap();
Optional<String> getAppConfigMap();

SecurityContextConfig getSecurityContext();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package io.quarkus.kubernetes.deployment;

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

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

@ConfigGroup
public class SecurityContextConfig {

/**
* SELinuxOptions to be applied to the container.
*/
SeLinuxOptions seLinuxOptions;

/**
* The Windows specific settings applied to all containers.
*/
WindowsOptions windowsOptions;

/**
* The UID to run the entrypoint of the container process.
*/
@ConfigItem
Optional<Long> runAsUser;

/**
* The GID to run the entrypoint of the container process.
*/
@ConfigItem
Optional<Long> runAsGroup;

/**
* Indicates that the container must run as a non-root user.
*/
@ConfigItem
Optional<Boolean> runAsNonRoot;

/**
* A list of groups applied to the first process run in each container, in addition to the container's primary GID.
* If unspecified, no groups will be added to any container.
*/
@ConfigItem
Optional<List<Long>> supplementalGroups;

/**
* A special supplemental group that applies to all containers in a pod.
*/
@ConfigItem
Optional<Long> fsGroup;

/**
* Sysctls hold a list of namespaced sysctls used for the pod.
*/
@ConfigItem
Optional<Map<String, String>> sysctls;

/**
* It holds policies that will be used for applying fsGroup to a volume when volume is mounted.
* Values: OnRootMismatch, Always
*/
@ConfigItem
Optional<PodFSGroupChangePolicy> fsGroupChangePolicy;

protected boolean isAnyPropertySet() {
return seLinuxOptions.isAnyPropertySet() || windowsOptions.isAnyPropertySet() || runAsUser.isPresent()
|| runAsGroup.isPresent() || runAsNonRoot.isPresent() || supplementalGroups.isPresent()
|| fsGroup.isPresent() || sysctls.isPresent() || fsGroupChangePolicy.isPresent();
}

@ConfigGroup
public static class SeLinuxOptions {

/**
* The SELinux level label that applies to the container.
*/
@ConfigItem
Optional<String> level;

/**
* The SELinux role label that applies to the container.
*/
@ConfigItem
Optional<String> role;

/**
* The SELinux type label that applies to the container.
*/
@ConfigItem
Optional<String> type;

/**
* The SELinux user label that applies to the container.
*/
@ConfigItem
Optional<String> user;

protected boolean isAnyPropertySet() {
return level.isPresent() || role.isPresent() || type.isPresent() || user.isPresent();
}
}

@ConfigGroup
public static class WindowsOptions {

/**
* The name of the GMSA credential spec to use.
*/
@ConfigItem
Optional<String> gmsaCredentialSpecName;

/**
* GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the
* contents of the GMSA credential spec named by the GMSACredentialSpecName field.
*/
@ConfigItem
Optional<String> gmsaCredentialSpec;

/**
* The UserName in Windows to run the entrypoint of the container process.
*/
@ConfigItem
Optional<String> runAsUserName;

/**
* HostProcess determines if a container should be run as a 'Host Process' container.
*/
@ConfigItem
Optional<Boolean> hostProcess;

protected boolean isAnyPropertySet() {
return gmsaCredentialSpecName.isPresent() || gmsaCredentialSpec.isPresent() || runAsUserName.isPresent()
|| hostProcess.isPresent();
}
}

public enum PodFSGroupChangePolicy {
/**
* It indicates that volume's ownership and permissions will be changed only when permission and ownership of root
* directory does not match with expected permissions on the volume.
*/
OnRootMismatch,
/**
* It indicates that volume's ownership and permissions should always be changed whenever volume is mounted inside a
* Pod. This the default behavior.
*/
Always;
}
}
Loading

0 comments on commit d3a366b

Please sign in to comment.