Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

impl: verify cli signature#148

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fioan89 merged 43 commits intomainfromimpl-verify-cli-signature
Jul 17, 2025
Merged

impl: verify cli signature#148

fioan89 merged 43 commits intomainfromimpl-verify-cli-signature
Jul 17, 2025

Conversation

fioan89
Copy link
Collaborator

@fioan89fioan89 commentedJul 9, 2025
edited
Loading

This PR introduces support for verifying the CLI binary using a detached PGP signature. Starting with version 2.24, Coder signs all CLI binaries. For clients using older versions or running TBX in air-gapped environments, unsigned CLIs can still be executed — but users will have to confirm it each time.

In terms of code changes - the PR includes a big refactor around CLI downloading with most of the code refactored and extracted in various components that provide clean steps and result state in the main download method. Then the pgp verification logic was added on top, with some particularities:

  • the pgp public key is embedded in the plugin as a jar resource
  • we support multiple key rings in the public key
  • the user has the option of running the CLI if no signature was found
  • the signature search has a fallback approach: first we look in the Coder deployment, and then fall back to releases.coder.com to search for the signature if the user allows it.
  • we expect the signature to be under the same relative path as the CLI (we have an option which allows user to pick the CLI from a different source other than the Coder deployment)

A new UI setting was introduced to allow users to run unsigned binarieswithout any input from the user. Defaults to false which means if a binaryis unsigned we will ask the user what to do next.
I moved and modified the logic from CliManager.download to a separatehttp client based on okhttp and retrofit. The refactor will allow usto easily add new steps in the main download method, and also to easilydownload new resources. Long term we could also re-use the okhttp clientto avoid setting twice the same boilerplate (proxy which is missing from CLIManager,hostname verification and other tls settings) between cli downloader and the rest client
From the same source where the cli binary was downloaded.Some of the previous classes like download result were updated to incorporatedetails like where the file was saved or whether a file was found on the remote
`allowUnsignedBinaryWithoutPrompt` was caching the initial value read from thestore, which required a restart of Toolbox for the real value to reflect.
A pop-up dialog is displayed asking the user if he wants to run an unsignedcli version. The pop-up can be skipped if the user configures the `Allow unsigned binary execution without prompt`
Adds logic to verify the CLI against a detached GPG signature with the help of bouncycastle library
This is the key that validates if the gpg signature was tampered
Initially I thought about embedding it as a const string in the codebut the string is too big, best to save it as resource file.The code changes are mostly related to loading the key from the file.
Signature verification some operations that are cpu bound that are light, likedecoding and decompressing signature data, some medium CPU intensive operationslike the cryptographic verifications and a couple of blocking IO operations:- reading the cli file- reading the signature file- reading the public key fileThese last should run on the IO thread to not block the main thread from drawing the screen.The cpu bound operation should run on the default thread.
previous implementation was selecting only the first key ring from the public key filewhich turns out to be wrong. Instead, we should keep all the key rings and search signaturekey id in all the key rings.
Otherwise, at the next Toolbox restart the signature will no longer be verified,and we run into the risk of running unsigned binaries.
`Files.readAllBytes()` uses direct buffers internally which can be filled up quickly whencalled repeatedly in coroutines, as these buffers are not released quickly by the GC.The scenario can be reproduced by trying to login a couple of times one after the other withsignature verification failing each time.Instead, we can avoid memory issues by streaming the cli and feed only blocks of bytes into the signature calculation.
When there is no signature and the user allowed running of unsigned binaries without prompt
A major feature was added
@fioan89fioan89 marked this pull request as ready for reviewJuly 11, 2025 22:52
Copy link
Member

@code-ashercode-asher left a comment

Choose a reason for hiding this comment

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

I have not ran it yet but looking good!

And also disable the work in progress animations after a fatal error occurred.Most exceptions, especially the ones related to cli signature verification weresuppressed and only displayed in the logs with no visual feedback.
The logic for matching the local CLI version with the deployment previously attempted to runthe CLI with --version without first verifying that the binary existed. This commit improvesthat by first checking if the file exists, avoiding the unnecessary overhead of spawning aprocess for a non-existent binary.
…er-cliRetroactive cli signatures are now published at releases.coder.com/coder-cli/x.y.z/where x.y.z is the major, minor and patch version of the deployment.
…blishedWe now have a lot of signatures published for a lot of older cli version which meanssome of the tests that were not expecting the fallback to activate are now in trouble.The simple fix is to "download" a very old version for which signatures will not be generated.
Instead of deriving the signature name from the cli name it is now codedinto settings store just like the default cli name
It is already supported by java.net.URI
The download and signature verification steps are now slightly altered to first downloadthe cli to a temporary location and only after the signature verification is successfulor the user accepted the risk of running an unsigned binary - then and only then the tempcli is moved to its final location (actually it is just a rename).If the delete fails, this prevents the unsigned binary from being picked up as thecached binary on the next run.
Ask the user if he wants to accept the risk of running a potentially tamperedCLI when signature verification failed (i.e. we downloaded the signatures but eitherit doesn't match or there were some error while computing the signature.
We are going to ask the user only once during the initial login screen if he wants tofallback on releases.coder.com when signatures are not present in the coder deployment.However, we still want to leave him the option to later change his mind the in Settings page.
The user can opt in to fallback on releases.coder.com from the login screenonly once. Later on he can change his mind in the Settings page.
Unless we restart toolbox. This happens because the UI fields are initialized onlyonce when the page is created. If we switch to a different page that can update thesettings (for example the login screen can alter the fallback strategy when signatureare missing) and then come back to the settings page - in that case Toolbox does notrequest the settings again from the settings store.
This happens in the following cases:- if the deployment did not have any signatures and user decided to fallback on  releases.coder.com but the fallback did not have any signature or we failed to download it.- similarly if the user does not want to fallback we still ask him if he wants to run the  unverified cli
Copy link

@jdomeracki-coderjdomeracki-coder left a comment

Choose a reason for hiding this comment

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

Discussed at length on Slack.
I'm giving this change a security-focused thumbs up.

Copy link
Member

@code-ashercode-asher left a comment

Choose a reason for hiding this comment

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

Turns out running Toolbox is not going so well for me (unrelated to the plugin) so I am having trouble testing but 👍 on the code.

@@ -489,6 +489,7 @@ class CoderCLIManager(
* version could not be parsed.
*/
fun matchesVersion(rawBuildVersion: String): Boolean? {
if (Files.notExists(localBinaryPath)) return null
Copy link
Member

Choose a reason for hiding this comment

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

Small nit, but does this really decrease overhead? My impression is that this could increase the overhead, at least in terms of the syscalls, since I imagine exec essentially does its own file check at the start anyway.

And we have to handle non-existent binary errors when executing anyway, since a file can be deleted at any time including between these calls. Also, I imagine in the majority of cases the binary will exist (all cases except the first setup), so it would usually add an extra call.

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

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

I would say yes. ProcessExecutor does not really check if the file/command process exists. It really hides some boiler plate that comes with java's own processbuilder. At JVM level I will simplify but it boils down to a fork + exec. So in both cases there will be at least one sys call, but we can avoid overhead of pointlessly forking a new process.

so it would usually add an extra call.
In those cases there is indeed one extra sys call.

Copy link
Member

@code-ashercode-asherJul 17, 2025
edited
Loading

Choose a reason for hiding this comment

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

Fair, makes sense that fork could have overhead, although it is copy on write these days if I understand correctly (in Linux at least), so if followed immediately by exec I wonder how much overhead there really could be, depending on how exec works.Edit: well I guess some overhead would still exist, caused after exec, nvm

Still, in the majority of cases this adds a call, duplicates logic in the error handler, and optimization ideally comes after benchmarks anyway.

But I feel silly going on about this, just ignore me, this is quite minor either way lol. I just have this drilled into me as an anti-pattern from a long time ago 😆

@fioan89fioan89 merged commitcb3aae6 intomainJul 17, 2025
6 checks passed
@fioan89fioan89 deleted the impl-verify-cli-signature branchJuly 17, 2025 22:00
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@code-ashercode-ashercode-asher approved these changes

@jdomeracki-coderjdomeracki-coderjdomeracki-coder left review comments

@matifalimatifaliAwaiting requested review from matifali

@f0sself0sselAwaiting requested review from f0ssel

@deansheatherdeansheatherAwaiting requested review from deansheather

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

5 participants
@fioan89@matifali@deansheather@code-asher@jdomeracki-coder

[8]ページ先頭

©2009-2025 Movatter.jp