diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9aee1786a66d4..16bc15225dbe4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -611,6 +611,7 @@ /test/new-e2e/tests/agent-subcommands @DataDog/agent-shared-components /test/new-e2e/tests/containers @DataDog/container-integrations @DataDog/container-platform /test/new-e2e/tests/discovery @DataDog/universal-service-monitoring +/test/new-e2e/tests/fips-compliance @DataDog/agent-shared-components /test/new-e2e/tests/ha-agent @DataDog/ndm-core /test/new-e2e/tests/language-detection @DataDog/container-intake /test/new-e2e/tests/ndm @DataDog/ndm-core diff --git a/.gitlab/dev_container_deploy/e2e.yml b/.gitlab/dev_container_deploy/e2e.yml index d0d721559059c..68d35d1f09731 100644 --- a/.gitlab/dev_container_deploy/e2e.yml +++ b/.gitlab/dev_container_deploy/e2e.yml @@ -17,6 +17,22 @@ qa_agent: IMG_SOURCES: ${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-amd64,${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-arm64,${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-win1809-amd64,${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-winltsc2022-amd64 IMG_DESTINATIONS: agent:${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA} +qa_agent_fips: + extends: .docker_publish_job_definition + stage: dev_container_deploy + rules: + - !reference [.except_mergequeue] + - !reference [.except_disable_e2e_tests] + - when: on_success + needs: + - docker_build_fips_agent7 + - docker_build_fips_agent7_arm64 + - docker_build_fips_agent7_windows2022_core + variables: + IMG_REGISTRIES: agent-qa + IMG_SOURCES: ${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-fips-amd64,${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-fips-arm64,${SRC_AGENT}:v${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-7-fips-winltsc2022-servercore-amd64 + IMG_DESTINATIONS: agent:${CI_PIPELINE_ID}-${CI_COMMIT_SHORT_SHA}-fips + qa_agent_jmx: extends: .docker_publish_job_definition stage: dev_container_deploy diff --git a/.gitlab/e2e/e2e.yml b/.gitlab/e2e/e2e.yml index 0a43ad218637d..5e74875fd16c6 100644 --- a/.gitlab/e2e/e2e.yml +++ b/.gitlab/e2e/e2e.yml @@ -196,6 +196,19 @@ new-e2e-agent-subcommands: - EXTRA_PARAMS: --run "Test(Linux|Windows)CheckSuite" - EXTRA_PARAMS: --run "Test(Linux|Windows)RunSuite" +new-e2e-fips-compliance-test: + extends: .new_e2e_template_needs_deb_x64 + needs: + - !reference [.needs_new_e2e_template] + - qa_agent_fips + - deploy_deb_testing-a7_x64 + rules: + - !reference [.on_asc_or_e2e_changes] + - !reference [.manual] + variables: + TARGETS: ./tests/fips-compliance + TEAM: agent-shared-components + new-e2e-windows-service-test: extends: .new_e2e_template needs: diff --git a/test/new-e2e/tests/fips-compliance/fips_ciphers_nix_test.go b/test/new-e2e/tests/fips-compliance/fips_ciphers_nix_test.go new file mode 100644 index 0000000000000..e18f605adfd9b --- /dev/null +++ b/test/new-e2e/tests/fips-compliance/fips_ciphers_nix_test.go @@ -0,0 +1,163 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package fipscompliance + +import ( + _ "embed" + "fmt" + "os" + "strings" + "time" + + "testing" + + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/environments" + awsdocker "github.com/DataDog/datadog-agent/test/new-e2e/pkg/provisioners/aws/docker" + + "github.com/DataDog/test-infra-definitions/components/datadog/dockeragentparams" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type cipherTestCase struct { + cert string + cipher string + tlsMax string + want bool +} + +var ( + testcases = []cipherTestCase{ + {cert: "ecc", cipher: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", want: true}, + {cert: "ecc", cipher: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", want: true}, + {cert: "ecc", cipher: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", want: false}, + {cert: "ecc", cipher: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", want: false}, + {cert: "ecc", cipher: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", want: false}, + {cert: "rsa", cipher: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", want: true}, + {cert: "rsa", cipher: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", want: true}, + {cert: "rsa", cipher: "TLS_AES_128_GCM_SHA256", tlsMax: "1.3", want: true}, + {cert: "rsa", cipher: "TLS_AES_256_GCM_SHA384", tlsMax: "1.3", want: true}, + {cert: "rsa", cipher: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", want: false}, + {cert: "rsa", cipher: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", want: false}, + {cert: "rsa", cipher: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", want: false}, + {cert: "rsa", cipher: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", want: false}, + } +) + +//go:embed fixtures/docker-compose.yaml +var dockerCompose string + +type fipsServerSuite struct { + e2e.BaseSuite[environments.DockerHost] +} + +func TestFIPSCiphersSuite(t *testing.T) { + e2e.Run(t, &fipsServerSuite{}, e2e.WithProvisioner(awsdocker.Provisioner()), e2e.WithSkipDeleteOnFailure()) +} + +func (v *fipsServerSuite) TestFIPSCiphersFIPSEnabled() { + var imageTag string + if os.Getenv("E2E_PIPELINE_ID") != "" && os.Getenv("CI_COMMIT_SHA") != "" { + imageTag = fmt.Sprintf("v%s-%s-7-fips-arm64", os.Getenv("E2E_PIPELINE_ID"), os.Getenv("CI_COMMIT_SHA")) + } else { + imageTag = "latest" + } + + fmt.Println("ImageTag: " + imageTag) + v.UpdateEnv( + awsdocker.Provisioner( + awsdocker.WithAgentOptions( + // dockeragentparams.WithImageTag(imageTag), // "registry.ddbuild.io/ci/datadog-agent/agent:v51515213-0796c161-7-fips-arm64"), + dockeragentparams.WithImageTag("registry.ddbuild.io/ci/datadog-agent/agent:v51515213-0796c161-7-fips-arm64"), + dockeragentparams.WithExtraComposeManifest("fips-server", pulumi.String(dockerCompose)), + ), + ), + ) + + composeFiles := strings.Split(v.Env().RemoteHost.MustExecute(`docker inspect --format='{{index (index .Config.Labels "com.docker.compose.project.config_files")}}' dd-fips-server`), ",") + formattedComposeFiles := strings.Join(composeFiles, " -f ") + + for _, tc := range testcases { + v.Run(fmt.Sprintf("FIPS enabled testing '%v -c %v' (should connect %v)", tc.cert, tc.cipher, tc.want), func() { + + // Start the fips-server and waits for it to be ready + runFipsServer(v, tc, formattedComposeFiles) + defer stopFipsServer(v, formattedComposeFiles) + + // Run diagnose to send requests and verify the server logs + runAgentDiagnose(v, formattedComposeFiles) + + serverLogs := v.Env().RemoteHost.MustExecute("docker logs dd-fips-server") + if tc.want { + assert.NotContains(v.T(), serverLogs, "no cipher suite supported by both client and server") + } else { + assert.Contains(v.T(), serverLogs, "no cipher suite supported by both client and server") + } + }) + } +} + +func (v *fipsServerSuite) TestFIPSCiphersTLSVersion() { + v.UpdateEnv( + awsdocker.Provisioner( + awsdocker.WithAgentOptions( + dockeragentparams.WithFullImagePath("registry.ddbuild.io/ci/datadog-agent/agent:v51515213-0796c161-7-fips-arm64"), + dockeragentparams.WithExtraComposeManifest("fips-server", pulumi.String(dockerCompose)), + ), + ), + ) + + composeFiles := strings.Split(v.Env().RemoteHost.MustExecute(`docker inspect --format='{{index (index .Config.Labels "com.docker.compose.project.config_files")}}' dd-fips-server`), ",") + formattedComposeFiles := strings.Join(composeFiles, " -f ") + + runFipsServer(v, cipherTestCase{cert: "rsa", tlsMax: "1.1"}, formattedComposeFiles) + defer stopFipsServer(v, formattedComposeFiles) + + runAgentDiagnose(v, formattedComposeFiles) + + serverLogs := v.Env().RemoteHost.MustExecute("docker logs dd-fips-server") + assert.Contains(v.T(), serverLogs, "tls: client offered only unsupported version") +} + +func runFipsServer(v *fipsServerSuite, tc cipherTestCase, composeFiles string) { + require.EventuallyWithT(v.T(), func(t *assert.CollectT) { + stopFipsServer(v, composeFiles) + envvar := fmt.Sprintf("CERT=%s", tc.cert) + if tc.cipher != "" { + envvar = fmt.Sprintf(`%s CIPHER="-c %s"`, envvar, tc.cipher) + } + if tc.tlsMax != "" { + envvar = fmt.Sprintf(`%s TLS_MAX="--tls-max %s"`, envvar, tc.tlsMax) + } + + _, err := v.Env().RemoteHost.Execute(fmt.Sprintf("%s docker-compose -f %s up --detach --wait --timeout 300", envvar, strings.TrimSpace(composeFiles))) + if err != nil { + v.T().Logf("Error starting fips-server: %v", err) + require.NoError(t, err) + } + assert.Nil(t, err) + }, 60*time.Second, 20*time.Second) + + require.EventuallyWithT(v.T(), func(t *assert.CollectT) { + serverLogs, _ := v.Env().RemoteHost.Execute("docker logs dd-fips-server") + assert.Contains(t, serverLogs, "Server Starting...", "Server should start") + assert.Equal(t, 1, strings.Count(serverLogs, "Server Starting..."), "Server should start only once, logs from previous runs should not be present") + }, 10*time.Second, 2*time.Second) +} + +func runAgentDiagnose(v *fipsServerSuite, composeFiles string) { + _ = v.Env().RemoteHost.MustExecute(fmt.Sprintf("docker-compose -f %s exec agent sh -c GOFIPS=1 DD_DD_URL=https://dd-fips-server:443 agent diagnose --include connectivity-datadog-core-endpoints --local", composeFiles)) +} + +func stopFipsServer(v *fipsServerSuite, composeFiles string) { + fipsContainer := v.Env().RemoteHost.MustExecute("docker container ls -a --filter name=dd-fips-server --format '{{.Names}}'") + if fipsContainer != "" { + v.Env().RemoteHost.MustExecute(fmt.Sprintf("docker-compose -f %s down fips-server", strings.TrimSpace(composeFiles))) + } +} diff --git a/test/new-e2e/tests/fips-compliance/fips_nix_test.go b/test/new-e2e/tests/fips-compliance/fips_nix_test.go new file mode 100644 index 0000000000000..c40a45b59aee0 --- /dev/null +++ b/test/new-e2e/tests/fips-compliance/fips_nix_test.go @@ -0,0 +1,78 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package fipscompliance + +import ( + _ "embed" + "fmt" + + "testing" + + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/environments" + awshost "github.com/DataDog/datadog-agent/test/new-e2e/pkg/provisioners/aws/host" + "github.com/DataDog/test-infra-definitions/components/datadog/agentparams" + "github.com/DataDog/test-infra-definitions/components/os" + "github.com/DataDog/test-infra-definitions/scenarios/aws/ec2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//go:embed fixtures/openssl-default.cnf +var defaultOpenSSLConfig []byte + +type LinuxFIPSComplianceSuite struct { + e2e.BaseSuite[environments.Host] +} + +func TestLinuxFIPSComplianceSuite(t *testing.T) { + e2e.Run(t, &LinuxFIPSComplianceSuite{}, + e2e.WithProvisioner(awshost.ProvisionerNoFakeIntake( + awshost.WithEC2InstanceOptions(ec2.WithOS(os.UbuntuDefault)), + awshost.WithAgentOptions(agentparams.WithFlavor("datadog-fips-agent")), + ))) +} + +func (v *LinuxFIPSComplianceSuite) TestFIPSDefaultConfig() { + status := v.Env().RemoteHost.MustExecute("sudo GOFIPS=0 datadog-agent status") + assert.NotContains(v.T(), status, "can't enable FIPS mode for OpenSSL") + assert.Contains(v.T(), status, "Status date") + + status = v.Env().RemoteHost.MustExecute("sudo GOFIPS=1 datadog-agent status") + assert.NotContains(v.T(), status, "can't enable FIPS mode for OpenSSL") + assert.Contains(v.T(), status, "Status date") +} + +func (v *LinuxFIPSComplianceSuite) TestFIPSNoFIPSProvider() { + v.Env().RemoteHost.MustExecute("sudo mv /opt/datadog-agent/embedded/ssl/openssl.cnf /opt/datadog-agent/embedded/ssl/openssl.cnf.tmp") + v.Env().RemoteHost.MustExecute(fmt.Sprintf(`sudo sh -c "echo '%s' > /opt/datadog-agent/embedded/ssl/openssl.cnf"`, defaultOpenSSLConfig)) + + status, err := v.Env().RemoteHost.Execute("sudo GOFIPS=0 datadog-agent status") + assert.Nil(v.T(), err) + assert.Contains(v.T(), status, "Status date") + + status, err = v.Env().RemoteHost.Execute("sudo GOFIPS=1 datadog-agent status") + require.NotNil(v.T(), err) + assert.Contains(v.T(), err.Error(), "can't enable FIPS mode for OpenSSL") + assert.NotContains(v.T(), status, "Status date") + + v.Env().RemoteHost.MustExecute("sudo mv /opt/datadog-agent/embedded/ssl/openssl.cnf.tmp /opt/datadog-agent/embedded/ssl/openssl.cnf") +} + +func (v *LinuxFIPSComplianceSuite) TestFIPSEnabledNoConfig() { + v.Env().RemoteHost.MustExecute("sudo mv /opt/datadog-agent/embedded/ssl/openssl.cnf /opt/datadog-agent/embedded/ssl/openssl.cnf.tmp") + + status, err := v.Env().RemoteHost.Execute("sudo GOFIPS=0 datadog-agent status") + assert.Nil(v.T(), err) + assert.Contains(v.T(), status, "Status date") + + status, err = v.Env().RemoteHost.Execute("sudo GOFIPS=1 datadog-agent status") + require.NotNil(v.T(), err) + assert.Contains(v.T(), err.Error(), "can't enable FIPS mode for OpenSSL") + assert.NotContains(v.T(), status, "Status date") + + v.Env().RemoteHost.MustExecute("sudo mv /opt/datadog-agent/embedded/ssl/openssl.cnf.tmp /opt/datadog-agent/embedded/ssl/openssl.cnf") +} diff --git a/test/new-e2e/tests/fips-compliance/fixtures/docker-compose.yaml b/test/new-e2e/tests/fips-compliance/fixtures/docker-compose.yaml new file mode 100644 index 0000000000000..baa9de8726857 --- /dev/null +++ b/test/new-e2e/tests/fips-compliance/fixtures/docker-compose.yaml @@ -0,0 +1,16 @@ +--- +version: "3.9" + +services: + agent: + image: "registry.ddbuild.io/ci/datadog-agent/agent:v51515213-0796c161-7-fips-arm64" + environment: + GOFIPS: 1 + DD_SKIP_SSL_VALIDATION: "true" + + fips-server: + container_name: "dd-fips-server" + image: "ghcr.io/datadog/apps-fips-server:main" + ports: + - "443:443" + entrypoint: ["./run.sh", "${CERT:-rsa}", "${CIPHER}", "${TLS_MAX}"] diff --git a/test/new-e2e/tests/fips-compliance/fixtures/openssl-default.cnf b/test/new-e2e/tests/fips-compliance/fixtures/openssl-default.cnf new file mode 100644 index 0000000000000..adfa225f64aaf --- /dev/null +++ b/test/new-e2e/tests/fips-compliance/fixtures/openssl-default.cnf @@ -0,0 +1,14 @@ +openssl_conf = openssl_init + +[openssl_init] +providers = provider_sect + +[provider_sect] +default = default_sect +legacy = legacy_sect + +[default_sect] +activate = 1 + +[legacy_sect] +activate = 1