Nik, Updated.
Dan On Tue, Mar 7, 2017 at 4:11 AM, Nikolai Kondrashov < nikolai.kondras...@redhat.com> wrote: > On 03/06/2017 09:34 PM, Dan Lavu wrote: > >> Since we last spoken, here is an update patch for review. Adding an >> optional >> ssl parameter to the DS class. When enabled, right now the tests fail >> because SSL is not configured properly on the client, but I'll get to that >> in my next patch? >> > > Glad to see your progress! > > Before I proceed with everything else, why don't you just let the DS > accept a > CA parameter as well as an SSL port? > > Like this: > > def __init__(self, dir, port, base_dn, admin_rdn, admin_pw, > ssl_port=None, ca=None): > # ... > self.ssl_port = ssl_port > self.ca = ca > # ... > > And then perhaps make it set up the SSL socket if the port is passed, in > *addition* > to the regular socket. > > I.e. have this in the constructor: > > self.ldap_url = "ldap://localhost:" + str(self.port) > if self.ssl_port: > self.ldaps_url = "ldaps://localhost:" + str(self.ssl_port) > > Nick > _______________________________________________ > sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org > To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org >
diff --git src/tests/intg/ca.py src/tests/intg/ca.py new file mode 100644 index 0000000..691c030 --- /dev/null +++ src/tests/intg/ca.py @@ -0,0 +1,40 @@ +# +# Abstract directory server instance class +# +# Copyright (c) 2015 Red Hat, Inc. +# Author: Nikolai Kondrashov <nikolai.kondras...@redhat.com> +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + + +class CA: + """Abstract CA server instance""" + + def __init__(self, config_dir, subject, country, state, city, organization, unit): + """Initialize the instance""" + self.config_dir = config_dir + self.subject = subject + self.country = country + self.state = state + self.city = city + self.organization = organization + self.unit = unit + + def setup(self): + """Setup the instance""" + raise NotImplementedError() + + def teardown(self): + """Teardown the instance""" + raise NotImplementedError() diff --git src/tests/intg/ca_openssl.py src/tests/intg/ca_openssl.py new file mode 100644 index 0000000..b5c51c3 --- /dev/null +++ src/tests/intg/ca_openssl.py @@ -0,0 +1,180 @@ +# +# SSSD LOCAL domain tests +# +# Copyright (c) 2016 Red Hat, Inc. +# Author: Dan Lavu <d...@redhat.com> +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import socket +import os.path + +from os.path import exists +from OpenSSL import crypto +from ca import CA + + +class CAOpenSSL(CA): + """OpenSSL CA instance""" + + def __init__(self, config_dir, subject, country, state, city, organization, unit): + """Initialize the instance""" + CA.__init__(self, config_dir, subject, country, state, city, organization, unit) + self.config_dir = config_dir + self.subject = subject + self.country = country + self.state = state + self.city = city + self.organization = organization + self.unit = unit + self.hostname = socket.gethostname() + + self.ca_cert_file = self.hostname + '-ca.crt' + self.ca_cert_key_file = self.hostname + '-ca.key' + + self.csr_dir = self.config_dir + '/newcerts' + self.key_dir = self.config_dir + '/private' + self.cert_dir = self.config_dir + '/certs' + + self.index = int(1000) + + def setup(self): + """Setup OpenSSL CA""" + if not exists(os.path.join(self.cert_dir, self.ca_cert_file)) or not \ + exists(os.path.join(self.key_dir, self.ca_cert_key_file)): + ca_key = crypto.PKey() + ca_key.generate_key(crypto.TYPE_RSA, 2048) + + ca_cert = crypto.X509() + ca_cert.get_subject().C = self.country + ca_cert.get_subject().ST = self.state + ca_cert.get_subject().L = self.city + ca_cert.get_subject().O = self.organization + ca_cert.get_subject().OU = self.unit + ca_cert.get_subject().CN = self.subject + ca_cert.set_serial_number(self.index) + ca_cert.gmtime_adj_notBefore(0) + ca_cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) + ca_cert.set_issuer(ca_cert.get_subject()) + ca_cert.set_pubkey(ca_key) + ca_cert.sign(ca_key, 'sha1') + + if not os.path.exists(self.csr_dir): + os.makedirs(self.csr_dir) + if not os.path.exists(self.key_dir): + os.makedirs(self.key_dir) + if not os.path.exists(self.cert_dir): + os.makedirs(self.cert_dir) + + open(os.path.join(self.cert_dir, self.ca_cert_file), 'wt').\ + write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert)) + open(os.path.join(self.key_dir, self.ca_cert_key_file), 'wt').\ + write(crypto.dump_privatekey(crypto.FILETYPE_PEM, ca_key)) + + def teardown(self): + """Teardown""" + + def get_ca_cert(self): + """Retrieves CA certificate""" + ca_cert_file = os.path.join(self.cert_dir, self.ca_cert_file) + ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, + open(ca_cert_file, 'rt').read()) + return ca_cert + + def get_cacert_x509(self): + """Retrieves CA certificate in x509""" + ca_cert_file = os.path.join(self.cert_dir, self.ca_cert_file) + ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, + open(ca_cert_file, 'rt').read()) + ca_cert_x509 = crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert) + return ca_cert_x509 + + def get_ca_cert_path(self): + """Retrieves CA certificate file path""" + ca_cert_file = os.path.join(self.cert_dir, self.ca_cert_file) + + return ca_cert_file + + def get_ca_key(self): + """Retrieves CA certificate private key""" + ca_key_file = os.path.join(self.key_dir, self.ca_cert_key_file) + ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, + open(ca_key_file, 'rt').read()) + return ca_key + + def gen_cert(self, csr, hostname): + """Generates certificate from csr""" + ca_cert_file = os.path.join(self.cert_dir, self.ca_cert_file) + ca_key_file = os.path.join(self.key_dir, self.ca_cert_key_file) + + ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, + open(ca_cert_file, 'rt').read()) + ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, + open(ca_key_file, 'rt').read()) + + cert = crypto.X509() + cert.set_subject(csr.get_subject()) + cert.set_serial_number(self.index + 1) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) + cert.set_issuer(ca_cert.get_subject()) + cert.set_pubkey(csr.get_pubkey()) + cert.sign(ca_key, 'sha1') + + open(os.path.join(self.cert_dir, hostname + '.crt'), 'wt').\ + write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + return cert + + def get_cert(self, hostname): + """Retrieves server certificate in PEM""" + cert_file = os.path.join(self.cert_dir, hostname + '.crt') + cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file, 'rt').read()) + return cert + + def get_cert_x509(self, hostname): + """Retrieves server certificate in x509""" + cert_file = os.path.join(self.cert_dir, hostname + '.crt') + cert_x509 = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file, 'rt').read()) + return cert_x509 + + def get_cert_path(self, hostname): + """Retrieves server certificate file path""" + cert_file = os.path.join(self.cert_dir, hostname + '.crt') + return cert_file + + def get_cert_key(self, hostname): + """Retrieves server certificate private key""" + key_file = os.path.join(self.key_dir, hostname + '.key') + key = crypto.load_privatekey(crypto.FILETYPE_PEM, open(key_file, 'rt').read()) + return key + + def get_cert_key_path(self, hostname): + """Retrieves server certificate private key file path""" + key_file = os.path.join(self.key_dir, hostname + '.key') + return key_file + + def gen_csr(self, hostname): + """Generates certificate signing request""" + key = crypto.PKey() + key.generate_key(crypto.TYPE_RSA, 2048) + csr = crypto.X509Req() + csr.get_subject().CN = hostname + csr.set_pubkey(key) + csr.sign(key, 'sha1') + + open(os.path.join(self.key_dir, hostname + '.key'), 'a').\ + write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) + open(os.path.join(self.csr_dir, hostname + '.csr'), 'a').\ + write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)) + return csr diff --git src/tests/intg/ds.py src/tests/intg/ds.py index 66cb887..13c0778 100644 --- src/tests/intg/ds.py +++ src/tests/intg/ds.py @@ -23,7 +23,7 @@ import ldap class DS: """Abstract directory server instance.""" - def __init__(self, dir, port, base_dn, admin_rdn, admin_pw): + def __init__(self, dir, port, base_dn, admin_rdn, admin_pw, ssl_port=None, ca=None): """ Initialize the instance. @@ -37,11 +37,17 @@ class DS: """ self.dir = dir self.port = port - self.ldap_url = "ldap://localhost:" + str(self.port) self.base_dn = base_dn self.admin_rdn = admin_rdn self.admin_dn = admin_rdn + "," + base_dn self.admin_pw = admin_pw + self.ssl_port = ssl_port + self.ca = ca + + if self.ssl_port and self.ca: + self.ldap_url = "ldaps://localhost:" + str(self.ssl_port) + else: + self.ldap_url = "ldap://localhost:" + str(self.port) def setup(self): """Setup the instance""" diff --git src/tests/intg/ds_openldap.py src/tests/intg/ds_openldap.py index 6a074c9..a5b2152 100644 --- src/tests/intg/ds_openldap.py +++ src/tests/intg/ds_openldap.py @@ -25,7 +25,10 @@ import os import errno import signal import shutil +import socket +import urllib import sys +import ca_openssl from util import * from ds import DS @@ -47,7 +50,7 @@ def hash_password(password): class DSOpenLDAP(DS): """OpenLDAP directory server instance.""" - def __init__(self, dir, port, base_dn, admin_rdn, admin_pw): + def __init__(self, dir, port, base_dn, admin_rdn, admin_pw, ssl_port=None, ca=False): """ Initialize the instance. @@ -59,12 +62,14 @@ class DSOpenLDAP(DS): admin_rdn Administrator DN, relative to BASE_DN. admin_pw Administrator password. """ - DS.__init__(self, dir, port, base_dn, admin_rdn, admin_pw) + DS.__init__(self, dir, port, base_dn, admin_rdn, admin_pw, ca) self.run_dir = self.dir + "/var/run/ldap" self.pid_path = self.run_dir + "/slapd.pid" self.conf_dir = self.dir + "/etc/ldap" self.conf_slapd_d_dir = self.conf_dir + "/slapd.d" self.data_dir = self.dir + "/var/lib/ldap" + self.ssl_port = ssl_port + self.ca = ca def _setup_config(self): """Setup the instance initial configuration.""" @@ -78,9 +83,6 @@ class DSOpenLDAP(DS): uid = os.geteuid() gid = os.getegid() - # - # Add configuration - # config = unindent(""" dn: cn=config objectClass: olcGlobal @@ -223,11 +225,23 @@ class DSOpenLDAP(DS): break except ldap.SERVER_DOWN: pass - attempt = attempt + 1 + attempt += 1 if attempt > 30: raise Exception("Failed to start slapd") time.sleep(1) + if self.ca: + self.ca_hostname = socket.gethostname() + self.ca_inst = ca_openssl.CAOpenSSL(config.PREFIX, self.ca_hostname, + "US", + "NC", + "Raleigh", + "Red Hat Inc.", + "SSSD") + self.ca_inst.setup() + self.ca_inst.gen_cert(self.ca_inst.gen_csr(self.ca_hostname), self.ca_hostname) + self.enable_ssl(self.ca_inst.ca_cert_file, self.ca_inst.ca_cert_file, self.ca_inst.ca_cert_key_file) + # # Relax requirement of member attribute presence in groupOfNames # @@ -277,7 +291,7 @@ class DSOpenLDAP(DS): pid_file.close() attempt = 0 while os.path.isfile(self.pid_path): - attempt = attempt + 1 + attempt += 1 if attempt > 30: raise Exception("Failed to stop slapd") time.sleep(1) @@ -287,3 +301,29 @@ class DSOpenLDAP(DS): for path in (self.conf_slapd_d_dir, self.run_dir, self.data_dir): shutil.rmtree(path, True) + + if self.ca: + self.ca_inst.teardown() + + def enable_ssl(self, ca_cert, cert, key): + """Enable SSL in OpenLDAP""" + + ldapi_socket = self.run_dir + "/ldapi" + ldapi_url = "ldapi://" + url_quote(ldapi_socket, "") + url_list = ldapi_url + " " + self.ldap_url + + modlist = [ + (ldap.MOD_ADD, "olcTLSCertificateKeyFile", [key]), + (ldap.MOD_ADD, "olcTLSCertificateFile", [cert]), + (ldap.MOD_ADD, "olcTLSCACertificatePath", [ca_cert]) + ] + ldap_conn = ldap.initialize(ldapi_url) + ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config", self.admin_pw) + ldap_conn.modify_s("cn=config", modlist) + ldap_conn.unbind_s() + + if subprocess.call(['pkill', 'slapd']) != 0: + raise execfile("Failed to stop slapd") + + if subprocess.call(["slapd", "-F", self.conf_slapd_d_dir, "-h", url_list]) != 0: + raise Exception("Failed to start slapd") diff --git src/tests/intg/test_ldap.py src/tests/intg/test_ldap.py index 848cb41..ea2e05f 100644 --- src/tests/intg/test_ldap.py +++ src/tests/intg/test_ldap.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # -import os +import os.path import stat import pwd import grp diff --git src/tests/intg/test_sssctl.py src/tests/intg/test_sssctl.py index 0df5d0b..3c2ba02 100644 --- src/tests/intg/test_sssctl.py +++ src/tests/intg/test_sssctl.py @@ -28,6 +28,7 @@ import signal import ds_openldap import ldap_ent import config +import socket from util import unindent import sssd_netgroup @@ -38,14 +39,14 @@ LDAP_BASE_DN = "dc=example,dc=com" def ds_inst(request): """LDAP server instance fixture""" ds_inst = ds_openldap.DSOpenLDAP( - config.PREFIX, 10389, LDAP_BASE_DN, - "cn=admin", "Secret123") + config.PREFIX, 10389, LDAP_BASE_DN, "cn=admin", "Secret123") try: ds_inst.setup() except: ds_inst.teardown() raise request.addfinalizer(lambda: ds_inst.teardown()) + return ds_inst
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org