-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat(secret-manager): secrets brokering (proxied services + agent proxy CA) #7246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
saifsmailbox98
merged 43 commits into
main
from
saif/age2-39-implement-secrets-brokering-infisical-agent-proxy
Jul 14, 2026
Merged
Changes from 19 commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
bb1c601
feat: secrets brokering (proxied services + agent proxy CA)
saifsmailbox98 a44a1ac
fix: address pre-PR review for secrets brokering
saifsmailbox98 e0b5de1
fix: rework proxied service form to match app conventions
saifsmailbox98 7f100aa
fix: align proxied service sheet with the create-secret form
saifsmailbox98 c34e01b
feat: show key icon in the proxied service secret picker
saifsmailbox98 8e18572
feat: proxied service entity color + field tooltips
saifsmailbox98 d542db5
fix: truncate long host patterns in the overview proxied service row
saifsmailbox98 b030de5
fix: truncate host pattern badge, add proxied service count to overvi…
saifsmailbox98 c711f65
fix: don't count proxied services as no-access secrets in overview
saifsmailbox98 ccb004c
feat: audit logs, license soft-gate, and validation hardening for pro…
saifsmailbox98 2a5d164
feat: register proxied service audit events in the frontend audit log…
saifsmailbox98 7490079
refactor: address review feedback for proxied services
saifsmailbox98 1cb1979
chore: use realistic timestamp for proxied-services migration
saifsmailbox98 0b544b4
docs: note why CredentialSubstitution enum value looks unused
saifsmailbox98 3a2e89c
fix: bound proxied service credential field lengths to match db columns
saifsmailbox98 283516f
docs: note intermediate CA TTL meaning
saifsmailbox98 4eccf85
feat: only allow attaching readable secrets in proxied service picker
saifsmailbox98 e06632e
refactor: gate proxied service dashboard includes like sibling resources
saifsmailbox98 eb8411a
chore: drop proxied services from the legacy single-env secret page
saifsmailbox98 d9c3f17
chore: trim non-essential comments from secrets brokering code
saifsmailbox98 62dcb35
fix: align proxied service form input heights with the secret picker
saifsmailbox98 10a629f
refactor: replace header mode tabs with a switch button in proxied se…
saifsmailbox98 88a3645
fix: refetch secrets in proxied service picker so deleted secrets don…
saifsmailbox98 124f844
improvement: clearer proxied service field hints and editable placeho…
saifsmailbox98 69fdd5b
improvement: consistent wording in proxied service substitution hints
saifsmailbox98 32ead6b
fix: honor tag-scoped secret deny rules when wiring proxied service c…
saifsmailbox98 f7d2e8d
improvement: sentence-style credential substitution rows and wider pr…
saifsmailbox98 f0abbd0
feat: support secret imports and references for proxied service crede…
saifsmailbox98 da82771
docs: expose proxied service endpoints in the API reference
saifsmailbox98 0c96fc0
docs: add Agent Proxy platform and CLI documentation
saifsmailbox98 3cfb964
docs: refine agent proxy docs
saifsmailbox98 802e511
feat: accept IPv6 proxied service host patterns and add host pattern …
saifsmailbox98 84b6e8f
refactor: split proxied service get endpoint into by-id and by-name r…
saifsmailbox98 f2927db
refactor: validate proxied service secret references via getSecrets
saifsmailbox98 40f455f
fix: re-validate retained proxied service credentials on every update
saifsmailbox98 fcfa601
fix: expand secret references when validating proxied service credent…
saifsmailbox98 7c40d2b
docs: document connect --allow-readable-brokered-secrets guardrail
saifsmailbox98 bcd0708
feat(ui): refine proxied service form, icon, and overview row
saifsmailbox98 4f928b3
feat(ui): refine secret substitution card (semantic layout, section i…
saifsmailbox98 0e389bf
docs: use <proxy-host> placeholder in agent-proxy --proxy examples
saifsmailbox98 95513ba
style: fix prettier formatting in proxied service form
saifsmailbox98 ce30ec2
docs: include --projectId in the agent-proxy connect example
saifsmailbox98 8306e0e
docs: include --projectId in agent-proxy connect usage line
saifsmailbox98 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
backend/src/db/migrations/20260710162607_create-proxied-services.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { Knex } from "knex"; | ||
|
|
||
| import { TableName } from "../schemas"; | ||
| import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; | ||
|
|
||
| export async function up(knex: Knex): Promise<void> { | ||
| if (!(await knex.schema.hasTable(TableName.OrgAgentProxyConfig))) { | ||
| await knex.schema.createTable(TableName.OrgAgentProxyConfig, (t) => { | ||
| t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); | ||
|
|
||
| t.string("rootCaKeyAlgorithm").notNullable(); | ||
| t.datetime("rootCaIssuedAt").notNullable(); | ||
| t.datetime("rootCaExpiration").notNullable(); | ||
| t.string("rootCaSerialNumber").notNullable(); | ||
| t.binary("encryptedRootCaCertificate").notNullable(); | ||
| t.binary("encryptedRootCaPrivateKey").notNullable(); | ||
|
|
||
| t.uuid("orgId").notNullable(); | ||
| t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); | ||
| t.unique("orgId"); | ||
|
|
||
| t.timestamps(true, true, true); | ||
| }); | ||
|
|
||
| await createOnUpdateTrigger(knex, TableName.OrgAgentProxyConfig); | ||
| } | ||
|
|
||
| if (!(await knex.schema.hasTable(TableName.ProxiedService))) { | ||
| await knex.schema.createTable(TableName.ProxiedService, (t) => { | ||
| t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); | ||
|
|
||
| t.string("name").notNullable(); | ||
| t.string("hostPattern").notNullable(); | ||
| t.boolean("isEnabled").notNullable().defaultTo(true); | ||
|
|
||
| t.uuid("folderId").notNullable(); | ||
| t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE"); | ||
|
|
||
| // project is derived via folder -> environment -> project chain; no projectId column | ||
| t.unique(["folderId", "name"]); | ||
|
|
||
| t.timestamps(true, true, true); | ||
| }); | ||
|
|
||
| await createOnUpdateTrigger(knex, TableName.ProxiedService); | ||
| } | ||
|
|
||
| if (!(await knex.schema.hasTable(TableName.ProxiedServiceCredential))) { | ||
| await knex.schema.createTable(TableName.ProxiedServiceCredential, (t) => { | ||
| t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); | ||
|
|
||
| t.uuid("serviceId").notNullable(); | ||
| t.foreign("serviceId").references("id").inTable(TableName.ProxiedService).onDelete("CASCADE"); | ||
| t.index("serviceId"); | ||
|
|
||
| // Reference to a secret by key name only. Location is implicitly the parent | ||
| // service's folder in V1, so no folder/env/path columns are stored here. | ||
| t.string("secretKey").notNullable(); | ||
| t.string("role").notNullable(); // "header-rewrite" | "credential-substitution" | ||
|
|
||
| // Header rewriting fields (nullable) | ||
| t.string("headerName"); | ||
| t.string("headerPrefix"); | ||
| t.string("headerPurpose"); // basic auth: "username" | "password" | ||
|
|
||
| // Credential substitution fields (nullable) | ||
| t.string("placeholderKey"); | ||
| t.string("placeholderValue"); | ||
| t.specificType("substitutionSurfaces", "text[]"); // subset of header|path|query|body | ||
|
|
||
| t.timestamps(true, true, true); | ||
| }); | ||
|
|
||
| await createOnUpdateTrigger(knex, TableName.ProxiedServiceCredential); | ||
| } | ||
| } | ||
|
|
||
| export async function down(knex: Knex): Promise<void> { | ||
| await knex.schema.dropTableIfExists(TableName.ProxiedServiceCredential); | ||
| await dropOnUpdateTrigger(knex, TableName.ProxiedServiceCredential); | ||
|
|
||
| await knex.schema.dropTableIfExists(TableName.ProxiedService); | ||
| await dropOnUpdateTrigger(knex, TableName.ProxiedService); | ||
|
|
||
| await knex.schema.dropTableIfExists(TableName.OrgAgentProxyConfig); | ||
| await dropOnUpdateTrigger(knex, TableName.OrgAgentProxyConfig); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Code generated by automation script, DO NOT EDIT. | ||
| // Automated by pulling database and generating zod schema | ||
| // To update. Just run npm run generate:schema | ||
| // Written by akhilmhdh. | ||
|
|
||
| import { z } from "zod"; | ||
|
|
||
| import { zodBuffer } from "@app/lib/zod"; | ||
|
|
||
| import { TImmutableDBKeys } from "./models"; | ||
|
|
||
| export const OrgAgentProxyConfigSchema = z.object({ | ||
| id: z.string().uuid(), | ||
| rootCaKeyAlgorithm: z.string(), | ||
| rootCaIssuedAt: z.date(), | ||
| rootCaExpiration: z.date(), | ||
| rootCaSerialNumber: z.string(), | ||
| encryptedRootCaCertificate: zodBuffer, | ||
| encryptedRootCaPrivateKey: zodBuffer, | ||
| orgId: z.string().uuid(), | ||
| createdAt: z.date(), | ||
| updatedAt: z.date() | ||
| }); | ||
|
|
||
| export type TOrgAgentProxyConfig = z.infer<typeof OrgAgentProxyConfigSchema>; | ||
| export type TOrgAgentProxyConfigInsert = Omit<z.input<typeof OrgAgentProxyConfigSchema>, TImmutableDBKeys>; | ||
| export type TOrgAgentProxyConfigUpdate = Partial<Omit<z.input<typeof OrgAgentProxyConfigSchema>, TImmutableDBKeys>>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Code generated by automation script, DO NOT EDIT. | ||
| // Automated by pulling database and generating zod schema | ||
| // To update. Just run npm run generate:schema | ||
| // Written by akhilmhdh. | ||
|
|
||
| import { z } from "zod"; | ||
|
|
||
| import { TImmutableDBKeys } from "./models"; | ||
|
|
||
| export const ProxiedServiceCredentialsSchema = z.object({ | ||
| id: z.string().uuid(), | ||
| serviceId: z.string().uuid(), | ||
| secretKey: z.string(), | ||
| role: z.string(), | ||
| headerName: z.string().nullable().optional(), | ||
| headerPrefix: z.string().nullable().optional(), | ||
| headerPurpose: z.string().nullable().optional(), | ||
| placeholderKey: z.string().nullable().optional(), | ||
| placeholderValue: z.string().nullable().optional(), | ||
| substitutionSurfaces: z.string().array().nullable().optional(), | ||
| createdAt: z.date(), | ||
| updatedAt: z.date() | ||
| }); | ||
|
|
||
| export type TProxiedServiceCredentials = z.infer<typeof ProxiedServiceCredentialsSchema>; | ||
| export type TProxiedServiceCredentialsInsert = Omit<z.input<typeof ProxiedServiceCredentialsSchema>, TImmutableDBKeys>; | ||
| export type TProxiedServiceCredentialsUpdate = Partial< | ||
| Omit<z.input<typeof ProxiedServiceCredentialsSchema>, TImmutableDBKeys> | ||
| >; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Code generated by automation script, DO NOT EDIT. | ||
| // Automated by pulling database and generating zod schema | ||
| // To update. Just run npm run generate:schema | ||
| // Written by akhilmhdh. | ||
|
|
||
| import { z } from "zod"; | ||
|
|
||
| import { TImmutableDBKeys } from "./models"; | ||
|
|
||
| export const ProxiedServicesSchema = z.object({ | ||
| id: z.string().uuid(), | ||
| name: z.string(), | ||
| hostPattern: z.string(), | ||
| isEnabled: z.boolean().default(true), | ||
| folderId: z.string().uuid(), | ||
| createdAt: z.date(), | ||
| updatedAt: z.date() | ||
| }); | ||
|
|
||
| export type TProxiedServices = z.infer<typeof ProxiedServicesSchema>; | ||
| export type TProxiedServicesInsert = Omit<z.input<typeof ProxiedServicesSchema>, TImmutableDBKeys>; | ||
| export type TProxiedServicesUpdate = Partial<Omit<z.input<typeof ProxiedServicesSchema>, TImmutableDBKeys>>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { z } from "zod"; | ||
|
|
||
| import { EventType } from "@app/ee/services/audit-log/audit-log-types"; | ||
| import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; | ||
| import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; | ||
| import { AuthMode } from "@app/services/auth/auth-type"; | ||
| import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; | ||
|
|
||
| export const registerAgentProxyCaRouter = async (server: FastifyZodProvider) => { | ||
| server.route({ | ||
| method: "GET", | ||
| url: "/", | ||
| config: { rateLimit: readLimit }, | ||
| onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]), | ||
| schema: { | ||
| response: { | ||
| 200: z.object({ | ||
| certificate: z.string(), | ||
| keyAlgorithm: z.nativeEnum(CertKeyAlgorithm), | ||
| issuedAt: z.date(), | ||
| expiration: z.date(), | ||
| serialNumber: z.string() | ||
| }) | ||
| } | ||
| }, | ||
| handler: async (req) => { | ||
| return server.services.agentProxyCa.getRootCa(req.permission); | ||
| } | ||
| }); | ||
|
|
||
| server.route({ | ||
| method: "POST", | ||
| url: "/sign", | ||
| config: { rateLimit: writeLimit }, | ||
| onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]), | ||
| schema: { | ||
| body: z.object({ | ||
| publicKey: z.string().trim().min(1) | ||
| }), | ||
| response: { | ||
| 200: z.object({ | ||
| certificate: z.string(), | ||
| issuedAt: z.date(), | ||
| expiration: z.date(), | ||
| serialNumber: z.string() | ||
| }) | ||
| } | ||
| }, | ||
| handler: async (req) => { | ||
| const intermediateCa = await server.services.agentProxyCa.signIntermediate(req.permission, req.body.publicKey); | ||
| await server.services.auditLog.createAuditLog({ | ||
| ...req.auditLogInfo, | ||
| orgId: req.permission.orgId, | ||
| event: { | ||
| type: EventType.SIGN_AGENT_PROXY_INTERMEDIATE_CA, | ||
| metadata: { | ||
| serialNumber: intermediateCa.serialNumber, | ||
| expiration: intermediateCa.expiration.toISOString() | ||
| } | ||
| } | ||
| }); | ||
| return intermediateCa; | ||
| } | ||
| }); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.