Add support for SSH public keys to user and host objects.

This patch adds a new multivalue param "sshpubkey" for specifying SSH public
keys to both user and host objects. The accepted value is base64-encoded
public key blob as specified in RFC4253, section 6.6.

Additionaly, host commands automatically update DNS SSHFP records when
requested by user.

https://fedorahosted.org/freeipa/ticket/754
This commit is contained in:
Jan Cholasta 2011-12-07 02:50:31 -05:00 committed by Rob Crittenden
parent 9b6baf9bee
commit 3c2b0fc28a
8 changed files with 193 additions and 36 deletions

13
API.txt
View File

@ -1657,7 +1657,7 @@ output: Output('notmatched', (<type 'list'>, <type 'tuple'>, <type 'NoneType'>),
output: Output('error', (<type 'list'>, <type 'tuple'>, <type 'NoneType'>), None) output: Output('error', (<type 'list'>, <type 'tuple'>, <type 'NoneType'>), None)
output: Output('value', <type 'bool'>, None) output: Output('value', <type 'bool'>, None)
command: host_add command: host_add
args: 1,15,3 args: 1,16,3
arg: Str('fqdn', attribute=True, cli_name='hostname', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9][a-zA-Z0-9-\\.]{0,254}$', pattern_errmsg='may only include letters, numbers, and -', primary_key=True, required=True) arg: Str('fqdn', attribute=True, cli_name='hostname', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9][a-zA-Z0-9-\\.]{0,254}$', pattern_errmsg='may only include letters, numbers, and -', primary_key=True, required=True)
option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False) option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
option: Str('l', attribute=True, cli_name='locality', multivalue=False, required=False) option: Str('l', attribute=True, cli_name='locality', multivalue=False, required=False)
@ -1668,6 +1668,7 @@ option: Str('userpassword', attribute=True, cli_name='password', multivalue=Fals
option: Flag('random', attribute=False, autofill=True, cli_name='random', default=False, multivalue=False, required=False) option: Flag('random', attribute=False, autofill=True, cli_name='random', default=False, multivalue=False, required=False)
option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=False, required=False) option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=False, required=False)
option: Str('macaddress', attribute=True, cli_name='macaddress', csv=True, multivalue=True, pattern='^([a-fA-F0-9]{2}[:|\\-]?){5}[a-fA-F0-9]{2}$', pattern_errmsg='Must be of the form HH:HH:HH:HH:HH:HH, where each H is a hexadecimal character.', required=False) option: Str('macaddress', attribute=True, cli_name='macaddress', csv=True, multivalue=True, pattern='^([a-fA-F0-9]{2}[:|\\-]?){5}[a-fA-F0-9]{2}$', pattern_errmsg='Must be of the form HH:HH:HH:HH:HH:HH, where each H is a hexadecimal character.', required=False)
option: Bytes('ipasshpubkey', attribute=True, cli_name='sshpubkey', multivalue=True, required=False)
option: Flag('force', autofill=True, default=False) option: Flag('force', autofill=True, default=False)
option: Flag('no_reverse', autofill=True, default=False) option: Flag('no_reverse', autofill=True, default=False)
option: Str('ip_address?') option: Str('ip_address?')
@ -1739,7 +1740,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
output: Output('count', <type 'int'>, None) output: Output('count', <type 'int'>, None)
output: Output('truncated', <type 'bool'>, None) output: Output('truncated', <type 'bool'>, None)
command: host_mod command: host_mod
args: 1,17,3 args: 1,19,3
arg: Str('fqdn', attribute=True, cli_name='hostname', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9][a-zA-Z0-9-\\.]{0,254}$', pattern_errmsg='may only include letters, numbers, and -', primary_key=True, query=True, required=True) arg: Str('fqdn', attribute=True, cli_name='hostname', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9][a-zA-Z0-9-\\.]{0,254}$', pattern_errmsg='may only include letters, numbers, and -', primary_key=True, query=True, required=True)
option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False) option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
option: Str('l', attribute=True, autofill=False, cli_name='locality', multivalue=False, required=False) option: Str('l', attribute=True, autofill=False, cli_name='locality', multivalue=False, required=False)
@ -1750,11 +1751,13 @@ option: Str('userpassword', attribute=True, autofill=False, cli_name='password',
option: Flag('random', attribute=False, autofill=True, cli_name='random', default=False, multivalue=False, required=False) option: Flag('random', attribute=False, autofill=True, cli_name='random', default=False, multivalue=False, required=False)
option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, required=False) option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, required=False)
option: Str('macaddress', attribute=True, autofill=False, cli_name='macaddress', csv=True, multivalue=True, pattern='^([a-fA-F0-9]{2}[:|\\-]?){5}[a-fA-F0-9]{2}$', pattern_errmsg='Must be of the form HH:HH:HH:HH:HH:HH, where each H is a hexadecimal character.', required=False) option: Str('macaddress', attribute=True, autofill=False, cli_name='macaddress', csv=True, multivalue=True, pattern='^([a-fA-F0-9]{2}[:|\\-]?){5}[a-fA-F0-9]{2}$', pattern_errmsg='Must be of the form HH:HH:HH:HH:HH:HH, where each H is a hexadecimal character.', required=False)
option: Bytes('ipasshpubkey', attribute=True, autofill=False, cli_name='sshpubkey', multivalue=True, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui') option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui') option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Str('delattr*', cli_name='delattr', exclude='webui') option: Str('delattr*', cli_name='delattr', exclude='webui')
option: Flag('rights', autofill=True, default=False) option: Flag('rights', autofill=True, default=False)
option: Str('krbprincipalname?', attribute=True, cli_name='principalname') option: Str('krbprincipalname?', attribute=True, cli_name='principalname')
option: Flag('updatedns?', autofill=True, default=False)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui') option: Str('version?', exclude='webui')
@ -3087,7 +3090,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None) output: Output('value', <type 'unicode'>, None)
command: user_add command: user_add
args: 1,32,3 args: 1,33,3
arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', primary_key=True, required=True) arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', primary_key=True, required=True)
option: Str('givenname', attribute=True, cli_name='first', multivalue=False, required=True) option: Str('givenname', attribute=True, cli_name='first', multivalue=False, required=True)
option: Str('sn', attribute=True, cli_name='last', multivalue=False, required=True) option: Str('sn', attribute=True, cli_name='last', multivalue=False, required=True)
@ -3115,6 +3118,7 @@ option: Str('ou', attribute=True, cli_name='orgunit', multivalue=False, required
option: Str('title', attribute=True, cli_name='title', multivalue=False, required=False) option: Str('title', attribute=True, cli_name='title', multivalue=False, required=False)
option: Str('manager', attribute=True, cli_name='manager', multivalue=False, required=False) option: Str('manager', attribute=True, cli_name='manager', multivalue=False, required=False)
option: Str('carlicense', attribute=True, cli_name='carlicense', multivalue=False, required=False) option: Str('carlicense', attribute=True, cli_name='carlicense', multivalue=False, required=False)
option: Bytes('ipasshpubkey', attribute=True, cli_name='sshpubkey', multivalue=True, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui') option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui') option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('noprivate', autofill=True, cli_name='noprivate', default=False) option: Flag('noprivate', autofill=True, cli_name='noprivate', default=False)
@ -3194,7 +3198,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
output: Output('count', <type 'int'>, None) output: Output('count', <type 'int'>, None)
output: Output('truncated', <type 'bool'>, None) output: Output('truncated', <type 'bool'>, None)
command: user_mod command: user_mod
args: 1,33,3 args: 1,34,3
arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', primary_key=True, query=True, required=True) arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', primary_key=True, query=True, required=True)
option: Str('givenname', attribute=True, autofill=False, cli_name='first', multivalue=False, required=False) option: Str('givenname', attribute=True, autofill=False, cli_name='first', multivalue=False, required=False)
option: Str('sn', attribute=True, autofill=False, cli_name='last', multivalue=False, required=False) option: Str('sn', attribute=True, autofill=False, cli_name='last', multivalue=False, required=False)
@ -3221,6 +3225,7 @@ option: Str('ou', attribute=True, autofill=False, cli_name='orgunit', multivalue
option: Str('title', attribute=True, autofill=False, cli_name='title', multivalue=False, required=False) option: Str('title', attribute=True, autofill=False, cli_name='title', multivalue=False, required=False)
option: Str('manager', attribute=True, autofill=False, cli_name='manager', multivalue=False, required=False) option: Str('manager', attribute=True, autofill=False, cli_name='manager', multivalue=False, required=False)
option: Str('carlicense', attribute=True, autofill=False, cli_name='carlicense', multivalue=False, required=False) option: Str('carlicense', attribute=True, autofill=False, cli_name='carlicense', multivalue=False, required=False)
option: Bytes('ipasshpubkey', attribute=True, autofill=False, cli_name='sshpubkey', multivalue=True, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui') option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui') option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Str('delattr*', cli_name='delattr', exclude='webui') option: Str('delattr*', cli_name='delattr', exclude='webui')

View File

@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000
# # # #
######################################################## ########################################################
IPA_API_VERSION_MAJOR=2 IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=25 IPA_API_VERSION_MINOR=26

View File

@ -22,6 +22,8 @@ import platform
import os import os
import sys import sys
from nss.error import NSPRError from nss.error import NSPRError
import nss.nss as nss
import netaddr
from ipalib import api, errors, util from ipalib import api, errors, util
from ipalib import Str, Flag, Bytes from ipalib import Str, Flag, Bytes
@ -34,11 +36,9 @@ from ipalib.plugins.dns import add_forward_record
from ipalib import _, ngettext from ipalib import _, ngettext
from ipalib import x509 from ipalib import x509
from ipalib.dn import * from ipalib.dn import *
from ipapython.ipautil import ipa_generate_password, CheckedIPAddress
from ipalib.request import context from ipalib.request import context
import base64 from ipalib.util import validate_sshpubkey, output_sshpubkey
import nss.nss as nss from ipapython.ipautil import ipa_generate_password, CheckedIPAddress, make_sshfp
import netaddr
__doc__ = _(""" __doc__ = _("""
Hosts/Machines Hosts/Machines
@ -87,6 +87,9 @@ EXAMPLES:
Modify information about a host: Modify information about a host:
ipa host-mod --os='Fedora 12' test.example.com ipa host-mod --os='Fedora 12' test.example.com
Remove SSH public keys of a host and update DNS to reflect this change:
ipa host-mod --sshpubkey= --updatedns test.example.com
Disable the host Kerberos key, SSL certificate and all of its services: Disable the host Kerberos key, SSL certificate and all of its services:
ipa host-disable test.example.com ipa host-disable test.example.com
@ -162,6 +165,22 @@ def remove_fwd_ptr(ipaddr, host, domain, recordtype):
except errors.NotFound: except errors.NotFound:
pass pass
def update_sshfp_record(zone, record, entry_attrs):
if 'ipasshpubkey' not in entry_attrs:
return
pubkeys = entry_attrs['ipasshpubkey'] or ()
sshfps=[]
for pubkey in pubkeys:
sshfp = unicode(make_sshfp(pubkey))
if sshfp is not None:
sshfps.append(sshfp)
try:
api.Command['dnsrecord_mod'](zone, record, sshfprecord=sshfps)
except errors.EmptyModlist:
pass
host_output_params = ( host_output_params = (
Flag('has_keytab', Flag('has_keytab',
label=_('Keytab'), label=_('Keytab'),
@ -226,6 +245,7 @@ class host(LDAPObject):
'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname', 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname',
'nshardwareplatform', 'nsosversion', 'usercertificate', 'memberof', 'nshardwareplatform', 'nsosversion', 'usercertificate', 'memberof',
'managedby', 'memberindirect', 'memberofindirect', 'macaddress', 'managedby', 'memberindirect', 'memberofindirect', 'macaddress',
'sshpubkeyfp',
] ]
uuid_attribute = 'ipauniqueid' uuid_attribute = 'ipauniqueid'
attribute_members = { attribute_members = {
@ -315,6 +335,15 @@ class host(LDAPObject):
label=_('MAC address'), label=_('MAC address'),
doc=_('Hardware MAC address(es) on this host'), doc=_('Hardware MAC address(es) on this host'),
), ),
Bytes('ipasshpubkey*', validate_sshpubkey,
cli_name='sshpubkey',
label=_('Base-64 encoded SSH public key'),
flags=['no_search'],
),
Str('sshpubkeyfp*',
label=_('SSH public key fingerprint'),
flags=['virtual_attribute', 'no_create', 'no_update', 'no_search'],
),
) )
def get_dn(self, *keys, **options): def get_dn(self, *keys, **options):
@ -452,33 +481,37 @@ class host_add(LDAPCreate):
entry_attrs['usercertificate'] = cert entry_attrs['usercertificate'] = cert
entry_attrs['managedby'] = dn entry_attrs['managedby'] = dn
entry_attrs['objectclass'].append('ieee802device') entry_attrs['objectclass'].append('ieee802device')
entry_attrs['objectclass'].append('ipasshhost')
return dn return dn
def post_callback(self, ldap, dn, entry_attrs, *keys, **options): def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
exc = None exc = None
try: if dns_container_exists(ldap):
if 'ip_address' in options and dns_container_exists(ldap): try:
parts = keys[-1].split('.') parts = keys[-1].split('.')
domain = unicode('.'.join(parts[1:])) domain = unicode('.'.join(parts[1:]))
ip = CheckedIPAddress(options['ip_address'], match_local=False) if 'ip_address' in options:
add_forward_record(domain, parts[0], unicode(ip)) ip = CheckedIPAddress(options['ip_address'], match_local=False)
add_forward_record(domain, parts[0], unicode(ip))
if not options.get('no_reverse', False): if not options.get('no_reverse', False):
try: try:
prefixlen = None prefixlen = None
if not ip.defaultnet: if not ip.defaultnet:
prefixlen = ip.prefixlen prefixlen = ip.prefixlen
revzone, revname = get_reverse_zone(ip, prefixlen) revzone, revname = get_reverse_zone(ip, prefixlen)
addkw = { 'ptrrecord' : keys[-1]+'.' } addkw = { 'ptrrecord' : keys[-1]+'.' }
api.Command['dnsrecord_add'](revzone, revname, **addkw) api.Command['dnsrecord_add'](revzone, revname, **addkw)
except errors.EmptyModlist: except errors.EmptyModlist:
# the entry already exists and matches # the entry already exists and matches
pass pass
del options['ip_address'] del options['ip_address']
except Exception, e:
exc = e update_sshfp_record(domain, unicode(parts[0]), entry_attrs)
except Exception, e:
exc = e
if options.get('random', False): if options.get('random', False):
try: try:
entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword')) entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
@ -493,13 +526,15 @@ class host_add(LDAPCreate):
set_certificate_attrs(entry_attrs) set_certificate_attrs(entry_attrs)
if options.get('all', False): if options.get('all', False):
entry_attrs['managing'] = self.obj.get_managed_hosts(dn) entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
self.obj.get_password_attributes(ldap, dn, entry_attrs) self.obj.get_password_attributes(ldap, dn, entry_attrs)
if entry_attrs['has_password']: if entry_attrs['has_password']:
# If an OTP is set there is no keytab, at least not one # If an OTP is set there is no keytab, at least not one
# fetched anywhere. # fetched anywhere.
entry_attrs['has_keytab'] = False entry_attrs['has_keytab'] = False
output_sshpubkey(ldap, dn, entry_attrs)
return dn return dn
api.register(host_add) api.register(host_add)
@ -632,6 +667,10 @@ class host_mod(LDAPUpdate):
doc=_('Kerberos principal name for this host'), doc=_('Kerberos principal name for this host'),
attribute=True, attribute=True,
), ),
Flag('updatedns?',
doc=_('Update DNS entries'),
default=False,
),
) )
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
@ -688,6 +727,7 @@ class host_mod(LDAPUpdate):
raise nsprerr raise nsprerr
entry_attrs['usercertificate'] = cert entry_attrs['usercertificate'] = cert
if options.get('random'): if options.get('random'):
entry_attrs['userpassword'] = ipa_generate_password() entry_attrs['userpassword'] = ipa_generate_password()
setattr(context, 'randompassword', entry_attrs['userpassword']) setattr(context, 'randompassword', entry_attrs['userpassword'])
@ -703,6 +743,30 @@ class host_mod(LDAPUpdate):
obj_classes.append('ieee802device') obj_classes.append('ieee802device')
entry_attrs['objectclass'] = obj_classes entry_attrs['objectclass'] = obj_classes
if options.get('updatedns', False) and dns_container_exists(ldap):
parts = keys[-1].split('.')
domain = unicode('.'.join(parts[1:]))
result = api.Command['dnszone_find']()['result']
match = False
for zone in result:
if domain == zone['idnsname'][0]:
match = True
break
if not match:
raise errors.NotFound(
reason=_('DNS zone %(zone)s not found') % dict(zone=domain)
)
update_sshfp_record(domain, unicode(parts[0]), entry_attrs)
if 'ipasshpubkey' in entry_attrs:
if 'objectclass' in entry_attrs:
obj_classes = entry_attrs['objectclass']
else:
(_dn, _entry_attrs) = ldap.get_entry(dn, ['objectclass'])
obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass']
if 'ipasshhost' not in obj_classes:
obj_classes.append('ipasshhost')
return dn return dn
def post_callback(self, ldap, dn, entry_attrs, *keys, **options): def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
@ -720,6 +784,8 @@ class host_mod(LDAPUpdate):
self.obj.suppress_netgroup_memberof(entry_attrs) self.obj.suppress_netgroup_memberof(entry_attrs)
output_sshpubkey(ldap, dn, entry_attrs)
return dn return dn
api.register(host_mod) api.register(host_mod)
@ -802,6 +868,8 @@ class host_find(LDAPSearch):
if options.get('all', False): if options.get('all', False):
entry_attrs['managing'] = self.obj.get_managed_hosts(entry[0]) entry_attrs['managing'] = self.obj.get_managed_hosts(entry[0])
output_sshpubkey(ldap, dn, entry_attrs)
api.register(host_find) api.register(host_find)
@ -831,6 +899,8 @@ class host_show(LDAPRetrieve):
self.obj.suppress_netgroup_memberof(entry_attrs) self.obj.suppress_netgroup_memberof(entry_attrs)
output_sshpubkey(ldap, dn, entry_attrs)
return dn return dn
def forward(self, *keys, **options): def forward(self, *keys, **options):

View File

@ -18,16 +18,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from ipalib import api, errors
from ipalib import Flag, Int, Password, Str, Bool
from ipalib.plugins.baseldap import *
from ipalib.request import context
from time import gmtime, strftime from time import gmtime, strftime
import copy import copy
import string
from ipalib import api, errors
from ipalib import Flag, Int, Password, Str, Bool, Bytes
from ipalib.plugins.baseldap import *
from ipalib.request import context
from ipalib import _, ngettext from ipalib import _, ngettext
from ipapython.ipautil import ipa_generate_password from ipapython.ipautil import ipa_generate_password
import string
import posixpath import posixpath
from ipalib.util import validate_sshpubkey, output_sshpubkey
__doc__ = _(""" __doc__ = _("""
Users Users
@ -154,12 +156,12 @@ class user(LDAPObject):
'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
'uidnumber', 'gidnumber', 'mail', 'ou', 'uidnumber', 'gidnumber', 'mail', 'ou',
'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'telephonenumber', 'title', 'memberof', 'nsaccountlock',
'memberofindirect', 'memberofindirect', 'sshpubkeyfp',
] ]
search_display_attributes = [ search_display_attributes = [
'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
'mail', 'telephonenumber', 'title', 'nsaccountlock', 'mail', 'telephonenumber', 'title', 'nsaccountlock',
'uidnumber', 'gidnumber', 'uidnumber', 'gidnumber', 'sshpubkeyfp',
] ]
uuid_attribute = 'ipauniqueid' uuid_attribute = 'ipauniqueid'
attribute_members = { attribute_members = {
@ -310,6 +312,15 @@ class user(LDAPObject):
label=_('Account disabled'), label=_('Account disabled'),
flags=['no_create', 'no_update', 'no_search'], flags=['no_create', 'no_update', 'no_search'],
), ),
Bytes('ipasshpubkey*', validate_sshpubkey,
cli_name='sshpubkey',
label=_('Base-64 encoded SSH public key'),
flags=['no_search'],
),
Str('sshpubkeyfp*',
label=_('SSH public key fingerprint'),
flags=['virtual_attribute', 'no_create', 'no_update', 'no_search'],
),
) )
def _normalize_email(self, email, config=None): def _normalize_email(self, email, config=None):
@ -489,6 +500,9 @@ class user_add(LDAPCreate):
pass pass
self.obj.get_password_attributes(ldap, dn, entry_attrs) self.obj.get_password_attributes(ldap, dn, entry_attrs)
output_sshpubkey(ldap, dn, entry_attrs)
return dn return dn
api.register(user_add) api.register(user_add)
@ -522,6 +536,14 @@ class user_mod(LDAPUpdate):
entry_attrs['userpassword'] = ipa_generate_password(user_pwdchars) entry_attrs['userpassword'] = ipa_generate_password(user_pwdchars)
# save the password so it can be displayed in post_callback # save the password so it can be displayed in post_callback
setattr(context, 'randompassword', entry_attrs['userpassword']) setattr(context, 'randompassword', entry_attrs['userpassword'])
if 'ipasshpubkey' in entry_attrs:
if 'objectclass' in entry_attrs:
obj_classes = entry_attrs['objectclass']
else:
(_dn, _entry_attrs) = ldap.get_entry(dn, ['objectclass'])
obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass']
if 'ipasshuser' not in obj_classes:
obj_classes.append('ipasshuser')
return dn return dn
def post_callback(self, ldap, dn, entry_attrs, *keys, **options): def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
@ -534,6 +556,7 @@ class user_mod(LDAPUpdate):
convert_nsaccountlock(entry_attrs) convert_nsaccountlock(entry_attrs)
self.obj._convert_manager(entry_attrs, **options) self.obj._convert_manager(entry_attrs, **options)
self.obj.get_password_attributes(ldap, dn, entry_attrs) self.obj.get_password_attributes(ldap, dn, entry_attrs)
output_sshpubkey(ldap, dn, entry_attrs)
return dn return dn
api.register(user_mod) api.register(user_mod)
@ -567,6 +590,7 @@ class user_find(LDAPSearch):
self.obj._convert_manager(attrs, **options) self.obj._convert_manager(attrs, **options)
self.obj.get_password_attributes(ldap, dn, attrs) self.obj.get_password_attributes(ldap, dn, attrs)
convert_nsaccountlock(attrs) convert_nsaccountlock(attrs)
output_sshpubkey(ldap, dn, attrs)
msg_summary = ngettext( msg_summary = ngettext(
'%(count)d user matched', '%(count)d users matched', 0 '%(count)d user matched', '%(count)d users matched', 0
@ -584,6 +608,7 @@ class user_show(LDAPRetrieve):
convert_nsaccountlock(entry_attrs) convert_nsaccountlock(entry_attrs)
self.obj._convert_manager(entry_attrs, **options) self.obj._convert_manager(entry_attrs, **options)
self.obj.get_password_attributes(ldap, dn, entry_attrs) self.obj.get_password_attributes(ldap, dn, entry_attrs)
output_sshpubkey(ldap, dn, entry_attrs)
return dn return dn
api.register(user_show) api.register(user_show)

View File

@ -32,6 +32,7 @@ from weakref import WeakKeyDictionary
from ipalib import errors from ipalib import errors
from ipalib.text import _ from ipalib.text import _
from ipapython import dnsclient from ipapython import dnsclient
from ipapython.ipautil import decode_ssh_pubkey
def json_serialize(obj): def json_serialize(obj):
@ -278,6 +279,37 @@ def validate_hostname(hostname, check_fqdn=True):
raise ValueError(_('only letters, numbers, and - are allowed. ' \ raise ValueError(_('only letters, numbers, and - are allowed. ' \
'- must not be the last name character')) '- must not be the last name character'))
def validate_sshpubkey(ugettext, pubkey):
try:
algo, data, fp = decode_ssh_pubkey(pubkey)
except ValueError:
return _('invalid SSH public key')
def output_sshpubkey(ldap, dn, entry_attrs):
if 'ipasshpubkey' in entry_attrs:
pubkeys = entry_attrs.get('ipasshpubkey')
else:
entry = ldap.get_entry(dn, ['ipasshpubkey'])
pubkeys = entry[1].get('ipasshpubkey')
if pubkeys is None:
return
fingerprints = []
for pubkey in pubkeys:
try:
algo, data, fp = decode_ssh_pubkey(pubkey)
fp = u':'.join([fp[j:j+2] for j in range(0, len(fp), 2)])
fingerprints.append(u'%s (%s)' % (fp, algo))
except ValueError:
pass
if fingerprints:
entry_attrs['sshpubkeyfp'] = fingerprints
def normalize_sshpubkeyfp(value):
value = value.split()[0]
value = unicode(c for c in value if c in '0123456789ABCDEFabcdef')
return value
class cachedproperty(object): class cachedproperty(object):
""" """
A property-like attribute that caches the return value of a method call. A property-like attribute that caches the return value of a method call.

View File

@ -36,6 +36,7 @@ import shutil
import urllib2 import urllib2
import socket import socket
import ldap import ldap
import struct
from ipapython import ipavalidate from ipapython import ipavalidate
from types import * from types import *
@ -58,6 +59,7 @@ except ImportError:
self.cmd = cmd self.cmd = cmd
def __str__(self): def __str__(self):
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
from ipapython.compat import sha1, md5
def get_domain_name(): def get_domain_name():
try: try:
@ -1395,3 +1397,22 @@ def backup_config_and_replace_variables(fstore, filepath, replacevars=dict(), ap
old_values = config_replace_variables(filepath, replacevars, appendvars) old_values = config_replace_variables(filepath, replacevars, appendvars)
return old_values return old_values
def decode_ssh_pubkey(data, fptype=md5):
try:
(algolen,) = struct.unpack('>I', data[:4])
if algolen > 0 and algolen <= len(data) - 4:
return (data[4:algolen+4], data[algolen+4:], fptype(data).hexdigest().upper())
except struct.error:
pass
raise ValueError('not a SSH public key')
def make_sshfp(key):
algo, data, fp = decode_ssh_pubkey(key, fptype=sha1)
if algo == 'ssh-rsa':
algo = 1
elif algo == 'ssh-dss':
algo = 2
else:
return
return '%d 1 %s' % (algo, fp)

View File

@ -110,7 +110,7 @@ class KrbInstance(service.Service):
# Create a host entry for this master # Create a host entry for this master
host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix)
host_entry = ipaldap.Entry(host_dn) host_entry = ipaldap.Entry(host_dn)
host_entry.setValues('objectclass', ['top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice', 'pkiuser', 'krbprincipalaux', 'krbprincipal', 'krbticketpolicyaux']) host_entry.setValues('objectclass', ['top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice', 'pkiuser', 'krbprincipalaux', 'krbprincipal', 'krbticketpolicyaux', 'ipasshhost'])
host_entry.setValues('krbextradata', service_entry.getValues('krbextradata')) host_entry.setValues('krbextradata', service_entry.getValues('krbextradata'))
host_entry.setValue('krblastpwdchange', service_entry.getValue('krblastpwdchange')) host_entry.setValue('krblastpwdchange', service_entry.getValue('krblastpwdchange'))
if 'krbpasswordexpiration' in service_entry.toDict(): if 'krbpasswordexpiration' in service_entry.toDict():

View File

@ -31,6 +31,8 @@ user_base = [
u'krbprincipalaux', u'krbprincipalaux',
u'krbticketpolicyaux', u'krbticketpolicyaux',
u'ipaobject', u'ipaobject',
u'ipasshuser',
u'ipaSshGroupOfPubKeys',
] ]
user = user_base + [u'mepOriginEntry'] user = user_base + [u'mepOriginEntry']
@ -44,6 +46,8 @@ group = [
] ]
host = [ host = [
u'ipasshhost',
u'ipaSshGroupOfPubKeys',
u'ieee802device', u'ieee802device',
u'ipaobject', u'ipaobject',
u'nshost', u'nshost',