diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index cccadb86e..5a6cf3077 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -131,12 +131,12 @@ function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrg } } -const escapeHandlebarsMissingMetadata = (obj: Record) => { +const escapeHandlebarsMissingDict = (obj: Record, key: string) => { const handler = { get(target: Record, prop: string) { - if (!(prop in target)) { + if (!Object.hasOwn(target, prop)) { // eslint-disable-next-line no-param-reassign - target[prop] = `{{identity.metadata.${prop}}}`; // Add missing key as an "own" property + target[prop] = `{{${key}.${prop}}}`; // Add missing key as an "own" property } return target[prop]; } @@ -145,4 +145,4 @@ const escapeHandlebarsMissingMetadata = (obj: Record) => { return new Proxy(obj, handler); }; -export { escapeHandlebarsMissingMetadata, isAuthMethodSaml, validateOrgSSO }; +export { escapeHandlebarsMissingDict, isAuthMethodSaml, validateOrgSSO }; diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 6b36234ca..dc68db1d0 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -23,7 +23,7 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission"; import { TPermissionDALFactory } from "./permission-dal"; -import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns"; +import { escapeHandlebarsMissingDict, validateOrgSSO } from "./permission-fns"; import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO, @@ -244,20 +244,22 @@ export const permissionServiceFactory = ({ const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); - const metadataKeyValuePair = escapeHandlebarsMissingMetadata( + const metadataKeyValuePair = escapeHandlebarsMissingDict( objectify( userProjectPermission.metadata, (i) => i.key, (i) => i.value - ) + ), + "identity.metadata" ); + const templateValue = { + id: userProjectPermission.userId, + username: userProjectPermission.username, + metadata: metadataKeyValuePair + }; const interpolateRules = templatedRules( { - identity: { - id: userProjectPermission.userId, - username: userProjectPermission.username, - metadata: metadataKeyValuePair - } + identity: templateValue }, { data: false } ); @@ -318,24 +320,26 @@ export const permissionServiceFactory = ({ const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); - const identityAuthInfo = requestContext.get("identityAuthInfo"); + const unescapedIdentityAuthInfo = requestContext.get("identityAuthInfo"); const unescapedMetadata = objectify( identityProjectPermission.metadata, (i) => i.key, (i) => i.value - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ) as Record; - if (identityAuthInfo?.identityId === identityId && identityAuthInfo) { - unescapedMetadata.auth = identityAuthInfo; - } - const metadataKeyValuePair = escapeHandlebarsMissingMetadata(unescapedMetadata); + ); + const identityAuthInfo = + unescapedIdentityAuthInfo?.identityId === identityId && unescapedIdentityAuthInfo + ? escapeHandlebarsMissingDict(unescapedIdentityAuthInfo as never, "identity.auth") + : {}; + const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata"); + const templateValue = { + id: identityProjectPermission.identityId, + username: identityProjectPermission.username, + metadata: metadataKeyValuePair, + auth: identityAuthInfo + }; const interpolateRules = templatedRules( { - identity: { - id: identityProjectPermission.identityId, - username: identityProjectPermission.username, - metadata: metadataKeyValuePair - } + identity: templateValue }, { data: false } ); @@ -428,20 +432,22 @@ export const permissionServiceFactory = ({ const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); - const metadataKeyValuePair = escapeHandlebarsMissingMetadata( + const metadataKeyValuePair = escapeHandlebarsMissingDict( objectify( userProjectPermission.metadata, (i) => i.key, (i) => i.value - ) + ), + "identity.metadata" ); + const templateValue = { + id: userProjectPermission.userId, + username: userProjectPermission.username, + metadata: metadataKeyValuePair + }; const interpolateRules = templatedRules( { - identity: { - id: userProjectPermission.userId, - username: userProjectPermission.username, - metadata: metadataKeyValuePair - } + identity: templateValue }, { data: false } ); @@ -473,21 +479,22 @@ export const permissionServiceFactory = ({ const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); - const metadataKeyValuePair = escapeHandlebarsMissingMetadata( + const metadataKeyValuePair = escapeHandlebarsMissingDict( objectify( identityProjectPermission.metadata, (i) => i.key, (i) => i.value - ) + ), + "identity.metadata" ); - + const templateValue = { + id: identityProjectPermission.identityId, + username: identityProjectPermission.username, + metadata: metadataKeyValuePair + }; const interpolateRules = templatedRules( { - identity: { - id: identityProjectPermission.identityId, - username: identityProjectPermission.username, - metadata: metadataKeyValuePair - } + identity: templateValue }, { data: false } ); diff --git a/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-1.png b/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-1.png new file mode 100644 index 000000000..e84a258e2 Binary files /dev/null and b/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-1.png differ diff --git a/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-2.png b/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-2.png new file mode 100644 index 000000000..824aabc25 Binary files /dev/null and b/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-2.png differ diff --git a/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-3.png b/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-3.png new file mode 100644 index 000000000..88d9a3da2 Binary files /dev/null and b/docs/documentation/platform/access-controls/abac/images/add-metadata-on-machine-identity-3.png differ diff --git a/docs/documentation/platform/access-controls/abac/managing-machine-identity-attributes.mdx b/docs/documentation/platform/access-controls/abac/managing-machine-identity-attributes.mdx new file mode 100644 index 000000000..5e1cc7093 --- /dev/null +++ b/docs/documentation/platform/access-controls/abac/managing-machine-identity-attributes.mdx @@ -0,0 +1,68 @@ +--- +title: "Machine identities" +description: "Learn how to set metadata and leverage authentication attributes for machine identities." +--- + +Machine identities can have metadata set manually, just like users. In addition, during the machine authentication process (e.g., via OIDC), extra attributes called claims—are provided, which can be used in your ABAC policies. + +#### Setting Metadata on Machine Identities + + + + + + + + + + + + + + + + + +#### Accessing Attributes From Machine Identity Login + +When machine identities authenticate, they may receive additional payloads/attributes from the service provider. +For methods like OIDC, these come as claims in the token and can be made available in your policies. + + + + 1. Navigate to the Identity Authentication settings and select the OIDC Auth Method. + 2. In the **Advanced section**, locate the Claim Mapping configuration. + 3. Map the OIDC claims to permission attributes by specifying: + - **Attribute Name:** The identifier to be used in your policies (e.g., department). + - **Claim Path:** The dot notation path to the claim in the OIDC token (e.g., user.department). + + For example, if your OIDC provider returns: + + ```json + { + "sub": "machine456", + "name": "Service A", + "user": { + "department": "engineering", + "role": "service" + } + } + ``` + + You might map: + + - **department:** to `user.department` + - **role:** to `user.role` + + Once configured, these attributes become available in your policies using the following format: + + ``` + {{ identity.auth.oidc.claims. }} + ``` + + + + + At the moment we only support OIDC claims. Payloads on other authentication methods are not yet accessible. + + diff --git a/docs/documentation/platform/access-controls/abac/managing-user-metadata.mdx b/docs/documentation/platform/access-controls/abac/managing-user-metadata.mdx new file mode 100644 index 000000000..3f62a3b61 --- /dev/null +++ b/docs/documentation/platform/access-controls/abac/managing-user-metadata.mdx @@ -0,0 +1,39 @@ +--- +title: "Users identities" +description: "How to set and use metadata attributes on user identities for ABAC." +--- + +User identities can have metadata attributes assigned directly. These attributes (such as location or department) are used to define dynamic access policies. + +#### Setting Metadata on Users + + + + + + + + + + + + + + + + + For organizations using SAML for **user logins**, Infisical automatically maps metadata attributes from SAML assertions to user identities on every login. This enables dynamic policies based on the user's SAML attributes. + + + +#### Applying ABAC Policies with User Metadata +Attribute-based access controls are currently only available for polices defined on Secrets Manager projects. +You can set ABAC permissions to dynamically set access to environments, folders, secrets, and secret tags. + + + +In your policies, metadata values are accessed as follows: + +- **User ID:** `{{ identity.id }}` (always available) +- **Username:** `{{ identity.username }}` (always available) +- **Metadata Attributes:** `{{ identity.metadata. }}` (available if set) diff --git a/docs/documentation/platform/access-controls/abac/overview.mdx b/docs/documentation/platform/access-controls/abac/overview.mdx new file mode 100644 index 000000000..0a14719f6 --- /dev/null +++ b/docs/documentation/platform/access-controls/abac/overview.mdx @@ -0,0 +1,15 @@ +--- +title: "Overview" +description: "Learn the basics of ABAC for both users and machine identities." +--- + +Infisical's Attribute-based Access Controls (ABAC) enable dynamic, attribute-driven permissions for both users and machine identities. ABAC enforces fine-grained, context-aware access controls using metadata attributes—stored as key-value pairs—either attached to identities or provided during authentication. + + + + Manage user metadata manually or automatically via SAML logins. + + + Set metadata manually like users and access additional attributes provided during machine authentication (for example, OIDC claims). + + \ No newline at end of file diff --git a/docs/documentation/platform/access-controls/attribute-based-access-controls.mdx b/docs/documentation/platform/access-controls/attribute-based-access-controls.mdx deleted file mode 100644 index 99c49c63a..000000000 --- a/docs/documentation/platform/access-controls/attribute-based-access-controls.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: "Attribute-based Access Controls" -description: "Learn how to use ABAC to manage permissions based on identity attributes." ---- - -Infisical's Attribute-based Access Controls (ABAC) allow for dynamic, attribute-driven permissions for both user and machine identities. -ABAC policies use metadata attributes—stored as key-value pairs on identities—to enforce fine-grained permissions that are context aware. - -In ABAC, access controls are defined using metadata attributes, such as location or department, which can be set directly on user or machine identities. -During policy execution, these attributes are evaluated, and determine whether said actor can access the requested resource or perform the requested operation. - -## Project-level Permissions - -Attribute-based access controls are currently available for polices defined on projects. You can set ABAC permissions to control access to environments, folders, secrets, and secret tags. - -### Setting Metadata on Identities - - - - - - - - - - - - - - - - - For organizations using SAML for login, Infisical automatically maps metadata attributes from SAML assertions to user identities. - This makes it easy to create policies that dynamically adapt based on the SAML user’s attributes. - - - - -## Defining ABAC Policies - - - -ABAC policies make use of identity metadata to define dynamic permissions. Each attribute must start and end with double curly-brackets `{{ }}`. -The following attributes are available within project permissions: - -- **User ID**: `{{ identity.id }}` -- **Username**: `{{ identity.username }}` -- **Metadata Attributes**: `{{ identity.metadata. }}` - -During policy execution, these placeholders are replaced by their actual values prior to evaluation. - -### Example Use Case - -#### Location-based Access Control - -Suppose you want to restrict access to secrets within a specific folder based on a user's geographic region. -You could assign a `location` attribute to each user (e.g., `identity.metadata.location`). -You could then structure your folders to align with this attribute and define permissions accordingly. - -For example, a policy might restrict access to folders matching the user's location attribute in the following pattern: -``` -/appA/{{ identity.metadata.location }} -``` -Using this structure, users can only access folders that correspond to their configured `location` attribute. -Consequently, if a users attribute changes due to relocation, no policies need to be changed to gain access to the folders associated with their new location. diff --git a/docs/documentation/platform/access-controls/overview.mdx b/docs/documentation/platform/access-controls/overview.mdx index 552117c7e..0afaaac54 100644 --- a/docs/documentation/platform/access-controls/overview.mdx +++ b/docs/documentation/platform/access-controls/overview.mdx @@ -18,7 +18,7 @@ To make sure that users and machine identities are only accessing the resources diff --git a/docs/images/platform/access-controls/abac-policies-by-auth.png b/docs/images/platform/access-controls/abac-policies-by-auth.png new file mode 100644 index 000000000..3c75aabbe Binary files /dev/null and b/docs/images/platform/access-controls/abac-policies-by-auth.png differ diff --git a/docs/images/platform/access-controls/abac-policy-oidc-format.png b/docs/images/platform/access-controls/abac-policy-oidc-format.png new file mode 100644 index 000000000..7fc76f50b Binary files /dev/null and b/docs/images/platform/access-controls/abac-policy-oidc-format.png differ diff --git a/docs/mint.json b/docs/mint.json index 8f4c22bd1..ea454e6a0 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -150,7 +150,14 @@ "pages": [ "documentation/platform/access-controls/overview", "documentation/platform/access-controls/role-based-access-controls", - "documentation/platform/access-controls/attribute-based-access-controls", + { + "group": "Attribute based access controls", + "pages": [ + "documentation/platform/access-controls/abac/overview", + "documentation/platform/access-controls/abac/managing-user-metadata", + "documentation/platform/access-controls/abac/managing-machine-identity-attributes" + ] + }, "documentation/platform/access-controls/additional-privileges", "documentation/platform/access-controls/temporary-access", "documentation/platform/access-controls/access-requests", diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx index 21440457a..c0a91a7f7 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx @@ -46,12 +46,14 @@ const schema = z.object({ caCert: z.string().trim().default(""), boundIssuer: z.string().min(1), boundAudiences: z.string().optional().default(""), - boundClaims: z.array( - z.object({ - key: z.string(), - value: z.string() - }) - ), + boundClaims: z + .array( + z.object({ + key: z.string(), + value: z.string() + }) + ) + .default([]), claimMetadataMapping: z .array( z.object({ @@ -59,8 +61,7 @@ const schema = z.object({ value: z.string() }) ) - .optional() - .nullable(), + .default([]), boundSubject: z.string().optional().default("") }); @@ -105,7 +106,9 @@ export const IdentityOidcAuthForm = ({ accessTokenTTL: "2592000", accessTokenMaxTTL: "2592000", accessTokenNumUsesLimit: "0", - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + boundClaims: [], + claimMetadataMapping: [] } }); const { @@ -172,7 +175,8 @@ export const IdentityOidcAuthForm = ({ accessTokenTTL: "2592000", accessTokenMaxTTL: "2592000", accessTokenNumUsesLimit: "0", - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + claimMetadataMapping: [] }); } }, [data]); @@ -253,7 +257,9 @@ export const IdentityOidcAuthForm = ({
{ setTabValue( - ["accessTokenTrustedIps", "caCert", "boundClaims"].includes(Object.keys(fields)[0]) + ["accessTokenTrustedIps", "caCert", "claimMetadataMapping"].includes( + Object.keys(fields)[0] + ) ? IdentityFormTab.Advanced : IdentityFormTab.Configuration ); @@ -343,63 +349,6 @@ export const IdentityOidcAuthForm = ({ )} /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - - - ( - -