diff --git a/src/commands/lambda/__tests__/fixtures.ts b/src/commands/lambda/__tests__/fixtures.ts index 32578c210..89b931f8d 100644 --- a/src/commands/lambda/__tests__/fixtures.ts +++ b/src/commands/lambda/__tests__/fixtures.ts @@ -47,7 +47,7 @@ export const makeCli = () => { return cli } -export const mockLambdaClientCommands = (lambdaClientMock: AwsStub) => { +export const mockLambdaClientCommands = (lambdaClientMock: AwsStub) => { lambdaClientMock.on(UpdateFunctionConfigurationCommand).resolves({}) lambdaClientMock.on(TagResourceCommand).resolves({}) lambdaClientMock.on(GetLayerVersionCommand).rejects() @@ -55,7 +55,7 @@ export const mockLambdaClientCommands = (lambdaClientMock: AwsStub, + lambdaClientMock: AwsStub, functionConfigurations: Record ) => { const functions: LFunctionConfiguration[] = [] @@ -88,7 +88,7 @@ export const mockLambdaConfigurations = ( } export const mockLambdaLayers = ( - lambdaClientMock: AwsStub, + lambdaClientMock: AwsStub, layers: Record ) => { for (const layerName in layers) { @@ -108,7 +108,7 @@ export const mockLambdaLayers = ( } export const mockLogGroups = ( - cloudWatchLogsClientMock: AwsStub, + cloudWatchLogsClientMock: AwsStub, logGroups: Record< string, { @@ -136,7 +136,7 @@ export const mockLogGroups = ( } export const mockCloudWatchLogsClientCommands = ( - cloudWatchLogsClientMock: AwsStub + cloudWatchLogsClientMock: AwsStub ) => { cloudWatchLogsClientMock.on(DescribeLogGroupsCommand).resolves({}) cloudWatchLogsClientMock.on(DescribeSubscriptionFiltersCommand).resolves({}) @@ -146,21 +146,21 @@ export const mockCloudWatchLogsClientCommands = ( } export const mockCloudWatchLogStreams = ( - cloudWatchLogsClientMock: AwsStub, + cloudWatchLogsClientMock: AwsStub, logStreams: LogStream[] ) => { cloudWatchLogsClientMock.on(DescribeLogStreamsCommand).resolves({logStreams}) } export const mockCloudWatchLogEvents = ( - cloudWatchLogsClientMock: AwsStub, + cloudWatchLogsClientMock: AwsStub, events: OutputLogEvent[] ) => { cloudWatchLogsClientMock.on(GetLogEventsCommand).resolves({events}) } export const mockResourceTags = ( - lambdaClientMock: AwsStub, + lambdaClientMock: AwsStub, output: ListTagsCommandOutput ) => { lambdaClientMock.on(ListTagsCommand).resolves(output) diff --git a/src/commands/synthetics/__tests__/cli.test.ts b/src/commands/synthetics/__tests__/cli.test.ts index 5cb938876..7e67bf423 100644 --- a/src/commands/synthetics/__tests__/cli.test.ts +++ b/src/commands/synthetics/__tests__/cli.test.ts @@ -29,6 +29,8 @@ test('all option flags are supported', async () => { 'failOnMissingTests', 'failOnTimeout', 'files', + 'fips', + 'fipsIgnoreError', 'jUnitReport', 'mobileApplicationVersion', 'mobileApplicationVersionFilePath', @@ -62,6 +64,8 @@ describe('run-test', () => { const overrideEnv = { DATADOG_API_KEY: 'fake_api_key', DATADOG_APP_KEY: 'fake_app_key', + DATADOG_FIPS: 'true', + DATADOG_FIPS_IGNORE_ERROR: 'true', DATADOG_SITE: 'datadoghq.eu', DATADOG_SUBDOMAIN: 'custom', DATADOG_SYNTHETICS_BATCH_TIMEOUT: '1', @@ -153,6 +157,8 @@ describe('run-test', () => { failOnMissingTests: toBoolean(overrideEnv.DATADOG_SYNTHETICS_FAIL_ON_MISSING_TESTS), failOnTimeout: toBoolean(overrideEnv.DATADOG_SYNTHETICS_FAIL_ON_TIMEOUT), files: overrideEnv.DATADOG_SYNTHETICS_FILES.split(';'), + fipsEnabled: true, + fipsIgnoreError: true, jUnitReport: overrideEnv.DATADOG_SYNTHETICS_JUNIT_REPORT, publicIds: overrideEnv.DATADOG_SYNTHETICS_PUBLIC_IDS.split(';'), selectiveRerun: toBoolean(overrideEnv.DATADOG_SYNTHETICS_SELECTIVE_RERUN), @@ -223,6 +229,8 @@ describe('run-test', () => { failOnMissingTests: true, failOnTimeout: false, files: ['my-new-file'], + fipsEnabled: true, + fipsIgnoreError: true, // TODO SYNTH-12989: Clean up deprecated `global` in favor of `defaultTestOverrides` global: { allowInsecureCertificates: true, @@ -288,6 +296,8 @@ describe('run-test', () => { failOnMissingTests: true, failOnTimeout: false, files: ['new-file'], + fipsEnabled: true, + fipsIgnoreError: true, jUnitReport: 'junit-report.xml', mobileApplicationVersionFilePath: './path/to/application.apk', pollingTimeout: 2, @@ -345,6 +355,8 @@ describe('run-test', () => { command['failOnMissingTests'] = overrideCLI.failOnMissingTests command['failOnTimeout'] = overrideCLI.failOnTimeout command['files'] = overrideCLI.files + command['fipsEnabled'] = overrideCLI.fipsEnabled + command['fipsIgnoreError'] = overrideCLI.fipsIgnoreError command['jUnitReport'] = overrideCLI.jUnitReport command['mobileApplicationVersion'] = defaultTestOverrides.mobileApplicationVersion command['mobileApplicationVersionFilePath'] = overrideCLI.mobileApplicationVersionFilePath @@ -436,6 +448,8 @@ describe('run-test', () => { failOnMissingTests: true, failOnTimeout: false, files: ['new-file'], + fipsEnabled: true, + fipsIgnoreError: true, jUnitReport: 'junit-report.xml', pollingTimeout: 2, publicIds: ['ran-dom-id2'], @@ -500,6 +514,8 @@ describe('run-test', () => { failOnMissingTests: true, failOnTimeout: false, files: ['new-file'], + fipsEnabled: true, + fipsIgnoreError: true, jUnitReport: 'junit-report.xml', publicIds: ['ran-dom-id2'], pollingTimeout: 1, @@ -562,7 +578,7 @@ describe('run-test', () => { // // (config file < ENV < CLI < test file) => execute tests - describe('override precedence - config file < ENV < CLI < test file', () => { + describe('override precedence - [config file < ENV < CLI < test file]', () => { const configFile = { apiKey: 'config_file_api_key', appKey: 'config_file_app_key', @@ -598,6 +614,8 @@ describe('run-test', () => { failOnMissingTests: false, failOnTimeout: false, files: ['from_config_file.json'], + fipsEnabled: false, + fipsIgnoreError: false, // TODO SYNTH-12989: Clean up deprecated `global` in favor of `defaultTestOverrides` global: {}, jUnitReport: 'junit-report-from-config-file.xml', @@ -622,6 +640,8 @@ describe('run-test', () => { const overrideEnv = { DATADOG_API_KEY: 'env_api_key', DATADOG_APP_KEY: 'env_app_key', + DATADOG_FIPS: 'true', + DATADOG_FIPS_IGNORE_ERROR: 'true', DATADOG_SITE: 'us5.datadoghq.com', DATADOG_SUBDOMAIN: 'subdomain_from_env', DATADOG_SYNTHETICS_BATCH_TIMEOUT: '1', @@ -714,6 +734,8 @@ describe('run-test', () => { failOnMissingTests: toBoolean(overrideEnv.DATADOG_SYNTHETICS_FAIL_ON_MISSING_TESTS), failOnTimeout: toBoolean(overrideEnv.DATADOG_SYNTHETICS_FAIL_ON_TIMEOUT), files: overrideEnv.DATADOG_SYNTHETICS_FILES?.split(';'), + fipsEnabled: toBoolean(overrideEnv.DATADOG_FIPS), + fipsIgnoreError: toBoolean(overrideEnv.DATADOG_FIPS_IGNORE_ERROR), jUnitReport: overrideEnv.DATADOG_SYNTHETICS_JUNIT_REPORT, publicIds: overrideEnv.DATADOG_SYNTHETICS_PUBLIC_IDS?.split(';'), selectiveRerun: toBoolean(overrideEnv.DATADOG_SYNTHETICS_SELECTIVE_RERUN), @@ -754,6 +776,8 @@ describe('run-test', () => { failOnMissingTests: true, failOnTimeout: true, files: ['new-file-from-cli'], + fipsEnabled: true, + fipsIgnoreError: true, jUnitReport: 'junit-report-from-cli.xml', mobileApplicationVersionFilePath: './path/to/application-from-cli.apk', pollingTimeout: 10, @@ -815,6 +839,8 @@ describe('run-test', () => { command['failOnMissingTests'] = overrideCLI.failOnMissingTests command['failOnTimeout'] = overrideCLI.failOnTimeout command['files'] = overrideCLI.files + command['fipsEnabled'] = overrideCLI.fipsEnabled + command['fipsIgnoreError'] = overrideCLI.fipsIgnoreError command['jUnitReport'] = overrideCLI.jUnitReport command['mobileApplicationVersion'] = defaultTestOverrides.mobileApplicationVersion command['mobileApplicationVersionFilePath'] = overrideCLI.mobileApplicationVersionFilePath @@ -881,6 +907,8 @@ describe('run-test', () => { const overrideEnv = { DATADOG_API_KEY: 'env_api_key', DATADOG_APP_KEY: 'env_app_key', + DATADOG_FIPS: 'false', + DATADOG_FIPS_IGNORE_ERROR: 'false', DATADOG_SITE: 'us5.datadoghq.com', DATADOG_SYNTHETICS_CONFIG_PATH: 'path/to/config_from_env.json', DATADOG_SUBDOMAIN: 'subdomain_from_env', @@ -932,6 +960,8 @@ describe('run-test', () => { failOnMissingTests: false, failOnTimeout: false, files: ['file-from-cli-1;file-from-cli-2'], + fipsEnabled: true, + fipsIgnoreError: true, jUnitReport: 'junit-report-from-cli.xml', mobileApplicationVersionFilePath: './path/to/application-from-cli.apk', pollingTimeout: 10, @@ -995,6 +1025,8 @@ describe('run-test', () => { command['failOnMissingTests'] = overrideCLI.failOnMissingTests command['failOnTimeout'] = overrideCLI.failOnTimeout command['files'] = overrideCLI.files + command['fipsEnabled'] = overrideCLI.fipsEnabled + command['fipsIgnoreError'] = overrideCLI.fipsIgnoreError command['jUnitReport'] = overrideCLI.jUnitReport command['mobileApplicationVersion'] = defaultTestOverrides.mobileApplicationVersion command['mobileApplicationVersionFilePath'] = overrideCLI.mobileApplicationVersionFilePath diff --git a/src/commands/synthetics/__tests__/config-fixtures/config-with-all-keys.json b/src/commands/synthetics/__tests__/config-fixtures/config-with-all-keys.json index f80e96c67..62b409238 100644 --- a/src/commands/synthetics/__tests__/config-fixtures/config-with-all-keys.json +++ b/src/commands/synthetics/__tests__/config-fixtures/config-with-all-keys.json @@ -8,6 +8,8 @@ "failOnMissingTests": true, "failOnTimeout": false, "files": ["my-new-file"], + "fipsEnabled": true, + "fipsIgnoreError": true, "jUnitReport": "junit-report.xml", "global": { "allowInsecureCertificates": true, diff --git a/src/commands/synthetics/__tests__/fixtures.ts b/src/commands/synthetics/__tests__/fixtures.ts index e9324c446..7c841e114 100644 --- a/src/commands/synthetics/__tests__/fixtures.ts +++ b/src/commands/synthetics/__tests__/fixtures.ts @@ -76,6 +76,8 @@ export const ciConfig: RunTestsCommandConfig = { failOnMissingTests: false, failOnTimeout: true, files: [], + fipsEnabled: false, + fipsIgnoreError: false, jUnitReport: '', global: {}, defaultTestOverrides: {}, diff --git a/src/commands/synthetics/__tests__/run-tests-lib.test.ts b/src/commands/synthetics/__tests__/run-tests-lib.test.ts index 995c730e7..714332692 100644 --- a/src/commands/synthetics/__tests__/run-tests-lib.test.ts +++ b/src/commands/synthetics/__tests__/run-tests-lib.test.ts @@ -62,6 +62,8 @@ describe('run-test', () => { failOnMissingTests: false, failOnTimeout: true, files: ['{,!(node_modules)/**/}*.synthetics.json'], + fipsEnabled: false, + fipsIgnoreError: false, global: {}, // deprecated locations: [], // deprecated pollingTimeout: 2 * 60 * 1000, @@ -90,6 +92,8 @@ describe('run-test', () => { failOnMissingTests: false, failOnTimeout: true, files: ['{,!(node_modules)/**/}*.synthetics.json'], + fipsEnabled: false, + fipsIgnoreError: false, // TODO SYNTH-12989: Clean up deprecated `global` and `locations` global: {}, locations: [], diff --git a/src/commands/synthetics/interfaces.ts b/src/commands/synthetics/interfaces.ts index 29064a546..bc91b148e 100644 --- a/src/commands/synthetics/interfaces.ts +++ b/src/commands/synthetics/interfaces.ts @@ -497,6 +497,8 @@ export interface RunTestsCommandConfig extends SyntheticsCIConfig { failOnMissingTests: boolean failOnTimeout: boolean files: string[] + fipsEnabled: boolean + fipsIgnoreError: boolean // TODO SYNTH-12989: Clean up deprecated `global` in favor of `defaultTestOverrides` /** @deprecated This property is deprecated, please use `defaultTestOverrides` instead. */ global?: UserConfigOverride diff --git a/src/commands/synthetics/run-tests-command.ts b/src/commands/synthetics/run-tests-command.ts index 667889bc0..50bbd527a 100644 --- a/src/commands/synthetics/run-tests-command.ts +++ b/src/commands/synthetics/run-tests-command.ts @@ -2,6 +2,7 @@ import {Command, Option} from 'clipanion' import deepExtend from 'deep-extend' import terminalLink from 'terminal-link' +import {enableFips, UnsupportedFipsError} from '../../helpers/fips' import {removeUndefinedValues, resolveConfigFromFile} from '../../helpers/utils' import * as validation from '../../helpers/validation' import {isValidDatadogSite} from '../../helpers/validation' @@ -47,6 +48,8 @@ export const DEFAULT_COMMAND_CONFIG: RunTestsCommandConfig = { failOnMissingTests: false, failOnTimeout: true, files: [], + fipsEnabled: false, + fipsIgnoreError: false, // TODO SYNTH-12989: Clean up deprecated `global` in favor of `defaultTestOverrides` global: {}, jUnitReport: '', @@ -133,6 +136,12 @@ export class RunTestsCommand extends Command { private files = Option.Array('-f,--files', { description: `Glob pattern to detect Synthetic test ${$2('configuration files')}}.`, }) + private fipsEnabled = Option.Boolean('--fips', { + description: 'Use a FIPS compliant crypto provider. Throws an error if no FIPS compliant crypto provider are available.', + }) + private fipsIgnoreError = Option.Boolean('--fipsIgnoreError', { + description: `Prevent error when using the ${$1('--fips')} option.`, + }) private mobileApplicationVersion = Option.String('--mobileApplicationVersion', { description: 'Override the default mobile application version to test a different version within Datadog.', }) @@ -187,6 +196,21 @@ export class RunTestsCommand extends Command { return 1 } + if (this.config.fipsEnabled) { + try { + const fipsEnabled = enableFips() + if (!fipsEnabled) { + this.reporter.error('FIPS could not be enabled. The command will continue without FIPS mode.\n') + } + } catch (error) { + if (error instanceof UnsupportedFipsError) { + this.reporter.error('FIPS mode is not supported. The command will continue without FIPS mode.\n') + } + + return toExitCode(getExitReason(this.config, {error})) + } + } + if (this.config.jUnitReport) { reporters.push( new JUnitReporter({ @@ -277,6 +301,8 @@ export class RunTestsCommand extends Command { failOnMissingTests: toBoolean(process.env.DATADOG_SYNTHETICS_FAIL_ON_MISSING_TESTS), failOnTimeout: toBoolean(process.env.DATADOG_SYNTHETICS_FAIL_ON_TIMEOUT), files: process.env.DATADOG_SYNTHETICS_FILES?.split(';'), + fipsEnabled: toBoolean(process.env.DATADOG_FIPS), + fipsIgnoreError: toBoolean(process.env.DATADOG_FIPS_IGNORE_ERROR), jUnitReport: process.env.DATADOG_SYNTHETICS_JUNIT_REPORT, publicIds: process.env.DATADOG_SYNTHETICS_PUBLIC_IDS?.split(';'), selectiveRerun: toBoolean(process.env.DATADOG_SYNTHETICS_SELECTIVE_RERUN), @@ -357,6 +383,8 @@ export class RunTestsCommand extends Command { failOnMissingTests: this.failOnMissingTests, failOnTimeout: this.failOnTimeout, files: this.files, + fipsEnabled: this.fipsEnabled, + fipsIgnoreError: this.fipsIgnoreError, jUnitReport: this.jUnitReport, publicIds: this.publicIds, selectiveRerun: this.selectiveRerun, diff --git a/src/commands/test-fips/cli.ts b/src/commands/test-fips/cli.ts new file mode 100644 index 000000000..e6cb23634 --- /dev/null +++ b/src/commands/test-fips/cli.ts @@ -0,0 +1,24 @@ +import { Command } from 'clipanion' + +import { enableFips } from '../../helpers/fips' + +class TestFipsCommand extends Command { + public static paths = [['test-fips']] + + public static usage = Command.Usage({ + description: 'Test fips compliance of datadog-ci.', + }) + + public async execute() { + try { + const fipsEnabled = enableFips() + this.context.stdout.write(`FIPS is ${fipsEnabled ? 'enabled' : 'disabled'} (${fipsEnabled})\n`) + } catch (error) { + this.context.stdout.write(error.message) + } + + return 0 + } +} + +module.exports = [TestFipsCommand] diff --git a/standalone-e2e/standalone-binary.test.ts b/standalone-e2e/standalone-binary.test.ts index f5fdd573a..cc1d14d09 100644 --- a/standalone-e2e/standalone-binary.test.ts +++ b/standalone-e2e/standalone-binary.test.ts @@ -37,6 +37,13 @@ describe('standalone binary', () => { expect(binaryVersion.slice(1)).toEqual(version) }) }) + describe('test-fips', () => { + it('report when FIPS is unsupported', async () => { + const {stdout} = await execPromise(`${STANDALONE_BINARY_PATH} test-fips`) + const testFipsOutput = sanitizeOutput(stdout) + expect(testFipsOutput).toEqual('FIPS mode is not supported') + }) + }) describe('dsyms', () => { it('can be called', async () => { const {stdout} = await execPromise(`${STANDALONE_BINARY_PATH} dsyms upload --help`)