@economist/aws-key-rotator
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -23,3 +23,3 @@ import { IAM } from "aws-sdk"; | ||
/** | ||
* Rotate the given Access Keys for the given IAM User. | ||
* Performs the core key rotation steps and attempts to self-heal if there are any errors. | ||
* @param user the IAM User that the Access Keys belong to | ||
@@ -30,2 +30,16 @@ * @param keys the Access Keys to rotate | ||
/** | ||
* Performs the core key rotation steps: creating a new key, propagating it as required and | ||
* deleting any old keys. | ||
* @param user the IAM User that the Access Keys belong to | ||
* @param keys the Access Keys to rotate | ||
*/ | ||
private performCoreKeyRotation; | ||
/** | ||
* Performs a self-healing step by deleting any inactive keys and then re-running the | ||
* core key rotation | ||
* @param user the IAM User that the Access Keys belong to | ||
* @param keys the Access Keys to rotate | ||
*/ | ||
private selfHeal; | ||
/** | ||
* Creates a new Access Key and updates the relevant CircleCI environment variables. | ||
@@ -32,0 +46,0 @@ * @param user the IAM User to create a new Access Key for |
@@ -20,3 +20,3 @@ "use strict"; | ||
console.error(`There was an error during key rotation: ${JSON.stringify(err)}`); | ||
return Promise.reject(err); | ||
throw err; | ||
}); | ||
@@ -29,3 +29,3 @@ }; | ||
this.getExistingKeys = (user) => { | ||
console.log(`Retrieving existing keys for User ${user}`); | ||
console.log(`Retrieving existing keys for ${user}`); | ||
const params = { | ||
@@ -37,8 +37,8 @@ UserName: user, | ||
.then((data) => { | ||
console.log(`Retrieved the following keys for User ${user}: ${JSON.stringify(data.AccessKeyMetadata)}`); | ||
return Promise.resolve(data.AccessKeyMetadata); | ||
console.log(`Retrieved the following keys for ${user}: ${JSON.stringify(data.AccessKeyMetadata)}`); | ||
return data.AccessKeyMetadata; | ||
}); | ||
}; | ||
/** | ||
* Rotate the given Access Keys for the given IAM User. | ||
* Performs the core key rotation steps and attempts to self-heal if there are any errors. | ||
* @param user the IAM User that the Access Keys belong to | ||
@@ -48,14 +48,36 @@ * @param keys the Access Keys to rotate | ||
this.performKeyRotation = (user, keys) => { | ||
return this.createNewKey(user) | ||
.then((key) => this.handleNewKey(user, key)) | ||
.then(() => this.deleteKeys(user, keys)) | ||
return this.performCoreKeyRotation(user, keys) | ||
.catch((err) => { | ||
// Try to self-heal by removing any inactive keys but still throw an error | ||
// as we haven't created/handled the new key correctly | ||
console.log(`Attempting to delete inactive keys`); | ||
return this.deleteKeys(user, keys, (key) => key.Status === keyStatus_1.INACTIVE) | ||
.then(() => Promise.reject(err)); | ||
console.error(`There was an error during key rotation: ${JSON.stringify(err)}`); | ||
return this.selfHeal(user, keys); | ||
}); | ||
}; | ||
/** | ||
* Performs the core key rotation steps: creating a new key, propagating it as required and | ||
* deleting any old keys. | ||
* @param user the IAM User that the Access Keys belong to | ||
* @param keys the Access Keys to rotate | ||
*/ | ||
this.performCoreKeyRotation = (user, keys) => { | ||
console.log(`Beginning key rotation.`); | ||
return this.createNewKey(user) | ||
.then((key) => this.handleNewKey(user, key)) | ||
.then(() => { | ||
console.log(`Deleting old keys.`); | ||
return this.deleteKeys(user, keys); | ||
}) | ||
.then(() => console.log(`Key rotation for ${user} completed succesfully.`)); | ||
}; | ||
/** | ||
* Performs a self-healing step by deleting any inactive keys and then re-running the | ||
* core key rotation | ||
* @param user the IAM User that the Access Keys belong to | ||
* @param keys the Access Keys to rotate | ||
*/ | ||
this.selfHeal = (user, keys) => { | ||
console.log(`Attempting to self-heal by deleting any inactive keys`); | ||
return this.deleteKeys(user, keys, (key) => key.Status === keyStatus_1.INACTIVE) | ||
.then(() => this.performCoreKeyRotation(user, keys)); | ||
}; | ||
/** | ||
* Creates a new Access Key and updates the relevant CircleCI environment variables. | ||
@@ -65,3 +87,3 @@ * @param user the IAM User to create a new Access Key for | ||
this.createNewKey = (user) => { | ||
console.log(`Creating a new Access Key for User: ${user}`); | ||
console.log(`Creating a new Access Key for ${user}`); | ||
const params = { | ||
@@ -75,3 +97,3 @@ UserName: user, | ||
console.log(`Created a new Access Key with ID: ${newKey.AccessKeyId}`); | ||
return Promise.resolve(newKey); | ||
return newKey; | ||
}); | ||
@@ -86,7 +108,9 @@ }; | ||
this.handleNewKey = (user, key) => { | ||
console.log(`Handling the newly created key.`); | ||
return this.newKeyHandler(key) | ||
.then(() => console.log(`Successfully handled the new key.`)) | ||
.catch((err) => { | ||
console.error(`New Key Handler failed with error: ${JSON.stringify(err)}. New key will be deleted.`); | ||
return this.deleteKey(user, key) | ||
.then(() => Promise.reject(err)); | ||
.then(() => { throw err; }); | ||
}); | ||
@@ -115,3 +139,3 @@ }; | ||
return Promise.all(promises) | ||
.then(() => Promise.resolve()); | ||
.then(() => { return; }); | ||
}; | ||
@@ -124,3 +148,3 @@ /** | ||
this.deleteKey = (user, key) => { | ||
console.log(`Deleting Access Key: ${key.AccessKeyId} for User ${user}`); | ||
console.log(`Deleting Access Key: ${key.AccessKeyId} for ${user}`); | ||
const params = { | ||
@@ -134,3 +158,3 @@ AccessKeyId: key.AccessKeyId, | ||
console.log(`Deleted Access Key: ${params.AccessKeyId}`); | ||
return Promise.resolve(data); | ||
return data; | ||
}); | ||
@@ -137,0 +161,0 @@ }; |
{ | ||
"name": "@economist/aws-key-rotator", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "AWS Access Key Rotation", | ||
@@ -16,3 +16,4 @@ "keywords": [ | ||
"clean-build": "npm run clean && npm run build", | ||
"test": "jest --coverage" | ||
"test": "jest --coverage", | ||
"prepublishOnly": "npm run clean-build" | ||
}, | ||
@@ -19,0 +20,0 @@ "author": { |
@@ -72,3 +72,3 @@ # aws-key-rotator | ||
Any errors that occur after the user's existing key(s) have been retrieved will trigger a clean-up stage that attempts to delete any inactive keys from the list of existing keys. AWS restricts users to having at most 2 Access Keys at any one time therefore rotation will fail if 2 keys are present as a new key cannot be created. Deleting inactive keys ensures that the [KeyRotator](#KeyRotator) can "self-heal" from this state and should allow it run successfully on its next attempt. | ||
Any errors that occur after the user's existing key(s) have been retrieved will trigger a clean-up stage that attempts to delete any inactive keys from the list of existing keys and then performs a re-run of the core rotation steps. AWS restricts users to having at most 2 Access Keys at any one time, therefore rotation will fail if 2 keys are present as a new key cannot be created. Deleting inactive keys ensures that the [KeyRotator](#KeyRotator) can "self-heal" from this state and should allow it to successfully rotate the keys on the re-run. | ||
@@ -75,0 +75,0 @@ Additionally, if the user-defined [NewKeyHandler](#NewKeyHandler) function returns a rejected `Promise`, indicating that the required handling failed, then the [KeyRotator](#KeyRotator) will delete the newly created Access Key ensuring that the user is not left with an unusable, active Access Key. |
@@ -104,17 +104,16 @@ import { Callback } from "aws-lambda"; | ||
.then(() => { | ||
fail(); | ||
done(); | ||
}) | ||
.catch((err) => { | ||
// Expect there to be 1 key | ||
expect(keys.length).toBe(1); | ||
// Expect existing inactive key to have been removed | ||
// Expect existing keys to have been removed | ||
expect(keys.indexOf(existingInactiveKey)).toBe(-1); | ||
expect(keys.indexOf(existingActiveKey)).toBe(-1); | ||
// Expect existing active key to be present and still active | ||
expect(existingActiveKey.Status).toBe(ACTIVE); | ||
expect(keys.indexOf(existingActiveKey)).toBeGreaterThanOrEqual(0); | ||
// Expect new key to be present and active | ||
expect(newKey.Status).toBe(ACTIVE); | ||
expect(keys.indexOf(newKey)).toBeGreaterThanOrEqual(0); | ||
done(); | ||
}) | ||
.catch((err) => { | ||
fail(); | ||
}); | ||
@@ -132,15 +131,16 @@ }); | ||
.then(() => { | ||
fail(); | ||
done(); | ||
}) | ||
.catch((err) => { | ||
// Expect there to be 2 keys | ||
expect(keys.length).toBe(0); | ||
// Expect there to be 1 key | ||
expect(keys.length).toBe(1); | ||
// Expect both existing keys to have been removed | ||
// Expect existing keys to have been removed | ||
expect(keys.indexOf(firstExistingKey)).toBe(-1); | ||
expect(keys.indexOf(secondExistingKey)).toBe(-1); | ||
// Expect new key to be present and active | ||
expect(newKey.Status).toBe(ACTIVE); | ||
expect(keys.indexOf(newKey)).toBeGreaterThanOrEqual(0); | ||
done(); | ||
}) | ||
.catch((err) => { | ||
fail(); | ||
}); | ||
@@ -159,3 +159,2 @@ }); | ||
fail(); | ||
done(); | ||
}) | ||
@@ -182,3 +181,2 @@ .catch((err) => { | ||
.then(() => { | ||
fail(); | ||
done(); | ||
@@ -204,3 +202,2 @@ }) | ||
fail(); | ||
done(); | ||
}) | ||
@@ -229,3 +226,2 @@ .catch((err) => { | ||
fail(); | ||
done(); | ||
}) | ||
@@ -254,3 +250,2 @@ .catch((err) => { | ||
fail(); | ||
done(); | ||
}) | ||
@@ -257,0 +252,0 @@ .catch((err) => { |
@@ -31,3 +31,3 @@ import { AWSError, IAM } from "aws-sdk"; | ||
console.error(`There was an error during key rotation: ${JSON.stringify(err)}`); | ||
return Promise.reject(err); | ||
throw err; | ||
}); | ||
@@ -41,3 +41,3 @@ } | ||
private getExistingKeys = (user: string): Promise<AccessKeyMetadata[]> => { | ||
console.log(`Retrieving existing keys for User ${user}`); | ||
console.log(`Retrieving existing keys for ${user}`); | ||
const params: ListAccessKeysRequest = { | ||
@@ -50,4 +50,4 @@ UserName: user, | ||
.then((data) => { | ||
console.log(`Retrieved the following keys for User ${user}: ${JSON.stringify(data.AccessKeyMetadata)}`); | ||
return Promise.resolve(data.AccessKeyMetadata); | ||
console.log(`Retrieved the following keys for ${user}: ${JSON.stringify(data.AccessKeyMetadata)}`); | ||
return data.AccessKeyMetadata; | ||
}); | ||
@@ -57,3 +57,3 @@ } | ||
/** | ||
* Rotate the given Access Keys for the given IAM User. | ||
* Performs the core key rotation steps and attempts to self-heal if there are any errors. | ||
* @param user the IAM User that the Access Keys belong to | ||
@@ -63,11 +63,6 @@ * @param keys the Access Keys to rotate | ||
private performKeyRotation = (user: string, keys: AccessKeyMetadata[]) => { | ||
return this.createNewKey(user) | ||
.then((key) => this.handleNewKey(user, key)) | ||
.then(() => this.deleteKeys(user, keys)) | ||
return this.performCoreKeyRotation(user, keys) | ||
.catch((err) => { | ||
// Try to self-heal by removing any inactive keys but still throw an error | ||
// as we haven't created/handled the new key correctly | ||
console.log(`Attempting to delete inactive keys`); | ||
return this.deleteKeys(user, keys, (key) => key.Status === INACTIVE) | ||
.then(() => Promise.reject(err)); | ||
console.error(`There was an error during key rotation: ${JSON.stringify(err)}`); | ||
return this.selfHeal(user, keys); | ||
}); | ||
@@ -77,2 +72,31 @@ } | ||
/** | ||
* Performs the core key rotation steps: creating a new key, propagating it as required and | ||
* deleting any old keys. | ||
* @param user the IAM User that the Access Keys belong to | ||
* @param keys the Access Keys to rotate | ||
*/ | ||
private performCoreKeyRotation = (user: string, keys: AccessKeyMetadata[]) => { | ||
console.log(`Beginning key rotation.`); | ||
return this.createNewKey(user) | ||
.then((key) => this.handleNewKey(user, key)) | ||
.then(() => { | ||
console.log(`Deleting old keys.`); | ||
return this.deleteKeys(user, keys); | ||
}) | ||
.then(() => console.log(`Key rotation for ${user} completed succesfully.`)); | ||
} | ||
/** | ||
* Performs a self-healing step by deleting any inactive keys and then re-running the | ||
* core key rotation | ||
* @param user the IAM User that the Access Keys belong to | ||
* @param keys the Access Keys to rotate | ||
*/ | ||
private selfHeal = (user: string, keys: AccessKeyMetadata[]) => { | ||
console.log(`Attempting to self-heal by deleting any inactive keys`); | ||
return this.deleteKeys(user, keys, (key) => key.Status === INACTIVE) | ||
.then(() => this.performCoreKeyRotation(user, keys)); | ||
} | ||
/** | ||
* Creates a new Access Key and updates the relevant CircleCI environment variables. | ||
@@ -82,3 +106,3 @@ * @param user the IAM User to create a new Access Key for | ||
private createNewKey = (user: string): Promise<AccessKey> => { | ||
console.log(`Creating a new Access Key for User: ${user}`); | ||
console.log(`Creating a new Access Key for ${user}`); | ||
@@ -94,3 +118,3 @@ const params: CreateAccessKeyRequest = { | ||
console.log(`Created a new Access Key with ID: ${newKey.AccessKeyId}`); | ||
return Promise.resolve(newKey); | ||
return newKey; | ||
}); | ||
@@ -106,7 +130,9 @@ } | ||
private handleNewKey = (user: string, key: AccessKey) => { | ||
console.log(`Handling the newly created key.`); | ||
return this.newKeyHandler(key) | ||
.then(() => console.log(`Successfully handled the new key.`)) | ||
.catch((err) => { | ||
console.error(`New Key Handler failed with error: ${JSON.stringify(err)}. New key will be deleted.`); | ||
return this.deleteKey(user, key) | ||
.then(() => Promise.reject(err)); | ||
.then(() => { throw err; }); | ||
}); | ||
@@ -140,3 +166,3 @@ } | ||
return Promise.all(promises) | ||
.then(() => Promise.resolve()); | ||
.then(() => { return; }); | ||
} | ||
@@ -150,3 +176,3 @@ | ||
private deleteKey = (user: string, key: AccessKeyMetadata) => { | ||
console.log(`Deleting Access Key: ${key.AccessKeyId} for User ${user}`); | ||
console.log(`Deleting Access Key: ${key.AccessKeyId} for ${user}`); | ||
const params: DeleteAccessKeyRequest = { | ||
@@ -161,5 +187,5 @@ AccessKeyId: key.AccessKeyId!, | ||
console.log(`Deleted Access Key: ${params.AccessKeyId}`); | ||
return Promise.resolve(data); | ||
return data; | ||
}); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
52557
24
700