Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 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 Jul 9, 2026
a44a1ac
fix: address pre-PR review for secrets brokering
saifsmailbox98 Jul 9, 2026
e0b5de1
fix: rework proxied service form to match app conventions
saifsmailbox98 Jul 9, 2026
7f100aa
fix: align proxied service sheet with the create-secret form
saifsmailbox98 Jul 9, 2026
c34e01b
feat: show key icon in the proxied service secret picker
saifsmailbox98 Jul 9, 2026
8e18572
feat: proxied service entity color + field tooltips
saifsmailbox98 Jul 9, 2026
d542db5
fix: truncate long host patterns in the overview proxied service row
saifsmailbox98 Jul 9, 2026
b030de5
fix: truncate host pattern badge, add proxied service count to overvi…
saifsmailbox98 Jul 9, 2026
c711f65
fix: don't count proxied services as no-access secrets in overview
saifsmailbox98 Jul 9, 2026
ccb004c
feat: audit logs, license soft-gate, and validation hardening for pro…
saifsmailbox98 Jul 10, 2026
2a5d164
feat: register proxied service audit events in the frontend audit log…
saifsmailbox98 Jul 10, 2026
7490079
refactor: address review feedback for proxied services
saifsmailbox98 Jul 10, 2026
1cb1979
chore: use realistic timestamp for proxied-services migration
saifsmailbox98 Jul 10, 2026
0b544b4
docs: note why CredentialSubstitution enum value looks unused
saifsmailbox98 Jul 10, 2026
3a2e89c
fix: bound proxied service credential field lengths to match db columns
saifsmailbox98 Jul 10, 2026
283516f
docs: note intermediate CA TTL meaning
saifsmailbox98 Jul 10, 2026
4eccf85
feat: only allow attaching readable secrets in proxied service picker
saifsmailbox98 Jul 10, 2026
e06632e
refactor: gate proxied service dashboard includes like sibling resources
saifsmailbox98 Jul 10, 2026
eb8411a
chore: drop proxied services from the legacy single-env secret page
saifsmailbox98 Jul 10, 2026
d9c3f17
chore: trim non-essential comments from secrets brokering code
saifsmailbox98 Jul 10, 2026
62dcb35
fix: align proxied service form input heights with the secret picker
saifsmailbox98 Jul 11, 2026
10a629f
refactor: replace header mode tabs with a switch button in proxied se…
saifsmailbox98 Jul 11, 2026
88a3645
fix: refetch secrets in proxied service picker so deleted secrets don…
saifsmailbox98 Jul 11, 2026
124f844
improvement: clearer proxied service field hints and editable placeho…
saifsmailbox98 Jul 11, 2026
69fdd5b
improvement: consistent wording in proxied service substitution hints
saifsmailbox98 Jul 11, 2026
32ead6b
fix: honor tag-scoped secret deny rules when wiring proxied service c…
saifsmailbox98 Jul 11, 2026
f7d2e8d
improvement: sentence-style credential substitution rows and wider pr…
saifsmailbox98 Jul 11, 2026
f0abbd0
feat: support secret imports and references for proxied service crede…
saifsmailbox98 Jul 11, 2026
da82771
docs: expose proxied service endpoints in the API reference
saifsmailbox98 Jul 11, 2026
0c96fc0
docs: add Agent Proxy platform and CLI documentation
saifsmailbox98 Jul 11, 2026
3cfb964
docs: refine agent proxy docs
saifsmailbox98 Jul 11, 2026
802e511
feat: accept IPv6 proxied service host patterns and add host pattern …
saifsmailbox98 Jul 13, 2026
84b6e8f
refactor: split proxied service get endpoint into by-id and by-name r…
saifsmailbox98 Jul 13, 2026
f2927db
refactor: validate proxied service secret references via getSecrets
saifsmailbox98 Jul 13, 2026
40f455f
fix: re-validate retained proxied service credentials on every update
saifsmailbox98 Jul 13, 2026
fcfa601
fix: expand secret references when validating proxied service credent…
saifsmailbox98 Jul 13, 2026
7c40d2b
docs: document connect --allow-readable-brokered-secrets guardrail
saifsmailbox98 Jul 13, 2026
bcd0708
feat(ui): refine proxied service form, icon, and overview row
saifsmailbox98 Jul 13, 2026
4f928b3
feat(ui): refine secret substitution card (semantic layout, section i…
saifsmailbox98 Jul 13, 2026
0e389bf
docs: use <proxy-host> placeholder in agent-proxy --proxy examples
saifsmailbox98 Jul 13, 2026
95513ba
style: fix prettier formatting in proxied service form
saifsmailbox98 Jul 13, 2026
ce30ec2
docs: include --projectId in the agent-proxy connect example
saifsmailbox98 Jul 14, 2026
8306e0e
docs: include --projectId in agent-proxy connect usage line
saifsmailbox98 Jul 14, 2026
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
3 changes: 2 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ The `mineshaft-*` scale (50–900) is the underlying neutral ramp; see

Reserved for resource types in the secret management product:
`--color-folder`, `--color-secret`, `--color-dynamic-secret`,
`--color-import`, `--color-secret-rotation`, `--color-override`.
`--color-import`, `--color-secret-rotation`, `--color-override`,
`--color-proxied-service`.
Do not repurpose these for generic UI.

### Tint pattern
Expand Down
4 changes: 4 additions & 0 deletions backend/src/@types/fastify.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Cluster, Redis } from "ioredis";
import { TUsers } from "@app/db/schemas";
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-types";
import { TAgentProxyCaServiceFactory } from "@app/ee/services/agent-proxy-ca/agent-proxy-ca-service";
import { TAiMcpActivityLogServiceFactory } from "@app/ee/services/ai-mcp-activity-log/ai-mcp-activity-log-service";
import { TAiMcpEndpointServiceFactory } from "@app/ee/services/ai-mcp-endpoint/ai-mcp-endpoint-service";
import { TAiMcpServerServiceFactory } from "@app/ee/services/ai-mcp-server/ai-mcp-server-service";
Expand Down Expand Up @@ -56,6 +57,7 @@ import { TPkiScepServiceFactory } from "@app/ee/services/pki-scep/pki-scep-servi
import { TProjectEventsService } from "@app/ee/services/project-events/project-events-service";
import { TProjectEventsSSEService } from "@app/ee/services/project-events/project-events-sse-service";
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types";
import { TProxiedServiceServiceFactory } from "@app/ee/services/proxied-service/proxied-service-service";
import { RateLimitConfiguration, TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-types";
import { TRelayServiceFactory } from "@app/ee/services/relay/relay-service";
import { TResourceAuthMethodServiceFactory } from "@app/ee/services/resource-auth-method/resource-auth-method-service";
Expand Down Expand Up @@ -418,6 +420,8 @@ declare module "fastify" {
gitHubApp: TGitHubAppServiceFactory;
honeyTokenConfig: THoneyTokenConfigServiceFactory;
honeyToken: THoneyTokenServiceFactory;
proxiedService: TProxiedServiceServiceFactory;
agentProxyCa: TAgentProxyCaServiceFactory;
folderCommit: TFolderCommitServiceFactory;
pit: TPitServiceFactory;
secretScanningV2: TSecretScanningV2ServiceFactory;
Expand Down
24 changes: 24 additions & 0 deletions backend/src/@types/knex.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ import {
TOidcConfigs,
TOidcConfigsInsert,
TOidcConfigsUpdate,
TOrgAgentProxyConfig,
TOrgAgentProxyConfigInsert,
TOrgAgentProxyConfigUpdate,
TOrganizationAssets,
TOrganizationAssetsInsert,
TOrganizationAssetsUpdate,
Expand Down Expand Up @@ -485,6 +488,12 @@ import {
TProjectTemplateUserMemberships,
TProjectTemplateUserMembershipsInsert,
TProjectTemplateUserMembershipsUpdate,
TProxiedServiceCredentials,
TProxiedServiceCredentialsInsert,
TProxiedServiceCredentialsUpdate,
TProxiedServices,
TProxiedServicesInsert,
TProxiedServicesUpdate,
TRateLimit,
TRateLimitInsert,
TRateLimitUpdate,
Expand Down Expand Up @@ -1573,6 +1582,21 @@ declare module "knex/types/tables" {
TOrgGatewayConfigInsert,
TOrgGatewayConfigUpdate
>;
[TableName.OrgAgentProxyConfig]: KnexOriginal.CompositeTableType<
TOrgAgentProxyConfig,
TOrgAgentProxyConfigInsert,
TOrgAgentProxyConfigUpdate
>;
[TableName.ProxiedService]: KnexOriginal.CompositeTableType<
TProxiedServices,
TProxiedServicesInsert,
TProxiedServicesUpdate
>;
[TableName.ProxiedServiceCredential]: KnexOriginal.CompositeTableType<
TProxiedServiceCredentials,
TProxiedServiceCredentialsInsert,
TProxiedServiceCredentialsUpdate
>;
[TableName.SecretRotationV2]: KnexOriginal.CompositeTableType<
TSecretRotationsV2,
TSecretRotationsV2Insert,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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");

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");

t.string("secretKey").notNullable();
t.string("role").notNullable();

t.string("headerName");
t.string("headerPrefix");
t.string("headerPurpose");

t.string("placeholderKey");
t.string("placeholderValue");
t.specificType("substitutionSurfaces", "text[]");

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);
}
Comment thread
saifsmailbox98 marked this conversation as resolved.
3 changes: 3 additions & 0 deletions backend/src/db/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export * from "./microsoft-teams-integrations";
export * from "./models";
export * from "./oauth-clients";
export * from "./oidc-configs";
export * from "./org-agent-proxy-config";
export * from "./org-bots";
export * from "./org-gateway-config";
export * from "./org-gateway-config-v2";
Expand Down Expand Up @@ -194,6 +195,8 @@ export * from "./project-templates";
export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles";
export * from "./projects";
export * from "./proxied-service-credentials";
export * from "./proxied-services";
export * from "./rate-limit";
export * from "./relays";
export * from "./resource-auth-methods";
Expand Down
10 changes: 9 additions & 1 deletion backend/src/db/schemas/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ export enum TableName {
// Audit Reports (exportable compliance reports)
AuditReport = "audit_reports",

// Secrets Brokering (Agent Proxy)
OrgAgentProxyConfig = "org_agent_proxy_config",
ProxiedService = "proxied_services",
ProxiedServiceCredential = "proxied_service_credentials",

// Deprecated - Not used anymore now that Redis is persistent
DeprecatedDurableQueueJobs = "queue_jobs",
DeprecatedSecretRotationV1 = "secret_rotations",
Expand Down Expand Up @@ -366,7 +371,10 @@ export enum ProjectMembershipRole {
// ssh
SshHostBootstrapper = "ssh-host-bootstrapper",
// kms
KmsCryptographicOperator = "cryptographic-operator"
KmsCryptographicOperator = "cryptographic-operator",
// secrets brokering (agent proxy)
Agent = "agent",
AgentProxy = "agent-proxy"
}

export enum ResourceMembershipRole {
Expand Down
27 changes: 27 additions & 0 deletions backend/src/db/schemas/org-agent-proxy-config.ts
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>>;
29 changes: 29 additions & 0 deletions backend/src/db/schemas/proxied-service-credentials.ts
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>
>;
22 changes: 22 additions & 0 deletions backend/src/db/schemas/proxied-services.ts
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>>;
65 changes: 65 additions & 0 deletions backend/src/ee/routes/v1/agent-proxy-ca-router.ts
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;
}
});
};
5 changes: 5 additions & 0 deletions backend/src/ee/routes/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { injectCertManagerProjectId } from "@app/server/plugins/inject-cert-mana

import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
import { registerAgentProxyCaRouter } from "./agent-proxy-ca-router";
import { registerAiMcpActivityLogRouter } from "./ai-mcp-activity-log-router";
import { registerAiMcpEndpointRouter } from "./ai-mcp-endpoint-router";
import { registerAiMcpServerRouter } from "./ai-mcp-server-router";
Expand Down Expand Up @@ -42,6 +43,7 @@ import { registerPkiDiscoveryRouter } from "./pki-discovery-router";
import { registerPkiInstallationRouter } from "./pki-installation-router";
import { registerProjectRoleRouter } from "./project-role-router";
import { registerProjectRouter } from "./project-router";
import { registerProxiedServiceRouter } from "./proxied-service-router";
import { registerRateLimitRouter } from "./rate-limit-router";
import { registerRelayRouter } from "./relay-router";
import { registerSamlRouter } from "./saml-router";
Expand Down Expand Up @@ -113,6 +115,9 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerGithubOrgSyncRouter, { prefix: "/github-org-sync-config" });
await server.register(registerHoneyTokenRouter, { prefix: "/honey-tokens" });

await server.register(registerProxiedServiceRouter, { prefix: "/proxied-services" });
await server.register(registerAgentProxyCaRouter, { prefix: "/organization/agent-proxy-ca" });

await server.register(registerInsightsRouter, { prefix: "/insights" });

await server.register(
Expand Down
Loading
Loading