Skip to content
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
16 changes: 16 additions & 0 deletions .gitlab/e2e/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,22 @@ new-e2e-fips-compliance-test:
TARGETS: ./tests/fips-compliance
TEAM: agent-runtimes

new-e2e-windows-fips-compliance-test:
extends: .new_e2e_template
needs:
- !reference [.needs_new_e2e_template]
- qa_agent_fips
- deploy_windows_testing-a7-fips
rules:
- !reference [.on_arun_or_e2e_changes]
- !reference [.manual]
variables:
TARGETS: ./tests/fips-compliance
TEAM: windows-agent
parallel:
matrix:
- EXTRA_PARAMS: --run "TestWindowsVM$"

new-e2e-windows-service-test:
extends: .new_e2e_template
needs:
Expand Down
1 change: 1 addition & 0 deletions .gitlab/e2e_install_packages/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ new-e2e-windows-agent-a7-x86_64-fips:
parallel:
matrix:
- EXTRA_PARAMS: --run "TestFIPSAgent$"
- EXTRA_PARAMS: --run "TestFIPSAgentAltDir$"
- EXTRA_PARAMS: --run "TestFIPSAgentDoesNotInstallOverAgent$"
- EXTRA_PARAMS: --run "TestAgentDoesNotInstallOverFIPSAgent$"
rules:
Expand Down
21 changes: 21 additions & 0 deletions Dockerfiles/agent/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ foreach ($s in $services.Keys) {
Install-Service -SvcName $s -BinPath $services[$s][0] $services[$s][1]
}

# Since OpenSSL 3.4, the install paths can be retrieved from the registry instead of being hardcoded at build time.
# https://github.com/openssl/openssl/blob/master/NOTES-WINDOWS.md#installation-directories
# TODO: How best to configure the OpenSSL version?
$opensslVersion = "3.4"
if ($env:WITH_FIPS -eq "true") {
$opensslctx = "datadog-fips-agent"
} else {
$opensslctx = "datadog-agent"
}
$keyPath = "HKLM:\SOFTWARE\Wow6432Node\OpenSSL-$opensslVersion-$opensslctx"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why WOW6432?


# Create the registry key
if (-not (Test-Path $keyPath)) {
New-Item -Path $keyPath -Force
}

# Set the registry values
$embeddedPath = "C:\Program Files\Datadog\Datadog Agent\embedded3"
Set-ItemProperty -Path $keyPath -Name "OPENSSLDIR" -Value "$embeddedPath\ssl"
Set-ItemProperty -Path $keyPath -Name "ENGINESDIR" -Value "$embeddedPath\lib\engines-3"
Set-ItemProperty -Path $keyPath -Name "MODULESDIR" -Value "$embeddedPath\lib\ossl-modules"

# Allow to run agent binaries as `agent`
setx /m PATH "$Env:Path;C:/Program Files/Datadog/Datadog Agent/bin;C:/Program Files/Datadog/Datadog Agent/bin/agent"
Expand Down
24 changes: 19 additions & 5 deletions tasks/msi.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,23 @@ def _get_vs_build_command(cmd, vstudio_root=None):
return cmd


def _get_env(ctx, major_version='7', release_version='nightly'):
def _get_env(ctx, major_version='7', release_version='nightly-a7', flavor=None):
env = load_release_versions(ctx, release_version)

if flavor is None:
flavor = os.getenv("AGENT_FLAVOR", "")

env['PACKAGE_VERSION'] = get_version(
ctx, include_git=True, url_safe=True, major_version=major_version, include_pipeline_id=True
)
env['AGENT_FLAVOR'] = os.getenv("AGENT_FLAVOR", "")
env['AGENT_FLAVOR'] = flavor
env['AGENT_INSTALLER_OUTPUT_DIR'] = BUILD_OUTPUT_DIR
env['NUGET_PACKAGES_DIR'] = NUGET_PACKAGES_DIR
env['AGENT_PRODUCT_NAME_SUFFIX'] = ""
# Used for installation directories registry keys
# https://github.com/openssl/openssl/blob/master/NOTES-WINDOWS.md#installation-directories
# TODO: How best to configure the OpenSSL version?
Copy link
Contributor

@alexn-dd alexn-dd Feb 3, 2025

Choose a reason for hiding this comment

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

This is a good question, because the version is spread among various files and can easily lead to version conflicts. Is there a ticket for tracking?

Copy link
Contributor

Choose a reason for hiding this comment

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

Note that if there is ever another SSL vulnerability like the Heartbleed bug, we need to act ASAP.

env['AGENT_OPENSSL_VERSION'] = "3.4"

return env

Expand Down Expand Up @@ -281,12 +288,19 @@ def _msi_output_name(env):

@task
def build(
ctx, vstudio_root=None, arch="x64", major_version='7', release_version='nightly', debug=False, build_upgrade=False
ctx,
vstudio_root=None,
arch="x64",
major_version='7',
release_version='nightly-a7',
flavor=None,
debug=False,
build_upgrade=False,
):
"""
Build the MSI installer for the agent
"""
env = _get_env(ctx, major_version, release_version)
env = _get_env(ctx, major_version, release_version, flavor=flavor)
env['OMNIBUS_TARGET'] = 'main'
configuration = _msbuild_configuration(debug=debug)
build_outdir = build_out_dir(arch, configuration)
Expand Down Expand Up @@ -385,7 +399,7 @@ def build_installer(ctx, vstudio_root=None, arch="x64", debug=False):


@task
def test(ctx, vstudio_root=None, arch="x64", major_version='7', release_version='nightly', debug=False):
def test(ctx, vstudio_root=None, arch="x64", major_version='7', release_version='nightly-a7', debug=False):
"""
Run the unit test for the MSI installer for the agent
"""
Expand Down
27 changes: 27 additions & 0 deletions test/new-e2e/pkg/provisioners/aws/host/windows/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/e2e/client/agentclientparams"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/optional"
"github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/components/defender"
"github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/components/fipsmode"
)

const (
Expand All @@ -43,6 +44,7 @@ type ProvisionerParams struct {
activeDirectoryOptions []activedirectory.Option
defenderoptions []defender.Option
installerOptions []installer.Option
fipsModeOptions []fipsmode.Option
}

// ProvisionerOption is a provisioner option.
Expand Down Expand Up @@ -120,6 +122,16 @@ func WithDefenderOptions(opts ...defender.Option) ProvisionerOption {
}
}

// WithFIPSModeOptions configures FIPS mode on an EC2 VM.
//
// Ordered before the Agent setup.
func WithFIPSModeOptions(opts ...fipsmode.Option) ProvisionerOption {
return func(params *ProvisionerParams) error {
params.fipsModeOptions = append(params.fipsModeOptions, opts...)
return nil
}
}

// WithInstaller configures Datadog Installer on an EC2 VM.
func WithInstaller(opts ...installer.Option) ProvisionerOption {
return func(params *ProvisionerParams) error {
Expand Down Expand Up @@ -231,6 +243,20 @@ func Run(ctx *pulumi.Context, env *environments.WindowsHost, params *Provisioner
env.Installer = nil
}

if params.fipsModeOptions != nil {
fipsMode, err := fipsmode.New(awsEnv.CommonEnvironment, host, params.fipsModeOptions...)
if err != nil {
return err
}
// We want Agent setup to happen after FIPS mode setup, but only
// because that's the use case we are interested in.
// Ideally the provisioner would allow the user to specify the order of
// the resources, but that's not supported right now.
params.agentOptions = append(params.agentOptions,
agentparams.WithPulumiResourceOptions(
pulumi.DependsOn(fipsMode.Resources)))
}

return nil
}

Expand All @@ -243,6 +269,7 @@ func getProvisionerParams(opts ...ProvisionerOption) *ProvisionerParams {
fakeintakeOptions: []fakeintake.Option{},
// Disable Windows Defender on VMs by default
defenderoptions: []defender.Option{defender.WithDefenderDisabled()},
fipsModeOptions: []fipsmode.Option{},
}
err := optional.ApplyOptions(params, opts)
if err != nil {
Expand Down
200 changes: 200 additions & 0 deletions test/new-e2e/tests/fips-compliance/fips_win_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// 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 2024-present Datadog, Inc.

package fipscompliance

import (
_ "embed"
"fmt"
"path/filepath"
"time"

"github.com/DataDog/test-infra-definitions/components/datadog/agentparams"

fakeintakeclient "github.com/DataDog/datadog-agent/test/fakeintake/client"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/environments"
awsHostWindows "github.com/DataDog/datadog-agent/test/new-e2e/pkg/provisioners/aws/host/windows"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/e2e/client"
windowsCommon "github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/common"
windowsAgent "github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/common/agent"
"github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/components/fipsmode"

"testing"

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

//go:embed fixtures/e2e_fips_test.py
var fipsTestCheck string

type windowsVMSuite struct {
e2e.BaseSuite[environments.WindowsHost]

installPath string
}

// TestWindowsVM tests that the FIPS Agent can report metrics to the fakeintake
func TestWindowsVM(t *testing.T) {
suiteParams := []e2e.SuiteOption{e2e.WithProvisioner(awsHostWindows.Provisioner(
// Enable FIPS mode on the host (done before Agent install)
awsHostWindows.WithFIPSModeOptions(fipsmode.WithFIPSModeEnabled()),
awsHostWindows.WithAgentOptions(
// Use FIPS Agent package
agentparams.WithFlavor(agentparams.FIPSFlavor),
// Install custom check that reports the FIPS mode of Python
// TODO ADXT-881: Need forward slashes to workaround test-infra bug
agentparams.WithFile(
`C:/ProgramData/Datadog/checks.d/e2e_fips_test.py`,
fipsTestCheck,
false,
),
agentparams.WithFile(
`C:/ProgramData/Datadog/conf.d/e2e_fips_test.yaml`,
`
init_config:
instances: [{}]
`,
false,
),
),
))}

e2e.Run(t, &windowsVMSuite{}, suiteParams...)
}

func (s *windowsVMSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
host := s.Env().RemoteHost
var err error

s.installPath, err = windowsAgent.GetInstallPathFromRegistry(host)
s.Require().NoError(err)
}

// TestVersionCommands tests that the version command for each of the Agent binaries
// works when FIPS mode is enabled and panics when GOFIPS=1 AND the system is not in FIPS mode.
func (s *windowsVMSuite) TestVersionCommands() {
host := s.Env().RemoteHost

windowsCommon.EnableFIPSMode(host)
s.Run("System FIPS Enabled", func() {
s.testAgentBinaries(func(executable string) {
var err error
_, err = s.execAgentCommandWithFIPS(executable, "version")
s.Assert().NoError(err)
_, err = s.execAgentCommand(executable, "version")
s.Assert().NoError(err)
})
})

windowsCommon.DisableFIPSMode(host)
s.Run("System FIPS Disabled", func() {
s.testAgentBinaries(func(executable string) {
var err error
_, err = s.execAgentCommandWithFIPS(executable, "version")
assertErrorContainsFIPSPanic(s.T(), err, "agent should panic when GOFIPS=1 but system FIPS is disabled")
_, err = s.execAgentCommand(executable, "version")
s.Assert().NoError(err)
})
})
}

// TestAgentStatusOutput tests that the Agent status command reports the correct FIPS mode status
func (s *windowsVMSuite) TestAgentStatusOutput() {
host := s.Env().RemoteHost

windowsCommon.EnableFIPSMode(host)
s.Run("status command", func() {
s.Run("gofips enabled", func() {
status, err := s.execAgentCommandWithFIPS("agent.exe", "status")
require.NoError(s.T(), err)
assert.Contains(s.T(), status, "FIPS Mode: enabled")
})

s.Run("gofips disabled", func() {
status, err := s.execAgentCommand("agent.exe", "status")
require.NoError(s.T(), err)
assert.Contains(s.T(), status, "FIPS Mode: enabled", "FIPS Mode should not depend on GOFIPS")
})
})

windowsCommon.DisableFIPSMode(host)
s.Run("status command", func() {
s.Run("gofips disabled", func() {
status, err := s.execAgentCommand("agent.exe", "status")
require.NoError(s.T(), err)
assert.Contains(s.T(), status, "FIPS Mode: disabled")
})
})

}

// TestReportsFIPSStatusMetrics tests that the custom check from our fixtures
// is able to report metrics while in FIPS mode. These metric values are based
// on the status of Python's FIPS mode.
func (s *windowsVMSuite) TestReportsFIPSStatusMetrics() {
host := s.Env().RemoteHost
// Restart the Agent and reset the aggregator to ensure the metrics are fresh
// with FIPS mode enabled.
err := windowsCommon.StopService(host, "datadogagent")
require.NoError(s.T(), err)
err = s.Env().FakeIntake.Client().FlushServerAndResetAggregators()
require.NoError(s.T(), err)
err = windowsCommon.EnableFIPSMode(host)
require.NoError(s.T(), err)
err = windowsCommon.StartService(host, "datadogagent")
require.NoError(s.T(), err)

s.EventuallyWithT(func(c *assert.CollectT) {
metrics, err := s.Env().FakeIntake.Client().FilterMetrics("e2e.fips_mode", fakeintakeclient.WithMetricValueHigherThan(0))
assert.NoError(c, err)
assert.Greater(c, len(metrics), 0, "no 'e2e.fips_mode' with value higher than 0 yet")

metrics, err = s.Env().FakeIntake.Client().FilterMetrics("e2e.fips_dll_loaded", fakeintakeclient.WithMetricValueHigherThan(0))
assert.NoError(c, err)
assert.Greater(c, len(metrics), 0, "no 'e2e.fips_dll_loaded' with value higher than 0 yet")
}, 5*time.Minute, 10*time.Second)
}

// testAgentBinaries runs a subtest for each of the Agent binaries in the install path
func (s *windowsVMSuite) testAgentBinaries(subtest func(executable string)) {
executables := []string{"agent.exe", "agent/system-probe.exe", "agent/trace-agent.exe",
"agent/process-agent.exe", "agent/security-agent.exe"}
for _, executable := range executables {
s.Run(executable, func() {
subtest(executable)
})
}
}

func (s *windowsVMSuite) execAgentCommand(executable, command string, options ...client.ExecuteOption) (string, error) {
host := s.Env().RemoteHost
s.Require().NotEmpty(s.installPath)

agentPath := filepath.Join(s.installPath, "bin", executable)
cmd := fmt.Sprintf(`& "%s" %s`, agentPath, command)
return host.Execute(cmd, options...)
}

func (s *windowsVMSuite) execAgentCommandWithFIPS(executable, command string) (string, error) {
// There isn't support for appending env vars to client.ExecuteOption, so
// this function doesn't accept any other options.

// Setting GOFIPS=1 causes the Windows FIPS Agent to panic if the system is not in FIPS mode.
// This setting does NOT control whether the FIPS Agent uses FIPS-compliant crypto libraries,
// the System-level setting determines that.
// https://github.com/microsoft/go/tree/microsoft/main/eng/doc/fips#windows-fips-mode-cng
vars := client.EnvVar{
"GOFIPS": "1",
}

return s.execAgentCommand(executable, command, client.WithEnvVariables(vars))
}

func assertErrorContainsFIPSPanic(t *testing.T, err error, args ...interface{}) bool {
return assert.ErrorContains(t, err, "panic: cngcrypto: not in FIPS mode", args...)
}
Loading
Loading