Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply ssh askpass flow for the workspace container #1307

Merged
merged 1 commit into from
Sep 9, 2024

Conversation

vinokurig
Copy link
Contributor

What does this PR do?

  • Add DISPLAY and SSH_ASKPASS environment variables to the workspace container for the ssh askpass flow.
  • Add ssh askpass provision step to create a configmap with the ssh askpass script content and propagate it to the workspace container.
  • Change the root of the ssh-askpass.sh file in the project clone Dockerfile to the one in the provision/automount package

What issues does this PR fix or reference?

fixes #1295

Is it tested? How?

  1. Start a workspace and check that the DISPLAY and SSH_ASKPASS environment variables are set
  2. Check that the /.ssh-askpass/ssh-askpass.sh file is present and have the right content.

PR Checklist

  • E2E tests pass (when PR is ready, comment /test v8-devworkspace-operator-e2e, v8-che-happy-path to trigger)
    • v8-devworkspace-operator-e2e: DevWorkspace e2e test
    • v8-che-happy-path: Happy path for verification integration with Che

Copy link

openshift-ci bot commented Aug 14, 2024

Hi @vinokurig. Thanks for your PR.

I'm waiting for a devfile member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@vinokurig
Copy link
Contributor Author

/test v8-devworkspace-operator-e2e, v8-che-happy-path

Copy link

openshift-ci bot commented Aug 15, 2024

@vinokurig: Cannot trigger testing until a trusted user reviews the PR and leaves an /ok-to-test message.

In response to this:

/test v8-devworkspace-operator-e2e, v8-che-happy-path

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Copy link
Collaborator

@AObuchow AObuchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome to me so far @vinokurig, great work 😁

I left some small suggestions as to how we inject the SSH ask pass script into the workspace deployment, as well as removing some functionality from #1295 that would be rendered redundant from this PR.

pkg/library/env/workspaceenv.go Outdated Show resolved Hide resolved
@@ -109,7 +109,12 @@ func getAutomountResources(api sync.ClusterAPI, namespace string) (*Resources, e
return nil, err
}

return mergeAutomountResources(gitCMAutoMountResources, mergedResources, pvcAutoMountResources), nil
sshCMAutoMountResources, err := ProvisionSshAskPassScript(api, namespace)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit hesitant for us to add the SSH ask pass script as part of the automount resources provisioning flow. From my understanding, automount resources are supposed to be optional, user-provided resources. In contrast, the SSH ask pass script is a static file that is automatically injected into the workspace without user intervention.

Instead, I'd suggest making a new package ssh in /pkg/provision/ssh/ to store all the ssh askpass related code. There, you can have a function like ssh.InjectSSHAskPassScript(podAdditions *v1alpha1.PodAdditions, api sync.ClusterAPI, namespace string) that:

  • Creates the SSH ask pass configmap script on the cluster (you already implemented this here)
  • Modifies the workspaces podAdditions to add the additional SSH ask pass configmap volume and volumeMounts

Basically, you'd inject the SSH ask pass configmap volume and volumeMount into the podAdditions, instead of the automount resources. We do a similar approach for ServiceAccountTokens.

Your new ssh.InjectSSHAskPassScript(podAdditions function could get called in the devworkspace-controller's reconcile function, just before we provision the automount resources. This way, we'll check whether the SSH ask pass volume and volumeMount conflict with any automount resources when checkAutomountVolumesForCollisions() is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -58,7 +58,7 @@ func TestProvisionAutomountResourcesIntoPersistentHomeEnabled(t *testing.T) {

err := ProvisionAutoMountResourcesInto(podAdditions, testAPI, testNamespace, true)

if !assert.NoError(t, err, "Unexpected error") {
if assert.ErrorContains(t, err, "Created object") || !assert.NoError(t, err, "Unexpected error") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this was added because ProvisionAutoMountResourcesInto() now creates the ssh ask pass configmap on the cluster, and we don't want to fail the test for this expected "error" right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked to intercept the error when initialising the configmap

return nil, dwerrors.WrapSyncError(err)
}
resources := flattenAutomountResources([]Resources{
getAutomountConfigmap(SshAskPassMountPath, constants.DevWorkspaceMountAsSubpath, pointer.Int32(0755), sshAskPassConfigMap),
Copy link
Collaborator

@AObuchow AObuchow Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in my earlier comment, I don't know if we should be creating an automount configmap in order to have DWO provision the volume and volumeMount in the workspace for us, rather than just creating and adding the volume and volumeMount to the workspace's podAdditions.

On one hand, it's nice to reuse existing code and functionality: here we're creating an automount configmap as if we were a user.

On the other hand, it seems to blur the responsibilities of the automount package, as it is no longer only responsible for automatically mounting user-provided data (since we are now mounting static data that is not provided by users).

@dkwon17 do you have any opinions here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reworked to initialise the resources without using the automount package

"k8s.io/utils/pointer"
)

const SshAskPassMountPath = "/.ssh-askpass/"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the project clone container, we mount the SSH askpass script to /usr/local/bin/ssh-askpass.sh. Is there a reason for now mounting it to /.ssh-askpass/? Was it to have it as a hidden file in the filesystem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not matter where to keep the script file but I changed it to be inline with

const mergedGitCredentialsMountPath = "/.git-credentials/"

@@ -49,7 +49,7 @@ ENV USER_UID=1001 \
SSH_ASKPASS=/usr/local/bin/ssh-askpass.sh

COPY build/bin /usr/local/bin
COPY project-clone/ssh-askpass.sh /usr/local/bin
COPY pkg/provision/automount/ssh-askpass.sh /usr/local/bin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of whether we use the current approach of using DWO's automount functionality, or directly add to the podAdditions, the SSH ask pass script should actually be injected into the project clone init container already when we create the workspace deployment.

This means that we shouldn't have to manually COPY the SSH ask pass script to the project clone image from the Dockerfile anymore (which is nice 🥳) -- so we should be able to remove this line altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the project clone container also gets environment variables from the commonEnvironmentVariables() function, we no longer have to setup the SSH askpass related environment variables in the project clone container's Dockerfile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome thank you! Just leaving a comment here as a reminder to myself: it's worth re-testing the functionality of #1291 since we're modifying how it's implemented now.

Value: ":0",
}, {
Name: constants.SSHAskPass,
Value: automount.SshAskPassMountPath + automount.SshAskPassScriptFileName,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the rest of DWO's codebase, we tend to use fmt.Sprintf() instead of using + to concatenate strings. In my opinion, it makes it more clear that we are concatenating two strings and not summing two numbers.

Admittedly, fmt.Sprintf("%s%s", automount.SshAskPassMountPath, automount.SshAskPassScriptFileName) does look a bit weird however, since we're just concatenating two strings with no extra formatting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@AObuchow
Copy link
Collaborator

@vinokurig Also, please run make fmt as the format validation CI check is currently failing

@vinokurig
Copy link
Contributor Author

@AObuchow Thank you for the review, I've added another commit to add a post start event to initialise ssh-agent and add the workspace ssh-key. Please take a look.

@vinokurig vinokurig force-pushed the dwo-1295 branch 6 times, most recently from 6a6cd5e to e641d7b Compare August 19, 2024 14:45
Copy link
Collaborator

@AObuchow AObuchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing my earlier comments @vinokurig, this is looking great :)
I left some minor comments but I think this is about ready for some testing on my end.


return envvars
}

func GetSshAskPassEnvVars() []corev1.EnvVar {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for putting this into a function :)
We probably don't need to have this exported (i.e. the function name should start with a lower case letter). since it's only being called in the same file.

It's actually odd that GetProxyEnvVars() is also exported but not used elsewhere.
Would you mind making both the GetSshAskPassEnvVars() and the GetProxyEnvVars() functions not exported please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome thank you! Just leaving a comment here as a reminder to myself: it's worth re-testing the functionality of #1291 since we're modifying how it's implemented now.

spec.Events = &v1alpha2.Events{}
}

var commandLine = `SSH_ENV_PATH=/home/user/ssh-environment \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments:

  • The commandLine variable could probably be a constant to make the rest of the code a little easier to read (like we did here)
  • It might be better to use $HOME instead of hard-coding /home/user/ since the users workspace might not be using the UDI. I'm not sure how common the ssh-agent binary is, but hopefully your implementation here could also work for other workspace images.
  • This is very subjective: I find using source is more readable than .. This is relevant IMO because we are modifying the users .bashrc.

The only non-straightforward thing here is the ssh-agent | sed 's/^echo/#echo/' > $SSH_ENV_PATH. My understanding is that there's some echo commands as part of ssh-agent's output that we don't want to be executed everytime the .bashrc is sourced?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The commandLine variable could probably be a constant to make the rest of the code a little easier to read (like we did here)
  • It might be better to use $HOME instead of hard-coding /home/user/ since the users workspace might not be using the UDI. I'm not sure how common the ssh-agent binary is, but hopefully your implementation here could also work for other workspace images.
  • This is very subjective: I find using source is more readable than .. This is relevant IMO because we are modifying the users .bashrc.

done

The only non-straightforward thing here is the ssh-agent | sed 's/^echo/#echo/' > $SSH_ENV_PATH. My understanding is that there's some echo commands as part of ssh-agent's output that we don't want to be executed everytime the .bashrc is sourced?

Right, we comment the echo Agent pid ****; line of the ssh-agent command output, so we have a clear terminal start. As an alternative we can add >/dev/null to the source ${SSH_ENV_PATH} bachrc command. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually like your current solution of not echoing the Agent pid **** over doing a >/dev/null since this would probably let any errors related to sourcing the ssh-agent environment variables get logged in the user's terminal and let them know something is not working correctly.

Copy link
Collaborator

@AObuchow AObuchow Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also @vinokurig it seems we're still referencing /home/user/ in two lines of the postStart event: here and here. Could you please change these to $HOME or ~?

Copy link
Collaborator

@AObuchow AObuchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted some more feedback

spec.Events = &v1alpha2.Events{}
}

var commandLine = `SSH_ENV_PATH=/home/user/ssh-environment \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually like your current solution of not echoing the Agent pid **** over doing a >/dev/null since this would probably let any errors related to sourcing the ssh-agent environment variables get logged in the user's terminal and let them know something is not working correctly.

spec.Events = &v1alpha2.Events{}
}

var commandLine = `SSH_ENV_PATH=/home/user/ssh-environment \
Copy link
Collaborator

@AObuchow AObuchow Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also @vinokurig it seems we're still referencing /home/user/ in two lines of the postStart event: here and here. Could you please change these to $HOME or ~?

@vinokurig vinokurig force-pushed the dwo-1295 branch 2 times, most recently from 30d5490 to 405ec53 Compare August 28, 2024 07:21
@vinokurig
Copy link
Contributor Author

@AObuchow

it seems we're still referencing /home/user/ in two lines of the postStart event: here and here. Could you please change these to $HOME or ~?

fixed

Copy link

codecov bot commented Aug 28, 2024

Codecov Report

Attention: Patch coverage is 2.77778% with 70 lines in your changes missing coverage. Please review.

Project coverage is 52.63%. Comparing base (adc8b5a) to head (405ec53).
Report is 64 commits behind head on main.

Files with missing lines Patch % Lines
pkg/provision/workspace/ssh.go 0.00% 50 Missing ⚠️
pkg/library/env/workspaceenv.go 0.00% 14 Missing ⚠️
controllers/workspace/devworkspace_controller.go 25.00% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1307      +/-   ##
==========================================
+ Coverage   52.52%   52.63%   +0.10%     
==========================================
  Files          84       85       +1     
  Lines        7642     7976     +334     
==========================================
+ Hits         4014     4198     +184     
- Misses       3335     3477     +142     
- Partials      293      301       +8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Collaborator

@AObuchow AObuchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-built the project-clone image with the changes from this PR and verified this current PR resolves #1295 without breaking the functionality of #1291.

I still need to do one last pass-through of this PR, and @dkwon17 should take a look as well, but IMO this PR should be good to merge very soon (ideally, before the end of this week).

Great work @vinokurig 😁 And sorry for taking so long with my review.

@AObuchow
Copy link
Collaborator

AObuchow commented Sep 4, 2024

/ok-to-test

controllers/workspace/devworkspace_controller.go Outdated Show resolved Hide resolved
@dkwon17
Copy link
Collaborator

dkwon17 commented Sep 5, 2024

LGTMl, I just have one comment

@openshift-ci openshift-ci bot removed the lgtm label Sep 5, 2024
Copy link
Collaborator

@AObuchow AObuchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some last minute remarks. I believe the typo and blank identifier are the only remarks that needs to be addressed before we merge this PR. Everything else could be left for a future PR.

"github.com/devfile/devworkspace-operator/pkg/library/lifecycle"
)

const commandLine = `SSH_ENV_PATH=$HOME/ssh-environment \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the current approach of modifying the .bashrc, it's probably worth making the ssh-environment file hidden with a . i.e. SSH_ENV_PATH=$HOME/.ssh-environment

then ssh-agent | sed 's/^echo/#echo/' > $SSH_ENV_PATH \
&& chmod 600 $SSH_ENV_PATH && source $SSH_ENV_PATH \
&& ssh-add /etc/ssh/dwo_ssh_key < /etc/ssh/passphrase \
&& if [ -f $HOME/.bashrc ]; then echo "source ${SSH_ENV_PATH}" >> $HOME/.bashrc; fi; fi`
Copy link
Collaborator

@AObuchow AObuchow Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying the .bashrc works, but if a user provides their own custom .bashrc through an automount configmap, this feature will actually break. Files that are mounted via the automount configmap mechanism cannot be written to.

Instead, we could place the SSH_ENV_PATH in /etc/profile.d/, which contains scripts that should be run when sourcing the .bashrc, e.g. SSH_ENV_PATH=/etc/profile.d/.ssh-environment. This way, any .bashrc will load the ssh-agent script.

$ cat ~/.bashrc
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi
(...)


$ cat /etc/bashrc 
(...)
    SHELL=/bin/bash
    # Only display echos from profile.d scripts if we are no login shell
    # and interactive - otherwise just process them to set envvars
    for i in /etc/profile.d/*.sh; do
        if [ -r "$i" ]; then
            if [ "$PS1" ]; then
                . "$i" # Here is where all the scripts in /etc/profile.d/ get sourced
            else
                . "$i" >/dev/null
            fi
        fi
    done

A caveat is that the /etc/profile.d/ scripts only get run for interactive shells, see here for more explanation. But for #1295 this is fine.

I do understand that this is a rather big change to request from your PR @vinokurig, so I'm perfectly fine with keeping the current approach that modifies the .bashrc and making a new issue/PR in the future to implement my suggestions. I haven't actually tested my suggestion, so it might not actually work (though I did something similar for the UDI a while back)

Copy link
Collaborator

@dkwon17 dkwon17 Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do understand that this is a rather big change to request from your PR @vinokurig, so I'm perfectly fine with keeping the current approach that modifies the .bashrc and making a new issue/PR in the future to implement my suggestions.

I think we should take the current approach for now since there isn't a straight forward alternative since the /etc/profile.d folder cannot be written by regular users by default in the UDI. I suggest we keep the functionality how it is for now, and document that if .bashrc is being mounted using a configmap, something like [ -f /home/user/ssh-environment ] && source /home/user/ssh-environment should be added to it.

I can make a new PR documenting this after this PR is merged.

Any thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch :) I agree that it's probably worth documenting this somewhere in the near future (maybe in the Che Docs as well as DWO additional docs)?

package workspace

import (
_ "embed"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why the blank identifier _ is being used? Couldn't we just have

import (
	"embed"
...
)

I've never used the embed directive, but examples of it don't seem to be using the blank identifier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing the embed explicitly causes an error: pkg/provision/workspace/ssh.go:19:2: "embed" imported and not used

//go:embed ssh-askpass.sh
var data string

func ProvisionSshSshAskPass(api sync.ClusterAPI, namespace string, podAdditions *v1alpha1.PodAdditions) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there's a typo in this function name: ProvisionSshSshAskPass -> ProvisionSshAskPass

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

pkg/library/ssh/event.go Outdated Show resolved Hide resolved
@openshift-ci openshift-ci bot removed the approved label Sep 5, 2024
Copy link
Collaborator

@AObuchow AObuchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, awesome work @vinokurig 😁
Please squash all your commits into a single commit (I think you could squash all your commits into your first commit)

You could also add "Fixes #1295" to the commit description.

Signed-off-by: ivinokur <ivinokur@redhat.com>

Fixes devfile#1295
@vinokurig
Copy link
Contributor Author

@AObuchow

LGTM, awesome work @vinokurig 😁
Please squash all your commits into a single commit (I think you could squash all your commits into your first commit)

You could also add "Fixes #1295" to the commit description.

thanks, done

@openshift-ci openshift-ci bot added the lgtm label Sep 9, 2024
Copy link

openshift-ci bot commented Sep 9, 2024

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: AObuchow, dkwon17, vinokurig

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DevWorkspace doesn't respect SSH key passphrase when git pushing changes from workspace
3 participants