Merge master
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
A local `ansible` repo is a Galaxy v3 server: `ansible-galaxy collection publish`
|
||||
and `ansible-galaxy collection install` work against `/api/v1/local/<repo>`.
|
||||
`publish` needs an API key; any value works.
|
||||
`publish` needs an API key; any value works unless [uploads are gated](../common/auth.md).
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection build
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Write authentication
|
||||
|
||||
artifactapi verifies writes against an OIDC issuer's JWKS, such as a Vault
|
||||
identity OIDC role. Reads, probes and anonymous pulls never need a token.
|
||||
|
||||
| Variable | Gates |
|
||||
|---|---|
|
||||
| `ARTIFACTAPI_OIDC_ISSUER` + `ARTIFACTAPI_OIDC_AUDIENCE` | creating, updating and deleting remotes and virtuals |
|
||||
| `ARTIFACTAPI_AUTH_UPLOADS=true` | also every local repo write: `PUT`/`DELETE /api/v2/remotes/<name>/files/*`, `DELETE /api/v2/{remotes,locals}/<name>/objects/*`, `PUT`/`POST`/`DELETE /api/v1/local/<repo>/*` (npm publish, gem push/yank, ansible-galaxy publish) and Docker Registry V2 push/delete under `/v2` |
|
||||
|
||||
Any token from the issuer whose `aud` is in `ARTIFACTAPI_OIDC_AUDIENCE` may
|
||||
write to every repo.
|
||||
|
||||
## Minting a token
|
||||
|
||||
```sh
|
||||
vault read -field=token identity/oidc/token/<role> > token
|
||||
```
|
||||
|
||||
The role's `client_id` must be one of the configured audiences.
|
||||
|
||||
## Passing the token
|
||||
|
||||
The token is accepted as `Authorization: Bearer <token>`, as `Token <token>`,
|
||||
as the password of HTTP Basic credentials with any username, or as a bare
|
||||
header value.
|
||||
|
||||
```sh
|
||||
# curl
|
||||
curl -fsSL -H "Authorization: Bearer $(cat token)" --upload-file f.tgz \
|
||||
https://artifactapi.example.com/api/v2/remotes/<repo>/files/f.tgz
|
||||
curl -fsSL -u "ci:$(cat token)" --upload-file f.tgz \
|
||||
https://artifactapi.example.com/api/v2/remotes/<repo>/files/f.tgz
|
||||
|
||||
# docker / podman / helm (OCI): any username, token as password
|
||||
docker login artifactapi.example.com -u ci --password-stdin < token
|
||||
helm registry login artifactapi.example.com -u ci --password-stdin < token
|
||||
|
||||
# npm (.npmrc)
|
||||
//artifactapi.example.com/api/v1/local/<repo>/:_authToken=<token>
|
||||
|
||||
# gem
|
||||
GEM_HOST_API_KEY="$(cat token)" gem push --host https://artifactapi.example.com/api/v1/local/<repo> mygem-1.0.0.gem
|
||||
|
||||
# ansible-galaxy
|
||||
ansible-galaxy collection publish c.tar.gz \
|
||||
--server https://artifactapi.example.com/api/v1/local/<repo>/ --api-key "$(cat token)"
|
||||
```
|
||||
|
||||
With uploads gated, `/v2/` answers anonymous requests with a Bearer challenge
|
||||
pointing at `/v2/token`. That endpoint returns the verified Basic password as
|
||||
the registry token, or an anonymous token that only reads, so `docker pull`
|
||||
keeps working without a login.
|
||||
+19
-1
@@ -15,7 +15,7 @@ Every proxied request on a remote is classified as **denied**, **immutable** or
|
||||
| `mutable_ttl` | Seconds a mutable path stays fresh. `0` also means never expires. Omitted on create, it defaults to `3600`; omitted on update, the stored value is kept. |
|
||||
| `check_mutable` | When a mutable path's `mutable_ttl` expires, sends a conditional `HEAD` (`If-None-Match`) with the stored upstream ETag. A `304` re-arms the TTL and serves the stored copy; anything else refetches in full. The ETag is kept for `max(10 × mutable_ttl, 7 days)`, re-armed on each `304`, so idle paths age out. It is only stored when `check_mutable` is set and `mutable_ttl` is non-zero, and never for immutable paths. |
|
||||
| `stale_on_error` | On an upstream network error, serve the stored copy instead of failing. |
|
||||
| `quarantine_enabled` / `quarantine_days` | Stored on the remote but not enforced by the proxy yet. |
|
||||
| `quarantine_enabled` / `quarantine_days` | Refuses immutable artifacts published less than `quarantine_days` (default `3`) ago. See [Quarantine](#quarantine). |
|
||||
| `mirrorlist` / `mirror_strategy` | rpm/deb/alpine only. Upstream pool is `[base_url] + mirrorlist`; `round_robin` (default) rotates the starting mirror, `least_conn` starts at the one with the fewest in-flight requests. Network errors and 5xx fail over to the next mirror; other statuses return as-is. |
|
||||
|
||||
## Precedence
|
||||
@@ -52,6 +52,24 @@ So `blocklist` beats the allowlist, and `immutable_patterns` beats
|
||||
Concurrent misses on one path share a fetch lock. Waiters poll the store for
|
||||
up to 5s instead of all going upstream.
|
||||
|
||||
## Quarantine
|
||||
|
||||
With `quarantine_enabled`, an uncached **immutable** path whose upstream copy
|
||||
was published less than `quarantine_days` ago answers `403` and is not cached:
|
||||
|
||||
```json
|
||||
{"error": "quarantined: published 2026-10-24T09:00:00Z, available from 2026-10-27T09:00:00Z",
|
||||
"published": "2026-10-24T09:00:00Z", "available": "2026-10-27T09:00:00Z"}
|
||||
```
|
||||
|
||||
- `quarantine_days` must be at least `1` when quarantine is enabled.
|
||||
- The publish time is the upstream `Last-Modified` header (capped at now), else the first time artifactapi was asked for the path.
|
||||
- If that first-seen lookup fails, the path answers `503` with `Retry-After` instead.
|
||||
- Mutable paths (indexes, metadata) are never quarantined.
|
||||
- Paths already cached keep serving, including ones cached before quarantine was enabled.
|
||||
- A virtual redirects to the member that owns the path, so its `403` reaches the client. A goproxy virtual probing for an unlisted version skips a quarantining member and only redirects to it when no other member serves the version.
|
||||
- Indexes still list fresh versions, so a client resolving "latest" may pick one and get the `403`.
|
||||
|
||||
## Eviction
|
||||
|
||||
```sh
|
||||
|
||||
@@ -10,6 +10,9 @@ docker push artifactapi.example.com/docker-internal/myapp:latest
|
||||
docker pull artifactapi.example.com/docker-internal/myapp:latest
|
||||
```
|
||||
|
||||
With [uploads gated](../common/auth.md), `docker login` with any username and
|
||||
a token as the password before pushing; pulls stay anonymous.
|
||||
|
||||
The first path segment after `/v2/` is the artifactapi repo name; the remainder
|
||||
is the image name. Blobs and manifests are stored through the shared
|
||||
content-addressable store (deduplicated by digest, reaped by GC once
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
A local `gems` repo is a RubyGems source: `gem push`, `gem yank`, `gem install`
|
||||
and `bundle install` work against `/api/v1/local/<repo>`. `gem push` needs an
|
||||
API key; any value works.
|
||||
API key; any value works unless [uploads are gated](../common/auth.md).
|
||||
|
||||
```bash
|
||||
GEM_HOST_API_KEY=unused gem push --host https://artifactapi.example.com/api/v1/local/<repo> mygem-1.0.0.gem
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
|
||||
A local `npm` repo is a real npm registry: `npm publish` (scoped and unscoped)
|
||||
and `npm install` work against `/api/v1/local/<repo>/`. npm requires an
|
||||
`_authToken` for the registry before it will publish; any value works.
|
||||
`_authToken` for the registry before it will publish; any value works unless
|
||||
[uploads are gated](../common/auth.md).
|
||||
|
||||
```ini
|
||||
# .npmrc
|
||||
|
||||
Reference in New Issue
Block a user