Browse Source

Ticket 48336 - setup-ds should detect if port is already defined

Bug Description:  Previously setup-ds.pl could not detect if a port was defined
in selinux policy or not.

Fix Description:  This adds a set of selinux helpers that can query selinux port
in policy. Using these, setup-ds.pl can now make better decisions about whether
to label ports, or if they should be removed from policy during removal.

https://fedorahosted.org/389/ticket/48336

Author: wibrown

Review by: vashirov and nhosoi (Thank you!)
William Brown 9 years ago
parent
commit
8269288d98

+ 5 - 0
Makefile.am

@@ -154,6 +154,7 @@ CLEANFILES =  dberrstrs.h ns-slapd.properties \
 	ldap/admin/src/scripts/ns-inactivate.pl ldap/admin/src/scripts/ns-newpwpolicy.pl \
 	ldap/admin/src/scripts/schema-reload.pl ldap/admin/src/scripts/syntax-validate.pl \
 	ldap/admin/src/scripts/usn-tombstone-cleanup.pl ldap/admin/src/scripts/verify-db.pl \
+	ldap/admin/src/scripts/ds_selinux_port_query ldap/admin/src/scripts/ds_selinux_enabled \
 	ldap/admin/src/scripts/dbverify \
 	$(NULL)
 
@@ -617,6 +618,8 @@ sbin_SCRIPTS = ldap/admin/src/scripts/setup-ds.pl \
 	ldap/admin/src/scripts/dbverify \
 	ldap/admin/src/scripts/upgradedb \
 	ldap/admin/src/scripts/dbmon.sh \
+	ldap/admin/src/scripts/ds_selinux_enabled \
+	ldap/admin/src/scripts/ds_selinux_port_query \
     wrappers/ldap-agent
 
 bin_SCRIPTS = ldap/servers/slapd/tools/rsearch/scripts/dbgen.pl \
@@ -1822,6 +1825,7 @@ fixupcmd = sed \
 	-e 's,@with_selinux\@,@with_selinux@,g' \
 	-e 's,@with_tmpfiles_d\@,@with_tmpfiles_d@,g' \
 	-e 's,@perlexec\@,@perlexec@,g' \
+	-e 's,@pythonexec\@,@pythonexec@,g' \
 	-e 's,@sttyexec\@,@sttyexec@,g' \
 	-e 's,@initconfigdir\@,$(initconfigdir),g'\
 	-e 's,@updatedir\@,$(updatedir),g' \
@@ -1890,6 +1894,7 @@ fixupcmd = sed \
 	-e 's,@with_selinux\@,@with_selinux@,g' \
 	-e 's,@with_tmpfiles_d\@,@with_tmpfiles_d@,g' \
 	-e 's,@perlexec\@,@perlexec@,g' \
+	-e 's,@pythonexec\@,@pythonexec@,g' \
 	-e 's,@sttyexec\@,@sttyexec@,g' \
 	-e 's,@initconfigdir\@,$(initconfigdir),g' \
 	-e 's,@updatedir\@,$(updatedir),g' \

+ 20 - 11
configure.ac

@@ -396,21 +396,21 @@ else
   with_perldir=
 fi
 
-AC_MSG_CHECKING(for --with-pythondir)
-AC_ARG_WITH([pythondir],
-   AS_HELP_STRING([--with-pythondir=PATH],
-                  [Directory for python)])
+AC_MSG_CHECKING(for --with-pythonexec)
+AC_ARG_WITH([pythonexec],
+   AS_HELP_STRING([--with-pythonexec=PATH],
+                  [Path to executable for python)])
 )
-if test -n "$with_pythondir"; then
-  if test  "$with_pythondir" = yes ; then
-    AC_MSG_ERROR([You must specify --with-pythondir=/full/path/to/python])
-  elif test  "$with_pythondir" = no ; then
-    with_pythondir=
+if test -n "$with_pythonexec"; then
+  if test  "$with_pythonexec" = yes ; then
+    AC_MSG_ERROR([You must specify --with-pythonexec=/full/path/to/python])
+  elif test  "$with_pythonexec" = no ; then
+    with_pythonexec=/usr/bin/python2
   else
-    AC_MSG_RESULT([$with_pythondir])
+    AC_MSG_RESULT([$with_pythonexec])
   fi
 else
-  with_pythondir=
+  with_pythonexec=/usr/bin/python2
 fi
 
 AC_SUBST(configdir)
@@ -483,6 +483,14 @@ if test -n "$with_perldir"; then
 else
   perlexec='/usr/bin/env perl'
 fi
+
+# This will let us change over the python version easier in the future.
+if test -n "$with_pythonexec"; then
+  pythonexec="$with_pythonexec"
+else
+  pythonexec='/usr/bin/env python2'
+fi
+
 # we use stty in perl scripts to disable password echo
 # this doesn't work unless the full absolute path of the
 # stty command is used e.g. system("stty -echo") does not
@@ -622,6 +630,7 @@ fi
 # sysv init scripts not used when systemd is used
 AC_SUBST(initdir)
 AC_SUBST(perlexec)
+AC_SUBST(pythonexec)
 AC_SUBST(sttyexec)
 
 # set default initconfigdir if not already set

+ 84 - 52
ldap/admin/src/scripts/DSCreate.pm.in

@@ -1009,8 +1009,10 @@ sub updateSelinuxPolicy {
     my $mydevnull = (-c "/dev/null" ? " /dev/null " : " NUL ");
 
     # if selinux is not available, do nothing
-    if ((getLogin() eq 'root') and "@with_selinux@" and
-        -f "@sbindir@/sestatus" and !system ("@sbindir@/sestatus | egrep -i \"selinux status:\\s*enabled\" > $mydevnull 2>&1")) {
+    # In perl, exit(1) is 256 from system. ds_selinux_enable returns 1 on true, 0 on false.
+    if ((getLogin() eq 'root') and "@with_selinux@" and system("$inf->{slapd}->{sbindir}/ds_selinux_enabled") == 256 ) {
+        debug(1, "Selinux is enabled or permissive, fixing contexts\n");
+        # -f "@sbindir@/sestatus" and !system ("@sbindir@/sestatus | egrep -i \"selinux status:\\s*enabled\" > $mydevnull 2>&1")) {
         my $localstatedir = $inf->{slapd}->{localstatedir};
 
         # run restorecon on all of the parent directories we
@@ -1036,36 +1038,35 @@ sub updateSelinuxPolicy {
         }
 
         # label the selected port as ldap_port_t
+        # We should be doing this for secure port too .....
         if ($inf->{slapd}->{ServerPort} != 0) {
-            my $need_label = 1;
-
-            # check if the port is already labeled properly
-            my $portline = `semanage port -l | grep ldap_port_t | grep tcp`;
-            chomp($portline);
-            $portline =~ s/ldap_port_t\s+tcp\s+//g;
-            my @labeledports = split(/,\s+/, $portline);
-            foreach my $labeledport (@labeledports) {
-                if (index($labeledport, "-") == -1) {
-                        # this is not a range of ports
-                        if ($inf->{slapd}->{ServerPort} == $labeledport) {
-                                $need_label = 0;
-                                last;
-                        }
-                } else {
-                        # this is a range of ports like '<portMin>-<portMax>'
-                        my @range = split(/-/, $labeledport);
-                        if ((@range[0] <= $inf->{slapd}->{ServerPort}) && ($inf->{slapd}->{ServerPort} <= @range[1])) {
-                                $need_label = 0;
-                                last;
-                        }
-                }
+            my $port_query_cmd = ("$inf->{slapd}->{sbindir}/ds_selinux_port_query $inf->{slapd}->{ServerPort} ldap_port_t 2> $mydevnull");
+            my $need_label = 0;
+            my $result = system($port_query_cmd);
+
+            # 0 is false, 1 is true. True means 'already in policy'.
+            if ($result == 0) {
+                debug(1, "Port $inf->{slapd}->{ServerPort} must be labeled as ldap_port_t \n");
+                $need_label = 1;
+            } 
+            if ($result == 512) {
+                $need_label = 0;
+                debug(0, "Port $inf->{slapd}->{ServerPort} already belongs to another selinux type.\n");
+                debug(0, " The command below will show you the current type that owns the port.\n");
+                debug(0, "sudo $inf->{slapd}->{sbindir}/ds_selinux_port_query $inf->{slapd}->{ServerPort} ldap_port_t\n");
+                debug(0, " It is highly likely your server will fail to start ... \n");
+            }
+            if ($result == 131072) {
+                $need_label = 0;
+                debug(0, "An error occured running ds_selinux_port_query. This is probably a bug\n");
+                debug(0, "$port_query_cmd \n");
             }
 
             if ($need_label == 1) {
                 my $semanage_err;
                 my $rc;
                 # 60 is a bit excessive, we should fail faster.
-                my $retry = 5;
+                my $retry = 2;
                 $ENV{LANG} = "C";
                 while (($retry > 0) && ($semanage_err = `semanage port -a -t ldap_port_t -p tcp $inf->{slapd}->{ServerPort} 2>&1`) && ($rc = $?)) {
                     debug(1, "Adding port $inf->{slapd}->{ServerPort} to selinux policy failed - $semanage_err (return code: $rc, $retry attempts remain).\n");
@@ -1461,52 +1462,83 @@ sub removeDSInstance {
 
     # remove the selinux label from the ports if needed
     my $mydevnull = (-c "/dev/null" ? " /dev/null " : " NUL ");
-    if ((getLogin() eq 'root') and "@with_selinux@" and
-        -f "@sbindir@/sestatus" and !system ("@sbindir@/sestatus | egrep -i \"selinux status:\\s*enabled\" > $mydevnull 2>&1")) {
+    if ((getLogin() eq 'root') and "@with_selinux@" and system("@sbindir@/ds_selinux_enabled") == 256 ) {
         foreach my $port (@{$entry->{"nsslapd-port"}}) 
         {
+
+            my $need_remove_label = 0;
+            my $port_query_cmd = ("@sbindir@/ds_selinux_port_query $port ldap_port_t 2> $mydevnull");
+            my $result = system($port_query_cmd);
+
+            if ($result == 256) {
+                debug(1, "Port $port may be removed as ldap_port_t \n");
+                $need_remove_label = 1;
+            } 
+            if ($result == 131072) {
+                $need_remove_label = 0;
+                debug(0, "An error occured running ds_selinux_port_query. This is probably a bug\n");
+                debug(0, "$port_query_cmd \n");
+            }
+
             my $semanage_err;
             my $rc;
             my $retry = 5;
             $ENV{LANG} = "C";
-            while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $port 2>&1`) && ($rc = $?)) {
-                if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
-                    $retry = -1;
-                } else {
-                    debug(1, "Warning: Port $port not removed from selinux policy correctly, $retry attempts remain.  Error: $semanage_err\n");
-                    debug(1, "Retrying in 5 seconds\n");
-                    sleep(5);
-                    $retry--;
+            if ($need_remove_label) {
+                while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $port 2>&1`) && ($rc = $?)) {
+                    if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
+                        $retry = -1;
+                    } else {
+                        debug(1, "Warning: Port $port not removed from selinux policy correctly, $retry attempts remain.  Error: $semanage_err\n");
+                        debug(1, "Retrying in 5 seconds\n");
+                        sleep(5);
+                        $retry--;
+                    }
+                }
+                if (0 == $retry) {
+                    push @errs, [ 'error_removing_port_label', $port, $semanage_err];
+                    debug(1, "Warning: Port $port not removed from selinux policy correctly.  Error: $semanage_err\n");
+                    debug(1, "Reached time limit.\n");
                 }
-            }
-            if (0 == $retry) {
-                push @errs, [ 'error_removing_port_label', $port, $semanage_err];
-                debug(1, "Warning: Port $port not removed from selinux policy correctly.  Error: $semanage_err\n");
-                debug(1, "Reached time limit.\n");
             }
         }
 
         foreach my $secureport (@{$entry->{"nsslapd-secureport"}})
         {
+            my $need_remove_label = 0;
+            my $port_query_cmd = ("@sbindir@/ds_selinux_port_query $secureport ldap_port_t 2> $mydevnull");
+            my $result = system($port_query_cmd);
+
+            if ($result == 256) {
+                debug(1, "Port $secureport may be removed as ldap_port_t \n");
+                $need_remove_label = 1;
+            } 
+            if ($result == 131072) {
+                $need_remove_label = 0;
+                debug(0, "An error occured running ds_selinux_port_query. This is probably a bug\n");
+                debug(0, "$port_query_cmd \n");
+            }
             my $semanage_err;
             my $rc;
             my $retry = 60;
             $ENV{LANG} = "C";
-            while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $secureport 2>&1`) && ($rc = $?)) {
-                if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
-                    $retry = -1;
-                } else {
+            if ($need_remove_label) {
+                while (($retry > 0) && ($semanage_err = `semanage port -d -t ldap_port_t -p tcp $secureport 2>&1`) && ($rc = $?)) {
+                    if (($semanage_err =~ /defined in policy, cannot be deleted/) || ($semanage_err =~ /is not defined/)) {
+                        $retry = -1;
+                    } else {
+                        debug(1, "Warning: Port $secureport not removed from selinux policy correctly.  Error: $semanage_err\n");
+                        debug(1, "Retrying in 5 seconds\n");
+                        sleep(5);
+                        $retry--;
+                    }
+                }
+                if (0 == $retry) {
+                    push @errs, [ 'error_removing_port_label', $secureport, $semanage_err];
                     debug(1, "Warning: Port $secureport not removed from selinux policy correctly.  Error: $semanage_err\n");
-                    debug(1, "Retrying in 5 seconds\n");
-                    sleep(5);
-                    $retry--;
+                    debug(1, "Reached time limit.\n");
                 }
             }
-            if (0 == $retry) {
-                push @errs, [ 'error_removing_port_label', $secureport, $semanage_err];
-                debug(1, "Warning: Port $secureport not removed from selinux policy correctly.  Error: $semanage_err\n");
-                debug(1, "Reached time limit.\n");
-            }
         }
     }
 

+ 23 - 0
ldap/admin/src/scripts/ds_selinux_enabled.in

@@ -0,0 +1,23 @@
+#!@pythonexec@
+
+# BEGIN COPYRIGHT BLOCK
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details. 
+# END COPYRIGHT BLOCK
+#
+
+# These are python 3 capable, but el7 doesn't have libsemanage-python3
+
+
+import sys
+import selinux
+import semanage
+
+# Returns 1 for true, 0 for false.
+
+sys.exit(selinux.is_selinux_enabled())
+
+

+ 69 - 0
ldap/admin/src/scripts/ds_selinux_port_query.in

@@ -0,0 +1,69 @@
+#!@pythonexec@
+
+# BEGIN COPYRIGHT BLOCK
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details. 
+# END COPYRIGHT BLOCK
+#
+
+import sys
+import selinux
+import semanage
+
+# These are python 3 capable, but el7 doesn't have libsemanage-python3
+
+# Given a port number as the first argument, determine if it's already part of the policy.
+# The second (optional) argument is a label type to check.
+
+# 0 for does not exist in policy. 1 mean exists (with no label)
+# or if a lable is given, exists AND inside of label type.
+# 2 means port exists but belongs to a different type.
+
+if len(sys.argv) <= 1:
+    sys.stderr.write("Must provide port to query\n")
+    sys.exit(512)
+
+port = int(sys.argv[1])
+label = None
+try:
+    label = sys.argv[2]
+except:
+    pass
+
+# Get the arguments
+
+# Fail if they are not set correctly.
+
+# Check the port in policy
+h = semanage.semanage_handle_create()
+semanage.semanage_connect(h)
+# This could check high / low values, but eh.
+(r, k) = semanage.semanage_port_key_create(h, port, port, semanage.SEMANAGE_PROTO_TCP)
+
+# Do I need to check _local too?
+(t, e) = semanage.semanage_port_exists(h, k)
+
+if label is None:
+    sys.exit(e)
+
+# See if it has a specifc label
+
+if (e == 0):
+    # No point checking the label, it doesn't exist
+    sys.exit(e)
+
+(t, sp) = semanage.semanage_port_query(h, k)
+
+# do we need to check if this is none? We already know that the port exists, so it must have a context ...
+r = semanage.semanage_port_get_con(sp)
+
+if label == semanage.semanage_context_get_type(r):
+    sys.exit(1)
+
+else:
+    sys.stderr.write('Port belongs to %s\n' % semanage.semanage_context_get_type(r))
+    sys.exit(2)
+