mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
Merge remote-tracking branch 'origin/main' into misc/privilege-management-v2-transition
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas/models";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.SuperAdmin, "adminIdentityIds"))) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
t.specificType("adminIdentityIds", "text[]");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.SuperAdmin, "adminIdentityIds")) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
t.dropColumn("adminIdentityIds");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,8 @@ export const SuperAdminSchema = z.object({
|
|||||||
encryptedSlackClientId: zodBuffer.nullable().optional(),
|
encryptedSlackClientId: zodBuffer.nullable().optional(),
|
||||||
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
||||||
authConsentContent: z.string().nullable().optional(),
|
authConsentContent: z.string().nullable().optional(),
|
||||||
pageFrameContent: z.string().nullable().optional()
|
pageFrameContent: z.string().nullable().optional(),
|
||||||
|
adminIdentityIds: z.string().array().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
|||||||
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
|
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
|
||||||
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login";
|
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login";
|
||||||
|
|
||||||
const LICENSE_SERVER_CLOUD_PLAN_TTL = 30; // 30 second
|
const LICENSE_SERVER_CLOUD_PLAN_TTL = 5 * 60; // 5 mins
|
||||||
const FEATURE_CACHE_KEY = (orgId: string) => `infisical-cloud-plan-${orgId}`;
|
const FEATURE_CACHE_KEY = (orgId: string) => `infisical-cloud-plan-${orgId}`;
|
||||||
|
|
||||||
export const licenseServiceFactory = ({
|
export const licenseServiceFactory = ({
|
||||||
@@ -142,7 +142,10 @@ export const licenseServiceFactory = ({
|
|||||||
try {
|
try {
|
||||||
if (instanceType === InstanceType.Cloud) {
|
if (instanceType === InstanceType.Cloud) {
|
||||||
const cachedPlan = await keyStore.getItem(FEATURE_CACHE_KEY(orgId));
|
const cachedPlan = await keyStore.getItem(FEATURE_CACHE_KEY(orgId));
|
||||||
if (cachedPlan) return JSON.parse(cachedPlan) as TFeatureSet;
|
if (cachedPlan) {
|
||||||
|
logger.info(`getPlan: plan fetched from cache [orgId=${orgId}] [projectId=${projectId}]`);
|
||||||
|
return JSON.parse(cachedPlan) as TFeatureSet;
|
||||||
|
}
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
@@ -170,6 +173,8 @@ export const licenseServiceFactory = ({
|
|||||||
JSON.stringify(onPremFeatures)
|
JSON.stringify(onPremFeatures)
|
||||||
);
|
);
|
||||||
return onPremFeatures;
|
return onPremFeatures;
|
||||||
|
} finally {
|
||||||
|
logger.info(`getPlan: Process done for [orgId=${orgId}] [projectId=${projectId}]`);
|
||||||
}
|
}
|
||||||
return onPremFeatures;
|
return onPremFeatures;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
||||||
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
|
|
||||||
export type TAuthMode =
|
export type TAuthMode =
|
||||||
| {
|
| {
|
||||||
@@ -44,6 +45,7 @@ export type TAuthMode =
|
|||||||
identityName: string;
|
identityName: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
authMethod: null;
|
authMethod: null;
|
||||||
|
isInstanceAdmin?: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
authMode: AuthMode.SCIM_TOKEN;
|
authMode: AuthMode.SCIM_TOKEN;
|
||||||
@@ -130,13 +132,15 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
case AuthMode.IDENTITY_ACCESS_TOKEN: {
|
case AuthMode.IDENTITY_ACCESS_TOKEN: {
|
||||||
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp);
|
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp);
|
||||||
|
const serverCfg = await getServerCfg();
|
||||||
req.auth = {
|
req.auth = {
|
||||||
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
|
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
|
||||||
actor,
|
actor,
|
||||||
orgId: identity.orgId,
|
orgId: identity.orgId,
|
||||||
identityId: identity.identityId,
|
identityId: identity.identityId,
|
||||||
identityName: identity.name,
|
identityName: identity.name,
|
||||||
authMethod: null
|
authMethod: null,
|
||||||
|
isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId)
|
||||||
};
|
};
|
||||||
if (token?.identityAuth?.oidc) {
|
if (token?.identityAuth?.oidc) {
|
||||||
requestContext.set("identityAuthInfo", {
|
requestContext.set("identityAuthInfo", {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
||||||
|
|
||||||
import { ForbiddenRequestError } from "@app/lib/errors";
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
export const verifySuperAdmin = <T extends FastifyRequest>(
|
export const verifySuperAdmin = <T extends FastifyRequest>(
|
||||||
req: T,
|
req: T,
|
||||||
_res: FastifyReply,
|
_res: FastifyReply,
|
||||||
done: HookHandlerDoneFunction
|
done: HookHandlerDoneFunction
|
||||||
) => {
|
) => {
|
||||||
if (req.auth.actor !== ActorType.USER || !req.auth.user.superAdmin)
|
if (isSuperAdmin(req.auth)) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Requires elevated super admin privileges"
|
message: "Requires elevated super admin privileges"
|
||||||
});
|
});
|
||||||
done();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -637,6 +637,9 @@ export const registerRoutes = async (
|
|||||||
userDAL,
|
userDAL,
|
||||||
identityDAL,
|
identityDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
|
identityTokenAuthDAL,
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
authService: loginService,
|
authService: loginService,
|
||||||
serverCfgDAL: superAdminDAL,
|
serverCfgDAL: superAdminDAL,
|
||||||
kmsRootConfigDAL,
|
kmsRootConfigDAL,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -139,7 +139,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -171,12 +171,16 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
identities: IdentitiesSchema.pick({
|
identities: IdentitiesSchema.pick({
|
||||||
name: true,
|
name: true,
|
||||||
id: true
|
id: true
|
||||||
}).array()
|
})
|
||||||
|
.extend({
|
||||||
|
isInstanceAdmin: z.boolean()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -206,7 +210,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -240,7 +244,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -265,7 +269,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -293,7 +297,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -316,7 +320,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onRequest: (req, res, done) => {
|
onRequest: (req, res, done) => {
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
verifySuperAdmin(req, res, done);
|
verifySuperAdmin(req, res, done);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -394,4 +398,141 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/identity-management/identities/:identityId/super-admin-access",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identity: IdentitiesSchema.pick({
|
||||||
|
name: true,
|
||||||
|
id: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identity = await server.services.superAdmin.deleteIdentitySuperAdminAccess(
|
||||||
|
req.params.identityId,
|
||||||
|
req.permission.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
identity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/user-management/users/:userId/admin-access",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
userId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
id: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.superAdmin.deleteUserSuperAdminAccess(req.params.userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/bootstrap",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
email: z.string().email().trim().min(1),
|
||||||
|
password: z.string().trim().min(1),
|
||||||
|
organization: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
user: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
id: true,
|
||||||
|
superAdmin: true
|
||||||
|
}),
|
||||||
|
organization: OrganizationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
slug: true
|
||||||
|
}),
|
||||||
|
identity: IdentitiesSchema.pick({
|
||||||
|
id: true,
|
||||||
|
name: true
|
||||||
|
}).extend({
|
||||||
|
credentials: z.object({
|
||||||
|
token: z.string()
|
||||||
|
}) // would just be Token AUTH for now
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { user, organization, machineIdentity } = await server.services.superAdmin.bootstrapInstance({
|
||||||
|
...req.body,
|
||||||
|
organizationName: req.body.organization
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
|
event: PostHogEventTypes.AdminInit,
|
||||||
|
distinctId: user.user.username ?? "",
|
||||||
|
properties: {
|
||||||
|
username: user.user.username,
|
||||||
|
email: user.user.email ?? "",
|
||||||
|
lastName: user.user.lastName || "",
|
||||||
|
firstName: user.user.firstName || ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Successfully bootstrapped instance",
|
||||||
|
user: user.user,
|
||||||
|
organization,
|
||||||
|
identity: machineIdentity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
validateAccountIds,
|
validateAccountIds,
|
||||||
validatePrincipalArns
|
validatePrincipalArns
|
||||||
} from "@app/services/identity-aws-auth/identity-aws-auth-validators";
|
} from "@app/services/identity-aws-auth/identity-aws-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -130,7 +131,8 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
import { validateAzureAuthField } from "@app/services/identity-azure-auth/identity-azure-auth-validators";
|
import { validateAzureAuthField } from "@app/services/identity-azure-auth/identity-azure-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
import {} from "../sanitizedSchemas";
|
|
||||||
|
|
||||||
export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -127,7 +126,8 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
import { validateGcpAuthField } from "@app/services/identity-gcp-auth/identity-gcp-auth-validators";
|
import { validateGcpAuthField } from "@app/services/identity-gcp-auth/identity-gcp-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -121,7 +122,8 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
validateJwtAuthAudiencesField,
|
validateJwtAuthAudiencesField,
|
||||||
validateJwtBoundClaimsField
|
validateJwtBoundClaimsField
|
||||||
} from "@app/services/identity-jwt-auth/identity-jwt-auth-validators";
|
} from "@app/services/identity-jwt-auth/identity-jwt-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
const IdentityJwtAuthResponseSchema = IdentityJwtAuthsSchema.omit({
|
const IdentityJwtAuthResponseSchema = IdentityJwtAuthsSchema.omit({
|
||||||
encryptedJwksCaCert: true,
|
encryptedJwksCaCert: true,
|
||||||
@@ -169,7 +170,8 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick({
|
const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@@ -147,7 +148,8 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
validateOidcAuthAudiencesField,
|
validateOidcAuthAudiencesField,
|
||||||
validateOidcBoundClaimsField
|
validateOidcBoundClaimsField
|
||||||
} from "@app/services/identity-oidc-auth/identity-oidc-auth-validators";
|
} from "@app/services/identity-oidc-auth/identity-oidc-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({
|
const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@@ -148,7 +149,8 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||||
@@ -118,6 +119,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.identityId,
|
id: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth),
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,7 +168,8 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.identityId
|
id: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -74,7 +75,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -157,7 +159,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -257,7 +260,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -312,6 +316,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth),
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -370,6 +375,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth),
|
||||||
...req.query
|
...req.query
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -421,6 +427,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
tokenId: req.params.tokenId,
|
tokenId: req.params.tokenId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth),
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -470,7 +477,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
tokenId: req.params.tokenId
|
tokenId: req.params.tokenId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
|
export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@@ -142,8 +143,10 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
...req.body,
|
||||||
identityId: req.params.identityId
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
orgId: identityUniversalAuth.orgId,
|
orgId: identityUniversalAuth.orgId,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type";
|
|||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityAwsAuthDALFactory } from "./identity-aws-auth-dal";
|
import { TIdentityAwsAuthDALFactory } from "./identity-aws-auth-dal";
|
||||||
import { extractPrincipalArn } from "./identity-aws-auth-fns";
|
import { extractPrincipalArn } from "./identity-aws-auth-fns";
|
||||||
import {
|
import {
|
||||||
@@ -152,8 +153,11 @@ export const identityAwsAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachAwsAuthDTO) => {
|
}: TAttachAwsAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type TAttachAwsAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateAwsAuthDTO = {
|
export type TUpdateAwsAuthDTO = {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type";
|
|||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityAzureAuthDALFactory } from "./identity-azure-auth-dal";
|
import { TIdentityAzureAuthDALFactory } from "./identity-azure-auth-dal";
|
||||||
import { validateAzureIdentity } from "./identity-azure-auth-fns";
|
import { validateAzureIdentity } from "./identity-azure-auth-fns";
|
||||||
import {
|
import {
|
||||||
@@ -125,8 +126,11 @@ export const identityAzureAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachAzureAuthDTO) => {
|
}: TAttachAzureAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type TAttachAzureAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateAzureAuthDTO = {
|
export type TUpdateAzureAuthDTO = {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type";
|
|||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityGcpAuthDALFactory } from "./identity-gcp-auth-dal";
|
import { TIdentityGcpAuthDALFactory } from "./identity-gcp-auth-dal";
|
||||||
import { validateIamIdentity, validateIdTokenIdentity } from "./identity-gcp-auth-fns";
|
import { validateIamIdentity, validateIdTokenIdentity } from "./identity-gcp-auth-fns";
|
||||||
import {
|
import {
|
||||||
@@ -165,8 +166,11 @@ export const identityGcpAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachGcpAuthDTO) => {
|
}: TAttachGcpAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type TAttachGcpAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateGcpAuthDTO = {
|
export type TUpdateGcpAuthDTO = {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identit
|
|||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { KmsDataKey } from "../kms/kms-types";
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityJwtAuthDALFactory } from "./identity-jwt-auth-dal";
|
import { TIdentityJwtAuthDALFactory } from "./identity-jwt-auth-dal";
|
||||||
import { doesFieldValueMatchJwtPolicy } from "./identity-jwt-auth-fns";
|
import { doesFieldValueMatchJwtPolicy } from "./identity-jwt-auth-fns";
|
||||||
import {
|
import {
|
||||||
@@ -253,8 +254,11 @@ export const identityJwtAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachJwtAuthDTO) => {
|
}: TAttachJwtAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) {
|
if (!identityMembershipOrg) {
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type TAttachJwtAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateJwtAuthDTO = {
|
export type TUpdateJwtAuthDTO = {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identit
|
|||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { KmsDataKey } from "../kms/kms-types";
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityKubernetesAuthDALFactory } from "./identity-kubernetes-auth-dal";
|
import { TIdentityKubernetesAuthDALFactory } from "./identity-kubernetes-auth-dal";
|
||||||
import { extractK8sUsername } from "./identity-kubernetes-auth-fns";
|
import { extractK8sUsername } from "./identity-kubernetes-auth-fns";
|
||||||
import {
|
import {
|
||||||
@@ -104,7 +105,8 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${tokenReviewerJwt}`
|
Authorization: `Bearer ${tokenReviewerJwt}`
|
||||||
},
|
},
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
timeout: 10000,
|
||||||
// if ca cert, rejectUnauthorized: true
|
// if ca cert, rejectUnauthorized: true
|
||||||
httpsAgent: new https.Agent({
|
httpsAgent: new https.Agent({
|
||||||
ca: caCert,
|
ca: caCert,
|
||||||
@@ -230,8 +232,11 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachKubernetesAuthDTO) => {
|
}: TAttachKubernetesAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export type TAttachKubernetesAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateKubernetesAuthDTO = {
|
export type TUpdateKubernetesAuthDTO = {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identit
|
|||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { KmsDataKey } from "../kms/kms-types";
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal";
|
import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal";
|
||||||
import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
|
import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
|
||||||
import {
|
import {
|
||||||
@@ -225,8 +226,10 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachOidcAuthDTO) => {
|
}: TAttachOidcAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) {
|
if (!identityMembershipOrg) {
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type TAttachOidcAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateOidcAuthDTO = {
|
export type TUpdateOidcAuthDTO = {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type";
|
|||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityTokenAuthDALFactory } from "./identity-token-auth-dal";
|
import { TIdentityTokenAuthDALFactory } from "./identity-token-auth-dal";
|
||||||
import {
|
import {
|
||||||
TAttachTokenAuthDTO,
|
TAttachTokenAuthDTO,
|
||||||
@@ -62,8 +63,11 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachTokenAuthDTO) => {
|
}: TAttachTokenAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
@@ -129,8 +133,11 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TUpdateTokenAuthDTO) => {
|
}: TUpdateTokenAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
@@ -221,8 +228,11 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TRevokeTokenAuthDTO) => {
|
}: TRevokeTokenAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
@@ -285,8 +295,11 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
name
|
name,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TCreateTokenAuthTokenDTO) => {
|
}: TCreateTokenAuthTokenDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
@@ -376,8 +389,11 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TGetTokenAuthTokensDTO) => {
|
}: TGetTokenAuthTokensDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
@@ -412,7 +428,8 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TUpdateTokenAuthTokenDTO) => {
|
}: TUpdateTokenAuthTokenDTO) => {
|
||||||
const foundToken = await identityAccessTokenDAL.findOne({
|
const foundToken = await identityAccessTokenDAL.findOne({
|
||||||
[`${TableName.IdentityAccessToken}.id` as "id"]: tokenId,
|
[`${TableName.IdentityAccessToken}.id` as "id"]: tokenId,
|
||||||
@@ -424,6 +441,8 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
if (!identityMembershipOrg) {
|
if (!identityMembershipOrg) {
|
||||||
throw new NotFoundError({ message: `Failed to find identity with ID ${foundToken.identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with ID ${foundToken.identityId}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(foundToken.identityId, isActorSuperAdmin);
|
||||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "The identity does not have Token Auth"
|
message: "The identity does not have Token Auth"
|
||||||
@@ -483,18 +502,22 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TRevokeTokenAuthTokenDTO) => {
|
}: TRevokeTokenAuthTokenDTO) => {
|
||||||
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
||||||
[`${TableName.IdentityAccessToken}.id` as "id"]: tokenId,
|
[`${TableName.IdentityAccessToken}.id` as "id"]: tokenId,
|
||||||
[`${TableName.IdentityAccessToken}.isAccessTokenRevoked` as "isAccessTokenRevoked"]: false,
|
[`${TableName.IdentityAccessToken}.isAccessTokenRevoked` as "isAccessTokenRevoked"]: false,
|
||||||
[`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH
|
[`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!identityAccessToken)
|
if (!identityAccessToken)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Token with ID ${tokenId} not found or already revoked`
|
message: `Token with ID ${tokenId} not found or already revoked`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityAccessToken.identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityOrgMembership = await identityOrgMembershipDAL.findOne({
|
const identityOrgMembership = await identityOrgMembershipDAL.findOne({
|
||||||
identityId: identityAccessToken.identityId
|
identityId: identityAccessToken.identityId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export type TAttachTokenAuthDTO = {
|
|||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateTokenAuthDTO = {
|
export type TUpdateTokenAuthDTO = {
|
||||||
@@ -14,6 +15,7 @@ export type TUpdateTokenAuthDTO = {
|
|||||||
accessTokenMaxTTL?: number;
|
accessTokenMaxTTL?: number;
|
||||||
accessTokenNumUsesLimit?: number;
|
accessTokenNumUsesLimit?: number;
|
||||||
accessTokenTrustedIps?: { ipAddress: string }[];
|
accessTokenTrustedIps?: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetTokenAuthDTO = {
|
export type TGetTokenAuthDTO = {
|
||||||
@@ -22,24 +24,29 @@ export type TGetTokenAuthDTO = {
|
|||||||
|
|
||||||
export type TRevokeTokenAuthDTO = {
|
export type TRevokeTokenAuthDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateTokenAuthTokenDTO = {
|
export type TCreateTokenAuthTokenDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetTokenAuthTokensDTO = {
|
export type TGetTokenAuthTokensDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateTokenAuthTokenDTO = {
|
export type TUpdateTokenAuthTokenDTO = {
|
||||||
tokenId: string;
|
tokenId: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TRevokeTokenAuthTokenDTO = {
|
export type TRevokeTokenAuthTokenDTO = {
|
||||||
tokenId: string;
|
tokenId: string;
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type";
|
|||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityUaClientSecretDALFactory } from "./identity-ua-client-secret-dal";
|
import { TIdentityUaClientSecretDALFactory } from "./identity-ua-client-secret-dal";
|
||||||
import { TIdentityUaDALFactory } from "./identity-ua-dal";
|
import { TIdentityUaDALFactory } from "./identity-ua-dal";
|
||||||
import {
|
import {
|
||||||
@@ -153,8 +154,11 @@ export const identityUaServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TAttachUaDTO) => {
|
}: TAttachUaDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type TAttachUaDTO = {
|
|||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
clientSecretTrustedIps: { ipAddress: string }[];
|
clientSecretTrustedIps: { ipAddress: string }[];
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateUaDTO = {
|
export type TUpdateUaDTO = {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
import { TIdentityDALFactory } from "./identity-dal";
|
import { TIdentityDALFactory } from "./identity-dal";
|
||||||
import { TIdentityMetadataDALFactory } from "./identity-metadata-dal";
|
import { TIdentityMetadataDALFactory } from "./identity-metadata-dal";
|
||||||
import { TIdentityOrgDALFactory } from "./identity-org-dal";
|
import { TIdentityOrgDALFactory } from "./identity-org-dal";
|
||||||
@@ -131,8 +132,11 @@ export const identityServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
metadata
|
metadata,
|
||||||
|
isActorSuperAdmin
|
||||||
}: TUpdateIdentityDTO) => {
|
}: TUpdateIdentityDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(id, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
|
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
|
||||||
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
|
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
|
||||||
|
|
||||||
@@ -224,7 +228,16 @@ export const identityServiceFactory = ({
|
|||||||
return identity;
|
return identity;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => {
|
const deleteIdentity = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
id,
|
||||||
|
isActorSuperAdmin
|
||||||
|
}: TDeleteIdentityDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(id, isActorSuperAdmin);
|
||||||
|
|
||||||
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
|
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
|
||||||
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
|
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ export type TUpdateIdentityDTO = {
|
|||||||
role?: string;
|
role?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
metadata?: { key: string; value: string }[];
|
metadata?: { key: string; value: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
export type TDeleteIdentityDTO = {
|
export type TDeleteIdentityDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
export type TGetIdentityByIdDTO = {
|
export type TGetIdentityByIdDTO = {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
||||||
|
|
||||||
|
import { ActorType } from "../auth/auth-type";
|
||||||
|
import { getServerCfg } from "./super-admin-service";
|
||||||
|
|
||||||
|
export const isSuperAdmin = (auth: TAuthMode) => {
|
||||||
|
if (auth.actor === ActorType.USER && auth.user.superAdmin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.actor === ActorType.IDENTITY && auth.isInstanceAdmin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateIdentityUpdateForSuperAdminPrivileges = async (
|
||||||
|
identityId: string,
|
||||||
|
isActorSuperAdmin?: boolean
|
||||||
|
) => {
|
||||||
|
const serverCfg = await getServerCfg();
|
||||||
|
if (serverCfg.adminIdentityIds?.includes(identityId) && !isActorSuperAdmin) {
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message:
|
||||||
|
"You are attempting to modify an instance admin identity. This requires elevated instance admin privileges"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
|
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
|
||||||
|
|
||||||
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
||||||
import { AuthMethod } from "../auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "../auth/auth-type";
|
||||||
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { TIdentityTokenAuthDALFactory } from "../identity-token-auth/identity-token-auth-dal";
|
||||||
import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns";
|
import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns";
|
||||||
import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
|
import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
@@ -20,10 +25,19 @@ import { TUserDALFactory } from "../user/user-dal";
|
|||||||
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
|
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
|
||||||
import { UserAliasType } from "../user-alias/user-alias-types";
|
import { UserAliasType } from "../user-alias/user-alias-types";
|
||||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||||
import { LoginMethod, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
|
import {
|
||||||
|
LoginMethod,
|
||||||
|
TAdminBootstrapInstanceDTO,
|
||||||
|
TAdminGetIdentitiesDTO,
|
||||||
|
TAdminGetUsersDTO,
|
||||||
|
TAdminSignUpDTO
|
||||||
|
} from "./super-admin-types";
|
||||||
|
|
||||||
type TSuperAdminServiceFactoryDep = {
|
type TSuperAdminServiceFactoryDep = {
|
||||||
identityDAL: Pick<TIdentityDALFactory, "getIdentitiesByFilter">;
|
identityDAL: TIdentityDALFactory;
|
||||||
|
identityTokenAuthDAL: TIdentityTokenAuthDALFactory;
|
||||||
|
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
|
||||||
|
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
||||||
serverCfgDAL: TSuperAdminDALFactory;
|
serverCfgDAL: TSuperAdminDALFactory;
|
||||||
userDAL: TUserDALFactory;
|
userDAL: TUserDALFactory;
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "findOne">;
|
userAliasDAL: Pick<TUserAliasDALFactory, "findOne">;
|
||||||
@@ -60,7 +74,10 @@ export const superAdminServiceFactory = ({
|
|||||||
keyStore,
|
keyStore,
|
||||||
kmsRootConfigDAL,
|
kmsRootConfigDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
licenseService
|
licenseService,
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityTokenAuthDAL,
|
||||||
|
identityOrgMembershipDAL
|
||||||
}: TSuperAdminServiceFactoryDep) => {
|
}: TSuperAdminServiceFactoryDep) => {
|
||||||
const initServerCfg = async () => {
|
const initServerCfg = async () => {
|
||||||
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
||||||
@@ -274,6 +291,137 @@ export const superAdminServiceFactory = ({
|
|||||||
return { token, user: userInfo, organization };
|
return { token, user: userInfo, organization };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const bootstrapInstance = async ({ email, password, organizationName }: TAdminBootstrapInstanceDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
if (serverCfg?.initialized) {
|
||||||
|
throw new BadRequestError({ message: "Instance has already been set up" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingUser = await userDAL.findOne({ email });
|
||||||
|
if (existingUser) throw new BadRequestError({ name: "Instance initialization", message: "User already exists" });
|
||||||
|
|
||||||
|
const userInfo = await userDAL.transaction(async (tx) => {
|
||||||
|
const newUser = await userDAL.create(
|
||||||
|
{
|
||||||
|
firstName: "Admin",
|
||||||
|
lastName: "User",
|
||||||
|
username: email,
|
||||||
|
email,
|
||||||
|
superAdmin: true,
|
||||||
|
isGhost: false,
|
||||||
|
isAccepted: true,
|
||||||
|
authMethods: [AuthMethod.EMAIL],
|
||||||
|
isEmailVerified: true
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(password);
|
||||||
|
const encKeys = await generateUserSrpKeys(email, password);
|
||||||
|
|
||||||
|
const userEnc = await userDAL.createUserEncryption(
|
||||||
|
{
|
||||||
|
userId: newUser.id,
|
||||||
|
encryptionVersion: 2,
|
||||||
|
protectedKey: encKeys.protectedKey,
|
||||||
|
protectedKeyIV: encKeys.protectedKeyIV,
|
||||||
|
protectedKeyTag: encKeys.protectedKeyTag,
|
||||||
|
publicKey: encKeys.publicKey,
|
||||||
|
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
||||||
|
iv: encKeys.encryptedPrivateKeyIV,
|
||||||
|
tag: encKeys.encryptedPrivateKeyTag,
|
||||||
|
salt: encKeys.salt,
|
||||||
|
verifier: encKeys.verifier,
|
||||||
|
serverEncryptedPrivateKeyEncoding: encoding,
|
||||||
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
|
serverEncryptedPrivateKey: ciphertext
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return { user: newUser, enc: userEnc };
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialOrganizationName = organizationName ?? "Admin Org";
|
||||||
|
|
||||||
|
const organization = await orgService.createOrganization({
|
||||||
|
userId: userInfo.user.id,
|
||||||
|
userEmail: userInfo.user.email,
|
||||||
|
orgName: initialOrganizationName
|
||||||
|
});
|
||||||
|
|
||||||
|
const { identity, credentials } = await identityDAL.transaction(async (tx) => {
|
||||||
|
const newIdentity = await identityDAL.create({ name: "Instance Admin Identity" }, tx);
|
||||||
|
await identityOrgMembershipDAL.create(
|
||||||
|
{
|
||||||
|
identityId: newIdentity.id,
|
||||||
|
orgId: organization.id,
|
||||||
|
role: OrgMembershipRole.Admin
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokenAuth = await identityTokenAuthDAL.create(
|
||||||
|
{
|
||||||
|
identityId: newIdentity.id,
|
||||||
|
accessTokenMaxTTL: 0,
|
||||||
|
accessTokenTTL: 0,
|
||||||
|
accessTokenNumUsesLimit: 0,
|
||||||
|
accessTokenTrustedIps: JSON.stringify([
|
||||||
|
{
|
||||||
|
type: "ipv4",
|
||||||
|
prefix: 0,
|
||||||
|
ipAddress: "0.0.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "ipv6",
|
||||||
|
prefix: 0,
|
||||||
|
ipAddress: "::"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
|
{
|
||||||
|
identityId: newIdentity.id,
|
||||||
|
isAccessTokenRevoked: false,
|
||||||
|
accessTokenTTL: tokenAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: tokenAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenNumUses: 0,
|
||||||
|
accessTokenNumUsesLimit: tokenAuth.accessTokenNumUsesLimit,
|
||||||
|
name: "Instance Admin Token",
|
||||||
|
authMethod: IdentityAuthMethod.TOKEN_AUTH
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const generatedAccessToken = jwt.sign(
|
||||||
|
{
|
||||||
|
identityId: newIdentity.id,
|
||||||
|
identityAccessTokenId: newToken.id,
|
||||||
|
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||||
|
} as TIdentityAccessTokenJwtPayload,
|
||||||
|
appCfg.AUTH_SECRET
|
||||||
|
);
|
||||||
|
|
||||||
|
return { identity: newIdentity, auth: tokenAuth, credentials: { token: generatedAccessToken } };
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateServerCfg({ initialized: true, adminIdentityIds: [identity.id] }, userInfo.user.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: userInfo,
|
||||||
|
organization,
|
||||||
|
machineIdentity: {
|
||||||
|
...identity,
|
||||||
|
credentials
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const getUsers = ({ offset, limit, searchTerm, adminsOnly }: TAdminGetUsersDTO) => {
|
const getUsers = ({ offset, limit, searchTerm, adminsOnly }: TAdminGetUsersDTO) => {
|
||||||
return userDAL.getUsersByFilter({
|
return userDAL.getUsersByFilter({
|
||||||
limit,
|
limit,
|
||||||
@@ -289,13 +437,46 @@ export const superAdminServiceFactory = ({
|
|||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIdentities = ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => {
|
const deleteIdentitySuperAdminAccess = async (identityId: string, actorId: string) => {
|
||||||
return identityDAL.getIdentitiesByFilter({
|
const identity = await identityDAL.findById(identityId);
|
||||||
|
if (!identity) {
|
||||||
|
throw new NotFoundError({ name: "Identity", message: "Identity not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentAdminIdentityIds = (await getServerCfg()).adminIdentityIds ?? [];
|
||||||
|
if (!currentAdminIdentityIds?.includes(identityId)) {
|
||||||
|
throw new BadRequestError({ name: "Identity", message: "Identity does not have super admin access" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateServerCfg({ adminIdentityIds: currentAdminIdentityIds.filter((id) => id !== identityId) }, actorId);
|
||||||
|
|
||||||
|
return identity;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUserSuperAdminAccess = async (userId: string) => {
|
||||||
|
const user = await userDAL.findById(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError({ name: "User", message: "User not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = userDAL.updateById(userId, { superAdmin: false });
|
||||||
|
|
||||||
|
return updatedUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => {
|
||||||
|
const identities = await identityDAL.getIdentitiesByFilter({
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
sortBy: "name"
|
sortBy: "name"
|
||||||
});
|
});
|
||||||
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
|
return identities.map((identity) => ({
|
||||||
|
...identity,
|
||||||
|
isInstanceAdmin: Boolean(serverCfg?.adminIdentityIds?.includes(identity.id))
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const grantServerAdminAccessToUser = async (userId: string) => {
|
const grantServerAdminAccessToUser = async (userId: string) => {
|
||||||
@@ -393,12 +574,15 @@ export const superAdminServiceFactory = ({
|
|||||||
initServerCfg,
|
initServerCfg,
|
||||||
updateServerCfg,
|
updateServerCfg,
|
||||||
adminSignUp,
|
adminSignUp,
|
||||||
|
bootstrapInstance,
|
||||||
getUsers,
|
getUsers,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
getIdentities,
|
getIdentities,
|
||||||
getAdminSlackConfig,
|
getAdminSlackConfig,
|
||||||
updateRootEncryptionStrategy,
|
updateRootEncryptionStrategy,
|
||||||
getConfiguredEncryptionStrategies,
|
getConfiguredEncryptionStrategies,
|
||||||
grantServerAdminAccessToUser
|
grantServerAdminAccessToUser,
|
||||||
|
deleteIdentitySuperAdminAccess,
|
||||||
|
deleteUserSuperAdminAccess
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export type TAdminSignUpDTO = {
|
|||||||
userAgent: string;
|
userAgent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAdminBootstrapInstanceDTO = {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
organizationName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TAdminGetUsersDTO = {
|
export type TAdminGetUsersDTO = {
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
|||||||
@@ -600,3 +600,23 @@ func CallGatewayHeartBeatV1(httpClient *resty.Client) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRequest) (map[string]interface{}, error) {
|
||||||
|
var resBody map[string]interface{}
|
||||||
|
response, err := httpClient.
|
||||||
|
R().
|
||||||
|
SetResult(&resBody).
|
||||||
|
SetHeader("User-Agent", USER_AGENT).
|
||||||
|
SetBody(request).
|
||||||
|
Post(fmt.Sprintf("%v/v1/admin/bootstrap", request.Domain))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CallBootstrapInstance: Unable to complete api request [err=%w]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.IsError() {
|
||||||
|
return nil, fmt.Errorf("CallBootstrapInstance: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBody, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -648,3 +648,10 @@ type ExchangeRelayCertResponseV1 struct {
|
|||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
CertificateChain string `json:"certificateChain"`
|
CertificateChain string `json:"certificateChain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BootstrapInstanceRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2023 Infisical Inc.
|
||||||
|
*/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Infisical/infisical-merge/packages/api"
|
||||||
|
"github.com/Infisical/infisical-merge/packages/util"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bootstrapCmd = &cobra.Command{
|
||||||
|
Use: "bootstrap",
|
||||||
|
Short: "Used to bootstrap your Infisical instance",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Example: "infisical bootstrap",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
email, _ := cmd.Flags().GetString("email")
|
||||||
|
if email == "" {
|
||||||
|
if envEmail, ok := os.LookupEnv("INFISICAL_ADMIN_EMAIL"); ok {
|
||||||
|
email = envEmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if email == "" {
|
||||||
|
log.Error().Msg("email is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password, _ := cmd.Flags().GetString("password")
|
||||||
|
if password == "" {
|
||||||
|
if envPassword, ok := os.LookupEnv("INFISICAL_ADMIN_PASSWORD"); ok {
|
||||||
|
password = envPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if password == "" {
|
||||||
|
log.Error().Msg("password is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
organization, _ := cmd.Flags().GetString("organization")
|
||||||
|
if organization == "" {
|
||||||
|
if envOrganization, ok := os.LookupEnv("INFISICAL_ADMIN_ORGANIZATION"); ok {
|
||||||
|
organization = envOrganization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization == "" {
|
||||||
|
log.Error().Msg("organization is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domain, _ := cmd.Flags().GetString("domain")
|
||||||
|
if domain == "" {
|
||||||
|
if envDomain, ok := os.LookupEnv("INFISICAL_API_URL"); ok {
|
||||||
|
domain = envDomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain == "" {
|
||||||
|
log.Error().Msg("domain is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := resty.New().
|
||||||
|
SetHeader("Accept", "application/json")
|
||||||
|
|
||||||
|
bootstrapResponse, err := api.CallBootstrapInstance(httpClient, api.BootstrapInstanceRequest{
|
||||||
|
Domain: util.AppendAPIEndpoint(domain),
|
||||||
|
Email: email,
|
||||||
|
Password: password,
|
||||||
|
Organization: organization,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf("Failed to bootstrap instance: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseJSON, err := json.MarshalIndent(bootstrapResponse, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Msgf("Failed to convert response to JSON: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(responseJSON))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bootstrapCmd.Flags().String("domain", "", "The domain of your self-hosted Infisical instance")
|
||||||
|
bootstrapCmd.Flags().String("email", "", "The desired email address of the instance admin")
|
||||||
|
bootstrapCmd.Flags().String("password", "", "The desired password of the instance admin")
|
||||||
|
bootstrapCmd.Flags().String("organization", "", "The name of the organization to create for the instance")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(bootstrapCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
title: "infisical bootstrap"
|
||||||
|
description: "Automate the initial setup of a new Infisical instance for headless deployment and infrastructure-as-code workflows"
|
||||||
|
---
|
||||||
|
|
||||||
|
```bash
|
||||||
|
infisical bootstrap --domain=<domain> --email=<email> --password=<password> --organization=<organization>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
The `infisical bootstrap` command is used when deploying Infisical in automated environments where manual UI setup is not feasible. It's ideal for:
|
||||||
|
|
||||||
|
- Containerized deployments in Kubernetes or Docker environments
|
||||||
|
- Infrastructure-as-code pipelines with Terraform or similar tools
|
||||||
|
- Continuous deployment workflows
|
||||||
|
- DevOps automation scenarios
|
||||||
|
|
||||||
|
The command initializes a fresh Infisical instance by creating an admin user, organization, and instance admin machine identity, enabling subsequent programmatic configuration without human intervention.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
This command creates an instance admin machine identity with the highest level
|
||||||
|
of privileges. The returned token should be treated with the utmost security,
|
||||||
|
similar to a root credential. Unauthorized access to this token could
|
||||||
|
compromise your entire Infisical instance.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
<Accordion title="--domain" defaultOpen="true">
|
||||||
|
The URL of your Infisical instance. This can be set using the `INFISICAL_API_URL` environment variable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical bootstrap --domain=https://your-infisical-instance.com
|
||||||
|
```
|
||||||
|
|
||||||
|
This flag is required.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--email">
|
||||||
|
Email address for the admin user account that will be created. This can be set using the `INFISICAL_ADMIN_EMAIL` environment variable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical bootstrap [email protected]
|
||||||
|
```
|
||||||
|
|
||||||
|
This flag is required.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--password">
|
||||||
|
Password for the admin user account. This can be set using the `INFISICAL_ADMIN_PASSWORD` environment variable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical bootstrap --password=your-secure-password
|
||||||
|
```
|
||||||
|
|
||||||
|
This flag is required.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--organization">
|
||||||
|
Name of the organization that will be created within the instance. This can be set using the `INFISICAL_ADMIN_ORGANIZATION` environment variable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example
|
||||||
|
infisical bootstrap --organization=your-org-name
|
||||||
|
```
|
||||||
|
|
||||||
|
This flag is required.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
## Response
|
||||||
|
|
||||||
|
The command returns a JSON response with details about the created user, organization, and machine identity:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"identity": {
|
||||||
|
"credentials": {
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eUlkIjoiZGIyMjQ3OTItZWQxOC00Mjc3LTlkYWUtNTdlNzUyMzE1ODU0IiwiaWRlbnRpdHlBY2Nlc3NUb2tlbklkIjoiZmVkZmZmMGEtYmU3Yy00NjViLWEwZWEtZjM5OTNjMTg4OGRlIiwiYXV0aFRva2VuVHlwZSI6ImlkZW50aXR5QWNjZXNzVG9rZW4iLCJpYXQiOjE3NDIzMjI0ODl9.mqcZZqIFqER1e9ubrQXp8FbzGYi8nqqZwfMvz09g-8Y"
|
||||||
|
},
|
||||||
|
"id": "db224792-ed18-4277-9dae-57e752315854",
|
||||||
|
"name": "Instance Admin Identity"
|
||||||
|
},
|
||||||
|
"message": "Successfully bootstrapped instance",
|
||||||
|
"organization": {
|
||||||
|
"id": "b56bece0-42f5-4262-b25e-be7bf5f84957",
|
||||||
|
"name": "dog",
|
||||||
|
"slug": "dog-v-e5l"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "[email protected]",
|
||||||
|
"firstName": "Admin",
|
||||||
|
"id": "a418f355-c8da-453c-bbc8-6c07208eeb3c",
|
||||||
|
"lastName": "User",
|
||||||
|
"superAdmin": true,
|
||||||
|
"username": "[email protected]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage with Automation
|
||||||
|
|
||||||
|
For automation purposes, you can extract just the machine identity token from the response:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
infisical bootstrap --domain=https://your-infisical-instance.com [email protected] --password=your-secure-password --organization=your-org-name | jq ".identity.credentials.token"
|
||||||
|
```
|
||||||
|
|
||||||
|
This extracts only the token, which can be captured in a variable or piped to other commands.
|
||||||
|
|
||||||
|
## Example: Capture Token in a Variable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TOKEN=$(infisical bootstrap --domain=https://your-infisical-instance.com [email protected] --password=your-secure-password --organization=your-org-name | jq -r ".identity.credentials.token")
|
||||||
|
|
||||||
|
# Now use the token for further automation
|
||||||
|
echo "Token has been captured and can be used for authentication"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The bootstrap process can only be performed once on a fresh Infisical instance
|
||||||
|
- All flags are required for the bootstrap process to complete successfully
|
||||||
|
- Security controls prevent privilege escalation: instance admin identities cannot be managed by non-instance admin users and identities
|
||||||
|
- The generated admin user account can be used to log in via the UI if needed
|
||||||
@@ -114,6 +114,13 @@ using the Universal Auth authentication method.
|
|||||||
that is to exchange the **Client ID** and **Client Secret** of the identity for an access token
|
that is to exchange the **Client ID** and **Client Secret** of the identity for an access token
|
||||||
by making a request to the `/api/v1/auth/universal-auth/login` endpoint.
|
by making a request to the `/api/v1/auth/universal-auth/login` endpoint.
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
Choose the correct base URL based on your region:
|
||||||
|
|
||||||
|
- For Infisical Cloud US users: `https://app.infisical.com`
|
||||||
|
- For Infisical Cloud EU users: `https://eu.infisical.com`
|
||||||
|
</Tip>
|
||||||
|
|
||||||
#### Sample request
|
#### Sample request
|
||||||
|
|
||||||
```bash Request
|
```bash Request
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ For organizations that work with US government agencies, FIPS compliance is almo
|
|||||||
<Step title="Configure HSM on Infisical">
|
<Step title="Configure HSM on Infisical">
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
Are you using Docker? If you are using Docker, please follow the instructions in the [Using HSM's with Docker](#using-hsms-with-docker) section.
|
Are you using Docker or Kubernetes for your deployment? If you are using Docker or Kubernetes, please follow the instructions in the [Using HSM's in your Deployment](#using-hsms-in-your-deployment) section.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
Configuring the HSM on Infisical requires setting a set of environment variables:
|
Configuring the HSM on Infisical requires setting a set of environment variables:
|
||||||
@@ -94,8 +94,12 @@ For organizations that work with US government agencies, FIPS compliance is almo
|
|||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
|
||||||
## Using HSMs with Docker
|
## Using HSMs In Your Deployment
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Docker">
|
||||||
When using Docker, you need to mount the path containing the HSM client files. This section covers how to configure your Infisical instance to use an HSM with Docker.
|
When using Docker, you need to mount the path containing the HSM client files. This section covers how to configure your Infisical instance to use an HSM with Docker.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="Thales Luna Cloud HSM">
|
<Tab title="Thales Luna Cloud HSM">
|
||||||
<Steps>
|
<Steps>
|
||||||
@@ -252,6 +256,284 @@ When using Docker, you need to mount the path containing the HSM client files. T
|
|||||||
After following these steps, your Docker setup will be ready to use HSM encryption.
|
After following these steps, your Docker setup will be ready to use HSM encryption.
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Kubernetes">
|
||||||
|
When you are deploying Infisical with the [Kubernetes self-hosting option](/self-hosting/deployment-options/kubernetes-helm), you can still use HSM encryption, but you need to ensure that the HSM client files are present in the container.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Thales Luna Cloud HSM">
|
||||||
|
<Note>
|
||||||
|
This is only supported on helm chart version `1.4.1` and above. Please see the [Helm Chart Changelog](https://github.com/Infisical/infisical/blob/main/helm-charts/infisical-standalone-postgres/CHANGELOG.md#141-march-19-2025) for more information.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create HSM client folder">
|
||||||
|
When using Kubernetes, you need to mount the path containing the HSM client files. This section covers how to configure your Infisical instance to use an HSM with Kubernetes.
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir /etc/hsm-client
|
||||||
|
```
|
||||||
|
|
||||||
|
After [setting up your Luna Cloud HSM client](https://thalesdocs.com/gphsm/luna/7/docs/network/Content/install/client_install/add_dpod.htm), you should have a set of files, referred to as the HSM client. You don't need all the files, but for simplicity we recommend copying all the files from the client.
|
||||||
|
|
||||||
|
A folder structure of a client folder will often look like this:
|
||||||
|
```
|
||||||
|
partition-ca-certificate.pem
|
||||||
|
partition-certificate.pem
|
||||||
|
server-certificate.pem
|
||||||
|
Chrystoki.conf
|
||||||
|
/plugins
|
||||||
|
libcloud.plugin
|
||||||
|
/lock
|
||||||
|
/libs
|
||||||
|
/64
|
||||||
|
libCryptoki2.so
|
||||||
|
/jsp
|
||||||
|
LunaProvider.jar
|
||||||
|
/64
|
||||||
|
libLunaAPI.so
|
||||||
|
/etc
|
||||||
|
openssl.cnf
|
||||||
|
/bin
|
||||||
|
/64
|
||||||
|
ckdemo
|
||||||
|
lunacm
|
||||||
|
multitoken
|
||||||
|
vtl
|
||||||
|
```
|
||||||
|
|
||||||
|
The most important parts of the client folder is the `Chrystoki.conf` file, and the `libs`, `plugins`, and `jsp` folders. You need to copy these files to the folder you created in the first step.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r /<path-to-where-your-hsm-client-is-located> /etc/hsm-client
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step title="Update Chrystoki.conf">
|
||||||
|
The `Chrystoki.conf` file is used to configure the HSM client. You need to update the `Chrystoki.conf` file to point to the correct file paths.
|
||||||
|
|
||||||
|
In this example, we will be mounting the `/etc/hsm-client` folder from the host to containers in our deployment's pods at the path `/hsm-client`. This means the contents of `/etc/hsm-client` on the host will be accessible at `/hsm-client` within the containers.
|
||||||
|
|
||||||
|
An example config file will look like this:
|
||||||
|
|
||||||
|
```Chrystoki.conf
|
||||||
|
Chrystoki2 = {
|
||||||
|
# This path points to the mounted path, /hsm-client
|
||||||
|
LibUNIX64 = /hsm-client/libs/64/libCryptoki2.so;
|
||||||
|
}
|
||||||
|
|
||||||
|
Luna = {
|
||||||
|
DefaultTimeOut = 500000;
|
||||||
|
PEDTimeout1 = 100000;
|
||||||
|
PEDTimeout2 = 200000;
|
||||||
|
PEDTimeout3 = 20000;
|
||||||
|
KeypairGenTimeOut = 2700000;
|
||||||
|
CloningCommandTimeOut = 300000;
|
||||||
|
CommandTimeOutPedSet = 720000;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardReader = {
|
||||||
|
LunaG5Slots = 0;
|
||||||
|
RemoteCommand = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Misc = {
|
||||||
|
# Update the paths to point to the mounted path if your folder structure is different from the one mentioned in the previous step.
|
||||||
|
PluginModuleDir = /hsm-client/plugins;
|
||||||
|
MutexFolder = /hsm-client/lock;
|
||||||
|
PE1746Enabled = 1;
|
||||||
|
ToolsDir = /usr/bin;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Presentation = {
|
||||||
|
ShowEmptySlots = no;
|
||||||
|
}
|
||||||
|
|
||||||
|
LunaSA Client = {
|
||||||
|
ReceiveTimeout = 20000;
|
||||||
|
# Update the paths to point to the mounted path if your folder structure is different from the one mentioned in the previous step.
|
||||||
|
SSLConfigFile = /hsm-client/etc/openssl.cnf;
|
||||||
|
ClientPrivKeyFile = ./etc/ClientNameKey.pem;
|
||||||
|
ClientCertFile = ./etc/ClientNameCert.pem;
|
||||||
|
ServerCAFile = ./etc/CAFile.pem;
|
||||||
|
NetClient = 1;
|
||||||
|
TCPKeepAlive = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
REST = {
|
||||||
|
AppLogLevel = error
|
||||||
|
ServerName = <REDACTED>;
|
||||||
|
ServerPort = 443;
|
||||||
|
AuthTokenConfigURI = <REDACTED>;
|
||||||
|
AuthTokenClientId = <REDACTED>;
|
||||||
|
AuthTokenClientSecret = <REDACTED>;
|
||||||
|
RestClient = 1;
|
||||||
|
ClientTimeoutSec = 120;
|
||||||
|
ClientPoolSize = 32;
|
||||||
|
ClientEofRetryCount = 15;
|
||||||
|
ClientConnectRetryCount = 900;
|
||||||
|
ClientConnectIntervalMs = 1000;
|
||||||
|
}
|
||||||
|
XTC = {
|
||||||
|
Enabled = 1;
|
||||||
|
TimeoutSec = 600;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save the file after updating the paths.
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Creating Persistent Volume Claim (PVC)">
|
||||||
|
You need to create a Persistent Volume Claim (PVC) to mount the HSM client files to the Infisical deployment.
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f - <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: infisical-data-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 500Mi
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
The above command will create a PVC named `infisical-data-pvc` with a storage size of `500Mi`. You can change the storage size if needed.
|
||||||
|
|
||||||
|
|
||||||
|
Next we need to create a temporary pod with the PVC mounted as a volume, allowing us to copy the HSM client files into this mounted storage.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f - <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: hsm-setup-pod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: setup
|
||||||
|
image: busybox
|
||||||
|
command: ["/bin/sh", "-c", "sleep 3600"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: hsm-data
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: hsm-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: infisical-data-pvc
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
The above command will create a pod named `hsm-setup-pod` with a busybox image. The pod will sleep for 3600 seconds _(one hour)_, which is enough time to upload the HSM client files to the PVC.
|
||||||
|
|
||||||
|
Ensure that the pod is running and is healthy by running the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl wait --for=condition=Ready pod/hsm-setup-pod --timeout=60s
|
||||||
|
```
|
||||||
|
|
||||||
|
Next we need to copy the HSM client files into the PVC.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl exec hsm-setup-pod -- mkdir -p /data/ # Create the data directory
|
||||||
|
kubectl cp ./hsm-client/ hsm-setup-pod:/data/ # Copy the HSM client files into the PVC
|
||||||
|
kubectl exec hsm-setup-pod -- chmod -R 755 /data/ # Set the correct permissions for the HSM client files
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, we are ready to delete the temporary pod, as we have successfully uploaded the HSM client files to the PVC. This step may take a few minutes to complete.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl delete pod hsm-setup-pod
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Updating your environment variables">
|
||||||
|
Next we need to update the environment variables used for the deployment. If you followed the [setup instructions for Kubernetes deployments](/self-hosting/deployment-options/kubernetes-helm), you should have a Kubernetes secret called `infisical-secrets`.
|
||||||
|
We need to update the secret with the following environment variables:
|
||||||
|
|
||||||
|
- `HSM_LIB_PATH` - The path to the HSM client library _(mapped to `/hsm-client/libs/64/libCryptoki2.so`)_
|
||||||
|
- `HSM_PIN` - The PIN for the HSM device that you created when setting up your Luna Cloud HSM client
|
||||||
|
- `HSM_SLOT` - The slot number for the HSM device that you selected when setting up your Luna Cloud HSM client
|
||||||
|
- `HSM_KEY_LABEL` - The label for the HSM key. If no key is found with the provided key label, the HSM will create a new key with the provided label.
|
||||||
|
|
||||||
|
The following is an example of the secret that you should update:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: infisical-secrets
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# ... Other environment variables ...
|
||||||
|
HSM_LIB_PATH: "/hsm-client/libs/64/libCryptoki2.so" # If you followed this guide, this will be the path of the Luna Cloud HSM client
|
||||||
|
HSM_PIN: "<your-hsm-device-pin>"
|
||||||
|
HSM_SLOT: "<hsm-device-slot>"
|
||||||
|
HSM_KEY_LABEL: "<your-key-label>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Save the file after updating the environment variables, and apply the secret changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f ./secret-file-name.yaml
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Updating the Deployment">
|
||||||
|
After we've successfully configured the PVC and updated our environment variables, we are ready to update the deployment configuration so that the pods it creates can access the HSM client files.
|
||||||
|
|
||||||
|
We need to update the Docker image of the deployment to use `infisical/infisical-fips`. The `infisical/infisical-fips` image is a functionally identical image to the `infisical/infisical` image, but it is built with support for HSM encryption.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ... The rest of the values.yaml file ...
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: infisical/infisical-fips # Very important: Must use "infisical/infisical-fips"
|
||||||
|
tag: "v0.117.1-postgres"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
extraVolumeMounts:
|
||||||
|
- name: hsm-data
|
||||||
|
mountPath: /hsm-client # The path we will mount the HSM client files to
|
||||||
|
subPath: ./hsm-client
|
||||||
|
|
||||||
|
extraVolumes:
|
||||||
|
- name: hsm-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: infisical-data-pvc # The PVC we created in the previous step
|
||||||
|
|
||||||
|
# ... The rest of the values.yaml file ...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Upgrading the Helm Chart">
|
||||||
|
After updating the values.yaml file, you need to upgrade the Helm chart in order for the changes to take effect.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade --install infisical infisical-helm-charts/infisical-standalone --values /path/to/values.yaml
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step title="Restarting the Deployment">
|
||||||
|
After upgrading the Helm chart, you need to restart the deployment in order for the changes to take effect.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl rollout restart deployment/infisical-infisical
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
After following these steps, your Kubernetes setup will be ready to use HSM encryption.
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
|
||||||
## Disabling HSM Encryption
|
## Disabling HSM Encryption
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 726 KiB |
+3
-1
@@ -318,7 +318,8 @@
|
|||||||
"group": "Guides",
|
"group": "Guides",
|
||||||
"pages": [
|
"pages": [
|
||||||
"self-hosting/guides/mongo-to-postgres",
|
"self-hosting/guides/mongo-to-postgres",
|
||||||
"self-hosting/guides/custom-certificates"
|
"self-hosting/guides/custom-certificates",
|
||||||
|
"self-hosting/guides/automated-bootstrapping"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -348,6 +349,7 @@
|
|||||||
"cli/commands/dynamic-secrets",
|
"cli/commands/dynamic-secrets",
|
||||||
"cli/commands/ssh",
|
"cli/commands/ssh",
|
||||||
"cli/commands/gateway",
|
"cli/commands/gateway",
|
||||||
|
"cli/commands/bootstrap",
|
||||||
"cli/commands/export",
|
"cli/commands/export",
|
||||||
"cli/commands/token",
|
"cli/commands/token",
|
||||||
"cli/commands/service-token",
|
"cli/commands/service-token",
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
title: "Programmatic Provisioning"
|
||||||
|
description: "Learn how to provision and configure Infisical instances programmatically without UI interaction"
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical's Automated Bootstrapping feature enables you to provision and configure an Infisical instance without using the UI, allowing for complete automation through static configuration files, API calls, or CLI commands. This is especially valuable for enterprise environments where automated deployment and infrastructure-as-code practices are essential.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Automated Bootstrapping workflow automates the following processes:
|
||||||
|
- Creating an admin user account
|
||||||
|
- Initializing an organization for the entire instance
|
||||||
|
- Establishing an **instance admin machine identity** with full administrative permissions
|
||||||
|
- Returning the machine identity credentials for further automation
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Instance Initialization**: Infisical requires [configuration variables](/self-hosting/configuration/envars) to be set during launch, after which the bootstrap process can be triggered.
|
||||||
|
- **Instance Admin Machine Identity**: The bootstrapping process creates a machine identity with instance-level admin privileges, which can be used to programmatically manage all aspects of the Infisical instance.
|
||||||
|

|
||||||
|
- **Token Auth**: The instance admin machine identity uses [Token Auth](/documentation/platform/identities/token-auth), providing a JWT token that can be used directly to make authenticated requests to the Infisical API.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- An Infisical instance launched with all required configuration variables
|
||||||
|
- Access to the Infisical CLI or the ability to make API calls to the instance
|
||||||
|
- Network connectivity to the Infisical instance
|
||||||
|
|
||||||
|
## Bootstrap Methods
|
||||||
|
|
||||||
|
You can bootstrap an Infisical instance using either the API or the CLI.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Using the API">
|
||||||
|
Make a POST request to the bootstrap endpoint:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST: http://your-infisical-instance.com/api/v1/admin/bootstrap
|
||||||
|
{
|
||||||
|
"email": "[email protected]",
|
||||||
|
"password": "your-secure-password",
|
||||||
|
"organization": "your-org-name"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example using curl:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"[email protected]","password":"your-secure-password","organization":"your-org-name"}' \
|
||||||
|
http://your-infisical-instance.com/api/v1/admin/bootstrap
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Using the CLI">
|
||||||
|
Use the [Infisical CLI](/cli/commands/bootstrap) to bootstrap the instance and extract the token for immediate use in automation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
infisical bootstrap --domain="http://localhost:8080" --email="[email protected]" --password="your-secure-password" --organization="your-org-name" | jq ".identity.credentials.token"
|
||||||
|
```
|
||||||
|
|
||||||
|
This example command pipes the output through `jq` to extract only the machine identity token, making it easy to capture and use directly in automation scripts or export as an environment variable for tools like Terraform.
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## API Response Structure
|
||||||
|
|
||||||
|
The bootstrap process returns a JSON response with details about the created user, organization, and machine identity:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"identity": {
|
||||||
|
"credentials": {
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eUlkIjoiZGIyMjQ3OTItZWQxOC00Mjc3LTlkYWUtNTdlNzUyMzE1ODU0IiwiaWRlbnRpdHlBY2Nlc3NUb2tlbklkIjoiZmVkZmZmMGEtYmU3Yy00NjViLWEwZWEtZjM5OTNjMTg4OGRlIiwiYXV0aFRva2VuVHlwZSI6ImlkZW50aXR5QWNjZXNzVG9rZW4iLCJpYXQiOjE3NDIzMjI0ODl9.mqcZZqIFqER1e9ubrQXp8FbzGYi8nqqZwfMvz09g-8Y"
|
||||||
|
},
|
||||||
|
"id": "db224792-ed18-4277-9dae-57e752315854",
|
||||||
|
"name": "Instance Admin Identity"
|
||||||
|
},
|
||||||
|
"message": "Successfully bootstrapped instance",
|
||||||
|
"organization": {
|
||||||
|
"id": "b56bece0-42f5-4262-b25e-be7bf5f84957",
|
||||||
|
"name": "dog",
|
||||||
|
"slug": "dog-v-e5l"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "[email protected]",
|
||||||
|
"firstName": "Admin",
|
||||||
|
"id": "a418f355-c8da-453c-bbc8-6c07208eeb3c",
|
||||||
|
"lastName": "User",
|
||||||
|
"superAdmin": true,
|
||||||
|
"username": "[email protected]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the Instance Admin Machine Identity Token
|
||||||
|
|
||||||
|
The bootstrap process automatically creates a machine identity with Token Auth configured. The returned token has instance-level admin privileges (the highest level of access) and should be treated with the same security considerations as a root credential.
|
||||||
|
|
||||||
|
The token enables full programmatic control of your Infisical instance and can be used in the following ways:
|
||||||
|
|
||||||
|
### 1. Infrastructure Automation
|
||||||
|
|
||||||
|
Store the token securely for use with infrastructure automation tools. Due to the sensitive nature of this token, ensure it's protected using appropriate secret management practices:
|
||||||
|
|
||||||
|
#### Kubernetes Secret (with appropriate RBAC restrictions)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: infisical-admin-credentials
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
token: <base64-encoded-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment Variable for Terraform
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export INFISICAL_TOKEN=your-access-token
|
||||||
|
terraform apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Programmatic Resource Management
|
||||||
|
|
||||||
|
Use the token to authenticate API calls for creating and managing Infisical resources. The token works exactly like any other Token Auth access token in the Infisical API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: Bearer ${INFISICAL_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"projectName": "New Project",
|
||||||
|
"projectDescription": "A project created via API",
|
||||||
|
"slug": "new-project-slug",
|
||||||
|
"template": "default",
|
||||||
|
"type": "SECRET_MANAGER"
|
||||||
|
}' \
|
||||||
|
https://your-infisical-instance.com/api/v2/projects
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **Security Warning**: The instance admin machine identity has the highest level of privileges in your Infisical deployment. The token should be treated with the utmost security and handled like a root credential. Unauthorized access to this token could compromise your entire Infisical instance.
|
||||||
|
- Security controls prevent privilege escalation: instance admin identities cannot be managed by non-instance admin users and identities
|
||||||
|
- The instance admin permission of the generated identity can be revoked later in the server admin panel if needed
|
||||||
|
- The generated admin user account can still be used for UI access if needed, or can be removed if you prefer to manage everything through the machine identity
|
||||||
|
- This process is designed to work with future Crossplane providers and the existing Terraform provider for full infrastructure-as-code capabilities
|
||||||
|
- All necessary configuration variables should be set during the initial launch of the Infisical instance
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
export {
|
export {
|
||||||
useAdminDeleteUser,
|
useAdminDeleteUser,
|
||||||
useAdminGrantServerAdminAccess,
|
useAdminGrantServerAdminAccess,
|
||||||
|
useAdminRemoveIdentitySuperAdminAccess,
|
||||||
useCreateAdminUser,
|
useCreateAdminUser,
|
||||||
|
useRemoveUserServerAdminAccess,
|
||||||
useUpdateAdminSlackConfig,
|
useUpdateAdminSlackConfig,
|
||||||
useUpdateServerConfig,
|
useUpdateServerConfig,
|
||||||
useUpdateServerEncryptionStrategy
|
useUpdateServerEncryptionStrategy
|
||||||
|
|||||||
@@ -70,6 +70,40 @@ export const useAdminDeleteUser = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAdminRemoveIdentitySuperAdminAccess = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (identityId: string) => {
|
||||||
|
await apiRequest.delete(
|
||||||
|
`/api/v1/admin/identity-management/identities/${identityId}/super-admin-access`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [adminStandaloneKeys.getIdentities]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRemoveUserServerAdminAccess = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (userId: string) => {
|
||||||
|
await apiRequest.delete(`/api/v1/admin/user-management/users/${userId}/admin-access`);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [adminStandaloneKeys.getUsers]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useAdminGrantServerAdminAccess = () => {
|
export const useAdminGrantServerAdminAccess = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type Identity = {
|
|||||||
authMethods: IdentityAuthMethod[];
|
authMethods: IdentityAuthMethod[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
isInstanceAdmin?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IdentityAccessToken = {
|
export type IdentityAccessToken = {
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ const formSchema = z.object({
|
|||||||
trustLdapEmails: z.boolean(),
|
trustLdapEmails: z.boolean(),
|
||||||
trustOidcEmails: z.boolean(),
|
trustOidcEmails: z.boolean(),
|
||||||
defaultAuthOrgId: z.string(),
|
defaultAuthOrgId: z.string(),
|
||||||
authConsentContent: z.string().optional(),
|
authConsentContent: z.string().optional().default(""),
|
||||||
pageFrameContent: z.string().optional()
|
pageFrameContent: z.string().optional().default("")
|
||||||
});
|
});
|
||||||
|
|
||||||
type TDashboardForm = z.infer<typeof formSchema>;
|
type TDashboardForm = z.infer<typeof formSchema>;
|
||||||
@@ -86,8 +86,8 @@ export const OverviewPage = () => {
|
|||||||
trustLdapEmails: config.trustLdapEmails,
|
trustLdapEmails: config.trustLdapEmails,
|
||||||
trustOidcEmails: config.trustOidcEmails,
|
trustOidcEmails: config.trustOidcEmails,
|
||||||
defaultAuthOrgId: config.defaultAuthOrgId ?? "",
|
defaultAuthOrgId: config.defaultAuthOrgId ?? "",
|
||||||
authConsentContent: config.authConsentContent,
|
authConsentContent: config.authConsentContent ?? "",
|
||||||
pageFrameContent: config.pageFrameContent
|
pageFrameContent: config.pageFrameContent ?? ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,8 +165,8 @@ export const OverviewPage = () => {
|
|||||||
<Tab value={TabSections.Auth}>Authentication</Tab>
|
<Tab value={TabSections.Auth}>Authentication</Tab>
|
||||||
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
||||||
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
||||||
<Tab value={TabSections.Users}>Users</Tab>
|
<Tab value={TabSections.Users}>User Identities</Tab>
|
||||||
<Tab value={TabSections.Identities}>Identities</Tab>
|
<Tab value={TabSections.Identities}>Machine Identities</Tab>
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={TabSections.Settings}>
|
<TabPanel value={TabSections.Settings}>
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { faMagnifyingGlass, faServer } from "@fortawesome/free-solid-svg-icons";
|
import { faEllipsis, faMagnifyingGlass, faServer } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
Input,
|
Input,
|
||||||
Table,
|
Table,
|
||||||
@@ -15,10 +22,22 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useDebounce } from "@app/hooks";
|
import { useDebounce, usePopUp } from "@app/hooks";
|
||||||
|
import { useAdminRemoveIdentitySuperAdminAccess } from "@app/hooks/api/admin";
|
||||||
import { useAdminGetIdentities } from "@app/hooks/api/admin/queries";
|
import { useAdminGetIdentities } from "@app/hooks/api/admin/queries";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const IdentityPanelTable = () => {
|
const IdentityPanelTable = ({
|
||||||
|
handlePopUpOpen
|
||||||
|
}: {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["removeServerAdmin"]>,
|
||||||
|
data?: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
const [searchIdentityFilter, setSearchIdentityFilter] = useState("");
|
const [searchIdentityFilter, setSearchIdentityFilter] = useState("");
|
||||||
const [debouncedSearchTerm] = useDebounce(searchIdentityFilter, 500);
|
const [debouncedSearchTerm] = useDebounce(searchIdentityFilter, 500);
|
||||||
|
|
||||||
@@ -48,15 +67,48 @@ const IdentityPanelTable = () => {
|
|||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Name</Th>
|
<Th>Name</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{isPending && <TableSkeleton columns={2} innerKey="identities" />}
|
{isPending && <TableSkeleton columns={2} innerKey="identities" />}
|
||||||
{!isPending &&
|
{!isPending &&
|
||||||
data?.pages?.map((identities) =>
|
data?.pages?.map((identities) =>
|
||||||
identities.map(({ name, id }) => (
|
identities.map(({ name, id, isInstanceAdmin }) => (
|
||||||
<Tr key={`identity-${id}`} className="w-full">
|
<Tr key={`identity-${id}`} className="w-full">
|
||||||
<Td>{name}</Td>
|
<Td>
|
||||||
|
{name}
|
||||||
|
{isInstanceAdmin && (
|
||||||
|
<Badge variant="primary" className="ml-2">
|
||||||
|
Server Admin
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{isInstanceAdmin && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
{isInstanceAdmin && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("removeServerAdmin", { name, id });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove Server Admin
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -81,11 +133,49 @@ const IdentityPanelTable = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IdentityPanel = () => (
|
export const IdentityPanel = () => {
|
||||||
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
|
"removeServerAdmin"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { mutate: deleteIdentitySuperAdminAccess } = useAdminRemoveIdentitySuperAdminAccess();
|
||||||
|
|
||||||
|
const handleRemoveServerAdmin = async () => {
|
||||||
|
const { id } = popUp?.removeServerAdmin?.data as { id: string; name: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteIdentitySuperAdminAccess(id);
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully removed server admin permissions"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Error removing server admin permissions"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpClose("removeServerAdmin");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
|
||||||
</div>
|
</div>
|
||||||
<IdentityPanelTable />
|
<IdentityPanelTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.removeServerAdmin.isOpen}
|
||||||
|
title={`Are you sure want to remove Server Admin permissions from ${
|
||||||
|
(popUp?.removeServerAdmin?.data as { name: string })?.name || ""
|
||||||
|
}?`}
|
||||||
|
subTitle=""
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("removeServerAdmin", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleRemoveServerAdmin}
|
||||||
|
buttonText="Remove Access"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -33,22 +33,26 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useSubscription, useUser } from "@app/context";
|
import { useSubscription } from "@app/context";
|
||||||
import { useDebounce, usePopUp } from "@app/hooks";
|
import { useDebounce, usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useAdminDeleteUser,
|
useAdminDeleteUser,
|
||||||
useAdminGetUsers,
|
useAdminGetUsers,
|
||||||
useAdminGrantServerAdminAccess
|
useAdminGrantServerAdminAccess,
|
||||||
|
useRemoveUserServerAdminAccess
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const addServerAdminUpgradePlanMessage = "Granting another user Server Admin permissions";
|
const addServerAdminUpgradePlanMessage = "Granting another user Server Admin permissions";
|
||||||
|
const removeServerAdminUpgradePlanMessage = "Removing Server Admin permissions from user";
|
||||||
|
|
||||||
const UserPanelTable = ({
|
const UserPanelTable = ({
|
||||||
handlePopUpOpen
|
handlePopUpOpen
|
||||||
}: {
|
}: {
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan", "upgradeToServerAdmin"]>,
|
popUpName: keyof UsePopUpState<
|
||||||
|
["removeUser", "upgradePlan", "upgradeToServerAdmin", "removeServerAdmin"]
|
||||||
|
>,
|
||||||
data?: {
|
data?: {
|
||||||
username: string;
|
username: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -58,8 +62,6 @@ const UserPanelTable = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [searchUserFilter, setSearchUserFilter] = useState("");
|
const [searchUserFilter, setSearchUserFilter] = useState("");
|
||||||
const [adminsOnly, setAdminsOnly] = useState(false);
|
const [adminsOnly, setAdminsOnly] = useState(false);
|
||||||
const { user } = useUser();
|
|
||||||
const userId = user?.id || "";
|
|
||||||
const [debouncedSearchTerm] = useDebounce(searchUserFilter, 500);
|
const [debouncedSearchTerm] = useDebounce(searchUserFilter, 500);
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
@@ -143,7 +145,6 @@ const UserPanelTable = ({
|
|||||||
</Td>
|
</Td>
|
||||||
<Td className="w-5/12">{email}</Td>
|
<Td className="w-5/12">{email}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{userId !== id && (
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
@@ -178,10 +179,27 @@ const UserPanelTable = ({
|
|||||||
Make User Server Admin
|
Make User Server Admin
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
{superAdmin && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!subscription?.instanceUserManagement) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
username,
|
||||||
|
id,
|
||||||
|
message: removeServerAdminUpgradePlanMessage
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlePopUpOpen("removeServerAdmin", { username, id });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove Server Admin
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
@@ -212,11 +230,13 @@ export const UserPanel = () => {
|
|||||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"removeUser",
|
"removeUser",
|
||||||
"upgradePlan",
|
"upgradePlan",
|
||||||
"upgradeToServerAdmin"
|
"upgradeToServerAdmin",
|
||||||
|
"removeServerAdmin"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { mutateAsync: deleteUser } = useAdminDeleteUser();
|
const { mutateAsync: deleteUser } = useAdminDeleteUser();
|
||||||
const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess();
|
const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess();
|
||||||
|
const { mutateAsync: removeAdminAccess } = useRemoveUserServerAdminAccess();
|
||||||
|
|
||||||
const handleRemoveUser = async () => {
|
const handleRemoveUser = async () => {
|
||||||
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
||||||
@@ -256,6 +276,25 @@ export const UserPanel = () => {
|
|||||||
handlePopUpClose("upgradeToServerAdmin");
|
handlePopUpClose("upgradeToServerAdmin");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRemoveServerAdminAccess = async () => {
|
||||||
|
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeAdminAccess(id);
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully removed server admin access from user"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Error removing server admin access from user"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpClose("removeServerAdmin");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
@@ -282,6 +321,17 @@ export const UserPanel = () => {
|
|||||||
onDeleteApproved={handleGrantServerAdminAccess}
|
onDeleteApproved={handleGrantServerAdminAccess}
|
||||||
buttonText="Grant Access"
|
buttonText="Grant Access"
|
||||||
/>
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.removeServerAdmin.isOpen}
|
||||||
|
title={`Are you sure want to remove Server Admin permissions from ${
|
||||||
|
(popUp?.removeServerAdmin?.data as { id: string; username: string })?.username || ""
|
||||||
|
}?`}
|
||||||
|
subTitle=""
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("removeServerAdmin", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleRemoveServerAdminAccess}
|
||||||
|
buttonText="Remove Access"
|
||||||
|
/>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
|||||||
+1
@@ -110,6 +110,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
|||||||
const secretsToDelete = Object.values(selectedEntries.secret).reduce(
|
const secretsToDelete = Object.values(selectedEntries.secret).reduce(
|
||||||
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => {
|
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => {
|
||||||
const entry = secretRecord[env.slug];
|
const entry = secretRecord[env.slug];
|
||||||
|
if (!entry) return accum;
|
||||||
const canDeleteSecret = permission.can(
|
const canDeleteSecret = permission.can(
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: v0.8.14
|
version: v0.8.15
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "v0.8.14"
|
appVersion: "v0.8.15"
|
||||||
|
|||||||
@@ -417,7 +417,6 @@ spec:
|
|||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- managedKubeConfigMapReferences
|
|
||||||
- resyncInterval
|
- resyncInterval
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ controllerManager:
|
|||||||
- ALL
|
- ALL
|
||||||
image:
|
image:
|
||||||
repository: infisical/kubernetes-operator
|
repository: infisical/kubernetes-operator
|
||||||
tag: v0.8.14
|
tag: v0.8.15
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 500m
|
cpu: 500m
|
||||||
|
|||||||
Reference in New Issue
Block a user