From patchwork Tue Oct 29 19:42:35 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chuck Lever X-Patchwork-Id: 287044 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from userp1040.oracle.com (userp1040.oracle.com [156.151.31.81]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "userp1040.oracle.com", Issuer "VeriSign Class 3 International Server CA - G3" (not verified)) by ozlabs.org (Postfix) with ESMTPS id 0DE992C0369 for ; Wed, 30 Oct 2013 06:42:53 +1100 (EST) Received: from acsinet21.oracle.com (acsinet21.oracle.com [141.146.126.237]) by userp1040.oracle.com (Sentrion-MTA-4.3.1/Sentrion-MTA-4.3.1) with ESMTP id r9TJgoYa023365 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Tue, 29 Oct 2013 19:42:51 GMT Received: from oss.oracle.com (oss-external.oracle.com [137.254.96.51]) by acsinet21.oracle.com (8.14.4+Sun/8.14.4) with ESMTP id r9TJgnUT022887 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Tue, 29 Oct 2013 19:42:50 GMT Received: from localhost ([127.0.0.1] helo=oss.oracle.com) by oss.oracle.com with esmtp (Exim 4.63) (envelope-from ) id 1VbFBh-0005RQ-OX; Tue, 29 Oct 2013 12:42:49 -0700 Received: from ucsinet21.oracle.com ([156.151.31.93]) by oss.oracle.com with esmtp (Exim 4.63) (envelope-from ) id 1VbFBY-0005RF-Tt for fedfs-utils-devel@oss.oracle.com; Tue, 29 Oct 2013 12:42:41 -0700 Received: from aserp1030.oracle.com (aserp1030.oracle.com [141.146.126.68]) by ucsinet21.oracle.com (8.14.4+Sun/8.14.4) with ESMTP id r9TJgdFU015364 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Tue, 29 Oct 2013 19:42:40 GMT Received: from mail-ob0-f178.google.com (mail-ob0-f178.google.com [209.85.214.178]) by aserp1030.oracle.com (Sentrion-MTA-4.3.1/Sentrion-MTA-4.3.1) with ESMTP id r9TJgckN011204 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=OK) for ; Tue, 29 Oct 2013 19:42:39 GMT Received: by mail-ob0-f178.google.com with SMTP id wm4so421879obc.9 for ; Tue, 29 Oct 2013 12:42:38 -0700 (PDT) X-Received: by 10.60.133.233 with SMTP id pf9mr1010134oeb.46.1383075758266; Tue, 29 Oct 2013 12:42:38 -0700 (PDT) Received: from seurat.1015granger.net (c-68-32-80-121.hsd1.mi.comcast.net. [68.32.80.121]) by mx.google.com with ESMTPSA id z5sm32228304obg.13.2013.10.29.12.42.37 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 29 Oct 2013 12:42:37 -0700 (PDT) To: fedfs-utils-devel@oss.oracle.com From: Chuck Lever Date: Tue, 29 Oct 2013 15:42:35 -0400 Message-ID: <20131029194235.19294.29650.stgit@seurat.1015granger.net> In-Reply-To: <20131029192303.19294.65282.stgit@seurat.1015granger.net> References: <20131029192303.19294.65282.stgit@seurat.1015granger.net> User-Agent: StGit/0.16 MIME-Version: 1.0 X-Flow-Control-Info: class=Pass-to-MM reputation=ipRisk-All ip=209.85.214.178 ct-class=R6 ct-vol1=0 ct-vol2=0 ct-vol3=0 ct-risk=68 ct-spam1=0 ct-spam2=0 ct-bulk=0 rcpts=1 size=80865 X-SPF-Info: PASS::mail-ob0-f178.google.com X-Sendmail-CM-Score: 0.00% X-Sendmail-CM-Analysis: v=2.1 cv=LqSrlBtc c=1 sm=1 tr=0 a=UGZ7QfJHoL6Lh6oHu0VgvA==:117 a=lHl1VeN+WkZht9Pe5WE6vg==:17 a=dzsqy3y4QnMA:10 a=OvR9cMkO9i4A:10 a=dPGociXpb70A:10 a=IkcTkHD0fZMA:10 a=yPCof4ZbAAAA:8 a=Lb1rMZzfAAAA:8 a=1XWaLZrsAAAA:8 a=C_IRinGWAAAA:8 a=rs gT92uGfecA:10 a=mDV3o1hIAAAA:8 a=OK-8mIdLAAAA:8 a=P6JkxrBpAAAA:8 a=WP5zsaevAAAA:8 a=jbOT9JJ1we512eLJ1ngA:9 a=cEfP8urELWsA-1tV:21 a=QEXdDO2ut3YA:10 a=u37mErDvIGIA:10 a=nvz6EU7xhngA:10 a=WcdFFF8jZaIA:10 a=7DSvI1NPTFQA:10 a=N9mWz7sHuv4A:10 X-Sendmail-CT-Classification: not spam X-Sendmail-CT-RefID: str=0001.0A090207.52700FAF.00DC, ss=1, re=-6.400, recu=0.000, reip=0.000, cl=1, cld=1, fgs=0 Subject: [fedfs-utils] [PATCH 5/5] Add a tool to administer a simple OpenLDAP-based NSDB X-BeenThere: fedfs-utils-devel@oss.oracle.com X-Mailman-Version: 2.1.9 Precedence: list Reply-To: fedfs-utils Developers List-Id: fedfs-utils Developers List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: fedfs-utils-devel-bounces@oss.oracle.com Errors-To: fedfs-utils-devel-bounces@oss.oracle.com X-Source-IP: acsinet21.oracle.com [141.146.126.237] 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 --- .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 diff --git a/.gitignore b/.gitignore index 407e013..00bafc1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/configure.ac b/configure.ac index 7b83a2c..f92f731 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 65e9e9c..1123dfc 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -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 *~ diff --git a/doc/man/nsdb-jumpstart.8 b/doc/man/nsdb-jumpstart.8 new file mode 100644 index 0000000..c6e443f --- /dev/null +++ b/doc/man/nsdb-jumpstart.8 @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 9f84ebc..51bc9d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/PyFedfs/Makefile.am b/src/PyFedfs/Makefile.am index bef9d11..fae2d1a 100644 --- a/src/PyFedfs/Makefile.am +++ b/src/PyFedfs/Makefile.am @@ -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 diff --git a/src/PyFedfs/jumpstart/Makefile.am b/src/PyFedfs/jumpstart/Makefile.am new file mode 100644 index 0000000..13db46a --- /dev/null +++ b/src/PyFedfs/jumpstart/Makefile.am @@ -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 diff --git a/src/PyFedfs/jumpstart/__init__.py b/src/PyFedfs/jumpstart/__init__.py new file mode 100644 index 0000000..4879aef --- /dev/null +++ b/src/PyFedfs/jumpstart/__init__.py @@ -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 +""" diff --git a/src/PyFedfs/jumpstart/backup.py b/src/PyFedfs/jumpstart/backup.py new file mode 100644 index 0000000..d2f1744 --- /dev/null +++ b/src/PyFedfs/jumpstart/backup.py @@ -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'] diff --git a/src/PyFedfs/jumpstart/cert.py b/src/PyFedfs/jumpstart/cert.py new file mode 100644 index 0000000..8105a1b --- /dev/null +++ b/src/PyFedfs/jumpstart/cert.py @@ -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() diff --git a/src/PyFedfs/jumpstart/firewall.py b/src/PyFedfs/jumpstart/firewall.py new file mode 100644 index 0000000..b1c6e52 --- /dev/null +++ b/src/PyFedfs/jumpstart/firewall.py @@ -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, '\n') + os.write(service, '\n') + os.write(service, '\n') + os.write(service, ' LDAP\n') + os.write(service, ' 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).\n') + os.write(service, ' \n') + os.write(service, ' \n') + os.write(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'] diff --git a/src/PyFedfs/jumpstart/install.py b/src/PyFedfs/jumpstart/install.py new file mode 100644 index 0000000..5dbf1ee --- /dev/null +++ b/src/PyFedfs/jumpstart/install.py @@ -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'] diff --git a/src/PyFedfs/jumpstart/slapd.py b/src/PyFedfs/jumpstart/slapd.py new file mode 100644 index 0000000..58ea670 --- /dev/null +++ b/src/PyFedfs/jumpstart/slapd.py @@ -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() diff --git a/src/PyFedfs/jumpstart/status.py b/src/PyFedfs/jumpstart/status.py new file mode 100644 index 0000000..31c17cc --- /dev/null +++ b/src/PyFedfs/jumpstart/status.py @@ -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'] diff --git a/src/PyFedfs/jumpstart/transaction.py b/src/PyFedfs/jumpstart/transaction.py new file mode 100644 index 0000000..22c76d7 --- /dev/null +++ b/src/PyFedfs/jumpstart/transaction.py @@ -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'] diff --git a/src/PyFedfs/run.py b/src/PyFedfs/run.py index dc04b92..2ee3dc3 100644 --- a/src/PyFedfs/run.py +++ b/src/PyFedfs/run.py @@ -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() diff --git a/src/jumpstart/Makefile.am b/src/jumpstart/Makefile.am new file mode 100644 index 0000000..0c5723b --- /dev/null +++ b/src/jumpstart/Makefile.am @@ -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 diff --git a/src/jumpstart/nsdb-jumpstart.in b/src/jumpstart/nsdb-jumpstart.in new file mode 100644 index 0000000..fb5b058 --- /dev/null +++ b/src/jumpstart/nsdb-jumpstart.in @@ -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: +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)