diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 77e4b1da9..45ce332a2 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -93,7 +93,14 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { const ldapClient = ldapjs.createClient({ url: ldapConfig.url, bindDN: ldapConfig.bindDN, - bindCredentials: ldapConfig.bindPass + bindCredentials: ldapConfig.bindPass, + ...(ldapConfig.caCert !== "" + ? { + tlsOptions: { + ca: [ldapConfig.caCert] + } + } + : {}) }); ldapClient.bind(ldapConfig.bindDN, ldapConfig.bindPass, (err) => { @@ -109,7 +116,6 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { searchGroups(ldapClient, searchFilter, ldapConfig.groupSearchBase) .then((groups) => { - // groups here ldapClient.unbind(); return server.services.ldap.ldapLogin({ ldapConfigId: ldapConfig.id, diff --git a/backend/src/ee/services/group/group-fns.ts b/backend/src/ee/services/group/group-fns.ts index 8b37de300..e308891f9 100644 --- a/backend/src/ee/services/group/group-fns.ts +++ b/backend/src/ee/services/group/group-fns.ts @@ -22,10 +22,6 @@ const addAcceptedUsersToGroup = async ({ projectBotDAL, tx }: TAddUsersToGroup) => { - console.log("addAcceptedUsersToGroup args: ", { - userIds, - group - }); const users = await userDAL.findUserEncKeyByUserIdsBatch( { userIds diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index 5071a5db5..e90f8c031 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -3,6 +3,8 @@ import jwt from "jsonwebtoken"; import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; +import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; +import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { getConfig } from "@app/lib/config/env"; import { decryptSymmetric, @@ -14,8 +16,12 @@ import { } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; +import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; +import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; +import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; import { normalizeUsername } from "@app/services/user/user-fns"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; @@ -43,8 +49,19 @@ type TLdapConfigServiceFactoryDep = { "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; orgBotDAL: Pick; - groupDAL: TGroupDALFactory; // TODO: Pick - userDAL: Pick; + groupDAL: Pick; + groupProjectDAL: Pick; + projectKeyDAL: Pick; + projectDAL: Pick; + projectBotDAL: Pick; + userGroupMembershipDAL: Pick< + TUserGroupMembershipDALFactory, + "find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete" + >; + userDAL: Pick< + TUserDALFactory, + "create" | "findOne" | "transaction" | "updateById" | "findUserEncKeyByUserIdsBatch" | "find" + >; userAliasDAL: Pick; permissionService: Pick; licenseService: Pick; @@ -58,6 +75,11 @@ export const ldapConfigServiceFactory = ({ orgDAL, orgBotDAL, groupDAL, + groupProjectDAL, + projectKeyDAL, + projectDAL, + projectBotDAL, + userGroupMembershipDAL, userDAL, userAliasDAL, permissionService, @@ -345,7 +367,7 @@ export const ldapConfigServiceFactory = ({ }; const ldapLogin = async ({ - // ldapConfigId, + ldapConfigId, externalId, username, firstName, @@ -431,26 +453,75 @@ export const ldapConfigServiceFactory = ({ const user = await userDAL.findOne({ id: userAlias.userId }); if (groups) { - // TODO - // const m = await ldapGroupMapDAL.find({ - // ldapConfigId, - // $in: { - // ldapGroupCN: groups.map((group) => group.cn) - // } - // }); - /** - * TODO: - * - Find relevant group maps - * - Query for groups matching name - * - Provision, de-provision user to groups accordingly - */ - // console.log("there are groups"); - // const matchingGroups = await groupDAL.find({ - // $in: { - // name: groups.map((group) => group.cn) - // } - // }); - // console.log("found matching groups"); + const ldapGroupIdsToBePartOf = ( + await ldapGroupMapDAL.find({ + ldapConfigId, + $in: { + ldapGroupCN: groups.map((group) => group.cn) + } + }) + ).map((groupMap) => groupMap.groupId); + + const groupsToBePartOf = await groupDAL.find({ + orgId, + $in: { + id: ldapGroupIdsToBePartOf + } + }); + const toBePartOfGroupIdsSet = new Set(groupsToBePartOf.map((groupToBePartOf) => groupToBePartOf.id)); + + const allLdapGroupMaps = await ldapGroupMapDAL.find({ + ldapConfigId + }); + + const ldapGroupIdsCurrentlyPartOf = ( + await userGroupMembershipDAL.find({ + userId: user.id, + $in: { + groupId: allLdapGroupMaps.map((groupMap) => groupMap.groupId) + } + }) + ).map((userGroupMembership) => userGroupMembership.groupId); + + const userGroupMembershipGroupIdsSet = new Set(ldapGroupIdsCurrentlyPartOf); + + for await (const group of groupsToBePartOf) { + if (!userGroupMembershipGroupIdsSet.has(group.id)) { + // add user to group that they should be part of + await addUsersToGroupByUserIds({ + group, + userIds: [user.id], + userDAL, + userGroupMembershipDAL, + orgDAL, + groupProjectDAL, + projectKeyDAL, + projectDAL, + projectBotDAL + }); + } + } + + const groupsCurrentlyPartOf = await groupDAL.find({ + orgId, + $in: { + id: ldapGroupIdsCurrentlyPartOf + } + }); + + for await (const group of groupsCurrentlyPartOf) { + if (!toBePartOfGroupIdsSet.has(group.id)) { + // remove user from group that they should no longer be part of + await removeUsersFromGroupByUserIds({ + group, + userIds: [user.id], + userDAL, + userGroupMembershipDAL, + groupProjectDAL, + projectKeyDAL + }); + } + } } const isUserCompleted = Boolean(user.isAccepted); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index d53830ae2..4cb56a222 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -306,6 +306,11 @@ export const registerRoutes = async ( orgDAL, orgBotDAL, groupDAL, + groupProjectDAL, + projectKeyDAL, + projectDAL, + projectBotDAL, + userGroupMembershipDAL, userDAL, userAliasDAL, permissionService, diff --git a/docs/documentation/platform/ldap.mdx b/docs/documentation/platform/ldap.mdx deleted file mode 100644 index ba01aa743..000000000 --- a/docs/documentation/platform/ldap.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: "LDAP" -description: "Log in to Infisical with LDAP" ---- - - - LDAP is a paid feature. - - If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, - then you should contact sales@infisical.com to purchase an enterprise license to use it. - - -You can configure your organization in Infisical to have members authenticate with the platform via [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol). - - - - In Infisical, head to your Organization Settings > Authentication > LDAP Configuration and select **Set up LDAP**. - - Next, input your LDAP server settings. - - ![LDAP configuration](/images/platform/ldap/ldap-config.png) - - Here's some guidance for each field: - - - URL: The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc. - - Bind DN: The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`. - - Bind Pass: The password to use along with `Bind DN` when performing the user search. - - Search Base / User DN: Base DN under which to perform user search such as `ou=Users,dc=example,dc=com` - - CA Certificate: The CA certificate to use when verifying the LDAP server certificate. - - - Enabling LDAP allows members in your organization to log into Infisical via LDAP. - - ![LDAP toggle](/images/platform/ldap/ldap-toggle.png) - - \ No newline at end of file diff --git a/docs/documentation/platform/ldap/general.mdx b/docs/documentation/platform/ldap/general.mdx index 5e50b736b..535cce734 100644 --- a/docs/documentation/platform/ldap/general.mdx +++ b/docs/documentation/platform/ldap/general.mdx @@ -4,16 +4,17 @@ description: "Learn how to log in to Infisical with LDAP." --- - LDAP is a paid feature. - If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, - then you should contact sales@infisical.com to purchase an enterprise license to use it. + LDAP is a paid feature. If you're using Infisical Cloud, then it is available + under the **Enterprise Tier**. If you're self-hosting Infisical, then you + should contact sales@infisical.com to purchase an enterprise license to use + it. You can configure your organization in Infisical to have members authenticate with the platform via [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) - In Infisical, head to your Organization Settings > Authentication > LDAP Configuration and select **Set up LDAP**. + In Infisical, head to your Organization Settings > Security > LDAP and select **Manage**. Next, input your LDAP server settings. @@ -24,11 +25,41 @@ You can configure your organization in Infisical to have members authenticate wi - URL: The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc. - Bind DN: The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`. - Bind Pass: The password to use along with `Bind DN` when performing the user search. - - Search Base / User DN: Base DN under which to perform user search such as `ou=Users,dc=example,dc=com` + - Search Base / User DN: Base DN under which to perform user search such as `ou=Users,dc=acme,dc=com` + - Group Search Base / Group DN (optional): LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`. + - Group Filter (optional): Template used when constructing the group membership query such as `(objectClass=posixGroup)`. The template can access the following context variables: [`UserDN`, `UserUID`, `UserName`]. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas. - CA Certificate: The CA certificate to use when verifying the LDAP server certificate. + + + The **Group Search Base / Group DN** and **Group Filter** fields are both required if you wish to sync LDAP groups to Infisical. + + + + + In order to sync LDAP groups to Infisical, head to the **LDAP Group Mappings** section to define mappings from LDAP groups to groups in Infisical. + + ![LDAP group mappings section](/images/platform/ldap/ldap-group-mappings-section.png) + + Group mappings ensure that users who log into Infisical via LDAP are added to or removed from the Infisical group(s) that corresponds to the LDAP group(s) they are a member of. + + ![LDAP group mappings table](/images/platform/ldap/ldap-group-mappings-table.png) + + Each group mapping consists of two parts: + - LDAP Group CN: The common name of the LDAP group to map. + - Infisical Group: The Infisical group to map the LDAP group to. + + For example, suppose you want to automatically add a user who is part of the LDAP group with CN `Engineers` to the Infisical group `Engineers` when the user sets up their account with Infisical. + + In this case, you would specify a mapping from the LDAP group with CN `Engineers` to the Infisical group `Engineers`. + Now when the user logs into Infisical via LDAP, Infisical will check the LDAP groups that the user is a part of whilst referencing the group mappings you created earlier. Since the user is a member of the LDAP group with CN `Engineers`, they will be added to the Infisical group `Engineers`. + In the future, if the user is no longer part of the LDAP group with CN `Engineers`, they will be removed from the Infisical group `Engineers` upon their next login. + + Prior to defining any group mappings, ensure that you've created the Infisical groups that you want to map the LDAP groups to. + You can read more about creating (user) groups in Infisical [here](/documentation/platform/groups). + Enabling LDAP allows members in your organization to log into Infisical via LDAP. ![LDAP toggle](/images/platform/ldap/ldap-toggle.png) - \ No newline at end of file + diff --git a/docs/documentation/platform/ldap/jumpcloud.mdx b/docs/documentation/platform/ldap/jumpcloud.mdx index 454a4d522..32b253eb7 100644 --- a/docs/documentation/platform/ldap/jumpcloud.mdx +++ b/docs/documentation/platform/ldap/jumpcloud.mdx @@ -4,9 +4,10 @@ description: "Learn how to configure JumpCloud LDAP for authenticating into Infi --- - LDAP is a paid feature. - If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, - then you should contact sales@infisical.com to purchase an enterprise license to use it. + LDAP is a paid feature. If you're using Infisical Cloud, then it is available + under the **Enterprise Tier**. If you're self-hosting Infisical, then you + should contact sales@infisical.com to purchase an enterprise license to use + it. @@ -17,13 +18,13 @@ description: "Learn how to configure JumpCloud LDAP for authenticating into Infi When creating the user, input their **First Name**, **Last Name**, **Username** (required), **Company Email** (required), and **Description**. Also, create a password for the user. - Next, under User Security Settings and Permissions > Permission Settings, check the box next to **Enable as LDAP Bind DN**. + Next, under User Security Settings and Permissions > Permission Settings, check the box next to **Enable as LDAP Bind DN**. ![LDAP JumpCloud](/images/platform/ldap/jumpcloud/ldap-jumpcloud-enable-bind-dn.png) - In Infisical, head to your Organization Settings > Authentication > LDAP Configuration and select **Set up LDAP**. + In Infisical, head to your Organization Settings > Security > LDAP and select **Manage**. Next, input your JumpCloud LDAP server settings. @@ -35,20 +36,48 @@ description: "Learn how to configure JumpCloud LDAP for authenticating into Infi - Bind DN: The distinguished name of object to bind when performing the user search (`uid=,ou=Users,o=,dc=jumpcloud,dc=com`). - Bind Pass: The password to use along with `Bind DN` when performing the user search. - Search Base / User DN: Base DN under which to perform user search (`ou=Users,o=,dc=jumpcloud,dc=com`). + - Group Search Base / Group DN (optional): LDAP search base to use for group membership search (`ou=Users,o=,dc=jumpcloud,dc=com`). + - Group Filter (optional): Template used when constructing the group membership query (`(objectClass=groupOfNames)`). - CA Certificate: The CA certificate to use when verifying the LDAP server certificate (instructions to obtain the certificate for JumpCloud [here](https://jumpcloud.com/support/connect-to-ldap-with-tls-ssl)). When filling out the **Bind DN** and **Bind Pass** fields, refer to the username and password of the user created in Step 1. - Also, for the **Bind DN** and **Search Base / User DN** fields, you'll want to use the organization ID that appears + Also, for the **Bind DN** and **Search Base / User DN** fields, you'll want to use the organization ID that appears in your LDAP instance **ORG DN**. + + In order to sync LDAP groups to Infisical, head to the **LDAP Group Mappings** section to define mappings from LDAP groups to groups in Infisical. + + ![LDAP group mappings section](/images/platform/ldap/ldap-group-mappings-section.png) + + Group mappings ensure that users who log into Infisical via LDAP are added to or removed from the Infisical group(s) that corresponds to the LDAP group(s) they are a member of. + + ![LDAP group mappings table](/images/platform/ldap/ldap-group-mappings-table.png) + + Each group mapping consists of two parts: + - LDAP Group CN: The common name of the LDAP group to map. + - Infisical Group: The Infisical group to map the LDAP group to. + + For example, suppose you want to automatically add a user who is part of the LDAP group with CN `Engineers` to the Infisical group `Engineers` when the user sets up their account with Infisical. + + In this case, you would specify a mapping from the LDAP group with CN `Engineers` to the Infisical group `Engineers`. + Now when the user logs into Infisical via LDAP, Infisical will check the LDAP groups that the user is a part of whilst referencing the group mappings you created earlier. Since the user is a member of the LDAP group with CN `Engineers`, they will be added to the Infisical group `Engineers`. + In the future, if the user is no longer part of the LDAP group with CN `Engineers`, they will be removed from the Infisical group `Engineers` upon their next login. + + Prior to defining any group mappings, ensure that you've created the Infisical groups that you want to map the LDAP groups to. + You can read more about creating (user) groups in Infisical [here](/documentation/platform/groups). + + + Enabling LDAP allows members in your organization to log into Infisical via LDAP. ![LDAP toggle](/images/platform/ldap/ldap-toggle.png) + Resources: -- [JumpCloud Cloud LDAP Guide](https://jumpcloud.com/support/use-cloud-ldap) \ No newline at end of file + +- [JumpCloud Cloud LDAP Guide](https://jumpcloud.com/support/use-cloud-ldap) diff --git a/docs/images/platform/ldap/ldap-config.png b/docs/images/platform/ldap/ldap-config.png index 8d105c1d6..499b942b0 100644 Binary files a/docs/images/platform/ldap/ldap-config.png and b/docs/images/platform/ldap/ldap-config.png differ diff --git a/docs/images/platform/ldap/ldap-group-mappings-section.png b/docs/images/platform/ldap/ldap-group-mappings-section.png new file mode 100644 index 000000000..9f668e44b Binary files /dev/null and b/docs/images/platform/ldap/ldap-group-mappings-section.png differ diff --git a/docs/images/platform/ldap/ldap-group-mappings-table.png b/docs/images/platform/ldap/ldap-group-mappings-table.png new file mode 100644 index 000000000..1003b5af8 Binary files /dev/null and b/docs/images/platform/ldap/ldap-group-mappings-table.png differ diff --git a/docs/images/platform/ldap/ldap-toggle.png b/docs/images/platform/ldap/ldap-toggle.png index dcc7ffc96..30755b7ec 100644 Binary files a/docs/images/platform/ldap/ldap-toggle.png and b/docs/images/platform/ldap/ldap-toggle.png differ diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx index e8c9828f6..a71b62df4 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx @@ -23,7 +23,8 @@ import { Td, Th, THead, - Tr} from "@app/components/v2"; + Tr +} from "@app/components/v2"; import { useOrganization } from "@app/context"; import { useCreateLDAPGroupMapping, @@ -193,7 +194,7 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: LDAP Group CN - Group Slug + Infisical Group