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

Port Linux FIPS ciphers E2E test to Windows #33565

Merged
merged 5 commits into from
Feb 3, 2025
Merged
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
5 changes: 5 additions & 0 deletions .gitlab/e2e/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ new-e2e-fips-compliance-test:
variables:
TARGETS: ./tests/fips-compliance
TEAM: agent-runtimes
parallel:
matrix:
- EXTRA_PARAMS: --run "TestFIPSCiphersLinuxSuite$"
- EXTRA_PARAMS: --run "TestLinuxFIPSComplianceSuite$"

new-e2e-windows-fips-compliance-test:
extends: .new_e2e_template
Expand All @@ -233,6 +237,7 @@ new-e2e-windows-fips-compliance-test:
parallel:
matrix:
- EXTRA_PARAMS: --run "TestWindowsVM$"
- EXTRA_PARAMS: --run "TestFIPSCiphersWindowsSuite$"

new-e2e-windows-service-test:
extends: .new_e2e_template
Expand Down
151 changes: 151 additions & 0 deletions test/new-e2e/tests/fips-compliance/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// 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 contains tests for the FIPS Agent runtime behavior
package fipscompliance

import (
"fmt"
"strings"
"time"

"github.com/DataDog/datadog-agent/test/new-e2e/pkg/components"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/e2e/client"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

type cipherTestCase struct {
cert string
cipher string
tlsMax string
want bool
}

// fipsServer is a helper for interacting with a datadog/apps-fips-server container
type fipsServer struct {
composeFiles string
dockerHost *components.RemoteHost
}

func newFIPSServer(dockerHost *components.RemoteHost, composeFiles string) fipsServer {
s := fipsServer{
dockerHost: dockerHost,
composeFiles: composeFiles,
}
return s
}

func (s *fipsServer) Start(t *testing.T, tc cipherTestCase) {
require.EventuallyWithT(t, func(c *assert.CollectT) {
// stop currently running server, if any, so we can reset logs+env
s.Stop()

// start datadog/apps-fips-server with env vars from the test case
envVars := map[string]string{
"CERT": tc.cert,
}
if tc.cipher != "" {
envVars["CIPHER"] = fmt.Sprintf("-c %s", tc.cipher)
}
if tc.tlsMax != "" {
envVars["TLS_MAX"] = fmt.Sprintf("--tls-max %s", tc.tlsMax)
}

cmd := fmt.Sprintf("docker-compose -f %s up --detach --wait --timeout 300", strings.TrimSpace(s.composeFiles))
_, err := s.dockerHost.Execute(cmd, client.WithEnvVariables(envVars))
if err != nil {
t.Logf("Error starting fips-server: %v", err)
require.NoError(c, err)
}
assert.Nil(c, err)
}, 60*time.Second, 20*time.Second)

// Wait for container to start and ensure it's a fresh instance
require.EventuallyWithT(t, func(c *assert.CollectT) {
serverLogs, _ := s.dockerHost.Execute("docker logs dd-fips-server")
assert.Contains(c, serverLogs, "Server Starting...", "Server should start")
assert.Equal(c, 1, strings.Count(serverLogs, "Server Starting..."), "Server should start only once, logs from previous runs should not be present")
}, 60*time.Second, 5*time.Second)
}

func (s *fipsServer) Logs() string {
return s.dockerHost.MustExecute("docker logs dd-fips-server")
}

func (s *fipsServer) Stop() {
fipsContainer := s.dockerHost.MustExecute("docker container ls -a --filter name=dd-fips-server --format '{{.Names}}'")
if fipsContainer != "" {
s.dockerHost.MustExecute(fmt.Sprintf("docker-compose -f %s down fips-server", strings.TrimSpace(s.composeFiles)))
}
}

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: "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: "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_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},
// TODO: the below are approved for TLS 1.3 but not supported by our fips-server yet
// see https://github.com/DataDog/test-infra-definitions/blob/221bbc806266eb15b90cb875deb79180e7591fbc/components/datadog/apps/fips/images/fips-server/src/tls.go#L48-L58
// {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},
}
)

type fipsServerSuite[Env any] struct {
e2e.BaseSuite[Env]

fipsServer fipsServer
// generates traffic to the FIPS server when called
generateTestTraffic func()
}

// TestFIPSCiphers tests that generateTestTraffic communicates with fipsServer as defined
// in each test case. Some cases assert that a FIPS-compliant cipher is used, others assert that a non-FIPS cipher is not used.
func (s *fipsServerSuite[Env]) TestFIPSCiphers() {
for _, tc := range testcases {
s.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
s.fipsServer.Start(s.T(), tc)
s.T().Cleanup(func() {
s.fipsServer.Stop()
})

s.generateTestTraffic()

serverLogs := s.fipsServer.Logs()
if tc.want {
assert.Contains(s.T(), serverLogs, fmt.Sprintf("Negotiated cipher suite: %s", tc.cipher))
} else {
assert.Contains(s.T(), serverLogs, "no cipher suite supported by both client and server")
}
})
}
}

// TestFIPSCiphersTLSVersion tests that generateTestTraffic rejects fipsServer when the TLS version is too low
func (s *fipsServerSuite[Env]) TestFIPSCiphersTLSVersion() {
tc := cipherTestCase{cert: "rsa", tlsMax: "1.1"}
s.fipsServer.Start(s.T(), tc)
s.T().Cleanup(func() {
s.fipsServer.Stop()
})

s.generateTestTraffic()

serverLogs := s.fipsServer.Logs()
assert.Contains(s.T(), serverLogs, "tls: client offered only unsupported version")
}
122 changes: 19 additions & 103 deletions test/new-e2e/tests/fips-compliance/fips_ciphers_nix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ package fipscompliance
import (
_ "embed"
"fmt"
"os"
"strings"
"time"

"testing"

Expand All @@ -18,50 +18,25 @@ import (
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: "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: "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_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},
// TODO: the below are approved for TLS 1.3 but not supported by our fips-server yet
// see https://github.com/DataDog/test-infra-definitions/blob/221bbc806266eb15b90cb875deb79180e7591fbc/components/datadog/apps/fips/images/fips-server/src/tls.go#L48-L58
// {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},
}
"github.com/stretchr/testify/require"
)

//go:embed fixtures/docker-compose.yaml
var dockerCompose string

type fipsServerSuite struct {
e2e.BaseSuite[environments.DockerHost]
type fipsServerLinuxSuite struct {
fipsServerSuite[environments.DockerHost]
}

func TestFIPSCiphersSuite(t *testing.T) {
func TestFIPSCiphersLinuxSuite(t *testing.T) {
require.NotEmpty(t, os.Getenv("E2E_COMMIT_SHA"), "E2E_COMMIT_SHA must be set")
require.NotEmpty(t, os.Getenv("E2E_PIPELINE_ID"), "E2E_PIPELINE_ID must be set")
clarkb7 marked this conversation as resolved.
Show resolved Hide resolved

e2e.Run(
t,
&fipsServerSuite{},
&fipsServerLinuxSuite{},
e2e.WithProvisioner(
awsdocker.Provisioner(
awsdocker.WithAgentOptions(
Expand All @@ -74,76 +49,17 @@ func TestFIPSCiphersSuite(t *testing.T) {
)
}

func (v *fipsServerSuite) TestFIPSCiphersFIPSEnabled() {
composeFiles := strings.Split(v.Env().RemoteHost.MustExecute(`docker inspect --format='{{index (index .Config.Labels "com.docker.compose.project.config_files")}}' datadog-agent`), ",")
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.Contains(v.T(), serverLogs, fmt.Sprintf("Negotiated cipher suite: %s", tc.cipher))
} else {
assert.Contains(v.T(), serverLogs, "no cipher suite supported by both client and server")
}
})
}
}
func (s *fipsServerLinuxSuite) SetupSuite() {
s.BaseSuite.SetupSuite()

func (v *fipsServerSuite) TestFIPSCiphersTLSVersion() {
composeFiles := strings.Split(v.Env().RemoteHost.MustExecute(`docker inspect --format='{{index (index .Config.Labels "com.docker.compose.project.config_files")}}' datadog-agent`), ",")
host := s.Env().RemoteHost
// lookup the compose file used by environments.DockerHost
composeFiles := strings.Split(host.MustExecute(`docker inspect --format='{{index (index .Config.Labels "com.docker.compose.project.config_files")}}' datadog-agent`), ",")
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")
}, 60*time.Second, 5*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"`, strings.TrimSpace(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)))
formattedComposeFiles = strings.TrimSpace(formattedComposeFiles)
// supply workers to base fipsServerSuite
s.fipsServer = newFIPSServer(host, formattedComposeFiles)
s.generateTestTraffic = func() {
clarkb7 marked this conversation as resolved.
Show resolved Hide resolved
_ = host.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"`, formattedComposeFiles))
}
}
Loading
Loading