Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
51 changed files
with
9,341 additions
and
285 deletions.
There are no files selected for viewing
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
Binary file added
BIN
+1.37 MB
...count-sdk-call/asset.4288ebb3652acdf2d828b7db7ca44a7162a401ace50ebb4026e84b18a02a06ee.zip
Binary file not shown.
17 changes: 17 additions & 0 deletions
17
...t-sdk-call/asset.42973d1d89f4a393a64981f78d088964ba13e63a3aab4478cd74109c77cf9174/diff.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,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), | ||
}; | ||
} |
53 changes: 53 additions & 0 deletions
53
...k-call/asset.42973d1d89f4a393a64981f78d088964ba13e63a3aab4478cd74109c77cf9174/external.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,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(), | ||
}; |
89 changes: 89 additions & 0 deletions
89
...-sdk-call/asset.42973d1d89f4a393a64981f78d088964ba13e63a3aab4478cd74109c77cf9174/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,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, | ||
}); | ||
} |
Oops, something went wrong.