feat: permission changes

This commit is contained in:
=
2025-02-26 20:45:38 +05:30
parent 55b1fbdf52
commit afd444cad6
11 changed files with 105 additions and 59 deletions
@@ -72,7 +72,10 @@ export async function up(knex: Knex): Promise<void> {
const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId"); const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => { await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
// not setting a foreign constraint so that cascade effects are not triggered // not setting a foreign constraint so that cascade effects are not triggered
if (!doesGatewayColExist) t.uuid("gatewayId"); if (!doesGatewayColExist) {
t.uuid("gatewayId");
t.foreign("gatewayId").references("id").inTable(TableName.Identity);
}
}); });
} }
} }
@@ -79,7 +79,10 @@ export const gatewayServiceFactory = ({
actorAuthMethod, actorAuthMethod,
orgId orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGatewayActions.Create, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways,
OrgPermissionSubjects.Gateway
);
}; };
const getGatewayRelayDetails = async (actorId: string, actorOrgId: string, actorAuthMethod: ActorAuthMethod) => { const getGatewayRelayDetails = async (actorId: string, actorOrgId: string, actorAuthMethod: ActorAuthMethod) => {
@@ -152,7 +155,7 @@ export const gatewayServiceFactory = ({
const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048; const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048;
const rootCaExpiration = new Date(new Date().setFullYear(2045)); const rootCaExpiration = new Date(new Date().setFullYear(2045));
const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({ const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
name: "CN=Infisical Gateway Root CA", name: `O=${identityOrg},CN=Infisical Gateway Root CA`,
serialNumber: rootCaSerialNumber, serialNumber: rootCaSerialNumber,
notBefore: rootCaIssuedAt, notBefore: rootCaIssuedAt,
notAfter: rootCaExpiration, notAfter: rootCaExpiration,
@@ -174,7 +177,7 @@ export const gatewayServiceFactory = ({
const clientCaCert = await x509.X509CertificateGenerator.create({ const clientCaCert = await x509.X509CertificateGenerator.create({
serialNumber: clientCaSerialNumber, serialNumber: clientCaSerialNumber,
subject: "CN=Client Intermediate CA", subject: `O=${identityOrg},CN=Client Intermediate CA`,
issuer: rootCaCert.subject, issuer: rootCaCert.subject,
notBefore: clientCaIssuedAt, notBefore: clientCaIssuedAt,
notAfter: clientCaExpiration, notAfter: clientCaExpiration,
@@ -227,12 +230,12 @@ export const gatewayServiceFactory = ({
// generate gateway ca // generate gateway ca
const gatewayCaSerialNumber = createSerialNumber(); const gatewayCaSerialNumber = createSerialNumber();
const gatewayCaIssuedAt = new Date(); const gatewayCaIssuedAt = new Date();
const gatewayCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10)); const gatewayCaExpiration = new Date(new Date().setFullYear(2045));
const gatewayCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); const gatewayCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const gatewayCaSkObj = crypto.KeyObject.from(gatewayCaKeys.privateKey); const gatewayCaSkObj = crypto.KeyObject.from(gatewayCaKeys.privateKey);
const gatewayCaCert = await x509.X509CertificateGenerator.create({ const gatewayCaCert = await x509.X509CertificateGenerator.create({
serialNumber: gatewayCaSerialNumber, serialNumber: gatewayCaSerialNumber,
subject: "CN=KMIP Server Intermediate CA", subject: `O=${identityOrg},CN=Gateway CA`,
issuer: rootCaCert.subject, issuer: rootCaCert.subject,
notBefore: gatewayCaIssuedAt, notBefore: gatewayCaIssuedAt,
notAfter: gatewayCaExpiration, notAfter: gatewayCaExpiration,
@@ -327,6 +330,12 @@ export const gatewayServiceFactory = ({
format: "der", format: "der",
type: "pkcs8" type: "pkcs8"
}); });
const gatewayCaCert = new x509.X509Certificate(
orgKmsDecryptor({
cipherTextBlob: orgGatewayConfig.encryptedGatewayCaCertificate
})
);
const gatewayCaPrivateKey = await crypto.subtle.importKey( const gatewayCaPrivateKey = await crypto.subtle.importKey(
"pkcs8", "pkcs8",
gatewayCaSkObj.export({ format: "der", type: "pkcs8" }), gatewayCaSkObj.export({ format: "der", type: "pkcs8" }),
@@ -343,7 +352,7 @@ export const gatewayServiceFactory = ({
const extensions: x509.Extension[] = [ const extensions: x509.Extension[] = [
new x509.BasicConstraintsExtension(false), new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false), await x509.AuthorityKeyIdentifierExtension.create(gatewayCaCert, false),
await x509.SubjectKeyIdentifierExtension.create(gatewayKeys.publicKey), await x509.SubjectKeyIdentifierExtension.create(gatewayKeys.publicKey),
new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy
new x509.KeyUsagesExtension( new x509.KeyUsagesExtension(
@@ -360,8 +369,8 @@ export const gatewayServiceFactory = ({
const privateKey = crypto.KeyObject.from(gatewayKeys.privateKey); const privateKey = crypto.KeyObject.from(gatewayKeys.privateKey);
const gatewayCertificate = await x509.X509CertificateGenerator.create({ const gatewayCertificate = await x509.X509CertificateGenerator.create({
serialNumber, serialNumber,
subject: `CN=${identityId},O=${identityOrg}`, subject: `CN=${identityId},O=${identityOrg},OU=Gateway`,
issuer: rootCaCert.subject, issuer: gatewayCaCert.subject,
notBefore: certIssuedAt, notBefore: certIssuedAt,
notAfter: certExpireAt, notAfter: certExpireAt,
signingKey: gatewayCaPrivateKey, signingKey: gatewayCaPrivateKey,
@@ -401,7 +410,7 @@ export const gatewayServiceFactory = ({
}).cipherTextBlob, }).cipherTextBlob,
identityId, identityId,
orgGatewayRootCaId: orgGatewayConfig.id, orgGatewayRootCaId: orgGatewayConfig.id,
name: alphaNumericNanoId(8) name: `gateway-${alphaNumericNanoId(6)}`
}); });
}); });
@@ -478,7 +487,10 @@ export const gatewayServiceFactory = ({
orgPermission.authMethod, orgPermission.authMethod,
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGatewayActions.Read, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways,
OrgPermissionSubjects.Gateway
);
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!orgGatewayConfig) return []; if (!orgGatewayConfig) return [];
@@ -496,7 +508,10 @@ export const gatewayServiceFactory = ({
orgPermission.authMethod, orgPermission.authMethod,
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGatewayActions.Read, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways,
OrgPermissionSubjects.Gateway
);
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
@@ -513,7 +528,10 @@ export const gatewayServiceFactory = ({
orgPermission.authMethod, orgPermission.authMethod,
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGatewayActions.Edit, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.EditGateways,
OrgPermissionSubjects.Gateway
);
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
@@ -530,7 +548,10 @@ export const gatewayServiceFactory = ({
orgPermission.authMethod, orgPermission.authMethod,
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGatewayActions.Delete, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.DeleteGateways,
OrgPermissionSubjects.Gateway
);
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
@@ -17,7 +17,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
environmentsUsed: 0, environmentsUsed: 0,
identityLimit: null, identityLimit: null,
identitiesUsed: 0, identitiesUsed: 0,
dynamicSecret: false, dynamicSecret: true,
secretVersioning: true, secretVersioning: true,
pitRecovery: false, pitRecovery: false,
ipAllowlisting: false, ipAllowlisting: false,
@@ -52,7 +52,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
enforceMfa: false, enforceMfa: false,
projectTemplates: false, projectTemplates: false,
kmip: false, kmip: false,
gateway: false gateway: true
}); });
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
@@ -34,10 +34,10 @@ export enum OrgPermissionAdminConsoleAction {
export enum OrgPermissionGatewayActions { export enum OrgPermissionGatewayActions {
// is there a better word for this. This mean can an identity be a gateway // is there a better word for this. This mean can an identity be a gateway
Create = "create", CreateGateways = "create-gateways",
Read = "read", ListGateways = "list-gateways",
Edit = "edit", EditGateways = "edit-gateways",
Delete = "delete" DeleteGateways = "delete-gateways"
} }
export enum OrgPermissionSubjects { export enum OrgPermissionSubjects {
@@ -280,10 +280,10 @@ const buildAdminPermission = () => {
can(OrgPermissionAppConnectionActions.Delete, OrgPermissionSubjects.AppConnections); can(OrgPermissionAppConnectionActions.Delete, OrgPermissionSubjects.AppConnections);
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections); can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
can(OrgPermissionGatewayActions.Read, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.Create, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.Edit, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.EditGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.Delete, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole); can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
@@ -321,8 +321,8 @@ const buildMemberPermission = () => {
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections); can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
can(OrgPermissionGatewayActions.Read, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.Create, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
return rules; return rules;
}; };
@@ -9,10 +9,10 @@ export enum OrgPermissionActions {
export enum OrgGatewayPermissionActions { export enum OrgGatewayPermissionActions {
// is there a better word for this. This mean can an identity be a gateway // is there a better word for this. This mean can an identity be a gateway
Create = "create", CreateGateways = "create-gateways",
Read = "read", ListGateways = "list-gateways",
Edit = "edit", EditGateways = "edit-gateways",
Delete = "delete" DeleteGateways = "delete-gateways"
} }
export enum OrgPermissionSubjects { export enum OrgPermissionSubjects {
+1 -1
View File
@@ -6,7 +6,7 @@ export type TGateway = {
updatedAt: string; updatedAt: string;
issuedAt: string; issuedAt: string;
serialNumber: string; serialNumber: string;
heartbeart: string; heartbeat: string;
identity: { identity: {
name: string; name: string;
id: string; id: string;
@@ -5,6 +5,7 @@ import {
faBookOpen, faBookOpen,
faEdit, faEdit,
faEllipsisV, faEllipsisV,
faInfoCircle,
faMagnifyingGlass, faMagnifyingGlass,
faPlug, faPlug,
faSearch, faSearch,
@@ -125,8 +126,18 @@ export const GatewayListPage = withPermission(
<THead> <THead>
<Tr> <Tr>
<Th className="w-1/3">Name</Th> <Th className="w-1/3">Name</Th>
<Th>Issued At</Th> <Th>Cert Issued At</Th>
<Th>Identity</Th> <Th>Identity</Th>
<Th>
Health Check
<Tooltip
asChild={false}
className="normal-case"
content="The last known healthcheck. Triggers every 1 hour."
>
<FontAwesomeIcon icon={faInfoCircle} className="ml-2" />
</Tooltip>
</Th>
<Th className="w-5" /> <Th className="w-5" />
</Tr> </Tr>
</THead> </THead>
@@ -140,8 +151,8 @@ export const GatewayListPage = withPermission(
<Td>{format(new Date(el.issuedAt), "yyyy-MM-dd hh:mm:ss aaa")}</Td> <Td>{format(new Date(el.issuedAt), "yyyy-MM-dd hh:mm:ss aaa")}</Td>
<Td>{el.identity.name}</Td> <Td>{el.identity.name}</Td>
<Td> <Td>
{el.heartbeart {el.heartbeat
? formatRelative(new Date(), new Date(el.heartbeart)) ? formatRelative(new Date(el.heartbeat), new Date())
: "-"} : "-"}
</Td> </Td>
<Td className="w-5"> <Td className="w-5">
@@ -159,7 +170,7 @@ export const GatewayListPage = withPermission(
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<OrgPermissionCan <OrgPermissionCan
I={OrgGatewayPermissionActions.Edit} I={OrgGatewayPermissionActions.EditGateways}
a={OrgPermissionSubjects.Gateway} a={OrgPermissionSubjects.Gateway}
> >
{(isAllowed: boolean) => ( {(isAllowed: boolean) => (
@@ -37,10 +37,10 @@ const kmipPermissionSchema = z
const orgGatewayPermissionSchema = z const orgGatewayPermissionSchema = z
.object({ .object({
[OrgGatewayPermissionActions.Read]: z.boolean().optional(), [OrgGatewayPermissionActions.ListGateways]: z.boolean().optional(),
[OrgGatewayPermissionActions.Edit]: z.boolean().optional(), [OrgGatewayPermissionActions.EditGateways]: z.boolean().optional(),
[OrgGatewayPermissionActions.Delete]: z.boolean().optional(), [OrgGatewayPermissionActions.DeleteGateways]: z.boolean().optional(),
[OrgGatewayPermissionActions.Create]: z.boolean().optional() [OrgGatewayPermissionActions.CreateGateways]: z.boolean().optional()
}) })
.optional(); .optional();
@@ -24,10 +24,10 @@ enum Permission {
} }
const PERMISSION_ACTIONS = [ const PERMISSION_ACTIONS = [
{ action: OrgGatewayPermissionActions.Read, label: "Read" }, { action: OrgGatewayPermissionActions.ListGateways, label: "List Gateways" },
{ action: OrgGatewayPermissionActions.Create, label: "Create" }, { action: OrgGatewayPermissionActions.CreateGateways, label: "Create Gateways" },
{ action: OrgGatewayPermissionActions.Edit, label: "Modify" }, { action: OrgGatewayPermissionActions.EditGateways, label: "Edit Gateways" },
{ action: OrgGatewayPermissionActions.Delete, label: "Remove" } { action: OrgGatewayPermissionActions.DeleteGateways, label: "Delete Gateways" }
] as const; ] as const;
export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props) => { export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props) => {
@@ -47,7 +47,7 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
if (isCustom) return Permission.Custom; if (isCustom) return Permission.Custom;
if (score === 0) return Permission.NoAccess; if (score === 0) return Permission.NoAccess;
if (score === totalActions) return Permission.FullAccess; if (score === totalActions) return Permission.FullAccess;
if (score === 1 && rule?.[OrgGatewayPermissionActions.Read]) return Permission.ReadOnly; if (score === 1 && rule?.[OrgGatewayPermissionActions.ListGateways]) return Permission.ReadOnly;
return Permission.Custom; return Permission.Custom;
}, [rule, isCustom]); }, [rule, isCustom]);
@@ -78,10 +78,10 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
setValue( setValue(
"permissions.gateway", "permissions.gateway",
{ {
[OrgGatewayPermissionActions.Read]: true, [OrgGatewayPermissionActions.ListGateways]: true,
[OrgGatewayPermissionActions.Edit]: true, [OrgGatewayPermissionActions.EditGateways]: true,
[OrgGatewayPermissionActions.Create]: true, [OrgGatewayPermissionActions.CreateGateways]: true,
[OrgGatewayPermissionActions.Delete]: true [OrgGatewayPermissionActions.DeleteGateways]: true
}, },
{ shouldDirty: true } { shouldDirty: true }
); );
@@ -90,10 +90,10 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
setValue( setValue(
"permissions.gateway", "permissions.gateway",
{ {
[OrgGatewayPermissionActions.Read]: true, [OrgGatewayPermissionActions.ListGateways]: true,
[OrgGatewayPermissionActions.Edit]: false, [OrgGatewayPermissionActions.EditGateways]: false,
[OrgGatewayPermissionActions.Create]: false, [OrgGatewayPermissionActions.CreateGateways]: false,
[OrgGatewayPermissionActions.Delete]: false [OrgGatewayPermissionActions.DeleteGateways]: false
}, },
{ shouldDirty: true } { shouldDirty: true }
); );
@@ -104,10 +104,10 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
setValue( setValue(
"permissions.gateway", "permissions.gateway",
{ {
[OrgGatewayPermissionActions.Read]: false, [OrgGatewayPermissionActions.ListGateways]: false,
[OrgGatewayPermissionActions.Edit]: false, [OrgGatewayPermissionActions.EditGateways]: false,
[OrgGatewayPermissionActions.Create]: false, [OrgGatewayPermissionActions.CreateGateways]: false,
[OrgGatewayPermissionActions.Delete]: false [OrgGatewayPermissionActions.DeleteGateways]: false
}, },
{ shouldDirty: true } { shouldDirty: true }
); );
@@ -247,9 +247,15 @@ export const SqlDatabaseInputForm = ({
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none" dropdownContainerClassName="max-w-none"
isLoading={isProjectGatewaysLoading} isLoading={isProjectGatewaysLoading}
placeholder="Select gateway" placeholder="Internet gateway"
position="popper" position="popper"
> >
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
{projectGateways?.map((el) => ( {projectGateways?.map((el) => (
<SelectItem value={el.id} key={el.id}> <SelectItem value={el.id} key={el.id}>
{el.name} {el.name}
@@ -195,7 +195,9 @@ export const EditDynamicSecretSqlProviderForm = ({
<FormControl <FormControl
isError={Boolean(error?.message) || isGatewayInActive} isError={Boolean(error?.message) || isGatewayInActive}
errorText={ errorText={
isGatewayInActive ? `Gateway ${selectedGatewayId} is removed` : error?.message isGatewayInActive && selectedGatewayId
? `Gateway ${selectedGatewayId} is removed`
: error?.message
} }
label="Gateway" label="Gateway"
helperText="" helperText=""
@@ -206,9 +208,12 @@ export const EditDynamicSecretSqlProviderForm = ({
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none" dropdownContainerClassName="max-w-none"
isLoading={isProjectGatewaysLoading} isLoading={isProjectGatewaysLoading}
placeholder="Select gateway" placeholder="Internet Gateway"
position="popper" position="popper"
> >
<SelectItem value={null as unknown as string} onClick={() => onChange(undefined)}>
Internet Gateway
</SelectItem>
{projectGateways?.map((el) => ( {projectGateways?.map((el) => (
<SelectItem value={el.id} key={el.id}> <SelectItem value={el.id} key={el.id}>
{el.name} {el.name}