@@ -10,6 +10,7 @@ Doxygen/
*.la
src/domainroot/fedfs-domainroot
src/fedfsd/fedfsd
+src/jumpstart/nsdb-jumpstart
src/nsdbparams/nsdbparams
src/nfsref/nfsref
src/mount/mount.fedfs
@@ -187,6 +187,7 @@ AC_CONFIG_FILES([Makefile
src/fedfsc/Makefile
src/fedfsd/Makefile
src/include/Makefile
+ src/jumpstart/Makefile
src/libadmin/Makefile
src/libjunction/Makefile
src/libnsdb/Makefile
@@ -199,5 +200,6 @@ AC_CONFIG_FILES([Makefile
src/nsdbparams/Makefile
src/PyFedfs/Makefile
src/PyFedfs/domainroot/Makefile
+ src/PyFedfs/jumpstart/Makefile
src/plug-ins/Makefile])
AC_OUTPUT
@@ -38,7 +38,7 @@ NSDB_CLIENT_CMDS = nsdb-create-fsl.8 nsdb-create-fsn.8 \
dist_man7_MANS = fedfs.7 nsdb-parameters.7
dist_man8_MANS = rpc.fedfsd.8 mount.fedfs.8 fedfs-map-nfs4.8 nfsref.8 \
- nsdbparams.8 fedfs-domainroot.8 \
+ nsdbparams.8 fedfs-domainroot.8 nsdb-jumpstart.8 \
$(FEDFS_CLIENT_CMDS) $(NSDB_CLIENT_CMDS)
CLEANFILES = cscope.in.out cscope.out cscope.po.out *~
new file mode 100644
@@ -0,0 +1,404 @@
+.\"@(#)nsdb-jumpstart.8
+.\"
+.\" @file doc/man/nsdb-jumpstart.8
+.\" @brief man page for nsdb-jumpstart tool
+.\"
+
+.\"
+.\" Copyright 2013 Oracle. All rights reserved.
+.\"
+.\" This file is part of fedfs-utils.
+.\"
+.\" fedfs-utils is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License version 2.0 as
+.\" published by the Free Software Foundation.
+.\"
+.\" fedfs-utils 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 version 2.0 for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" version 2.0 along with fedfs-utils. If not, see:
+.\"
+.\" http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+.\"
+.TH NSDB-JUMPSTART 8 "@publication-date@"
+.SH NAME
+nsdb-jumpstart \- Administer a basic FedFS NSDB using OpenLDAP
+.SH SYNOPSIS
+.B nsdb-jumpstart
+.RB [ \-h , \-\-help ]
+.RB [ \-\-version ]
+.P
+.B nsdb-jumpstart
+.RB [ \-\-statedir =
+.IR statedir ]
+.B install
+.RB [ \-\-security =
+.IR mode ]
+.P
+.B nsdb-jumpstart
+.RB [ \-\-statedir =
+.IR statedir ]
+.B status
+.P
+.B nsdb-jumpstart
+.RB [ \-\-statedir =
+.IR statedir ]
+.B backup
+.P
+.B nsdb-jumpstart
+.RB [ \-\-statedir =
+.IR statedir ]
+.B restore
+.RI [ backup-name ]
+.SH INTRODUCTION
+RFC 5716 introduces the Federated File System (FedFS, for short).
+FedFS is an extensible standardized mechanism
+by which system administrators construct
+a coherent namespace across multiple file servers using
+.IR "file system referrals" .
+For further details, see
+.BR fedfs (7).
+.P
+A FedFS domain's namespace is joined together via
+.IR junctions .
+When a file-access client encounters a junction on a file server,
+the file server provides a list of locations where that client
+can access the target file set to which the juntion refers.
+.P
+In a FedFS domain, these location lists are stored on one or more LDAP servers,
+known as
+.IR "namespace databases" ,
+or
+.IR NSDBs ,
+for short.
+.P
+FedFS-enabled file servers access the information stored
+on NSDBs via standard LDAP queries.
+Tools that administer a FedFS domain use ldapmodify queries
+to manage information stored on an NSDB.
+File-access clients have no need to access NSDBs directly.
+.P
+Further information about junctions and NSDBs is available in
+.BR fedfs (7).
+.SH DESCRIPTION
+The FedFS NSDB Proposed Standard allows flexible use
+of any LDAP server and its Directory Information Tree
+to store and manage NSDB information.
+.P
+The
+.BR nsdb-jumpstart (8)
+command provides a simplified but fully capable stand-alone
+NSDB based specifically on OpenLDAP.
+Using this command,
+you can install a fresh NSDB, or back up or restore your NSDB data.
+It can even construct a self-signed x.509 certificate to enable
+secure NSDB queries.
+.SS Operation
+The
+.B install
+subcommand sets up an empty NSDB, ready to be used in a FedFS domain.
+The new NSDB replaces any OpenLDAP configuration
+that may already exist on the system.
+OpenLDAP must already be installed on the system.
+.P
+Once the new NSDB is running,
+FedFS fileset location information is stored as records
+in a Directory Information Tree under the NCE.
+This information is managed with commands like
+.BR nsdb-create-fsn (8).
+.P
+A handful of parameters are needed to set up the new NSDB.
+These are gathered via a brief interview.
+The domain name and administrator credentials are provided during
+this interview.
+Passwords are not checked for strength,
+however blank passwords are not permitted.
+.P
+The baseline security requirements for the NSDB are specified
+at install time using the
+.B \-\-security=
+option. See the
+.B SECURITY
+section for an in-depth discussion.
+.P
+Once set up with the
+.B install
+subcommand, OpenLDAP listens for LDAP queries on the standard LDAP port (389).
+The underlying LDAP server can be configured like any other OpenLDAP server
+using the new-style
+.I cn=config
+configuration interface.
+.P
+To display the current status of the NSDB service on the local host, use the
+.B status
+subcommand.
+Information about the local NSDB service is displayed, including whether
+the LDAP service is started, whether it actually is an NSDB, and
+whether TLS security is required to use it.
+.P
+The
+.BR nsdb-jumpstart (8)
+command also provides backup and restore facilities.
+The
+.B backup
+subcommand saves location information stored on the local NSDB
+to a dated LDIF file.
+LDIF files created by the
+.B backup
+command are stored in the
+.I @statedir@/nsdb-backup
+directory by default.
+.P
+The
+.B restore
+subcommand completely replaces the contents of the NSDB with a backup
+contained in of one of the previously saved LDIF files.
+The
+.B restore
+subcommand takes one positional argument, which is the name of
+the backup to restore.
+A list of backups is displayed by using the
+.B restore
+subcommand with no argument.
+.P
+The
+.BR nsdb-jumpstart (8)
+command must run as root.
+A audit log of each
+.BR nsdb-jumpstart (8)
+operation is stored in
+.IR @statedir@/nsdb-jumpstart.log .
+.SS Subcommands
+Valid
+.BR nsdb-jumpstart (8)
+subcommands are:
+.IP "\fBinstall\fP"
+Replace the OpenLDAP configuration on the local system with
+a ready-built NSDB.
+The user is asked to confirm before action is taken.
+.IP
+Specifying the
+.B \-\-security=
+option sets the transport security that the NSDB requires
+clients to use when communicating with it.
+.IP "\fBstatus\fP"
+Display the status of the NSDB on the local system.
+This subcommand takes no arguments.
+.IP "\fBbackup\fP"
+Generate an LDIF containing the NSDB information stored
+on the local LDAP server.
+The LDIF is stored in a dated file under
+.IR @statedir@/nsdb-backup .
+This subcommand takes no arguments.
+.IP "\fBrestore\fP"
+Replace the NSDB information on the local LDAP server
+with the contents of an LDIF.
+This subcommand takes a backup name as an argument.
+If no backup name is given,
+a list of backups that can be restored is displayed.
+The user is asked to confirm before action is taken.
+.SS Command line options
+The following options are specified before the subcommand on the command line.
+.IP "\fB\-\-help"
+Displays usage and copyright information, then exit.
+.IP "\fB\-\-version"
+Displays fedfs-utils version information, then exit.
+.IP "\fB\-\-stateidr=\fIpathname\fP"
+Specifies the pathname of the local directory
+under which NSDB data is maintained.
+By default, this directory is
+.IR @statedir .
+.SS Subcommand options
+.IP "\fB\-\-security=\fImode\fP"
+Selects the security mode of the NSDB.
+This option may be specified only on the
+.B install
+subcommand.
+Valid
+.I mode
+values are
+.B none
+and
+.BR tls .
+.P
+If
+.B none
+is specified, or the
+.B \-\-security=
+option is not specified, clients can connect to this NSDB in the clear.
+.P
+If
+.B tls
+is specified, the
+.B install
+subcommand creates a self-signed x.509 certificate,
+and configures the NSDB so that clients are required to use TLS
+when connecting to the NSDB.
+.SH EXIT CODES
+The
+.BR nsdb-jumpstart (8)
+command returns one of two values upon exit.
+.TP
+.B 0
+The subcommand succeeded.
+.TP
+.B 1
+The subcommand failed.
+.SH EXAMPLES
+Suppose you are the FedFS administrator of the
+.I example.net
+FedFS domain.
+After you have chosen a reliable server in the
+.I example.net
+domain to act as your NSDB, log in on that server as root,
+ensure that OpenLDAP is installed,
+and that any configuration can be discarded.
+.P
+To create a new NSDB with a self-signed certificate for the
+.I example.net
+domain, use:
+.RS
+.sp
+# ./nsdb-jumpstart install --security=tls
+.br
+This command is about to replace the OpenLDAP configuration on this system.
+.br
+Do you want to continue? [y/N] y
+.br
+Enter the name of the Fedfs domain this NSDB will server
+.br
+FedFS domain [ example.net ]:
+.br
+Enter the LDAP administrator DN for this NSDB
+.br
+Admin DN [ cn=admin,cn=config ]:
+.br
+Enter the LDAP administrator password for this DN
+.br
+New password:
+.br
+Re-enter new password:
+.br
+Enter the NSDB administrator password for this DN
+.br
+New password:
+.br
+Re-enter new password:
+.br
+Last chance: about to replace the OpenLDAP configuration on this system.
+.br
+Continue? [y/N] y
+.br
+Setting up a self-signed x.509 certificate. Please answer the following questions:
+.br
+
+.br
+Country (C)? US
+.br
+State or province (ST)? Massachusetts
+.br
+City (L)? Boston
+.br
+Organization (O)? Red Sox
+.br
+Organizational unit (OU)? Fans
+.br
+
+.br
+NSDB configuration was successful.
+.br
+
+.br
+Slapd is enabled and running
+.br
+The LDAP administrator DN is: cn=admin,cn=config
+.br
+The NSDB administrator DN is: cn=NSDB Manager,dc=example,dc=net
+.br
+The NCE is: ou=fedfs,dc=example,dc=net
+.br
+
+.br
+Distribute the NSDB's certificate in /etc/openldap/nsdb-cert.pem
+.br
+#
+.RE
+.SH SECURITY
+The NSDB created by the
+.BR nsdb-jumpstart (8)
+command allows anonymous read access to the NCE and all entries under it.
+The LDAP server's rootDSE is also readable by anyone.
+An NSDB client must bind with administrator privileges
+to update NSDB records for a FedFS domain.
+ACLs may be adjusted after the NSDB is set up with
+.BR nsdb-jumpstart (8).
+.P
+Before binding, however, NSDB clients must connect to the NSDB to use it.
+The
+.B \-\-security=
+setting determines what type of transport layer security is required
+to connect to the NSDB.
+.P
+When the
+.B \-\-security=none
+option is specified during NSDB setup,
+or if no
+.B \-\-security=
+setting is specified,
+NSDB clients can connect to the NSDB using an unencrypted
+connection to the standard LDAP port (389).
+.P
+By specifying the
+.B \-\-security=tls
+option on the
+.BR nsdb-jumpstart (8)
+command, a self-signed x.509 certificate is created
+that NSDB clients must use to authenticate the NSDB and its contents.
+The underlying LDAP server requires the use of TLS
+and the use of AES or better encryption when a client access the NSDB.
+The NSDB never authenticates its clients.
+.P
+To use this NSDB, the new certificate material must be distributed
+to NSDB clients (fileservers and administrative systems)
+and installed using the
+.BR nsdbparams (8)
+command, or it can be transferred directly to NSDB clients that
+are running the
+.BR rpc.fedfsd (8)
+daemon.
+.P
+The use of a transport encryption mechanism such as TLS is
+strongly recommended to protect NSDB requests on untrusted networks.
+SASL is currently not supported for the NSDB protocol.
+.SH FILES
+.TP
+.I @statedir/nsdb-jumpstart.log
+Log file created during subcommand processing
+.TP
+.I /etc/openldap/nsdb-cert.pem
+File containing the server's x.509 certificate, in PEM format
+.TP
+.I /etc/openldap/nsdb-key.pem
+File containing the server's private key, in PEM format
+.TP
+.I @statedir/nsdb-db
+Directory containing back-end database for the LDAP server's
+domain controller root suffix
+.SH "SEE ALSO"
+.BR fedfs (7),
+.BR nfsref (8),
+.BR nsdb-create-fsn (8),
+.BR nsdbparams (8),
+.BR rpc.fedfsd (8)
+.sp
+RFC 5716 for FedFS requirements and overview
+.SH COLOPHON
+This page is part of the fedfs-utils package.
+A description of the project and information about reporting bugs
+can be found at
+.IR http://wiki.linux-nfs.org/wiki/index.php/FedFsUtilsProject .
+.SH "AUTHOR"
+Chuck Lever <chuck.lever@oracle.com>
@@ -26,7 +26,7 @@
SUBDIRS = include libxlog libadmin libnsdb libjunction \
libparser libsi \
fedfsc fedfsd mount nfsref nsdbc nsdbparams \
- plug-ins PyFedfs domainroot
+ plug-ins PyFedfs domainroot jumpstart
CLEANFILES = cscope.in.out cscope.out cscope.po.out *~
DISTCLEANFILES = Makefile.in
@@ -23,7 +23,7 @@
## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
##
-SUBDIRS = domainroot
+SUBDIRS = domainroot jumpstart
pyfedfs_PYTHON = __init__.py run.py userinput.py utilities.py
pyfedfsdir = $(pythondir)/PyFedfs
new file mode 100644
@@ -0,0 +1,31 @@
+##
+## @file src/PyFedfs/jumpstart/Makefile.am
+## @brief Process this file with automake to produce src/PyFedfs/jumpstart/Makefile.in
+##
+
+##
+## Copyright 2013 Oracle. All rights reserved.
+##
+## This file is part of fedfs-utils.
+##
+## fedfs-utils is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License version 2.0 as
+## published by the Free Software Foundation.
+##
+## fedfs-utils 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 version 2.0 for more details.
+##
+## You should have received a copy of the GNU General Public License
+## version 2.0 along with fedfs-utils. If not, see:
+##
+## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+##
+
+jumpstart_PYTHON = __init__.py backup.py cert.py firewall.py \
+ install.py slapd.py status.py transaction.py
+jumpstartdir = $(pythondir)/PyFedfs/jumpstart
+
+CLEANFILES = cscope.in.out cscope.out cscope.po.out *~
+DISTCLEANFILES = Makefile.in
new file mode 100644
@@ -0,0 +1,23 @@
+"""
+PyFedfs
+
+This module contains components of the nsdb-jumpstart command.
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
new file mode 100644
@@ -0,0 +1,186 @@
+"""
+Back up an OpenLDAP-based FedFS NSDB
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+
+try:
+ import sys
+ import os
+ import logging as log
+ import datetime
+
+ from PyFedfs.jumpstart.slapd import backup_slapd_backend
+ from PyFedfs.jumpstart.slapd import make_ldap_directory
+ from PyFedfs.jumpstart.slapd import get_slapd_backend_dir
+ from PyFedfs.jumpstart.slapd import restore_slapd_backend
+ from PyFedfs.jumpstart.transaction import Transaction
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import start_service, stop_service
+ from PyFedfs.userinput import confirm
+ from PyFedfs.utilities import list_directory
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+BACKUP_DIRNAME = 'nsdb-backup'
+
+
+def do_backup(backup_dir, nocompress):
+ """
+ Backup the local NSDB
+
+ Returns a shell exit status
+ """
+ ret = stop_service('slapd')
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = backup_slapd_backend(backup_dir,
+ datetime.datetime.now().strftime("%F-%T"),
+ nocompress)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = start_service('slapd')
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return EXIT_SUCCESS
+
+
+def subcmd_backup(args):
+ """
+ Run the backup procedure
+
+ Returns a shell exit status
+ """
+ backup_dir = os.path.join(args.statedir, BACKUP_DIRNAME)
+
+ ret = make_ldap_directory(backup_dir, 0700)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ log.info('Running NSDB backup...')
+
+ os.umask(0277)
+ ret = do_backup(backup_dir, args.nocompress)
+ if ret != EXIT_SUCCESS:
+ log.info('Command aborted')
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+
+def list_backups(args):
+ """
+ List available backups
+
+ Returns a shell exit status
+ """
+ backup_dir = os.path.join(args.statedir, BACKUP_DIRNAME)
+
+ listing = list_directory(backup_dir)
+
+ output = []
+ for item in listing:
+ if item.endswith('.ldif'):
+ output.append(item[:-5])
+ if item.endswith('.ldif.gz'):
+ output.append(item[:-8])
+
+ if len(output) == 0:
+ log.info('No backups are available')
+ return EXIT_SUCCESS
+
+ log.info('Listing NSDB backups...')
+ for line in list(set(output)):
+ log.info(line)
+
+ return EXIT_SUCCESS
+
+
+def preserve_and_restore(args):
+ """
+ Restore the local NSDB
+
+ Returns a shell exit status
+ """
+ backend_dir = get_slapd_backend_dir()
+ if backend_dir == '':
+ log.error('Failed to find local NSDB\'s backend database')
+ return EXIT_FAILURE
+ log.info('NSDB backend database: %s', backend_dir)
+
+ xact = Transaction()
+ xact.add(backend_dir)
+ xact.checkpoint()
+
+ backup_dir = os.path.join(args.statedir, BACKUP_DIRNAME)
+ ret = restore_slapd_backend(backend_dir, backup_dir, args.backup)
+ if ret != EXIT_SUCCESS:
+ xact.revert()
+ return EXIT_FAILURE
+
+ xact.commit()
+ return EXIT_SUCCESS
+
+
+def do_restore(args):
+ """
+ Restore the local NSDB
+
+ Returns a shell exit status
+ """
+ ret = stop_service('slapd')
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = preserve_and_restore(args)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return start_service('slapd')
+
+
+def subcmd_restore(args):
+ """
+ Run the backup procedure
+
+ Returns a shell exit status
+ """
+ if args.backup == '':
+ return list_backups(args)
+
+ print('This command replaces all the NSDB information on this system.')
+ if not confirm('Do you want to continue?'):
+ log.error('Quitting...')
+ return EXIT_FAILURE
+
+ log.info('Restoring NSDB...')
+
+ if do_restore(args) != EXIT_SUCCESS:
+ log.info('Command aborted')
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+
+__all__ = ['subcmd_backup', 'subcmd_restore']
new file mode 100644
@@ -0,0 +1,91 @@
+"""
+Create a self-signed x.509 certificate for an LDAP server
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+
+try:
+ import sys
+ import os
+ import socket
+ from OpenSSL import crypto
+
+ from PyFedfs.run import EXIT_SUCCESS
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def create_self_signed_certificate(certfile, keyfile, owner_uid, owner_gid):
+ """
+ Create a self-signed server certificate
+ """
+ keypair = crypto.PKey()
+ keypair.generate_key(crypto.TYPE_RSA, 2048)
+
+ ss_cert = crypto.X509()
+
+ print('\nSetting up a self-signed x.509 certificate. ' \
+ 'Please answer the following questions:\n')
+ ss_cert.get_subject().C = raw_input('Country (C)? ')
+ ss_cert.get_subject().ST = raw_input('State or province (ST)? ')
+ ss_cert.get_subject().L = raw_input('City (L)? ')
+ ss_cert.get_subject().O = raw_input('Organization (O)? ')
+ ss_cert.get_subject().OU = raw_input('Organizational unit (OU)? ')
+ ss_cert.get_subject().CN = socket.getfqdn()
+ ss_cert.set_serial_number(1000)
+ ss_cert.gmtime_adj_notBefore(0)
+ ss_cert.gmtime_adj_notAfter(2 * 365 * 24 * 60 * 60)
+
+ ss_cert.set_issuer(ss_cert.get_subject())
+ ss_cert.set_pubkey(keypair)
+ ss_cert.sign(keypair, 'sha1')
+
+ cert_file = os.open(certfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0440)
+ os.fchown(cert_file, owner_uid, owner_gid)
+ os.write(cert_file, crypto.dump_certificate(crypto.FILETYPE_PEM, ss_cert))
+ os.close(cert_file)
+
+ key_file = os.open(keyfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0440)
+ os.fchown(key_file, owner_uid, owner_gid)
+ os.write(key_file, crypto.dump_privatekey(crypto.FILETYPE_PEM, keypair))
+ os.close(key_file)
+
+ return EXIT_SUCCESS
+
+
+def __ut_create_certificate():
+ """
+ Unit tests for create_self_signed_certificate
+ """
+ ret = create_self_signed_certificate('/tmp/cert.pem', '/tmp/key.pem',
+ os.geteuid(), os.getegid())
+ if ret != EXIT_SUCCESS:
+ return
+
+ print('\nDone. See /tmp/{cert,key}.pem\n')
+
+
+__all__ = ['create_self_signed_certificate']
+
+
+if __name__ == '__main__':
+ __ut_create_certificate()
new file mode 100644
@@ -0,0 +1,97 @@
+"""
+Manage local network firewall
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+
+try:
+ import sys
+ import os
+ import logging as log
+ from subprocess import Popen, PIPE
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import run_command
+ from PyFedfs.run import check_for_daemon
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def adjust_firewall():
+ """
+ Ensure LDAP (port 389) is allowed through the system firewall
+
+ Returns a shell exit status value
+ """
+ if not check_for_daemon('firewalld'):
+ log.info('firewalld is not running... skipping firewalld configuration')
+ return EXIT_SUCCESS
+
+ pathname = '/etc/firewalld/services/ldap.xml'
+ if os.path.isfile(pathname):
+ log.info('ldap.xml exists... skipping firewalld configuration')
+ return EXIT_SUCCESS
+
+ log.debug('Adjusting firewalld to permit LDAP service...')
+ try:
+ service = os.open(pathname, os.O_CREAT | os.O_WRONLY)
+ except OSError:
+ log.exception('Failed to create "%s"', pathname)
+ return EXIT_FAILURE
+
+ try:
+ os.fchmod(service, 0640)
+ os.write(service, '<?xml version="1.0" encoding="utf-8"?>\n')
+ os.write(service, '<?xml version="1.0" encoding="utf-8"?>\n')
+ os.write(service, '<service>\n')
+ os.write(service, ' <short>LDAP</short>\n')
+ os.write(service, ' <description>The Lightweight Directory '
+ 'Access Protocol is an application protocol for '
+ 'accessing and maintaining distributed directory '
+ 'information services over an Internet Protocol (IP) '
+ 'network. Directory services may provide any '
+ 'organized set of records, often with a hierarchical '
+ 'structure, such as a corporate email directory. '
+ 'Enable this option if you plan to provide an LDAP '
+ 'directory service (e.g. with slapd).</description>\n')
+ os.write(service, ' <port protocol="tcp" port="389"/>\n')
+ os.write(service, ' <port protocol="udp" port="389"/>\n')
+ os.write(service, '</service>\n')
+ except OSError:
+ log.exception('Failed to write "%s"', pathname)
+ os.close(service)
+ os.remove('/etc/firewalld/services/ldap.xml')
+ return EXIT_FAILURE
+ os.close(service)
+
+ ret = run_command(['firewall-cmd', '--reload'])
+ if ret != EXIT_SUCCESS:
+ os.remove('/etc/firewalld/services/ldap.xml')
+ return ret
+
+ # XXX: These are backwards: need the --reload after
+ # XXX: setting permanent configuration settings
+ return run_command(['firewall-cmd', '--permanent',
+ '--zone=public', '--add-service=ldap'])
+
+
+__all__ = ['adjust_firewall']
new file mode 100644
@@ -0,0 +1,341 @@
+"""
+Set up a simple FedFS NSDB using OpenLDAP
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+
+import sys
+import os
+import logging as log
+import socket
+import ldap
+
+try:
+ from PyFedfs.jumpstart.cert import create_self_signed_certificate
+ from PyFedfs.jumpstart.firewall import adjust_firewall
+ from PyFedfs.jumpstart.slapd import slapd_config, adjust_slapd_log
+ from PyFedfs.jumpstart.slapd import LDAP_UID, LDAP_GID
+ from PyFedfs.jumpstart.slapd import NSDB_CERTFILE, NSDB_KEYFILE
+ from PyFedfs.jumpstart.transaction import Transaction
+
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import stop_service, enable_and_start_service
+ from PyFedfs.userinput import confirm, get_password
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+PRESERVE_LIST = ['/etc/openldap/slapd.d',
+ NSDB_CERTFILE,
+ NSDB_KEYFILE]
+
+
+def get_domainname():
+ """
+ Get local system's domain name
+
+ Returns a string
+ """
+ hostname = socket.getfqdn().split('.')
+ if len(hostname) < 2:
+ return ''
+
+ domainname = '.'.join(hostname[1:])
+ return domainname.lower()
+
+
+def get_domaincontroller_dn(domainname):
+ """
+ Generate a domaincontroller DN
+
+ Returns a string
+ """
+ components = domainname.split('.')
+ if len(components) < 2:
+ return ''
+
+ distinguished_name = [[('dc', component, 1)] for component in components]
+ return ldap.dn.dn2str(distinguished_name)
+
+
+def get_nce_dn(domaincontroller):
+ """
+ Generate an NCE DN
+
+ Returns a string
+ """
+ try:
+ distinguished_name = ldap.dn.str2dn(domaincontroller)
+ except ldap.ENCODING_ERROR:
+ return ''
+
+ distinguished_name.insert(0, [('ou', 'fedfs', 1)])
+ return ldap.dn.dn2str(distinguished_name)
+
+
+def get_full_nsdb_admin(answers):
+ """
+ Generate NSDB administrator DN
+
+ Returns a string
+ """
+ try:
+ distinguished_name = ldap.dn.str2dn(answers['domaincontroller'])
+ except ldap.ENCODING_ERROR:
+ return ''
+
+ distinguished_name.insert(0, ldap.dn.str2dn(answers['nsdb_admin'])[0])
+ return ldap.dn.dn2str(distinguished_name)
+
+
+def ask_for_domainname(answers):
+ """
+ Ask for the system's domain name
+
+ Returns True if the interview data is good
+ """
+ answers['domainname'] = get_domainname()
+ print('Enter the name of the FedFS domain this NSDB will server')
+ if answers['domainname'] == []:
+ sys.stdout.write('FedFS domain: ')
+ choice = raw_input().lower()
+ if choice == '':
+ log.error('No domainname was provided')
+ return False
+ answers['domainname'] = choice
+ else:
+ sys.stdout.write('FedFS domain [ ' + answers['domainname'] + ' ]: ')
+ choice = raw_input().lower()
+ if choice != '':
+ answers['domainname'] = choice
+ return True
+
+
+def ask_for_domaincontroller(answers):
+ """
+ Ask for the domain controller DN
+
+ Returns True if the interview data is good
+ """
+ answers['domaincontroller'] = \
+ get_domaincontroller_dn(answers['domainname'])
+ if answers['domaincontroller'] == '':
+ log.error('An invalid domainname was provided')
+ return False
+ answers['nce'] = get_nce_dn(answers['domaincontroller'])
+ if answers['nce'] == '':
+ log.error('An invalid domainname was provided')
+ return False
+ log.info('Using "%s" as your FedFS domain name', answers['domainname'])
+ return True
+
+
+def ask_for_ldap_admin(answers):
+ """
+ Ask for the LDAP administrator DN and password
+
+ Returns True if the interview data is good
+ """
+ answers['ldap_admin'] = \
+ ldap.dn.dn2str([[('cn', 'admin', 1)], [('cn', 'config', 1)]])
+ print('Enter the LDAP administrator DN for this NSDB')
+ sys.stdout.write('Admin DN [ ' + answers['ldap_admin'] + ' ]: ')
+ choice = raw_input()
+ if choice != '':
+ answers['ldap_admin'] = choice
+ try:
+ ldap.dn.str2dn(choice)
+ except ldap.DECODING_ERROR:
+ log.error('An invalid administrator DN was provided')
+ return False
+ log.info('Using "%s" as your LDAP administrator', answers['ldap_admin'])
+
+ answers['ldap_password'] = \
+ get_password('Enter the LDAP administrator password for this DN')
+ if answers['ldap_password'] == '':
+ return False
+
+ return True
+
+
+def ask_for_nsdb_admin(answers):
+ """
+ Ask for the NSDB administrator DN and password
+
+ Returns True if the interview data is good
+ """
+ answers['nsdb_admin'] = 'cn=NSDB Manager'
+ answers['full_nsdb_admin'] = get_full_nsdb_admin(answers)
+ if answers['full_nsdb_admin'] == '':
+ return False
+ log.info('Using "%s" as your NSDB administrator',
+ answers['full_nsdb_admin'])
+
+ answers['nsdb_password'] = \
+ get_password('Enter the NSDB administrator password for this DN')
+ if answers['nsdb_password'] == '':
+ return False
+
+ return True
+
+
+def interview(answers):
+ """
+ Gather information for the configuration, perform some sanity checks
+
+ Returns True if the interview data is good
+ """
+ if not ask_for_domainname(answers):
+ return False
+ if not ask_for_domaincontroller(answers):
+ return False
+ if not ask_for_ldap_admin(answers):
+ return False
+ if not ask_for_nsdb_admin(answers):
+ return False
+ return True
+
+
+def setup_nsdb(answers):
+ """
+ Run the set-up procedure
+
+ Returns a shell exit status value
+ """
+
+ ret = slapd_config(answers)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = adjust_slapd_log()
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return adjust_firewall()
+
+
+def abort_command(xact):
+ """
+ Print a message and return failure
+
+ Returns a shell exit status value
+ """
+ xact.revert()
+
+ log.info('Command aborted')
+ return EXIT_FAILURE
+
+
+def do_setup(answers):
+ """
+ Interview user for configuration parameters, then run the setup
+
+ Returns a shell exit status value
+ """
+ print('Last chance: about to replace the OpenLDAP configuration '
+ 'on this system.')
+ if not confirm('Continue?'):
+ log.error('Quitting...')
+ return EXIT_FAILURE
+
+ xact = Transaction()
+ for item in answers['preserve_list']:
+ xact.add(item)
+ xact.checkpoint()
+
+ ret = stop_service('slapd')
+ if ret != EXIT_SUCCESS:
+ return abort_command(xact)
+
+ ret = setup_nsdb(answers)
+ if ret != EXIT_SUCCESS:
+ return abort_command(xact)
+
+ if answers['security'] == 'tls':
+ ret = create_self_signed_certificate(NSDB_CERTFILE, NSDB_KEYFILE,
+ LDAP_UID, LDAP_GID)
+ if ret != EXIT_SUCCESS:
+ return abort_command(xact)
+
+ enable_and_start_service('slapd')
+
+ log.info('\nNSDB configuration was successful.\n')
+ log.info('Slapd is enabled and running')
+ log.info('The LDAP administrator DN is: ' + answers['ldap_admin'])
+ log.info('The NSDB administrator DN is: ' + answers['full_nsdb_admin'])
+ log.info('The NCE is: ' + answers['nce'])
+ if answers['security'] == 'tls':
+ log.info('Distribute the certificate in %s', NSDB_CERTFILE)
+
+ xact.commit()
+
+ return EXIT_SUCCESS
+
+
+def openldap_is_installed():
+ """
+ Predicate: Is an OpenLDAP server package installed?
+
+ Returns True if OpenLDAP server software is found;
+ otherwise returns False
+ """
+ if not os.path.isdir('/etc/openldap'):
+ log.error('No OpenLDAP configuration directory')
+ return False
+
+ if not os.path.isfile('/etc/openldap/schema/fedfs.schema'):
+ log.error('The FedFS schema is not installed')
+ return False
+
+ log.info('OpenLDAP server software found, proceeding')
+ return True
+
+
+def subcmd_install(args):
+ """
+ Set up local LDAP server as NSDB based on interview responses
+
+ Returns a shell exit status value
+ """
+ if not openldap_is_installed():
+ log.error('Quitting...')
+ return EXIT_FAILURE
+
+ print('This command replaces the OpenLDAP configuration on this system.')
+ if not confirm('Do you want to continue?'):
+ log.error('Quitting...')
+ return EXIT_FAILURE
+
+ backend_dir = os.path.join(args.statedir, 'nsdb-db')
+ answers = {'security': args.security, 'dc_backend': backend_dir}
+
+ if not interview(answers):
+ log.error('Quitting...')
+ return EXIT_FAILURE
+
+ answers['preserve_list'] = PRESERVE_LIST
+ answers['preserve_list'].append(backend_dir)
+
+ return do_setup(answers)
+
+
+__all__ = ['subcmd_install']
new file mode 100644
@@ -0,0 +1,671 @@
+"""
+Utility functions for interacting with slapd tools
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+import sys
+import os
+import logging as log
+import tempfile
+import pwd
+import grp
+import ldap
+import ldif
+import socket
+
+from subprocess import Popen, PIPE
+
+try:
+ from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+ from PyFedfs.run import run_as_user, restart_service
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+LDAP_USERNAME = 'ldap'
+LDAP_GROUPNAME = 'ldap'
+
+LDAP_UID = pwd.getpwnam(LDAP_USERNAME).pw_uid
+LDAP_GID = grp.getgrnam(LDAP_GROUPNAME).gr_gid
+
+NSDB_CERTFILE = '/etc/openldap/nsdb-cert.pem'
+NSDB_KEYFILE = '/etc/openldap/nsdb-key.pem'
+
+
+def adjust_slapd_log():
+ """
+ Set up syslog configuration for slapd
+ slapd must be restarted after this procedure.
+
+ Returns a shell exit status value
+ """
+ try:
+ logfile = os.open('/var/log/slapd', os.O_CREAT | os.O_EXCL)
+ except OSError as inst:
+ if inst.errno != 17:
+ log.error('Failed to create slapd log file')
+ return EXIT_FAILURE
+ log.info('/var/log/slapd exists... skipping rsyslog configuration')
+ return EXIT_SUCCESS
+ os.close(logfile)
+
+ log.debug('Enabling slapd logging with rsyslog...')
+ try:
+ os.chown('/var/log/slapd', LDAP_UID, LDAP_GID)
+ except OSError:
+ log.error('Failed to chown slapd log file')
+ os.remove('/var/log/slapd')
+ return EXIT_FAILURE
+
+ try:
+ config = os.open('/etc/rsyslog.d/slapd.conf',
+ os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+ except OSError:
+ log.error('Failed to create slapd log file')
+ os.remove('/var/log/slapd')
+ return EXIT_FAILURE
+
+ try:
+ os.write(config, 'local4.*\t/var/log/slapd\n')
+ except OSError:
+ log.error('Failed to write rsyslog config file')
+ os.close(config)
+ os.remove('/etc/rsyslog.d/slapd.conf')
+ os.remove('/var/log/slapd')
+ return EXIT_FAILURE
+ os.close(config)
+
+ ret = restart_service('rsyslog')
+ if ret != EXIT_SUCCESS:
+ os.remove('/etc/rsyslog.d/slapd.conf')
+ os.remove('/var/log/slapd')
+
+ return ret
+
+
+def search_ldif(blob, attribute):
+ """
+ Look for 'attribute' in an LDIF
+
+ Returns the first attribute value if found, or '' if not
+ """
+ for line in blob.splitlines():
+ record = line.split()
+ if len(record) and record[0] == attribute + ':':
+ return record[1]
+ return ''
+
+
+def local_nce_found():
+ """
+ Predicate: Does the local configuration contain an NCE record?
+
+ Returns True if an NCE record is found in a local LDAP database
+ """
+ log.debug('Checking local configuration for an NCE record...')
+
+ try:
+ process = Popen(['slapcat', '-n2'],
+ stdout=PIPE, stderr=PIPE, shell=False)
+ output = process.communicate()[0]
+ except OSError:
+ log.error('slapcat command failed')
+ return False
+
+ return search_ldif(output, 'fedfsNceDN') != ''
+
+
+def __ut_local_nce_found():
+ """
+ Unit test for local_nce_found()
+ """
+ if local_nce_found():
+ print 'Local NCE found'
+ else:
+ print 'Local NCE not found'
+
+
+def local_tls_found():
+ """
+ Predicate: Is TLS enabled on the local configuration?
+
+ Returns True if TLS is enabled
+ """
+ log.debug('Checking local configuration for TLS support...')
+ try:
+ process = Popen(['slapcat', '-n0'],
+ stdout=PIPE, stderr=PIPE, shell=False)
+ output = process.communicate()[0]
+ except OSError:
+ log.error('slapcat command failed')
+ return False
+
+ return search_ldif(output, 'olcTLSCACertificateFile') != ''
+
+
+def __ut_local_tls_found():
+ """
+ Unit test for local_tls_found()
+ """
+ if local_tls_found():
+ print 'TLS is configured on local slapd service'
+ else:
+ print 'TLS is not configured on local slapd service'
+
+
+def get_slapd_backend_dir():
+ """
+ Get the directory pathname of the configured backend database
+
+ Returns a string
+ """
+ log.debug('Retrieving pathname of configured backend database...')
+
+ try:
+ process = Popen(['slapcat', '-n0'],
+ stdout=PIPE, stderr=PIPE, shell=False)
+ output = process.communicate()[0]
+ except OSError:
+ log.error('slapcat command failed')
+ return False
+
+ return search_ldif(output, 'olcDbDirectory')
+
+
+def __ut_get_slapd_backend_dir():
+ """
+ Unit test for get_slapd_backend_dir()
+ """
+ print get_slapd_backend_dir()
+
+
+def get_slapd_status():
+ """
+ Display status of local LDAP service on standard out
+
+ Returns a string
+ """
+ process = Popen(['systemctl', 'status', 'slapd.service'],
+ stdout=PIPE, stderr=PIPE, shell=False)
+ return process.communicate()[0]
+
+
+def __ut_get_slapd_status():
+ """
+ Unit test for get_slapd_status()
+ """
+ print get_slapd_status()
+
+
+def check_ldap_connectivity():
+ """
+ Predicate: Can a basic LDAP query be performed on the local LDAP server?
+
+ Returns True if yes, otherwise False is returned
+ """
+ ldap_server = ldap.initialize('ldap://' + socket.getfqdn())
+ try:
+ ldap_server.search_s('', ldap.SCOPE_BASE, '(objectClass=*)')
+ except ldap.CONFIDENTIALITY_REQUIRED:
+ log.debug('Local LDAP server requires TLS confidentiality')
+ return True
+ except ldap.SERVER_DOWN:
+ log.debug('Local LDAP server is unreachable')
+ return False
+ except ldap.NO_SUCH_OBJECT:
+ log.debug('Local LDAP server contains no rootDSE')
+ return False
+ return True
+
+
+def __ut_check_ldap_connectivity():
+ """
+ Unit test for check_ldap_connectivity()
+ """
+ if check_ldap_connectivity():
+ print 'Able to contact local LDAP server'
+ else:
+ print 'Not able to contact local LDAP server'
+
+
+def temporary_ldap_file():
+ """
+ Create a temporary file owned by the ldap user
+
+ Returns a file-like object or None
+ """
+ try:
+ result = tempfile.NamedTemporaryFile(mode='w',
+ dir='/tmp',
+ delete=True)
+ os.chown(result.name, LDAP_UID, LDAP_GID)
+ except OSError:
+ return None
+ return result
+
+
+def make_ldap_directory(pathname, mode=0755):
+ """
+ Create a directory owned by the ldap user
+
+ Returns a shell exit status value
+ """
+ if os.path.isdir(pathname):
+ log.info('Directory "%s" already exists', pathname)
+ return EXIT_SUCCESS
+
+ try:
+ os.mkdir(pathname)
+ os.chmod(pathname, mode)
+ os.chown(pathname, LDAP_UID, LDAP_GID)
+ except OSError:
+ log.error('Failed to create "%s"', pathname)
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+
+def wipe_slapd_d():
+ """
+ Clean out the slapd.d directory
+ slapd must be stopped for this procedure.
+
+ Returns a shell exit status value
+ """
+ log.debug('Cleaning out "/etc/openldap/"...')
+
+ ret = make_ldap_directory('/etc/openldap/slapd.d')
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ try:
+ if os.path.isfile(NSDB_CERTFILE):
+ os.remove(NSDB_CERTFILE)
+ if os.path.isfile(NSDB_KEYFILE):
+ os.remove(NSDB_KEYFILE)
+ except OSError:
+ log.error('Failed to remove old certificates')
+ return EXIT_FAILURE
+
+ return EXIT_SUCCESS
+
+
+def replace_slapd_database(pathname):
+ """
+ Replace the contents of the back-end database
+ slapd must be stopped for this procedure.
+
+ Returns a shell exit status value
+ """
+ log.debug('Replacing "%s"...', pathname)
+
+ ret = make_ldap_directory(pathname, 0700)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ try:
+ dbconfig = os.open(os.path.join(pathname, 'DB_CONFIG'),
+ os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0444)
+ except OSError:
+ log.error('Failed to create DB_CONFIG')
+ return EXIT_FAILURE
+
+ ret = EXIT_FAILURE
+ try:
+ os.fchown(dbconfig, LDAP_UID, LDAP_GID)
+ os.write(dbconfig, 'set_cachesize 0 268435456 1\n')
+ os.write(dbconfig, 'set_lg_regionmax 262144\n')
+ os.write(dbconfig, 'set_lg_bsize 2097152\n')
+ ret = EXIT_SUCCESS
+ except OSError:
+ log.error('Failed to write DB_CONFIG')
+
+ os.close(dbconfig)
+ return ret
+
+
+def generate_schema(config):
+ """
+ Generate set of include statements to construct server's schema
+ """
+ print >> config, 'include /etc/openldap/schema/corba.schema'
+ print >> config, 'include /etc/openldap/schema/core.schema'
+ print >> config, 'include /etc/openldap/schema/cosine.schema'
+ print >> config, 'include /etc/openldap/schema/duaconf.schema'
+ print >> config, 'include /etc/openldap/schema/dyngroup.schema'
+ print >> config, 'include /etc/openldap/schema/inetorgperson.schema'
+ print >> config, 'include /etc/openldap/schema/java.schema'
+ print >> config, 'include /etc/openldap/schema/misc.schema'
+ print >> config, 'include /etc/openldap/schema/nis.schema'
+ print >> config, 'include /etc/openldap/schema/openldap.schema'
+ print >> config, 'include /etc/openldap/schema/ppolicy.schema'
+ print >> config, 'include /etc/openldap/schema/collective.schema'
+ print >> config, 'include /etc/openldap/schema/fedfs.schema'
+ print >> config
+ print >> config, 'pidfile /var/run/openldap/slapd.pid'
+ print >> config, 'argsfile /var/run/openldap/slapd.args'
+ print >> config
+
+
+def generate_security_config(config, answers):
+ """
+ Generate server's security settings
+ """
+ if answers['security'] == 'tls':
+ print >> config, 'TLSCACertificateFile %s' % NSDB_CERTFILE
+ print >> config, 'TLSCertificateFile %s' % NSDB_CERTFILE
+ print >> config, 'TLSCertificateKeyFile %s' % NSDB_KEYFILE
+ print >> config, 'TLSVerifyClient never'
+ print >> config, 'security ssf=128 tls=1'
+ else:
+ print >> config, 'security tls=0'
+ print >> config
+
+
+def generate_config_database(config, answers):
+ """
+ Generate server's cn=config database
+ """
+ print >> config, 'database config'
+ print >> config, 'rootdn "%s"' % answers['ldap_admin']
+ print >> config, 'rootpw %s' % answers['ldap_password']
+ print >> config, 'access to *'
+ print >> config, '\tby dn.exact="gidNumber=0+uidNumber=0,' \
+ 'cn=peercred,cn=external,cn=auth" manage'
+ print >> config, '\tby * none'
+ print >> config
+
+
+def generate_monitor_database(config, answers):
+ """
+ Generate server's cn=monitor database
+ """
+ print >> config, 'database monitor'
+ print >> config, 'access to *'
+ print >> config, '\tby dn.exact="gidNumber=0+uidNumber=0,' \
+ 'cn=peercred,cn=external,cn=auth" read'
+ print >> config, '\tby dn.exact="%s" read' % answers['ldap_admin']
+ print >> config, '\tby * none'
+ print >> config
+
+
+def generate_dc_database(config, answers):
+ """
+ Generate database for domaincontroller root suffix
+ """
+ print >> config, 'database hdb'
+ print >> config, 'suffix "%s"' % answers['domaincontroller']
+ print >> config, 'checkpoint 1024 15'
+ print >> config, 'directory %s' % answers['dc_backend']
+ print >> config, 'rootdn "%s"' % answers['ldap_admin']
+ print >> config, 'access to filter=(objectClass=fedfsFsn)'
+ print >> config, '\tby dn="%s" manage' % answers['full_nsdb_admin']
+ print >> config, '\tby * read'
+ print >> config, 'access to filter=(objectClass=fedfsFsl)'
+ print >> config, '\tby dn="%s" manage' % answers['full_nsdb_admin']
+ print >> config, '\tby * read'
+ print >> config, 'access to filter=(objectClass=fedfsNsdbContainerEntry)'
+ print >> config, '\tby dn="%s" manage' % answers['full_nsdb_admin']
+ print >> config, '\tby * read'
+ print >> config, 'access to * by * read'
+ print >> config
+
+
+def generate_indices(config):
+ """
+ Generate index definitions for this server
+ """
+ print >> config, 'index objectClass eq,pres'
+ print >> config, 'index fedfsFsnUuid eq,pres'
+ print >> config, 'index fedFsFslUuid eq,pres'
+
+
+def generate_slapd_config(config, answers):
+ """
+ Build fresh old-style slapd configuration
+
+ Returns a shell exit status value
+ """
+ log.debug('Generating fresh slapd config in "%s"...', config.name)
+
+ ret = EXIT_FAILURE
+ try:
+ generate_schema(config)
+ generate_security_config(config, answers)
+ generate_config_database(config, answers)
+ generate_monitor_database(config, answers)
+ generate_dc_database(config, answers)
+ generate_indices(config)
+ config.flush()
+ ret = EXIT_SUCCESS
+ except OSError:
+ log.error('Failed to write new config file')
+
+ return ret
+
+
+def replace_slapd_config(answers):
+ """
+ Replace slapd configuration.
+ slapd must be stopped for this procedure.
+
+ Returns a shell exit status value
+ """
+ log.debug('Replacing slapd configuration...')
+
+ ret = wipe_slapd_d()
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ config = temporary_ldap_file()
+ if config is None:
+ log.error('Failed to create new config file')
+ return EXIT_FAILURE
+
+ ret = generate_slapd_config(config, answers)
+ if ret != EXIT_SUCCESS:
+ config.close()
+ return ret
+
+ ret = run_as_user(LDAP_USERNAME, ['slapadd', '-n2', '-l', '/dev/null',
+ '-f', config.name])
+ if ret != EXIT_SUCCESS:
+ config.close()
+ return ret
+
+ ret = run_as_user(LDAP_USERNAME, ['slaptest', '-f', config.name,
+ '-F', '/etc/openldap/slapd.d'])
+ config.close()
+ return ret
+
+
+def add_new_record(distinguished_name, new_entry):
+ """
+ Add a new entry to a slapd database
+
+ Returns a shell exit status value
+ """
+ tmp = temporary_ldap_file()
+ if tmp is None:
+ log.error('Failed to create temporary LDIF file')
+ return EXIT_FAILURE
+
+ writer = ldif.LDIFWriter(tmp)
+ writer.unparse(distinguished_name, new_entry)
+ tmp.flush()
+
+ ret = run_as_user(LDAP_USERNAME, ['slapadd', '-n2', '-l', tmp.name])
+
+ tmp.close()
+ return ret
+
+
+def add_domaincontroller(answers):
+ """
+ Add a domain controller root suffix.
+ slapd must be stopped for this procedure.
+
+ Returns a shell exit status value
+ """
+ log.debug('Adding root suffix for "%s"...', answers['domaincontroller'])
+
+ components = answers['domainname'].split('.')
+
+ entry = {}
+ entry['objectClass'] = ['top', 'organization', 'dcObject',
+ 'fedfsNsdbContainerInfo']
+ entry['dc'] = [components[0]]
+ entry['o'] = [answers['domainname']]
+ entry['fedfsNceDN'] = [answers['nce']]
+
+ return add_new_record(answers['domaincontroller'], entry)
+
+
+def add_nce(answers):
+ """
+ Add the NSDB Container Entry.
+ slapd must be stopped for this procedure.
+
+ Returns a shell exit status value
+ """
+ log.debug('Adding NSDB Container Entry "%s"...', answers['nce'])
+
+ entry = {}
+ entry['objectClass'] = ['top', 'organizationalUnit',
+ 'fedfsNsdbContainerEntry']
+ entry['ou'] = ['fedfs']
+
+ return add_new_record(answers['nce'], entry)
+
+
+def add_nsdb_manager(answers):
+ """
+ Add the NSDB Manager account
+
+ Returns a shell exit status value
+ """
+ log.debug('Adding NSDB Manager...')
+
+ ava = ldap.dn.str2dn(answers['nsdb_admin'])[0][0]
+
+ entry = {}
+ entry['objectClass'] = ['top', 'person']
+ entry['sn'] = ['Administrator']
+ entry['cn'] = [ava[1]]
+ entry['userPassword'] = [answers['nsdb_password']]
+
+ return add_new_record(answers['full_nsdb_admin'], entry)
+
+
+def slapd_config(answers):
+ """
+ Generate and customize slapd.conf
+ slapd must be stopped for this procedure.
+
+ Returns a shell exit status value
+ """
+ ret = replace_slapd_database(answers['dc_backend'])
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = replace_slapd_config(answers)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = add_domaincontroller(answers)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ ret = add_nce(answers)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return add_nsdb_manager(answers)
+
+
+def backup_slapd_backend(backup_dir, backup, nocompress):
+ """
+ Backup the slapd NSDB backend
+
+ Returns a shell exit status
+ """
+ ldif_file = os.path.join(backup_dir, backup + '.ldif')
+
+ ret = run_as_user(LDAP_USERNAME, ['slapcat', '-n2', '-l', ldif_file])
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ if not nocompress:
+ if run_as_user(LDAP_USERNAME, ['gzip', ldif_file]) != EXIT_SUCCESS:
+ log.warning('Failed to compress the backup')
+
+ log.info('Backup "%s" created successfully', backup)
+ return EXIT_SUCCESS
+
+
+def restore_from_ldif(backup_dir, backup):
+ """
+ Restore the slapd NSDB backend
+
+ Returns a shell exit status
+ """
+ log.info('Restoring from backup "%s"...', backup)
+
+ ldif_gzip = os.path.join(backup_dir, backup + '.ldif.gz')
+ if os.path.isfile(ldif_gzip):
+ ret = run_as_user(LDAP_USERNAME, ['gunzip', ldif_gzip])
+ if ret != EXIT_SUCCESS:
+ log.error('Failed to uncompress "%s"', ldif_gzip)
+ return ret
+
+ ldif_file = os.path.join(backup_dir, backup + '.ldif')
+ if not os.path.isfile(ldif_file):
+ log.error('Backup "%s" not found', backup)
+ return EXIT_FAILURE
+
+ return run_as_user(LDAP_USERNAME, ['slapadd', '-n2', '-l', ldif_file])
+
+
+def restore_slapd_backend(backend_dir, backup_dir, backup):
+ """
+ Restore the slapd NSDB backend, assumes slapd is stopped
+
+ Returns a shell exit status
+ """
+ ret = replace_slapd_database(backend_dir)
+ if ret != EXIT_SUCCESS:
+ return ret
+
+ return restore_from_ldif(backup_dir, backup)
+
+
+__all__ = ['LDAP_UID', 'LDAP_GID', 'NSDB_CERTFILE', 'NSDB_KEYFILE',
+ 'local_nce_found', 'local_tls_found', 'get_slapd_status',
+ 'check_ldap_connectivity', 'slapd_config',
+ 'restore_slapd_backend', 'backup_slapd_backend',
+ 'adjust_slapd_log', 'make_ldap_directory']
+
+if __name__ == '__main__':
+ __ut_local_nce_found()
+ __ut_local_tls_found()
+ __ut_get_slapd_backend_dir()
+ __ut_get_slapd_status()
+ __ut_check_ldap_connectivity()
new file mode 100644
@@ -0,0 +1,78 @@
+"""
+Set up a simple FedFS NSDB using OpenLDAP
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+
+try:
+ import sys
+ import os
+ import logging as log
+
+ from PyFedfs.jumpstart.slapd import get_slapd_status
+ from PyFedfs.jumpstart.slapd import check_ldap_connectivity
+ from PyFedfs.jumpstart.slapd import local_nce_found, local_tls_found
+
+ from PyFedfs.run import EXIT_SUCCESS
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+# pylint: disable-msg=W0613
+def subcmd_status(args):
+ """
+ Display the status of the local LDAP/NSDB service
+
+ Returns a shell exit status value
+ """
+ if not os.path.isdir('/etc/openldap'):
+ log.info('OpenLDAP is not installed on this system')
+ return EXIT_SUCCESS
+
+ log.info(get_slapd_status())
+
+ if check_ldap_connectivity():
+ log.info('Local LDAP service is reachable')
+ else:
+ log.info('Unable to contact local LDAP service')
+
+ if not os.path.isfile('/var/log/slapd'):
+ log.info('Slapd logging is not configured')
+
+ if not os.path.isfile('/etc/openldap/schema/fedfs.schema'):
+ log.info('The FedFS schema file is not installed')
+ return EXIT_SUCCESS
+
+ if local_nce_found():
+ log.info('Local server is an NSDB')
+ else:
+ log.info('Local server is not an NSDB')
+
+ if local_tls_found():
+ log.info('TLS is enabled')
+ else:
+ log.info('TLS is not enabled')
+
+ return EXIT_SUCCESS
+
+
+__all__ = ['subcmd_status']
new file mode 100644
@@ -0,0 +1,237 @@
+"""
+transaction - a simple way to commit or revert system changes
+
+Part of the PyFedfs module
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+import os
+import time
+import logging as log
+from shutil import rmtree, Error
+
+from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE
+
+
+class Transaction(object):
+ """
+ Represents a directory or file to be able to revert
+ """
+ def __init__(self):
+ self.items = []
+ self.unique = '%u' % int(time.time())
+ self.state = 'inited'
+
+ log.info('Created transaction %s', self.unique)
+
+ def __backup_name(self, pathname):
+ """
+ Generate name of backup object
+
+ Returns a string
+ """
+ return pathname + '.bak.' + self.unique
+
+ def __iterate(self, func):
+ """
+ Call a function on all items in the transaction list
+
+ Returns a shell exit status value
+ """
+ ret = EXIT_SUCCESS
+ for item in self.items:
+ if func(item) != EXIT_SUCCESS:
+ ret = EXIT_FAILURE
+ return ret
+
+ def __remove_item(self, pathname):
+ """
+ Remove a directory tree or file
+
+ Returns a shell exit status value
+ """
+ if not os.path.exists(pathname):
+ log.debug('No object "%s" to remove', pathname)
+ return EXIT_SUCCESS
+
+ if os.path.isfile(pathname):
+ try:
+ log.debug('Removing file "%s"...', pathname)
+ os.remove(pathname)
+ except OSError:
+ return EXIT_FAILURE
+ else:
+ try:
+ log.debug('Removing directory "%s"...', pathname)
+ rmtree(pathname)
+ except Error:
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+ def __checkpoint_item(self, pathname):
+ """
+ Move existing object out of the way
+
+ Returns a shell exit status value
+ """
+ if not os.path.exists(pathname):
+ log.warning('Checkpoint of non-existing object "%s"', pathname)
+ return EXIT_SUCCESS
+
+ if os.path.exists(self.__backup_name(pathname)):
+ log.error('Checkpoint of "%s" already exists', pathname)
+ return EXIT_FAILURE
+
+ log.debug('Checkpointing "%s"...', pathname)
+
+ try:
+ os.rename(pathname, self.__backup_name(pathname))
+ except OSError:
+ log.error('Failed to checkpoint "%s"', pathname)
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+ def __commit_item(self, pathname):
+ """
+ Remove an object's backup
+
+ Returns a shell exit status value
+ """
+ if not os.path.exists(self.__backup_name(pathname)):
+ log.warning('Backup of object "%s" is missing', pathname)
+ return EXIT_SUCCESS
+
+ log.debug('Committing "%s"...', pathname)
+
+ if self.__remove_item(self.__backup_name(pathname)) != EXIT_SUCCESS:
+ log.error('Failed to remove backup of "%s"', pathname)
+ return EXIT_FAILURE
+
+ return EXIT_SUCCESS
+
+ def __revert_item(self, pathname):
+ """
+ Restore an object from its backup
+
+ Returns a shell exit status value
+ """
+ if not os.path.exists(self.__backup_name(pathname)):
+ log.error('Backup of "%s" was not found', pathname)
+ return EXIT_FAILURE
+
+ if self.__remove_item(pathname) != EXIT_SUCCESS:
+ log.error('Failed to revert "%s"', pathname)
+ return EXIT_FAILURE
+
+ log.info('Reverting "%s"...', pathname)
+
+ try:
+ os.rename(self.__backup_name(pathname), pathname)
+ except OSError:
+ log.error('Failed to revert "%s"', pathname)
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+ def add(self, pathname):
+ """
+ Add filename of an object to be controlled by this transaction
+
+ Returns a shell exit status value
+ """
+ if self.state != 'inited':
+ log.error('"%s" not added to transaction %s: '
+ 'transaction already checkpointed',
+ pathname, self.unique)
+ return EXIT_FAILURE
+
+ if type(pathname) != str:
+ log.error('Object not added to transaction %s: '
+ 'not a string', self.unique)
+ return EXIT_FAILURE
+
+ if not os.path.exists(pathname):
+ log.debug('"%s" not added to transacion %s: '
+ 'object does not exist', pathname, self.unique)
+ return EXIT_SUCCESS
+
+ self.items.append(pathname)
+ return EXIT_SUCCESS
+
+ def checkpoint(self):
+ """
+ Checkpoint all items in this transaction
+
+ Returns a shell exit status value
+ """
+ if self.state != 'inited':
+ log.warning('Transaction %s has already been checkpointed',
+ self.unique)
+ return EXIT_SUCCESS
+
+ if len(self.items) == 0:
+ log.error('Transaction %s has no items to checkpoint',
+ self.unique)
+ return EXIT_FAILURE
+
+ log.info('Checkpointing transaction %s...', self.unique)
+ self.state = 'checkpointed'
+ return self.__iterate(self.__checkpoint_item)
+
+ def commit(self):
+ """
+ Commit all items in this transaction
+
+ Returns a shell exit status value
+ """
+ if self.state == 'inited':
+ log.error('Nothing to commit: transaction %s has '
+ 'not been checkpointed', self.unique)
+ return EXIT_FAILURE
+
+ if self.state != 'checkpointed':
+ log.warning('Transaction %s has already been committed',
+ self.unique)
+ return EXIT_SUCCESS
+
+ log.info('Committing transaction %s...', self.unique)
+ self.state = 'committed'
+ return self.__iterate(self.__commit_item)
+
+ def revert(self):
+ """
+ Revert all items in this transaction
+
+ Returns a shell exit status value
+ """
+ if self.state == 'inited':
+ log.error('Nothing to commit: transaction %s has '
+ 'not been checkpointed', self.unique)
+ return EXIT_FAILURE
+
+ if self.state != 'checkpointed':
+ log.warning('Transaction %s has already been committed',
+ self.unique)
+ return EXIT_SUCCESS
+
+ log.info('Reverting transaction %s...', self.unique)
+ self.state = 'reverted'
+ return self.__iterate(self.__revert_item)
+
+__all__ = ['Transaction']
@@ -33,14 +33,14 @@ import logging as log
from subprocess import Popen, PIPE
-def __run(command):
+def __run(command, shell=False):
"""
Run a command, ignore all command output, but return exit status
Returns a shell exit status value
"""
try:
- process = Popen(command, stdout=PIPE, stderr=PIPE, shell=False)
+ process = Popen(command, stdout=PIPE, stderr=PIPE, shell=shell)
except OSError:
log.error('"%s" command did not execute', ' '.join(command))
return None
@@ -85,6 +85,41 @@ def __ut_run_command():
print('run_command("ls -l"): %d' % result)
+def run_shell(line, force=False):
+ """
+ Run a shell command, ignore all command output, but return exit status
+
+ Returns a shell exit status value
+ """
+ log.debug('Running "%s"...', line)
+
+ process = __run(line, shell=True)
+ if process is None:
+ return EXIT_FAILURE
+
+ # pylint: disable-msg=E1101
+ process.wait()
+ # pylint: disable-msg=E1101
+ if process.returncode != 0:
+ if not force:
+ log.error('"%s" returned %d', line, process.returncode)
+ return EXIT_FAILURE
+ return EXIT_SUCCESS
+
+
+def __ut_run_shell():
+ """
+ Unit tests for run_shell
+ """
+ result = run_shell('ls -l')
+ if result == EXIT_SUCCESS:
+ print('run_shell("ls -l") succeeded')
+ elif result == EXIT_FAILURE:
+ print('run_shell("ls -l") failed')
+ else:
+ print('run_shell("ls -l"): %d' % result)
+
+
def demote(user_uid, user_gid):
"""
Returns a function that changes the UID and GID of a process
@@ -295,5 +330,6 @@ if __name__ == '__main__':
__ut_check_for_daemon()
__ut_run_command()
+ __ut_run_shell()
__ut_run_as_user()
__ut_systemctl()
new file mode 100644
@@ -0,0 +1,40 @@
+##
+## @file src/jumpstart/Makefile.am
+## @brief Process this file with automake to produce src/jumpstart/Makefile.in
+##
+
+##
+## Copyright 2013 Oracle. All rights reserved.
+##
+## This file is part of fedfs-utils.
+##
+## fedfs-utils is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License version 2.0 as
+## published by the Free Software Foundation.
+##
+## fedfs-utils 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 version 2.0 for more details.
+##
+## You should have received a copy of the GNU General Public License
+## version 2.0 along with fedfs-utils. If not, see:
+##
+## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+##
+
+
+bin_SCRIPTS = nsdb-jumpstart
+EXTRA_DIST = nsdb-jumpstart.in
+
+do_substitution = $(SED) -e 's,[@]pythondir[@],$(pythondir),g' \
+ -e 's,[@]PACKAGE[@],$(PACKAGE),g' \
+ -e 's,[@]VERSION[@],$(VERSION),g' \
+ -e 's,[@]STATEDIR[@],$(statedir),g'
+
+nsdb-jumpstart: nsdb-jumpstart.in Makefile
+ $(do_substitution) < $(srcdir)/nsdb-jumpstart.in > nsdb-jumpstart
+ chmod +x nsdb-jumpstart
+
+CLEANFILES = $(bin_SCRIPTS) cscope.in.out cscope.out cscope.po.out *~
+DISTCLEANFILES = Makefile.in
new file mode 100644
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+
+"""
+Run the NSDB jump-start administration tool
+"""
+
+__copyright__ = """
+Copyright 2013 Oracle. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2.0
+as published by the Free Software Foundation.
+
+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 version 2.0 for more details.
+
+A copy of the GNU General Public License version 2.0 is
+available here:
+
+ http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+"""
+
+import sys
+import os
+import argparse
+import logging as log
+
+sys.path.insert(1, '@pythondir@')
+
+try:
+ from PyFedfs.run import EXIT_FAILURE
+
+ from PyFedfs.jumpstart.install import subcmd_install
+ from PyFedfs.jumpstart.backup import subcmd_backup, subcmd_restore
+ from PyFedfs.jumpstart.status import subcmd_status
+except ImportError:
+ print >> sys.stderr, \
+ 'Could not import a required Python module:', sys.exc_value
+ sys.exit(1)
+
+
+def main():
+ """
+ Domainroot helper main program
+
+ Returns a shell exit status value
+ """
+ if os.getegid() != 0:
+ print >> sys.stderr, 'You must be root to run nsdb-jumpstart.'
+ return EXIT_FAILURE
+
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description='Jump-start a simple NSDB service',
+ epilog='''\
+Copyright 2013 Oracle. All rights reserved.
+
+License GPLv2: <http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt>
+This is free software. You are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.''')
+ parser.add_argument('--version',
+ help='Display the version of this command',
+ action='version',
+ version='@PACKAGE@ @VERSION@')
+ parser.add_argument('--statedir',
+ help='FedFS state dir (default @STATEDIR@)',
+ default='@STATEDIR@')
+ subparsers = parser.add_subparsers(title='Sub-commands')
+
+ install_parser = subparsers.add_parser('install',
+ help='Install an NSDB')
+ install_parser.add_argument('--security',
+ choices=['none', 'tls'],
+ help='security strength')
+ install_parser.set_defaults(func=subcmd_install)
+
+ backup_parser = subparsers.add_parser('backup',
+ help='Backup the local NSDB')
+ backup_parser.add_argument('--nocompress',
+ help='Do not compress the backup file',
+ action='store_true')
+ backup_parser.set_defaults(func=subcmd_backup)
+
+ restore_parser = subparsers.add_parser('restore',
+ help='Restore NSDB from backup')
+ restore_parser.add_argument('backup',
+ nargs='?', default='',
+ help='Which backup to restore')
+ restore_parser.set_defaults(func=subcmd_restore)
+
+ status_parser = subparsers.add_parser('status',
+ help='Display status of NSDB '
+ 'service')
+ status_parser.set_defaults(func=subcmd_status)
+
+ args = parser.parse_args()
+
+ log.basicConfig(level=log.DEBUG,
+ format='%(asctime)s %(name)-12s '
+ '%(levelname)-8s %(message)s',
+ datefmt='%m-%d %H:%M',
+ filename='/var/lib/fedfs/nsdb-jumpstart.log',
+ filemode='a')
+ console = log.StreamHandler()
+ console.setLevel(log.INFO)
+ formatter = log.Formatter('%(levelname)-8s: %(message)s')
+ console.setFormatter(formatter)
+ log.getLogger('').addHandler(console)
+
+ return args.func(args)
+
+
+try:
+ if __name__ == '__main__':
+ sys.exit(main())
+except (SystemExit, KeyboardInterrupt, RuntimeError):
+ sys.exit(1)
NSDB set-up has been made simpler over the past releases, but it remains onerously complex, even more so now that TLS is in the picture. Introduce a administration tool that can set up an NSDB from scratch using a fresh OpenLDAP installation. The tool depends on distribution packaging to pre-install an appropriate OpenLDAP server package and copy in the FedFS schema definition. Several subcommands are available: o install -- set up an NSDB o backup -- save a dated backup of the NSDB o restore -- restore a backup o status -- report status of NSDB service The install subcommand has a --security= option to specify whether to set up a server certificate and enable TLS. The tool maintains an activity log under /var/lib/fedfs. Signed-off-by: Chuck Lever <chuck.lever@oracle.com> --- .gitignore | 1 configure.ac | 2 doc/man/Makefile.am | 2 doc/man/nsdb-jumpstart.8 | 404 ++++++++++++++++++++ src/Makefile.am | 2 src/PyFedfs/Makefile.am | 2 src/PyFedfs/jumpstart/Makefile.am | 31 ++ src/PyFedfs/jumpstart/__init__.py | 23 + src/PyFedfs/jumpstart/backup.py | 186 +++++++++ src/PyFedfs/jumpstart/cert.py | 91 +++++ src/PyFedfs/jumpstart/firewall.py | 97 +++++ src/PyFedfs/jumpstart/install.py | 341 +++++++++++++++++ src/PyFedfs/jumpstart/slapd.py | 671 ++++++++++++++++++++++++++++++++++ src/PyFedfs/jumpstart/status.py | 78 ++++ src/PyFedfs/jumpstart/transaction.py | 237 ++++++++++++ src/PyFedfs/run.py | 40 ++ src/jumpstart/Makefile.am | 40 ++ src/jumpstart/nsdb-jumpstart.in | 119 ++++++ 18 files changed, 2362 insertions(+), 5 deletions(-) create mode 100644 doc/man/nsdb-jumpstart.8 create mode 100644 src/PyFedfs/jumpstart/Makefile.am create mode 100644 src/PyFedfs/jumpstart/__init__.py create mode 100644 src/PyFedfs/jumpstart/backup.py create mode 100644 src/PyFedfs/jumpstart/cert.py create mode 100644 src/PyFedfs/jumpstart/firewall.py create mode 100644 src/PyFedfs/jumpstart/install.py create mode 100644 src/PyFedfs/jumpstart/slapd.py create mode 100644 src/PyFedfs/jumpstart/status.py create mode 100644 src/PyFedfs/jumpstart/transaction.py create mode 100644 src/jumpstart/Makefile.am create mode 100644 src/jumpstart/nsdb-jumpstart.in