Files
2026-11-09 16:23:48 -05:00

127 lines
7.5 KiB
Markdown

# Resources: public vs private
Pangolin has two kinds of resource. The names vary across the codebase, the UI,
the API and blueprints, so learn the mapping:
| | Public resource | Private resource |
| --- | --- | --- |
| Other names | proxy resource, `resource` | site resource, client resource, `siteResource` |
| What it is | Exposed through Traefik on a domain/port, protected by Pangolin auth | Reachable only by Pangolin clients (Olm) over the tunnel |
| DB table | `resources` (`resourceId`, `resourceGuid`, `niceId`) | `siteResources` (`siteResourceId`, `niceId`) |
| Modes (`mode`) | `http`, `tcp`, `udp`, `ssh`, `rdp`, `vnc`, `inference` | `host`, `cidr`, `http`, `ssh`, `inference`, `gateway` |
| Backends | `targets` table (`server/routers/target/`) | Fields on the site resource itself + `siteNetworks`/`networks` |
| Access join tables | `roleResources`, `userResources`, `resourceWhitelist`, policies | `roleSiteResources`, `userSiteResources`, `clientSiteResources` |
| Labels | `resourceLabels` | `siteResourceLabels` |
| AI | `resourceAiProviders`, `resourceAiModels` | `siteResourceAiProviders`, `siteResourceAiModels` |
| Server router | `server/routers/resource/` (+ `server/private/routers/resource/`) | `server/routers/siteResource/` |
| Access middleware | `verifyResourceAccess` / `verifyApiKeyResourceAccess` | `verifySiteResourceAccess` / `verifyApiKeySiteResourceAccess` |
| Delete helper | `server/lib/deleteResource.ts` | `server/lib/deleteSiteResource.ts` |
| Blueprint key | `public-resources` (legacy alias `proxy-resources`) | `private-resources` (legacy alias `client-resources`) |
| Blueprint schema | `PublicResourceSchema` in `server/lib/blueprints/types.ts` | `PrivateResourceSchema` in the same file |
| Blueprint apply | `server/lib/blueprints/publicResources.ts` → `updatePublicResources` | `server/lib/blueprints/privateResources.ts` → `updatePrivateResources` |
| Blueprint export | `buildPublicResourceBlueprintConfig` in `server/lib/blueprints/exportResourceBlueprint.ts` | `buildPrivateResourceBlueprintConfig` in the same file |
| Integration API path | `/resource/...` **and** `/public-resource/...` | `/site-resource/...` **and** `/private-resource/...` |
| OpenAPI tags | `PublicResource` + `PublicResourceLegacy` | `PrivateResource` + `PrivateResourceLegacy` |
| UI pages | `src/app/[orgId]/settings/resources/public/` | `src/app/[orgId]/settings/resources/private/` |
| UI context | `ResourceProvider` / `useResourceContext` | `SiteResourceProvider` / `useSiteResourceContext`, `useSaveSiteResource` |
| Traefik config | `server/lib/traefik/getTraefikConfig.ts` (+ private override) | `server/lib/traefik/siteResourceAlias.ts` (cert placeholders for http-mode aliases) |
"Every resource" means both columns. When you add a capability to one kind, check
whether the other needs it too.
## Checklist: changing what a resource can be configured with
Use this whenever you touch `createResource`, `updateResource`,
`createSiteResource` or `updateSiteResource` (a new field, a renamed field, new
enum values, or a new mode). The blueprint must be able to do everything the API
can do.
1. **DB**: add the column to **both** `server/db/pg/schema/schema.ts` and
`server/db/sqlite/schema/schema.ts` using portable types (see
`docs/database.md`). Use `privateSchema.ts` if the feature is enterprise-only.
Don't write a migration script. If existing rows need values copied or
generated, add them to the release's migration notes
(`server/setup/migrationNotes/<version>.md`).
2. **API handlers**: update the zod body schemas in
- `server/routers/resource/createResource.ts`, `updateResource.ts`, or
- `server/routers/siteResource/createSiteResource.ts`, `updateSiteResource.ts`
and the `get`/`list` response types if the field is returned.
3. **Blueprint schema**: add the field to `PublicResourceSchema` or
`PrivateResourceSchema` in `server/lib/blueprints/types.ts`. Blueprint keys are
**kebab-case** (e.g. `"auth-daemon"`, `"estimated-time"`). DB/API fields are
camelCase. Enterprise-only blueprint fragments get a `#dynamic` schema with an
empty OSS stub, like `server/lib/blueprints/MaintenanceSchema.ts` ↔
`server/private/lib/blueprints/MaintenanceSchema.ts`.
4. **Blueprint apply**: map the blueprint field to the DB column in both the
create and the update branches of `updatePublicResources`
(`publicResources.ts`) or `updatePrivateResources` (`privateResources.ts`).
Mirror any paywall or tier checks the API handler does (see how
`tierMatrix.maintenancePage` is enforced there).
5. **Blueprint export**: emit the field in `buildPublicResourceBlueprintConfig` or
`buildPrivateResourceBlueprintConfig` (`exportResourceBlueprint.ts`) so a
resource round-trips through export and apply.
6. **Docker labels**: Newt docker labels (`pangolin.public-resources.*` /
`pangolin.proxy-resources.*` and `pangolin.private-resources.*` /
`pangolin.client-resources.*`) are parsed by
`parseDockerContainers.ts` / `parseDotNotation.ts` into the same schema. They
usually work once the schema is updated. Check them if the field is an array or
object.
7. **Integration API**: if you add a route, register it in
`server/routers/integration.ts` (or the private one) with **both** paths (see
below), and add a `registry.registerPath` per path with the right OpenAPI tag.
8. **UI**: update the relevant tab page under
`src/app/[orgId]/settings/resources/{public,private}/[niceId]/` and the create
page. All strings go in `messages/en-US.json`.
9. **Traefik / Newt / Olm**: if the field changes routing, update
`server/lib/traefik/` (public) or the Newt/Olm message builders in
`server/routers/newt/` and `server/routers/olm/` (private).
## Integration API aliases
Integration API resource routes always register the legacy path **and** the new
alias in one array:
```ts
authenticated.put(
["/org/:orgId/resource", "/org/:orgId/public-resource"],
verifyApiKeyOrgAccess,
verifyLimits,
verifyApiKeyHasAction(ActionsEnum.createResource),
logActionAudit(ActionsEnum.createResource),
resource.createResource
);
authenticated.get(
["/site-resource/:siteResourceId", "/private-resource/:siteResourceId"],
verifyApiKeySiteResourceAccess,
verifyApiKeyHasAction(ActionsEnum.getSiteResource),
siteResource.getSiteResource
);
```
Mapping: `resource(s)` → `public-resource(s)`, `resource-policy`/`resource-policies`
→ `public-resource-policy`/`public-resource-policies`, `site-resource(s)` →
`private-resource(s)`.
The handler registers OpenAPI docs for both: the legacy path with
`OpenAPITags.PublicResourceLegacy` (or `PrivateResourceLegacy`) and the alias
with `OpenAPITags.PublicResource` (or `PrivateResource`). See
`server/routers/resource/createResource.ts` and
`server/routers/siteResource/createSiteResource.ts`.
The dashboard routes in `server/routers/external.ts` don't need the alias.
## Related code
- Access checks: `server/auth/canUserAccessResource.ts`,
`server/auth/canUserAccessSiteResource.ts`, `server/lib/canUserAccessResource.ts`.
- Client ↔ site resource access recomputation:
`server/lib/rebuildClientAssociations.ts` (call it after changing who can
reach a site resource).
- Resource policies (shared auth and rules for public resources):
`server/routers/policy/`, `server/private/routers/policy/`, blueprint
`public-policies` → `server/lib/blueprints/resourcePolicies.ts`.
- Applying blueprints: `server/lib/blueprints/applyBlueprint.ts`, routes in
`server/routers/blueprints/`, UI in `src/app/[orgId]/settings/blueprints/`.