Add xml-rpc interface for getting keytabs.

Warning: this lacks any sort of authorization.
This commit is contained in:
Karl MacMillan
-
parent 9038bf71dd
commit edc7af1446
13 changed files with 524 additions and 4 deletions

View File

@@ -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; \

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)

View File

@@ -11,6 +11,7 @@ SUBDIRS = \
ipaserver \
ipa-slapi-plugins \
xmlrpc-server \
ipa-keytab-util \
$(NULL)
EXTRA_DIST = \

View File

@@ -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

View File

@@ -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

View File

@@ -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";)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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"""

View File

@@ -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