Skip to content

Commit

Permalink
revert(iam): oidc provider retrieves leaf certificate instead of root…
Browse files Browse the repository at this point in the history
… certificate (#22805)

Reverts #22509. See #22802.
  • Loading branch information
iliapolo authored Nov 7, 2022
1 parent 9bde9f3 commit 64232ce
Show file tree
Hide file tree
Showing 100 changed files with 1,428 additions and 866 deletions.
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-eks/lib/oidc-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ export class OpenIdConnectProvider extends iam.OpenIdConnectProvider {
* @param props Initialization properties
*/
public constructor(scope: Construct, id: string, props: OpenIdConnectProviderProps) {
/**
* For some reason EKS isn't validating the root certificate but a intermediate certificate
* which is one level up in the tree. Because of the a constant thumbprint value has to be
* stated with this OpenID Connect provider. The certificate thumbprint is the same for all the regions.
*/
const thumbprints = ['9e99a48a9960b14926bb7f3b02e22da2b0ab7280'];

const clientIds = ['sts.amazonaws.com'];

super(scope, id, {
url: props.url,
thumbprints,
clientIds,
});
}
Expand Down
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-eks/test/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,9 @@ describe('cluster', () => {
ClientIDList: [
'sts.amazonaws.com',
],
ThumbprintList: [
'9e99a48a9960b14926bb7f3b02e22da2b0ab7280',
],
Url: {
'Fn::GetAtt': [
'Cluster9EE0221C',
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export declare function arrayDiff(oldValues: string[], newValues: string[]): {
adds: string[];
deletes: string[];
};
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),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as aws from 'aws-sdk';
declare function defaultLogger(fmt: string, ...args: any[]): void;
/**
* Downloads the CA thumbprint from the issuer URL
*/
declare function downloadThumbprint(issuerUrl: string): Promise<string>;
export declare const external: {
downloadThumbprint: typeof downloadThumbprint;
log: typeof defaultLogger;
createOpenIDConnectProvider: (req: aws.IAM.CreateOpenIDConnectProviderRequest) => Promise<import("aws-sdk/lib/request").PromiseResult<aws.IAM.CreateOpenIDConnectProviderResponse, aws.AWSError>>;
deleteOpenIDConnectProvider: (req: aws.IAM.DeleteOpenIDConnectProviderRequest) => Promise<{
$response: aws.Response<{}, aws.AWSError>;
}>;
updateOpenIDConnectProviderThumbprint: (req: aws.IAM.UpdateOpenIDConnectProviderThumbprintRequest) => Promise<{
$response: aws.Response<{}, aws.AWSError>;
}>;
addClientIDToOpenIDConnectProvider: (req: aws.IAM.AddClientIDToOpenIDConnectProviderRequest) => Promise<{
$response: aws.Response<{}, aws.AWSError>;
}>;
removeClientIDFromOpenIDConnectProvider: (req: aws.IAM.RemoveClientIDFromOpenIDConnectProviderRequest) => Promise<{
$response: aws.Response<{}, aws.AWSError>;
}>;
};
export {};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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(),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export declare function handler(event: AWSLambda.CloudFormationCustomResourceEvent): Promise<void | {
PhysicalResourceId: string | undefined;
}>;
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,
});
}
Loading

0 comments on commit 64232ce

Please sign in to comment.