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

feat: fetch SSH key from Coder if required #174

Closed
wants to merge 4 commits into from
Closed

Conversation

johnstcn
Copy link
Member

@johnstcn johnstcn commented May 3, 2024

Relates to #31

Depends on #173

Modifies the SSH logic introduced in #170 with an extra step to fetch the SSH key from Coder.

There is currently a race condition between envbuilder starting and the workspace build being marked as 'completed'. To work around this, I added some backoff to fetching the SSH key using the agent token.

Demo:

envbuilder-ssh-auth-coder.mov

@johnstcn johnstcn marked this pull request as draft May 3, 2024 17:10
Base automatically changed from cj/fix-coder-url to main May 3, 2024 17:21
@johnstcn johnstcn marked this pull request as ready for review May 3, 2024 18:21
@johnstcn johnstcn self-assigned this May 3, 2024
Comment on lines +299 to +305
if errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusUnauthorized {
// Retry, as this may just mean that the workspace build has not yet
// completed.
log(codersdk.LogLevelInfo, "#1: 🕐 Backing off as the workspace build has not yet completed...")
return err
}
close(signerChan)
Copy link
Member Author

Choose a reason for hiding this comment

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

review: I'm open to adding more cases here, but keeping the retry scope small for now.

Comment on lines +334 to +339
// keyFingerprint returns the md5 checksum of the public key of signer.
func keyFingerprint(s gossh.Signer) string {
h := md5.New()
h.Write(s.PublicKey().Marshal())
return fmt.Sprintf("%x", h.Sum(nil))
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is an unrelated change, but I figured it would be nice to see

signerChan := make(chan gossh.Signer, 1)
eb := backoff.NewExponentialBackOff()
eb.MaxElapsedTime = 0
eb.MaxInterval = time.Minute
Copy link
Member Author

Choose a reason for hiding this comment

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

review: should this maybe be a configurable knob?

@matifali
Copy link
Member

matifali commented May 3, 2024

Is it related to #92?

Comment on lines +234 to 244
if signer == nil && options.CoderAgentURL != "" && options.CoderAgentToken != "" {
options.Logger(codersdk.LogLevelInfo, "#1: 🔑 Fetching key from %s!", options.CoderAgentURL)
fetchCtx, cancel := context.WithCancel(ctx)
defer cancel()
s, err := FetchCoderSSHKeyRetry(fetchCtx, options.Logger, options.CoderAgentURL, options.CoderAgentToken)
if err == nil {
signer = s
options.Logger(codersdk.LogLevelInfo, "#1: 🔑 Fetched %s key %s !", signer.PublicKey().Type(), keyFingerprint(signer)[:8])
} else {
options.Logger(codersdk.LogLevelInfo, "#1: ❌ Failed to fetch SSH key from %s: %w", options.CoderAgentURL, err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Deeply integrating the hot-path code with Coder feels incorrect to me.

I don't believe the envbuilder package should have any knowledge of the Coder agent or Coder URL. Prior to the CLI change, it didn't. The command-line simply logged to Coder as a sink if those vars were provided, and I think implementing this similarly abstractly would be an improvement.

We want people outside of the Coder ecosystem to use envbuilder. By integrating the codebase so heavily into Coder, we alienate that possibility.

Copy link
Member

Choose a reason for hiding this comment

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

My preference would be to allow passing SSH options to envbuilder, and also not provide Coder options directly to envbuilder.

Copy link
Member Author

@johnstcn johnstcn May 3, 2024

Choose a reason for hiding this comment

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

How about instead exposing some 'lifecycle hooks' inside envbuilder?
For example, we could expose a 'pre-clone' hook that executes a given script before cloning the git repository. For this use-case, that script could curl | jq to get the git private key. It would require modifying the envbuilder image, but I think this provides the decoupling and flexibility you're looking for.

Copy link
Member

Choose a reason for hiding this comment

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

Adopting "lifecycle hooks" is a reasonable concept, we may argue where they should be scripts or just shell commands. Maybe we can implement them similarly to Git Hooks?

Copy link
Member

Choose a reason for hiding this comment

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

Any thoughts as to what kind of hook, except pre-clone, we might need in envbuilder? Running scripts is generally already addressable by devcontainer.json.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think you're right that another data source for the coder_user is good, but shouldn't block this effort.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

If we added a new data source to the coder provider, I believe we can address this similarly to what @kylecarbs suggested:

container {
  env = {
    GIT_SSH_KEY: basy64(data.coder_workspace.owner_ssh_key)
  }
}

👍 That's certainly a valid path forward.

Based on what was implemented in #170, there would be a few extra steps:

  • Create a secret containing the user's private key
  • Mount that secret into the container under a well-known path
  • Reference that path in the container's environment

Will open a separate PR for this.

Copy link
Member

Choose a reason for hiding this comment

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

@matifali it's funny you should mention that, it was my initial thought as well, but then I got lazy 😄. Perhaps we could move this to a separate issue in https://github.com/coder/terraform-provider-coder?

Copy link
Member

Choose a reason for hiding this comment

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

@nwrkbiz
Copy link
Contributor

nwrkbiz commented May 5, 2024

Awesome, this is was I am waiting for since a while 🙏🏻

Copy link
Member

@mtojek mtojek left a comment

Choose a reason for hiding this comment

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

I'm happy with exploring the idea for hooks unless #31 is bleeding hard (urgent).

Comment on lines +234 to 244
if signer == nil && options.CoderAgentURL != "" && options.CoderAgentToken != "" {
options.Logger(codersdk.LogLevelInfo, "#1: 🔑 Fetching key from %s!", options.CoderAgentURL)
fetchCtx, cancel := context.WithCancel(ctx)
defer cancel()
s, err := FetchCoderSSHKeyRetry(fetchCtx, options.Logger, options.CoderAgentURL, options.CoderAgentToken)
if err == nil {
signer = s
options.Logger(codersdk.LogLevelInfo, "#1: 🔑 Fetched %s key %s !", signer.PublicKey().Type(), keyFingerprint(signer)[:8])
} else {
options.Logger(codersdk.LogLevelInfo, "#1: ❌ Failed to fetch SSH key from %s: %w", options.CoderAgentURL, err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Adopting "lifecycle hooks" is a reasonable concept, we may argue where they should be scripts or just shell commands. Maybe we can implement them similarly to Git Hooks?

@johnstcn
Copy link
Member Author

johnstcn commented May 9, 2024

Thanks @mafredri @matifali for your excellent input and comments.

The overwhelming consensus here is to avoid a direct integration and instead allow injecting a Coder user's SSH private key into the envbuilder container. There should be no further changes in envbuilder required to support this. Closing this PR out in favour of coder/terraform-provider-coder#219

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

Successfully merging this pull request may close these issues.

7 participants