Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The system uses the VS Code **companion extension pattern**: two extensions comm
2. Writes each cert to every configured extra destination via `writeExtraDestination`.
3. Runs a single rehash per directory destination after all writes.

- **Devcontainer feature** (`src/devcontainer-feature/`) — sets `SSL_CERT_DIR` via `containerEnv`, creates `.dotnet/corefx/cryptography/x509stores/my/` and `.aspnet/dev-certs/trust/` directories, requests both extensions via `customizations.vscode.extensions`. `install.sh` also pre-creates any directories named in `extraCertDestinations` with `vscode` ownership so the remote extension can write without privileged escalation. Option values (`generateDotNetCert`, `syncUserCertificates`, `extraCertDestinations`) are surfaced to the runtime container via `/etc/environment`.
- **Devcontainer feature** (`src/devcontainer-feature/`) — sets `SSL_CERT_DIR` from `install.sh` by writing `/etc/profile.d/devcontainer-dev-certs.sh` (login shells, `$HOME`-expanded) and `/etc/environment` (PAM, resolved `_REMOTE_USER_HOME`); the manifest can't carry it because `${containerEnv:HOME}` doesn't resolve inside `containerEnv` and `remoteEnv` isn't allowed in features under strict-schema validation. Creates `.dotnet/corefx/cryptography/x509stores/my/` and `.aspnet/dev-certs/trust/` directories, requests both extensions via `customizations.vscode.extensions`. `install.sh` also pre-creates any directories named in `extraCertDestinations` with `vscode` ownership so the remote extension can write without privileged escalation. Option values (`generateDotNetCert`, `syncUserCertificates`, `extraCertDestinations`) are surfaced to the runtime container via `/etc/environment`.

## Key Design Decisions

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ src/

devcontainer-feature/ Devcontainer feature
src/devcontainer-dev-certs/
devcontainer-feature.json Feature metadata, containerEnv, extension references
devcontainer-feature.json Feature metadata, options, extension references
install.sh Container build-time setup (creates directories)

test/
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "devcontainer-dev-certs",
"version": "1.0.1-pre",
"version": "1.0.1",
"name": "Dev Container Development Certificates",
"description": "Enables trusted HTTPS in Dev Containers by preparing certificate trust infrastructure and installing companion VS Code extensions that automatically generate, trust, and transfer ASP.NET and Aspire compatible development certificates from the host machine. Add to your devcontainer.json: \"features\": { \"ghcr.io/dnegstad/devcontainer-dev-certs/devcontainer-dev-certs\": {} }",
"documentationURL": "https://github.com/dnegstad/devcontainer-dev-certs",
Expand Down Expand Up @@ -48,9 +48,6 @@
]
}
},
"containerEnv": {
"SSL_CERT_DIR": "${containerEnv:HOME}/.aspnet/dev-certs/trust:/etc/ssl/certs:/usr/lib/ssl/certs:/etc/pki/tls/certs:/var/lib/ca-certificates/openssl"
},
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils",
"ghcr.io/devcontainers/features/dotnet"
Expand Down
26 changes: 17 additions & 9 deletions src/devcontainer-feature/src/devcontainer-dev-certs/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ set -e
# Options from devcontainer-feature.json (uppercased)
TRUST_NSS="${TRUSTNSS:-false}"
SSL_CERT_DIRS="${SSLCERTDIRS:-/etc/ssl/certs:/usr/lib/ssl/certs:/etc/pki/tls/certs:/var/lib/ca-certificates/openssl}"
DEFAULT_SSL_CERT_DIRS="/etc/ssl/certs:/usr/lib/ssl/certs:/etc/pki/tls/certs:/var/lib/ca-certificates/openssl"
GENERATE_DOTNET_CERT="${GENERATEDOTNETCERT:-true}"
SYNC_USER_CERTIFICATES="${SYNCUSERCERTIFICATES:-true}"
EXTRA_CERT_DESTINATIONS="${EXTRACERTDESTINATIONS:-}"
Expand Down Expand Up @@ -92,14 +91,22 @@ append_env() {
echo "${key}=\"${escaped}\"" >> /etc/environment
}

# If the user overrode sslCertDirs, we need to override the containerEnv value
# that was baked into the image with the default paths. containerEnv handles the
# default case; this only fires on user override.
if [ "${SSL_CERT_DIRS}" != "${DEFAULT_SSL_CERT_DIRS}" ]; then
SSL_CERT_DIR_VALUE="\$HOME/.aspnet/dev-certs/trust:${SSL_CERT_DIRS}"
append_env "SSL_CERT_DIR" "${SSL_CERT_DIR_VALUE}"
echo "Overriding SSL_CERT_DIR: ${SSL_CERT_DIR_VALUE}"
fi
# Set SSL_CERT_DIR for the container. The feature manifest can't set this via
# containerEnv because ${containerEnv:HOME} isn't resolvable at containerEnv
# bake time, and remoteEnv isn't permitted in a feature under strict-schema
# validation. Writing it here at install time covers both default and
# user-overridden sslCertDirs uniformly.
#
# /etc/profile.d/devcontainer-dev-certs.sh — sourced by login shells; $HOME
# expands per user, which is what VS Code's userEnvProbe picks up.
# /etc/environment — read by pam_env on PAM-based logins (sshd); needs the
# resolved REMOTE_USER_HOME baked in since pam_env doesn't expand $HOME.
PROFILE_SCRIPT="/etc/profile.d/devcontainer-dev-certs.sh"
echo "export SSL_CERT_DIR=\"\$HOME/.aspnet/dev-certs/trust:${SSL_CERT_DIRS}\"" > "${PROFILE_SCRIPT}"
chmod 0644 "${PROFILE_SCRIPT}"

SSL_CERT_DIR_RESOLVED="${REMOTE_USER_HOME}/.aspnet/dev-certs/trust:${SSL_CERT_DIRS}"
append_env "SSL_CERT_DIR" "${SSL_CERT_DIR_RESOLVED}"

# Surface the feature options to the running container so the remote extension
# can read them via process.env. extraCertDestinations can contain spaces
Expand All @@ -121,5 +128,6 @@ echo "Dev certificate infrastructure ready."
echo " .NET cert store: ${DOTNET_STORE_DIR}"
echo " .NET root store: ${DOTNET_ROOT_STORE_DIR}"
echo " OpenSSL trust: ${TRUST_DIR}"
echo " SSL_CERT_DIR: ${SSL_CERT_DIR_RESOLVED}"
echo " generateDotNetCert: ${GENERATE_DOTNET_CERT}"
echo " syncUserCertificates: ${SYNC_USER_CERTIFICATES}"
2 changes: 1 addition & 1 deletion src/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devcontainer-dev-certs/shared",
"version": "1.0.1-pre",
"version": "1.0.1",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/vscode-ui-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Dev Container Dev Certificates (Host)",
"description": "Generates and trusts ASP.NET and Aspire compatible HTTPS development certificates on the host machine for use in Dev Containers and remote environments.",
"icon": "images/dn_ui_extension_icon_256.png",
"version": "1.0.1-pre",
"version": "1.0.1",
"publisher": "dnegstad",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion src/vscode-workspace-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Dev Container Dev Certificates (Remote)",
"description": "Receives and installs ASP.NET and Aspire compatible HTTPS development certificates inside Dev Containers and remote environments.",
"icon": "images/dn_workspace_extension_icon_256.png",
"version": "1.0.1-pre",
"version": "1.0.1",
"publisher": "dnegstad",
"license": "MIT",
"repository": {
Expand Down
5 changes: 3 additions & 2 deletions src/vscode-workspace-extension/src/util/sslCertDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { getOpenSslTrustDir } from "@devcontainer-dev-certs/shared";
* Ensure SSL_CERT_DIR includes the dev-certs trust directory alongside
* the system CA directories.
*
* In devcontainer scenarios, the feature's containerEnv handles this.
* In devcontainer scenarios, the feature's install.sh handles this by
* writing /etc/profile.d/devcontainer-dev-certs.sh and /etc/environment.
* For SSH remoting, WSL, and other remotes, the workspace extension
* handles it here by writing a profile script if SSL_CERT_DIR isn't
* already configured with the trust directory.
Expand Down Expand Up @@ -39,7 +40,7 @@ export function ensureSslCertDir(systemCertDirs: string): void {
);
}
} catch {
// Not critical — the containerEnv or manual configuration can handle it.
// Not critical — the feature's profile.d script or manual configuration can handle it.
// This just improves the experience for terminal sessions.
}

Expand Down
32 changes: 17 additions & 15 deletions test/validate-feature.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,29 @@ check(

console.log("\nSSL_CERT_DIR consistency:");
const defaultSslDirs = feature.options?.sslCertDirs?.default;
const containerEnvSslCertDir = feature.containerEnv?.SSL_CERT_DIR;

// containerEnv should be ${containerEnv:HOME}/.aspnet/dev-certs/trust:<defaultSslDirs>
const expectedContainerEnv = `\${containerEnv:HOME}/.aspnet/dev-certs/trust:${defaultSslDirs}`;
// install.sh writes the trust dir + SSL_CERT_DIRS to /etc/profile.d (login
// shells, $HOME-expanded) and /etc/environment (PAM, REMOTE_USER_HOME-expanded).
check(
"containerEnv.SSL_CERT_DIR matches trust dir + sslCertDirs default",
containerEnvSslCertDir === expectedContainerEnv,
`expected "${expectedContainerEnv}" but got "${containerEnvSslCertDir}"`
);

// install.sh DEFAULT_SSL_CERT_DIRS should match the feature option default
const defaultMatch = installSh.match(
/DEFAULT_SSL_CERT_DIRS="([^"]+)"/
"install.sh writes SSL_CERT_DIR to /etc/profile.d/devcontainer-dev-certs.sh",
installSh.includes("/etc/profile.d/devcontainer-dev-certs.sh") &&
installSh.includes(
'export SSL_CERT_DIR=\\"\\$HOME/.aspnet/dev-certs/trust:${SSL_CERT_DIRS}\\"'
),
"expected install.sh to write `export SSL_CERT_DIR=\"$HOME/.aspnet/dev-certs/trust:${SSL_CERT_DIRS}\"` to /etc/profile.d/devcontainer-dev-certs.sh"
);
check(
"install.sh DEFAULT_SSL_CERT_DIRS matches feature option default",
defaultMatch && defaultMatch[1] === defaultSslDirs,
`install.sh has "${defaultMatch?.[1]}" but feature default is "${defaultSslDirs}"`
"install.sh appends SSL_CERT_DIR to /etc/environment with REMOTE_USER_HOME",
installSh.includes(
'append_env "SSL_CERT_DIR" "${SSL_CERT_DIR_RESOLVED}"'
) &&
installSh.includes(
'SSL_CERT_DIR_RESOLVED="${REMOTE_USER_HOME}/.aspnet/dev-certs/trust:${SSL_CERT_DIRS}"'
),
"expected install.sh to compute SSL_CERT_DIR_RESOLVED from REMOTE_USER_HOME and append it via append_env"
);

// install.sh SSLCERTDIRS fallback should match too
// install.sh SSLCERTDIRS fallback should match the feature option default
const fallbackMatch = installSh.match(
/SSL_CERT_DIRS="\$\{SSLCERTDIRS:-([^}]+)\}"/
);
Expand Down
Loading