Skip to content

Commit

Permalink
snapdtool: enable FIPS on classic without snap
Browse files Browse the repository at this point in the history
Enable Go FIPS runtime when executing on a classic distro when system is in FIPS
mode. This allows us to run a FIPS variant of the snapd deb package on a FIPS
enabled system and ensure that snapd will now attempt to setup the FIPS
execution mode. Note, this is transparent for snapd binaries that have not been
built with FIPS Go toolhchain.

Signed-off-by: Maciej Borzecki <[email protected]>
  • Loading branch information
bboozzoo authored and Meulengracht committed Sep 10, 2024
1 parent b074a2f commit 73efe2e
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 30 deletions.
53 changes: 30 additions & 23 deletions snapdtool/fips_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
package snapdtool

import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -90,15 +92,6 @@ func MaybeSetupFIPS() error {
return nil
}

snapdRev, err := osReadlink(snapdSnap)
if err != nil {
return err
}

currentRevSnapdSnap := filepath.Join(dirs.SnapMountDir, "snapd", snapdRev)

logger.Debugf("snapd snap: %s", currentRevSnapdSnap)

rootDir, exe, err := exeAndRoot()
if err != nil {
return err
Expand All @@ -107,27 +100,42 @@ func MaybeSetupFIPS() error {
logger.Debugf("self exe: %s", exe)
logger.Debugf("exe root dir: %q", rootDir)

// on a classic system we need to be reexecuted from the snapd snap for
// the FIPS setup to be relevant, but on core we are not reexeced but
// running directly from the mount of the snapd snap under
// /usr/lib/snapd, yet we still want to set up the right environment
if release.OnClassic {
if rootDir != currentRevSnapdSnap {
// this is only supported for reexecing from the snapd snap
return nil
}
snapdRev, err := osReadlink(snapdSnap)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}

lib, mod := findFIPSLibsAndModules(currentRevSnapdSnap)
var currentRevSnapdSnap string
if snapdRev != "" {
currentRevSnapdSnap = filepath.Join(dirs.SnapMountDir, "snapd", snapdRev)
logger.Debugf("snapd snap: %s", currentRevSnapdSnap)
}

env := append(os.Environ(), []string{
"SNAPD_FIPS_BOOTSTRAP=1",
// make FIPS mod required at runtime, if the module was not
// found or the setup is incorrect snapd will fail in a
// predictable way
// make FIPS mode required at runtime, if FIPS support in Go
// runtime cannot be completed successfully the startup will
// fail in a predictable manner
"GOFIPS=1",
}...)

// need we need to set up environment such that the FIPS library module
// will be picked up at startup, however this is only relevant in the
// following cases:
// - on classic, when reexecuted from the snapd snap
// - on core
// in all other cases (eg. a deb package), we've already determined that
// system wide FIPS mode is enabled, so simply attempt to reexecute
// ourselves, but this time with GO FIPS mode required, hoping that the
// Go crypto runtime will be able to complete FIPS setup successfully at
// startup
if release.OnClassic && rootDir != currentRevSnapdSnap {
// reexecute ourselves with Go FIPS support in required mode
panic(syscallExec(filepath.Join(rootDir, exe), os.Args, env))
}

lib, mod := findFIPSLibsAndModules(currentRevSnapdSnap)

if mod != "" {
// version override uses the version suffix right after *.so.
libVer := strings.TrimPrefix(filepath.Ext(lib), ".")
Expand All @@ -141,6 +149,5 @@ func MaybeSetupFIPS() error {
}

// TODO how to ensure that we only load the library from the snapd snap?

panic(syscallExec(filepath.Join(rootDir, exe), os.Args, env))
}
67 changes: 60 additions & 7 deletions snapdtool/fips_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package snapdtool_test
import (
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -47,9 +48,7 @@ func (s *fipsSuite) SetUpTest(c *C) {
}

func (s *fipsSuite) TearDownTest(c *C) {
if c.Failed() {
c.Logf("logs:\n%s", s.logbuf.String())
}
c.Logf("logs:\n%s", s.logbuf.String())
s.BaseTest.TearDownTest(c)
}

Expand Down Expand Up @@ -259,22 +258,76 @@ func (s *fipsSuite) TestMaybeSetupFIPSBootstrapAlreadyDone(c *C) {
func (s *fipsSuite) TestMaybeSetupFIPSSnapdNotFromSnapOnClassic(c *C) {
// FIPS is enabled, but snapd is not running from the snapd snap

s.mockFIPSState(c, fipsConf{
fipsEnabledPresent: true,
fipsEnabledYes: true,
})

mockSelfExe := filepath.Join(dirs.DistroLibExecDir, "snapd")
// override mocked os.Readlink()
restore := snapdtool.MockOsReadlink(func(p string) (string, error) {
switch {
case p == "/snap/snapd/current":
return "123", nil
return "", fs.ErrNotExist
case strings.HasSuffix(p, "/proc/self/exe"):
return filepath.Join(dirs.DistroLibExecDir, "snapd"), nil
return mockSelfExe, nil
}
return "", fmt.Errorf("unexpected path %q", p)
})
s.AddCleanup(restore)

osArgs := os.Args
s.AddCleanup(func() { os.Args = osArgs })
os.Args = []string{"--arg"}

var observedEnv []string
var observedArgv []string
var observedArg0 string

restore = snapdtool.MockSyscallExec(func(argv0 string, argv []string, envv []string) (err error) {
observedArg0 = argv0
observedArgv = argv
observedEnv = envv
return fmt.Errorf("exec in tests")
})
s.AddCleanup(restore)

err := snapdtool.MaybeSetupFIPS()
c.Assert(err, IsNil)
c.Assert(snapdtool.MaybeSetupFIPS, PanicMatches, "exec in tests")

c.Check(observedArg0, Equals, mockSelfExe)
c.Check(observedArgv, DeepEquals, []string{"--arg"})
// FIPS mode is erquired
c.Check(observedEnv, testutil.Contains, "GOFIPS=1")
// since we're not reexecuting from snapd snap, no additional env for openssl modules is set
for _, env := range observedEnv {
if strings.HasPrefix(env, "OPENSSL_MODULES=") || strings.HasPrefix(env, "GO_OPENSSL_VERSION_OVERRIDE=") {
c.Fatalf("found unexpected env %q", env)
}
}
// bootstrap is done
c.Check(observedEnv, testutil.Contains, "SNAPD_FIPS_BOOTSTRAP=1")
}

func (s *fipsSuite) TestMaybeSetupFIPSSnapdNotFromSnapFIPSNotEnabled(c *C) {
// FIPS is not enabled, snapd is not running from the snapd snap

s.mockFIPSState(c, fipsConf{
fipsEnabledPresent: false,
fipsEnabledYes: false,
})

mockSelfExe := filepath.Join(dirs.DistroLibExecDir, "snapd")
// override mocked os.Readlink()
restore := snapdtool.MockOsReadlink(func(p string) (string, error) {
switch {
case p == "/snap/snapd/current":
return "", fs.ErrNotExist
case strings.HasSuffix(p, "/proc/self/exe"):
return mockSelfExe, nil
}
return "", fmt.Errorf("unexpected path %q", p)
})
s.AddCleanup(restore)

c.Assert(snapdtool.MaybeSetupFIPS(), IsNil)
}

0 comments on commit 73efe2e

Please sign in to comment.