mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 07:26:11 +00:00
Merge remote-tracking branch 'origin' into fix-integration-sync-import-priority
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
const doesCreatedAtExist = await knex.schema.hasColumn(TableName.AuditLog, "createdAt");
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesProjectIdExist && doesCreatedAtExist) t.index(["projectId", "createdAt"]);
|
||||||
|
if (doesOrgIdExist && doesCreatedAtExist) t.index(["orgId", "createdAt"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
const doesCreatedAtExist = await knex.schema.hasColumn(TableName.AuditLog, "createdAt");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesProjectIdExist && doesCreatedAtExist) t.dropIndex(["projectId", "createdAt"]);
|
||||||
|
if (doesOrgIdExist && doesCreatedAtExist) t.dropIndex(["orgId", "createdAt"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import {
|
||||||
|
AddUserToGroupCommand,
|
||||||
|
AttachUserPolicyCommand,
|
||||||
|
CreateAccessKeyCommand,
|
||||||
|
CreateUserCommand,
|
||||||
|
DeleteAccessKeyCommand,
|
||||||
|
DeleteUserCommand,
|
||||||
|
DeleteUserPolicyCommand,
|
||||||
|
DetachUserPolicyCommand,
|
||||||
|
GetUserCommand,
|
||||||
|
IAMClient,
|
||||||
|
ListAccessKeysCommand,
|
||||||
|
ListAttachedUserPoliciesCommand,
|
||||||
|
ListGroupsForUserCommand,
|
||||||
|
ListUserPoliciesCommand,
|
||||||
|
PutUserPolicyCommand,
|
||||||
|
RemoveUserFromGroupCommand
|
||||||
|
} from "@aws-sdk/client-iam";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
const generateUsername = () => {
|
||||||
|
return alphaNumericNanoId(32);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AwsIamProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await DynamicSecretAwsIamSchema.parseAsync(inputs);
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof DynamicSecretAwsIamSchema>) => {
|
||||||
|
const client = new IAMClient({
|
||||||
|
region: providerInputs.region,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: providerInputs.accessKey,
|
||||||
|
secretAccessKey: providerInputs.secretAccessKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const isConnected = await client.send(new GetUserCommand({})).then(() => true);
|
||||||
|
return isConnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = generateUsername();
|
||||||
|
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
|
||||||
|
const createUserRes = await client.send(
|
||||||
|
new CreateUserCommand({
|
||||||
|
Path: awsPath,
|
||||||
|
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
|
||||||
|
Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }],
|
||||||
|
UserName: username
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
|
||||||
|
if (userGroups) {
|
||||||
|
await Promise.all(
|
||||||
|
userGroups
|
||||||
|
.split(",")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((group) =>
|
||||||
|
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (policyArns) {
|
||||||
|
await Promise.all(
|
||||||
|
policyArns
|
||||||
|
.split(",")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((policyArn) =>
|
||||||
|
client.send(new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn }))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (policyDocument) {
|
||||||
|
await client.send(
|
||||||
|
new PutUserPolicyCommand({
|
||||||
|
UserName: createUserRes.User.UserName,
|
||||||
|
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
|
||||||
|
PolicyDocument: policyDocument
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAccessKeyRes = await client.send(
|
||||||
|
new CreateAccessKeyCommand({
|
||||||
|
UserName: createUserRes.User.UserName
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (!createAccessKeyRes.AccessKey)
|
||||||
|
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" });
|
||||||
|
|
||||||
|
return {
|
||||||
|
entityId: username,
|
||||||
|
data: {
|
||||||
|
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
|
||||||
|
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
|
||||||
|
USERNAME: username
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, entityId: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = entityId;
|
||||||
|
|
||||||
|
// remove user from groups
|
||||||
|
const userGroups = await client.send(new ListGroupsForUserCommand({ UserName: username }));
|
||||||
|
await Promise.all(
|
||||||
|
(userGroups.Groups || []).map(({ GroupName }) =>
|
||||||
|
client.send(
|
||||||
|
new RemoveUserFromGroupCommand({
|
||||||
|
GroupName,
|
||||||
|
UserName: username
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove user access keys
|
||||||
|
const userAccessKeys = await client.send(new ListAccessKeysCommand({ UserName: username }));
|
||||||
|
await Promise.all(
|
||||||
|
(userAccessKeys.AccessKeyMetadata || []).map(({ AccessKeyId }) =>
|
||||||
|
client.send(
|
||||||
|
new DeleteAccessKeyCommand({
|
||||||
|
AccessKeyId,
|
||||||
|
UserName: username
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove user inline policies
|
||||||
|
const userInlinePolicies = await client.send(new ListUserPoliciesCommand({ UserName: username }));
|
||||||
|
await Promise.all(
|
||||||
|
(userInlinePolicies.PolicyNames || []).map((policyName) =>
|
||||||
|
client.send(
|
||||||
|
new DeleteUserPolicyCommand({
|
||||||
|
PolicyName: policyName,
|
||||||
|
UserName: username
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove user attached policies
|
||||||
|
const userAttachedPolicies = await client.send(new ListAttachedUserPoliciesCommand({ UserName: username }));
|
||||||
|
await Promise.all(
|
||||||
|
(userAttachedPolicies.AttachedPolicies || []).map((policy) =>
|
||||||
|
client.send(
|
||||||
|
new DetachUserPolicyCommand({
|
||||||
|
PolicyArn: policy.PolicyArn,
|
||||||
|
UserName: username
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.send(new DeleteUserCommand({ UserName: username }));
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (_inputs: unknown, entityId: string) => {
|
||||||
|
// do nothing
|
||||||
|
const username = entityId;
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { AwsIamProvider } from "./aws-iam";
|
||||||
import { CassandraProvider } from "./cassandra";
|
import { CassandraProvider } from "./cassandra";
|
||||||
import { DynamicSecretProviders } from "./models";
|
import { DynamicSecretProviders } from "./models";
|
||||||
import { SqlDatabaseProvider } from "./sql-database";
|
import { SqlDatabaseProvider } from "./sql-database";
|
||||||
|
|
||||||
export const buildDynamicSecretProviders = () => ({
|
export const buildDynamicSecretProviders = () => ({
|
||||||
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider(),
|
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider(),
|
||||||
[DynamicSecretProviders.Cassandra]: CassandraProvider()
|
[DynamicSecretProviders.Cassandra]: CassandraProvider(),
|
||||||
|
[DynamicSecretProviders.AwsIam]: AwsIamProvider()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,38 +8,51 @@ export enum SqlProviders {
|
|||||||
|
|
||||||
export const DynamicSecretSqlDBSchema = z.object({
|
export const DynamicSecretSqlDBSchema = z.object({
|
||||||
client: z.nativeEnum(SqlProviders),
|
client: z.nativeEnum(SqlProviders),
|
||||||
host: z.string().toLowerCase(),
|
host: z.string().trim().toLowerCase(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
database: z.string(),
|
database: z.string().trim(),
|
||||||
username: z.string(),
|
username: z.string().trim(),
|
||||||
password: z.string(),
|
password: z.string().trim(),
|
||||||
creationStatement: z.string(),
|
creationStatement: z.string().trim(),
|
||||||
revocationStatement: z.string(),
|
revocationStatement: z.string().trim(),
|
||||||
renewStatement: z.string().optional(),
|
renewStatement: z.string().trim().optional(),
|
||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DynamicSecretCassandraSchema = z.object({
|
export const DynamicSecretCassandraSchema = z.object({
|
||||||
host: z.string().toLowerCase(),
|
host: z.string().trim().toLowerCase(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
localDataCenter: z.string().min(1),
|
localDataCenter: z.string().trim().min(1),
|
||||||
keyspace: z.string().optional(),
|
keyspace: z.string().trim().optional(),
|
||||||
username: z.string(),
|
username: z.string().trim(),
|
||||||
password: z.string(),
|
password: z.string().trim(),
|
||||||
creationStatement: z.string(),
|
creationStatement: z.string().trim(),
|
||||||
revocationStatement: z.string(),
|
revocationStatement: z.string().trim(),
|
||||||
renewStatement: z.string().optional(),
|
renewStatement: z.string().trim().optional(),
|
||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DynamicSecretAwsIamSchema = z.object({
|
||||||
|
accessKey: z.string().trim().min(1),
|
||||||
|
secretAccessKey: z.string().trim().min(1),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
awsPath: z.string().trim().optional(),
|
||||||
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
|
policyDocument: z.string().trim().optional(),
|
||||||
|
userGroups: z.string().trim().optional(),
|
||||||
|
policyArns: z.string().trim().optional()
|
||||||
|
});
|
||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
Cassandra = "cassandra"
|
Cassandra = "cassandra",
|
||||||
|
AwsIam = "aws-iam"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema })
|
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TDynamicProviderFns = {
|
export type TDynamicProviderFns = {
|
||||||
|
|||||||
@@ -566,20 +566,32 @@ export const integrationAuthServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const kms = new AWS.KMS();
|
const kms = new AWS.KMS();
|
||||||
|
|
||||||
const aliases = await kms.listAliases({}).promise();
|
const aliases = await kms.listAliases({}).promise();
|
||||||
const keys = await kms.listKeys({}).promise();
|
|
||||||
const response = keys
|
|
||||||
.Keys!.map((key) => {
|
|
||||||
const keyAlias = aliases.Aliases!.find((alias) => key.KeyId === alias.TargetKeyId);
|
|
||||||
if (!keyAlias?.AliasName?.includes("alias/aws/")) {
|
|
||||||
return { id: String(key.KeyId), alias: String(keyAlias?.AliasName || key.KeyId) };
|
|
||||||
}
|
|
||||||
return { id: "null", alias: "null" };
|
|
||||||
})
|
|
||||||
.filter((elem) => elem.id !== "null");
|
|
||||||
|
|
||||||
return [...response, { id: "null", alias: "default" }];
|
const keyAliases = aliases.Aliases!.filter((alias) => {
|
||||||
|
if (!alias.TargetKeyId) return false;
|
||||||
|
|
||||||
|
if (integrationAuth.integration === Integrations.AWS_PARAMETER_STORE && alias.AliasName === "alias/aws/ssm")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
integrationAuth.integration === Integrations.AWS_SECRET_MANAGER &&
|
||||||
|
alias.AliasName === "alias/aws/secretsmanager"
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (alias.AliasName?.includes("alias/aws/")) return false;
|
||||||
|
return alias.TargetKeyId;
|
||||||
|
});
|
||||||
|
|
||||||
|
const keysWithAliases = keyAliases.map((alias) => {
|
||||||
|
return {
|
||||||
|
id: alias.TargetKeyId!,
|
||||||
|
alias: alias.AliasName!
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return keysWithAliases;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getQoveryProjects = async ({
|
const getQoveryProjects = async ({
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
Name: `${integration.path}${key}`,
|
Name: `${integration.path}${key}`,
|
||||||
Type: "SecureString",
|
Type: "SecureString",
|
||||||
Value: secrets[key].value,
|
Value: secrets[key].value,
|
||||||
KeyId: metadata.kmsKeyId ? metadata.kmsKeyId : undefined,
|
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }),
|
||||||
// Overwrite: true,
|
// Overwrite: true,
|
||||||
Tags: metadata.secretAWSTag
|
Tags: metadata.secretAWSTag
|
||||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
|
||||||
@@ -572,7 +572,6 @@ const syncSecretsAWSSecretManager = async ({
|
|||||||
if (awsSecretManagerSecret?.SecretString) {
|
if (awsSecretManagerSecret?.SecretString) {
|
||||||
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
|
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) {
|
if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) {
|
||||||
await secretsManager.send(
|
await secretsManager.send(
|
||||||
new UpdateSecretCommand({
|
new UpdateSecretCommand({
|
||||||
@@ -587,7 +586,7 @@ const syncSecretsAWSSecretManager = async ({
|
|||||||
new CreateSecretCommand({
|
new CreateSecretCommand({
|
||||||
Name: integration.app as string,
|
Name: integration.app as string,
|
||||||
SecretString: JSON.stringify(secKeyVal),
|
SecretString: JSON.stringify(secKeyVal),
|
||||||
KmsKeyId: metadata.kmsKeyId ? metadata.kmsKeyId : null,
|
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
||||||
Tags: metadata.secretAWSTag
|
Tags: metadata.secretAWSTag
|
||||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
||||||
: []
|
: []
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
title: "AWS IAM"
|
||||||
|
description: "How to dynamically generate AWS IAM Users."
|
||||||
|
---
|
||||||
|
|
||||||
|
The Infisical AWS IAM dynamic secret allows you to generate AWS IAM Users on demand based on configured AWS policy.
|
||||||
|
|
||||||
|
## Prerequisite
|
||||||
|
|
||||||
|
Infisical needs an initial AWS IAM user with the required permissions to create sub IAM users. This IAM user will be responsible for managing the lifecycle of new IAM users.
|
||||||
|
|
||||||
|
<Accordion title="Managing AWS IAM User minimum permission policy">
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"iam:AttachUserPolicy",
|
||||||
|
"iam:CreateAccessKey",
|
||||||
|
"iam:CreateUser",
|
||||||
|
"iam:DeleteAccessKey",
|
||||||
|
"iam:DeleteUser",
|
||||||
|
"iam:DeleteUserPolicy",
|
||||||
|
"iam:DetachUserPolicy",
|
||||||
|
"iam:GetUser",
|
||||||
|
"iam:ListAccessKeys",
|
||||||
|
"iam:ListAttachedUserPolicies",
|
||||||
|
"iam:ListGroupsForUser",
|
||||||
|
"iam:ListUserPolicies",
|
||||||
|
"iam:PutUserPolicy",
|
||||||
|
"iam:AddUserToGroup",
|
||||||
|
"iam:RemoveUserFromGroup"
|
||||||
|
],
|
||||||
|
"Resource": ["*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To minimize managing user access you can attach a resource in format
|
||||||
|
|
||||||
|
> arn:aws:iam::\<account-id\>:user/\<aws-scope-path\>
|
||||||
|
|
||||||
|
Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** with a path to minimize managing user access.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
## Set up Dynamic Secrets with AWS IAM
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Secret Overview Dashboard">
|
||||||
|
Navigate to the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret to.
|
||||||
|
</Step>
|
||||||
|
<Step title="Click on the 'Add Dynamic Secret' button">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select AWS IAM">
|
||||||
|

|
||||||
|
</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="AWS Access Key" type="string" required>
|
||||||
|
The managing AWS IAM User Access Key
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="AWS Secret Key" type="string" required>
|
||||||
|
The managing AWS IAM User Secret Key
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="AWS IAM Path" type="string">
|
||||||
|
[IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="AWS Region" type="string" required>
|
||||||
|
The AWS data center region.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="IAM User Permission Boundary" type="string" required>
|
||||||
|
The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="AWS IAM Groups" type="string">
|
||||||
|
The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="AWS Policy ARNs" type="string">
|
||||||
|
The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="AWS IAM Policy Document" type="string">
|
||||||
|
The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas
|
||||||
|
</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 in step 4.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|
|
||||||
|
Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Audit or Revoke Leases
|
||||||
|
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
|
||||||
|
This will allow you see the lease details and delete the lease ahead of its expiration time.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Renew Leases
|
||||||
|
To extend the life of the generated dynamic secret lease past its initial time to live, simply click on the **Renew** as illustrated below.
|
||||||
|

|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
|
||||||
|
</Warning>
|
||||||
@@ -5,7 +5,7 @@ description: "Learn how secret versioning works in Infisical."
|
|||||||
|
|
||||||
Every time a secret change is persformed, a new version of the same secret is created.
|
Every time a secret change is persformed, a new version of the same secret is created.
|
||||||
|
|
||||||
Such versions can be accessed visually by opening up the [secret sidebar](/documentation/platform/project#drawer) (as seen below) or [retrived via API](/api-reference/endpoints/secrets/read)
|
Such versions can be accessed visually by opening up the [secret sidebar](/documentation/platform/project#drawer) (as seen below) or [retrieved via API](/api-reference/endpoints/secrets/read)
|
||||||
by specifying the `version` query parameter.
|
by specifying the `version` query parameter.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -31,7 +31,9 @@ Prerequisites:
|
|||||||
"secretsmanager:UpdateSecret",
|
"secretsmanager:UpdateSecret",
|
||||||
"secretsmanager:TagResource", // if you need to add tags to secrets
|
"secretsmanager:TagResource", // if you need to add tags to secrets
|
||||||
"kms:ListKeys", // if you need to specify the KMS key
|
"kms:ListKeys", // if you need to specify the KMS key
|
||||||
"kms:ListAliases" // if you need to specify the KMS key
|
"kms:ListAliases", // if you need to specify the KMS key
|
||||||
|
"kms:Encrypt", // if you need to specify the KMS key
|
||||||
|
"kms:Decrypt" // if you need to specify the KMS key
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -146,7 +146,8 @@
|
|||||||
"documentation/platform/dynamic-secrets/postgresql",
|
"documentation/platform/dynamic-secrets/postgresql",
|
||||||
"documentation/platform/dynamic-secrets/mysql",
|
"documentation/platform/dynamic-secrets/mysql",
|
||||||
"documentation/platform/dynamic-secrets/oracle",
|
"documentation/platform/dynamic-secrets/oracle",
|
||||||
"documentation/platform/dynamic-secrets/cassandra"
|
"documentation/platform/dynamic-secrets/cassandra",
|
||||||
|
"documentation/platform/dynamic-secrets/aws-iam"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"documentation/platform/groups"
|
"documentation/platform/groups"
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export type TDynamicSecret = {
|
|||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
Cassandra = "cassandra"
|
Cassandra = "cassandra",
|
||||||
|
AwsIam = "aws-iam"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SqlProviders {
|
export enum SqlProviders {
|
||||||
@@ -56,6 +57,18 @@ export type TDynamicSecretProvider =
|
|||||||
renewStatement?: string;
|
renewStatement?: string;
|
||||||
ca?: string | undefined;
|
ca?: string | undefined;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: DynamicSecretProviders.AwsIam;
|
||||||
|
inputs: {
|
||||||
|
accessKey: string;
|
||||||
|
secretAccessKey: string;
|
||||||
|
region: string;
|
||||||
|
awsPath?: string;
|
||||||
|
policyDocument?: string;
|
||||||
|
userGroups?: string;
|
||||||
|
policyArns?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateDynamicSecretDTO = {
|
export type TCreateDynamicSecretDTO = {
|
||||||
|
|||||||
@@ -48,10 +48,9 @@ const integrationAuthKeys = {
|
|||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
region
|
region
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string,
|
integrationAuthId: string;
|
||||||
region: string
|
region: string;
|
||||||
}) =>
|
}) => [{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const,
|
||||||
[{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const,
|
|
||||||
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
||||||
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
||||||
getIntegrationAuthQoveryProjects: ({
|
getIntegrationAuthQoveryProjects: ({
|
||||||
@@ -226,27 +225,6 @@ const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
|
|||||||
return orgs;
|
return orgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchIntegrationAuthAwsKmsKeys = async ({
|
|
||||||
integrationAuthId,
|
|
||||||
region
|
|
||||||
}: {
|
|
||||||
integrationAuthId: string;
|
|
||||||
region: string;
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
data: { kmsKeys }
|
|
||||||
} = await apiRequest.get<{ kmsKeys: KmsKey[] }>(
|
|
||||||
`/api/v1/integration-auth/${integrationAuthId}/aws-secrets-manager/kms-keys`,
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
region
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return kmsKeys;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchIntegrationAuthQoveryProjects = async ({
|
const fetchIntegrationAuthQoveryProjects = async ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
orgId
|
orgId
|
||||||
@@ -586,11 +564,22 @@ export const useGetIntegrationAuthAwsKmsKeys = ({
|
|||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
region
|
region
|
||||||
}),
|
}),
|
||||||
queryFn: () =>
|
queryFn: async () => {
|
||||||
fetchIntegrationAuthAwsKmsKeys({
|
if (!region) return [];
|
||||||
integrationAuthId,
|
|
||||||
region
|
const {
|
||||||
}),
|
data: { kmsKeys }
|
||||||
|
} = await apiRequest.get<{ kmsKeys: KmsKey[] }>(
|
||||||
|
`/api/v1/integration-auth/${integrationAuthId}/aws-secrets-manager/kms-keys`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return kmsKeys;
|
||||||
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,19 +100,12 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
|
|
||||||
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
|
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
|
||||||
useGetIntegrationAuthAwsKmsKeys({
|
useGetIntegrationAuthAwsKmsKeys({
|
||||||
integrationAuthId: String(integrationAuthId),
|
integrationAuthId: String(integrationAuthId),
|
||||||
region: selectedAWSRegion
|
region: selectedAWSRegion
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (integrationAuthAwsKmsKeys) {
|
|
||||||
setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "default")[0]?.id))
|
|
||||||
}
|
|
||||||
}, [integrationAuthAwsKmsKeys])
|
|
||||||
|
|
||||||
const isValidAWSParameterStorePath = (awsStorePath: string) => {
|
const isValidAWSParameterStorePath = (awsStorePath: string) => {
|
||||||
const pattern = /^\/([\w-]+\/)*[\w-]+\/$/;
|
const pattern = /^\/([\w-]+\/)*[\w-]+\/$/;
|
||||||
return pattern.test(awsStorePath) && awsStorePath.length <= 2048;
|
return pattern.test(awsStorePath) && awsStorePath.length <= 2048;
|
||||||
@@ -143,16 +136,15 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
metadata: {
|
metadata: {
|
||||||
...(shouldTag
|
...(shouldTag
|
||||||
? {
|
? {
|
||||||
secretAWSTag: [{
|
secretAWSTag: [
|
||||||
key: tagKey,
|
{
|
||||||
value: tagValue
|
key: tagKey,
|
||||||
}]
|
value: tagValue
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ?
|
...(kmsKeyId && { kmsKeyId })
|
||||||
{
|
|
||||||
kmsKeyId
|
|
||||||
}: {})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,7 +157,10 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
|
return integrationAuth &&
|
||||||
|
workspace &&
|
||||||
|
selectedSourceEnvironment &&
|
||||||
|
!isIntegrationAuthAwsKmsKeysLoading ? (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Set Up AWS Parameter Integration</title>
|
<title>Set Up AWS Parameter Integration</title>
|
||||||
@@ -241,7 +236,10 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
<FormControl label="AWS Region">
|
<FormControl label="AWS Region">
|
||||||
<Select
|
<Select
|
||||||
value={selectedAWSRegion}
|
value={selectedAWSRegion}
|
||||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
onValueChange={(val) => {
|
||||||
|
setSelectedAWSRegion(val);
|
||||||
|
setKmsKeyId("");
|
||||||
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
{awsRegions.map((awsRegion) => (
|
{awsRegions.map((awsRegion) => (
|
||||||
@@ -285,20 +283,16 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
</div>
|
</div>
|
||||||
{shouldTag && (
|
{shouldTag && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<FormControl
|
<FormControl label="Tag Key">
|
||||||
label="Tag Key"
|
<Input
|
||||||
>
|
placeholder="managed-by"
|
||||||
<Input
|
|
||||||
placeholder="managed-by"
|
|
||||||
value={tagKey}
|
value={tagKey}
|
||||||
onChange={(e) => setTagKey(e.target.value)}
|
onChange={(e) => setTagKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl label="Tag Value">
|
||||||
label="Tag Value"
|
<Input
|
||||||
>
|
placeholder="infisical"
|
||||||
<Input
|
|
||||||
placeholder="infisical"
|
|
||||||
value={tagValue}
|
value={tagValue}
|
||||||
onChange={(e) => setTagValue(e.target.value)}
|
onChange={(e) => setTagValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -309,7 +303,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
<Select
|
<Select
|
||||||
value={kmsKeyId}
|
value={kmsKeyId}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setKmsKeyId(e)
|
setKmsKeyId(e);
|
||||||
}}
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
@@ -362,7 +356,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
<title>Set Up AWS Parameter Store Integration</title>
|
<title>Set Up AWS Parameter Store Integration</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
{(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? (
|
{isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading ? (
|
||||||
<img
|
<img
|
||||||
src="/images/loading/loading.gif"
|
src="/images/loading/loading.gif"
|
||||||
height={70}
|
height={70}
|
||||||
|
|||||||
@@ -96,19 +96,12 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [shouldTag, setShouldTag] = useState(false);
|
const [shouldTag, setShouldTag] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
|
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
|
||||||
useGetIntegrationAuthAwsKmsKeys({
|
useGetIntegrationAuthAwsKmsKeys({
|
||||||
integrationAuthId: String(integrationAuthId),
|
integrationAuthId: String(integrationAuthId),
|
||||||
region: selectedAWSRegion
|
region: selectedAWSRegion
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (integrationAuthAwsKmsKeys) {
|
|
||||||
setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "alias/aws/secretsmanager")[0]?.id))
|
|
||||||
}
|
|
||||||
}, [integrationAuthAwsKmsKeys])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
@@ -142,16 +135,15 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
metadata: {
|
metadata: {
|
||||||
...(shouldTag
|
...(shouldTag
|
||||||
? {
|
? {
|
||||||
secretAWSTag: [{
|
secretAWSTag: [
|
||||||
key: tagKey,
|
{
|
||||||
value: tagValue
|
key: tagKey,
|
||||||
}]
|
value: tagValue
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ?
|
...(kmsKeyId && { kmsKeyId })
|
||||||
{
|
|
||||||
kmsKeyId
|
|
||||||
}: {})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,7 +156,10 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
|
return integrationAuth &&
|
||||||
|
workspace &&
|
||||||
|
selectedSourceEnvironment &&
|
||||||
|
!isIntegrationAuthAwsKmsKeysLoading ? (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Set Up AWS Secrets Manager Integration</title>
|
<title>Set Up AWS Secrets Manager Integration</title>
|
||||||
@@ -240,7 +235,10 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
<FormControl label="AWS Region">
|
<FormControl label="AWS Region">
|
||||||
<Select
|
<Select
|
||||||
value={selectedAWSRegion}
|
value={selectedAWSRegion}
|
||||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
onValueChange={(val) => {
|
||||||
|
setSelectedAWSRegion(val);
|
||||||
|
setKmsKeyId("");
|
||||||
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
{awsRegions.map((awsRegion) => (
|
{awsRegions.map((awsRegion) => (
|
||||||
@@ -284,20 +282,16 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
</div>
|
</div>
|
||||||
{shouldTag && (
|
{shouldTag && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<FormControl
|
<FormControl label="Tag Key">
|
||||||
label="Tag Key"
|
<Input
|
||||||
>
|
placeholder="managed-by"
|
||||||
<Input
|
|
||||||
placeholder="managed-by"
|
|
||||||
value={tagKey}
|
value={tagKey}
|
||||||
onChange={(e) => setTagKey(e.target.value)}
|
onChange={(e) => setTagKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl label="Tag Value">
|
||||||
label="Tag Value"
|
<Input
|
||||||
>
|
placeholder="infisical"
|
||||||
<Input
|
|
||||||
placeholder="infisical"
|
|
||||||
value={tagValue}
|
value={tagValue}
|
||||||
onChange={(e) => setTagValue(e.target.value)}
|
onChange={(e) => setTagValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -308,7 +302,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
<Select
|
<Select
|
||||||
value={kmsKeyId}
|
value={kmsKeyId}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setKmsKeyId(e)
|
setKmsKeyId(e);
|
||||||
}}
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
@@ -361,7 +355,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
<title>Set Up AWS Secrets Manager Integration</title>
|
<title>Set Up AWS Secrets Manager Integration</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
{(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? (
|
{isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading ? (
|
||||||
<img
|
<img
|
||||||
src="/images/loading/loading.gif"
|
src="/images/loading/loading.gif"
|
||||||
height={70}
|
height={70}
|
||||||
|
|||||||
+303
@@ -0,0 +1,303 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import ms from "ms";
|
||||||
|
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 { useCreateDynamicSecret } from "@app/hooks/api";
|
||||||
|
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
provider: z.object({
|
||||||
|
accessKey: z.string().trim().min(1),
|
||||||
|
secretAccessKey: z.string().trim().min(1),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
awsPath: z.string().trim().optional(),
|
||||||
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
|
policyDocument: z.string().trim().optional(),
|
||||||
|
userGroups: z.string().trim().optional(),
|
||||||
|
policyArns: z.string().trim().optional()
|
||||||
|
}),
|
||||||
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
// a day
|
||||||
|
if (valMs > 24 * 60 * 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
maxTTL: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
// a day
|
||||||
|
if (valMs > 24 * 60 * 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
});
|
||||||
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onCompleted: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
secretPath: string;
|
||||||
|
projectSlug: string;
|
||||||
|
environment: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AwsIamInputForm = ({
|
||||||
|
onCompleted,
|
||||||
|
onCancel,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
projectSlug
|
||||||
|
}: Props) => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
handleSubmit
|
||||||
|
} = useForm<TForm>({
|
||||||
|
resolver: zodResolver(formSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const createDynamicSecret = useCreateDynamicSecret();
|
||||||
|
|
||||||
|
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
|
||||||
|
// wait till previous request is finished
|
||||||
|
if (createDynamicSecret.isLoading) return;
|
||||||
|
try {
|
||||||
|
await createDynamicSecret.mutateAsync({
|
||||||
|
provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
|
||||||
|
maxTTL,
|
||||||
|
name,
|
||||||
|
path: secretPath,
|
||||||
|
defaultTTL,
|
||||||
|
projectSlug,
|
||||||
|
environmentSlug: environment
|
||||||
|
});
|
||||||
|
onCompleted();
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to create dynamic secret"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={handleSubmit(handleCreateDynamicSecret)} autoComplete="off">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="flex-grow">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="dynamic-postgres" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-32">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="defaultTTL"
|
||||||
|
defaultValue="1h"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={<TtlFormLabel label="Default TTL" />}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-32">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="maxTTL"
|
||||||
|
defaultValue="24h"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={<TtlFormLabel label="Max TTL" />}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
|
Configuration
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.accessKey"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Access Key"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.secretAccessKey"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Secret Key"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} type="password" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.awsPath"
|
||||||
|
defaultValue="/"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS IAM Path"
|
||||||
|
className="flex-grow"
|
||||||
|
isOptional
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.region"
|
||||||
|
defaultValue="us-east-1"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Region"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.permissionBoundaryPolicyArn"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="IAM User Permission Boundary ARN"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.userGroups"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS IAM Groups"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="Generated users will get attached to given groups."
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="group1,group2" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.policyArns"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Policy ARNs"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="Generated users will get attached to given policy arns."
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.policyDocument"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS IAM Policy Document"
|
||||||
|
isOptional
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="Generated users will have the inline policy."
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
{...field}
|
||||||
|
reSize="none"
|
||||||
|
rows={3}
|
||||||
|
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center space-x-4">
|
||||||
|
<Button type="submit" isLoading={isSubmitting}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline_bg" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+25
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { faAws } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faDatabase } from "@fortawesome/free-solid-svg-icons";
|
import { faDatabase } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
@@ -6,6 +7,7 @@ import { AnimatePresence, motion } from "framer-motion";
|
|||||||
import { Modal, ModalContent } from "@app/components/v2";
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
import { AwsIamInputForm } from "./AwsIamInputForm";
|
||||||
import { CassandraInputForm } from "./CassandraInputForm";
|
import { CassandraInputForm } from "./CassandraInputForm";
|
||||||
import { SqlDatabaseInputForm } from "./SqlDatabaseInputForm";
|
import { SqlDatabaseInputForm } from "./SqlDatabaseInputForm";
|
||||||
|
|
||||||
@@ -32,6 +34,11 @@ const DYNAMIC_SECRET_LIST = [
|
|||||||
icon: faDatabase,
|
icon: faDatabase,
|
||||||
provider: DynamicSecretProviders.Cassandra,
|
provider: DynamicSecretProviders.Cassandra,
|
||||||
title: "Cassandra"
|
title: "Cassandra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: faAws,
|
||||||
|
provider: DynamicSecretProviders.AwsIam,
|
||||||
|
title: "AWS IAM"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -129,6 +136,24 @@ export const CreateDynamicSecretForm = ({
|
|||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
{wizardStep === WizardSteps.ProviderInputs &&
|
||||||
|
selectedProvider === DynamicSecretProviders.AwsIam && (
|
||||||
|
<motion.div
|
||||||
|
key="dynamic-aws-iam-step"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: -30 }}
|
||||||
|
>
|
||||||
|
<AwsIamInputForm
|
||||||
|
onCompleted={handleFormReset}
|
||||||
|
onCancel={handleFormReset}
|
||||||
|
projectSlug={projectSlug}
|
||||||
|
secretPath={secretPath}
|
||||||
|
environment={environment}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
+20
-1
@@ -54,11 +54,11 @@ const OutputDisplay = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderOutputForm = (provider: DynamicSecretProviders, data: unknown) => {
|
const renderOutputForm = (provider: DynamicSecretProviders, data: unknown) => {
|
||||||
const { DB_PASSWORD, DB_USERNAME } = data as { DB_USERNAME: string; DB_PASSWORD: string };
|
|
||||||
if (
|
if (
|
||||||
provider === DynamicSecretProviders.SqlDatabase ||
|
provider === DynamicSecretProviders.SqlDatabase ||
|
||||||
provider === DynamicSecretProviders.Cassandra
|
provider === DynamicSecretProviders.Cassandra
|
||||||
) {
|
) {
|
||||||
|
const { DB_PASSWORD, DB_USERNAME } = data as { DB_USERNAME: string; DB_PASSWORD: string };
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<OutputDisplay label="Database User" value={DB_USERNAME} />
|
<OutputDisplay label="Database User" value={DB_USERNAME} />
|
||||||
@@ -70,6 +70,25 @@ const renderOutputForm = (provider: DynamicSecretProviders, data: unknown) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider === DynamicSecretProviders.AwsIam) {
|
||||||
|
const { USERNAME, ACCESS_KEY, SECRET_ACCESS_KEY } = data as {
|
||||||
|
ACCESS_KEY: string;
|
||||||
|
SECRET_ACCESS_KEY: string;
|
||||||
|
USERNAME: string;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<OutputDisplay label="AWS Username" value={USERNAME} />
|
||||||
|
<OutputDisplay label="AWS IAM Access Key" value={ACCESS_KEY} />
|
||||||
|
<OutputDisplay
|
||||||
|
label="AWS IAM Secret Key"
|
||||||
|
value={SECRET_ACCESS_KEY}
|
||||||
|
helperText="Important: Copy these credentials now. You will not be able to see them again after you close the modal."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+313
@@ -0,0 +1,313 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import ms from "ms";
|
||||||
|
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 { useUpdateDynamicSecret } from "@app/hooks/api";
|
||||||
|
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
inputs: z
|
||||||
|
.object({
|
||||||
|
accessKey: z.string().trim().min(1),
|
||||||
|
secretAccessKey: z.string().trim().min(1),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
awsPath: z.string().trim().optional(),
|
||||||
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
|
policyDocument: z.string().trim().optional(),
|
||||||
|
userGroups: z.string().trim().optional(),
|
||||||
|
policyArns: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
// a day
|
||||||
|
if (valMs > 24 * 60 * 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
maxTTL: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
// a day
|
||||||
|
if (valMs > 24 * 60 * 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
|
newName: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.optional()
|
||||||
|
});
|
||||||
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void;
|
||||||
|
dynamicSecret: TDynamicSecret & { inputs: unknown };
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditDynamicSecretAwsIamForm = ({
|
||||||
|
onClose,
|
||||||
|
dynamicSecret,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
projectSlug
|
||||||
|
}: Props) => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
handleSubmit
|
||||||
|
} = useForm<TForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: {
|
||||||
|
defaultTTL: dynamicSecret.defaultTTL,
|
||||||
|
maxTTL: dynamicSecret.maxTTL,
|
||||||
|
newName: dynamicSecret.name,
|
||||||
|
inputs: {
|
||||||
|
...(dynamicSecret.inputs as TForm["inputs"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateDynamicSecret = useUpdateDynamicSecret();
|
||||||
|
|
||||||
|
const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
|
||||||
|
// wait till previous request is finished
|
||||||
|
if (updateDynamicSecret.isLoading) return;
|
||||||
|
try {
|
||||||
|
await updateDynamicSecret.mutateAsync({
|
||||||
|
name: dynamicSecret.name,
|
||||||
|
path: secretPath,
|
||||||
|
projectSlug,
|
||||||
|
environmentSlug: environment,
|
||||||
|
data: {
|
||||||
|
maxTTL: maxTTL || undefined,
|
||||||
|
defaultTTL,
|
||||||
|
inputs,
|
||||||
|
newName: newName === dynamicSecret.name ? undefined : newName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully updated dynamic secret"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update dynamic secret"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={handleSubmit(handleUpdateDynamicSecret)} autoComplete="off">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="flex-grow">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="newName"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="DYN-1" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-32">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="defaultTTL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={<TtlFormLabel label="Default TTL" />}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-32">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="maxTTL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={<TtlFormLabel label="Max TTL" />}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-4 border-b border-b-mineshaft-600 pb-2">Configuration</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.accessKey"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Access Key"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.secretAccessKey"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Secret Key"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} type="password" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.awsPath"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS IAM Path"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.region"
|
||||||
|
defaultValue="us-east-1"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Region"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.userGroups"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS IAM Groups"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="Generated users will get attached to given groups."
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="group1,group2" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.permissionBoundaryPolicyArn"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="IAM User Permission Boundary ARN"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.policyArns"
|
||||||
|
defaultValue="datacenter1"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS Policy ARNs"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="Generated users will get attached to given policy arns."
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.policyDocument"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="AWS IAM Policy Document"
|
||||||
|
isOptional
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
helperText="Generated users will have the inline policy."
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
{...field}
|
||||||
|
reSize="none"
|
||||||
|
rows={3}
|
||||||
|
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center space-x-4">
|
||||||
|
<Button type="submit" isLoading={isSubmitting}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline_bg" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+18
@@ -4,6 +4,7 @@ import { Spinner } from "@app/components/v2";
|
|||||||
import { useGetDynamicSecretDetails } from "@app/hooks/api";
|
import { useGetDynamicSecretDetails } from "@app/hooks/api";
|
||||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
import { EditDynamicSecretAwsIamForm } from "./EditDynamicSecretAwsIamForm";
|
||||||
import { EditDynamicSecretCassandraForm } from "./EditDynamicSecretCassandraForm";
|
import { EditDynamicSecretCassandraForm } from "./EditDynamicSecretCassandraForm";
|
||||||
import { EditDynamicSecretSqlProviderForm } from "./EditDynamicSecretSqlProviderForm";
|
import { EditDynamicSecretSqlProviderForm } from "./EditDynamicSecretSqlProviderForm";
|
||||||
|
|
||||||
@@ -74,6 +75,23 @@ export const EditDynamicSecretForm = ({
|
|||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
{dynamicSecretDetails?.type === DynamicSecretProviders.AwsIam && (
|
||||||
|
<motion.div
|
||||||
|
key="aws-iam-provider-edit"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: -30 }}
|
||||||
|
>
|
||||||
|
<EditDynamicSecretAwsIamForm
|
||||||
|
onClose={onClose}
|
||||||
|
projectSlug={projectSlug}
|
||||||
|
secretPath={secretPath}
|
||||||
|
dynamicSecret={dynamicSecretDetails}
|
||||||
|
environment={environment}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 1.0.7
|
version: 1.0.8
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ spec:
|
|||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if $infisicalValues.image.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
{{- if $infisicalValues.autoDatabaseSchemaMigration }}
|
{{- if $infisicalValues.autoDatabaseSchemaMigration }}
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: "migration-init"
|
- name: "migration-init"
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ spec:
|
|||||||
app.kubernetes.io/instance: {{ .Release.Name | quote }}
|
app.kubernetes.io/instance: {{ .Release.Name | quote }}
|
||||||
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
spec:
|
spec:
|
||||||
|
{{- if $infisicalValues.image.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
restartPolicy: OnFailure
|
restartPolicy: OnFailure
|
||||||
containers:
|
containers:
|
||||||
- name: infisical-schema-migration
|
- name: infisical-schema-migration
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ infisical:
|
|||||||
repository: infisical/infisical
|
repository: infisical/infisical
|
||||||
tag: "v0.46.3-postgres"
|
tag: "v0.46.3-postgres"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
imagePullSecrets: []
|
||||||
|
|
||||||
affinity: {}
|
affinity: {}
|
||||||
kubeSecretRef: "infisical-secrets"
|
kubeSecretRef: "infisical-secrets"
|
||||||
@@ -29,11 +30,11 @@ infisical:
|
|||||||
cpu: 350m
|
cpu: 350m
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
hostName: ""
|
hostName: ""
|
||||||
ingressClassName: nginx
|
ingressClassName: nginx
|
||||||
nginx:
|
nginx:
|
||||||
enabled: true
|
enabled: false
|
||||||
annotations: {}
|
annotations: {}
|
||||||
tls:
|
tls:
|
||||||
[]
|
[]
|
||||||
|
|||||||
Reference in New Issue
Block a user