Skip to content

Commit

Permalink
Add ws attribute to work around arbitrary user ID
Browse files Browse the repository at this point in the history
Add the workspace attribute 'supportArbitraryUser' to enable an attempt
at working around containers running with arbitrary user ID on
OpenShift.

When the attribute is set to 'true', Che will modify the recipe containers'
command and arguments to attempt to add an entry to /etc/passwd for the
current user ID. This resolves various problems around programs that
depend on username being available when running (e.g. maven, bash).

If the command fails (e.g. because /etc/passwd is not writable), the
original container command will still execute.

Signed-off-by: Angel Misevski <amisevsk@redhat.com>
  • Loading branch information
amisevsk committed Jun 20, 2019
1 parent 2add904 commit 9963f94
Show file tree
Hide file tree
Showing 7 changed files with 413 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner;
import org.slf4j.Logger;
Expand Down Expand Up @@ -66,6 +67,7 @@ public class OpenShiftEnvironmentProvisioner
private final ProxySettingsProvisioner proxySettingsProvisioner;
private final ServiceAccountProvisioner serviceAccountProvisioner;
private final CertificateProvisioner certificateProvisioner;
private final OpenShiftCommandProvisioner commandProvisioner;

@Inject
public OpenShiftEnvironmentProvisioner(
Expand All @@ -83,7 +85,8 @@ public OpenShiftEnvironmentProvisioner(
ImagePullSecretProvisioner imagePullSecretProvisioner,
ProxySettingsProvisioner proxySettingsProvisioner,
ServiceAccountProvisioner serviceAccountProvisioner,
CertificateProvisioner certificateProvisioner) {
CertificateProvisioner certificateProvisioner,
OpenShiftCommandProvisioner commandProvisioner) {
this.pvcEnabled = pvcEnabled;
this.volumesStrategy = volumesStrategy;
this.uniqueNamesProvisioner = uniqueNamesProvisioner;
Expand All @@ -99,6 +102,7 @@ public OpenShiftEnvironmentProvisioner(
this.proxySettingsProvisioner = proxySettingsProvisioner;
this.serviceAccountProvisioner = serviceAccountProvisioner;
this.certificateProvisioner = certificateProvisioner;
this.commandProvisioner = commandProvisioner;
}

@Override
Expand Down Expand Up @@ -133,6 +137,7 @@ public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity)
proxySettingsProvisioner.provision(osEnv, identity);
serviceAccountProvisioner.provision(osEnv, identity);
certificateProvisioner.provision(osEnv, identity);
commandProvisioner.provision(osEnv, identity);
LOG.debug(
"Provisioning OpenShift environment done for workspace '{}'", identity.getWorkspaceId());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.openshift.provision;

import static org.eclipse.che.api.workspace.shared.Constants.ARBITRARY_USER_ATTRIBUTE;
import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE;
import static org.eclipse.che.api.workspace.shared.Constants.RECIPE_CONTAINER_SOURCE;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import io.fabric8.kubernetes.api.model.Container;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;

public class OpenShiftCommandProvisioner implements ConfigurationProvisioner<OpenShiftEnvironment> {

@VisibleForTesting protected static final List<String> SHELL_BINARY = ImmutableList.of("/bin/sh");
@VisibleForTesting protected static final String SHELL_ARGS = "-c";

@VisibleForTesting
protected static final String ADD_USER_COMMAND =
"if ! whoami &> /dev/null && [ -w /etc/passwd ]; then "
+ "echo \"user:x:$(id -u):0:user user:projects/:/bin/bash\" >> /etc/passwd;"
+ "fi;";

@VisibleForTesting protected static final String COMMAND_FORMAT = "%s %s %s";

@Override
public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity)
throws InfrastructureException {

if (!supportArbitraryUser(osEnv.getAttributes())) {
return;
}

Set<String> recipeMachineNames =
osEnv
.getMachines()
.entrySet()
.stream()
.filter(
e ->
RECIPE_CONTAINER_SOURCE.equals(
e.getValue().getAttributes().get(CONTAINER_SOURCE_ATTRIBUTE)))
.map(Entry::getKey)
.collect(Collectors.toSet());

for (PodData podData : osEnv.getPodsData().values()) {
for (Container container : podData.getSpec().getContainers()) {
String machineName = Names.machineName(podData, container);
if (recipeMachineNames.contains(machineName)) {
rewriteContainerCommand(container);
}
}
}
}

private void rewriteContainerCommand(Container container) {
List<String> defaultCommand = container.getCommand();
List<String> defaultArgs = container.getArgs();

if (defaultCommand == null || defaultCommand.size() == 0) {
return;
}

String script =
String.format(
COMMAND_FORMAT,
ADD_USER_COMMAND,
String.join(" ", defaultCommand),
String.join(" ", defaultArgs));
container.setCommand(SHELL_BINARY);
container.setArgs(ImmutableList.of(SHELL_ARGS, script));
}

private boolean supportArbitraryUser(Map<String, String> workspaceAttributes) {
String supportArbitraryUser = workspaceAttributes.get(ARBITRARY_USER_ATTRIBUTE);
return "true".equals(supportArbitraryUser);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner;
import org.mockito.InOrder;
Expand Down Expand Up @@ -61,6 +62,7 @@ public class OpenShiftEnvironmentProvisionerTest {
@Mock private ProxySettingsProvisioner proxySettingsProvisioner;
@Mock private ServiceAccountProvisioner serviceAccountProvisioner;
@Mock private CertificateProvisioner certificateProvisioner;
@Mock private OpenShiftCommandProvisioner commandProvisioner;

private OpenShiftEnvironmentProvisioner osInfraProvisioner;

Expand All @@ -84,7 +86,8 @@ public void setUp() {
imagePullSecretProvisioner,
proxySettingsProvisioner,
serviceAccountProvisioner,
certificateProvisioner);
certificateProvisioner,
commandProvisioner);
provisionOrder =
inOrder(
installerServersPortProvisioner,
Expand All @@ -100,7 +103,8 @@ public void setUp() {
imagePullSecretProvisioner,
proxySettingsProvisioner,
serviceAccountProvisioner,
certificateProvisioner);
certificateProvisioner,
commandProvisioner);
}

@Test
Expand All @@ -125,6 +129,7 @@ public void performsOrderedProvisioning() throws Exception {
provisionOrder.verify(proxySettingsProvisioner).provision(eq(osEnv), eq(runtimeIdentity));
provisionOrder.verify(serviceAccountProvisioner).provision(eq(osEnv), eq(runtimeIdentity));
provisionOrder.verify(certificateProvisioner).provision(eq(osEnv), eq(runtimeIdentity));
provisionOrder.verify(commandProvisioner).provision(eq(osEnv), eq(runtimeIdentity));
provisionOrder.verifyNoMoreInteractions();
}
}
Loading

0 comments on commit 9963f94

Please sign in to comment.