Skip to content

Commit

Permalink
merge from main + rerun integ test
Browse files Browse the repository at this point in the history
  • Loading branch information
comcalvi committed Oct 22, 2022
1 parent 90ae922 commit 6548d0b
Show file tree
Hide file tree
Showing 51 changed files with 9,341 additions and 285 deletions.
Expand Up @@ -27,4 +27,4 @@ def handler(event, context):
# If the bucket does not exist, then this error will be thrown
raise RuntimeError(f'failed to delete bucket: {str(error)}')

return {'Data': {'Value': f'confirmed that bucket with name {s3_bucket_name} exists...bucket has been deleted' }}
return {'Data': {'Value': f'confirmed that bucket with name {s3_bucket_name} exists...bucket has been deleted' }}
Binary file not shown.
@@ -0,0 +1,17 @@
export function arrayDiff(oldValues: string[], newValues: string[]) {
const deletes = new Set(oldValues);
const adds = new Set<string>();

for (const v of new Set(newValues)) {
if (deletes.has(v)) {
deletes.delete(v);
} else {
adds.add(v);
}
}

return {
adds: Array.from(adds),
deletes: Array.from(deletes),
};
}
@@ -0,0 +1,53 @@
/* istanbul ignore file */

import * as tls from 'tls';
import * as url from 'url';
// eslint-disable-next-line import/no-extraneous-dependencies
import * as aws from 'aws-sdk';

let client: aws.IAM;

function iam() {
if (!client) { client = new aws.IAM(); }
return client;
}

function defaultLogger(fmt: string, ...args: any[]) {
// eslint-disable-next-line no-console
console.log(fmt, ...args);
}

/**
* Downloads the CA thumbprint from the issuer URL
*/
async function downloadThumbprint(issuerUrl: string) {
external.log(`downloading certificate authority thumbprint for ${issuerUrl}`);
return new Promise<string>((ok, ko) => {
const purl = url.parse(issuerUrl);
const port = purl.port ? parseInt(purl.port, 10) : 443;
if (!purl.host) {
return ko(new Error(`unable to determine host from issuer url ${issuerUrl}`));
}
const socket = tls.connect(port, purl.host, { rejectUnauthorized: false, servername: purl.host });
socket.once('error', ko);
socket.once('secureConnect', () => {
const cert = socket.getPeerCertificate();
socket.end();
const thumbprint = cert.fingerprint.split(':').join('');
external.log(`certificate authority thumbprint for ${issuerUrl} is ${thumbprint}`);
ok(thumbprint);
});
});
}

// allows unit test to replace with mocks
/* eslint-disable max-len */
export const external = {
downloadThumbprint,
log: defaultLogger,
createOpenIDConnectProvider: (req: aws.IAM.CreateOpenIDConnectProviderRequest) => iam().createOpenIDConnectProvider(req).promise(),
deleteOpenIDConnectProvider: (req: aws.IAM.DeleteOpenIDConnectProviderRequest) => iam().deleteOpenIDConnectProvider(req).promise(),
updateOpenIDConnectProviderThumbprint: (req: aws.IAM.UpdateOpenIDConnectProviderThumbprintRequest) => iam().updateOpenIDConnectProviderThumbprint(req).promise(),
addClientIDToOpenIDConnectProvider: (req: aws.IAM.AddClientIDToOpenIDConnectProviderRequest) => iam().addClientIDToOpenIDConnectProvider(req).promise(),
removeClientIDFromOpenIDConnectProvider: (req: aws.IAM.RemoveClientIDFromOpenIDConnectProviderRequest) => iam().removeClientIDFromOpenIDConnectProvider(req).promise(),
};
@@ -0,0 +1,89 @@
import { arrayDiff } from './diff';
import { external } from './external';

export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) {
if (event.RequestType === 'Create') { return onCreate(event); }
if (event.RequestType === 'Update') { return onUpdate(event); }
if (event.RequestType === 'Delete') { return onDelete(event); }
throw new Error('invalid request type');
}

async function onCreate(event: AWSLambda.CloudFormationCustomResourceCreateEvent) {
const issuerUrl = event.ResourceProperties.Url;
const thumbprints: string[] = (event.ResourceProperties.ThumbprintList ?? []).sort(); // keep sorted for UPDATE
const clients: string[] = (event.ResourceProperties.ClientIDList ?? []).sort();

if (thumbprints.length === 0) {
thumbprints.push(await external.downloadThumbprint(issuerUrl));
}

const resp = await external.createOpenIDConnectProvider({
Url: issuerUrl,
ClientIDList: clients,
ThumbprintList: thumbprints,
});

return {
PhysicalResourceId: resp.OpenIDConnectProviderArn,
};
}

async function onUpdate(event: AWSLambda.CloudFormationCustomResourceUpdateEvent) {
const issuerUrl = event.ResourceProperties.Url;
const thumbprints: string[] = (event.ResourceProperties.ThumbprintList ?? []).sort(); // keep sorted for UPDATE
const clients: string[] = (event.ResourceProperties.ClientIDList ?? []).sort();

// determine which update we are talking about.
const oldIssuerUrl = event.OldResourceProperties.Url;

// if this is a URL update, then we basically create a new resource and cfn will delete the old one
// since the physical resource ID will change.
if (oldIssuerUrl !== issuerUrl) {
return onCreate({ ...event, RequestType: 'Create' });
}

const providerArn = event.PhysicalResourceId;

// if thumbprints changed, we can update in-place, but bear in mind that if the new thumbprint list
// is empty, we will grab it from the server like we do in CREATE
const oldThumbprints = (event.OldResourceProperties.ThumbprintList || []).sort();
if (JSON.stringify(oldThumbprints) !== JSON.stringify(thumbprints)) {
const thumbprintList = thumbprints.length > 0 ? thumbprints : [await external.downloadThumbprint(issuerUrl)];
external.log('updating thumbprint list from', oldThumbprints, 'to', thumbprints);
await external.updateOpenIDConnectProviderThumbprint({
OpenIDConnectProviderArn: providerArn,
ThumbprintList: thumbprintList,
});

// don't return, we might have more updates...
}

// if client ID list has changed, determine "diff" because the API is add/remove
const oldClients: string[] = (event.OldResourceProperties.ClientIDList || []).sort();
const diff = arrayDiff(oldClients, clients);
external.log(`client ID diff: ${JSON.stringify(diff)}`);

for (const addClient of diff.adds) {
external.log(`adding client id "${addClient}" to provider ${providerArn}`);
await external.addClientIDToOpenIDConnectProvider({
OpenIDConnectProviderArn: providerArn,
ClientID: addClient,
});
}

for (const deleteClient of diff.deletes) {
external.log(`removing client id "${deleteClient}" from provider ${providerArn}`);
await external.removeClientIDFromOpenIDConnectProvider({
OpenIDConnectProviderArn: providerArn,
ClientID: deleteClient,
});
}

return;
}

async function onDelete(deleteEvent: AWSLambda.CloudFormationCustomResourceDeleteEvent) {
await external.deleteOpenIDConnectProvider({
OpenIDConnectProviderArn: deleteEvent.PhysicalResourceId,
});
}

0 comments on commit 6548d0b

Please sign in to comment.