Skip to content

Commit

Permalink
Merge pull request #24089 from Sgitario/feat_k8s_security_context
Browse files Browse the repository at this point in the history
Kubernetes support of SecurityContext configuration
  • Loading branch information
gsmet authored Mar 21, 2022
2 parents 7d435c8 + d3a366b commit 5bad76e
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 @@ -728,6 +728,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 @@ -812,6 +813,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 @@ -1012,6 +1045,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 @@ -1115,6 +1149,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 5bad76e

Please sign in to comment.