-
Notifications
You must be signed in to change notification settings - Fork 99
/
share_encrypted_ami.js
163 lines (149 loc) · 5.84 KB
/
share_encrypted_ami.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* Shares AMI with another AWS account.
* Supports AMI encrypted with default KMS key
*
* Usage for sharing AMI encrypted with Default Key:
* AWS_PROFILE=<your-aws-profile> node share_encrypted_ami.js -r <source-region> -a <ami-id> -A <accountId-to-share> -k <KMS Customer managed key>
* Usage for sharing AMI encrypted with Default Key to different region:
* AWS_PROFILE=<your-aws-profile> node share_encrypted_ami.js -r <source-region> -R <destination-region> -a <ami-id> -A <accountId-to-share> -k <KMS Customer managed key>
* Usage for sharing AMI to different region without encryption:
* AWS_PROFILE=<your-aws-profile> node share_encrypted_ami.js -r <source-region> -R <destination-region> -a <ami-id> -A <accountId-to-share>
*/
const awsConfigHelper = require('./util/awsConfigHelper');
const AWS = require('aws-sdk');
const cli = require('cli');
const commonUtils = require('./util/common');
const waitUtils = require('./util/wait');
const cliArgs = cli.parse({
region: ['r', 'Source AWS region', 'string'],
dest_region: ['R', 'Destination AWS region', 'string'],
ami: ['a', "AMI ID", "string"],
account_to_share: ['A', "Target AWS account ID", "string"],
kms_key: ['k', "KMS KeyId to be used to encrypt", "string"],
});
if (!cliArgs.region || !cliArgs.ami || !cliArgs.account_to_share) {
cli.getUsage();
}
if (!cliArgs.dest_region) {
cliArgs.dest_region = cliArgs.region;
}
async function fetchKMSKey(kms, key) {
const keyDetails = await kms.describeKey({ KeyId: key }).promise();
return keyDetails.KeyMetadata;
}
async function fetchKMSKeys(kms, keys) {
const kmsKeysDetails = [];
for (let key of keys) {
kmsKeysDetails.push(await fetchKMSKey(kms, key));
}
return kmsKeysDetails;
}
function isDefaultKmsKey(kmsKey) {
return kmsKey.KeyManager === "AWS";
}
function getAWSDefaultKeys(kmsKeysDetails) {
const kmsKeysDetailsArray = commonUtils.toArray(kmsKeysDetails);
return kmsKeysDetailsArray.filter((kmsKey) => {
return isDefaultKmsKey(kmsKey);
});
}
async function getImageDetails(ec2, amiId) {
const amisDetails = await ec2.describeImages({
ImageIds: commonUtils.toArray(amiId)
}).promise();
if (!amisDetails.Images || !amisDetails.Images.length) {
return console.error("Provided AMI Id doesn't exist");
}
return amisDetails.Images[0];
}
async function getKmsKeysUsed(ec2, ami) {
const snapshotIDs = new Set();
ami.BlockDeviceMappings.forEach((volume) => {
if (volume.Ebs.SnapshotId) {
snapshotIDs.add(volume.Ebs.SnapshotId);
}
});
const snapshotDetails = await ec2.describeSnapshots({
SnapshotIds: Array.from(snapshotIDs)
}).promise();
return snapshotDetails.Snapshots.filter((snapshot) => snapshot.KmsKeyId)
.map((snapshot) => snapshot.KmsKeyId);
}
async function copyAMI(ec2, ami, kmsKey) {
const response = await ec2.copyImage({
Encrypted: !!kmsKey,
KmsKeyId: kmsKey,
SourceImageId: ami.ImageId,
SourceRegion: cliArgs.region,
Name: ami.Name + "_" + Date.now()
}).promise();
return response.ImageId;
}
async function handleDefaultKmsKeys(kms) {
console.log("AMI is encrypted with default KMS keys");
if (!cliArgs.kms_key) {
console.error(`AMI is encrypted with default key so please provide Customer Manager Key (CMK)`);
process.exit(-1);
} else {
const userKmsKey = await fetchKMSKey(kms, cliArgs.kms_key);
if (isDefaultKmsKey(userKmsKey)) {
console.error(`${cliArgs.kms_key} is AWS default key, please provide customer managed key`);
process.exit(-1);
}
if (!userKmsKey.Arn.includes(cliArgs.dest_region)) {
console.error(`${cliArgs.kms_key} should be from the destination region`);
process.exit(-1);
}
}
}
async function shareAmiWithDifferentAccount(ec2, ami, accountId) {
let imageDetails = {};
do {
imageDetails = await getImageDetails(ec2, ami);
console.log(`ami=${imageDetails.ImageId} is in state=${imageDetails.State}`);
if (imageDetails.State === 'pending') {
console.log("so waiting");
await waitUtils(5000);
}
} while (imageDetails.State === 'pending');
if (imageDetails.State !== "available") {
console.error(`ami=${imageDetails.ImageId} is in state=${imageDetails.State} so can't be shared`);
process.exit(-1);
}
console.log(`ami=${imageDetails.ImageId} will be shared with ${accountId}`);
return await ec2.modifyImageAttribute({
ImageId: ami,
LaunchPermission: {
Add: [
{
UserId: accountId.toString()
}
]
}
}).promise();
}
async function shareAMI() {
try {
await awsConfigHelper.updateConfig(cliArgs.region);
const ec2_source_region = new AWS.EC2();
const ec2_destination_region = new AWS.EC2({ region: cliArgs.dest_region });
const kms = new AWS.KMS();
const amiInfo = await getImageDetails(ec2_source_region, cliArgs.ami);
const kmsKeysUsed = await getKmsKeysUsed(ec2_source_region, amiInfo);
const kmsKeysDetails = await fetchKMSKeys(kms, kmsKeysUsed);
const defaultKMSKeys = getAWSDefaultKeys(kmsKeysDetails);
let newAMIId = cliArgs.ami;
let shouldCopyAmi = defaultKMSKeys.length > 0 || cliArgs.region !== cliArgs.dest_region;
if (defaultKMSKeys.length > 0) {
handleDefaultKmsKeys(kms);
}
if (shouldCopyAmi) {
console.log("AMI will be copied to a new AMI");
newAMIId = await copyAMI(ec2_destination_region, amiInfo, cliArgs.kms_key);
}
await shareAmiWithDifferentAccount(ec2_destination_region, newAMIId, cliArgs.account_to_share);
} catch (error) {
throw error;
};
}
shareAMI();