Skip to content

Commit 550c544

Browse files
committed
fix: build and address review feedback
1 parent e73f44c commit 550c544

File tree

22 files changed

+632
-546
lines changed

22 files changed

+632
-546
lines changed

backend/src/@types/fastify.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/
9393
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
9494
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
9595
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
96-
import { TIdentitySpiffeAuthServiceFactory } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-service";
9796
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
9897
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
9998
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
10099
import { TIdentityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-auth-service";
101100
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
102101
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
102+
import { TIdentitySpiffeAuthServiceFactory } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-service";
103103
import { TIdentityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-types";
104104
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
105105
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";

backend/src/db/migrations/20260305201413_add-spiffe-machine-auth.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export async function up(knex: Knex): Promise<void> {
1515
t.string("configurationType").notNullable();
1616
t.binary("encryptedCaBundleJwks").nullable();
1717
t.string("bundleEndpointUrl").nullable();
18-
t.string("bundleEndpointProfile").nullable();
1918
t.binary("encryptedBundleEndpointCaCert").nullable();
2019
t.binary("encryptedCachedBundleJwks").nullable();
2120
t.datetime("cachedBundleLastRefreshedAt").nullable();

backend/src/db/schemas/identity-spiffe-auths.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ export const IdentitySpiffeAuthsSchema = z.object({
1818
configurationType: z.string(),
1919
encryptedCaBundleJwks: zodBuffer.nullable().optional(),
2020
bundleEndpointUrl: z.string().nullable().optional(),
21-
bundleEndpointProfile: z.string().nullable().optional(),
2221
encryptedBundleEndpointCaCert: zodBuffer.nullable().optional(),
2322
encryptedCachedBundleJwks: zodBuffer.nullable().optional(),
2423
cachedBundleLastRefreshedAt: z.date().nullable().optional(),
25-
bundleRefreshHintSeconds: z.coerce.number().default(300),
24+
bundleRefreshHintSeconds: z.number().default(300),
2625
accessTokenTTL: z.coerce.number().default(7200),
2726
accessTokenMaxTTL: z.coerce.number().default(7200),
2827
accessTokenNumUsesLimit: z.coerce.number().default(0),

backend/src/db/schemas/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ export * from "./identity-azure-auths";
6464
export * from "./identity-gcp-auths";
6565
export * from "./identity-group-membership";
6666
export * from "./identity-jwt-auths";
67-
export * from "./identity-spiffe-auths";
6867
export * from "./identity-kubernetes-auths";
6968
export * from "./identity-metadata";
7069
export * from "./identity-oci-auths";
@@ -73,6 +72,7 @@ export * from "./identity-org-memberships";
7372
export * from "./identity-project-additional-privilege";
7473
export * from "./identity-project-membership-role";
7574
export * from "./identity-project-memberships";
75+
export * from "./identity-spiffe-auths";
7676
export * from "./identity-tls-cert-auths";
7777
export * from "./identity-token-auths";
7878
export * from "./identity-ua-client-secrets";

backend/src/lib/api-docs/constants.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -758,17 +758,16 @@ export const SPIFFE_AUTH = {
758758
allowedSpiffeIds:
759759
"Comma-separated list of allowed SPIFFE ID patterns. Supports picomatch glob patterns (e.g. spiffe://prod.example.com/**).",
760760
allowedAudiences: "Comma-separated list of allowed audiences for JWT-SVID validation.",
761-
configurationType:
762-
"The configuration type for trust bundle management. Must be one of: 'static' (admin uploads JWKS), 'remote' (auto-refresh from SPIRE bundle endpoint).",
763-
caBundleJwks:
764-
"The JWKS JSON containing public keys for JWT-SVID verification. Required if configurationType is 'static'.",
765-
bundleEndpointUrl:
766-
"The SPIRE bundle endpoint URL for automatic trust bundle retrieval. Required if configurationType is 'remote'.",
767-
bundleEndpointProfile:
768-
"The bundle endpoint authentication profile. Must be one of: 'https_web' (standard HTTPS), 'https_spiffe' (mTLS with SPIFFE auth).",
769-
bundleEndpointCaCert:
770-
"The PEM-encoded CA certificate for verifying the bundle endpoint TLS connection. Required when bundleEndpointProfile is 'https_spiffe'.",
771-
bundleRefreshHintSeconds: "The interval in seconds between bundle refresh attempts. Defaults to 300.",
761+
trustBundleDistribution: {
762+
profile:
763+
"The trust bundle distribution profile. Must be one of: 'static' (admin uploads JWKS), 'https_web_bundle' (auto-refresh from HTTPS endpoint), 'https_spiffe_bundle' (auto-refresh with SPIFFE mTLS auth).",
764+
bundle: "The JWKS JSON containing public keys for JWT-SVID verification. Required when profile is 'static'.",
765+
endpointUrl:
766+
"The SPIRE bundle endpoint URL for automatic trust bundle retrieval. Required when profile is 'https_web_bundle' or 'https_spiffe_bundle'.",
767+
caCert:
768+
"The PEM-encoded CA certificate for verifying the bundle endpoint TLS connection. Required when profile is 'https_spiffe_bundle'.",
769+
refreshHintSeconds: "The interval in seconds between bundle refresh attempts. Defaults to 3600."
770+
},
772771
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
773772
accessTokenTTL: "The lifetime for an access token in seconds.",
774773
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
@@ -779,12 +778,13 @@ export const SPIFFE_AUTH = {
779778
trustDomain: "The new SPIFFE trust domain.",
780779
allowedSpiffeIds: "The new comma-separated list of allowed SPIFFE ID patterns.",
781780
allowedAudiences: "The new comma-separated list of allowed audiences.",
782-
configurationType: "The new configuration type for trust bundle management.",
783-
caBundleJwks: "The new JWKS JSON containing public keys.",
784-
bundleEndpointUrl: "The new SPIRE bundle endpoint URL.",
785-
bundleEndpointProfile: "The new bundle endpoint authentication profile.",
786-
bundleEndpointCaCert: "The new PEM-encoded CA certificate for the bundle endpoint.",
787-
bundleRefreshHintSeconds: "The new interval in seconds between bundle refresh attempts.",
781+
trustBundleDistribution: {
782+
profile: "The new trust bundle distribution profile.",
783+
bundle: "The new JWKS JSON containing public keys.",
784+
endpointUrl: "The new SPIRE bundle endpoint URL.",
785+
caCert: "The new PEM-encoded CA certificate for the bundle endpoint.",
786+
refreshHintSeconds: "The new interval in seconds between bundle refresh attempts."
787+
},
788788
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
789789
accessTokenTTL: "The new lifetime for an access token in seconds.",
790790
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",

backend/src/server/routes/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,6 @@ import { identityGcpAuthDALFactory } from "@app/services/identity-gcp-auth/ident
285285
import { identityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
286286
import { identityJwtAuthDALFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-dal";
287287
import { identityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
288-
import { identitySpiffeAuthDALFactory } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-dal";
289-
import { identitySpiffeAuthServiceFactory } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-service";
290288
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
291289
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
292290
import { identityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
@@ -297,6 +295,8 @@ import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/ide
297295
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
298296
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
299297
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
298+
import { identitySpiffeAuthDALFactory } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-dal";
299+
import { identitySpiffeAuthServiceFactory } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-service";
300300
import { identityTlsCertAuthDALFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-dal";
301301
import { identityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-service";
302302
import { identityTokenAuthDALFactory } from "@app/services/identity-token-auth/identity-token-auth-dal";
@@ -1972,7 +1972,6 @@ export const registerRoutes = async (
19721972
membershipIdentityDAL
19731973
});
19741974

1975-
19761975
const identityLdapAuthService = identityLdapAuthServiceFactory({
19771976
identityLdapAuthDAL,
19781977
orgDAL,

backend/src/server/routes/v1/identity-spiffe-auth-router.ts

Lines changed: 95 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,109 @@ import { slugSchema } from "@app/server/lib/schemas";
88
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
99
import { AuthMode } from "@app/services/auth/auth-type";
1010
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
11-
import {
12-
SpiffeBundleEndpointProfile,
13-
SpiffeConfigurationType
14-
} from "@app/services/identity-spiffe-auth/identity-spiffe-auth-types";
11+
import { SpiffeTrustBundleProfile } from "@app/services/identity-spiffe-auth/identity-spiffe-auth-types";
1512
import {
1613
validateSpiffeAllowedAudiencesField,
1714
validateSpiffeAllowedIdsField,
1815
validateTrustDomain
1916
} from "@app/services/identity-spiffe-auth/identity-spiffe-auth-validators";
2017
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
2118

22-
const IdentitySpiffeAuthResponseSchema = IdentitySpiffeAuthsSchema.omit({
23-
encryptedCaBundleJwks: true,
24-
encryptedBundleEndpointCaCert: true,
25-
encryptedCachedBundleJwks: true
19+
const StaticTrustBundleSchema = z.object({
20+
profile: z.literal(SpiffeTrustBundleProfile.STATIC).describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.profile),
21+
bundle: z.string().min(1).describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.bundle)
22+
});
23+
24+
const HttpsWebBundleSchema = z.object({
25+
profile: z
26+
.literal(SpiffeTrustBundleProfile.HTTPS_WEB_BUNDLE)
27+
.describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.profile),
28+
endpointUrl: z
29+
.string()
30+
.trim()
31+
.url()
32+
.refine((url) => url.startsWith("https://"), "Bundle endpoint URL must use HTTPS")
33+
.describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.endpointUrl),
34+
refreshHintSeconds: z
35+
.number()
36+
.int()
37+
.min(0)
38+
.default(3600)
39+
.describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.refreshHintSeconds)
40+
});
41+
42+
const HttpsSpiffeBundleSchema = z.object({
43+
profile: z
44+
.literal(SpiffeTrustBundleProfile.HTTPS_SPIFFE_BUNDLE)
45+
.describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.profile),
46+
endpointUrl: z
47+
.string()
48+
.trim()
49+
.url()
50+
.refine((url) => url.startsWith("https://"), "Bundle endpoint URL must use HTTPS")
51+
.describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.endpointUrl),
52+
caCert: z.string().min(1).describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.caCert),
53+
refreshHintSeconds: z
54+
.number()
55+
.int()
56+
.min(0)
57+
.default(3600)
58+
.describe(SPIFFE_AUTH.ATTACH.trustBundleDistribution.refreshHintSeconds)
59+
});
60+
61+
const TrustBundleDistributionSchema = z.discriminatedUnion("profile", [
62+
StaticTrustBundleSchema,
63+
HttpsWebBundleSchema,
64+
HttpsSpiffeBundleSchema
65+
]);
66+
67+
const StaticTrustBundleResponseSchema = z.object({
68+
profile: z.literal(SpiffeTrustBundleProfile.STATIC),
69+
bundle: z.string()
70+
});
71+
72+
const HttpsWebBundleResponseSchema = z.object({
73+
profile: z.literal(SpiffeTrustBundleProfile.HTTPS_WEB_BUNDLE),
74+
endpointUrl: z.string(),
75+
refreshHintSeconds: z.number(),
76+
cachedBundleLastRefreshedAt: z.date().nullable().optional()
77+
});
78+
79+
const HttpsSpiffeBundleResponseSchema = z.object({
80+
profile: z.literal(SpiffeTrustBundleProfile.HTTPS_SPIFFE_BUNDLE),
81+
endpointUrl: z.string(),
82+
caCert: z.string(),
83+
refreshHintSeconds: z.number(),
84+
cachedBundleLastRefreshedAt: z.date().nullable().optional()
85+
});
86+
87+
const TrustBundleDistributionResponseSchema = z.discriminatedUnion("profile", [
88+
StaticTrustBundleResponseSchema,
89+
HttpsWebBundleResponseSchema,
90+
HttpsSpiffeBundleResponseSchema
91+
]);
92+
93+
const IdentitySpiffeAuthResponseSchema = IdentitySpiffeAuthsSchema.pick({
94+
id: true,
95+
identityId: true,
96+
trustDomain: true,
97+
allowedSpiffeIds: true,
98+
allowedAudiences: true,
99+
accessTokenTTL: true,
100+
accessTokenMaxTTL: true,
101+
accessTokenNumUsesLimit: true,
102+
accessTokenTrustedIps: true,
103+
createdAt: true,
104+
updatedAt: true
26105
}).extend({
27-
caBundleJwks: z.string(),
28-
bundleEndpointCaCert: z.string()
106+
trustBundleDistribution: TrustBundleDistributionResponseSchema
29107
});
30108

31109
const CommonCreateFields = z.object({
32110
trustDomain: validateTrustDomain.describe(SPIFFE_AUTH.ATTACH.trustDomain),
33111
allowedSpiffeIds: validateSpiffeAllowedIdsField.describe(SPIFFE_AUTH.ATTACH.allowedSpiffeIds),
34112
allowedAudiences: validateSpiffeAllowedAudiencesField.describe(SPIFFE_AUTH.ATTACH.allowedAudiences),
113+
trustBundleDistribution: TrustBundleDistributionSchema,
35114
accessTokenTrustedIps: z
36115
.object({
37116
ipAddress: z.string().trim()
@@ -51,68 +130,7 @@ const CommonCreateFields = z.object({
51130
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(SPIFFE_AUTH.ATTACH.accessTokenNumUsesLimit)
52131
});
53132

54-
const CommonUpdateFields = z
55-
.object({
56-
trustDomain: validateTrustDomain.describe(SPIFFE_AUTH.UPDATE.trustDomain),
57-
allowedSpiffeIds: validateSpiffeAllowedIdsField.describe(SPIFFE_AUTH.UPDATE.allowedSpiffeIds),
58-
allowedAudiences: validateSpiffeAllowedAudiencesField.describe(SPIFFE_AUTH.UPDATE.allowedAudiences),
59-
accessTokenTrustedIps: z
60-
.object({
61-
ipAddress: z.string().trim()
62-
})
63-
.array()
64-
.min(1)
65-
.describe(SPIFFE_AUTH.UPDATE.accessTokenTrustedIps),
66-
accessTokenTTL: z
67-
.number()
68-
.int()
69-
.min(0)
70-
.max(315360000)
71-
.describe(SPIFFE_AUTH.UPDATE.accessTokenTTL),
72-
accessTokenMaxTTL: z
73-
.number()
74-
.int()
75-
.min(0)
76-
.max(315360000)
77-
.describe(SPIFFE_AUTH.UPDATE.accessTokenMaxTTL),
78-
accessTokenNumUsesLimit: z.number().int().min(0).describe(SPIFFE_AUTH.UPDATE.accessTokenNumUsesLimit)
79-
})
80-
.partial();
81-
82-
const StaticConfigurationSchema = z.object({
83-
configurationType: z
84-
.literal(SpiffeConfigurationType.STATIC)
85-
.describe(SPIFFE_AUTH.ATTACH.configurationType),
86-
caBundleJwks: z.string().min(1).describe(SPIFFE_AUTH.ATTACH.caBundleJwks),
87-
bundleEndpointUrl: z.string().optional().default(""),
88-
bundleEndpointProfile: z.nativeEnum(SpiffeBundleEndpointProfile).optional(),
89-
bundleEndpointCaCert: z.string().optional().default(""),
90-
bundleRefreshHintSeconds: z.number().int().min(0).optional().default(3600)
91-
});
92-
93-
const RemoteConfigurationSchema = z.object({
94-
configurationType: z
95-
.literal(SpiffeConfigurationType.REMOTE)
96-
.describe(SPIFFE_AUTH.ATTACH.configurationType),
97-
caBundleJwks: z.string().optional().default(""),
98-
bundleEndpointUrl: z
99-
.string()
100-
.trim()
101-
.url()
102-
.refine((url) => url.startsWith("https://"), "Bundle endpoint URL must use HTTPS")
103-
.describe(SPIFFE_AUTH.ATTACH.bundleEndpointUrl),
104-
bundleEndpointProfile: z
105-
.nativeEnum(SpiffeBundleEndpointProfile)
106-
.default(SpiffeBundleEndpointProfile.HTTPS_WEB)
107-
.describe(SPIFFE_AUTH.ATTACH.bundleEndpointProfile),
108-
bundleEndpointCaCert: z.string().optional().default("").describe(SPIFFE_AUTH.ATTACH.bundleEndpointCaCert),
109-
bundleRefreshHintSeconds: z
110-
.number()
111-
.int()
112-
.min(0)
113-
.default(3600)
114-
.describe(SPIFFE_AUTH.ATTACH.bundleRefreshHintSeconds)
115-
});
133+
const CommonUpdateFields = CommonCreateFields.partial();
116134

117135
export const registerIdentitySpiffeAuthRouter = async (server: FastifyZodProvider) => {
118136
server.route({
@@ -185,10 +203,7 @@ export const registerIdentitySpiffeAuthRouter = async (server: FastifyZodProvide
185203
params: z.object({
186204
identityId: z.string().trim().describe(SPIFFE_AUTH.ATTACH.identityId)
187205
}),
188-
body: z.discriminatedUnion("configurationType", [
189-
StaticConfigurationSchema.merge(CommonCreateFields),
190-
RemoteConfigurationSchema.merge(CommonCreateFields)
191-
]),
206+
body: CommonCreateFields,
192207
response: {
193208
200: z.object({
194209
identitySpiffeAuth: IdentitySpiffeAuthResponseSchema
@@ -216,7 +231,7 @@ export const registerIdentitySpiffeAuthRouter = async (server: FastifyZodProvide
216231
trustDomain: identitySpiffeAuth.trustDomain,
217232
allowedSpiffeIds: identitySpiffeAuth.allowedSpiffeIds,
218233
allowedAudiences: identitySpiffeAuth.allowedAudiences,
219-
configurationType: identitySpiffeAuth.configurationType,
234+
configurationType: identitySpiffeAuth.trustBundleDistribution.profile,
220235
accessTokenTTL: identitySpiffeAuth.accessTokenTTL,
221236
accessTokenMaxTTL: identitySpiffeAuth.accessTokenMaxTTL,
222237
accessTokenTrustedIps: identitySpiffeAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
@@ -251,10 +266,7 @@ export const registerIdentitySpiffeAuthRouter = async (server: FastifyZodProvide
251266
params: z.object({
252267
identityId: z.string().trim().describe(SPIFFE_AUTH.UPDATE.identityId)
253268
}),
254-
body: z.discriminatedUnion("configurationType", [
255-
StaticConfigurationSchema.merge(CommonUpdateFields),
256-
RemoteConfigurationSchema.merge(CommonUpdateFields)
257-
]),
269+
body: CommonUpdateFields,
258270
response: {
259271
200: z.object({
260272
identitySpiffeAuth: IdentitySpiffeAuthResponseSchema
@@ -281,7 +293,7 @@ export const registerIdentitySpiffeAuthRouter = async (server: FastifyZodProvide
281293
trustDomain: identitySpiffeAuth.trustDomain,
282294
allowedSpiffeIds: identitySpiffeAuth.allowedSpiffeIds,
283295
allowedAudiences: identitySpiffeAuth.allowedAudiences,
284-
configurationType: identitySpiffeAuth.configurationType,
296+
configurationType: identitySpiffeAuth.trustBundleDistribution.profile,
285297
accessTokenTTL: identitySpiffeAuth.accessTokenTTL,
286298
accessTokenMaxTTL: identitySpiffeAuth.accessTokenMaxTTL,
287299
accessTokenTrustedIps: identitySpiffeAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
@@ -416,10 +428,7 @@ export const registerIdentitySpiffeAuthRouter = async (server: FastifyZodProvide
416428
}),
417429
response: {
418430
200: z.object({
419-
identitySpiffeAuth: IdentitySpiffeAuthResponseSchema.omit({
420-
caBundleJwks: true,
421-
bundleEndpointCaCert: true
422-
})
431+
identitySpiffeAuth: IdentitySpiffeAuthResponseSchema
423432
})
424433
}
425434
},

0 commit comments

Comments
 (0)