summaryrefslogtreecommitdiffstats
path: root/ipaserver
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2009-09-14 17:04:08 -0400
committerJason Gerard DeRose <jderose@redhat.com>2009-09-24 17:45:49 -0600
commitd0587cbdd5bc5e07a6e8519deb07adaace643740 (patch)
treeaa6b96e33337a809687ab025ec4d2a392ca757f0 /ipaserver
parent4f4d57cd30ac7169e18a8e2e22e62d8bdda083c4 (diff)
downloadfreeipa-d0587cbdd5bc5e07a6e8519deb07adaace643740.tar.gz
freeipa-d0587cbdd5bc5e07a6e8519deb07adaace643740.tar.xz
freeipa-d0587cbdd5bc5e07a6e8519deb07adaace643740.zip
Enrollment for a host in an IPA domain
This will create a host service principal and may create a host entry (for admins). A keytab will be generated, by default in /etc/krb5.keytab If no kerberos credentails are available then enrollment over LDAPS is used if a password is provided. This change requires that openldap be used as our C LDAP client. It is much easier to do SSL using openldap than mozldap (no certdb required). Otherwise we'd have to write a slew of extra code to create a temporary cert database, import the CA cert, ...
Diffstat (limited to 'ipaserver')
-rw-r--r--ipaserver/install/dsinstance.py4
-rw-r--r--ipaserver/plugins/join.py120
-rw-r--r--ipaserver/plugins/ldap2.py42
3 files changed, 165 insertions, 1 deletions
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index eb0356289..ea9f26da2 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -172,6 +172,7 @@ class DsInstance(service.Service):
self.step("enabling memberof plugin", self.__add_memberof_module)
self.step("enabling referential integrity plugin", self.__add_referint_module)
self.step("enabling winsync plugin", self.__add_winsync_module)
+ self.step("enabling IPA enrollment plugin", self.__add_enrollment_module)
self.step("enabling ldapi", self.__enable_ldapi)
self.step("configuring uniqueness plugin", self.__set_unique_attrs)
self.step("creating indices", self.__create_indices)
@@ -316,6 +317,9 @@ class DsInstance(service.Service):
def __add_winsync_module(self):
self._ldap_mod("ipa-winsync-conf.ldif")
+ def __add_enrollment_module(self):
+ self._ldap_mod("enrollment-conf.ldif", self.sub_dict)
+
def __enable_ssl(self):
dirname = config_dirname(self.serverid)
dsdb = certs.CertDB(dirname)
diff --git a/ipaserver/plugins/join.py b/ipaserver/plugins/join.py
new file mode 100644
index 000000000..b63000d89
--- /dev/null
+++ b/ipaserver/plugins/join.py
@@ -0,0 +1,120 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Joining an IPA domain
+"""
+
+from ipalib import api, util
+from ipalib import Command, Str, Int
+from ipalib import errors
+import krbV
+import os, subprocess
+from ipapython import ipautil
+import tempfile
+import sha
+import stat
+import shutil
+
+def get_realm():
+ krbctx = krbV.default_context()
+
+ return unicode(krbctx.default_realm)
+
+def validate_host(ugettext, cn):
+ """
+ Require at least one dot in the hostname (to support localhost.localdomain)
+ """
+ dots = len(cn.split('.'))
+ if dots < 2:
+ return 'Fully-qualified hostname required'
+ return None
+
+class join(Command):
+ """Join an IPA domain"""
+
+ requires_root = True
+
+ takes_args = (
+ Str('cn',
+ validate_host,
+ cli_name='hostname',
+ doc="The hostname to register as",
+ create_default=lambda **kw: unicode(util.get_fqdn()),
+ autofill=True,
+ #normalizer=lamda value: value.lower(),
+ ),
+ )
+ takes_options= (
+ Str('realm',
+ doc="The IPA realm",
+ create_default=lambda **kw: get_realm(),
+ autofill=True,
+ ),
+ Str('nshardwareplatform?',
+ cli_name='platform',
+ doc='Hardware platform of the host (e.g. Lenovo T61)',
+ ),
+ Str('nsosversion?',
+ cli_name='os',
+ doc='Operating System and version of the host (e.g. Fedora 9)',
+ ),
+ )
+
+ def execute(self, hostname, **kw):
+ """
+ Execute the machine join operation.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param hostname: The name of the host joined
+ :param kw: Keyword arguments for the other attributes.
+ """
+ assert 'cn' not in kw
+ ldap = self.api.Backend.ldap2
+
+ host = None
+ try:
+ # First see if the host exists
+ kw = {'fqdn': hostname, 'all': True}
+ (dn, attrs_list) = api.Command['host_show'](**kw)
+
+ # If no principal name is set yet we need to try to add
+ # one.
+ if 'krbprincipalname' not in attrs_list:
+ service = "host/%s@%s" % (hostname, api.env.realm)
+ (d, a) = api.Command['host_mod'](hostname, krbprincipalname=service)
+
+ # It exists, can we write the password attributes?
+ allowed = ldap.can_write(dn, 'krblastpwdchange')
+ if not allowed:
+ raise errors.ACIError(info="Insufficient 'write' privilege to the 'krbLastPwdChange' attribute of entry '%s'." % dn)
+
+ kw = {'fqdn': hostname, 'all': True}
+ (dn, attrs_list) = api.Command['host_show'](**kw)
+ except errors.NotFound:
+ (dn, attrs_list) = api.Command['host_add'](hostname)
+
+ return (dn, attrs_list)
+
+ def output_for_cli(self, textui, result, args, **options):
+ textui.print_plain("Welcome to the %s realm" % options['realm'])
+ textui.print_plain("Your keytab is in %s" % result.get('keytab'))
+
+api.register(join)
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index c854dac28..0deded937 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -31,15 +31,18 @@ import os
import socket
import string
+import krbV
import ldap as _ldap
import ldap.filter as _ldap_filter
import ldap.sasl as _ldap_sasl
+from ldap.controls import LDAPControl
# for backward compatibility
from ldap.functions import explode_dn
from ipalib import api, errors
from ipalib.crud import CrudBackend
from ipalib.encoder import Encoder, encode_args, decode_retval
+from ipalib.request import context
# attribute syntax to python type mapping, 'SYNTAX OID': type
# everything not in this dict is considered human readable unicode
@@ -140,7 +143,11 @@ _schema = _load_schema(api.env.ldap_uri)
def _get_syntax(attr, value):
schema = api.Backend.ldap2._schema
- return schema.get_obj(_ldap.schema.AttributeType, attr).syntax
+ obj = schema.get_obj(_ldap.schema.AttributeType, attr)
+ if obj is not None:
+ return obj.syntax
+ else:
+ return None
# ldap backend class
@@ -215,6 +222,9 @@ class ldap2(CrudBackend, Encoder):
if ccache is not None:
os.environ['KRB5CCNAME'] = ccache
conn.sasl_interactive_bind_s('', _sasl_auth)
+ principal = krbV.CCache(name=ccache,
+ context=krbV.default_context()).principal().name
+ setattr(context, "principal", principal)
else:
# no kerberos ccache, use simple bind
conn.simple_bind_s(bind_dn, bind_pw)
@@ -486,6 +496,36 @@ class ldap2(CrudBackend, Encoder):
return copy.deepcopy(self._schema)
@encode_args(1, 2)
+ def get_effective_rights(self, dn, entry_attrs):
+ """Returns the rights the currently bound user has for the given DN.
+
+ Returns 2 attributes, the attributeLevelRights for the given list of
+ attributes and the entryLevelRights for the entry itself.
+ """
+ principal = getattr(context, 'principal')
+ (binddn, attrs) = self.find_entry_by_attr("krbprincipalname", principal, "posixAccount")
+ sctrl = [LDAPControl("1.3.6.1.4.1.42.2.27.9.5.2", True, "dn: " + binddn.encode('UTF-8'))]
+ self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl)
+ (dn, attrs) = self.get_entry(dn, entry_attrs)
+ # remove the control so subsequent operations don't include GER
+ self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, [])
+ return (dn, attrs)
+
+ @encode_args(1, 2)
+ def can_write(self, dn, attr):
+ """Returns True/False if the currently bound user has write permissions
+ on the attribute. This only operates on a single attribute at a time.
+ """
+ (dn, attrs) = self.get_effective_rights(dn, [attr])
+ if 'attributelevelrights' in attrs:
+ attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8')
+ (attr, rights) = attr_rights.split(':')
+ if 'w' in rights:
+ return True
+
+ return False
+
+ @encode_args(1, 2)
def update_entry_rdn(self, dn, new_rdn, del_old=True):
"""
Update entry's relative distinguished name.