[lxc-devel] [PATCH] Modify lxc-fedora and lxc-centos for multiple issues...

Michael H. Warfield mhw at WittsEnd.com
Mon Jan 13 01:53:49 UTC 2014


This is a reissue of two previous patches along with some additional
changes for hardening the root password process based on discussions
on-list.

-- 
This patch modifies the lxc-fedora and lxc-centos templates for 3 things.

1) Extensively modifies root password generation, storage, and management
    based on discussions on the devel list.

  Root passwords are hardened and have advanced configurability.
    A static password may be provided.
    A password based on a template may be generated, including ${RANDOM}.
    A password may be generated through mktmp using a template with X's.
    Root passwords default to expired, initially.
    Passwords may optionally be echoed to stdout at container creation. (no)
    Passwords may optionally be stored in ${rootfs_path}/tmp_root_pass. (yes)
    Users may be optionally forced to change the password at creation time. (no)
    Default is to generate a pattern based password and store, no force change.
    All of this may be overridden by environment variables through
      conditional assignment.

2) Random static hardware addresses are generated for all configured
    interfaces.

3) Add code to create sysv init style scripts to intercept shutdown and
    reboot to prevent init restart and hang for CentOS and legacy Fedora
    systems on shutdown, reboot, init 0, and init 6.  This solves a variety
    of hang conditions but only affects newly created containers.  Does
    not have any impact on systemd based containers.

Signed-off-by: Michael H. Warfield <mhw at WittsEnd.com>
---
 templates/lxc-centos.in | 197 +++++++++++++++++++++++++++++++++++++++++-------
 templates/lxc-fedora.in | 182 +++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 342 insertions(+), 37 deletions(-)

diff --git a/templates/lxc-centos.in b/templates/lxc-centos.in
index 3126bba..4b12850 100644
--- a/templates/lxc-centos.in
+++ b/templates/lxc-centos.in
@@ -30,9 +30,40 @@
 arch=$(arch)
 cache_base=@LOCALSTATEDIR@/cache/lxc/centos/$arch
 default_path=@LXCPATH@
-# We really need something better here!
-root_password=root
 
+# Some combinations of the tunning knobs below do not exactly make sense.
+# but that's ok.
+#
+# If the "root_password" is non-blank, use it, else set a default.
+# This can be passed to the script as an environment variable and is
+# set by a shell conditional assignment.  Looks weird but it is what it is.
+#
+# If the root password contains a ding ($) then try to expand it.
+# That will pick up things like ${name} and ${RANDOM}.
+# If the root password contians more than 3 consecutive X's, pass it as
+# a template to mktemp and take the result.
+#
+# If root_display_password = yes, display the temporary root password at exit.
+# If root_store_password = yes, store it in the configuration directory
+# If root_prompt_password = yes, invoke "passwd" to force the user to change
+# the root password after the container is created.
+#
+# These are conditional assignments...  The can be overridden from the
+# preexisting environment variables...
+#
+# Make sure this is in single quotes to defer expansion to later!
+# :{root_password='Root-${name}-${RANDOM}'}
+: ${root_password='Root-${name}-XXXXXX'}
+
+# Now, it doesn't make much sense to display, store, and force change
+# together.  But, we gotta test, right???
+: ${root_display_password='no'}
+: ${root_store_password='yes'}
+# Prompting for something interactive has potential for mayhem
+# with users running under the API...  Don't default to "yes"
+: ${root_prompt_password='no'}
+
+# These are only going into comments in the resulting config...
 lxc_network_type=veth
 lxc_network_link=lxcbr0
 
@@ -284,8 +315,21 @@ EOF
     mknod -m 600 ${dev_path}/initctl p
     mknod -m 666 ${dev_path}/ptmx c 5 2
 
-    echo "setting root passwd to $root_password"
+    if [ ${root_display_password} = "yes" ]
+    then
+        echo "Setting root password to '$root_password'"
+    fi
+    if [ ${root_store_password} = "yes" ]
+    then
+        touch ${config_path}/tmp_root_pass
+        chmod 600 ${config_path}/tmp_root_pass
+        echo ${root_password} > ${config_path}/tmp_root_pass
+        echo "Storing root password in '${config_path}/tmp_root_pass'"
+    fi
+
     echo "root:$root_password" | chroot $rootfs_path chpasswd
+    # Also set this password as expired to force the user to change it!
+    chroot $rootfs_path passwd -e root
 
     # This will need to be enhanced for CentOS 7 when systemd
     # comes into play...   /\/\|=mhw=|\/\/
@@ -402,6 +446,7 @@ copy_centos()
     # i prefer rsync (no reason really)
     mkdir -p $rootfs_path
     rsync -a $cache/rootfs/ $rootfs_path/
+    echo
     return 0
 }
 
@@ -456,28 +501,71 @@ install_centos()
     return $?
 }
 
-copy_configuration()
+create_hwaddr()
 {
+    echo $(dd if=/dev/urandom bs=8 count=1 2>/dev/null | md5sum |
+        sed -e 's/\(..\)\(..\)\(..\)\(..\)\(..\).*/fe:\1:\2:\3:\4:\5/')
+}
 
+copy_configuration()
+{
     mkdir -p $config_path
+
+    grep -q "^lxc.rootfs" $config_path/config 2>/dev/null || echo "
+lxc.rootfs = $rootfs_path
+" >> $config_path/config
+
+    # The following code is to create static MAC addresses for each
+    # interface in the container.  This code will work for multiple
+    # interfaces in the default config.
+    mv $config_path/config $config_path/config.def
+    while read LINE
+    do
+        # This should catch variable expansions from the default config...
+        if expr "${LINE}" : '.*\$' > /dev/null 2>&1
+        then
+                LINE=$(eval "echo \"${LINE}\"")
+        fi
+
+        # There is a tab and a space in the regex bracket below!
+        # Seems that \s doesn't work in brackets.
+        KEY=$(expr "${LINE}" : '\s*\([^	 ]*\)\s*=')
+
+        if [[ "${KEY}" != "lxc.network.hwaddr" ]]
+        then
+            echo ${LINE} >> $config_path/config
+
+            if [[ "${KEY}" == "lxc.network.link" ]]
+            then
+                echo "lxc.network.hwaddr = $(create_hwaddr)" >> $config_path/config
+            fi
+        fi
+    done < $config_path/config.def
+
+    rm -f $config_path/config.def
+
     cat <<EOF >> $config_path/config
 lxc.utsname = $utsname
 lxc.tty = 4
 lxc.pts = 1024
-lxc.rootfs = $rootfs_path
-lxc.mount  = $config_path/fstab
+lxc.mount = $config_path/fstab
 lxc.cap.drop = sys_module mac_admin mac_override sys_time
 
 lxc.autodev = $auto_dev
 
+# When using LXC with apparmor, uncomment the next line to run unconfined:
+#lxc.aa_profile = unconfined
+
 # example simple networking setup, uncomment to enable
 #lxc.network.type = $lxc_network_type
 #lxc.network.flags = up
 #lxc.network.link = $lxc_network_link
 #lxc.network.name = eth0
-# additional example for veth network type, static MAC address,
-# and persistent veth device name on host side
+# Additional example for veth network type
+#    static MAC address,
 #lxc.network.hwaddr = 00:16:3e:77:52:20
+#    persistent veth device name on host side
+#        Note: This may potentially collide with other containers of same name!
 #lxc.network.veth.pair = v-$name-e0
 
 #cgroups
@@ -488,8 +576,6 @@ lxc.cgroup.devices.allow = c 1:5 rwm
 # consoles
 lxc.cgroup.devices.allow = c 5:1 rwm
 lxc.cgroup.devices.allow = c 5:0 rwm
-lxc.cgroup.devices.allow = c 4:0 rwm
-lxc.cgroup.devices.allow = c 4:1 rwm
 # /dev/{,u}random
 lxc.cgroup.devices.allow = c 1:9 rwm
 lxc.cgroup.devices.allow = c 1:8 rwm
@@ -501,13 +587,12 @@ EOF
 
     cat <<EOF > $config_path/fstab
 proc            proc         proc    nodev,noexec,nosuid 0 0
-devpts          dev/pts      devpts defaults 0 0
 sysfs           sys          sysfs defaults  0 0
 EOF
 
     if [ $? -ne 0 ]; then
-    echo "Failed to add configuration"
-    return 1
+        echo "Failed to add configuration"
+        return 1
     fi
 
     return 0
@@ -517,22 +602,21 @@ clean()
 {
 
     if [ ! -e $cache ]; then
-    exit 0
+        exit 0
     fi
 
     # lock, so we won't purge while someone is creating a repository
     (
-    flock -x 200
-    if [ $? != 0 ]; then
-        echo "Cache repository is busy."
-        exit 1
-    fi
-
-    echo -n "Purging the download cache for centos-$release..."
-    rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1
-    exit 0
+        flock -x 200
+        if [ $? != 0 ]; then
+            echo "Cache repository is busy."
+            exit 1
+        fi
 
-    ) 200>/var/lock/subsys/lxc-centos
+        echo -n "Purging the download cache for centos-$release..."
+        rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1
+        exit 0
+    ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-centos
 }
 
 usage()
@@ -582,6 +666,28 @@ if [ ! -z "$clean" -a -z "$path" ]; then
     exit 0
 fi
 
+# Let's do something better for the initial root password.
+# It's not perfect but it will defeat common scanning brute force
+# attacks in the case where ssh is exposed.  It will also be set to
+# expired, forcing the user to change it at first login.
+if [ "${root_password}" = "" ]
+then
+    root_password=Root-${name}-${RANDOM}
+else
+    # If it's got a ding in it, try and expand it!
+    if [ $(expr "${root_password}" : '.*$.') != 0 ]
+    then
+        root_password=$(eval echo "${root_password}")
+    fi
+
+    # If it has more than 3 consequtive X's in it, feed it
+    # through mktemp as a template.
+    if [ $(expr "${root_password}" : '.*XXXX') != 0 ]
+    then
+        root_password=$(mktemp -u ${root_password})
+    fi
+fi
+
 if [ -z "${utsname}" ]; then
     utsname=${name}
 fi
@@ -600,7 +706,7 @@ fi
 #    utsname and hostname = Container_Name.Domain_Name
 
 if [ $(expr "$utsname" : '.*\..*\.') = 0 ]; then
-    if [ -n "$(dnsdomainname)" ]; then
+    if [[ "$(dnsdomainname)" != "" && "$(dnsdomainname)" != "localdomain" ]]; then
         utsname=${utsname}.$(dnsdomainname)
     fi
 fi
@@ -694,5 +800,42 @@ if [ ! -z $clean ]; then
     clean || exit 1
     exit 0
 fi
-echo "container rootfs and config created, default root password is '$root_password'"
-echo "edit the config file to check/enable networking setup"
+echo "
+Container rootfs and config have been created.
+Edit the config file to check/enable networking setup.
+"
+
+if [ ${root_display_password} = "yes" ]
+then
+    echo "The temporary password for root is: '$root_password'
+
+You may want to note that password down before starting the container.
+"
+fi
+
+if [ ${root_store_password} = "yes" ]
+then
+    echo "The temporary root password is stored in:
+
+        '${config_path}/tmp_root_pass'
+"
+fi
+
+if [ ${root_prompt_password} = "yes" ]
+then
+    echo "Invoking the passwd command in the container to set the root password.
+
+        chroot ${rootfs_path} passwd
+"
+    chroot ${rootfs_path} passwd
+else
+    echo "
+The root password is set up as "expired" and will require it to be changed
+at first login, which you should do as soon as possible.  If you lose the
+root password or wish to change it without starting the container, you
+can change it from the host by running the following command (which will
+also reset the expired flag):
+
+        chroot ${rootfs_path} passwd
+"
+fi
diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in
index 8db93b3..a96701f 100644
--- a/templates/lxc-fedora.in
+++ b/templates/lxc-fedora.in
@@ -30,8 +30,42 @@
 arch=$(uname -m)
 cache_base=@LOCALSTATEDIR@/cache/lxc/fedora/$arch
 default_path=@LXCPATH@
-# We really need something better here!
-root_password=root
+
+# Some combinations of the tunning knobs below do not exactly make sense.
+# but that's ok.
+#
+# If the "root_password" is non-blank, use it, else set a default.
+# This can be passed to the script as an environment variable and is
+# set by a shell conditional assignment.  Looks weird but it is what it is.
+#
+# If the root password contains a ding ($) then try to expand it.
+# That will pick up things like ${name} and ${RANDOM}.
+# If the root password contians more than 3 consecutive X's, pass it as
+# a template to mktemp and take the result.
+#
+# If root_display_password = yes, display the temporary root password at exit.
+# If root_store_password = yes, store it in the configuration directory
+# If root_prompt_password = yes, invoke "passwd" to force the user to change
+# the root password after the container is created.
+#
+# These are conditional assignments...  The can be overridden from the
+# preexisting environment variables...
+#
+# Make sure this is in single quotes to defer expansion to later!
+# :{root_password='Root-${name}-${RANDOM}'}
+: ${root_password='Root-${name}-XXXXXX'}
+
+# Now, it doesn't make much sense to display, store, and force change
+# together.  But, we gotta test, right???
+: ${root_display_password='no'}
+: ${root_store_password='yes'}
+# Prompting for something interactive has potential for mayhem
+# with users running under the API...  Don't default to "yes"
+: ${root_prompt_password='no'}
+
+# These are only going into comments in the resulting config...
+lxc_network_type=veth
+lxc_network_link=lxcbr0
 
 # is this fedora?
 # Alow for weird remixes like the Raspberry Pi
@@ -225,8 +259,21 @@ EOF
     mknod -m 600 ${dev_path}/initctl p
     mknod -m 666 ${dev_path}/ptmx c 5 2
 
-    echo "setting root passwd to $root_password"
+    if [ ${root_display_password} = "yes" ]
+    then
+        echo "Setting root password to '$root_password'"
+    fi
+    if [ ${root_store_password} = "yes" ]
+    then
+        touch ${config_path}/tmp_root_pass
+        chmod 600 ${config_path}/tmp_root_pass
+        echo ${root_password} > ${config_path}/tmp_root_pass
+        echo "Storing root password in '${config_path}/tmp_root_pass'"
+    fi
+
     echo "root:$root_password" | chroot $rootfs_path chpasswd
+    # Also set this password as expired to force the user to change it!
+    chroot $rootfs_path passwd -e root
 
     # specifying this in the initial packages doesn't always work.
     # Even though it should have...
@@ -274,7 +321,7 @@ configure_fedora_init()
 
 configure_fedora_systemd()
 {
-    unlink ${rootfs_path}/etc/systemd/system/default.target
+    rm -f ${rootfs_path}/etc/systemd/system/default.target
     touch ${rootfs_path}/etc/fstab
     chroot ${rootfs_path} ln -s /dev/null /etc/systemd/system/udev.service
     chroot ${rootfs_path} ln -s /lib/systemd/system/multi-user.target /etc/systemd/system/default.target
@@ -868,6 +915,7 @@ copy_fedora()
     # i prefer rsync (no reason really)
     mkdir -p $rootfs_path
     rsync -Ha $cache/rootfs/ $rootfs_path/
+    echo
     return 0
 }
 
@@ -922,11 +970,53 @@ install_fedora()
     return $?
 }
 
-copy_configuration()
+# Generate a random hardware (MAC) address composed of FE followed by
+# 5 random bytes...
+create_hwaddr()
 {
+    echo $(dd if=/dev/urandom bs=8 count=1 2>/dev/null | md5sum |
+        sed -e 's/\(..\)\(..\)\(..\)\(..\)\(..\).*/fe:\1:\2:\3:\4:\5/')
+}
 
+copy_configuration()
+{
     mkdir -p $config_path
-    grep -q "^lxc.rootfs" $config_path/config 2>/dev/null || echo "lxc.rootfs = $rootfs_path" >> $config_path/config
+
+    grep -q "^lxc.rootfs" $config_path/config 2>/dev/null || echo "
+lxc.rootfs = $rootfs_path
+" >> $config_path/config
+
+    # The following code is to create static MAC addresses for each
+    # interface in the container.  This code will work for multiple
+    # interfaces in the default config.  It will also strip any
+    # hwaddr stanzas out of the default config since we can not share
+    # MAC addresses between containers.
+    mv $config_path/config $config_path/config.def
+    while read LINE
+    do
+        # This should catch variable expansions from the default config...
+        if expr "${LINE}" : '.*\$' > /dev/null 2>&1
+        then
+                LINE=$(eval "echo \"${LINE}\"")
+        fi
+
+        # There is a tab and a space in the regex bracket below!
+        # Seems that \s doesn't work in brackets.
+        KEY=$(expr "${LINE}" : '\s*\([^	 ]*\)\s*=')
+
+        if [[ "${KEY}" != "lxc.network.hwaddr" ]]
+        then
+            echo "${LINE}" >> $config_path/config
+
+            if [[ "${KEY}" == "lxc.network.link" ]]
+            then
+                echo "lxc.network.hwaddr = $(create_hwaddr)" >> $config_path/config
+            fi
+        fi
+    done < $config_path/config.def
+
+    rm -f $config_path/config.def
+
     cat <<EOF >> $config_path/config
 lxc.utsname = $utsname
 lxc.tty = 4
@@ -939,6 +1029,18 @@ lxc.autodev = $auto_dev
 # When using LXC with apparmor, uncomment the next line to run unconfined:
 #lxc.aa_profile = unconfined
 
+# example simple networking setup, uncomment to enable
+#lxc.network.type = $lxc_network_type
+#lxc.network.flags = up
+#lxc.network.link = $lxc_network_link
+#lxc.network.name = eth0
+# Additional example for veth network type
+#    static MAC address,
+#lxc.network.hwaddr = 00:16:3e:77:52:20
+#    persistent veth device name on host side
+#        Note: This may potentially collide with other containers of same name!
+#lxc.network.veth.pair = v-$name-e0
+
 #cgroups
 lxc.cgroup.devices.deny = a
 # /dev/null and zero
@@ -960,6 +1062,7 @@ EOF
 proc            proc         proc    nodev,noexec,nosuid 0 0
 sysfs           sys          sysfs defaults  0 0
 EOF
+
     if [ $? -ne 0 ]; then
         echo "Failed to add configuration"
         return 1
@@ -1037,6 +1140,28 @@ if [ ! -z "$clean" -a -z "$path" ]; then
     exit 0
 fi
 
+# Let's do something better for the initial root password.
+# It's not perfect but it will defeat common scanning brute force
+# attacks in the case where ssh is exposed.  It will also be set to
+# expired, forcing the user to change it at first login.
+if [ "${root_password}" = "" ]
+then
+    root_password=Root-${name}-${RANDOM}
+else
+    # If it's got a ding in it, try and expand it!
+    if [ $(expr "${root_password}" : '.*$.') != 0 ]
+    then
+        root_password=$(eval echo "${root_password}")
+    fi
+
+    # If it has more than 3 consequtive X's in it, feed it
+    # through mktemp as a template.
+    if [ $(expr "${root_password}" : '.*XXXX') != 0 ]
+    then
+        root_password=$(mktemp -u ${root_password})
+    fi
+fi
+
 if [ -z "${utsname}" ]; then
     utsname=${name}
 fi
@@ -1055,7 +1180,7 @@ fi
 #    utsname and hostname = Container_Name.Domain_Name
 
 if [ $(expr "$utsname" : '.*\..*\.') = 0 ]; then
-    if [ -n "$(dnsdomainname)" ]; then
+    if [[ "$(dnsdomainname)" != "" && "$(dnsdomainname)" != "localdomain" ]]; then
         utsname=${utsname}.$(dnsdomainname)
     fi
 fi
@@ -1159,12 +1284,14 @@ if [ ! -z $clean ]; then
     clean || exit 1
     exit 0
 fi
-echo "container rootfs and config created"
+echo "
+Container rootfs and config have been created.
+Edit the config file to check/enable networking setup.
+"
 
 if [[ -d ${cache_base}/bootstrap ]]
 then
-    echo "
-You have successfully built a Fedora container and cache.  This cache may
+    echo "You have successfully built a Fedora container and cache.  This cache may
 be used to create future containers of various revisions.  The directory
 ${cache_base}/bootstrap contains a bootstrap
 which may no longer needed and can be removed.
@@ -1178,3 +1305,38 @@ This is only used in the creation of the bootstrap run-time-environment
 and may be removed.
 "
 fi
+
+if [ ${root_display_password} = "yes" ]
+then
+    echo "The temporary password for root is: '$root_password'
+
+You may want to note that password down before starting the container.
+"
+fi
+
+if [ ${root_store_password} = "yes" ]
+then
+    echo "The temporary root password is stored in:
+
+        '${config_path}/tmp_root_pass'
+"
+fi
+
+if [ ${root_prompt_password} = "yes" ]
+then
+    echo "Invoking the passwd command in the container to set the root password.
+
+        chroot ${rootfs_path} passwd
+"
+    chroot ${rootfs_path} passwd
+else
+    echo "
+The root password is set up as "expired" and will require it to be changed
+at first login, which you should do as soon as possible.  If you lose the
+root password or wish to change it without starting the container, you
+can change it from the host by running the following command (which will
+also reset the expired flag):
+
+        chroot ${rootfs_path} passwd
+"
+fi
-- 
1.8.3.1


-- 
Michael H. Warfield (AI4NB) | (770) 978-7061 |  mhw at WittsEnd.com
   /\/\|=mhw=|\/\/          | (678) 463-0932 |  http://www.wittsend.com/mhw/
   NIC whois: MHW9          | An optimist believes we live in the best of all
 PGP Key: 0x674627FF        | possible worlds.  A pessimist is sure of it!

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 465 bytes
Desc: This is a digitally signed message part
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20140112/8a99def7/attachment.pgp>


More information about the lxc-devel mailing list