mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 09:27:50 +00:00
Merge pull request #2641 from Infisical/feat/ldap-static-dynamic-secret
feat: static ldap credentials
This commit is contained in:
@@ -7,7 +7,7 @@ import { z } from "zod";
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
import { LdapSchema, TDynamicProviderFns } from "./models";
|
import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||||
@@ -193,29 +193,76 @@ export const LdapProvider = (): TDynamicProviderFns => {
|
|||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
const client = await getClient(providerInputs);
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
const username = generateUsername();
|
if (providerInputs.credentialType === LdapCredentialType.Static) {
|
||||||
const password = generatePassword();
|
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
|
||||||
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
|
|
||||||
|
|
||||||
try {
|
if (dnMatch) {
|
||||||
const dnArray = await executeLdif(client, generatedLdif);
|
const username = dnMatch[1];
|
||||||
|
const password = generatePassword();
|
||||||
|
|
||||||
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
|
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif });
|
||||||
} catch (err) {
|
|
||||||
if (providerInputs.rollbackLdif) {
|
try {
|
||||||
const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif });
|
const dnArray = await executeLdif(client, generatedLdif);
|
||||||
await executeLdif(client, rollbackLdif);
|
|
||||||
|
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 revoke = async (inputs: unknown, entityId: string) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
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 });
|
const revocationLdif = generateLDIF({ username: entityId, ldifTemplate: providerInputs.revocationLdif });
|
||||||
|
|
||||||
await executeLdif(connection, revocationLdif);
|
await executeLdif(client, revocationLdif);
|
||||||
|
|
||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export enum ElasticSearchAuthTypes {
|
|||||||
ApiKey = "api-key"
|
ApiKey = "api-key"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum LdapCredentialType {
|
||||||
|
Dynamic = "dynamic",
|
||||||
|
Static = "static"
|
||||||
|
}
|
||||||
|
|
||||||
export const DynamicSecretRedisDBSchema = z.object({
|
export const DynamicSecretRedisDBSchema = z.object({
|
||||||
host: z.string().trim().toLowerCase(),
|
host: z.string().trim().toLowerCase(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
@@ -195,16 +200,26 @@ export const AzureEntraIDSchema = z.object({
|
|||||||
clientSecret: z.string().trim().min(1)
|
clientSecret: z.string().trim().min(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LdapSchema = z.object({
|
export const LdapSchema = z.union([
|
||||||
url: z.string().trim().min(1),
|
z.object({
|
||||||
binddn: z.string().trim().min(1),
|
url: z.string().trim().min(1),
|
||||||
bindpass: z.string().trim().min(1),
|
binddn: z.string().trim().min(1),
|
||||||
ca: z.string().optional(),
|
bindpass: z.string().trim().min(1),
|
||||||
|
ca: z.string().optional(),
|
||||||
creationLdif: z.string().min(1),
|
credentialType: z.literal(LdapCredentialType.Dynamic).optional().default(LdapCredentialType.Dynamic),
|
||||||
revocationLdif: z.string().min(1),
|
creationLdif: z.string().min(1),
|
||||||
rollbackLdif: z.string().optional()
|
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 {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
|
|||||||
@@ -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.
|
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.
|
2. Ensure your LDAP server is reachable via Infisical instance.
|
||||||
|
|
||||||
## Set up Dynamic Secrets with LDAP
|
## Create LDAP Credentials
|
||||||
|
|
||||||
<Steps>
|
<Tabs>
|
||||||
<Step title="Open Secret Overview Dashboard">
|
<Tab title="Dynamic">
|
||||||
Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret.
|
<Steps>
|
||||||
</Step>
|
<Step title="Open Secret Overview Dashboard">
|
||||||
<Step title="Click on the 'Add Dynamic Secret' button">
|
Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret.
|
||||||

|
</Step>
|
||||||
</Step>
|
<Step title="Click on the 'Add Dynamic Secret' button">
|
||||||
<Step title="Select 'LDAP'">
|

|
||||||

|
</Step>
|
||||||
</Step>
|
<Step title="Select 'LDAP'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="Provide the inputs for dynamic secret parameters">
|
<Step title="Provide the inputs for dynamic secret parameters">
|
||||||
<ParamField path="Secret Name" type="string" required>
|
<ParamField path="Secret Name" type="string" required>
|
||||||
Name by which you want the secret to be referenced
|
Name by which you want the secret to be referenced
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Default TTL" type="string" required>
|
<ParamField path="Default TTL" type="string" required>
|
||||||
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)
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Max TTL" type="string" required>
|
<ParamField path="Max TTL" type="string" required>
|
||||||
Maximum time-to-live for a generated secret.
|
Maximum time-to-live for a generated secret.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="URL" type="string" required>
|
<ParamField path="URL" type="string" required>
|
||||||
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)_
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="BIND DN" type="string" required>
|
<ParamField path="BIND DN" type="string" required>
|
||||||
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.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="BIND Password" type="string" required>
|
<ParamField path="BIND Password" type="string" required>
|
||||||
Password for the given DN.
|
Password for the given DN.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="CA" type="text">
|
<ParamField path="CA" type="text">
|
||||||
CA certificate to use for TLS in case of a secure connection.
|
CA certificate to use for TLS in case of a secure connection.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Creation LDIF" type="text" required>
|
<ParamField path="Credential Type" type="enum">
|
||||||
LDIF to run while creating a user in LDAP. This can include extra steps to assign the user to groups or set permissions.
|
The type of LDAP credential - select Dynamic.
|
||||||
Here `{{Username}}`, `{{Password}}` and `{{EncodedPassword}}` are templatized variables for the username and password generated by the dynamic secret.
|
</ParamField>
|
||||||
|
|
||||||
`{{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).
|
<ParamField path="Creation LDIF" type="text" required>
|
||||||
|
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:
|
`{{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).
|
||||||
```
|
|
||||||
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: [email protected]
|
|
||||||
userPassword: {{Password}}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Active Directory** Example:
|
**OpenLDAP** Example:
|
||||||
```
|
```
|
||||||
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
dn: uid={{Username}},dc=infisical,dc=com
|
||||||
changetype: add
|
changetype: add
|
||||||
objectClass: top
|
objectClass: top
|
||||||
objectClass: person
|
objectClass: person
|
||||||
objectClass: organizationalPerson
|
objectClass: organizationalPerson
|
||||||
objectClass: user
|
objectClass: inetOrgPerson
|
||||||
userPrincipalName: {{Username}}@infisical.com
|
cn: John Doe
|
||||||
sAMAccountName: {{Username}}
|
sn: Doe
|
||||||
unicodePwd::{{EncodedPassword}}
|
uid: jdoe
|
||||||
userAccountControl: 66048
|
mail: [email protected]
|
||||||
|
userPassword: {{Password}}
|
||||||
|
```
|
||||||
|
|
||||||
dn: CN=test-group,OU=Test Create,DC=infisical,DC=com
|
**Active Directory** Example:
|
||||||
changetype: modify
|
```
|
||||||
add: member
|
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
||||||
member: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
changetype: add
|
||||||
-
|
objectClass: top
|
||||||
```
|
objectClass: person
|
||||||
</ParamField>
|
objectClass: organizationalPerson
|
||||||
|
objectClass: user
|
||||||
|
userPrincipalName: {{Username}}@infisical.com
|
||||||
|
sAMAccountName: {{Username}}
|
||||||
|
unicodePwd::{{EncodedPassword}}
|
||||||
|
userAccountControl: 66048
|
||||||
|
|
||||||
<ParamField path="Revocation LDIF" type="text" required>
|
dn: CN=test-group,OU=Test Create,DC=infisical,DC=com
|
||||||
LDIF to run while revoking a user in LDAP. This can include extra steps to remove the user from groups or set permissions.
|
changetype: modify
|
||||||
Here `{{Username}}` is a templatized variable for the username generated by the dynamic secret.
|
add: member
|
||||||
|
member: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
||||||
|
-
|
||||||
|
```
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
**OpenLDAP / Active Directory** Example:
|
<ParamField path="Revocation LDIF" type="text" required>
|
||||||
```
|
LDIF to run while revoking a user in LDAP. This can include extra steps to remove the user from groups or set permissions.
|
||||||
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
Here `{{Username}}` is a templatized variable for the username generated by the dynamic secret.
|
||||||
changetype: delete
|
|
||||||
```
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField path="Rollback LDIF" type="text">
|
**OpenLDAP / Active Directory** Example:
|
||||||
LDIF to run incase Creation LDIF fails midway.
|
```
|
||||||
|
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
||||||
|
changetype: delete
|
||||||
|
```
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
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.
|
<ParamField path="Rollback LDIF" type="text">
|
||||||
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:
|
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.
|
||||||
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
|
||||||
changetype: delete
|
|
||||||
```
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
</Step>
|
**OpenLDAP / Active Directory** Example:
|
||||||
|
```
|
||||||
|
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
|
||||||
|
changetype: delete
|
||||||
|
```
|
||||||
|
</ParamField>
|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="Click `Submit`">
|
<Step title="Click `Submit`">
|
||||||
After submitting the form, you will see a dynamic secret created in the dashboard.
|
After submitting the form, you will see a dynamic secret created in the dashboard.
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Generate dynamic secrets">
|
<Step title="Generate dynamic secrets">
|
||||||
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
|
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.
|
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.
|
Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
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.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
</Step>
|
||||||
|
|
||||||
</Step>
|
</Steps>
|
||||||
|
|
||||||
</Steps>
|
</Tab>
|
||||||
|
<Tab title="Static">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Open Secret Overview Dashboard">
|
||||||
|
Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret.
|
||||||
|
</Step>
|
||||||
|
<Step title="Click on the 'Add Dynamic Secret' button">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select 'LDAP'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Provide the inputs for dynamic secret parameters">
|
||||||
|
<ParamField path="Secret Name" type="string" required>
|
||||||
|
Name by which you want the secret to be referenced
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Default TTL" type="string" required>
|
||||||
|
Default time-to-live for a generated secret (it is possible to modify this value when a secret is generate)
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Max TTL" type="string" required>
|
||||||
|
Maximum time-to-live for a generated secret.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="URL" type="string" required>
|
||||||
|
LDAP url to connect to. _(Example: ldap://your-ldap-ip:389 or ldaps://domain:636)_
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="BIND DN" type="string" required>
|
||||||
|
DN to bind to. This should have permissions to create a new users.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="BIND Password" type="string" required>
|
||||||
|
Password for the given DN.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="CA" type="text">
|
||||||
|
CA certificate to use for TLS in case of a secure connection.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Credential Type" type="enum">
|
||||||
|
The type of LDAP credential - select Static.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Rotation LDIF" type="text" required>
|
||||||
|
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).
|
||||||
|
|
||||||
|
</ParamField>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Click `Submit`">
|
||||||
|
After submitting the form, you will see a dynamic secret created in the dashboard.
|
||||||
|
</Step>
|
||||||
|
<Step title="Generate dynamic secrets">
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Active Directory Integration
|
## Active Directory Integration
|
||||||
|
|
||||||
|
|||||||
@@ -199,9 +199,11 @@ export type TDynamicSecretProvider =
|
|||||||
binddn: string;
|
binddn: string;
|
||||||
bindpass: string;
|
bindpass: string;
|
||||||
ca?: string | undefined;
|
ca?: string | undefined;
|
||||||
creationLdif: string;
|
credentialType: string;
|
||||||
revocationLdif: string;
|
creationLdif?: string;
|
||||||
|
revocationLdif?: string;
|
||||||
rollbackLdif?: string;
|
rollbackLdif?: string;
|
||||||
|
rotationLdif?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
|||||||
+133
-41
@@ -8,21 +8,47 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TtlFormLabel } from "@app/components/features";
|
import { TtlFormLabel } from "@app/components/features";
|
||||||
import { createNotification } from "@app/components/notifications";
|
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 { useCreateDynamicSecret } from "@app/hooks/api";
|
||||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
const formSchema = z.object({
|
enum CredentialType {
|
||||||
provider: z.object({
|
Dynamic = "dynamic",
|
||||||
url: z.string().trim().min(1),
|
Static = "static"
|
||||||
binddn: z.string().trim().min(1),
|
}
|
||||||
bindpass: z.string().trim().min(1),
|
|
||||||
ca: z.string().optional(),
|
|
||||||
|
|
||||||
creationLdif: z.string().min(1),
|
const credentialTypes = [
|
||||||
revocationLdif: z.string().min(1),
|
{
|
||||||
rollbackLdif: z.string().optional()
|
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) => {
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
const valMs = ms(val);
|
const valMs = ms(val);
|
||||||
@@ -67,6 +93,8 @@ export const LdapInputForm = ({
|
|||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
handleSubmit
|
handleSubmit
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@@ -78,11 +106,14 @@ export const LdapInputForm = ({
|
|||||||
ca: "",
|
ca: "",
|
||||||
creationLdif: "",
|
creationLdif: "",
|
||||||
revocationLdif: "",
|
revocationLdif: "",
|
||||||
rollbackLdif: ""
|
rollbackLdif: "",
|
||||||
|
credentialType: CredentialType.Dynamic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedCredentialType = watch("provider.credentialType");
|
||||||
|
|
||||||
const createDynamicSecret = useCreateDynamicSecret();
|
const createDynamicSecret = useCreateDynamicSecret();
|
||||||
|
|
||||||
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
|
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
|
||||||
@@ -240,45 +271,106 @@ export const LdapInputForm = ({
|
|||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.creationLdif"
|
name="provider.credentialType"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Creation LDIF"
|
label="Credential Type"
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<TextArea {...field} />
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
onValueChange={(e) => {
|
||||||
|
const ldifFields = [
|
||||||
|
"provider.creationLdif",
|
||||||
|
"provider.revocationLdif",
|
||||||
|
"provider.rollbackLdif",
|
||||||
|
"provider.rotationLdif"
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
ldifFields.forEach((f) => {
|
||||||
|
setValue(f, "");
|
||||||
|
});
|
||||||
|
|
||||||
|
field.onChange(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{credentialTypes.map((credentialType) => (
|
||||||
|
<SelectItem
|
||||||
|
value={credentialType.value}
|
||||||
|
key={`credential-type-${credentialType.value}`}
|
||||||
|
>
|
||||||
|
{credentialType.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
{selectedCredentialType === CredentialType.Dynamic && (
|
||||||
control={control}
|
<>
|
||||||
name="provider.revocationLdif"
|
<Controller
|
||||||
render={({ field, fieldState: { error } }) => (
|
control={control}
|
||||||
<FormControl
|
name="provider.creationLdif"
|
||||||
label="Revocation LDIF"
|
render={({ field, fieldState: { error } }) => (
|
||||||
isError={Boolean(error?.message)}
|
<FormControl
|
||||||
errorText={error?.message}
|
label="Creation LDIF"
|
||||||
>
|
isError={Boolean(error?.message)}
|
||||||
<TextArea {...field} />
|
errorText={error?.message}
|
||||||
</FormControl>
|
>
|
||||||
)}
|
<TextArea {...field} />
|
||||||
/>
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.rollbackLdif"
|
name="provider.revocationLdif"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Rollback LDIF"
|
label="Revocation LDIF"
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<TextArea {...field} />
|
<TextArea {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.rollbackLdif"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Rollback LDIF"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<TextArea {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{selectedCredentialType === CredentialType.Static && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.rotationLdif"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Rotation LDIF"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<TextArea {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+123
-35
@@ -5,22 +5,47 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TtlFormLabel } from "@app/components/features";
|
import { TtlFormLabel } from "@app/components/features";
|
||||||
import { createNotification } from "@app/components/notifications";
|
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 { useUpdateDynamicSecret } from "@app/hooks/api";
|
import { useUpdateDynamicSecret } from "@app/hooks/api";
|
||||||
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
enum CredentialType {
|
||||||
|
Dynamic = "dynamic",
|
||||||
|
Static = "static"
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentialTypes = [
|
||||||
|
{
|
||||||
|
label: "Dynamic",
|
||||||
|
value: CredentialType.Dynamic
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Static",
|
||||||
|
value: CredentialType.Static
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
inputs: z
|
inputs: z.discriminatedUnion("credentialType", [
|
||||||
.object({
|
z.object({
|
||||||
url: z.string().trim().min(1),
|
url: z.string().trim().min(1),
|
||||||
binddn: z.string().trim().min(1),
|
binddn: z.string().trim().min(1),
|
||||||
bindpass: z.string().trim().min(1),
|
bindpass: z.string().trim().min(1),
|
||||||
ca: z.string().optional(),
|
ca: z.string().optional(),
|
||||||
|
credentialType: z.literal(CredentialType.Dynamic),
|
||||||
creationLdif: z.string().min(1),
|
creationLdif: z.string().min(1),
|
||||||
revocationLdif: z.string().min(1),
|
revocationLdif: z.string().min(1),
|
||||||
rollbackLdif: z.string().optional()
|
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)
|
||||||
})
|
})
|
||||||
.partial(),
|
]),
|
||||||
defaultTTL: z.string().superRefine((val, ctx) => {
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
const valMs = ms(val);
|
const valMs = ms(val);
|
||||||
if (valMs < 60 * 1000)
|
if (valMs < 60 * 1000)
|
||||||
@@ -67,7 +92,9 @@ export const EditDynamicSecretLdapForm = ({
|
|||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
handleSubmit
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
values: {
|
values: {
|
||||||
@@ -81,6 +108,7 @@ export const EditDynamicSecretLdapForm = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateDynamicSecret = useUpdateDynamicSecret();
|
const updateDynamicSecret = useUpdateDynamicSecret();
|
||||||
|
const selectedCredentialType = watch("inputs.credentialType");
|
||||||
|
|
||||||
const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
|
const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
|
||||||
// wait till previous request is finished
|
// wait till previous request is finished
|
||||||
@@ -204,43 +232,103 @@ export const EditDynamicSecretLdapForm = ({
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="inputs.creationLdif"
|
name="inputs.credentialType"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Creation LDIF"
|
label="Credential Type"
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<TextArea {...field} />
|
<Select
|
||||||
</FormControl>
|
defaultValue={field.value}
|
||||||
)}
|
{...field}
|
||||||
/>
|
className="w-full"
|
||||||
<Controller
|
onValueChange={(e) => {
|
||||||
control={control}
|
const ldifFields = [
|
||||||
name="inputs.revocationLdif"
|
"inputs.creationLdif",
|
||||||
render={({ field, fieldState: { error } }) => (
|
"inputs.revocationLdif",
|
||||||
<FormControl
|
"inputs.rollbackLdif",
|
||||||
label="Revocation LDIF"
|
"inputs.rotationLdif"
|
||||||
isError={Boolean(error)}
|
] as const;
|
||||||
errorText={error?.message}
|
|
||||||
>
|
ldifFields.forEach((f) => {
|
||||||
<TextArea {...field} />
|
setValue(f, "");
|
||||||
</FormControl>
|
});
|
||||||
)}
|
|
||||||
/>
|
field.onChange(e);
|
||||||
<Controller
|
}}
|
||||||
control={control}
|
>
|
||||||
name="inputs.rollbackLdif"
|
{credentialTypes.map((credentialType) => (
|
||||||
render={({ field, fieldState: { error } }) => (
|
<SelectItem
|
||||||
<FormControl
|
value={credentialType.value}
|
||||||
label="Rollback LDIF"
|
key={`credential-type-${credentialType.value}`}
|
||||||
isError={Boolean(error)}
|
>
|
||||||
errorText={error?.message}
|
{credentialType.label}
|
||||||
>
|
</SelectItem>
|
||||||
<TextArea {...field} />
|
))}
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{selectedCredentialType === CredentialType.Dynamic && (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.creationLdif"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Creation LDIF"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<TextArea {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.revocationLdif"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Revocation LDIF"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<TextArea {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.rollbackLdif"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Rollback LDIF"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<TextArea {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{selectedCredentialType === CredentialType.Static && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.rotationLdif"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Rotation LDIF"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<TextArea {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center space-x-4">
|
<div className="mt-4 flex items-center space-x-4">
|
||||||
<Button type="submit" isLoading={isSubmitting}>
|
<Button type="submit" isLoading={isSubmitting}>
|
||||||
|
|||||||
Reference in New Issue
Block a user