Skip to content
This repository has been archived by the owner on Dec 4, 2023. It is now read-only.

Fix #2714: Allow setting security context of pods using persistent volumes #3538

Merged
merged 2 commits into from
Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.31.0
* #2714: Allow setting security context of pods using persistent volumes

## 0.30.0
* Fix go tool vet on newer versions of go
* #3247: Fix path to probe endpoint used when running on Kubernetes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,22 @@
import java.nio.file.Files;
import java.util.*;

import static io.enmasse.address.model.KubeUtil.applyPodTemplate;
import static io.enmasse.address.model.KubeUtil.lookupResource;
import static io.enmasse.address.model.KubeUtil.*;

public class TemplateInfraResourceFactory implements InfraResourceFactory {
private static final String FS_GROUP_OVERRIDE = "FS_GROUP_OVERRIDE";
private static final Logger log = LoggerFactory.getLogger(TemplateInfraResourceFactory.class);
private final String WELL_KNOWN_CONSOLE_SERVICE_NAME = "console";

private final Kubernetes kubernetes;
private final AuthenticationServiceResolver authenticationServiceResolver;
private final Map<String, String> env;
private final SchemaProvider schemaProvider;
private final Long fsGroupOverride;

public TemplateInfraResourceFactory(Kubernetes kubernetes, AuthenticationServiceResolver authenticationServiceResolver, Map<String, String> env, SchemaProvider schemaProvider) {
this.kubernetes = kubernetes;
this.authenticationServiceResolver = authenticationServiceResolver;
this.env = env;
this.schemaProvider = schemaProvider;
this.fsGroupOverride = getFsGroupOverride();
}

private void prepareParameters(AddressSpace addressSpace,
Expand Down Expand Up @@ -184,10 +180,7 @@ private List<HasMetadata> createStandardInfra(AddressSpace addressSpace, Standar
setIfEnvPresent(parameters, TemplateParameter.BROKER_PLUGIN_IMAGE);
setIfEnvPresent(parameters, TemplateParameter.TOPIC_FORWARDER_IMAGE);
setIfEnvPresent(parameters, TemplateParameter.IMAGE_PULL_POLICY);

if (fsGroupOverride != null) {
parameters.put(TemplateParameter.FS_GROUP_OVERRIDE, fsGroupOverride.toString());
}
setIfEnvPresent(parameters, TemplateParameter.FS_GROUP_FALLBACK_MAP);

Map<String, String> infraAnnotations = standardInfraConfig.getMetadata().getAnnotations();
String templateName = getAnnotation(infraAnnotations, AnnotationKeys.TEMPLATE_NAME, "standard-space-infra");
Expand Down Expand Up @@ -290,25 +283,11 @@ private List<HasMetadata> createBrokeredInfra(AddressSpace addressSpace, Brokere
PodTemplateSpec actualPodTemplate = brokerDeployment.getSpec().getTemplate();
applyPodTemplate(actualPodTemplate, podTemplate);
}

if (this.fsGroupOverride != null) {
KubeUtil.applyFsGroupOverride(Collections.singletonList(brokerDeployment), this.fsGroupOverride);
}
overrideFsGroup(brokerDeployment.getSpec().getTemplate(), "broker", kubernetes.getNamespace());

return items;
}

private Long getFsGroupOverride() {
Long fsGroupOverride = null;
if (env.containsKey(FS_GROUP_OVERRIDE)) {
try {
fsGroupOverride = Long.parseLong(env.get(FS_GROUP_OVERRIDE));
} catch (NumberFormatException ignore) {
}
}
return fsGroupOverride;
}

private List<HasMetadata> applyStorageClassName(String storageClassName, List<HasMetadata> items) {
if (storageClassName != null) {
for (HasMetadata item : items) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ public interface TemplateParameter {
String CONSOLE_OAUTH_DISCOVERY_URL = "CONSOLE_OAUTH_DISCOVERY_URL";
String CONSOLE_OAUTH_SCOPE = "CONSOLE_OAUTH_SCOPE";
String CONSOLE_LINK = "CONSOLE_LINK";
String FS_GROUP_OVERRIDE = "FS_GROUP_OVERRIDE";
String FS_GROUP_FALLBACK_MAP = "FS_GROUP_FALLBACK_MAP";
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ objects:
value: ${TOPIC_FORWARDER_IMAGE}
- name: IMAGE_PULL_POLICY
value: ${IMAGE_PULL_POLICY}
- name: FS_GROUP_OVERRIDE
value: ${FS_GROUP_OVERRIDE}
- name: FS_GROUP_FALLBACK_MAP
value: ${FS_GROUP_FALLBACK_MAP}
image: ${STANDARD_CONTROLLER_IMAGE}
imagePullPolicy: ${IMAGE_PULL_POLICY}
livenessProbe:
Expand Down Expand Up @@ -608,4 +608,4 @@ parameters:
- description:
name: CONSOLE_LINK
- description:
name: FS_GROUP_OVERRIDE
name: FS_GROUP_FALLBACK_MAP
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2019, EnMasse authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/

package io.enmasse.address.model;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;

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

public class FsGroupFallback {

private static TypeReference<Map<String, Long>> MAP_STRING_LONG = new TypeReference<>() {
};

private static final Map<String, Long> FS_GROUP_FALLBACK;

static {
final String json = System.getenv().get("FS_GROUP_FALLBACK_MAP");
if (Strings.isNullOrEmpty(json)) {
FS_GROUP_FALLBACK = new HashMap<>();
} else {
ObjectMapper objectMapper = new ObjectMapper();
try {
FS_GROUP_FALLBACK = objectMapper.readValue(json, MAP_STRING_LONG);
} catch (JsonProcessingException e) {
throw new ExceptionInInitializerError(e);
}
}
}

public static Long getFsGroupOverride(String component) {
return FS_GROUP_FALLBACK.get(component);
}
}
39 changes: 17 additions & 22 deletions api-model/src/main/java/io/enmasse/address/model/KubeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.Probe;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -195,6 +192,10 @@ public static void applyPodTemplate(PodTemplateSpec actual, PodTemplateSpec desi
actualPodSpec.setTolerations(podSpec.getTolerations());
}

if (podSpec.getSecurityContext() != null) {
actualPodSpec.setSecurityContext(podSpec.getSecurityContext());
}

for (Container desiredContainer : podSpec.getInitContainers()) {
for (Container actualContainer : actualPodSpec.getInitContainers()) {
if (actualContainer.getName() != null && actualContainer.getName().equals(desiredContainer.getName())) {
Expand Down Expand Up @@ -291,23 +292,17 @@ public static boolean isNameValid(final String name) {
}
}

public static void applyFsGroupOverride(List<HasMetadata> items, Long fsGroupOverride) {
items.stream().filter(i -> i instanceof Deployment || i instanceof StatefulSet).forEach(
i -> {
PodSpec spec;
if (i instanceof StatefulSet) {
spec = ((StatefulSet) i).getSpec().getTemplate().getSpec();
} else {
spec = ((Deployment) i).getSpec().getTemplate().getSpec();
}

PodSecurityContext securityContext = new PodSecurityContext();
if (spec.getSecurityContext() != null) {
securityContext = spec.getSecurityContext();
}
securityContext.setFsGroup(fsGroupOverride);
spec.setSecurityContext(securityContext);
}
);
public static void overrideFsGroup(PodTemplateSpec target, String component, String namespace) {
final Long fsGroupOverride = FsGroupFallback.getFsGroupOverride(component);
if (Objects.equals(namespace, "openshift-operators") && fsGroupOverride != null) {
PodSecurityContext securityContext = target.getSpec().getSecurityContext();
if (securityContext == null) {
securityContext = new PodSecurityContext();
}
if (securityContext.getFsGroup() == null) {
securityContext.setFsGroup(fsGroupOverride);
target.getSpec().setSecurityContext(securityContext);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.fabric8.kubernetes.api.model.Doneable;
import io.fabric8.kubernetes.api.model.PodSecurityContext;
import io.fabric8.kubernetes.api.model.SecretReference;
import io.sundr.builder.annotations.Buildable;
import io.sundr.builder.annotations.BuildableReference;
Expand All @@ -23,12 +24,13 @@
},
inline = @Inline(type = Doneable.class, prefix = "Doneable", value = "done")
)
@JsonPropertyOrder({"credentialsSecret", "certificateSecret", "storage"})
@JsonPropertyOrder({"credentialsSecret", "certificateSecret", "storage", "securityContext"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AuthenticationServiceSpecStandard extends AbstractWithAdditionalProperties {
private SecretReference credentialsSecret;
private SecretReference certificateSecret;
private AuthenticationServiceSpecStandardStorage storage;
private PodSecurityContext securityContext;

public SecretReference getCredentialsSecret() {
return credentialsSecret;
Expand All @@ -45,12 +47,13 @@ public boolean equals(Object o) {
AuthenticationServiceSpecStandard that = (AuthenticationServiceSpecStandard) o;
return Objects.equals(credentialsSecret, that.credentialsSecret) &&
Objects.equals(certificateSecret, that.certificateSecret) &&
Objects.equals(storage, that.storage);
Objects.equals(storage, that.storage) &&
Objects.equals(securityContext, that.securityContext);
}

@Override
public int hashCode() {
return Objects.hash(credentialsSecret, certificateSecret, storage);
return Objects.hash(credentialsSecret, certificateSecret, storage, securityContext);
}

@Override
Expand All @@ -59,6 +62,7 @@ public String toString() {
"credentialsSecret=" + credentialsSecret +
", certificateSecret=" + certificateSecret +
", storage=" + storage +
", securityContext=" + securityContext +
'}';
}

Expand All @@ -77,4 +81,12 @@ public AuthenticationServiceSpecStandardStorage getStorage() {
public void setStorage(AuthenticationServiceSpecStandardStorage storage) {
this.storage = storage;
}

public PodSecurityContext getSecurityContext() {
return securityContext;
}

public void setSecurityContext(PodSecurityContext securityContext) {
this.securityContext = securityContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ public void appliesContainerOrderIgnored() {
assertThat(fooContainer.getResources(), equalTo(resources));
}

@Test
public void appliesSecurityContext() {

PodTemplateSpec actual = new PodTemplateSpecBuilder()
.withNewSpec()
.endSpec()
.build();

final long runAsGroup = 1000L;
final long fsGroup = 1234L;
PodTemplateSpec desired = new PodTemplateSpecBuilder()
.withNewSpec()
.withNewSecurityContext()
.withRunAsGroup(runAsGroup)
.withFsGroup(fsGroup)
.endSecurityContext()
.endSpec()
.build();

KubeUtil.applyPodTemplate(actual, desired);

assertThat(actual.getSpec().getSecurityContext().getFsGroup(), equalTo(fsGroup));
assertThat(actual.getSpec().getSecurityContext().getRunAsGroup(), equalTo(runAsGroup));
}

@Test
public void appliesContainerEnvVarToPodTemplate() {
Container actualContainer = new ContainerBuilder()
Expand Down
3 changes: 3 additions & 0 deletions api-server/src/main/resources/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@
"priorityClassName": {
"type": "string"
},
"securityContext": {
"type": "object"
},
"tolerations": {
"items": {
"type": "object"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,8 @@ spec:
value: "${env.CONSOLE_PROXY_KUBERNETES_IMAGE}"
- name: CONSOLE_HTTPD_IMAGE
value: "${env.CONSOLE_HTTPD_IMAGE}"
- name: FS_GROUP_STANDARD_AUTHSERVICE_OVERRIDE
value: "1000"
- name: "FS_GROUP_FALLBACK_MAP"
value: "{\"standard-authservice\": 1000}"
- name: user-api-server
spec:
replicas: 1
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/admin/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type AuthenticationServiceSpecStandard struct {
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
Storage *AuthenticationServiceSpecStandardStorage `json:"storage,omitempty"`
Datasource *AuthenticationServiceSpecStandardDatasource `json:"datasource,omitempty"`
SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"`
}

type StorageType string
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/admin/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/apis/iot/v1alpha1/types_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ type AuthenticationServiceConfig struct {
type FileBasedDeviceRegistry struct {
NumberOfDevicesPerTenant *uint32 `json:"numberOfDevicesPerTenant,omitempty"`
CommonServiceConfig `json:",inline"`
SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"`
}

type InfinispanDeviceRegistry struct {
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions pkg/controller/address_space_controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,8 @@ func ApplyDeployment(deployment *appsv1.Deployment) error {
install.ApplyEnvSimple(container, "ENABLE_EVENT_LOGGER", "true")
install.ApplyEnvSimple(container, "TEMPLATE_DIR", "/opt/templates")
install.ApplyEnvSimple(container, "RESOURCES_DIR", "/opt")

value, ok := os.LookupEnv("FS_GROUP_OVERRIDE")
if ok {
install.ApplyEnvSimple(container, "FS_GROUP_OVERRIDE", value)
if value, ok := os.LookupEnv("FS_GROUP_FALLBACK_MAP"); ok {
install.ApplyOrRemoveEnvSimple(container, "FS_GROUP_FALLBACK_MAP", value)
}

t := true
Expand Down
10 changes: 1 addition & 9 deletions pkg/controller/authenticationservice/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,7 @@ func applyStandardAuthServiceDeployment(authservice *adminv1beta1.Authentication

install.ApplyDeploymentDefaults(deployment, "standard-authservice", *authservice.Spec.Standard.DeploymentName)

err := install.ApplyFsGroupOverride("FS_GROUP_OVERRIDE", deployment)
if err != nil {
return err
}

err = install.ApplyFsGroupOverride("FS_GROUP_STANDARD_AUTHSERVICE_OVERRIDE", deployment)
if err != nil {
return err
}
install.OverrideSecurityContextFsGroup("standard-authservice", authservice.Spec.Standard.SecurityContext, deployment)

if err := install.ApplyInitContainerWithError(deployment, "keycloak-plugin", func(container *corev1.Container) error {
if err := install.ApplyContainerImage(container, "keycloak-plugin", authservice.Spec.Standard.InitImage); err != nil {
Expand Down
Loading