Skip to content

Commit 9b618ee

Browse files
waldekmastykarzCopilotgarrytrinder
authored
Add Dev Container Feature for Dev Proxy (#2009)
* Add Dev Container Feature for Dev Proxy Adds a Dev Container Feature (features/src/devproxy), published as ghcr.io/dotnet/dev-proxy/devproxy, that installs Dev Proxy and, after the container is created, generates a per-container root certificate and trusts it system-wide, for Node.js and Python. Includes tests for the devcontainer CLI and workflows to test and publish the Feature. Closes #1334 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Harden Dev Container Feature certificate handling - Pin DEV_PROXY_CERT_PATH to the throwaway home during the build-time version check so an inherited absolute path can't leave a CA in the image - Generate the root certificate with umask 077 and restrict it to 0600 - Scan for stray certificates as root when possible and fail on incomplete scans Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Rename Dev Container Feature ID to dev-proxy Publishes the Feature as ghcr.io/dotnet/dev-proxy/dev-proxy so the name is spelled consistently with the repository. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Garry Trinder <garry@trinder365.co.uk>
1 parent 4837212 commit 9b618ee

22 files changed

Lines changed: 1101 additions & 0 deletions

‎.github/copilot-instructions.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ dotnet run --project DevProxy -- --detach --as-system-proxy false --port 0 --api
6060
- **CLI Commands:** `DevProxy/Commands/` — built with System.CommandLine, registered via `DevProxyCommand.cs`
6161
- **Configuration:** `DevProxy/config/devproxyrc.json` and other JSON files in `config/`
6262
- **macOS scripts:** `trust-cert.sh`, `remove-cert.sh`, `toggle-proxy.sh` in `DevProxy/`
63+
- **Dev Container Feature:** `features/src/dev-proxy` (published as `ghcr.io/dotnet/dev-proxy/dev-proxy`), tests in `features/test/dev-proxy`. Run tests from `features/` with `npx @devcontainers/cli features test -f dev-proxy .`. Bump the version in `devcontainer-feature.json` to publish a new Feature version
6364

6465
## Resources
6566

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Dev Container Feature - Release
2+
3+
# Publishes the Dev Container Feature in features/src to GHCR
4+
# (ghcr.io/dotnet/dev-proxy/dev-proxy). To release a new version of the
5+
# Feature, bump the version in devcontainer-feature.json. Versions that are
6+
# already published are skipped.
7+
8+
on:
9+
workflow_dispatch:
10+
push:
11+
branches: [ main ]
12+
paths: [ 'features/src/**' ]
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
publish:
19+
if: github.repository == 'dotnet/dev-proxy' && github.ref == 'refs/heads/main'
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
packages: write
24+
steps:
25+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26+
- name: Publish the Feature
27+
uses: devcontainers/action@bef2cf2d1a019b00b45e184f83035e01bd1972f3 # v1.4.4
28+
with:
29+
publish-features: 'true'
30+
base-path-to-features: ./features/src
31+
disable-repo-tagging: 'true'
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Dev Container Feature - Test
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [ main ]
7+
paths: [ 'features/**', '.github/workflows/devcontainer-feature-test.yml' ]
8+
pull_request:
9+
paths: [ 'features/**', '.github/workflows/devcontainer-feature-test.yml' ]
10+
11+
permissions:
12+
contents: read
13+
14+
env:
15+
DEVCONTAINER_CLI_VERSION: 0.89.0
16+
17+
jobs:
18+
test-autogenerated:
19+
name: Test on ${{ matrix.baseImage }} (${{ matrix.runner }})
20+
runs-on: ${{ matrix.runner }}
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
runner: [ ubuntu-latest, ubuntu-24.04-arm ]
25+
baseImage:
26+
- debian:latest
27+
- ubuntu:latest
28+
- mcr.microsoft.com/devcontainers/base:ubuntu
29+
steps:
30+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
31+
- name: Install the Dev Container CLI
32+
run: npm install -g @devcontainers/cli@${{ env.DEVCONTAINER_CLI_VERSION }}
33+
- name: Test the Feature with default options, installed once and twice
34+
working-directory: features
35+
run: devcontainer features test --skip-scenarios -f dev-proxy -i ${{ matrix.baseImage }} .
36+
37+
test-scenarios:
38+
name: Test scenarios (${{ matrix.runner }})
39+
runs-on: ${{ matrix.runner }}
40+
strategy:
41+
fail-fast: false
42+
matrix:
43+
runner: [ ubuntu-latest, ubuntu-24.04-arm ]
44+
steps:
45+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46+
- name: Install the Dev Container CLI
47+
run: npm install -g @devcontainers/cli@${{ env.DEVCONTAINER_CLI_VERSION }}
48+
- name: Test the Feature scenarios
49+
working-directory: features
50+
run: devcontainer features test -f dev-proxy --skip-autogenerated --skip-duplicated .
51+
52+
validate:
53+
name: Validate the Feature metadata
54+
runs-on: ubuntu-latest
55+
steps:
56+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57+
- name: Validate devcontainer-feature.json
58+
uses: devcontainers/action@bef2cf2d1a019b00b45e184f83035e01bd1972f3 # v1.4.4
59+
with:
60+
validate-only: 'true'
61+
base-path-to-features: ./features/src

‎README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ brew install dev-proxy
6161
bash -c "$(curl -sL https://aka.ms/devproxy/setup.sh)"
6262
```
6363

64+
**Dev containers and GitHub Codespaces** (installs Dev Proxy and trusts its certificate in the container, see [the Feature's docs](./features/src/dev-proxy/README.md)):
65+
66+
```json
67+
"features": {
68+
"ghcr.io/dotnet/dev-proxy/dev-proxy:1": {}
69+
}
70+
```
71+
6472
Download the preset for the API your app calls, and start Dev Proxy with it:
6573

6674
```bash

‎features/src/dev-proxy/README.md‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Dev Proxy (dev-proxy)
2+
3+
Installs [Dev Proxy](https://aka.ms/devproxy), an API simulator that helps you test how your app handles API failures, rate limits, latency, and more. Trusts a per-container Dev Proxy root certificate system-wide.
4+
5+
## Example Usage
6+
7+
```json
8+
"features": {
9+
"ghcr.io/dotnet/dev-proxy/dev-proxy:1": {}
10+
}
11+
```
12+
13+
## Options
14+
15+
| Options Id | Description | Type | Default Value |
16+
|-----|-----|-----|-----|
17+
| version | Dev Proxy version to install: 'latest', 'beta', or a specific version like '2.2.0' or 'v2.2.0'. | string | latest |
18+
| trustCertificate | After the container is created, generate a Dev Proxy root certificate unique to the container and trust it system-wide and for common runtimes. | boolean | true |
19+
20+
## Customizations
21+
22+
### VS Code Extensions
23+
24+
- `garrytrinder.dev-proxy-toolkit`
25+
26+
## What the Feature does
27+
28+
When the image is built, the Feature:
29+
30+
- Installs Dev Proxy in `/opt/devproxy` and adds the `devproxy` command to `/usr/local/bin`. For beta versions, `devproxy-beta` is available too.
31+
- Installs the required dependencies (`curl`, `unzip`, `ca-certificates`, `openssl`, ICU) if they're missing.
32+
- Creates the Dev Proxy configuration folder (`~/.config/dev-proxy`) for the remote user.
33+
- If `trustCertificate` is `true`, installs a root-owned helper that adds the Dev Proxy root certificate to the system certificate store, and allows the remote user to run it with `sudo` without a password.
34+
35+
The Feature doesn't generate the Dev Proxy root certificate when building the image. If it did, the certificate and its private key would end up in an image layer, and everyone using the same image or Codespaces prebuild would share the same trusted CA.
36+
37+
Instead, after the container is created (`postCreateCommand`), the Feature:
38+
39+
1. Runs `devproxy cert ensure` as the remote user to generate a root certificate unique to the container.
40+
2. Adds the certificate to the system certificate store (`/usr/local/share/ca-certificates` and `update-ca-certificates`), so that curl, .NET, and other tools that use the system store trust it.
41+
42+
Some runtimes don't use the system certificate store, so the Feature points them at the system CA bundle (`/etc/ssl/certs/ca-certificates.crt`):
43+
44+
| Variable | Used by |
45+
|----------|---------|
46+
| `NODE_EXTRA_CA_CERTS` | Node.js |
47+
| `REQUESTS_CA_BUNDLE` | Python `requests` |
48+
| `SSL_CERT_FILE` | Python and other OpenSSL-based tools |
49+
50+
Java keystores aren't supported.
51+
52+
Python 3.13 and newer enable strict certificate verification (`VERIFY_X509_STRICT`), which rejects the certificates that Dev Proxy v3 and earlier generate for intercepted hosts. Use Dev Proxy v4 or newer (`"version": "beta"` until v4 is released) to call APIs through Dev Proxy from Python 3.13+.
53+
54+
## Using Dev Proxy
55+
56+
The Feature doesn't start Dev Proxy or set `HTTP_PROXY`/`HTTPS_PROXY` for the whole container. Container-wide proxy variables would route package managers, git, and VS Code's own traffic through Dev Proxy, which breaks them when Dev Proxy isn't running.
57+
58+
Start Dev Proxy with your configuration, and configure the app you're testing to use it:
59+
60+
```bash
61+
devproxy --config-file .devproxy/devproxyrc.json
62+
```
63+
64+
```bash
65+
HTTPS_PROXY=http://127.0.0.1:8000 npm start
66+
```
67+
68+
## Not installing the Dev Proxy Toolkit
69+
70+
The Feature adds the [Dev Proxy Toolkit](https://marketplace.visualstudio.com/items?itemName=garrytrinder.dev-proxy-toolkit) VS Code extension. To skip it, exclude it in your `devcontainer.json`:
71+
72+
```json
73+
"customizations": {
74+
"vscode": {
75+
"extensions": ["-garrytrinder.dev-proxy-toolkit"]
76+
}
77+
}
78+
```
79+
80+
## OS support
81+
82+
The Feature supports Debian and Ubuntu-based images on x64 and arm64. Alpine and other musl-based images aren't supported.
83+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"id": "dev-proxy",
3+
"version": "1.0.0",
4+
"name": "Dev Proxy",
5+
"documentationURL": "https://github.com/dotnet/dev-proxy/tree/main/features/src/dev-proxy",
6+
"licenseURL": "https://github.com/dotnet/dev-proxy/blob/main/LICENSE",
7+
"description": "Installs Dev Proxy, an API simulator that helps you test how your app handles API failures, rate limits, latency, and more. Trusts a per-container Dev Proxy root certificate system-wide.",
8+
"keywords": [
9+
"dev-proxy",
10+
"devproxy",
11+
"api",
12+
"proxy",
13+
"testing",
14+
"mocking"
15+
],
16+
"options": {
17+
"version": {
18+
"type": "string",
19+
"proposals": [
20+
"latest",
21+
"beta"
22+
],
23+
"default": "latest",
24+
"description": "Dev Proxy version to install: 'latest', 'beta', or a specific version like '2.2.0' or 'v2.2.0'."
25+
},
26+
"trustCertificate": {
27+
"type": "boolean",
28+
"default": true,
29+
"description": "After the container is created, generate a Dev Proxy root certificate unique to the container and trust it system-wide and for common runtimes."
30+
}
31+
},
32+
"containerEnv": {
33+
"NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt",
34+
"REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt",
35+
"SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt"
36+
},
37+
"customizations": {
38+
"vscode": {
39+
"extensions": [
40+
"garrytrinder.dev-proxy-toolkit"
41+
]
42+
}
43+
},
44+
"postCreateCommand": "/usr/local/share/devproxy/post-create.sh",
45+
"installsAfter": [
46+
"ghcr.io/devcontainers/features/common-utils"
47+
]
48+
}

0 commit comments

Comments
 (0)