Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support FIPS in endpoint heuristics #3923

Merged
merged 24 commits into from
Oct 21, 2021
Merged

Conversation

trivikr
Copy link
Member

@trivikr trivikr commented Oct 20, 2021

Issue

Internal JS-2892

Description

Adds support for FIPS in endpoint heuristics

Code used to populated test cases
// The models directory contains *.min.json files
// from https://github.com/aws/aws-sdk-js/tree/master/apis
// The endpoints.json contains RIP endpoints
// The metadata.json contains file from
// https://github.com/aws/aws-sdk-js/blob/master/apis/metadata.json

import { readFile, readdir, writeFile } from "fs/promises";

const getClientName = (api, metadata) => {
  if (api.metadata.endpointPrefix === "api.tunneling.iot") {
    return "IoTSecureTunneling";
  }
  const getPrefix = (str) => str.substring(0, str.length - 11);
  const isPrefixInMetadata = ([key, value]) =>
    api.metadata.uid &&
    (getPrefix(api.metadata.uid) === key ||
      getPrefix(api.metadata.uid) === value.prefix);
  if (Object.entries(metadata).some(isPrefixInMetadata)) {
    return Object.entries(metadata).find(isPrefixInMetadata)[1].name;
  }
  let name =
    api.metadata.serviceId ||
    api.metadata.serviceAbbreviation ||
    api.metadata.serviceFullName;
  if (!name) return null;

  name = name.replace(/^Amazon|AWS\s*|\(.*|\s+|\W+/g, "");
  return name;
};

const endpointPrefixSdkIdHash = {};
const models = await readdir("models");
const metadata = JSON.parse((await readFile("metadata.json")).toString());
for (const modelFileName of models) {
  const model = JSON.parse(
    (await readFile(`models/${modelFileName}`)).toString()
  );
  const clientName = getClientName(model, metadata);
  const { endpointPrefix } = model.metadata;
  endpointPrefixSdkIdHash[
    endpointPrefix === "api.tunneling.iot"
      ? "iotsecuredtunneling"
      : endpointPrefix
  ] = clientName;
}

const isFipsRegion = (region) =>
  region.startsWith("fips-") || region.endsWith("-fips");

const testCases = [];
const endpoints = JSON.parse((await readFile("endpoints.json")).toString());
for (const partition of endpoints.partitions) {
  for (const [endpointPrefix, serviceConfig] of Object.entries(
    partition.services
  )) {
    for (const [region, regionConfig] of Object.entries(
      serviceConfig.endpoints
    )) {
      if (isFipsRegion(region)) {
        const { hostname } = regionConfig;
        const regionRegex = partition.regionRegex
          .replace("\\\\", "\\")
          .replace(/^\^/g, "")
          .replace(/\$$/g, "");
        const signingRegion =
          regionConfig.credentialScope?.region ||
          hostname.match(regionRegex)[0];
        testCases.push({
          endpointPrefix,
          clientName: endpointPrefixSdkIdHash[endpointPrefix],
          region,
          signingRegion,
          hostname,
        });
      }
    }
  }
}
await writeFile("testCases.json", JSON.stringify(testCases, null, 2));

Testing

Added mocha tests under test/endpoint

Code used for testing externally
import AWS from "aws-sdk";
import { deepEqual } from "assert";
import { readFile } from "fs/promises";

const getFunctionName = (client) => {
  const funcPrefixes = ["list", "describe", "query", "get", "invoke", "send"];
  const properties = Object.getOwnPropertyNames(Object.getPrototypeOf(client));
  for (const funcPrefix of funcPrefixes) {
    const startsWith = (property) => property.startsWith(funcPrefix);
    if (properties.some(startsWith)) {
      return properties.find(startsWith);
    }
  }
};

const testApiCall = async ({ clientName, region, signingRegion, hostname }) => {
  if (clientName === "IotData") {
    // requires an explicit `endpoint' configuration option.
    return;
  }
  if (!AWS[clientName]) {
    console.log(`${clientName} does not exist`);
    return;
  }

  const client = new AWS[clientName]({ region });

  const functionName = getFunctionName(client);
  if (!functionName) {
    console.log(
      `Function starting with list/describe does not exist for ${clientName}`
    );
    return;
  }
  const req = client[getFunctionName(client)]();
  req.on("complete", () => {
    deepEqual(
      { region, signingRegion, hostname },
      {
        region: client.config.region,
        signingRegion: req.httpRequest.region,
        hostname: req.httpRequest.endpoint.host,
      }
    );
  });

  try {
    await req.promise();
  } catch (error) {}
};

const testCases = JSON.parse((await readFile("testCases.json")).toString());
for (const testCase of testCases) {
  await testApiCall(testCase);
}
Checklist
  • npm run test passes
  • changelog is added, npm run add-change
  • run npm run integration if integration test is changed

@trivikr trivikr marked this pull request as ready for review October 21, 2021 16:18
lib/region_config.js Outdated Show resolved Hide resolved
@AllanZhengYP
Copy link
Contributor

Awesome work, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants