mirror of
https://github.com/fosrl/pangolin.git
synced 2026-11-10 01:32:29 -06:00
127 lines
7.5 KiB
Markdown
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/`.
|