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

pkg/subscriptions: Modernize FIPS mounts #2174

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

neverpanic
Copy link

/etc/system-fips is deprecated in CentOS Stream 9 and has been removed from CentOS Stream 10. UBI8 containers still contain /etc/system-fips -> /run/secrets/system-fips, but UBI9 containers do not, so creating /run/secrets/system-fips on UBI9 (or later) does not serve a useful purpose. See [1, 2].

Instead of checking /etc/system-fips to determine whether FIPS mode is enabled on the host, read /proc/sys/crypto/fips_enabled, which works for all supported RHEL versions and likely even earlier.

In CentOS 10 Stream, the crypto-policies package does now contain /usr/share/crypto-policies/default-fips-config, which is meant to serve as a file to bind-mount over /etc/crypto-policies/config when in FIPS mode [3]. Manual creation of this file is thus no longer required in containers/common for modern containers. Using this file as a source also enables improvements in crypto-policies tooling which will now

  • unmount the two bind mounts when a user manually changes the policy using update-crypto-policies --set, something which was previously broken in containers because /etc/crypto-policies/config was a read-only bind-mount, and
  • unmount and restore the two bind-mounts when the crypto-policies package is updated. The crypto-policies package will only do these steps if the the bind mounts for crypto-policies use the
    /usr/share/crypto-policies/default-fips-config
    file as source, so it makes sense for containers/common to switch to that.

Closes: #2130
Related: https://issues.redhat.com/browse/CRYPTO-13556

/etc/system-fips is deprecated in CentOS Stream 9 and has been removed
from CentOS Stream 10. UBI8 containers still contain /etc/system-fips ->
/run/secrets/system-fips, but UBI9 containers do not, so creating
/run/secrets/system-fips on UBI9 (or later) does not serve a useful
purpose. See [1, 2].

Instead of checking /etc/system-fips to determine whether FIPS mode is
enabled on the host, read /proc/sys/crypto/fips_enabled, which works for
all supported RHEL versions and likely even earlier.

In CentOS 10 Stream, the crypto-policies package does now contain
/usr/share/crypto-policies/default-fips-config, which is meant to serve
as a file to bind-mount over /etc/crypto-policies/config when in FIPS
mode [3]. Manual creation of this file is thus no longer required in
containers/common for modern containers. Using this file as a source
also enables improvements in crypto-policies tooling which will now
 - unmount the two bind mounts when a user manually changes the policy
   using update-crypto-policies --set, something which was previously
   broken in containers because /etc/crypto-policies/config was
   a read-only bind-mount, and
 - unmount and restore the two bind-mounts when the crypto-policies
   package is updated.
The crypto-policies package will only do these steps if the the bind
mounts for crypto-policies use the
  /usr/share/crypto-policies/default-fips-config
file as source, so it makes sense for containers/common to switch to
that.

[1]: https://gitlab.com/redhat-crypto/fedora-crypto-policies/-/merge_requests/111
[2]: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/9.0_release_notes/deprecated_functionality#deprecated-functionality_security
[3]: https://gitlab.com/redhat-crypto/fedora-crypto-policies/-/commit/04ceadccfc07e5946b08157d06ca5c0d5a229d92

Closes: containers#2130
Related: https://issues.redhat.com/browse/CRYPTO-13556
Signed-off-by: Clemens Lang <[email protected]>
Copy link
Contributor

openshift-ci bot commented Sep 25, 2024

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: neverpanic
Once this PR has been reviewed and has the lgtm label, please assign baude for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Member

@Honny1 Honny1 left a comment

Choose a reason for hiding this comment

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

Thanks!

The approach looks good overall, but I have a few suggestions that should reduce maintenance costs. Also, vendoring will need to be done to the projects that using c/common for example podman and buildah to make the changes take effect.

PTAL @mtrmac, @Luap99

if err := addFIPSModeSubscription(&subscriptionMounts, containerRunDir, mountPoint, mountLabel, uid, gid); err != nil {
logrus.Errorf("Adding FIPS mode subscription to container: %v", err)
// Enable FIPS mode in crypto-policies if /proc/sys/crypto/fips_enabled on the host contains "1"
if fips_enabled, err := ioutil.ReadFile("/proc/sys/crypto/fips_enabled"); err == nil {
Copy link
Member

Choose a reason for hiding this comment

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

The ioutil.ReadFile function is deprecated. I would use os.ReadFile.

// Enable FIPS mode in crypto-policies if /proc/sys/crypto/fips_enabled on the host contains "1"
if fips_enabled, err := ioutil.ReadFile("/proc/sys/crypto/fips_enabled"); err == nil {
if strings.TrimSpace(string(fips_enabled)) == "1" {
if err := addFIPSMounts(&subscriptionMounts, containerRunDir, mountPoint, mountLabel, uid, gid); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Please reduce the nesting of if statements.

Copy link
Contributor

Choose a reason for hiding this comment

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

(I don’t really mind three ifs nested, but extracting the whole condition, incl. the disableFips part, into a separate helper function would be nice.)

// In the event of restart, it is possible for the FIPS mode file to already exist
if err := fileutils.Exists(fipsFile); errors.Is(err, os.ErrNotExist) {
file, err := os.Create(fipsFile)
fipsFileHost := filepath.Join(mountPoint, "etc/system-fips")
Copy link
Member

Choose a reason for hiding this comment

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

The following if statement should create a separate function that checks whether $container/etc/system-fips exists and whether it is a symlink to /run/secrets/system-fips with reduced if nesting.

Copy link
Contributor

@mtrmac mtrmac Oct 1, 2024

Choose a reason for hiding this comment

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

(For me, the more relevant benefit is having the else logrus.… conditions much closer to the triggering cause, rather than just nesting per se. A separate function with early exits would make that easier to structure.)

destPolicyConfig := "/etc/crypto-policies/config"
srcPolicyConfigOnHost := filepath.Join(mountPoint, srcPolicyConfig)

err := fileutils.Exists(srcPolicyConfigOnHost)
Copy link
Member

Choose a reason for hiding this comment

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

This should be part of the following if statement.


// /usr/share/crypto-policies/default-fips-config does not exist, let's create it ourselves
cryptoPoliciesConfigFile := filepath.Join(containerRunDir, "fips-config")
file, err := os.Create(cryptoPoliciesConfigFile)
Copy link
Member

Choose a reason for hiding this comment

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

Instead, create a file, write to it, and close the file. You can use os.WriteFile. This should reduce the amount of code. Then you can use: os.Chown

logrus.Errorf("Adding FIPS mode bind mounts to container: %v", err)
}
} else {
logrus.Debug("/proc/sys/crypto/fips_enabled does not contain '1', not adding FIPS mode bind mounts")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth logging the value here?

file, err := os.Create(fipsFile)
fipsFileHost := filepath.Join(mountPoint, "etc/system-fips")
if fileutils.Lexists(fipsFileHost) != nil {
fipsFileTarget, err := filepath.EvalSymlinks(fipsFileHost)
Copy link
Contributor

Choose a reason for hiding this comment

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

This conceptually allows an attacker to set up /etc/system-fips inside the container to be a symlink with ../../… to break out of the container and refer to hosts’ files.

Yes, we actually only compare the fipsFileTarget text without referring to the possibly-unwanted file, but, still…

In other words, either we care about the specific symlink text, and we can just Readlink (probably not), or we care about where the symlink evaluates, whatever its form, and in that case .. in the / directory of the container should be evaluated correctly (restricted to the containers’ root).

(https://github.com/cyphar/filepath-securejoin is used for that purpose in some parts of the codebase.)

// Enable FIPS mode in crypto-policies if /proc/sys/crypto/fips_enabled on the host contains "1"
if fips_enabled, err := ioutil.ReadFile("/proc/sys/crypto/fips_enabled"); err == nil {
if strings.TrimSpace(string(fips_enabled)) == "1" {
if err := addFIPSMounts(&subscriptionMounts, containerRunDir, mountPoint, mountLabel, uid, gid); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

(I don’t really mind three ifs nested, but extracting the whole condition, incl. the disableFips part, into a separate helper function would be nice.)

Copy link
Contributor

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

I didn’t actually review the substance of the PR, whether the code correctly handles all relevant versions of the OSes and behaviors.


Just looking at the mechanism, all the filepath.Join(mountPoint, …) in this file make me worried about symlink breakouts. There might not be much actually wrong there (at worst, I guess, triggering an an audit message about a DAC or SELinux permission violation by the container runtime when checking for presence of an out-of-contaienr file), but that’s just one small refactor away from disaster.

I do appreciate that almost all of that is pre-existing and not directly relevant to this PR.

@Luap99
Copy link
Member

Luap99 commented Oct 1, 2024

Just looking at the mechanism, all the filepath.Join(mountPoint, …) in this file make me worried about symlink breakouts. There might not be much actually wrong there (at worst, I guess, triggering an an audit message about a DAC or SELinux permission violation by the container runtime when checking for presence of an out-of-contaienr file), but that’s just one small refactor away from disaster.

I do appreciate that almost all of that is pre-existing and not directly relevant to this PR.

I noticed this last week when I looked at this PR and reported this internally so yes this is an pre existing issue, fix in #2185
@neverpanic once this is merged please rebase and make sure all joins with of the container mount point use securejoin.

@neverpanic
Copy link
Author

I'm on PTO until Oct 14, I'll fix the comments after that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Automatic FIPS mode bind-mounts rely on the presence of deprecated /etc/system-fips
4 participants