summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--install/share/default-aci.ldif4
-rw-r--r--ipa-client/ipa-getkeytab.c36
-rw-r--r--ipalib/plugins/service.py70
-rw-r--r--tests/test_xmlrpc/test_host_plugin.py3
-rw-r--r--tests/test_xmlrpc/test_service_plugin.py376
5 files changed, 364 insertions, 125 deletions
diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif
index 3f7469027..829bc3485 100644
--- a/install/share/default-aci.ldif
+++ b/install/share/default-aci.ldif
@@ -38,10 +38,12 @@ add: aci
aci: (targetattr="krbPrincipalName || krbCanonicalName || krbUPEnabled || krbPrincipalKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData")(version 3.0; acl "KDC System Account"; allow (read, search, compare, write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
# Define which hosts can edit services
+# The managedby attribute stores the DN of hosts that are allowed to manage
+# a service. Use service-add-host to add hosts to a service.
dn: cn=services,cn=accounts,$SUFFIX
changetype: modify
add: aci
-aci: (targetattr=userCertificate)(version 3.0; aci "Hosts can modify service userCertificate"; allow(write) userattr = "parent[0,1].managedby#USERDN";)
+aci: (targetattr="userCertificate || krbPrincipalKey")(version 3.0; aci "Hosts can manage service Certificates and kerberos keys"; allow(write) userattr = "parent[0,1].managedby#USERDN";)
# Allow hosts to update their own certificate in host/
dn: cn=computers,cn=accounts,$SUFFIX
diff --git a/ipa-client/ipa-getkeytab.c b/ipa-client/ipa-getkeytab.c
index d55355aab..4bbbf1c7c 100644
--- a/ipa-client/ipa-getkeytab.c
+++ b/ipa-client/ipa-getkeytab.c
@@ -69,14 +69,19 @@ static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *s
sasl_interact_t *in = NULL;
int ret = LDAP_OTHER;
krb5_principal princ = (krb5_principal)priv_data;
+ krb5_context krbctx;
+ char *outname = NULL;
if (!ld) return LDAP_PARAM_ERROR;
+ krb5_init_context(&krbctx);
+
for (in = sit; in && in->id != SASL_CB_LIST_END; in++) {
switch(in->id) {
case SASL_CB_USER:
- in->result = princ->data[0].data;
- in->len = princ->data[0].length;
+ krb5_unparse_name(krbctx, princ, &outname);
+ in->result = outname;
+ in->len = strlen(outname);
ret = LDAP_SUCCESS;
break;
case SASL_CB_GETREALM:
@@ -90,7 +95,8 @@ static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *s
ret = LDAP_OTHER;
}
}
- return ret;
+ krb5_free_context(krbctx);
+ return ret;
}
static void free_keys_contents(krb5_context krbctx, struct keys_container *keys)
@@ -809,19 +815,19 @@ int main(int argc, char *argv[])
}
if (NULL == bindpw) {
- krberr = krb5_cc_default(krbctx, &ccache);
- if (krberr) {
- fprintf(stderr, "Kerberos Credential Cache not found\n"
- "Do you have a Kerberos Ticket?\n");
- exit(5);
- }
+ krberr = krb5_cc_default(krbctx, &ccache);
+ if (krberr) {
+ fprintf(stderr, "Kerberos Credential Cache not found\n"
+ "Do you have a Kerberos Ticket?\n");
+ exit(5);
+ }
- krberr = krb5_cc_get_principal(krbctx, ccache, &uprinc);
- if (krberr) {
- fprintf(stderr, "Kerberos User Principal not found\n"
- "Do you have a valid Credential Cache?\n");
- exit(6);
- }
+ krberr = krb5_cc_get_principal(krbctx, ccache, &uprinc);
+ if (krberr) {
+ fprintf(stderr, "Kerberos User Principal not found\n"
+ "Do you have a valid Credential Cache?\n");
+ exit(6);
+ }
}
krberr = krb5_kt_resolve(krbctx, ktname, &kt);
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
index ac949b788..c9ae0b88e 100644
--- a/ipalib/plugins/service.py
+++ b/ipalib/plugins/service.py
@@ -65,8 +65,18 @@ from ipalib import Str, Flag, Bytes
from ipalib.plugins.baseldap import *
from ipalib import x509
from ipalib import _, ngettext
+from nss.error import NSPRError
+output_params = (
+ Flag('has_keytab',
+ label=_('Keytab'),
+ ),
+ Str('managedby_host',
+ label='Managed by',
+ ),
+)
+
def split_principal(principal):
service = hostname = realm = None
@@ -155,6 +165,7 @@ class service_add(LDAPCreate):
"""
msg_summary = _('Added service "%(value)s"')
member_attributes = ['managedby']
+ has_output_params = LDAPCreate.has_output_params + output_params
takes_options = (
Flag('force',
doc=_('force principal name even if not in DNS'),
@@ -171,7 +182,7 @@ class service_add(LDAPCreate):
raise errors.HostService()
try:
- api.Command['host_show'](hostname)
+ hostresult = api.Command['host_show'](hostname)['result']
except errors.NotFound:
raise errors.NotFound(reason="The host '%s' does not exist to add a service to." % hostname)
@@ -188,6 +199,8 @@ class service_add(LDAPCreate):
# really want to discourage creating services for hosts that
# don't exist in DNS.
util.validate_host_dns(self.log, hostname)
+ if not 'managedby' in entry_attrs:
+ entry_attrs['managedby'] = hostresult['dn']
return dn
@@ -206,18 +219,26 @@ class service_del(LDAPDelete):
cert = entry_attrs.get('usercertificate')
if cert:
cert = cert[0]
- serial = unicode(x509.get_serial_number(cert, x509.DER))
try:
- result = api.Command['cert_show'](unicode(serial))['result']
- if 'revocation_reason' not in result:
- try:
- api.Command['cert_revoke'](unicode(serial), revocation_reason=4)
- except errors.NotImplementedError:
- # some CA's might not implement revoke
- pass
- except errors.NotImplementedError:
- # some CA's might not implement revoke
- pass
+ serial = unicode(x509.get_serial_number(cert, x509.DER))
+ try:
+ result = api.Command['cert_show'](unicode(serial))['result']
+ if 'revocation_reason' not in result:
+ try:
+ api.Command['cert_revoke'](unicode(serial), revocation_reason=4)
+ except errors.NotImplementedError:
+ # some CA's might not implement revoke
+ pass
+ except errors.NotImplementedError:
+ # some CA's might not implement revoke
+ pass
+ except NSPRError, nsprerr:
+ if nsprerr.errno == -8183:
+ # If we can't decode the cert them proceed with
+ # removing the service.
+ self.log.info("Problem decoding certificate %s" % nsprerr.args[1])
+ else:
+ raise nsprerr
return dn
api.register(service_del)
@@ -227,6 +248,7 @@ class service_mod(LDAPUpdate):
"""
Modify service.
"""
+ msg_summary = _('Modified service "%(value)s"')
takes_options = LDAPUpdate.takes_options + (
Bytes('usercertificate?', validate_certificate,
cli_name='certificate',
@@ -234,6 +256,7 @@ class service_mod(LDAPUpdate):
doc=_('Base-64 encoded server certificate'),
),
)
+ has_output_params = LDAPUpdate.has_output_params + output_params
member_attributes = ['managedby']
@@ -261,6 +284,9 @@ class service_find(LDAPSearch):
"""
Search for services.
"""
+ msg_summary = ngettext(
+ '%(count)d service matched', '%(count)d services matched'
+ )
member_attributes = ['managedby']
takes_options = LDAPSearch.takes_options + (
Bytes('usercertificate?', validate_certificate,
@@ -269,6 +295,7 @@ class service_find(LDAPSearch):
doc=_('Base-64 encoded server certificate'),
),
)
+ has_output_params = LDAPSearch.has_output_params + output_params
def pre_callback(self, ldap, filter, attrs_list, base_dn, *args, **options):
# lisp style!
custom_filter = '(&(objectclass=ipaService)' \
@@ -282,6 +309,16 @@ class service_find(LDAPSearch):
(custom_filter, filter), rules=ldap.MATCH_ALL
)
+ def post_callback(self, ldap, entries, truncated, *args, **options):
+ for entry in entries:
+ entry_attrs = entry[1]
+ if 'krblastpwdchange' in entry_attrs:
+ entry_attrs['has_keytab'] = True
+ if not options.get('all', False):
+ del entry_attrs['krblastpwdchange']
+ else:
+ entry_attrs['has_keytab'] = False
+
api.register(service_find)
@@ -297,11 +334,7 @@ class service_show(LDAPRetrieve):
doc=_('Base-64 encoded server certificate'),
),
)
- has_output_params = (
- Flag('has_keytab',
- label=_('Keytab'),
- )
- )
+ has_output_params = LDAPRetrieve.has_output_params + output_params
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
if 'krblastpwdchange' in entry_attrs:
@@ -320,6 +353,7 @@ class service_add_host(LDAPAddMember):
Add hosts that can manage this service.
"""
member_attributes = ['managedby']
+ has_output_params = LDAPAddMember.has_output_params + output_params
api.register(service_add_host)
@@ -329,6 +363,7 @@ class service_remove_host(LDAPRemoveMember):
Remove hosts that can manage this service.
"""
member_attributes = ['managedby']
+ has_output_params = LDAPRemoveMember.has_output_params + output_params
api.register(service_remove_host)
@@ -339,6 +374,7 @@ class service_disable(LDAPQuery):
"""
has_output = output.standard_value
msg_summary = _('Removed kerberos key from "%(value)s"')
+ has_output_params = LDAPQuery.has_output_params + output_params
def execute(self, *keys, **options):
ldap = self.obj.backend
diff --git a/tests/test_xmlrpc/test_host_plugin.py b/tests/test_xmlrpc/test_host_plugin.py
index 8c2224b58..6a07763c6 100644
--- a/tests/test_xmlrpc/test_host_plugin.py
+++ b/tests/test_xmlrpc/test_host_plugin.py
@@ -300,6 +300,7 @@ class test_host(Declarative):
dn=service1dn,
krbprincipalname=[service1],
objectclass=objectclasses.service,
+ managedby_host=[fqdn1],
ipauniqueid=[fuzzy_uuid],
),
),
@@ -321,7 +322,7 @@ class test_host(Declarative):
expected=dict(
count=0,
truncated=False,
- summary=None,
+ summary=u'0 services matched',
result=[
],
),
diff --git a/tests/test_xmlrpc/test_service_plugin.py b/tests/test_xmlrpc/test_service_plugin.py
index 96827e4c5..18eecd7ff 100644
--- a/tests/test_xmlrpc/test_service_plugin.py
+++ b/tests/test_xmlrpc/test_service_plugin.py
@@ -21,94 +21,288 @@
Test the `ipalib/plugins/service.py` module.
"""
-import sys
-from xmlrpc_test import XMLRPC_test, assert_attr_equal
-from ipalib import api
-from ipalib import errors
-
-
-class test_service(XMLRPC_test):
- """
- Test the `service` plugin.
- """
- host = u'ipatest.%s' % api.env.domain
- principal = u'HTTP/ipatest.%s@%s' % (api.env.domain, api.env.realm)
- hostprincipal = u'host/ipatest.%s@%s' % (api.env.domain, api.env.realm)
- kw = {'krbprincipalname': principal}
-
- def test_1_service_add(self):
- """
- Test adding a HTTP principal using the `xmlrpc.service_add` method.
- """
- self.failsafe_add(api.Object.host, self.host, force=True)
- entry = self.failsafe_add(api.Object.service, self.principal, force=True)['result']
- assert_attr_equal(entry, 'krbprincipalname', self.principal)
- assert_attr_equal(entry, 'objectclass', 'ipaobject')
-
- def test_2_service_add(self):
- """
- Test adding a host principal using `xmlrpc.service_add`. Host
- services are not allowed.
- """
- kw = {'krbprincipalname': self.hostprincipal}
- try:
- api.Command['service_add'](**kw)
- except errors.HostService:
- pass
- else:
- assert False
-
- def test_3_service_add(self):
- """
- Test adding a malformed principal ('foo').
- """
- kw = {'krbprincipalname': u'foo', 'force': True}
- try:
- api.Command['service_add'](**kw)
- except errors.MalformedServicePrincipal:
- pass
- else:
- assert False
-
- def test_4_service_add(self):
- """
- Test adding a malformed principal ('HTTP/foo@FOO.NET').
- """
- kw = {'krbprincipalname': u'HTTP/foo@FOO.NET', 'force': True}
- try:
- api.Command['service_add'](**kw)
- except errors.RealmMismatch:
- pass
- else:
- assert False
-
- def test_5_service_show(self):
- """
- Test the `xmlrpc.service_show` method.
- """
- entry = api.Command['service_show'](self.principal)['result']
- assert_attr_equal(entry, 'krbprincipalname', self.principal)
- assert(entry['has_keytab'] == False)
-
- def test_6_service_find(self):
- """
- Test the `xmlrpc.service_find` method.
- """
- entries = api.Command['service_find'](self.principal)['result']
- assert_attr_equal(entries[0], 'krbprincipalname', self.principal)
-
- def test_7_service_del(self):
- """
- Test the `xmlrpc.service_del` method.
- """
- assert api.Command['service_del'](self.principal)['result'] is True
-
- # Verify that it is gone
- try:
- api.Command['service_show'](self.principal)
- except errors.NotFound:
- pass
- else:
- assert False
-
- api.Command['host_del'](self.host)
+from ipalib import api, errors
+from tests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid
+from tests.test_xmlrpc import objectclasses
+
+
+fqdn1 = u'testhost1.%s' % api.env.domain
+fqdn2 = u'testhost2.%s' % api.env.domain
+service1 = u'HTTP/%s@%s' % (fqdn1, api.env.realm)
+hostprincipal1 = u'host/%s@%s' % (fqdn1, api.env.realm)
+service1dn = u'krbprincipalname=%s,cn=services,cn=accounts,%s' % (service1.lower(), api.env.basedn)
+host1dn = u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn1, api.env.basedn)
+host2dn = u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn2, api.env.basedn)
+
+
+class test_host(Declarative):
+
+ cleanup_commands = [
+ ('host_del', [fqdn1], {}),
+ ('host_del', [fqdn2], {}),
+ ('service_del', [service1], {}),
+ ]
+
+ tests = [
+ dict(
+ desc='Try to retrieve non-existent %r' % service1,
+ command=('service_show', [service1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Try to update non-existent %r' % service1,
+ command=('service_mod', [service1], dict(usercertificate='Nope')),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Try to delete non-existent %r' % service1,
+ command=('service_del', [service1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Create %r' % fqdn1,
+ command=('host_add', [fqdn1],
+ dict(
+ description=u'Test host 1',
+ l=u'Undisclosed location 1',
+ force=True,
+ ),
+ ),
+ expected=dict(
+ value=fqdn1,
+ summary=u'Added host "%s"' % fqdn1,
+ result=dict(
+ dn=host1dn,
+ fqdn=[fqdn1],
+ description=[u'Test host 1'],
+ l=[u'Undisclosed location 1'],
+ krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)],
+ objectclass=objectclasses.host,
+ ipauniqueid=[fuzzy_uuid],
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Create %r' % fqdn2,
+ command=('host_add', [fqdn2],
+ dict(
+ description=u'Test host 2',
+ l=u'Undisclosed location 2',
+ force=True,
+ ),
+ ),
+ expected=dict(
+ value=fqdn2,
+ summary=u'Added host "%s"' % fqdn2,
+ result=dict(
+ dn=host2dn,
+ fqdn=[fqdn2],
+ description=[u'Test host 2'],
+ l=[u'Undisclosed location 2'],
+ krbprincipalname=[u'host/%s@%s' % (fqdn2, api.env.realm)],
+ objectclass=objectclasses.host,
+ ipauniqueid=[fuzzy_uuid],
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Create %r' % service1,
+ command=('service_add', [service1],
+ dict(
+ force=True,
+ ),
+ ),
+ expected=dict(
+ value=service1,
+ summary=u'Added service "%s"' % service1,
+ result=dict(
+ dn=service1dn,
+ krbprincipalname=[service1],
+ objectclass=objectclasses.service,
+ ipauniqueid=[fuzzy_uuid],
+ managedby_host=[fqdn1],
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Try to create duplicate %r' % service1,
+ command=('service_add', [service1],
+ dict(
+ force=True,
+ ),
+ ),
+ expected=errors.DuplicateEntry(),
+ ),
+
+
+ dict(
+ desc='Retrieve %r' % service1,
+ command=('service_show', [service1], {}),
+ expected=dict(
+ value=service1,
+ summary=None,
+ result=dict(
+ dn=service1dn,
+ krbprincipalname=[service1],
+ has_keytab=False,
+ managedby_host=[fqdn1],
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Retrieve %r with all=True' % service1,
+ command=('service_show', [service1], dict(all=True)),
+ expected=dict(
+ value=service1,
+ summary=None,
+ result=dict(
+ dn=service1dn,
+ krbprincipalname=[service1],
+ objectclass=objectclasses.service,
+ ipauniqueid=[fuzzy_uuid],
+ managedby_host=[fqdn1],
+ has_keytab=False
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Search for %r' % service1,
+ command=('service_find', [service1], {}),
+ expected=dict(
+ count=1,
+ truncated=False,
+ summary=u'1 service matched',
+ result=[
+ dict(
+ dn=service1dn,
+ krbprincipalname=[service1],
+ managedby_host=[fqdn1],
+ has_keytab=False,
+ ),
+ ],
+ ),
+ ),
+
+
+ dict(
+ desc='Search for %r with all=True' % service1,
+ command=('service_find', [service1], dict(all=True)),
+ expected=dict(
+ count=1,
+ truncated=False,
+ summary=u'1 service matched',
+ result=[
+ dict(
+ dn=service1dn,
+ krbprincipalname=[service1],
+ objectclass=objectclasses.service,
+ ipauniqueid=[fuzzy_uuid],
+ has_keytab=False,
+ managedby_host=[fqdn1],
+ ),
+ ],
+ ),
+ ),
+
+
+ dict(
+ desc='Update %r' % service1,
+ command=('service_mod', [service1], dict(usercertificate='aGVsbG8=')),
+ expected=dict(
+ value=service1,
+ summary=u'Modified service "%s"' % service1,
+ result=dict(
+ usercertificate=['hello'],
+ krbprincipalname=[service1],
+ managedby_host=[fqdn1],
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Retrieve %r to verify update' % service1,
+ command=('service_show', [service1], {}),
+ expected=dict(
+ value=service1,
+ summary=None,
+ result=dict(
+ dn=service1dn,
+ usercertificate=['hello'],
+ krbprincipalname=[service1],
+ has_keytab=False,
+ managedby_host=[fqdn1],
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Delete %r' % service1,
+ command=('service_del', [service1], {}),
+ expected=dict(
+ value=service1,
+ summary=u'Deleted service "%s"' % service1,
+ result=True,
+ ),
+ ),
+
+
+ dict(
+ desc='Try to retrieve non-existent %r' % service1,
+ command=('service_show', [service1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Try to update non-existent %r' % service1,
+ command=('service_mod', [service1], dict(usercertificate='Nope')),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Try to delete non-existent %r' % service1,
+ command=('service_del', [service1], {}),
+ expected=errors.NotFound(reason='no such entry'),
+ ),
+
+
+ dict(
+ desc='Create service with malformed principal "foo"',
+ command=('service_add', [u'foo'], {}),
+ expected=errors.MalformedServicePrincipal(reason='missing service')
+ ),
+
+
+ dict(
+ desc='Create service with bad realm "HTTP/foo@FOO.NET"',
+ command=('service_add', [u'HTTP/foo@FOO.NET'], {}),
+ expected=errors.RealmMismatch(),
+ ),
+
+
+ dict(
+ desc='Create a host service %r' % hostprincipal1,
+ command=('service_add', [hostprincipal1], {}),
+ expected=errors.HostService()
+ ),
+
+ ]