Repository: openshift/assisted-service (language: python)

Issue:
OCPBUGS-34181: Don't use the statically linked installer if FIPS is enabled
Adds a check for the FIPS status of the current system and blocks use of the statically linked installer binary if FIPS is enabled.

## List all the issues related to this PR

Resolves https://issues.redhat.com//browse/OCPBUGS-34181

- [ ] New Feature <!-- new functionality -->
- [ ] Enhancement <!-- refactor, code changes, improvement, that won't add new features -->
- [x] Bug fix
- [ ] Tests
- [ ] Documentation
- [ ] CI/CD <!-- Notice that changes for Dockerfiles/Jenkinsfiles aren't tested in CI due to a known bug. -->

## What environments does this code impact?

Mostly operator and ABI since FIPS will never be active in the Cloud.

- [ ] Automation (CI, tools, etc)
- [ ] Cloud
- [x] Operator Managed Deployments
- [ ] None

## How was this code tested?

<!-- Please, select one or more if needed: -->

- [ ] assisted-test-infra environment
- [ ] dev-scripts environment
- [ ] Reviewer's test appreciated
- [x] Waiting for CI to do a full test run
- [ ] Manual (Elaborate on how it was tested)
- [ ] No tests needed

## Checklist

- [x] Title and description added to both, commit and PR.
- [x] Relevant issues have been associated (see [CONTRIBUTING] guide)
- [x] This change does not require a documentation update (docstring, `docs`, README, etc)
- [x] Does this change include unit-tests (note that code changes require unit-tests)

## Reviewers Checklist

- Are the title and description (in both PR and commit) meaningful and clear?
- Is there a bug required (and linked) for this change?
- Should this PR be backported?



Discussion / hints:

Fix patch:
diff --git a/cmd/agentbasedinstaller/register.go b/cmd/agentbasedinstaller/register.go
index 1118df38a08..71c340d9451 100644
--- a/cmd/agentbasedinstaller/register.go
+++ b/cmd/agentbasedinstaller/register.go
@@ -18,6 +18,7 @@ import (
 	"github.com/openshift/assisted-service/internal/common"
 	"github.com/openshift/assisted-service/internal/controller/controllers"
 	"github.com/openshift/assisted-service/internal/oc"
+	"github.com/openshift/assisted-service/internal/system"
 	"github.com/openshift/assisted-service/models"
 	errorutil "github.com/openshift/assisted-service/pkg/error"
 	"github.com/openshift/assisted-service/pkg/executer"
@@ -276,8 +277,12 @@ func getReleaseVersion(clusterImageSetPath string) (string, error) {
 func getReleaseVersionAndCpuArch(log *log.Logger, releaseImage string, releaseMirror string, pullSecret string) (string, string, error) {
 	// releaseImage is in the form: quay.io:443/openshift-release-dev/ocp-release:4.9.17-x86_64
 	mirrorRegistriesBuilder := mirrorregistries.New()
-	releaseHandler := oc.NewRelease(&executer.CommonExecuter{},
-		oc.Config{MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay}, mirrorRegistriesBuilder)
+	releaseHandler := oc.NewRelease(
+		&executer.CommonExecuter{},
+		oc.Config{MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay},
+		mirrorRegistriesBuilder,
+		system.NewLocalSystemInfo(),
+	)
 
 	version, versionError := releaseHandler.GetOpenshiftVersion(log, releaseImage, releaseMirror, pullSecret)
 	if versionError != nil {
diff --git a/cmd/main.go b/cmd/main.go
index 92aebbf2a16..53eda14ad1e 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -51,6 +51,7 @@ import (
 	"github.com/openshift/assisted-service/internal/spec"
 	"github.com/openshift/assisted-service/internal/spoke_k8s_client"
 	"github.com/openshift/assisted-service/internal/stream"
+	"github.com/openshift/assisted-service/internal/system"
 	"github.com/openshift/assisted-service/internal/uploader"
 	"github.com/openshift/assisted-service/internal/usage"
 	"github.com/openshift/assisted-service/internal/versions"
@@ -301,8 +302,12 @@ func main() {
 	var startupLeader leader.ElectorInterface
 
 	mirrorRegistriesBuilder := mirrorregistries.New()
-	releaseHandler := oc.NewRelease(&executer.CommonExecuter{},
-		oc.Config{MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay}, mirrorRegistriesBuilder)
+	releaseHandler := oc.NewRelease(
+		&executer.CommonExecuter{},
+		oc.Config{MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay},
+		mirrorRegistriesBuilder,
+		system.NewLocalSystemInfo(),
+	)
 	extracterHandler := oc.NewExtracter(&executer.CommonExecuter{},
 		oc.Config{MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay})
 
diff --git a/internal/ignition/ignition.go b/internal/ignition/ignition.go
index 78bdf62269c..c4fa470b53c 100644
--- a/internal/ignition/ignition.go
+++ b/internal/ignition/ignition.go
@@ -38,6 +38,7 @@ import (
 	"github.com/openshift/assisted-service/internal/oc"
 	"github.com/openshift/assisted-service/internal/operators"
 	"github.com/openshift/assisted-service/internal/provider/registry"
+	"github.com/openshift/assisted-service/internal/system"
 	"github.com/openshift/assisted-service/internal/versions"
 	"github.com/openshift/assisted-service/models"
 	"github.com/openshift/assisted-service/pkg/auth"
@@ -327,8 +328,12 @@ func (g *installerGenerator) Generate(ctx context.Context, installConfig []byte,
 	}
 
 	mirrorRegistriesBuilder := mirrorregistries.New()
-	ocRelease := oc.NewRelease(&executer.CommonExecuter{}, oc.Config{
-		MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay}, mirrorRegistriesBuilder)
+	ocRelease := oc.NewRelease(
+		&executer.CommonExecuter{},
+		oc.Config{MaxTries: oc.DefaultTries, RetryDelay: oc.DefaltRetryDelay},
+		mirrorRegistriesBuilder,
+		system.NewLocalSystemInfo(),
+	)
 
 	release, err := g.installerCache.Get(g.installerReleaseImageOverride, g.releaseImageMirror,
 		g.cluster.PullSecret, ocRelease, g.cluster.OpenshiftVersion)
diff --git a/internal/oc/release.go b/internal/oc/release.go
index 21f827c4c49..4686da7630e 100644
--- a/internal/oc/release.go
+++ b/internal/oc/release.go
@@ -13,6 +13,7 @@ import (
 	"github.com/hashicorp/go-version"
 	operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1"
 	"github.com/openshift/assisted-service/internal/common"
+	"github.com/openshift/assisted-service/internal/system"
 	"github.com/openshift/assisted-service/pkg/executer"
 	"github.com/openshift/assisted-service/pkg/mirrorregistries"
 	"github.com/patrickmn/go-cache"
@@ -60,14 +61,20 @@ type release struct {
 	executer                executer.Executer
 	config                  Config
 	mirrorRegistriesBuilder mirrorregistries.MirrorRegistriesConfigBuilder
+	sys                     system.SystemInfo
 
 	// A map for caching images (image name > release image URL > image)
 	imagesMap common.ExpiringCache
 }
 
-func NewRelease(executer executer.Executer, config Config, mirrorRegistriesBuilder mirrorregistries.MirrorRegistriesConfigBuilder) Release {
-	return &release{executer: executer, config: config, imagesMap: common.NewExpiringCache(cache.NoExpiration, cache.NoExpiration),
-		mirrorRegistriesBuilder: mirrorRegistriesBuilder}
+func NewRelease(executer executer.Executer, config Config, mirrorRegistriesBuilder mirrorregistries.MirrorRegistriesConfigBuilder, sys system.SystemInfo) Release {
+	return &release{
+		executer:                executer,
+		config:                  config,
+		imagesMap:               common.NewExpiringCache(cache.NoExpiration, cache.NoExpiration),
+		mirrorRegistriesBuilder: mirrorRegistriesBuilder,
+		sys:                     sys,
+	}
 }
 
 const (
@@ -347,16 +354,23 @@ func (r *release) Extract(log logrus.FieldLogger, releaseImage string, releaseIm
 func (r *release) GetReleaseBinaryPath(releaseImage string, cacheDir string, ocpVersion string) (workdir string, binary string, path string, err error) {
 	binary = "openshift-baremetal-install"
 
-	// use the statically linked binary for 4.16 and up since our container is el8
-	// based and the baremetal binary for those versions is dynamically linked
-	// against el9 libaries
-	staticLinkingRequiredVersion := version.Must(version.NewVersion(staticInstallerRequiredVersion))
-	v, err := version.NewVersion(ocpVersion)
+	fipsEnabled, err := r.sys.FIPSEnabled()
 	if err != nil {
 		return "", "", "", err
 	}
-	if v.GreaterThanOrEqual(staticLinkingRequiredVersion) {
-		binary = "openshift-install"
+
+	if !fipsEnabled {
+		// use the statically linked binary for 4.16 and up since our container is el8
+		// based and the baremetal binary for those versions is dynamically linked
+		// against el9 libaries
+		staticLinkingRequiredVersion := version.Must(version.NewVersion(staticInstallerRequiredVersion))
+		v, err := version.NewVersion(ocpVersion)
+		if err != nil {
+			return "", "", "", err
+		}
+		if v.GreaterThanOrEqual(staticLinkingRequiredVersion) {
+			binary = "openshift-install"
+		}
 	}
 
 	workdir = filepath.Join(cacheDir, releaseImage)
diff --git a/internal/system/mock_system.go b/internal/system/mock_system.go
new file mode 100644
index 00000000000..ff3db9e3f94
--- /dev/null
+++ b/internal/system/mock_system.go
@@ -0,0 +1,49 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: system.go
+
+// Package system is a generated GoMock package.
+package system
+
+import (
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockSystemInfo is a mock of SystemInfo interface.
+type MockSystemInfo struct {
+	ctrl     *gomock.Controller
+	recorder *MockSystemInfoMockRecorder
+}
+
+// MockSystemInfoMockRecorder is the mock recorder for MockSystemInfo.
+type MockSystemInfoMockRecorder struct {
+	mock *MockSystemInfo
+}
+
+// NewMockSystemInfo creates a new mock instance.
+func NewMockSystemInfo(ctrl *gomock.Controller) *MockSystemInfo {
+	mock := &MockSystemInfo{ctrl: ctrl}
+	mock.recorder = &MockSystemInfoMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockSystemInfo) EXPECT() *MockSystemInfoMockRecorder {
+	return m.recorder
+}
+
+// FIPSEnabled mocks base method.
+func (m *MockSystemInfo) FIPSEnabled() (bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "FIPSEnabled")
+	ret0, _ := ret[0].(bool)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// FIPSEnabled indicates an expected call of FIPSEnabled.
+func (mr *MockSystemInfoMockRecorder) FIPSEnabled() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FIPSEnabled", reflect.TypeOf((*MockSystemInfo)(nil).FIPSEnabled))
+}
diff --git a/internal/system/system.go b/internal/system/system.go
new file mode 100644
index 00000000000..a58193e9bb6
--- /dev/null
+++ b/internal/system/system.go
@@ -0,0 +1,46 @@
+package system
+
+import (
+	"os"
+	"strings"
+)
+
+const (
+	fipsFile = "/proc/sys/crypto/fips_enabled"
+)
+
+//go:generate mockgen -source=system.go -package=system -destination=mock_system.go
+type SystemInfo interface {
+	FIPSEnabled() (bool, error)
+}
+
+type fileReader func(name string) ([]byte, error)
+
+type localSystemInfo struct {
+	fileReader fileReader
+}
+
+var _ SystemInfo = &localSystemInfo{}
+
+func NewLocalSystemInfo() SystemInfo {
+	return &localSystemInfo{
+		fileReader: os.ReadFile,
+	}
+}
+
+func (s *localSystemInfo) FIPSEnabled() (bool, error) {
+	content, err := s.fileReader(fipsFile)
+	if err != nil {
+		// if the FIPS file doesn't exist then it surely is not enabled
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		return false, err
+	}
+
+	if strings.TrimSpace(string(content)) == "1" {
+		return true, nil
+	}
+
+	return false, nil
+}


Test patch:
diff --git a/internal/oc/release_test.go b/internal/oc/release_test.go
index 89b1903b7bf..f632c11aefb 100644
--- a/internal/oc/release_test.go
+++ b/internal/oc/release_test.go
@@ -13,6 +13,7 @@ import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/openshift/assisted-service/internal/common"
+	"github.com/openshift/assisted-service/internal/system"
 	"github.com/openshift/assisted-service/pkg/executer"
 	"github.com/openshift/assisted-service/pkg/mirrorregistries"
 	logrus "github.com/sirupsen/logrus"
@@ -35,18 +36,20 @@ var test_skopeo_multiarch_image_output string
 
 var _ = Describe("oc", func() {
 	var (
-		oc           Release
-		tempFilePath string
-		ctrl         *gomock.Controller
-		mockExecuter *executer.MockExecuter
+		oc             Release
+		tempFilePath   string
+		ctrl           *gomock.Controller
+		mockExecuter   *executer.MockExecuter
+		mockSystemInfo *system.MockSystemInfo
 	)
 
 	BeforeEach(func() {
 		ctrl = gomock.NewController(GinkgoT())
 		mockExecuter = executer.NewMockExecuter(ctrl)
+		mockSystemInfo = system.NewMockSystemInfo(ctrl)
 		config := Config{MaxTries: DefaultTries, RetryDelay: time.Millisecond}
 		mirrorRegistriesBuilder := mirrorregistries.New()
-		oc = NewRelease(mockExecuter, config, mirrorRegistriesBuilder)
+		oc = NewRelease(mockExecuter, config, mirrorRegistriesBuilder, mockSystemInfo)
 		tempFilePath = "/tmp/pull-secret"
 		mockExecuter.EXPECT().TempFile(gomock.Any(), gomock.Any()).DoAndReturn(
 			func(dir, pattern string) (*os.File, error) {
@@ -368,6 +371,10 @@ var _ = Describe("oc", func() {
 	})
 
 	Context("Extract", func() {
+		BeforeEach(func() {
+			mockSystemInfo.EXPECT().FIPSEnabled().Return(false, nil).AnyTimes()
+		})
+
 		It("extract baremetal-install from release image", func() {
 			command := fmt.Sprintf(templateExtract+" --registry-config=%s",
 				baremetalInstallBinary, filepath.Join(cacheDir, releaseImage), false, releaseImage, tempFilePath)
@@ -606,39 +613,92 @@ var _ = Describe("getIcspFileFromRegistriesConfig", func() {
 })
 
 var _ = Describe("GetReleaseBinaryPath", func() {
-	var r Release
+	var (
+		ctrl           *gomock.Controller
+		mockSystemInfo *system.MockSystemInfo
+		r              Release
+	)
+
 	BeforeEach(func() {
-		r = NewRelease(nil, Config{}, nil)
+		ctrl = gomock.NewController(GinkgoT())
+		mockSystemInfo = system.NewMockSystemInfo(ctrl)
+		r = NewRelease(nil, Config{}, nil, mockSystemInfo)
 	})
 
-	It("returns the openshift-baremetal-install binary for versions earlier than 4.16", func() {
-		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.15.0")
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(bin).To(Equal("openshift-baremetal-install"))
+	AfterEach(func() {
+		ctrl.Finish()
 	})
 
-	It("returns the openshift-install binary for 4.16.0", func() {
-		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0")
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(bin).To(Equal("openshift-install"))
-	})
+	Context("with FIPS disabled", func() {
+		BeforeEach(func() {
+			mockSystemInfo.EXPECT().FIPSEnabled().Return(false, nil).AnyTimes()
+		})
 
-	It("returns the openshift-install binary for 4.16 pre release", func() {
-		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0-ec.6")
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(bin).To(Equal("openshift-install"))
-		_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.16.0-rc.0")
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(bin).To(Equal("openshift-install"))
+		It("returns the openshift-baremetal-install binary for versions earlier than 4.16", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.15.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+		})
+
+		It("returns the openshift-install binary for 4.16.0", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-install"))
+		})
+
+		It("returns the openshift-install binary for 4.16 pre release", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0-ec.6")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-install"))
+			_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.16.0-rc.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-install"))
+		})
+
+		It("returns the openshift-install binary for versions later than 4.16.0", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.17.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-install"))
+			_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.18.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-install"))
+		})
 	})
 
-	It("returns the openshift-install binary for versions later than 4.16.0", func() {
-		_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.17.0")
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(bin).To(Equal("openshift-install"))
-		_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.18.0")
-		Expect(err).ShouldNot(HaveOccurred())
-		Expect(bin).To(Equal("openshift-install"))
+	Context("with FIPS enabled", func() {
+		BeforeEach(func() {
+			mockSystemInfo.EXPECT().FIPSEnabled().Return(true, nil).AnyTimes()
+		})
+
+		It("returns the openshift-baremetal-install binary for versions earlier than 4.16", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.15.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+		})
+
+		It("returns the openshift-baremetal-install binary for 4.16.0", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+		})
+
+		It("returns the openshift-install binary for 4.16 pre release", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.16.0-ec.6")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+			_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.16.0-rc.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+		})
+
+		It("returns the openshift-install binary for versions later than 4.16.0", func() {
+			_, bin, _, err := r.GetReleaseBinaryPath("image", "dir", "4.17.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+			_, bin, _, err = r.GetReleaseBinaryPath("image", "dir", "4.18.0")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(bin).To(Equal("openshift-baremetal-install"))
+		})
 	})
 })
 
diff --git a/internal/system/system_test.go b/internal/system/system_test.go
new file mode 100644
index 00000000000..ec889c1ac07
--- /dev/null
+++ b/internal/system/system_test.go
@@ -0,0 +1,39 @@
+package system
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+)
+
+var _ = DescribeTable("FIPSEnabled",
+	func(fileContent []byte, readError error, expectedEnabled bool, expectedError bool) {
+		reader := func(name string) ([]byte, error) {
+			Expect(name).To(Equal(fipsFile))
+			return fileContent, readError
+		}
+		sys := localSystemInfo{fileReader: reader}
+		enabled, err := sys.FIPSEnabled()
+		if expectedError {
+			Expect(err).ToNot(BeNil())
+			return
+		}
+		Expect(err).To(BeNil())
+		Expect(enabled).To(Equal(expectedEnabled))
+	},
+	Entry("returns true when the file exists with 1", []byte("1"), nil, true, false),
+	Entry("returns true when the file exists with 1 with whitespace", []byte("1\n"), nil, true, false),
+	Entry("returns false when the file exists with 0", []byte("0"), nil, false, false),
+	Entry("returns false when the file exists with 0 with whitespace", []byte("0 \n"), nil, false, false),
+	Entry("returns false when the file does not exist", []byte(""), os.ErrNotExist, false, false),
+	Entry("returns an error when a error other than NotExist is created", []byte(""), fmt.Errorf("some error"), false, true),
+)
+
+func TestSystem(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "system tests")
+}