-
Notifications
You must be signed in to change notification settings - Fork 28
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
Conversation
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) |
There was a problem hiding this comment.
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.
// 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)) | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
Is it related to #92? |
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) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioning that in coder/terraform-provider-coder#217
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already moved. See
coder/terraform-provider-coder#219
Awesome, this is was I am waiting for since a while 🙏🏻 |
There was a problem hiding this 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).
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) | ||
} |
There was a problem hiding this comment.
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?
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 |
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