Skip to content
Draft
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,15 @@ Or set globally:
composer config -g repositories.proxy composer http://localhost:8080/composer
```

`composer audit`, `audit.block-insecure` and Packagist's malware filter list
work through the proxy when `upstream.composer_repository` offers them in its
`packages.json`, as repo.packagist.org does. The proxy advertises them in its
own `packages.json` and passes the advisories and filter list requests through
to the upstream uncached. Names of packages in `upstream.composer_routes` or
`hosted.composer_vendors` are removed from advisories requests before they are
sent. Versions withheld by [cooldown](#version-cooldown) are not excluded from
the report.

To serve private packages through the same URL, see [Private package routes](docs/configuration.md#private-package-routes).

### Conan (C/C++)
Expand Down Expand Up @@ -1280,6 +1289,8 @@ Recently cached:
| `GET /maven/*` | Maven repository protocol |
| `GET /nuget/*` | NuGet V3 API |
| `GET /composer/*` | Composer/Packagist protocol |
| `GET`, `POST /composer/api/security-advisories/` | Composer security advisories, passed through to upstream without private package names |
| `GET /composer/lists/summary.json` | Composer filter list summary, passed through to upstream uncached |
| `GET /conan/*` | Conan C/C++ protocol |
| `GET /chef/*` | Chef Supermarket protocol (universe, cookbook API and downloads) |
| `GET /conda/*` | Conda/Anaconda protocol |
Expand Down
3 changes: 3 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ upstream:
composer_repository: "https://upstream-proxy.example.com/composer"
```

The proxy reads `packages.json` from `upstream.composer_repository` and keeps it in memory for the metadata TTL, or an hour when the TTL is 0, whether or not `cache_metadata` is on. A failed fetch is not retried within that time: the last good copy is used, or the entries are left out. It passes on its `security-advisories` and `filter` entries (Packagist's malware list) with URLs pointing at the proxy. An upstream that offers neither is still usable; `composer audit` then reports nothing from it. A filter `api-url` is not passed on; Composer reads filter entries from package metadata instead.

`upstream.hex_api` is used for cooldown timestamps and must expose Hex's `/api/packages/{name}` JSON endpoint.

`upstream.chef` is the Supermarket served at `/chef/`, for Berkshelf (`source "https://proxy.example.com/chef"`) and Policyfiles (`default_source :supermarket, "https://proxy.example.com/chef"`). The upstream has to serve the Supermarket API: `/universe`, `/api/v1/cookbooks/{name}`, its versions and their downloads. A private Supermarket that needs credentials takes them from `upstream.auth`, scoped to its URL. A download redirect to another host, such as the storage bucket behind Supermarket, does not receive them:
Expand Down Expand Up @@ -388,6 +390,7 @@ upstream:
- Composer metadata of routed packages carries an empty `notification-url`, so Composer does not report their installs to the default upstream's `notify-batch` endpoint.
- Use `upstream.auth` for the registry's credentials. The proxy has no authentication of its own, so every client that can reach it can download routed packages.
- Search (`/composer/search.json`) and the package list (`/composer/packages/list.json`) still come from the default upstream only.
- Composer security advisories and filter lists come from the default upstream. Routed package names, and those in `hosted.composer_vendors`, are removed from advisories requests before they are sent, so they only get advisories that their route's own metadata carries.
- Attestations of a routed npm version (`/npm/-/npm/v1/attestations/...`) come from the route, like its tarballs.
- npm audit, signing-key, search and ping requests still go to `upstream.npm`. An audit request lists every package in the dependency tree, routed ones included.
- Routes can only be set in the configuration file, not with environment variables.
Expand Down
16 changes: 14 additions & 2 deletions internal/handler/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ComposerHandler struct {
repoURL string
proxyURL string
routes packageRoutes
indexCache composerIndexCache
}

// NewComposerHandler creates a new Composer protocol handler.
Expand Down Expand Up @@ -100,19 +101,30 @@ func (h *ComposerHandler) Routes() http.Handler {
mux.HandleFunc("GET /search.json", h.proxyUpstream)
mux.HandleFunc("GET /packages/list.json", h.proxyUpstream)

// Security advisories and filter lists, as the default repository offers them
mux.HandleFunc("GET "+composerAdvisoriesPath, h.handleSecurityAdvisories)
mux.HandleFunc("POST "+composerAdvisoriesPath, h.handleSecurityAdvisories)
mux.HandleFunc("GET "+composerFilterSummaryPath, h.handleFilterSummary)

return mux
}

// handleServiceIndex returns the Composer repository service index.
// handleServiceIndex returns the Composer repository service index. It
// carries the security advisories and filter list entries of the default
// repository's own index when that has them, kept in memory for the metadata
// TTL; if the repository has not been reached, the index goes out without
// them.
func (h *ComposerHandler) handleServiceIndex(w http.ResponseWriter, r *http.Request) {
// Return a minimal service index pointing to our proxy
index := map[string]any{
"packages": map[string]any{},
"metadata-url": h.proxyURL + "/composer/p2/%package%.json",
"notify-batch": h.upstreamURL + "/downloads/",
"search": h.proxyURL + "/composer/search.json?q=%query%&type=%type%",
"providers-lazy-url": h.proxyURL + "/composer/p2/%package%.json",
}
if upstream, ok := h.upstreamIndex(r.Context()); ok {
upstream.advertise(index, h.proxyURL)
}

w.Header().Set(headerContentType, "application/json")
_ = json.NewEncoder(w).Encode(index)
Expand Down
Loading
Loading