diff options
author | Karl MacMillan <kmacmill@redhat.com> | 2007-11-21 23:28:25 -0500 |
---|---|---|
committer | Karl MacMillan <kmacmill@redhat.com> | 2007-11-21 23:28:25 -0500 |
commit | edc7af1446af451ea5ed44420cceb05059a7b973 (patch) | |
tree | c8ef012239d7ed5f9cce0190d7f071b871e3d070 | |
parent | 9038bf71dd76d845746e0ea3e94bca9f52f60c03 (diff) | |
download | freeipa-edc7af1446af451ea5ed44420cceb05059a7b973.tar.gz freeipa-edc7af1446af451ea5ed44420cceb05059a7b973.tar.xz freeipa-edc7af1446af451ea5ed44420cceb05059a7b973.zip |
Add xml-rpc interface for getting keytabs.
Warning: this lacks any sort of authorization.
-rw-r--r-- | ipa-admintools/Makefile | 1 | ||||
-rw-r--r-- | ipa-admintools/ipa-getkeytab | 83 | ||||
-rw-r--r-- | ipa-python/ipaclient.py | 7 | ||||
-rw-r--r-- | ipa-python/rpcclient.py | 24 | ||||
-rw-r--r-- | ipa-server/Makefile.am | 1 | ||||
-rw-r--r-- | ipa-server/configure.ac | 1 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/bootstrap-template.ldif | 9 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/default-aci.ldif | 5 | ||||
-rw-r--r-- | ipa-server/ipa-keytab-util/Makefile.am | 22 | ||||
-rw-r--r-- | ipa-server/ipa-keytab-util/ipa-keytab-util.c | 304 | ||||
-rw-r--r-- | ipa-server/ipaserver/krbinstance.py | 2 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 67 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 2 |
13 files changed, 524 insertions, 4 deletions
diff --git a/ipa-admintools/Makefile b/ipa-admintools/Makefile index 9d63db082..f4ee40a63 100644 --- a/ipa-admintools/Makefile +++ b/ipa-admintools/Makefile @@ -21,6 +21,7 @@ install: install -m 755 ipa-deldelegation $(SBINDIR) install -m 755 ipa-listdelegation $(SBINDIR) install -m 755 ipa-moddelegation $(SBINDIR) + install -m 755 ipa-getkeytab $(SBINDIR) @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ diff --git a/ipa-admintools/ipa-getkeytab b/ipa-admintools/ipa-getkeytab new file mode 100644 index 000000000..5ecb7e4a6 --- /dev/null +++ b/ipa-admintools/ipa-getkeytab @@ -0,0 +1,83 @@ +#! /usr/bin/python -E +# Authors: Karl MacMillan <kmacmill@redhat.com> +# +# Copyright (C) 2007 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 +# + +import sys +from optparse import OptionParser +import ipa +import ipa.user +import ipa.ipaclient as ipaclient +import ipa.ipavalidate as ipavalidate +import ipa.config + +import base64 + +import xmlrpclib +import kerberos +import krbV +import ldap +import getpass +import errno + +def usage(): + print "ipa-getkeytab [-a] principal filename" + sys.exit(1) + +def parse_options(): + parser = OptionParser() + parser.add_option("-a", "--add", dest="add_princ", action="store_true", + help="add the principal") + + args = ipa.config.init_config(sys.argv) + options, args = parser.parse_args(args) + + return options, args + +def main(): + # The following fields are required + princ_name = "" + + options, args = parse_options() + + if len(args) != 3: + usage() + princ_name = args[1] + file_name = args[2] + + client = ipaclient.IPAClient() + + try: + if options.add_princ: + client.add_service_principal(princ_name) + + princs = client.get_keytab(princ_name) + + if princs is None: + print "could not generate keytab" + sys.exit(1) + + fd = open(file_name, "w") + fd.write(princs) + + except Exception, e: + print str(e) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index cda8ceb93..c551f0435 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -381,3 +381,10 @@ class IPAClient: """ result = self.transport.update_password_policy(policy.origDataDict(), policy.toDict()) return result + + def add_service_principal(self, princ_name): + return self.transport.add_service_principal(princ_name) + + def get_keytab(self, princ_name): + return self.transport.get_keytab(princ_name) + diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index d4c3dcc8e..d7ff97405 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -690,3 +690,27 @@ class RPCClient: raise xmlrpclib.Fault(value, msg) return ipautil.unwrap_binary_data(result) + + def add_service_principal(self, princ_name): + server = self.setup_server() + + try: + result = server.add_service_principal(princ_name) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + + def get_keytab(self, princ_name): + server = self.setup_server() + + try: + result = server.get_keytab(princ_name) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) diff --git a/ipa-server/Makefile.am b/ipa-server/Makefile.am index b5da3f566..9638cdab6 100644 --- a/ipa-server/Makefile.am +++ b/ipa-server/Makefile.am @@ -11,6 +11,7 @@ SUBDIRS = \ ipaserver \ ipa-slapi-plugins \ xmlrpc-server \ + ipa-keytab-util \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-server/configure.ac b/ipa-server/configure.ac index 3cd44fef2..1e62a2f82 100644 --- a/ipa-server/configure.ac +++ b/ipa-server/configure.ac @@ -229,6 +229,7 @@ AC_CONFIG_FILES([ ipa-slapi-plugins/ipa-pwd-extop/Makefile xmlrpc-server/Makefile xmlrpc-server/test/Makefile + ipa-keytab-util/Makefile ]) AC_OUTPUT diff --git a/ipa-server/ipa-install/share/bootstrap-template.ldif b/ipa-server/ipa-install/share/bootstrap-template.ldif index dc403b637..5efec3c44 100644 --- a/ipa-server/ipa-install/share/bootstrap-template.ldif +++ b/ipa-server/ipa-install/share/bootstrap-template.ldif @@ -28,10 +28,11 @@ objectClass: top objectClass: nsContainer cn: groups -#dn: cn=computers,cn=accounts,$SUFFIX -#objectClass: top -#objectClass: nsContainer -#cn: computers +dn: cn=services,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +cn: services dn: cn=etc,$SUFFIX changetype: add diff --git a/ipa-server/ipa-install/share/default-aci.ldif b/ipa-server/ipa-install/share/default-aci.ldif index 4a5befbec..f6f165629 100644 --- a/ipa-server/ipa-install/share/default-aci.ldif +++ b/ipa-server/ipa-install/share/default-aci.ldif @@ -19,3 +19,8 @@ dn: cn=accounts,$SUFFIX changetype: modify add: aci aci: (targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) + +dn: cn=services,cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr="krbPrincipalName || krbUPEnabled || krbPrincipalKey || krbMKey || 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";) diff --git a/ipa-server/ipa-keytab-util/Makefile.am b/ipa-server/ipa-keytab-util/Makefile.am new file mode 100644 index 000000000..f0680e598 --- /dev/null +++ b/ipa-server/ipa-keytab-util/Makefile.am @@ -0,0 +1,22 @@ +NULL = + +sbin_PROGRAMS = \ + ipa-keytab-util \ + $(NULL) + +ipa_keytab_util_SOURCES = \ + ipa-keytab-util.c \ + $(NULL) + +ipa_keytab_util_LDADD = \ + -lcap \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in + +install-exec-hook: + -chown root:apache $(DESTDIR)$(sbindir)/ipa-keytab-util + -chmod o-rwxs $(DESTDIR)$(sbindir)/ipa-keytab-util + -chmod ug+s $(DESTDIR)$(sbindir)/ipa-keytab-util diff --git a/ipa-server/ipa-keytab-util/ipa-keytab-util.c b/ipa-server/ipa-keytab-util/ipa-keytab-util.c new file mode 100644 index 000000000..d080d0cd5 --- /dev/null +++ b/ipa-server/ipa-keytab-util/ipa-keytab-util.c @@ -0,0 +1,304 @@ +/* + * Authors: + * Karl MacMillan <kmacmill@redhat.com> + * + * Copyright (C) 2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define _GNU_SOURCE /* for asprintf */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/capability.h> +#include <sys/prctl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#define KADMIN_PATH "/usr/kerberos/sbin/kadmin.local" + +struct options +{ + char *princ_name; + char *realm; + int kstdin, kstdout, kstderr; +}; + +void *xmalloc(size_t size) +{ + void *foo = malloc(size); + if (!foo) { + fprintf(stderr, "malloc error of size %jd\n", size); + exit(1); + } + memset(foo, 0, size); + + return foo; +} + +void usage(void) +{ + printf("ipa-keytab-util princ-name realm-name\n"); +} + +struct options *process_args(int argc, char **argv) +{ + struct options* opts; + + opts = xmalloc(sizeof(struct options)); + + if (argc != 3) { + usage(); + exit(1); + } + + opts->princ_name = argv[1]; + opts->realm = argv[2]; + + return opts; +} + +void drop_caps(void) +{ + cap_t caps; + int ret; + + if (geteuid() != 0) + return; + if (getuid() != 0) + return; + + caps = cap_init(); + if (!caps) { + perror("error initializing caps"); + exit(1); + } + ret = cap_clear(caps); + if (ret != 0) { + perror("could not clear capps"); + exit(1); + } + + ret = cap_set_proc(caps); + if (ret != 0) { + perror("could not drop caps"); + exit(1); + } + + cap_free(caps); +} + +pid_t exec_kadmin_local(struct options *opts) +{ + int stdin_pipes[2]; + int stdout_pipes[2]; + int stderr_pipes[2]; + int ret; + pid_t chpid; + char *princ; + + /* create a pair of pipes for stdin / stdout + of the child process. + */ + + if (pipe(stdin_pipes) == -1) { + perror("creating stdin"); + exit(1); + } + + if (pipe(stdout_pipes) == -1) { + perror("creating stdin"); + exit(1); + } + + if (pipe(stderr_pipes) == -1) { + perror("creating stdin"); + exit(1); + } + + chpid = fork(); + if (chpid == -1) { + perror("fork"); + exit(1); + } + + /* CHILD */ + if (chpid == 0) { + /* stdin */ + close(stdin_pipes[1]); + dup2(stdin_pipes[0], 0); + + /* stdout */ + close(stdout_pipes[0]); + dup2(stdout_pipes[1], 1); + + /* stderr */ + close(stderr_pipes[0]); + dup2(stdout_pipes[1], 2); + + /* now exec kadmin.local */ + + ret = asprintf(&princ, "admin@%s", opts->realm); + if (!princ) { + perror("creating bind princ"); + exit(1); + } + ret = execl(KADMIN_PATH, "kadmin.local", "-p", princ, NULL); + free(princ); + if (ret == -1) { + perror("exec"); + exit(1); + } + } else { + close(stdin_pipes[0]); + close(stdout_pipes[1]); + close(stderr_pipes[1]); + + opts->kstdin = stdin_pipes[1]; + opts->kstdout = stdout_pipes[0]; + opts->kstderr = stdout_pipes[0]; + } + + return chpid; +} + +void write_to_kadmin(struct options *opts, char *buf, int len) +{ + int ret; + + ret = write(opts->kstdin, buf, len); + if (ret != len) { + perror("write"); + fprintf(stderr, "write is short %d:%d\n", len, ret); + exit(1); + } + fsync(opts->kstdin); +} + +char *get_temp_filename(void) +{ + char *fname; + /* ok - we have to use mktemp here even w/ the race + * because kadmin.local barfs if the file exists. The + * risk is pretty low and we will try to protect the files + * with selinux. + * + * TODO: generate these files in a safer place than /tmp + */ + fname = strdup("/tmp/ipa-keytab-util-XXXXXX"); + if (!fname) { + fprintf(stderr, "could not allocate temporary file name"); + exit(1); + } + fname = mktemp(fname); + + return fname; +} + +char *create_keytab(struct options *opts) +{ + char *buf, *fname; + int ret; + + fname = get_temp_filename(); + + ret = asprintf(&buf, "ktadd -k %s %s\n", fname, opts->princ_name); + if (ret == -1) { + perror("asprintf"); + exit(1); + } + + write_to_kadmin(opts, buf, ret); + + free(buf); + + write_to_kadmin(opts, "quit\n", sizeof("quit\n")); + + return fname; +} + +void read_keytab(char *fname) +{ + FILE *fd; + char *data; + long flen, ret; + + fd = fopen(fname, "r"); + if (!fd) { + fprintf(stderr, "could not open file %s: ", fname); + perror(NULL); + exit(1); + } + + fseek(fd, 0, SEEK_END); + flen = ftell(fd); + rewind(fd); + + data = xmalloc(flen); + + /* TODO: handle short reads */ + ret = fread(data, 1, flen, fd); + if (ret != flen) { + fprintf(stderr, "short read"); + exit(1); + } + + fclose(fd); + + /* write to stdout */ + ret = fwrite(data, 1, flen, stdout); + if (ret != flen) { + fprintf(stderr, "short write"); + exit(1); + } +} + +void remove_keytab(char *filename) +{ + unlink(filename); +} + +/* TODO: add significantly better authorization */ +int main(int argc, char **argv) +{ + struct options *opts; + pid_t chpid; + int status, ret; + char *fname; + + opts = process_args(argc, argv); + + /* must really be root */ + setuid(0); + + drop_caps(); + + + chpid = exec_kadmin_local(opts); + fname = create_keytab(opts); + + ret = waitpid(-1, &status, 0); + if (WEXITSTATUS(status)) { + fprintf(stderr, "error creating keytab\n"); + exit(1); + } + + read_keytab(fname); + remove_keytab(fname); + + return 0; +} diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py index 84d677162..c83002f73 100644 --- a/ipa-server/ipaserver/krbinstance.py +++ b/ipa-server/ipaserver/krbinstance.py @@ -165,6 +165,7 @@ class KrbInstance(service.Service): def __copy_ldap_passwd(self, filename): shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd") + os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) def __configure_kdc_account_password(self): @@ -175,6 +176,7 @@ class KrbInstance(service.Service): pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w") pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n") pwd_fd.close() + os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) def __setup_sub_dict(self): self.sub_dict = dict(FQDN=self.fqdn, diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 17555afb7..ef196a0a5 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -37,6 +37,7 @@ from types import * import os import re import logging +import subprocess try: from threading import Lock @@ -49,6 +50,7 @@ _LDAPPool = None ACIContainer = "cn=accounts" DefaultUserContainer = "cn=users,cn=accounts" DefaultGroupContainer = "cn=groups,cn=accounts" +DefaultServiceContainer = "cn=services,cn=accounts" # FIXME: need to check the ipadebug option in ipa.conf #logging.basicConfig(level=logging.DEBUG, @@ -1287,6 +1289,71 @@ class IPAServer: group = self.get_entry_by_cn(cn, ['dn', 'uid'], opts) return self.mark_entry_inactive(group.get('dn')) + def __is_service_unique(self, name, opts): + """Return 1 if the uid is unique in the tree, 0 otherwise.""" + name = self.__safe_filter(name) + filter = "(&(krbprincipalname=%s)(objectclass=krbPrincipal))" % name + + try: + entry = self.__get_sub_entry(self.basedn, filter, ['dn','krbprincipalname'], opts) + return 0 + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + return 1 + + def add_service_principal(self, name, opts=None): + service_container = DefaultServiceContainer + + princ_name = name + "@" + self.realm + + conn = self.getConnection(opts) + if self.__is_service_unique(name, opts) == 0: + raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + + dn = "krbprincipalname=%s,%s,%s" % (ldap.dn.escape_dn_chars(princ_name), + service_container,self.basedn) + entry = ipaserver.ipaldap.Entry(dn) + + entry.setValues('objectclass', 'krbPrincipal', 'krbPrincipalAux', 'krbTicketPolicyAux') + entry.setValues('krbprincipalname', princ_name) + + try: + res = conn.addEntry(entry) + finally: + self.releaseConnection(conn) + return res + + + def get_keytab(self, name, opts=None): + """get a keytab""" + + princ_name = name + "@" + self.realm + + conn = self.getConnection(opts) + + if conn.principal != "admin@" + self.realm: + raise ipaerror.gen_exception(ipaerror.CONNECTION_GSSAPI_CREDENTIALS) + + try: + try: + princs = conn.getList(self.basedn, self.scope, "krbprincipalname=" + princ_name, None) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + return None + finally: + self.releaseConnection(conn) + + + # This is ugly - call out to a C wrapper around kadmin.local + p = subprocess.Popen(["/usr/sbin/ipa-keytab-util", princ_name, self.realm], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout,stderr = p.communicate() + + if p.returncode != 0: + return None + + return stdout + + + # Configuration support def get_ipa_config(self, opts=None): """Retrieve the IPA configuration""" diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 789233c9f..c6f0ec2ce 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -359,6 +359,8 @@ def handler(req, profiling=False): h.register_function(f.update_ipa_config) h.register_function(f.get_password_policy) h.register_function(f.update_password_policy) + h.register_function(f.add_service_principal) + h.register_function(f.get_keytab) h.handle_request(req) finally: pass |