Skip to content

Commit

Permalink
Set OpenSSL paths in registry (#31960)
Browse files Browse the repository at this point in the history
  • Loading branch information
clarkb7 authored Jan 31, 2025
1 parent 2d57cea commit dcd0631
Show file tree
Hide file tree
Showing 13 changed files with 501 additions and 89 deletions.
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"

# 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?
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

0 comments on commit dcd0631

Please sign in to comment.