Managing Mount Points and fstab Entries in Puppet: Handling SELinux Contexts and Filesystem Ownership Conflicts


6 views

When automating filesystem mounts through Puppet, we encounter a fundamental tension between mount point directory management and actual mounted filesystem attributes. The mount resource type handles fstab entries perfectly, but the accompanying file resource for mount point directories often causes undesired permission changes.

Here's what happens with a standard implementation:

file { "/mnt/data":
  ensure  => directory,
  owner   => "root",
  group   => "root",
  mode    => "0755",
  seltype => "mnt_t"
}

mount { "/mnt/data":
  ensure  => mounted,
  device  => "/dev/sdb1",
  fstype  => "xfs",
  options => "defaults",
  require => File["/mnt/data"]
}

After mounting, Puppet will persistently try to reset the directory attributes to match the file resource definition, regardless of the mounted filesystem's actual permissions.

For modern Puppet versions (4.8+), we can use the noop meta-parameter for mount points:

file { "/mnt/data":
  ensure  => directory,
  noop    => true,  # Puppet won't enforce attributes
  before  => Mount["/mnt/data"]
}

For SELinux contexts, we need a more nuanced approach:

file { "/mnt/data":
  ensure   => directory,
  selrange => 's0',
  selrole  => 'object_r',
  seltype  => 'mnt_t',
  seluser  => 'system_u',
  before   => Mount["/mnt/data"]
}

mount { "/mnt/data":
  ensure     => mounted,
  device     => "/dev/sdb1",
  fstype     => "xfs",
  options    => "context=system_u:object_r:public_content_t:s0",
  remounts   => false,
  subscribe  => File["/mnt/data"]
}

Here's a refined version of the original implementation:

define managed_mount(
  String $device,
  String $fstype,
  String $mountpoint,
  String $options = 'defaults',
  Boolean $manage_attributes = false,
  Optional[String] $seltype = undef
) {
  if $manage_attributes {
    file { $mountpoint:
      ensure  => directory,
      owner   => 'root',
      group   => 'root',
      mode    => '0755',
      seltype => $seltype,
      before  => Mount[$mountpoint]
    }
  } else {
    file { $mountpoint:
      ensure  => directory,
      noop    => true,
      before  => Mount[$mountpoint]
    }
  }

  mount { $mountpoint:
    ensure  => mounted,
    device  => $device,
    fstype  => $fstype,
    options => $options,
    atboot  => true,
    dump    => 0,
    pass    => 2
  }
}

For NFS mounts or special filesystems, consider these patterns:

# For NFS with specific context requirements
managed_mount { 'nfs_share':
  device        => 'nfs-server:/export/share',
  fstype        => 'nfs4',
  mountpoint    => '/mnt/nfs_share',
  options       => 'context=system_u:object_r:nfs_t:s0,ro',
  seltype       => 'nfs_t',
  manage_attributes => false
}

# For tmpfs with size limits
managed_mount { 'tmp_work':
  device     => 'tmpfs',
  fstype     => 'tmpfs',
  mountpoint => '/tmp/work',
  options    => 'size=1G,context=system_u:object_r:tmp_t:s0'
}

When automating mount point management with Puppet, we encounter a fundamental tension between declarative configuration and filesystem realities. The mount resource type handles /etc/fstab perfectly, but the file resource for creating mount directories often clashes with actual mounted filesystem permissions.

# Problematic scenario:
file { '/mnt/data':
  ensure => directory,
  owner  => 'root',
  group  => 'root',
  mode   => '0755'
}

mount { '/mnt/data':
  ensure  => mounted,
  device  => '/dev/sdb1',
  fstype  => 'ext4',
  options => 'defaults'
}

When a filesystem gets mounted, it brings its own ownership and permissions. Puppet's file resource will attempt to enforce its declared state, causing:

  • Unnecessary permission changes during each Puppet run
  • Potential service disruptions when mounted FS permissions differ
  • SELinux context conflicts (especially problematic in RedHat systems)

Here are three battle-tested approaches:

1. The Minimalist Mount Point

define safe_mount(
  $device,
  $fstype,
  $options = 'defaults',
  $point   = $title
) {
  file { $point:
    ensure => directory,
    # Critical: Don't manage permissions
    backup => false
  }

  mount { $point:
    ensure  => mounted,
    device  => $device,
    fstype  => $fstype,
    options => $options,
    require => File[$point]
  }
}

2. SELinux-Aware Implementation

define selinux_mount(
  $device,
  $fstype,
  $seltype = undef,
  $point   = "/mnt/${title}"
) {
  file { $point:
    ensure  => directory,
    seltype => $seltype, # Let mounted FS determine context if undef
    before  => Mount[$point]
  }

  mount { $point:
    ensure  => mounted,
    device  => $device,
    fstype  => $fstype,
    options => 'context="system_u:object_r:default_t:s0"',
    require => File[$point]
  }
}

3. Conditional Permission Management

define smart_mount(
  $device,
  $fstype,
  $manage_perms = false,
  $point = $title
) {
  $file_params = $manage_perms ? {
    true    => { owner => 'root', group => 'root', mode => '0755' },
    default => { backup => false }
  }

  file { $point:
    ensure => directory,
    *      => $file_params
  }

  mount { $point:
    # ... standard mount params ...
    require => File[$point]
  }
}

For particularly tricky situations, consider these patterns:

# Only create directory if not exists, never touch afterwards
exec { "create_${mount_point}":
  command => "mkdir -p ${mount_point}",
  unless  => "test -d ${mount_point}",
  before  => Mount[$mount_point]
}

# Or using modern Puppet's sensitive type
file { $mount_point:
  ensure    => directory,
  show_diff => false,
  replace   => false
}
  • Avoid managing mount point permissions unless absolutely necessary
  • Use backup => false to prevent Puppet from saving original permissions
  • For SELinux, either set explicit context or leave undefined
  • Consider using exec resources for mount point creation in complex environments
  • Test thoroughly with --noop before applying changes