Skip to content

feat: Add a param isFIPSEnabled #598

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

Open
wants to merge 7 commits into
base: yiming.luo/fips
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ To further configure your plugin, use the following custom parameters in your `s
| `enableStepFunctionsTracing` | Enable automatic subscription of the Datadog Forwarder to Step Function log groups and Step Functions tracing. If no Step Function log groups are configured, then they are automatically created. Requires setting `forwarderArn`. Defaults to `false`. |
| `propagateUpstreamTrace` | When set to `true`, downstream Stepfunction invocation traces merge with upstream Stepfunction invocations. Defaults to `false`. |
| `redirectHandlers` | Optionally disable handler redirection if set to `false`. This should only be set to `false` when APM is fully disabled. Defaults to `true`. |
| `isFIPSEnabled` | When set to `true`, a FIPS-compliant Lambda extension layer is used. This only works if `addExtension` is `true`. Defaults to `true` if `addExtension` is `true`, and AWS region starts with `us-gov-`. Defaults to `false` otherwise. |
To use any of these parameters, add a `custom` > `datadog` section to your `serverless.yml` similar to this example:

```yaml
Expand Down
4 changes: 4 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ export interface Configuration {
// Used for testing or for someone exclusively forwarding logs
// or including the library only for metrics.
redirectHandlers?: boolean;

// When set to `true`, a FIPS-compliant lambda extension layer will be used.
// Only works if `addExtension` is `true`.
isFIPSEnabled?: boolean;
}
const webpackPluginName = "serverless-webpack";
const apiKeyEnvVar = "DD_API_KEY";
Expand Down
52 changes: 52 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,58 @@ describe("ServerlessPlugin", () => {
},
});
});

it("adds FIPS extension layer by default for GovCloud regions", async () => {
mock({});
const serverless = {
cli: {
log: () => {},
},
getProvider: (_name: string) => awsMock(),
service: {
getServiceName: () => "dev",
provider: {
region: "us-gov-east-1",
},
functions: {
node1: {
handler: "my-func.ev",
layers: [],
runtime: "nodejs20.x",
},
},
custom: {
datadog: {
addExtension: true,
site: "ddog-gov.com",
apiKey: 1234,
},
},
},
};

const plugin = new ServerlessPlugin(serverless, {});
await plugin.hooks["before:package:createDeploymentArtifacts"]();
expect(serverless).toMatchObject({
service: {
functions: {
node1: {
handler: "my-func.ev",
layers: [
expect.stringMatching(/arn\:aws\-us\-gov\:lambda\:us\-gov\-east\-1\:.*\:layer\:.*/),
expect.stringMatching(
/arn\:aws\-us\-gov\:lambda\:us\-gov\-east\-1\:.*\:layer\:Datadog-Extension-FIPS\:.*/,
),
],
runtime: "nodejs20.x",
},
},
provider: {
region: "us-gov-east-1",
},
},
});
});
});

it("Adds tracing layer for dotnet", async () => {
Expand Down
13 changes: 11 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ import {
addStepFunctionLogGroupSubscription,
} from "./forwarder";
import { newSimpleGit } from "./git";
import { applyExtensionLayer, applyLambdaLibraryLayers, findHandlers, FunctionInfo, RuntimeType } from "./layer";
import {
applyExtensionLayer,
applyLambdaLibraryLayers,
findHandlers,
FunctionInfo,
RuntimeType,
getDefaultIsFIPSEnabledFlag,
} from "./layer";
import * as govLayers from "./layers-gov.json";
import * as layers from "./layers.json";
import { getCloudFormationStackId } from "./monitor-api-requests";
Expand Down Expand Up @@ -139,7 +146,9 @@ module.exports = class ServerlessPlugin {
if (config.addExtension) {
this.serverless.cli.log("Adding Datadog Lambda Extension Layer to functions");
this.debugLogHandlers(handlers);
applyExtensionLayer(this.serverless.service, handlers, allLayers, accountId);
const isFIPSEnabled =
config.isFIPSEnabled ?? getDefaultIsFIPSEnabledFlag(config, this.serverless.service.provider.region);
applyExtensionLayer(this.serverless.service, handlers, allLayers, accountId, isFIPSEnabled);
} else {
this.serverless.cli.log("Skipping adding Lambda Extension Layer");
}
Expand Down
84 changes: 84 additions & 0 deletions src/layer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,90 @@ describe("applyLambdaLibraryLayers", () => {
runtime: "dotnet6",
});
});

it("adds the FIPS extension layer when isFIPSEnabled is true", () => {
const handler = {
handler: { runtime: "dotnet6" },
type: RuntimeType.DOTNET,
runtime: "dotnet6",
} as FunctionInfo;
const layers: LayerJSON = {
regions: {
"us-gov-east-1": {
extension: "arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension:47",
"extension-fips": "arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension-FIPS:47",
},
},
};
const mockService = createMockService(
"us-gov-east-1",
{
"dotnet-function": { handler: "AwsDotnetCsharp::AwsDotnetCsharp.Handler::HelloWorld", runtime: "dotnet6" },
},
"x86_64",
);
applyExtensionLayer(mockService, [handler], layers, undefined, true);
expect(handler.handler).toEqual({
runtime: "dotnet6",
layers: ["arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension-FIPS:47"],
});
});

it("adds the ARM FIPS extension layer when isFIPSEnabled is true", () => {
const handler = {
handler: { runtime: "dotnet6" },
type: RuntimeType.DOTNET,
runtime: "dotnet6",
} as FunctionInfo;
const layers: LayerJSON = {
regions: {
"us-gov-east-1": {
"extension-arm": "arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension:47",
"extension-arm-fips": "arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension-FIPS:47",
},
},
};
const mockService = createMockService(
"us-gov-east-1",
{
"dotnet-function": { handler: "AwsDotnetCsharp::AwsDotnetCsharp.Handler::HelloWorld", runtime: "dotnet6" },
},
"arm64",
);
applyExtensionLayer(mockService, [handler], layers, undefined, true);
expect(handler.handler).toEqual({
runtime: "dotnet6",
layers: ["arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension-FIPS:47"],
});
});

it("does not add the FIPS extension layer when isFIPSEnabled is false", () => {
const handler = {
handler: { runtime: "dotnet6" },
type: RuntimeType.DOTNET,
runtime: "dotnet6",
} as FunctionInfo;
const layers: LayerJSON = {
regions: {
"us-gov-east-1": {
extension: "arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension:47",
"extension-fips": "arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension-FIPS:47",
},
},
};
const mockService = createMockService(
"us-gov-east-1",
{
"dotnet-function": { handler: "AwsDotnetCsharp::AwsDotnetCsharp.Handler::HelloWorld", runtime: "dotnet6" },
},
"x86_64",
);
applyExtensionLayer(mockService, [handler], layers, undefined, false);
expect(handler.handler).toEqual({
runtime: "dotnet6",
layers: ["arn:aws:lambda:us-gov-east-1:464622532012:layer:Datadog-Extension:47"],
});
});
});

describe("pushLayerARN", () => {
Expand Down
14 changes: 14 additions & 0 deletions src/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import { FunctionDefinition, FunctionDefinitionHandler } from "serverless";
import Service from "serverless/classes/Service";
import { Configuration } from "./env";

export enum RuntimeType {
NODE = "node",
Expand Down Expand Up @@ -221,6 +222,7 @@ export function applyExtensionLayer(
handlers: FunctionInfo[],
layers: LayerJSON,
accountId?: string,
isFIPSEnabled: boolean = false,
): void {
const { region } = service.provider;
// It's possible a local account layer is being used in a region we have not published to so we use a default region's ARNs
Expand All @@ -247,6 +249,10 @@ export function applyExtensionLayer(
extensionLayerKey = ARM_RUNTIME_KEYS[extensionLayerKey];
}

if (isFIPSEnabled) {
extensionLayerKey = `${extensionLayerKey}-fips`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
extensionLayerKey = `${extensionLayerKey}-fips`;
extensionLayerKey += "-fips";

}

let extensionARN = regionRuntimes[extensionLayerKey];
if (accountId && extensionARN) {
extensionARN = buildLocalLambdaLayerARN(extensionARN, accountId, region);
Expand All @@ -268,6 +274,14 @@ export function isFunctionDefinitionHandler(funcDef: FunctionDefinition): funcDe
return typeof (funcDef as any).handler === "string";
}

/**
* The isFIPSEnabled flag defaults to `true` if `addExtension` is `true` and region
* starts with "us-gov-". It defaults to `false` otherwise.
*/
export function getDefaultIsFIPSEnabledFlag(config: Configuration, region: string): boolean {
return config.addExtension && region.startsWith("us-gov-");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should "us-gov-" be refactored out into a constant?

}

function addLayer(service: Service, handler: FunctionInfo, layerArn: string): void {
setLayers(handler, pushLayerARN(layerArn, getLayers(service, handler)));
}
Expand Down