-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin' into fix-integration-sync-impor…
…t-priority
- Loading branch information
Showing
26 changed files
with
1,210 additions
and
131 deletions.
There are no files selected for viewing
28 changes: 28 additions & 0 deletions
28
backend/src/db/migrations/20240429154610_audit-log-index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"]); | ||
}); | ||
} | ||
} |
194 changes: 194 additions & 0 deletions
194
backend/src/ee/services/dynamic-secret/providers/aws-iam.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
import { AwsIamProvider } from "./aws-iam"; | ||
import { CassandraProvider } from "./cassandra"; | ||
import { DynamicSecretProviders } from "./models"; | ||
import { SqlDatabaseProvider } from "./sql-database"; | ||
|
||
export const buildDynamicSecretProviders = () => ({ | ||
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider(), | ||
[DynamicSecretProviders.Cassandra]: CassandraProvider() | ||
[DynamicSecretProviders.Cassandra]: CassandraProvider(), | ||
[DynamicSecretProviders.AwsIam]: AwsIamProvider() | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.