Skip to content

Trust the Dev Proxy root certificate on Linux via 'devproxy cert ensure' - #2029

Open
waldekmastykarz wants to merge 12 commits into
dotnet:mainfrom
waldekmastykarz:linux-cert-trust
Open

waldekmastykarz wants to merge 12 commits into
dotnet:mainfrom
waldekmastykarz:linux-cert-trust

Conversation

@waldekmastykarz

Copy link
Copy Markdown
Collaborator

Closes #601

On Linux, devproxy cert ensure and devproxy cert remove now trust and untrust the Dev Proxy root certificate. Until now they only logged a warning.

  • System store: the certificate is written with sudo. When stdin isn't a terminal or CI is set, sudo runs with -n. Root doesn't use sudo.
    • Debian/Ubuntu: /usr/local/share/ca-certificates, then update-ca-certificates (--fresh on remove).
    • Fedora/RHEL: /etc/pki/ca-trust/source/anchors, then update-ca-trust extract.
    • Arch: /etc/ca-certificates/trust-source/anchors, then update-ca-trust extract.
    • Other distros: prints manual instructions, no failure.
  • Browsers (NSS): if certutil is installed, the certificate is added to ~/.pki/nssdb as Dev Proxy CA (C,,) and removed on cert remove. Otherwise Dev Proxy prints which package to install.
  • Opt-in: trust stays out of first run on Linux because it needs sudo. The first-run warning now points to devproxy cert ensure, and RootTrustPolicy is unchanged.
  • Safety: only the public certificate is exported, to a 0600 temp file that's deleted afterwards. Commands run through ArgumentList, with no shell.
  • Docs: updated the README and the skill's installation reference. The public docs (separate repo) need a follow-up.

Validation

  • Unit tests for distro detection and sudo/command construction. DevProxy.Tests (139) and DevProxy.Abstractions.Tests (95) pass.
  • Docker runs on Ubuntu 24.04, Fedora and Arch:
    • after cert ensure, curl -x through the proxy to a watched HTTPS URL succeeds without -k;
    • after cert remove, the same curl fails with a TLS error;
    • running cert ensure twice leaves a single NSS entry.
  • Edge cases checked: running as root without sudo installed, a user without sudo rights (fails clearly), an interactive sudo password prompt through a pty, an unknown distro, and the first-run warning text.

Adds Linux support to 'devproxy cert ensure' and 'devproxy cert remove':
installs the root certificate into the system store on Debian/Ubuntu
(update-ca-certificates), Fedora/RHEL and Arch (update-ca-trust) using
sudo, and into the user's NSS database (~/.pki/nssdb) when certutil is
available. Trust stays opt-in on Linux; the first-run warning now points
to 'devproxy cert ensure'. Unknown distros get manual instructions.

Closes dotnet#601

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 31, 2026 09:05
@waldekmastykarz
waldekmastykarz requested a review from a team as a code owner October 31, 2026 09:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The helper can execute a caller-controlled PATH executable as root, while anchor handling and failed-refresh retries can leave certificates trusted or remove another user’s trust anchor.

4 open issues
What changed in this PR

Adds opt-in Linux certificate trust management to Dev Proxy, addressing HTTPS setup issues in #601 while keeping privileged installation out of first-run startup.

Changes:

  • Adds system trust installation and removal for Debian/Ubuntu, Fedora/RHEL, and Arch, plus optional NSS browser trust.
  • Connects Linux trust handling to certificate commands without changing the startup trust policy.
  • Adds command-construction tests and updates Linux setup guidance.
File Description
skills/​dev-proxy/​references/​installation.md Documents Linux certificate commands and manual fallback.
README.md Points Linux users to devproxy cert ensure.
DevProxy/​Proxy/​RootCertificateTrust.cs Integrates Linux trust handling and updates the startup warning.
DevProxy/​Proxy/​LinuxCertificateHelper.cs Implements system and NSS trust operations, elevation, and certificate export.
DevProxy.Tests/​LinuxCertificateHelperTests.cs Tests store detection, elevation, and command construction.
DevProxy.Abstractions/​Proxy/​RootTrustPolicy.cs Clarifies opt-in Linux trust in comments; behavior is unchanged.
DevProxy.Abstractions/​Proxy/​IRootCertificateTrust.cs Documents Linux system-store support.

Review effort: Balanced

- Resolve root-run refresh tools only from system directories, not PATH
- Remove the legacy dev-proxy-ca.crt anchor when it matches the current root
- On remove, refresh the store when the CA bundle still contains the root,
  so a removal whose refresh failed can be retried

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Commands can execute binaries from user-writable PATH directories as root, tests fail on Windows, and certificate handling can leave NSS trust intact or export the wrong certificate.

2 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Missing certutil silently leaves the NSS trust entry intact

DevProxy/​Proxy/​LinuxCertificateHelper.cs:278

If certutil was available during cert ensure but has since been uninstalled, the NSS database still contains the trusted CA and this branch silently skips revoking it. cert remove then reports DONE after removing system trust even though Chrome can still trust the certificate. When the database exists but the tool is missing, warn that NSS removal is incomplete and tell the user to reinstall certutil and retry.

Medium severity Fallback export instructions ignore configured certificate paths

DevProxy/​Proxy/​LinuxCertificateHelper.cs:289

The fallback export command always reads ~/.config/dev-proxy/rootCert.pfx, but CertificateAuthority.CreateDefault supports DEV_PROXY_CERT_PATH and resolves its default directory through ApplicationData (including XDG_CONFIG_HOME on Linux). With either override, this command fails or exports a different root than the current proxy uses. Generate the manual instructions from the resolved certificate path, or export the supplied root certificate and report that PEM path.

4 resolved since last review

Review effort: Balanced

…rators

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The certificate changes break non-root Dev Container setup under restricted sudo and can leave newer Chromium browsers untrusted by importing the CA into the wrong NSS database.

No open issues 🎉

2 previously missed issues

In code that hasn't changed since last review

High severity Avoid requiring unrestricted sudo in non-root Feature setup

DevProxy/​Proxy/​RootCertificateTrust.cs:104

The Dev Container Feature's non-root setup now fails when the user has only its restricted sudo grant: features/src/dev-proxy/scripts/post-create.sh:37 runs cert ensure and exits on failure, before reaching its authorized trust helper. The grant at features/src/dev-proxy/install.sh:258 permits only that helper, not the new sudo -n install and refresh commands. Preserve a generation-only bootstrap path for the Feature, or integrate its authorized helper without granting unrestricted sudo.

Medium severity Use Chrome's active NSS database when installing or removing the CA

DevProxy/​Proxy/​LinuxCertificateHelper.cs:171

Chromium M146+ defaults to ~/.local/share/pki/nssdb when no legacy database exists. If Chrome is already using that database, cert ensure creates a separate ~/.pki/nssdb and imports the CA there, so the running browser still rejects intercepted HTTPS despite the success message. Select the existing legacy directory when present, otherwise use the current default, sharing that selection between installation and removal and updating the installation reference accordingly.

2 resolved since last review

Review effort: Balanced

…ve NSS database

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

Addressed the 2 previously missed issues from the last review in 7fb1fac:

  • Avoid requiring unrestricted sudo in non-root Feature setup: when there's no terminal and sudo -n true fails (for example, with the Feature's restricted sudoers grant), devproxy cert ensure still creates the certificate. It skips trusting it, logs a warning on how to trust it from an interactive terminal, and exits 0. The Feature doesn't need changes, and post-create.sh goes on to its own trust helper. Tests added. Checked in Docker with a sudoers grant limited to one helper.
  • Use Chrome's active NSS database: cert ensure and cert remove now use the same database: ~/.pki/nssdb if it exists, otherwise ~/.local/share/pki/nssdb (the Chromium M146+ default). Tests and the installation reference are updated. Checked in Docker with both locations.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The sudo authorization probe can reject command-scoped NOPASSWD grants, causing non-interactive certificate setup to skip trust installation even when the required commands are permitted.

1 open issue

Review effort: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed because certificate removal can report success while browser trust remains, and fallback export instructions may target the wrong root certificate.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

…wn distros

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

Addressed the 2 concerns from the last review summary in 3ecf9d4:

  • Certificate removal can report success while browser trust remains: cert ensure still picks one NSS database (~/.pki/nssdb if it exists, otherwise ~/.local/share/pki/nssdb). cert remove now deletes the Dev Proxy entry from every one of those databases that exists, so a CA added before the active database changed doesn't stay trusted. Tests added. Checked in Docker: add to the XDG database, create ~/.pki/nssdb, add again, then remove clears both.
  • Fallback export instructions may target the wrong root certificate: on an unknown distro, cert ensure now saves the public certificate to devproxy-ca.crt in the current directory and tells the user to add that file to their CA anchors. The hard-coded ~/.config/dev-proxy/rootCert.pfx openssl command is gone, so it's always the root Dev Proxy uses, whatever DEV_PROXY_CERT_PATH or the config folder is. The docs are updated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed because certificate removal can silently leave browser trust intact while reporting success.

No open issues 🎉

Review effort: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

Addressed the remaining concern from the last review summary ("certificate removal can silently leave browser trust intact while reporting success") in b5bcf47. NSS removal no longer skips silently:

  • If certutil is missing and an NSS database exists, cert remove warns that the user should remove Dev Proxy CA manually or install certutil and run it again.
  • For each database, it checks whether the certificate is there (certutil -L). If it is and certutil -D fails, it logs the error and the manual step instead of reporting success.

Checked in Docker: normal removal, a second removal with nothing to remove (no output), a read-only database (error), and no certutil (warning).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Privileged CA-store changes and NSS initialization require human validation of mixed PASSWD/NOPASSWD sudo policies and pre-existing database states not established by the supplied unit tests or reported Docker scenarios.

No open issues 🎉

Review effort: Balanced

- Run trust commands with sudo -n and skip (rolling back the anchor) when
  sudo needs a password, instead of probing with sudo -l
- Fail cert remove when the system store can't be updated
- Warn instead of reporting success when the NSS step fails, and remove
  any untrusted leftover entry

Refs dotnet#601

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

Fixes for 3 issues found while testing b5bcf47 in Docker (ubuntu:24.04, non-root user) — d500e05:

  1. The sudo pre-check let partially permitted setups through. sudo -n -l checks whether a command is permitted, not whether it needs a password. With mixed or last-match-wins sudoers rules, it passed, and then a later step failed. The pre-check is removed. Each command now runs with sudo -n. If one needs a password, the steps already done are undone (the anchor is removed), a warning is shown, and the command exits 0. If the rollback rm also needs a password, the warning gives the anchor path and explains how to finish trusting it or remove it. Other failures still exit 1.
  2. cert remove reported success while the system store still trusted the CA. It now logs an error saying the system store still trusts the cert and to run devproxy cert remove in a terminal, and exits 1. It still removes the NSS entries.
  3. A failed NSS add still printed "Certificate trusted successfully". It now warns that the system store is trusted but NSS-based browsers aren't, with certutil's error and how to fix it. It removes any entry left without trust and warns if that entry can't be removed.

Docker validation of all scenarios passed. Unit tests cover the trust/rollback sequence.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The fallback certificate export follows existing symlinks, allowing a planted destination in a user-writable directory to overwrite unrelated root-owned files when run as root.

3 open issues

Review effort: Balanced

- Replace an existing devproxy-ca.crt instead of following a symlink
- Warn instead of failing when the NSS directory can't be created
- Exit with an error when cert remove can't delete an NSS entry

Refs dotnet#601

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed to evaluate omitted critical findings affecting certificate-removal success reporting and preservation of existing trust during rollback.

No open issues 🎉

3 resolved since last review

Review effort: Balanced

- Remove the legacy anchor only after the new anchor is trusted
- Restore a replaced anchor's previous contents when trust is skipped
- Fail cert remove if the bundle still contains the certificate

Refs dotnet#601

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

Re: the omitted findings in the latest review's verdict (no inline threads), fixed in the latest commit:

  • Keeping existing trust during rollback
    • The legacy dev-proxy-ca.crt anchor is removed only after the new anchor is trusted. If that cleanup fails, a warning is shown; trust isn't affected because both files hold the same cert.
    • If the anchor held a different certificate before and trust is skipped, its previous contents are restored instead of the file being deleted.
  • Certificate-removal success reporting: after a successful refresh, cert remove checks the bundle. If the bundle still contains the cert (another anchor file holds it), it logs an error naming the bundle and exits 1.

Validated in Docker (ubuntu:24.04, non-root): legacy anchor kept when skipping, previous anchor restored byte-for-byte, removal with a second anchor reports an error. Regression scenarios still pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed because rollback can delete an existing trust anchor, NSS inspection failures can be silently skipped, and non-interactive commands can block on password prompts.

No open issues 🎉

Review effort: Balanced

- Pass an empty password file to certutil when non-interactive so it
  fails instead of prompting on /dev/tty
- Treat only certutil's not-found error as a missing NSS entry; fail
  cert remove when the database can't be checked
- Back up a replaced anchor with an elevated copy so it can always be
  restored

Refs dotnet#601

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

These are the concerns from the latest review's verdict, which had no inline threads. They're fixed in the latest commit:

  • Non-interactive commands blocking on password prompts. When there's no terminal, certutil gets -f <empty file>, because it prompts on /dev/tty even when stdin is redirected. A password-protected database now fails right away with SEC_ERROR_TOKEN_NOT_LOGGED_IN, which triggers the existing warning. Databases without a password work as before. A comment and tests pin this.
  • NSS inspection failures skipped silently. Only certutil's PR_FILE_NOT_FOUND_ERROR means there's no entry. If any other -L error happens, such as SEC_ERROR_BAD_DATABASE, cert remove logs an error and exits 1, and cert ensure says it couldn't check for a leftover entry.
  • Rollback deleting an existing anchor. An anchor being replaced is now backed up with an elevated install -m 0600 into a private temp directory, so it can be restored even if the user can't read it. If the backup needs a password, the trust step is skipped and nothing is changed.

I validated this in Docker (ubuntu:24.04, user without root):

  • No hang with a TTY when stdin is /dev/null or CI=1.
  • An unreadable anchor is restored byte-for-byte.
  • An NSS database that can't be read makes cert remove exit 1.
  • All earlier scenarios still pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed because password-file creation failure can make CI certificate removal fall back to interactive prompting and block on a password-protected NSS database.

No open issues 🎉

Review effort: Balanced

Nothing to create, so certutil can't fall back to prompting.

Refs dotnet#601

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

This is the verdict concern from the latest review (no inline thread): if the password file couldn't be created, certutil could still prompt. Fixed in the latest commit. Without a terminal, certutil now reads its empty password from /dev/null with -f /dev/null. No file is created, so nothing can fail and fall back to prompting. I checked in Docker that a password-protected database fails right away (no hang when CI=1 and a TTY is attached), and that databases without a password still get C,,.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Privileged changes to global CA stores and NSS databases across three distro families require maintainer confirmation of reported integration tests and interactive password/failure-recovery behavior that this source-only review cannot reproduce.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
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.

Automate configuring Dev Proxy certificate on Linux

2 participants