diff --git a/src/action.enum.ts b/src/action.enum.ts index 7e0fed5..9dcbcbb 100644 --- a/src/action.enum.ts +++ b/src/action.enum.ts @@ -1,56 +1,216 @@ export enum Action { + // IAM + IamAddClientIDToOpenIDConnectProvider = 'AddClientIDToOpenIDConnectProvider', + IamAddRoleToInstanceProfile = 'AddRoleToInstanceProfile', + IamAddUserToGroup = 'AddUserToGroup', + IamAttachGroupPolicy = 'AttachGroupPolicy', + IamAttachRolePolicy = 'AttachRolePolicy', + IamAttachUserPolicy = 'AttachUserPolicy', + IamChangePassword = 'ChangePassword', + IamCreateAccessKey = 'CreateAccessKey', + IamCreateAccountAlias = 'CreateAccountAlias', + IamCreateGroup = 'CreateGroup', + IamCreateInstanceProfile = 'CreateInstanceProfile', + IamCreateLoginProfile = 'CreateLoginProfile', + IamCreateOpenIDConnectProvider = 'CreateOpenIDConnectProvider', + IamCreatePolicy = 'CreatePolicy', + IamCreatePolicyVersion = 'CreatePolicyVersion', + IamCreateRole = 'CreateRole', + IamCreateSAMLProvider = 'CreateSAMLProvider', + IamCreateServiceLinkedRole = 'CreateServiceLinkedRole', + IamCreateServiceSpecificCredential = 'CreateServiceSpecificCredential', + IamCreateUser = 'CreateUser', + IamCreateVirtualMFADevice = 'CreateVirtualMFADevice', + IamDeactivateMFADevice = 'DeactivateMFADevice', + IamDeleteAccessKey = 'DeleteAccessKey', + IamDeleteAccountAlias = 'DeleteAccountAlias', + IamDeleteAccountPasswordPolicy = 'DeleteAccountPasswordPolicy', + IamDeleteGroup = 'DeleteGroup', + IamDeleteGroupPolicy = 'DeleteGroupPolicy', + IamDeleteInstanceProfile = 'DeleteInstanceProfile', + IamDeleteLoginProfile = 'DeleteLoginProfile', + IamDeleteOpenIDConnectProvider = 'DeleteOpenIDConnectProvider', + IamDeletePolicy = 'DeletePolicy', + IamDeletePolicyVersion = 'DeletePolicyVersion', + IamDeleteRole = 'DeleteRole', + IamDeleteRolePermissionsBoundary = 'DeleteRolePermissionsBoundary', + IamDeleteRolePolicy = 'DeleteRolePolicy', + IamDeleteSAMLProvider = 'DeleteSAMLProvider', + IamDeleteServerCertificate = 'DeleteServerCertificate', + IamDeleteServiceLinkedRole = 'DeleteServiceLinkedRole', + IamDeleteServiceSpecificCredential = 'DeleteServiceSpecificCredential', + IamDeleteSigningCertificate = 'DeleteSigningCertificate', + IamDeleteSSHPublicKey = 'DeleteSSHPublicKey', + IamDeleteUser = 'DeleteUser', + IamDeleteUserPermissionsBoundary = 'DeleteUserPermissionsBoundary', + IamDeleteUserPolicy = 'DeleteUserPolicy', + IamDeleteVirtualMFADevice = 'DeleteVirtualMFADevice', + IamDetachGroupPolicy = 'DetachGroupPolicy', + IamDetachRolePolicy = 'DetachRolePolicy', + IamDetachUserPolicy = 'DetachUserPolicy', + IamEnableMFADevice = 'EnableMFADevice', + IamGenerateCredentialReport = 'GenerateCredentialReport', + IamGenerateOrganizationsAccessReport = 'GenerateOrganizationsAccessReport', + IamGenerateServiceLastAccessedDetails = 'GenerateServiceLastAccessedDetails', + IamGetAccessKeyLastUsed = 'GetAccessKeyLastUsed', + IamGetAccountAuthorizationDetails = 'GetAccountAuthorizationDetails', + IamGetAccountPasswordPolicy = 'GetAccountPasswordPolicy', + IamGetAccountSummary = 'GetAccountSummary', + IamGetContextKeysForCustomPolicy = 'GetContextKeysForCustomPolicy', + IamGetContextKeysForPrincipalPolicy = 'GetContextKeysForPrincipalPolicy', + IamGetCredentialReport = 'GetCredentialReport', + IamGetGroup = 'GetGroup', + IamGetGroupPolicy = 'GetGroupPolicy', + IamGetInstanceProfile = 'GetInstanceProfile', + IamGetLoginProfile = 'GetLoginProfile', + IamGetOpenIDConnectProvider = 'GetOpenIDConnectProvider', + IamGetOrganizationsAccessReport = 'GetOrganizationsAccessReport', + IamGetPolicy = 'GetPolicy', + IamGetPolicyVersion = 'GetPolicyVersion', + IamGetRole = 'GetRole', + IamGetRolePolicy = 'GetRolePolicy', + IamGetSAMLProvider = 'GetSAMLProvider', + IamGetServerCertificate = 'GetServerCertificate', + IamGetServiceLastAccessedDetails = 'GetServiceLastAccessedDetails', + IamGetServiceLastAccessedDetailsWithEntities = 'GetServiceLastAccessedDetailsWithEntities', + IamGetServiceLinkedRoleDeletionStatus = 'GetServiceLinkedRoleDeletionStatus', + IamGetSSHPublicKey = 'GetSSHPublicKey', + IamGetUser = 'GetUser', + IamGetUserPolicy = 'GetUserPolicy', + IamListAccessKeys = 'ListAccessKeys', + IamListAccountAliases = 'ListAccountAliases', + IamListAttachedGroupPolicies = 'ListAttachedGroupPolicies', + IamListAttachedRolePolicies = 'ListAttachedRolePolicies', + IamListAttachedUserPolicies = 'ListAttachedUserPolicies', + IamListEntitiesForPolicy = 'ListEntitiesForPolicy', + IamListGroupPolicies = 'ListGroupPolicies', + IamListGroups = 'ListGroups', + IamListGroupsForUser = 'ListGroupsForUser', + IamListInstanceProfiles = 'ListInstanceProfiles', + IamListInstanceProfilesForRole = 'ListInstanceProfilesForRole', + IamListInstanceProfileTags = 'ListInstanceProfileTags', + IamListMFADevices = 'ListMFADevices', + IamListMFADeviceTags = 'ListMFADeviceTags', + IamListOpenIDConnectProviders = 'ListOpenIDConnectProviders', + IamListOpenIDConnectProviderTags = 'ListOpenIDConnectProviderTags', + IamListPolicies = 'ListPolicies', + IamListPoliciesGrantingServiceAccess = 'ListPoliciesGrantingServiceAccess', + IamListPolicyTags = 'ListPolicyTags', + IamListPolicyVersions = 'ListPolicyVersions', + IamListRolePolicies = 'ListRolePolicies', + IamListRoles = 'ListRoles', + IamListRoleTags = 'ListRoleTags', + IamListSAMLProviders = 'ListSAMLProviders', + IamListSAMLProviderTags = 'ListSAMLProviderTags', + IamListServerCertificates = 'ListServerCertificates', + IamListServerCertificateTags = 'ListServerCertificateTags', + IamListServiceSpecificCredentials = 'ListServiceSpecificCredentials', + IamListSigningCertificates = 'ListSigningCertificates', + IamListSSHPublicKeys = 'ListSSHPublicKeys', + IamListUserPolicies = 'ListUserPolicies', + IamListUsers = 'ListUsers', + IamListUserTags = 'ListUserTags', + IamListVirtualMFADevices = 'ListVirtualMFADevices', + IamPutGroupPolicy = 'PutGroupPolicy', + IamPutRolePermissionsBoundary = 'PutRolePermissionsBoundary', + IamPutRolePolicy = 'PutRolePolicy', + IamPutUserPermissionsBoundary = 'PutUserPermissionsBoundary', + IamPutUserPolicy = 'PutUserPolicy', + IamRemoveClientIDFromOpenIDConnectProvider = 'RemoveClientIDFromOpenIDConnectProvider', + IamRemoveRoleFromInstanceProfile = 'RemoveRoleFromInstanceProfile', + IamRemoveUserFromGroup = 'RemoveUserFromGroup', + IamResetServiceSpecificCredential = 'ResetServiceSpecificCredential', + IamResyncMFADevice = 'ResyncMFADevice', + IamSetDefaultPolicyVersion = 'SetDefaultPolicyVersion', + IamSetSecurityTokenServicePreferences = 'SetSecurityTokenServicePreferences', + IamSimulateCustomPolicy = 'SimulateCustomPolicy', + IamSimulatePrincipalPolicy = 'SimulatePrincipalPolicy', + IamTagInstanceProfile = 'TagInstanceProfile', + IamTagMFADevice = 'TagMFADevice', + IamTagOpenIDConnectProvider = 'TagOpenIDConnectProvider', + IamTagPolicy = 'TagPolicy', + IamTagRole = 'TagRole', + IamTagSAMLProvider = 'TagSAMLProvider', + IamTagServerCertificate = 'TagServerCertificate', + IamTagUser = 'TagUser', + IamUntagInstanceProfile = 'UntagInstanceProfile', + IamUntagMFADevice = 'UntagMFADevice', + IamUntagOpenIDConnectProvider = 'UntagOpenIDConnectProvider', + IamUntagPolicy = 'UntagPolicy', + IamUntagRole = 'UntagRole', + IamUntagSAMLProvider = 'UntagSAMLProvider', + IamUntagServerCertificate = 'UntagServerCertificate', + IamUntagUser = 'UntagUser', + IamUpdateAccessKey = 'UpdateAccessKey', + IamUpdateAccountPasswordPolicy = 'UpdateAccountPasswordPolicy', + IamUpdateAssumeRolePolicy = 'UpdateAssumeRolePolicy', + IamUpdateGroup = 'UpdateGroup', + IamUpdateLoginProfile = 'UpdateLoginProfile', + IamUpdateOpenIDConnectProviderThumbprint = 'UpdateOpenIDConnectProviderThumbprint', + IamUpdateRole = 'UpdateRole', + IamUpdateRoleDescription = 'UpdateRoleDescription', + IamUpdateSAMLProvider = 'UpdateSAMLProvider', + IamUpdateServerCertificate = 'UpdateServerCertificate', + IamUpdateServiceSpecificCredential = 'UpdateServiceSpecificCredential', + IamUpdateSigningCertificate = 'UpdateSigningCertificate', + IamUpdateSSHPublicKey = 'UpdateSSHPublicKey', + IamUpdateUser = 'UpdateUser', + IamUploadServerCertificate = 'UploadServerCertificate', + IamUploadSigningCertificate = 'UploadSigningCertificate', + IamUploadSSHPublicKey = 'UploadSSHPublicKey', + // KMS - KmsCancelKeyDeletion = 'CancelKeyDeletion', - KmsConnectCustomKeyStore = 'ConnectCustomKeyStore', - KmsCreateAlias = 'CreateAlias', - KmsCreateCustomKeyStore = 'CreateCustomKeyStore', - KmsCreateGrant = 'CreateGrant', - KmsCreateKey = 'CreateKey', - KmsDecrypt = 'Decrypt', - KmsDeleteAlias = 'DeleteAlias', - KmsDeleteCustomKeyStore = 'DeleteCustomKeyStore', - KmsDeleteImportedKeyMaterial = 'DeleteImportedKeyMaterial', - KmsDescribeCustomKeyStores = 'DescribeCustomKeyStores', - KmsDescribeKey = 'DescribeKey', - KmsDisableKey = 'DisableKey', - KmsDisableKeyRotation = 'DisableKeyRotation', - KmsDisconnectCustomKeyStore = 'DisconnectCustomKeyStore', - KmsEnableKey = 'EnableKey', - KmsEnableKeyRotation = 'EnableKeyRotation', - KmsEncrypt = 'Encrypt', - KmsGenerateDataKey = 'GenerateDataKey', - KmsGenerateDataKeyPair = 'GenerateDataKeyPair', - KmsGenerateDataKeyPairWithoutPlaintext = 'GenerateDataKeyPairWithoutPlaintext', - KmsGenerateDataKeyWithoutPlaintext = 'GenerateDataKeyWithoutPlaintext', - KmsGenerateMac = 'GenerateMac', - KmsGenerateRandom = 'GenerateRandom', - KmsGetKeyPolicy = 'GetKeyPolicy', - KmsGetKeyRotationStatus = 'GetKeyRotationStatus', - KmsGetParametersForImport = 'GetParametersForImport', - KmsGetPublicKey = 'GetPublicKey', - KmsImportKeyMaterial = 'ImportKeyMaterial', - KmsListAliases = 'ListAliases', - KmsListGrants = 'ListGrants', - KmsListKeyPolicies = 'ListKeyPolicies', - KmsListKeys = 'ListKeys', - KmsListResourceTags = 'ListResourceTags', - KmsListRetirableGrants = 'ListRetirableGrants', - KmsPutKeyPolicy = 'PutKeyPolicy', - KmsReEncrypt = 'ReEncrypt', - KmsReplicateKey = 'ReplicateKey', - KmsRetireGrant = 'RetireGrant', - KmsRevokeGrant = 'RevokeGrant', - KmsScheduleKeyDeletion = 'ScheduleKeyDeletion', - KmsSign = 'Sign', - KmsTagResource = 'TagResource', - KmsUntagResource = 'UntagResource', - KmsUpdateAlias = 'UpdateAlias', - KmsUpdateCustomKeyStore = 'UpdateCustomKeyStore', - KmsUpdateKeyDescription = 'UpdateKeyDescription', - KmsUpdatePrimaryRegion = 'UpdatePrimaryRegion', - KmsVerify = 'Verify', - KmsVerifyMac = 'VerifyMac', + KmsCancelKeyDeletion = 'TrentService.CancelKeyDeletion', + KmsConnectCustomKeyStore = 'TrentService.ConnectCustomKeyStore', + KmsCreateAlias = 'TrentService.CreateAlias', + KmsCreateCustomKeyStore = 'TrentService.CreateCustomKeyStore', + KmsCreateGrant = 'TrentService.CreateGrant', + KmsCreateKey = 'TrentService.CreateKey', + KmsDecrypt = 'TrentService.Decrypt', + KmsDeleteAlias = 'TrentService.DeleteAlias', + KmsDeleteCustomKeyStore = 'TrentService.DeleteCustomKeyStore', + KmsDeleteImportedKeyMaterial = 'TrentService.DeleteImportedKeyMaterial', + KmsDescribeCustomKeyStores = 'TrentService.DescribeCustomKeyStores', + KmsDescribeKey = 'TrentService.DescribeKey', + KmsDisableKey = 'TrentService.DisableKey', + KmsDisableKeyRotation = 'TrentService.DisableKeyRotation', + KmsDisconnectCustomKeyStore = 'TrentService.DisconnectCustomKeyStore', + KmsEnableKey = 'TrentService.EnableKey', + KmsEnableKeyRotation = 'TrentService.EnableKeyRotation', + KmsEncrypt = 'TrentService.Encrypt', + KmsGenerateDataKey = 'TrentService.GenerateDataKey', + KmsGenerateDataKeyPair = 'TrentService.GenerateDataKeyPair', + KmsGenerateDataKeyPairWithoutPlaintext = 'TrentService.GenerateDataKeyPairWithoutPlaintext', + KmsGenerateDataKeyWithoutPlaintext = 'TrentService.GenerateDataKeyWithoutPlaintext', + KmsGenerateMac = 'TrentService.GenerateMac', + KmsGenerateRandom = 'TrentService.GenerateRandom', + KmsGetKeyPolicy = 'TrentService.GetKeyPolicy', + KmsGetKeyRotationStatus = 'TrentService.GetKeyRotationStatus', + KmsGetParametersForImport = 'TrentService.GetParametersForImport', + KmsGetPublicKey = 'TrentService.GetPublicKey', + KmsImportKeyMaterial = 'TrentService.ImportKeyMaterial', + KmsListAliases = 'TrentService.ListAliases', + KmsListGrants = 'TrentService.ListGrants', + KmsListKeyPolicies = 'TrentService.ListKeyPolicies', + KmsListKeys = 'TrentService.ListKeys', + KmsListResourceTags = 'TrentService.ListResourceTags', + KmsListRetirableGrants = 'TrentService.ListRetirableGrants', + KmsPutKeyPolicy = 'TrentService.PutKeyPolicy', + KmsReEncrypt = 'TrentService.ReEncrypt', + KmsReplicateKey = 'TrentService.ReplicateKey', + KmsRetireGrant = 'TrentService.RetireGrant', + KmsRevokeGrant = 'TrentService.RevokeGrant', + KmsScheduleKeyDeletion = 'TrentService.ScheduleKeyDeletion', + KmsSign = 'TrentService.Sign', + KmsTagResource = 'TrentService.TagResource', + KmsUntagResource = 'TrentService.UntagResource', + KmsUpdateAlias = 'TrentService.UpdateAlias', + KmsUpdateCustomKeyStore = 'TrentService.UpdateCustomKeyStore', + KmsUpdateKeyDescription = 'TrentService.UpdateKeyDescription', + KmsUpdatePrimaryRegion = 'TrentService.UpdatePrimaryRegion', + KmsVerify = 'TrentService.Verify', + KmsVerifyMac = 'TrentService.VerifyMac', // SecretsManager SecretsManagerCancelRotateSecret = 'secretsmanager.CancelRotateSecret', diff --git a/src/app.controller.ts b/src/app.controller.ts index 27486c9..fe35622 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -33,6 +33,7 @@ export class AppController { }, {}) const queryParams = { __path: request.path, ...body, ...lowerCasedHeaders }; + console.log({ queryParams }) const actionKey = queryParams['x-amz-target'] ? 'x-amz-target' : 'Action'; const { error: actionError } = Joi.object({ [actionKey]: Joi.string().valid(...Object.values(Action)).required(), diff --git a/src/app.module.ts b/src/app.module.ts index 2eceeaa..6afed0f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -17,6 +17,8 @@ import { AuditInterceptor } from './audit/audit.interceptor'; import { KmsModule } from './kms/kms.module'; import { KMSHandlers } from './kms/kms.constants'; import { configValidator } from './config/config.validator'; +import { IamModule } from './iam/iam.module'; +import { IAMHandlers } from './iam/iam.constants'; @Module({ imports: [ @@ -35,6 +37,7 @@ import { configValidator } from './config/config.validator'; }), }), TypeOrmModule.forFeature([Audit]), + IamModule, KmsModule, SecretsManagerModule, SnsModule, @@ -54,6 +57,7 @@ import { configValidator } from './config/config.validator'; SqsHandlers, SecretsManagerHandlers, KMSHandlers, + IAMHandlers, ], }, ], diff --git a/src/iam/attach-role-policy.handler.ts b/src/iam/attach-role-policy.handler.ts new file mode 100644 index 0000000..3962eef --- /dev/null +++ b/src/iam/attach-role-policy.handler.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import * as uuid from 'uuid'; +import { IamPolicy } from './iam-policy.entity'; +import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity'; +import { IamRole } from './iam-role.entity'; + +type QueryParams = { + PolicyArn: string; + RoleName: string; +} + +@Injectable() +export class AttachRolePolicyHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamRole) + private readonly roleRepo: Repository, + @InjectRepository(IamRolePolicyAttachment) + private readonly attachRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamAttachRolePolicy; + validator = Joi.object({ + PolicyArn: Joi.string().required(), + RoleName: Joi.string().required(), + }); + + protected async handle({ PolicyArn, RoleName }: QueryParams, awsProperties: AwsProperties) { + + const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId} }); + + await this.attachRepo.create({ + id: uuid.v4(), + policyArn: PolicyArn, + roleId: role.id, + accountId: awsProperties.accountId, + }).save(); + } +} diff --git a/src/iam/create-policy-version.handler.ts b/src/iam/create-policy-version.handler.ts new file mode 100644 index 0000000..51de055 --- /dev/null +++ b/src/iam/create-policy-version.handler.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import * as uuid from 'uuid'; +import { IamPolicy } from './iam-policy.entity'; +import { breakdownArn } from '../util/breakdown-arn'; + +type QueryParams = { + PolicyArn: string; + PolicyDocument: string; + SetAsDefault: boolean; +} + +@Injectable() +export class CreatePolicyVersionHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamPolicy) + private readonly policyRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamCreatePolicyVersion; + validator = Joi.object({ + PolicyArn: Joi.string().required(), + PolicyDocument: Joi.string().required(), + SetAsDefault: Joi.boolean().required(), + }); + + protected async handle({ PolicyArn, PolicyDocument, SetAsDefault }: QueryParams, awsProperties: AwsProperties) { + + const { identifier, accountId } = breakdownArn(PolicyArn); + const [_policy, name] = identifier.split('/'); + const currentPolicy = await this.policyRepo.findOne({ where: { accountId, name, isDefault: true } }); + + if (SetAsDefault) { + await this.policyRepo.update({ accountId, name }, { isDefault: false }) + } + + const policy = await this.policyRepo.create({ + id: uuid.v4(), + name: name, + isDefault: SetAsDefault, + version: currentPolicy.version + 1, + document: PolicyDocument, + accountId: awsProperties.accountId, + }).save(); + + return { + PolicyVersion: { + IsDefaultVersion: policy.isDefault, + VersionId: `v${policy.version}`, + CreateDate: new Date(policy.createdAt).toISOString(), + } + } + } +} diff --git a/src/iam/create-policy.handler.ts b/src/iam/create-policy.handler.ts new file mode 100644 index 0000000..66462ff --- /dev/null +++ b/src/iam/create-policy.handler.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import * as uuid from 'uuid'; +import { IamPolicy } from './iam-policy.entity'; + +type QueryParams = { + PolicyName: string; + PolicyDocument: string; +} + +@Injectable() +export class CreatePolicyHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamPolicy) + private readonly policyRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamCreatePolicy; + validator = Joi.object({ + PolicyName: Joi.string().required(), + PolicyDocument: Joi.string().required(), + }); + + protected async handle({ PolicyName, PolicyDocument }: QueryParams, awsProperties: AwsProperties) { + + const policy = await this.policyRepo.create({ + id: uuid.v4(), + name: PolicyName, + document: PolicyDocument, + accountId: awsProperties.accountId, + }).save(); + + return { + Policy: { + PolicyName: policy.name, + DefaultVersionId: 'v1', + PolicyId: policy.id, + Path: '/', + Arn: policy.arn, + AttachmentCount: 0, + CreateDate: new Date(policy.createdAt).toISOString(), + UpdateDate: new Date(policy.updatedAt).toISOString(), + } + } + } +} diff --git a/src/iam/create-role.handler.ts b/src/iam/create-role.handler.ts new file mode 100644 index 0000000..86ff38f --- /dev/null +++ b/src/iam/create-role.handler.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { IamRole } from './iam-role.entity'; +import * as uuid from 'uuid'; +import { IamPolicy } from './iam-policy.entity'; + +type QueryParams = { + RoleName: string; + Path: string; + AssumeRolePolicyDocument: string; +} + +@Injectable() +export class CreateRoleHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamRole) + private readonly roleRepo: Repository, + @InjectRepository(IamPolicy) + private readonly policyRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamCreateRole; + validator = Joi.object({ + RoleName: Joi.string().required(), + Path: Joi.string().required(), + AssumeRolePolicyDocument: Joi.string().required(), + }); + + protected async handle({ RoleName, Path, AssumeRolePolicyDocument }: QueryParams, awsProperties: AwsProperties) { + + const policy = await this.policyRepo.create({ + id: uuid.v4(), + name: `${RoleName}-AssumeRolePolicyDocument`, + document: AssumeRolePolicyDocument, + accountId: awsProperties.accountId, + }).save(); + + const id = uuid.v4(); + + await this.roleRepo.create({ + id, + roleName: RoleName, + path: Path, + accountId: awsProperties.accountId, + assumeRolePolicyDocumentId: policy.id, + }).save(); + + const role = await this.roleRepo.findOne({ where: { id }}); + + return { + Role: role.metadata, + } + } +} diff --git a/src/iam/get-policy.handler.ts b/src/iam/get-policy.handler.ts new file mode 100644 index 0000000..208d679 --- /dev/null +++ b/src/iam/get-policy.handler.ts @@ -0,0 +1,58 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { IamPolicy } from './iam-policy.entity'; +import { breakdownArn } from '../util/breakdown-arn'; +import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity'; + +type QueryParams = { + PolicyArn: string; +} + +@Injectable() +export class GetPolicyHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamPolicy) + private readonly policyRepo: Repository, + @InjectRepository(IamRolePolicyAttachment) + private readonly attachmentRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamGetPolicy; + validator = Joi.object({ + PolicyArn: Joi.string().required(), + }); + + protected async handle({ PolicyArn }: QueryParams, awsProperties: AwsProperties) { + + const { identifier, accountId } = breakdownArn(PolicyArn); + const [_policy, name] = identifier.split('/'); + const policy = await this.policyRepo.findOne({ where: { name, accountId, isDefault: true }}); + + if (!policy) { + throw new NotFoundException('NoSuchEntity', 'The request was rejected because it referenced a resource entity that does not exist. The error message describes the resource.'); + } + + const attachmentCount = await this.attachmentRepo.count({ where: { policyArn: policy.arn } }); + + return { + Policy: { + PolicyName: policy.name, + DefaultVersionId: `v${policy.version}`, + PolicyId: policy.id, + Path: '/', + Arn: policy.arn, + AttachmentCount: attachmentCount, + CreateDate: new Date(policy.createdAt).toISOString(), + UpdateDate: new Date(policy.updatedAt).toISOString(), + } + } + } +} diff --git a/src/iam/get-role.handler.ts b/src/iam/get-role.handler.ts new file mode 100644 index 0000000..e132be4 --- /dev/null +++ b/src/iam/get-role.handler.ts @@ -0,0 +1,41 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { IamRole } from './iam-role.entity'; + +type QueryParams = { + RoleName: string; +} + +@Injectable() +export class GetRoleHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamRole) + private readonly roleRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamGetRole; + validator = Joi.object({ + RoleName: Joi.string().required(), + }); + + protected async handle({ RoleName }: QueryParams, awsProperties: AwsProperties) { + + const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId } }); + + if (!role) { + throw new NotFoundException('NoSuchEntity', 'The request was rejected because it referenced a resource entity that does not exist. The error message describes the resource.'); + } + + return { + Role: role.metadata, + } + } +} diff --git a/src/iam/iam-policy.entity.ts b/src/iam/iam-policy.entity.ts new file mode 100644 index 0000000..ce74774 --- /dev/null +++ b/src/iam/iam-policy.entity.ts @@ -0,0 +1,38 @@ +import { BaseEntity, Column, CreateDateColumn, Entity, JoinColumn, OneToMany, OneToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; +import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity'; +import { IamRole } from './iam-role.entity'; + +@Entity({ name: 'iam_policy' }) +export class IamPolicy extends BaseEntity { + + @PrimaryColumn() + id: string; + + @Column({ default: 1 }) + version: number; + + @Column({ name: 'is_default', default: true }) + isDefault: boolean; + + @Column() + name: string; + + @Column() + document: string; + + @Column({ name: 'account_id', nullable: false }) + accountId: string; + + @CreateDateColumn() + createdAt: string; + + @UpdateDateColumn() + updatedAt: string; + + @OneToOne(() => IamRole, role => role.assumeRolePolicyDocument) + iamRole: IamRole; + + get arn() { + return `arn:aws:iam::${this.accountId}:policy/${this.name}`; + } +} diff --git a/src/iam/iam-role-policy-attachment.entity.ts b/src/iam/iam-role-policy-attachment.entity.ts new file mode 100644 index 0000000..24d500c --- /dev/null +++ b/src/iam/iam-role-policy-attachment.entity.ts @@ -0,0 +1,18 @@ +import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { IamPolicy } from './iam-policy.entity'; + +@Entity({ name: 'iam_role_policy_attachment' }) +export class IamRolePolicyAttachment extends BaseEntity { + + @PrimaryColumn() + id: string; + + @Column({ name: 'policy_arn' }) + policyArn: string; + + @Column({ name: 'role_name' }) + roleId: string; + + @Column({ name: 'account_id'}) + accountId: string; +} diff --git a/src/iam/iam-role.entity.ts b/src/iam/iam-role.entity.ts new file mode 100644 index 0000000..27eee9e --- /dev/null +++ b/src/iam/iam-role.entity.ts @@ -0,0 +1,48 @@ +import { BaseEntity, Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; +import { IamPolicy } from './iam-policy.entity'; + +@Entity({ name: 'iam_role' }) +export class IamRole extends BaseEntity { + + @PrimaryColumn() + id: string + + @Column({ name: 'role_name' }) + roleName: string; + + @Column() + path: string; + + @Column({ name: 'assume_role_policy_document_id', nullable: false }) + assumeRolePolicyDocumentId: string; + + @Column({ name: 'account_id', nullable: false }) + accountId: string; + + @CreateDateColumn() + createdAt: string; + + @UpdateDateColumn() + updatedAt: string; + + @OneToOne(() => IamPolicy, (policy) => policy.id, { eager: true }) + @JoinColumn({ name: 'assume_role_policy_document_id' }) + assumeRolePolicyDocument: IamPolicy; + + get arn() { + const identifier = this.path.split('/'); + identifier.push(this.roleName); + return `arn:aws:iam::${this.accountId}:role/${identifier.join('/')}`; + } + + get metadata() { + return { + Path: this.path, + Arn: this.arn, + RoleName: this.roleName, + AssumeRolePolicyDocument: this.assumeRolePolicyDocument.document, + CreateDate: new Date(this.createdAt).toISOString(), + RoleId: this.id, + } + } +} diff --git a/src/iam/iam.constants.ts b/src/iam/iam.constants.ts new file mode 100644 index 0000000..9cc27d3 --- /dev/null +++ b/src/iam/iam.constants.ts @@ -0,0 +1,5 @@ +import { AbstractActionHandler } from '../abstract-action.handler'; +import { Action } from '../action.enum'; + +export type IAMHandlers = Record; +export const IAMHandlers = Symbol.for('IAMHandlers'); diff --git a/src/iam/iam.module.ts b/src/iam/iam.module.ts new file mode 100644 index 0000000..48e7026 --- /dev/null +++ b/src/iam/iam.module.ts @@ -0,0 +1,205 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module'; +import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider'; +import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider'; +import { AttachRolePolicyHandler } from './attach-role-policy.handler'; +import { CreatePolicyVersionHandler } from './create-policy-version.handler'; +import { CreatePolicyHandler } from './create-policy.handler'; +import { CreateRoleHandler } from './create-role.handler'; +import { GetPolicyHandler } from './get-policy.handler'; +import { GetRoleHandler } from './get-role.handler'; +import { IamPolicy } from './iam-policy.entity'; +import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity'; +import { IamRole } from './iam-role.entity'; +import { IAMHandlers } from './iam.constants'; +import { ListAttachedRolePoliciesHandler } from './list-attached-role-policies'; +import { ListRolePoliciesHandler } from './list-role-policies.handler'; + +const handlers = [ + AttachRolePolicyHandler, + CreatePolicyHandler, + CreatePolicyVersionHandler, + CreateRoleHandler, + GetPolicyHandler, + GetRoleHandler, + ListAttachedRolePoliciesHandler, + ListRolePoliciesHandler, +] + +const actions = [ + Action.IamAddClientIDToOpenIDConnectProvider, + Action.IamAddRoleToInstanceProfile, + Action.IamAddUserToGroup, + Action.IamAttachGroupPolicy, + Action.IamAttachRolePolicy, + Action.IamAttachUserPolicy, + Action.IamChangePassword, + Action.IamCreateAccessKey, + Action.IamCreateAccountAlias, + Action.IamCreateGroup, + Action.IamCreateInstanceProfile, + Action.IamCreateLoginProfile, + Action.IamCreateOpenIDConnectProvider, + Action.IamCreatePolicy, + Action.IamCreatePolicyVersion, + Action.IamCreateRole, + Action.IamCreateSAMLProvider, + Action.IamCreateServiceLinkedRole, + Action.IamCreateServiceSpecificCredential, + Action.IamCreateUser, + Action.IamCreateVirtualMFADevice, + Action.IamDeactivateMFADevice, + Action.IamDeleteAccessKey, + Action.IamDeleteAccountAlias, + Action.IamDeleteAccountPasswordPolicy, + Action.IamDeleteGroup, + Action.IamDeleteGroupPolicy, + Action.IamDeleteInstanceProfile, + Action.IamDeleteLoginProfile, + Action.IamDeleteOpenIDConnectProvider, + Action.IamDeletePolicy, + Action.IamDeletePolicyVersion, + Action.IamDeleteRole, + Action.IamDeleteRolePermissionsBoundary, + Action.IamDeleteRolePolicy, + Action.IamDeleteSAMLProvider, + Action.IamDeleteServerCertificate, + Action.IamDeleteServiceLinkedRole, + Action.IamDeleteServiceSpecificCredential, + Action.IamDeleteSigningCertificate, + Action.IamDeleteSSHPublicKey, + Action.IamDeleteUser, + Action.IamDeleteUserPermissionsBoundary, + Action.IamDeleteUserPolicy, + Action.IamDeleteVirtualMFADevice, + Action.IamDetachGroupPolicy, + Action.IamDetachRolePolicy, + Action.IamDetachUserPolicy, + Action.IamEnableMFADevice, + Action.IamGenerateCredentialReport, + Action.IamGenerateOrganizationsAccessReport, + Action.IamGenerateServiceLastAccessedDetails, + Action.IamGetAccessKeyLastUsed, + Action.IamGetAccountAuthorizationDetails, + Action.IamGetAccountPasswordPolicy, + Action.IamGetAccountSummary, + Action.IamGetContextKeysForCustomPolicy, + Action.IamGetContextKeysForPrincipalPolicy, + Action.IamGetCredentialReport, + Action.IamGetGroup, + Action.IamGetGroupPolicy, + Action.IamGetInstanceProfile, + Action.IamGetLoginProfile, + Action.IamGetOpenIDConnectProvider, + Action.IamGetOrganizationsAccessReport, + Action.IamGetPolicy, + Action.IamGetPolicyVersion, + Action.IamGetRole, + Action.IamGetRolePolicy, + Action.IamGetSAMLProvider, + Action.IamGetServerCertificate, + Action.IamGetServiceLastAccessedDetails, + Action.IamGetServiceLastAccessedDetailsWithEntities, + Action.IamGetServiceLinkedRoleDeletionStatus, + Action.IamGetSSHPublicKey, + Action.IamGetUser, + Action.IamGetUserPolicy, + Action.IamListAccessKeys, + Action.IamListAccountAliases, + Action.IamListAttachedGroupPolicies, + Action.IamListAttachedRolePolicies, + Action.IamListAttachedUserPolicies, + Action.IamListEntitiesForPolicy, + Action.IamListGroupPolicies, + Action.IamListGroups, + Action.IamListGroupsForUser, + Action.IamListInstanceProfiles, + Action.IamListInstanceProfilesForRole, + Action.IamListInstanceProfileTags, + Action.IamListMFADevices, + Action.IamListMFADeviceTags, + Action.IamListOpenIDConnectProviders, + Action.IamListOpenIDConnectProviderTags, + Action.IamListPolicies, + Action.IamListPoliciesGrantingServiceAccess, + Action.IamListPolicyTags, + Action.IamListPolicyVersions, + Action.IamListRolePolicies, + Action.IamListRoles, + Action.IamListRoleTags, + Action.IamListSAMLProviders, + Action.IamListSAMLProviderTags, + Action.IamListServerCertificates, + Action.IamListServerCertificateTags, + Action.IamListServiceSpecificCredentials, + Action.IamListSigningCertificates, + Action.IamListSSHPublicKeys, + Action.IamListUserPolicies, + Action.IamListUsers, + Action.IamListUserTags, + Action.IamListVirtualMFADevices, + Action.IamPutGroupPolicy, + Action.IamPutRolePermissionsBoundary, + Action.IamPutRolePolicy, + Action.IamPutUserPermissionsBoundary, + Action.IamPutUserPolicy, + Action.IamRemoveClientIDFromOpenIDConnectProvider, + Action.IamRemoveRoleFromInstanceProfile, + Action.IamRemoveUserFromGroup, + Action.IamResetServiceSpecificCredential, + Action.IamResyncMFADevice, + Action.IamSetDefaultPolicyVersion, + Action.IamSetSecurityTokenServicePreferences, + Action.IamSimulateCustomPolicy, + Action.IamSimulatePrincipalPolicy, + Action.IamTagInstanceProfile, + Action.IamTagMFADevice, + Action.IamTagOpenIDConnectProvider, + Action.IamTagPolicy, + Action.IamTagRole, + Action.IamTagSAMLProvider, + Action.IamTagServerCertificate, + Action.IamTagUser, + Action.IamUntagInstanceProfile, + Action.IamUntagMFADevice, + Action.IamUntagOpenIDConnectProvider, + Action.IamUntagPolicy, + Action.IamUntagRole, + Action.IamUntagSAMLProvider, + Action.IamUntagServerCertificate, + Action.IamUntagUser, + Action.IamUpdateAccessKey, + Action.IamUpdateAccountPasswordPolicy, + Action.IamUpdateAssumeRolePolicy, + Action.IamUpdateGroup, + Action.IamUpdateLoginProfile, + Action.IamUpdateOpenIDConnectProviderThumbprint, + Action.IamUpdateRole, + Action.IamUpdateRoleDescription, + Action.IamUpdateSAMLProvider, + Action.IamUpdateServerCertificate, + Action.IamUpdateServiceSpecificCredential, + Action.IamUpdateSigningCertificate, + Action.IamUpdateSSHPublicKey, + Action.IamUpdateUser, + Action.IamUploadServerCertificate, + Action.IamUploadSigningCertificate, + Action.IamUploadSSHPublicKey, +] + +@Module({ + imports: [ + TypeOrmModule.forFeature([IamPolicy, IamRole, IamRolePolicyAttachment]), + AwsSharedEntitiesModule, + ], + providers: [ + ...handlers, + ExistingActionHandlersProvider(handlers), + DefaultActionHandlerProvider(IAMHandlers, Format.Xml, actions), + ], + exports: [IAMHandlers], +}) +export class IamModule {} diff --git a/src/iam/list-attached-role-policies.ts b/src/iam/list-attached-role-policies.ts new file mode 100644 index 0000000..cd4b5e3 --- /dev/null +++ b/src/iam/list-attached-role-policies.ts @@ -0,0 +1,57 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; +import { IamRole } from './iam-role.entity'; +import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity'; +import { IamPolicy } from './iam-policy.entity'; +import { breakdownArn } from '../util/breakdown-arn'; + +type QueryParams = { + RoleName: string; +} + +@Injectable() +export class ListAttachedRolePoliciesHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamRole) + private readonly roleRepo: Repository, + @InjectRepository(IamPolicy) + private readonly policyRepo: Repository, + @InjectRepository(IamRolePolicyAttachment) + private readonly attachmentRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamListAttachedRolePolicies; + validator = Joi.object({ + RoleName: Joi.string().required(), + }); + + protected async handle({ RoleName }: QueryParams, awsProperties: AwsProperties) { + + const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId } }); + + if (!role) { + throw new NotFoundException('NoSuchEntity', 'The request was rejected because it referenced a resource entity that does not exist. The error message describes the resource.'); + } + + const attachments = await this.attachmentRepo.find({ where: { roleId: role.id } }) + const policyIds = attachments.map(({ policyArn }) => breakdownArn(policyArn)).map(({ identifier }) => identifier.split('/')[1]); + const policies = await this.policyRepo.find({ where: { name: In(policyIds), isDefault: true } }); + + return { + AttachedPolicies: { + member: [role.assumeRolePolicyDocument, ...policies].map(p => ({ + PolicyName: p.name, + PolicyArn: p.arn, + })), + } + } + } +} diff --git a/src/iam/list-role-policies.handler.ts b/src/iam/list-role-policies.handler.ts new file mode 100644 index 0000000..9730cf0 --- /dev/null +++ b/src/iam/list-role-policies.handler.ts @@ -0,0 +1,44 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { IamRole } from './iam-role.entity'; +import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity'; + +type QueryParams = { + RoleName: string; +} + +@Injectable() +export class ListRolePoliciesHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(IamRole) + private readonly roleRepo: Repository, + @InjectRepository(IamRolePolicyAttachment) + private readonly attachmentRepo: Repository, + ) { + super(); + } + + format = Format.Xml; + action = Action.IamListRolePolicies; + validator = Joi.object({ + RoleName: Joi.string().required(), + }); + + protected async handle({ RoleName }: QueryParams, awsProperties: AwsProperties) { + + const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId } }); + + if (!role) { + throw new NotFoundException('NoSuchEntity', 'The request was rejected because it referenced a resource entity that does not exist. The error message describes the resource.'); + } + + return { + PolicyNames: [], + } + } +} diff --git a/src/kms/create-alias.handler.ts b/src/kms/create-alias.handler.ts new file mode 100644 index 0000000..2a13a5c --- /dev/null +++ b/src/kms/create-alias.handler.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { KmsKeyAlias } from './kms-key-alias.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +type QueryParams = { + AliasName: string; + TargetKeyId: string; +} + +@Injectable() +export class CreateAliasHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(KmsKeyAlias) + private readonly aliasRepo: Repository, + ) { + super(); + } + + format = Format.Json; + action = Action.KmsCreateAlias; + validator = Joi.object({ + AliasName: Joi.string().required(), + TargetKeyId: Joi.string().required(), + }); + + protected async handle({ AliasName, TargetKeyId }: QueryParams, awsProperties: AwsProperties) { + + await this.aliasRepo.save({ + name: AliasName.split('/')[1], + targetKeyId: TargetKeyId, + accountId: awsProperties.accountId, + region: awsProperties.region, + }); + } +} diff --git a/src/kms/describe-key.handler.ts b/src/kms/describe-key.handler.ts new file mode 100644 index 0000000..2445516 --- /dev/null +++ b/src/kms/describe-key.handler.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@nestjs/common'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { KmsKeyAlias } from './kms-key-alias.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { KmsKey } from './kms-key.entity'; +import { ArnParts, breakdownArn } from '../util/breakdown-arn'; + +type QueryParams = { + KeyId: string; +} + +@Injectable() +export class DescribeKeyHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(KmsKeyAlias) + private readonly aliasRepo: Repository, + @InjectRepository(KmsKey) + private readonly keyRepo: Repository, + ) { + super(); + } + + format = Format.Json; + action = Action.KmsDescribeKey; + validator = Joi.object({ + KeyId: Joi.string().required(), + }); + + protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) { + + const searchable = KeyId.startsWith('arn') ? breakdownArn(KeyId) : { + service: 'kms', + region: awsProperties.region, + accountId: awsProperties.accountId, + identifier: KeyId, + }; + const [ type, pk ] = searchable.identifier.split('/'); + const keyId: Promise = type === 'key' ? + Promise.resolve(pk) : + this.findKeyIdFromAlias(pk, searchable); + + + const keyRecord = await this.keyRepo.findOne({ where: { + id: await keyId, + region: searchable.region, + accountId: searchable.accountId, + }}); + + return { + KeyMetadata: keyRecord.metadata, + } + } + + private async findKeyIdFromAlias(alias: string ,arn: ArnParts): Promise { + const record = await this.aliasRepo.findOne({ where: { + name: alias, + accountId: arn.accountId, + region: arn.region, + }}); + return record.targetKeyId; + } +} diff --git a/src/kms/kms-key-alias.entity.ts b/src/kms/kms-key-alias.entity.ts new file mode 100644 index 0000000..1862ae1 --- /dev/null +++ b/src/kms/kms-key-alias.entity.ts @@ -0,0 +1,21 @@ +import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity({ name: 'kms_key_alias' }) +export class KmsKeyAlias extends BaseEntity { + + @PrimaryColumn() + name: string; + + @Column({ name: 'target_key_id' }) + targetKeyId: string; + + @Column({ name: 'account_id', nullable: false }) + accountId: string; + + @Column({ name: 'region', nullable: false }) + region: string; + + get arn() { + return `arn:aws:kms:${this.region}:${this.accountId}:alias/${this.name}`; + } +} diff --git a/src/kms/kms-key.entity.ts b/src/kms/kms-key.entity.ts new file mode 100644 index 0000000..834adf0 --- /dev/null +++ b/src/kms/kms-key.entity.ts @@ -0,0 +1,58 @@ +import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm'; + +@Entity({ name: 'kms_key'}) +export class KmsKey extends BaseEntity { + + @PrimaryColumn() + id: string; + + @Column({ name: 'usage' }) + usage: string; + + @Column({ name: 'description' }) + description: string; + + @Column({ name: 'key_spec' }) + keySpec: string; + + @Column({ name: 'key' }) + key: string; + + @Column({ name: 'account_id', nullable: false }) + accountId: string; + + @Column({ name: 'region', nullable: false }) + region: string; + + @CreateDateColumn() + createdAt: string; + + get arn() { + return `arn:aws:kms:${this.region}:${this.accountId}:key/${this.id}`; + } + + get metadata() { + return { + AWSAccountId: this.accountId, + KeyId: this.id, + Arn: this.arn, + CreationDate: new Date(this.createdAt).toISOString(), + Enabled: true, + Description: this.description, + KeyUsage: this.usage, + KeyState: 'Enabled', + KeyManager: "CUSTOMER", + CustomerMasterKeySpec: this.keySpec, + KeySpec: this.keySpec, + DeletionDate: null, + SigningAlgorithms: [ + "RSASSA_PSS_SHA_256", + "RSASSA_PSS_SHA_384", + "RSASSA_PSS_SHA_512", + "RSASSA_PKCS1_V1_5_SHA_256", + "RSASSA_PKCS1_V1_5_SHA_384", + "RSASSA_PKCS1_V1_5_SHA_512" + ] + } + } +} diff --git a/src/kms/kms.module.ts b/src/kms/kms.module.ts index d8af731..e52fe3c 100644 --- a/src/kms/kms.module.ts +++ b/src/kms/kms.module.ts @@ -5,10 +5,15 @@ import { Action } from '../action.enum'; import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module'; import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider'; import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider'; +import { CreateAliasHandler } from './create-alias.handler'; +import { DescribeKeyHandler } from './describe-key.handler'; +import { KmsKeyAlias } from './kms-key-alias.entity'; +import { KmsKey } from './kms-key.entity'; import { KMSHandlers } from './kms.constants'; const handlers = [ - + CreateAliasHandler, + DescribeKeyHandler, ] const actions = [ @@ -66,7 +71,7 @@ const actions = [ @Module({ imports: [ - TypeOrmModule.forFeature([]), + TypeOrmModule.forFeature([KmsKey, KmsKeyAlias]), AwsSharedEntitiesModule, ], providers: [ diff --git a/src/util/breakdown-arn.ts b/src/util/breakdown-arn.ts new file mode 100644 index 0000000..0c92e0b --- /dev/null +++ b/src/util/breakdown-arn.ts @@ -0,0 +1,21 @@ +export type ArnParts = { + service: string; + region: string; + accountId: string; + identifier: string; +} + +export const breakdownArn = (arn: string): ArnParts => { + if (!arn.startsWith('arn')) { + throw new Error('Invalid arn'); + } + + const [_arn, _aws, service, region, accountId, ...identifierData] = arn.split(':'); + + return { + service, + region, + accountId, + identifier: identifierData.join(':'), + } +}