Skip to content

Commit

Permalink
SSH Auth for Git Init #69
Browse files Browse the repository at this point in the history
* implement injecting ssh key and password to init container
* adjust git init script to work with any user id
  • Loading branch information
jfaltermeier committed May 17, 2023
1 parent 6d368df commit c547548
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 11 deletions.
5 changes: 4 additions & 1 deletion dockerfiles/git-init/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ FROM debian:11-slim

RUN apt update && \
apt install python git -y && \
apt clean
apt clean && \
mkdir /user && \
chmod 777 /user && \
chmod 777 /etc/passwd

WORKDIR /tmp
COPY python/git-init/entrypoint.sh .
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public static void addInitContainers(String correlationId, TheiaCloudClient clie
}
}

public static Volume createVolume(String pvcName) {
public static Volume createUserDataVolume(String pvcName) {
Volume volume = new Volume();
volume.setName(USER_DATA);
PersistentVolumeClaimVolumeSource persistentVolumeClaim = new PersistentVolumeClaimVolumeSource();
Expand All @@ -351,7 +351,7 @@ public static Volume createVolume(String pvcName) {
return volume;
}

public static VolumeMount createVolumeMount(AppDefinitionSpec appDefinition) {
public static VolumeMount createUserDataVolumeMount(AppDefinitionSpec appDefinition) {
VolumeMount volumeMount = new VolumeMount();
volumeMount.setName(AddedHandlerUtil.USER_DATA);
volumeMount.setMountPath(TheiaCloudPersistentVolumeUtil.getMountPath(appDefinition));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,26 @@
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarSource;
import io.fabric8.kubernetes.api.model.KeyToPath;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretKeySelector;
import io.fabric8.kubernetes.api.model.SecretVolumeSource;
import io.fabric8.kubernetes.api.model.SecurityContext;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.apps.Deployment;

public class GitInitOperationHandler implements InitOperationHandler {

protected static final String ETC_THEIA_CLOUD_SSH = "/etc/theia-cloud-ssh";
protected static final String ID_THEIACLOUD = "id_theiacloud";
protected static final String SSH_PRIVATEKEY = "ssh-privatekey";
protected static final String SSH_KEY = "ssh-key";
protected static final String PASSWORD = "password";
protected static final String USERNAME = "username";
protected static final String GIT_PROMPT1 = "GIT_PROMPT1";
protected static final String GIT_PROMPT2 = "GIT_PROMPT2";
protected static final String KUBERNETES_IO_SSH_AUTH = "kubernetes.io/ssh-auth";
protected static final String KUBERNETES_IO_BASIC_AUTH = "kubernetes.io/basic-auth";
protected static final String HTTPS = "https://";
protected static final String HTTP = "http://";
Expand Down Expand Up @@ -80,6 +88,7 @@ public void addInitContainer(String correlationId, TheiaCloudClient client, Depl
}

List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();
List<Volume> volumes = deployment.getSpec().getTemplate().getSpec().getVolumes();

Container gitInitContainer = new Container();
initContainers.add(gitInitContainer);
Expand All @@ -100,7 +109,7 @@ public void addInitContainer(String correlationId, TheiaCloudClient client, Depl
securityContext.setRunAsUser(Long.valueOf(appDefinition.getSpec().getUid()));
securityContext.setRunAsGroup(Long.valueOf(appDefinition.getSpec().getUid()));

VolumeMount volumeMount = AddedHandlerUtil.createVolumeMount(appDefinition.getSpec());
VolumeMount volumeMount = AddedHandlerUtil.createUserDataVolumeMount(appDefinition.getSpec());
gitInitContainer.getVolumeMounts().add(volumeMount);

String secretName = args.get(2);
Expand Down Expand Up @@ -131,8 +140,9 @@ public void addInitContainer(String correlationId, TheiaCloudClient client, Depl
return;
}
} else {
/* get SSH Key and password from secret */
// TODO JF
if (!injectSSHRepoCredentials(correlationId, secret, secretName, repository, gitInitContainer, volumes)) {
return;
}
}

}
Expand Down Expand Up @@ -195,6 +205,45 @@ protected boolean injectHTTPRepoCredentials(String correlationId, Secret secret,
return true;
}

protected boolean injectSSHRepoCredentials(String correlationId, Secret secret, String secretName,
String repository, Container gitInitContainer, List<Volume> volumes) {

if (!KUBERNETES_IO_SSH_AUTH.equals(secret.getType())) {
LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId, MessageFormat
.format("Secret with name {0} is not of type {1}.", secretName, KUBERNETES_IO_SSH_AUTH)));
return false;
}

/* inject password */
EnvVar envVar = new EnvVar();
gitInitContainer.getEnv().add(envVar);
envVar.setName(GIT_PROMPT1);

EnvVarSource envVarSource = new EnvVarSource();
envVar.setValueFrom(envVarSource);
envVarSource.setSecretKeyRef(new SecretKeySelector(PASSWORD, secretName, false));

/* inject ssh key */
Volume volume = new Volume();
volumes.add(volume);
volume.setName(SSH_KEY);
SecretVolumeSource secretVolumeSource = new SecretVolumeSource();
volume.setSecret(secretVolumeSource);
secretVolumeSource.setSecretName(secretName);
KeyToPath keyToPath = new KeyToPath();
secretVolumeSource.getItems().add(keyToPath);
keyToPath.setKey(SSH_PRIVATEKEY);
keyToPath.setPath(ID_THEIACLOUD);

VolumeMount volumeMount = new VolumeMount();
gitInitContainer.getVolumeMounts().add(volumeMount);
volumeMount.setName(SSH_KEY);
volumeMount.setMountPath(ETC_THEIA_CLOUD_SSH);
volumeMount.setReadOnly(true);

return true;
}

protected static boolean isHTTP(String repository) {
String lowerCasedRepo = repository.toLowerCase(Locale.US);
return (lowerCasedRepo.startsWith(HTTP) || lowerCasedRepo.startsWith(HTTPS));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,10 @@ protected void createAndApplyDeployment(String correlationId, String sessionReso

protected void addVolumeClaim(Deployment deployment, String pvcName, AppDefinitionSpec appDefinition) {
PodSpec podSpec = deployment.getSpec().getTemplate().getSpec();
Volume volume = AddedHandlerUtil.createVolume(pvcName);
Volume volume = AddedHandlerUtil.createUserDataVolume(pvcName);
podSpec.getVolumes().add(volume);
Container container = TheiaCloudPersistentVolumeUtil.getTheiaContainer(podSpec, appDefinition);
VolumeMount volumeMount = AddedHandlerUtil.createVolumeMount(appDefinition);
VolumeMount volumeMount = AddedHandlerUtil.createUserDataVolumeMount(appDefinition);
container.getVolumeMounts().add(volumeMount);
}

Expand Down
49 changes: 49 additions & 0 deletions python/git-init/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ docker run --env GIT_PROMPT1=$SSH_PASSWORD -v ~/tmp/ssh/:/etc/theia-cloud-ssh --

### Create Kubernetes Resources

#### Secret for HTTP(S) auth

```yaml
apiVersion: v1
kind: Secret
Expand All @@ -74,6 +76,8 @@ stringData:
password: pat
```
#### Example Session for HTTP(S) auth
```yaml
apiVersion: theia.cloud/v5beta
kind: Session
Expand All @@ -96,3 +100,48 @@ spec:
- maintenance_1_1_x
- foo-theiacloud-io-basic-auth
```
#### Secrets for SSH auth
```yaml
apiVersion: v1
kind: Secret
metadata:
name: foo-theiacloud-io-ssh-auth
namespace: theiacloud
labels:
theiaCloudInit: git
annotations:
theiaCloudUser: [email protected]
type: kubernetes.io/ssh-auth
stringData:
ssh-privatekey: |
-----BEGIN OPENSSH PRIVATE KEY-----
b3B...
password: sshpw
```
#### Example Session for SSH auth
```yaml
apiVersion: theia.cloud/v5beta
kind: Session
metadata:
name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io-session
namespace: theiacloud
spec:
appDefinition: theia-cloud-demo
envVars: {}
envVarsFromConfigMaps: []
envVarsFromSecrets: []
name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io-session
user: [email protected]
workspace: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io
sessionSecret: 3e68605f-0c6d-4ae5-9816-738f15d34fc9
initOperations:
- id: git
arguments:
- [email protected]:username/my.repository.git
- maintenance_1_1_x
- foo-theiacloud-io-ssh-auth
```
18 changes: 17 additions & 1 deletion python/git-init/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
#!/bin/bash
USERID=$(id -u)

if id "$USERID" &>/dev/null; then
echo 'Existing user'
else
echo 'Setup user for id'
# add an entry to /etc/passwd for the user
echo user:x:$USERID:$USERID:user:/user:/bin/bash >> /etc/passwd
export HOME=/user
fi

# start SSH agent
eval `ssh-agent`
mkdir $HOME/.ssh

# prepare required directories and import ssh key, if available
mkdir -p $HOME/.ssh
touch $HOME/.ssh/known_hosts
[ -e /etc/theia-cloud-ssh/id_theiacloud ] && { sleep 1; echo $GIT_PROMPT1; } | script -q /dev/null -c 'ssh-add /etc/theia-cloud-ssh/id_theiacloud'

# hand over to clone script
python git-init.py "$@"
2 changes: 1 addition & 1 deletion python/git-init/git-init.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def getHostname(repository):

if debugLogging:
runProcess(["ssh-add", "-l"])
runProcess(["cat", "/root/.ssh/known_hosts"])
runProcess(["cat", os.environ.get('HOME') + "/.ssh/known_hosts"])

# Clone repository
code = runProcess(["git", "clone", args.repository, args.directory])
Expand Down
2 changes: 1 addition & 1 deletion python/git-init/ssh-keyscan.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/bash
ssh-keyscan -H $@ >> /root/.ssh/known_hosts
ssh-keyscan -H $@ >> $HOME/.ssh/known_hosts
9 changes: 9 additions & 0 deletions terraform/terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ terraform apply

Point your browser to the `try_now` output value URL printed to the console at the end.

If you want to use non ephemeral workspaces with minikube you have to mount a directory with the expected user id. Please check the existing persisted volume in Minikube for the path.\
You might have to configure the firewall for mounting.

```bash
# This mounts the ~/tmp/minikube on the machine running minikube into minkube.
# Check the persisted volume to find the exact /tmp/hostpath-provisioner/theia-cloud/id path
minikube mount ~/tmp/minikube:/tmp/hostpath-provisioner/theia-cloud/a36c30cee-4d97-4097-826a-31ba72734fd0-pvc-ws-asdfghjkl-theia-c/
```

#### Destroy Minikube Cluster

First remove the persistent volume from the terraform state:
Expand Down

0 comments on commit c547548

Please sign in to comment.