dirsrvtests/cmd/dsadm/dsadm.py | 551 ++++++++++++++++++++++++++++++++++
ldap/admin/src/scripts/DSCreate.pm.in | 10
2 files changed, 556 insertions(+), 5 deletions(-)
New commits:
commit 3c3fe1b6a5492b7b074f7e460ea9403d5c4801c2
Author: Thierry bordaz (tbordaz) <tbordaz(a)redhat.com>
Date: Thu Mar 12 19:05:25 2015 +0100
Ticket 48127: Using RPM, allows non root user to create/remove DS instance
Bug Description:
RPM 389-ds scripts to create and delete instance, assumes that the caller
can update selinux, tmpfiles and systemD.
This prevent regular user to create/delete an instance.
Fix Description:
Test the caller is root before doing these updates
The fix also create a helper command 'dsadm.py' to
create/delete/start/stop/restart instance
This command is not delivered
https://fedorahosted.org/389/ticket/48127
Reviewed by: Rich (thanks Rich !)
Platforms tested: F17/F21
Flag Day: no
Doc impact: no
diff --git a/dirsrvtests/cmd/dsadm/dsadm.py b/dirsrvtests/cmd/dsadm/dsadm.py
new file mode 100755
index 0000000..0d868c2
--- /dev/null
+++ b/dirsrvtests/cmd/dsadm/dsadm.py
@@ -0,0 +1,551 @@
+#! /usr/bin/python2
+
+# Authors:
+# Thierry Bordaz <tbordaz(a)redhat.com>
+#
+# Copyright (C) 2015 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <
http://www.gnu.org/licenses/>.
+
+import sys
+import os
+import argparse
+import pdb
+import tempfile
+import time
+import pwd
+import grp
+import platform
+import socket
+import shutil
+from subprocess import Popen, PIPE, STDOUT
+import string
+
+SETUP_DS = "/sbin/setup-ds.pl"
+REMOVE_DS = "/sbin/remove-ds.pl"
+INITCONFIGDIR = ".dirsrv"
+SCRIPT_START = "start-slapd"
+SCRIPT_STOP = "stop-slapd"
+SCRIPT_RESTART = "restart-slapd"
+ENVIRON_SERVERID = '389-SERVER-ID'
+ENVIRON_USER = '389-USER'
+ENVIRON_GROUP = '389-GROUP'
+ENVIRON_DIRECTORY = '389-DIRECTORY'
+ENVIRON_PORT = '389-PORT'
+ENVIRON_SECURE_PORT = '389-SECURE-PORT'
+DEFAULT_PORT_ROOT = str(389)
+DEFAULT_PORT_NON_ROOT = str(1389)
+DEFAULT_SECURE_PORT_ROOT = str(636)
+DEFAULT_SECURE_PORT_NON_ROOT = str(1636)
+DEFAULT_USER = 'nobody'
+DEFAULT_GROUP = 'nobody'
+DEFAULT_ROOT_DN = 'cn=Directory Manager'
+DEFAULT_HOSTNAME = socket.gethostname()
+
+
+
+def validate_user(user):
+ '''
+ If a user is provided it returns its username
+ else it returns the current username.
+ It checks that the userId or userName exists
+
+ :param: user (optional) can be a userName or userId
+ :return: userName of the provided user, if none is provided, it returns current user
name
+ '''
+ assert(user)
+ if user.isdigit():
+ try:
+ username = pwd.getpwuid(int(user)).pw_name
+ except KeyError:
+ raise KeyError('Unknown userId %d' % user)
+ return username
+ else:
+ try:
+ pwd.getpwnam(user).pw_uid
+ except KeyError:
+ raise KeyError('Unknown userName %s' % user)
+ return user
+
+def get_default_user():
+ user = os.environ.get(ENVIRON_USER, None)
+ if not user:
+ user = os.getuid()
+ return str(user)
+
+def get_default_group():
+ '''
+ If a group is provided it returns its groupname
+ else it returns the current groupname.
+ It checks that the groupId or groupName exists
+
+ :param: group (optional) can be a groupName or groupId
+ :return: groupName of the provided group, if none is provided, it returns current
group name
+ '''
+ group = os.environ.get(ENVIRON_GROUP, None)
+ if not group:
+ return pwd.getpwuid(os.getuid()).pw_name
+ return group
+
+def validate_group(group):
+ assert(group)
+ if str(group).isdigit():
+ try:
+ groupname = grp.getgrgid(group).gr_name
+ return groupname
+ except:
+ raise KeyError('Unknown groupId %d' % group)
+ else:
+ try:
+ groupname = grp.getgrnam(group).gr_name
+ return groupname
+ except:
+ raise KeyError('Unknown groupName %s' % group)
+
+def test_get_group():
+ try:
+ grpname = get_default_group()
+ print 'get_group: %s' % grpname
+ except:
+ raise
+ print "Can not find user group"
+ pass
+ try:
+ grpname = get_default_group(group='tbordaz')
+ print 'get_group: %s' % grpname
+ except:
+ raise
+ print "Can not find user group"
+ pass
+ try:
+ grpname = get_default_group(group='coucou')
+ print 'get_group: %s' % grpname
+ except:
+ print "Can not find user group coucou"
+ pass
+ try:
+ grpname = get_default_group('thierry')
+ print 'get_group: %s' % grpname
+ except:
+ raise
+ print "Can not find user group thierry"
+ pass
+ try:
+ grpname = get_default_group(1000)
+ print 'get_group: %s' % grpname
+ except:
+ raise
+ print "Can not find user group 1000"
+ pass
+ try:
+ grpname = get_default_group(20532)
+ print 'get_group: %s' % grpname
+ except:
+ raise
+ print "Can not find user group 20532"
+ pass
+ try:
+ grpname = get_default_group(123)
+ print 'get_group: %s' % grpname
+ except:
+ print "Can not find user group 123"
+ pass
+
+def get_default_port():
+ port = os.environ.get(ENVIRON_PORT, None)
+ if port:
+ return port
+
+ if os.getuid() == 0:
+ return DEFAULT_PORT_ROOT
+ else:
+ return DEFAULT_PORT_NON_ROOT
+
+def validate_port(port):
+ assert port
+ if not port.isdigit() or int(port) <= 0 :
+ raise Exception("port number is invalid: %s" % port)
+
+def get_default_directory():
+ directory = os.environ.get(ENVIRON_DIRECTORY, None)
+ if not directory:
+ directory = os.getcwd()
+ return directory
+
+def validate_directory(directory):
+ assert directory
+ if not os.path.isdir(directory):
+ raise Exception("Supplied directory path is not a directory")
+
+ if not os.access(directory, os.W_OK):
+ raise Exception("Supplied directory is not writable")
+
+def get_default_serverid():
+ serverid = os.environ.get(ENVIRON_SERVERID, None)
+ if not serverid:
+ serverid = socket.gethostname().split('.')[0]
+ return serverid
+
+def validate_serverid(serverid):
+ if not serverid:
+ raise Exception("Server id is not defined")
+ return serverid
+
+
+def get_inst_dir(serverid):
+ assert serverid
+ home = os.getenv("HOME")
+ inst_initconfig_file = "%s/%s/dirsrv-%s" % (home, INITCONFIGDIR, serverid)
+ if not os.path.isfile(inst_initconfig_file):
+ raise Exception("%s config file not found" % inst_initconfig_file)
+ f = open(inst_initconfig_file, "r")
+ for line in f:
+ if line.startswith("INST_DIR"):
+ inst_dir = line.split("=")[1]
+ inst_dir = inst_dir.replace("\r", "")
+ inst_dir = inst_dir.replace("\n", "")
+ return inst_dir
+
+def sanity_check():
+ if os.getuid() == 0:
+ raise Exception("Not tested for root user.. sorry")
+
+ home = os.getenv("HOME")
+ inst_initconfig_dir = "%s/%s" % (home, INITCONFIGDIR)
+ if not os.path.isdir(inst_initconfig_dir):
+ raise Exception("Please create the directory \'%s\' and retry."
% inst_initconfig_dir )
+
+class DSadmCmd(object):
+ def __init__(self):
+ self.version = '0.1'
+
+ def _start_subparser(self, subparsers):
+ start_parser = subparsers.add_parser(
+ 'start',
+ help='Start a Directory Server Instance')
+ start_parser.add_argument('-I', '--server-id',
dest='server_id', type=str, nargs='?',
+ metavar='SERVER-ID',
+ help='Server Identifier (Default: %s) ' %
get_default_serverid())
+ start_parser.set_defaults(func=self.start_action)
+
+ def _stop_subparser(self, subparsers):
+ start_parser = subparsers.add_parser(
+ 'stop',
+ help='Stop a Directory Server Instance')
+ start_parser.add_argument('-I', '--server-id',
dest='server_id', type=str, nargs='?',
+ metavar='SERVER-ID',
+ help='Server Identifier (Default: %s) ' %
get_default_serverid())
+ start_parser.set_defaults(func=self.stop_action)
+
+ def _restart_subparser(self, subparsers):
+ start_parser = subparsers.add_parser(
+ 'restart',
+ help='Retart a Directory Server Instance')
+ start_parser.add_argument('-I', '--server-id',
dest='server_id', type=str, nargs='?',
+ metavar='SERVER-ID',
+ help='Server Identifier (Default: %s) ' %
get_default_serverid())
+ start_parser.set_defaults(func=self.restart_action)
+
+ def _delete_subparser(self, subparsers):
+ delete_parser = subparsers.add_parser(
+ 'delete',
+ help='Delete a Directory Server Instance')
+ delete_parser.add_argument('-I', '--server-id',
dest='server_id', type=str, nargs='?',
+ metavar='SERVER-ID',
+ help='Server Identifier (Default: %s) ' %
get_default_serverid())
+ delete_parser.add_argument('-debug', '--debug',
dest='debug_level', type=int, nargs='?',
+ metavar='DEBUG_LEVEL',
+ help='Debug level (Default: 0)')
+ delete_parser.set_defaults(func=self.delete_action)
+
+ def _create_subparser(self, subparsers):
+ create_parser = subparsers.add_parser(
+ 'create',
+ help='Create a Directory Server Instance')
+ create_parser.add_argument('-I', '--server-id',
dest='server_id', type=str, nargs='?',
+ metavar='SERVER-ID',
+ help='Server Identifier (Default: %s) ' %
get_default_serverid())
+ create_parser.add_argument('-s', '--suffix',
dest='suffix', type=str, nargs='?',
+ metavar='SUFFIX-DN',
+ help='Suffix (Default: create no suffix)')
+ create_parser.add_argument('-p', '--port', dest='port',
type=int, nargs='?',
+ metavar='NON-SECURE-PORT',
+ help='Normal Port to listen (Default:
%s(root)/%s(non-root)) ' % (DEFAULT_PORT_ROOT, DEFAULT_PORT_NON_ROOT))
+
+ create_parser.add_argument('-P', '--secure-port',
dest='secure_port', type=int, nargs='?',
+ metavar='SECURE-PORT',
+ help='Secure Port to listen (Default:
%s(root)/%s(non-root))' % (DEFAULT_SECURE_PORT_ROOT, DEFAULT_SECURE_PORT_NON_ROOT))
+
+ create_parser.add_argument('-D', '--rootDN',
dest='root_dn', type=str, nargs='?',
+ metavar='ROOT-DN',
+ help='Uses DN as Directory Manager DN (Default:
\'%s\')' % (DEFAULT_ROOT_DN))
+
+ create_parser.add_argument('-u', '--user-name',
dest='user_name', type=str, nargs='?',
+ metavar='USER-NAME',
+ help='User name of the instance owner (Default: %s)'
% DEFAULT_USER)
+
+ create_parser.add_argument('-g', '--group-name',
dest='group_name', type=str, nargs='?',
+ metavar='GROUP-NAME',
+ help='Group name of the instance owner (Default: %s)'
% DEFAULT_GROUP)
+
+ create_parser.add_argument('-d', '--directory-path',
dest='directory_path', type=str, nargs='?',
+ metavar='DIRECTORY-PATH',
+ help='Installation directory path (Default: %s)' %
get_default_directory())
+ create_parser.add_argument('-debug', '--debug',
dest='debug_level', type=int, nargs='?',
+ metavar='DEBUG_LEVEL',
+ help='Debug level (Default: 0)')
+ create_parser.add_argument('-k', '--keep_template',
dest='keep_template', type=str, nargs='?',
+ help='Keep template file')
+
+ create_parser.set_defaults(func=self.create_action)
+
+ #
+ # common function for start/stop/restart actions
+ #
+ def script_action(self, args, script, action_str):
+ args = vars(args)
+ serverid = args.get('server_id', None)
+ if not serverid:
+ serverid = get_default_serverid()
+
+ script_file = "%s/%s" % (get_inst_dir(serverid), script)
+ if not os.path.isfile(script_file):
+ raise Exception("%s not found" % script_file)
+
+ if not os.access(script_file, os.X_OK):
+ raise Exception("%s not executable" % script_file)
+
+ env = os.environ.copy()
+ prog = [ script_file ]
+ pipe = Popen(prog, cwd=os.getcwd(), env=env,
+ stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ child_stdin = pipe.stdin
+ child_stdout = pipe.stdout
+ for line in child_stdout:
+ sys.stdout.write(line)
+ child_stdout.close()
+ child_stdin.close()
+
+ rc = pipe.wait()
+ if rc == 0:
+ print "Directory %s %s" % (serverid, action_str)
+ else:
+ print "Failure: directory %s not %s (%s)" % (serverid, action_str,
rc)
+ return
+
+ def start_action(self, args):
+ self.script_action(args, SCRIPT_START, "started")
+
+
+ def stop_action(self, args):
+ self.script_action(args, SCRIPT_STOP, "stopped")
+
+
+ def restart_action(self, args):
+
+ self.script_action(args, SCRIPT_RESTART, "restarted")
+
+ def delete_action(self, args):
+ args = vars(args)
+ serverid = args.get('server_id', None)
+ if not serverid:
+ serverid = get_default_serverid()
+
+ #prepare the remove-ds options
+ debug_level = args.get('debug_level', None)
+ if debug_level:
+ debug_str = ['-d']
+ for i in range(1, int(debug_level)):
+ debug_str.append('d')
+ debug_str = ''.join(debug_str)
+
+ env = os.environ.copy()
+ prog = [REMOVE_DS]
+ if debug_level:
+ prog.append(debug_str)
+ prog.append("-i")
+ prog.append("slapd-%s" % serverid)
+
+ # run the REMOVE_DS command and print the possible output
+ pipe = Popen(prog, cwd=os.getcwd(), env=env,
+ stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ child_stdin = pipe.stdin
+ child_stdout = pipe.stdout
+ for line in child_stdout:
+ if debug_level:
+ sys.stdout.write(line)
+ child_stdout.close()
+ child_stdin.close()
+
+ rc = pipe.wait()
+ if rc == 0:
+ print "Directory server \'%s\' successfully deleted" %
serverid
+ else:
+ print "Fail to delete directory \'%s\': %d" % (serverid,
rc)
+ return
+
+ #
+ # used by create subcommand to build the template file
+ #
+ def _create_setup_ds_file(self, args, user=None, group=None):
+ # Get/checks the argument with the following order
+ # - parameter
+ # - Environment
+ # - default
+ serverid = args.get('server_id', None)
+ if not serverid:
+ serverid = get_default_serverid()
+ serverid = validate_serverid(serverid)
+
+ username = args.get('user_name', None)
+ if not username:
+ username = get_default_user()
+ username = validate_user(username)
+
+ groupname = args.get('group_name', None)
+ if not groupname:
+ groupname = get_default_group()
+ groupname = validate_group(groupname)
+
+ directoryname = args.get('directory_path', None)
+ if not directoryname:
+ directoryname = get_default_directory()
+ validate_directory(directoryname)
+
+ portnumber = args.get('port', None)
+ if not portnumber:
+ portnumber = get_default_port()
+ validate_port(portnumber)
+
+ suffix = args.get('suffix', None)
+
+ tempf = tempfile.NamedTemporaryFile(delete=False)
+
+ tempf.write('[General]\n')
+ tempf.write('FullMachineName=%s\n' % DEFAULT_HOSTNAME)
+ tempf.write('SuiteSpotUserID=%s\n' % username)
+ tempf.write('SuiteSpotGroup=%s\n' % groupname)
+ tempf.write('ServerRoot=%s\n' % directoryname)
+ tempf.write('\n')
+ tempf.write('[slapd]\n')
+ tempf.write('ServerPort=1389\n')
+ tempf.write('ServerIdentifier=%s\n' % serverid)
+ if suffix:
+ tempf.write('Suffix=%s\n' % suffix)
+ tempf.write('RootDN=cn=Directory Manager\n')
+ tempf.write('RootDNPwd=Secret12\n')
+ tempf.write('sysconfdir=%s/etc\n' % directoryname)
+ tempf.write('localstatedir=%s/var\n' % directoryname)
+ tempf.write('inst_dir=%s/lib/dirsrv/slapd-%s\n'% (directoryname,
serverid))
+ tempf.write('config_dir=%s/etc/dirsrv/slapd-%s\n' % (directoryname,
serverid))
+ tempf.close()
+
+ keep_template = args.get('keep_template', None)
+ if keep_template:
+ shutil.copy(tempf.name, keep_template)
+
+
+ return tempf
+
+ #
+ # It silently creates an instance.
+ # After creation the instance is started
+ #
+ def create_action(self, args):
+ args = vars(args)
+
+ # retrieve the serverid here just to log the final status
+ serverid = args.get('server_id', None)
+ if not serverid:
+ serverid = get_default_serverid()
+
+ # prepare the template file
+ tempf = self._create_setup_ds_file(args)
+
+ #prepare the setup-ds options
+ debug_level = args.get('debug_level', None)
+ if debug_level:
+ debug_str = ['-d']
+ for i in range(1, int(debug_level)):
+ debug_str.append('d')
+ debug_str = ''.join(debug_str)
+
+ #
+ # run the SETUP_DS command and print the possible output
+ #
+ env = os.environ.copy()
+ prog = [SETUP_DS]
+ if debug_level:
+ prog.append(debug_str)
+ prog.append("--silent")
+ prog.append("--file=%s" % tempf.name)
+ tempf.close()
+
+ pipe = Popen(prog, cwd=os.getcwd(), env=env,
+ stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ child_stdin = pipe.stdin
+ child_stdout = pipe.stdout
+ for line in child_stdout:
+ if debug_level:
+ sys.stdout.write(line)
+ child_stdout.close()
+ child_stdin.close()
+
+ os.unlink(tempf.name)
+ rc = pipe.wait()
+ if rc == 0:
+ print "Directory server \'%s\' successfully created" %
serverid
+ else:
+ print "Fail to create directory \'%s\': %d" % (serverid,
rc)
+ return
+
+ #
+ # parser of the main command. It contains subcommands
+ #
+ def get_parser(self, argv):
+
+
+ parser = argparse.ArgumentParser(
+ description='Managing a local directory server instance')
+
+ subparsers = parser.add_subparsers(
+ metavar='SUBCOMMAND',
+ help='The action to perform')
+
+ #pdb.set_trace()
+ # subcommands
+ self._create_subparser(subparsers)
+ self._delete_subparser(subparsers)
+ self._start_subparser(subparsers)
+ self._stop_subparser(subparsers)
+ self._restart_subparser(subparsers)
+
+ # Sanity check that the debug level is valid
+ args = vars(parser.parse_args(argv))
+ debug_level = args.get('debug_level', None)
+ if debug_level and (int(debug_level) < 1 or int(debug_level > 5)):
+ raise Exception("invalid debug level: range 1..5")
+
+ return parser
+
+ def main(self, argv):
+ sanity_check()
+ parser = self.get_parser(argv)
+ args = parser.parse_args(argv)
+ args.func(args)
+ return
+
+if __name__ == '__main__':
+ DSadmCmd().main(sys.argv[1:])
diff --git a/ldap/admin/src/scripts/DSCreate.pm.in
b/ldap/admin/src/scripts/DSCreate.pm.in
index 2756b85..ae8d1e3 100644
--- a/ldap/admin/src/scripts/DSCreate.pm.in
+++ b/ldap/admin/src/scripts/DSCreate.pm.in
@@ -986,7 +986,7 @@ sub updateSelinuxPolicy {
my $inf = shift;
# if selinux is not available, do nothing
- if ("@with_selinux@") {
+ if ((getLogin() eq 'root') and "@with_selinux@") {
my $localstatedir = $inf->{slapd}->{localstatedir};
# run restorecon on all of the parent directories we
@@ -1062,7 +1062,7 @@ sub updateTmpfilesDotD {
my $parentdir;
# if tmpfiles.d is not available, do nothing
- if ($dir and -d $dir) {
+ if ((getLogin() eq 'root') and $dir and -d $dir) {
my $filename =
"$dir/@package_name(a)-$inf->{slapd}->{ServerIdentifier}.conf";
if (-f $filename) {
debug(3, "Removing the old tmpfile: $filename\n");
@@ -1131,7 +1131,7 @@ sub updateSystemD {
my $confbasedir = "@systemdsystemconfdir@";
my $confdir = "$confbasedir/@systemdgroupname@.wants";
- if (!$unitdir or !$confdir or ! -d $unitdir or ! -d $confdir) {
+ if ((getLogin() ne 'root') or !$unitdir or !$confdir or ! -d $unitdir or ! -d
$confdir) {
debug(3, "no systemd - skipping\n");
return ();
}
@@ -1421,7 +1421,7 @@ sub removeDSInstance {
my $tmpfilesdir = "@with_tmpfiles_d@";
my $tmpfilesname = "$tmpfilesdir/@package_name(a)-$inst.conf";
- if ($tmpfilesdir && -d $tmpfilesdir && -f $tmpfilesname) {
+ if ((getLogin() eq 'root') && $tmpfilesdir && -d $tmpfilesdir
&& -f $tmpfilesname) {
my $rc = unlink($tmpfilesname);
if ( 0 == $rc )
{
@@ -1431,7 +1431,7 @@ sub removeDSInstance {
}
# remove the selinux label from the ports if needed
- if ("@with_selinux@") {
+ if ((getLogin() eq 'root') and "@with_selinux@") {
foreach my $port (@{$entry->{"nsslapd-port"}})
{
my $semanage_err;