diff --git a/backend/src/ee/services/dynamic-secret/providers/ldap.ts b/backend/src/ee/services/dynamic-secret/providers/ldap.ts index a4c6408cb..f94e61629 100644 --- a/backend/src/ee/services/dynamic-secret/providers/ldap.ts +++ b/backend/src/ee/services/dynamic-secret/providers/ldap.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { BadRequestError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; -import { LdapSchema, TDynamicProviderFns } from "./models"; +import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models"; const generatePassword = () => { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#"; @@ -193,29 +193,76 @@ export const LdapProvider = (): TDynamicProviderFns => { const providerInputs = await validateProviderInputs(inputs); const client = await getClient(providerInputs); - const username = generateUsername(); - const password = generatePassword(); - const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif }); + if (providerInputs.credentialType === LdapCredentialType.Static) { + const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m); - try { - const dnArray = await executeLdif(client, generatedLdif); + if (dnMatch) { + const username = dnMatch[1]; + const password = generatePassword(); - return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } }; - } catch (err) { - if (providerInputs.rollbackLdif) { - const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif }); - await executeLdif(client, rollbackLdif); + const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif }); + + try { + const dnArray = await executeLdif(client, generatedLdif); + + return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } }; + } catch (err) { + throw new BadRequestError({ message: (err as Error).message }); + } + } else { + throw new BadRequestError({ + message: "Invalid rotation LDIF, missing DN." + }); + } + } else { + const username = generateUsername(); + const password = generatePassword(); + const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif }); + + try { + const dnArray = await executeLdif(client, generatedLdif); + + return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } }; + } catch (err) { + if (providerInputs.rollbackLdif) { + const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif }); + await executeLdif(client, rollbackLdif); + } + throw new BadRequestError({ message: (err as Error).message }); } - throw new BadRequestError({ message: (err as Error).message }); } }; const revoke = async (inputs: unknown, entityId: string) => { const providerInputs = await validateProviderInputs(inputs); - const connection = await getClient(providerInputs); + const client = await getClient(providerInputs); + + if (providerInputs.credentialType === LdapCredentialType.Static) { + const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m); + + if (dnMatch) { + const username = dnMatch[1]; + const password = generatePassword(); + + const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif }); + + try { + const dnArray = await executeLdif(client, generatedLdif); + + return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } }; + } catch (err) { + throw new BadRequestError({ message: (err as Error).message }); + } + } else { + throw new BadRequestError({ + message: "Invalid rotation LDIF, missing DN." + }); + } + } + const revocationLdif = generateLDIF({ username: entityId, ldifTemplate: providerInputs.revocationLdif }); - await executeLdif(connection, revocationLdif); + await executeLdif(client, revocationLdif); return { entityId }; }; diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index c57f14477..d98215fd4 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -12,6 +12,11 @@ export enum ElasticSearchAuthTypes { ApiKey = "api-key" } +export enum LdapCredentialType { + Dynamic = "dynamic", + Static = "static" +} + export const DynamicSecretRedisDBSchema = z.object({ host: z.string().trim().toLowerCase(), port: z.number(), @@ -195,16 +200,26 @@ export const AzureEntraIDSchema = z.object({ clientSecret: z.string().trim().min(1) }); -export const LdapSchema = z.object({ - url: z.string().trim().min(1), - binddn: z.string().trim().min(1), - bindpass: z.string().trim().min(1), - ca: z.string().optional(), - - creationLdif: z.string().min(1), - revocationLdif: z.string().min(1), - rollbackLdif: z.string().optional() -}); +export const LdapSchema = z.union([ + z.object({ + url: z.string().trim().min(1), + binddn: z.string().trim().min(1), + bindpass: z.string().trim().min(1), + ca: z.string().optional(), + credentialType: z.literal(LdapCredentialType.Dynamic).optional().default(LdapCredentialType.Dynamic), + creationLdif: z.string().min(1), + revocationLdif: z.string().min(1), + rollbackLdif: z.string().optional() + }), + z.object({ + url: z.string().trim().min(1), + binddn: z.string().trim().min(1), + bindpass: z.string().trim().min(1), + ca: z.string().optional(), + credentialType: z.literal(LdapCredentialType.Static), + rotationLdif: z.string().min(1) + }) +]); export enum DynamicSecretProviders { SqlDatabase = "sql-database", diff --git a/docs/documentation/platform/dynamic-secrets/ldap.mdx b/docs/documentation/platform/dynamic-secrets/ldap.mdx index 2dc51d74e..ac06a7576 100644 --- a/docs/documentation/platform/dynamic-secrets/ldap.mdx +++ b/docs/documentation/platform/dynamic-secrets/ldap.mdx @@ -10,143 +10,253 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem 1. Create a user with the necessary permissions to create users in your LDAP server. 2. Ensure your LDAP server is reachable via Infisical instance. -## Set up Dynamic Secrets with LDAP +## Create LDAP Credentials - - - Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. - - - ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) - - - ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-ldap-select.png) - + + + + + Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. + + + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) + + + ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-ldap-select.png) + - - - Name by which you want the secret to be referenced - + + + Name by which you want the secret to be referenced + - - Default time-to-live for a generated secret (it is possible to modify this value when a secret is generate) - + + Default time-to-live for a generated secret (it is possible to modify this value when a secret is generate) + - - Maximum time-to-live for a generated secret. - + + Maximum time-to-live for a generated secret. + - - LDAP url to connect to. _(Example: ldap://your-ldap-ip:389 or ldaps://domain:636)_ - + + LDAP url to connect to. _(Example: ldap://your-ldap-ip:389 or ldaps://domain:636)_ + - - DN to bind to. This should have permissions to create a new users. - + + DN to bind to. This should have permissions to create a new users. + - - Password for the given DN. - + + Password for the given DN. + - - CA certificate to use for TLS in case of a secure connection. - + + CA certificate to use for TLS in case of a secure connection. + - - LDIF to run while creating a user in LDAP. This can include extra steps to assign the user to groups or set permissions. - Here `{{Username}}`, `{{Password}}` and `{{EncodedPassword}}` are templatized variables for the username and password generated by the dynamic secret. + + The type of LDAP credential - select Dynamic. + - `{{EncodedPassword}}` is the encoded password required for the `unicodePwd` field in Active Directory as described [here](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/change-windows-active-directory-user-password). + + LDIF to run while creating a user in LDAP. This can include extra steps to assign the user to groups or set permissions. + Here `{{Username}}`, `{{Password}}` and `{{EncodedPassword}}` are templatized variables for the username and password generated by the dynamic secret. - **OpenLDAP** Example: - ``` - dn: uid={{Username}},dc=infisical,dc=com - changetype: add - objectClass: top - objectClass: person - objectClass: organizationalPerson - objectClass: inetOrgPerson - cn: John Doe - sn: Doe - uid: jdoe - mail: jdoe@infisical.com - userPassword: {{Password}} - ``` + `{{EncodedPassword}}` is the encoded password required for the `unicodePwd` field in Active Directory as described [here](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/change-windows-active-directory-user-password). - **Active Directory** Example: - ``` - dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com - changetype: add - objectClass: top - objectClass: person - objectClass: organizationalPerson - objectClass: user - userPrincipalName: {{Username}}@infisical.com - sAMAccountName: {{Username}} - unicodePwd::{{EncodedPassword}} - userAccountControl: 66048 + **OpenLDAP** Example: + ``` + dn: uid={{Username}},dc=infisical,dc=com + changetype: add + objectClass: top + objectClass: person + objectClass: organizationalPerson + objectClass: inetOrgPerson + cn: John Doe + sn: Doe + uid: jdoe + mail: jdoe@infisical.com + userPassword: {{Password}} + ``` - dn: CN=test-group,OU=Test Create,DC=infisical,DC=com - changetype: modify - add: member - member: CN={{Username}},OU=Test Create,DC=infisical,DC=com - - - ``` - + **Active Directory** Example: + ``` + dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com + changetype: add + objectClass: top + objectClass: person + objectClass: organizationalPerson + objectClass: user + userPrincipalName: {{Username}}@infisical.com + sAMAccountName: {{Username}} + unicodePwd::{{EncodedPassword}} + userAccountControl: 66048 - - LDIF to run while revoking a user in LDAP. This can include extra steps to remove the user from groups or set permissions. - Here `{{Username}}` is a templatized variable for the username generated by the dynamic secret. + dn: CN=test-group,OU=Test Create,DC=infisical,DC=com + changetype: modify + add: member + member: CN={{Username}},OU=Test Create,DC=infisical,DC=com + - + ``` + - **OpenLDAP / Active Directory** Example: - ``` - dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com - changetype: delete - ``` - + + LDIF to run while revoking a user in LDAP. This can include extra steps to remove the user from groups or set permissions. + Here `{{Username}}` is a templatized variable for the username generated by the dynamic secret. - - LDIF to run incase Creation LDIF fails midway. + **OpenLDAP / Active Directory** Example: + ``` + dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com + changetype: delete + ``` + - For the creation example shown above, if the user is created successfully but not added to a group, this LDIF can be used to remove the user. - Here `{{Username}}`, `{{Password}}` and `{{EncodedPassword}}` are templatized variables for the username generated by the dynamic secret. + + LDIF to run incase Creation LDIF fails midway. - **OpenLDAP / Active Directory** Example: - ``` - dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com - changetype: delete - ``` - + For the creation example shown above, if the user is created successfully but not added to a group, this LDIF can be used to remove the user. + Here `{{Username}}`, `{{Password}}` and `{{EncodedPassword}}` are templatized variables for the username generated by the dynamic secret. - + **OpenLDAP / Active Directory** Example: + ``` + dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com + changetype: delete + ``` + + - - After submitting the form, you will see a dynamic secret created in the dashboard. - - - Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. - To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item. - Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section. + + After submitting the form, you will see a dynamic secret created in the dashboard. + + + Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. + To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item. + Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section. - ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png) - ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png) - When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. + When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. - ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) - - Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. - + + Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. + - Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you with an array of DN's altered depending on the Creation LDIF. + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you with an array of DN's altered depending on the Creation LDIF. - ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-ldap-lease.png) + ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-ldap-lease.png) + - + - + + + + + Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. + + + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) + + + ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-ldap-select.png) + + + + + Name by which you want the secret to be referenced + + + + Default time-to-live for a generated secret (it is possible to modify this value when a secret is generate) + + + + Maximum time-to-live for a generated secret. + + + + LDAP url to connect to. _(Example: ldap://your-ldap-ip:389 or ldaps://domain:636)_ + + + + DN to bind to. This should have permissions to create a new users. + + + + Password for the given DN. + + + + CA certificate to use for TLS in case of a secure connection. + + + + The type of LDAP credential - select Static. + + + + LDIF to run for rotating the credentals of an LDAP user. This can include extra LDAP steps based on your needs. + Here `{{Password}}` and `{{EncodedPassword}}` are templatized variables for the password generated by the dynamic secret. + + Note that the `-` characters and the empty lines found at the end of the examples are necessary based on the LDIF format. + + **OpenLDAP** Example: + ``` + dn: cn=sheencaps capadngan,ou=people,dc=acme,dc=com + changetype: modify + replace: userPassword + password: {{Password}} + - + + ``` + + **Active Directory** Example: + ``` + dn: cn=sheencaps capadngan,ou=people,dc=acme,dc=com + changetype: modify + replace: unicodePwd + unicodePwd::{{EncodedPassword}} + - + + ``` + `{{EncodedPassword}}` is the encoded password required for the `unicodePwd` field in Active Directory as described [here](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/change-windows-active-directory-user-password). + + + + + + After submitting the form, you will see a dynamic secret created in the dashboard. + + + Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. + To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item. + Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section. + + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png) + + When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. + + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) + + + Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. + + + + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you with an array of DN's altered depending on the Creation LDIF. + + ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-ldap-lease.png) + + + + + + ## Active Directory Integration diff --git a/frontend/src/hooks/api/dynamicSecret/types.ts b/frontend/src/hooks/api/dynamicSecret/types.ts index 4f2a787b9..7ac8d4147 100644 --- a/frontend/src/hooks/api/dynamicSecret/types.ts +++ b/frontend/src/hooks/api/dynamicSecret/types.ts @@ -199,9 +199,11 @@ export type TDynamicSecretProvider = binddn: string; bindpass: string; ca?: string | undefined; - creationLdif: string; - revocationLdif: string; + credentialType: string; + creationLdif?: string; + revocationLdif?: string; rollbackLdif?: string; + rotationLdif?: string; }; } | { diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx index b78161aa5..1a7e0e8fa 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx @@ -8,21 +8,47 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input, TextArea } from "@app/components/v2"; +import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -const formSchema = z.object({ - provider: z.object({ - url: z.string().trim().min(1), - binddn: z.string().trim().min(1), - bindpass: z.string().trim().min(1), - ca: z.string().optional(), +enum CredentialType { + Dynamic = "dynamic", + Static = "static" +} - creationLdif: z.string().min(1), - revocationLdif: z.string().min(1), - rollbackLdif: z.string().optional() - }), +const credentialTypes = [ + { + label: "Dynamic", + value: CredentialType.Dynamic + }, + { + label: "Static", + value: CredentialType.Static + } +] as const; + +const formSchema = z.object({ + provider: z.discriminatedUnion("credentialType", [ + z.object({ + url: z.string().trim().min(1), + binddn: z.string().trim().min(1), + bindpass: z.string().trim().min(1), + ca: z.string().optional(), + credentialType: z.literal(CredentialType.Dynamic), + creationLdif: z.string().min(1), + revocationLdif: z.string().min(1), + rollbackLdif: z.string().optional() + }), + z.object({ + url: z.string().trim().min(1), + binddn: z.string().trim().min(1), + bindpass: z.string().trim().min(1), + ca: z.string().optional(), + credentialType: z.literal(CredentialType.Static), + rotationLdif: z.string().min(1) + }) + ]), defaultTTL: z.string().superRefine((val, ctx) => { const valMs = ms(val); @@ -67,6 +93,8 @@ export const LdapInputForm = ({ const { control, formState: { isSubmitting }, + setValue, + watch, handleSubmit } = useForm({ resolver: zodResolver(formSchema), @@ -78,11 +106,14 @@ export const LdapInputForm = ({ ca: "", creationLdif: "", revocationLdif: "", - rollbackLdif: "" + rollbackLdif: "", + credentialType: CredentialType.Dynamic } } }); + const selectedCredentialType = watch("provider.credentialType"); + const createDynamicSecret = useCreateDynamicSecret(); const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { @@ -240,45 +271,106 @@ export const LdapInputForm = ({ ( -