Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Authors: # Rob Crittenden <rcritten@redhat.com> # Pavel Zuna <pzuna@redhat.com> # # Copyright (C) 2008 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, either version 3 of the License, or # (at your option) any later version. # # 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, see <http://www.gnu.org/licenses/>.
set_certificate_attrs, ticket_flags_params, update_krbticketflags, set_kerberos_attrs) add_records_for_host_validation, add_records_for_host, _hostname_validator, get_reverse_zone) convert_sshpubkey_post)
Hosts/Machines
A host represents a machine. It can be used in a number of contexts: - service entries are associated with a host - a host stores the host/ service principal - a host can be used in Host-based Access Control (HBAC) rules - every enrolled client generates a host entry
ENROLLMENT:
There are three enrollment scenarios when enrolling a new client:
1. You are enrolling as a full administrator. The host entry may exist or not. A full administrator is a member of the hostadmin role or the admins group. 2. You are enrolling as a limited administrator. The host must already exist. A limited administrator is a member a role with the Host Enrollment privilege. 3. The host has been created with a one-time password.
A host can only be enrolled once. If a client has enrolled and needs to be re-enrolled, the host entry must be removed and re-created. Note that re-creating the host entry will result in all services for the host being removed, and all SSL certificates associated with those services being revoked.
A host can optionally store information such as where it is located, the OS that it runs, etc.
EXAMPLES:
Add a new host: ipa host-add --location="3rd floor lab" --locality=Dallas test.example.com
Delete a host: ipa host-del test.example.com
Add a new host with a one-time password: ipa host-add --os='Fedora 12' --password=Secret123 test.example.com
Add a new host with a random one-time password: ipa host-add --os='Fedora 12' --random test.example.com
Modify information about a host: 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: ipa host-disable test.example.com
Add a host that can manage this host's keytab and certificate: ipa host-add-managedby --hosts=test2 test """)
# Characters to be used by random password generator # The set was chosen to avoid the need for escaping the characters by user
api.log.debug('deleting ipaddr %s' % ipaddr) try: revzone, revname = get_reverse_zone(ipaddr)
# in case domain is in FQDN form with a trailing dot, we needn't add # another one, in case it has no trailing dot, dnsrecord-del will # normalize the entry delkw = { 'ptrrecord' : "%s.%s" % (host, domain) }
api.Command['dnsrecord_del'](revzone, revname, **delkw) except errors.NotFound: pass
try: delkw = { recordtype : ipaddr } api.Command['dnsrecord_del'](domain, host, **delkw) except errors.NotFound: pass
pubkeys = entry_attrs['ipasshpubkey'] or () sshfps=[] for pubkey in pubkeys: try: sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha1() except ValueError, UnicodeDecodeError: continue if sshfp is not None: sshfps.append(sshfp) try: sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha256() except ValueError, UnicodeDecodeError: continue if sshfp is not None: sshfps.append(sshfp)
try: api.Command['dnsrecord_mod'](zone, record, sshfprecord=sshfps) except errors.EmptyModlist: pass
Flag('has_keytab', label=_('Keytab'), ), Str('managedby_host', label='Managed by', ), Str('managing_host', label='Managing', ), Str('subject', label=_('Subject'), ), Str('serial_number', label=_('Serial Number'), ), Str('serial_number_hex', label=_('Serial Number (hex)'), ), Str('issuer', label=_('Issuer'), ), Str('valid_not_before', label=_('Not Before'), ), Str('valid_not_after', label=_('Not After'), ), Str('md5_fingerprint', label=_('Fingerprint (MD5)'), ), Str('sha1_fingerprint', label=_('Fingerprint (SHA1)'), ), Str('revocation_reason?', label=_('Revocation reason'), ), Str('managedby', label=_('Failed managedby'), ), Str('sshpubkeyfp*', label=_('SSH public key fingerprint'), ), )
""" Verify that we have either an IPv4 or IPv6 address. """ try: ip = CheckedIPAddress(ipaddr, match_local=False) except Exception, e: return unicode(e) return None
"""Use common fqdn form without the trailing dot""" hostname = hostname[:-1]
""" Host object. """ # object_class_config = 'ipahostobjectclasses' 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname', 'nshardwareplatform', 'nsosversion', 'managedby' ] 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname', 'nshardwareplatform', 'nsosversion', 'usercertificate', 'memberof', 'managedby', 'memberindirect', 'memberofindirect', 'macaddress', ] 'enrolledby': ['user'], 'memberof': ['hostgroup', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'managedby': ['host'], 'managing': ['host'], 'memberofindirect': ['hostgroup', 'netgroup', 'role', 'hbacrule', 'sudorule'], } 'memberof': ('Member Of', 'in_', 'not_in_'), 'enrolledby': ('Enrolled by', 'enroll_by_', 'not_enroll_by_'), 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), 'managing': ('Managing', 'man_', 'not_man_'), } ('krbprincipalkey', 'has_keytab')]
Str('fqdn', _hostname_validator, cli_name='hostname', label=_('Host name'), primary_key=True, normalizer=normalize_hostname, ), Str('description?', cli_name='desc', label=_('Description'), doc=_('A description of this host'), ), Str('l?', cli_name='locality', label=_('Locality'), doc=_('Host locality (e.g. "Baltimore, MD")'), ), Str('nshostlocation?', cli_name='location', label=_('Location'), doc=_('Host location (e.g. "Lab 2")'), ), Str('nshardwareplatform?', cli_name='platform', label=_('Platform'), doc=_('Host hardware platform (e.g. "Lenovo T61")'), ), Str('nsosversion?', cli_name='os', label=_('Operating system'), doc=_('Host operating system and version (e.g. "Fedora 9")'), ), Str('userpassword?', cli_name='password', label=_('User password'), doc=_('Password used in bulk enrollment'), ), Flag('random?', doc=_('Generate a random password to be used in bulk enrollment'), flags=('no_search', 'virtual_attribute'), default=False, ), Str('randompassword?', label=_('Random password'), flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'), ), Bytes('usercertificate?', validate_certificate, cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded server certificate'), ), Str('krbprincipalname?', label=_('Principal name'), flags=['no_create', 'no_update', 'no_search'], ), Str('macaddress*', normalizer=lambda value: value.upper(), 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.', csv=True, label=_('MAC address'), doc=_('Hardware MAC address(es) on this host'), ), Str('ipasshpubkey*', validate_sshpubkey_no_options, cli_name='sshpubkey', label=_('SSH public key'), normalizer=normalize_sshpubkey, csv=True, flags=['no_search'], ), ) + ticket_flags_params
'serverhostname', hostname, self.object_class, [''], DN(self.container_dn, api.env.basedn))
base_dn=DN(self.container_dn, api.env.basedn), filter=host_filter, attrs_list=host_attrs)
except errors.NotFound: return []
""" We don't want to show managed netgroups so remove them from the memberofindirect list. """ except errors.NotFound: pass
Flag('force', label=_('Force'), doc=_('force host name even if not in DNS'), ), Flag('no_reverse', doc=_('skip reverse DNS detection'), ), Str('ip_address?', validate_ipaddr, doc=_('Add the host to DNS with this IP address'), label=_('IP Address'), ), )
parts = keys[-1].split('.') host = parts[0] domain = unicode('.'.join(parts[1:])) check_reverse = not options.get('no_reverse', False) add_records_for_host_validation('ip_address', host, domain, options['ip_address'], check_forward=True, check_reverse=check_reverse) keys[-1], self.api.env.realm ) else: entry_attrs['objectclass'].remove('krbprincipalaux') entry_attrs['objectclass'].remove('krbprincipal') # save the password so it can be displayed in post_callback cert = x509.normalize_certificate(cert) x509.verify_cert_subject(ldap, keys[-1], cert) entry_attrs['usercertificate'] = cert entry_attrs['objectclass'].append('krbticketpolicyaux')
add_reverse = not options.get('no_reverse', False)
add_records_for_host(host, domain, options['ip_address'], add_forward=True, add_reverse=add_reverse) del options['ip_address']
except Exception, e: exc = e except AttributeError: # On the off-chance some other extension deletes this from the # context, don't crash. pass raise errors.NonFatalError( reason=_('The host was added but the DNS update failed with: %(exc)s') % dict(exc=exc) )
entry_attrs['managing'] = self.obj.get_managed_hosts(dn) # If an OTP is set there is no keytab, at least not one # fetched anywhere.
Flag('updatedns?', doc=_('Remove entries from DNS'), default=False, ), )
# If we aren't given a fqdn, find it else: # Remove all service records for this host except errors.NotFound: break else: try: updatedns = dns_container_exists(ldap) except errors.NotFound: updatedns = False
# Remove DNS entries parts = fqdn.split('.') domain = unicode('.'.join(parts[1:])) try: result = api.Command['dnszone_show'](domain)['result'] domain = result['idnsname'][0] except errors.NotFound: self.obj.handle_not_found(*keys) # Get all forward resources for this host records = api.Command['dnsrecord_find'](domain, idnsname=parts[0])['result'] for record in records: if 'arecord' in record: remove_fwd_ptr(record['arecord'][0], parts[0], domain, 'arecord') if 'aaaarecord' in record: remove_fwd_ptr(record['aaaarecord'][0], parts[0], domain, 'aaaarecord') else: # Try to delete all other record types too _attribute_types = [str('%srecord' % t.lower()) for t in _record_types] for attr in _attribute_types: if attr not in ['arecord', 'aaaarecord'] and attr in record: for i in xrange(len(record[attr])): if (record[attr][i].endswith(parts[0]) or record[attr][i].endswith(fqdn+'.')): delkw = { unicode(attr) : record[attr][i] } api.Command['dnsrecord_del'](domain, record['idnsname'][0], **delkw) break
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 host. self.log.info("Problem decoding certificate %s" % nsprerr.args[1]) else: raise nsprerr
Str('krbprincipalname?', cli_name='principalname', label=_('Principal name'), doc=_('Kerberos principal name for this host'), attribute=True, ), Flag('updatedns?', doc=_('Update DNS entries'), default=False, ), )
# Allow an existing OTP to be reset but don't allow a OTP to be # added to an enrolled host.
# Once a principal name is set it cannot be changed raise errors.ACIError(info=_('cn is immutable')) entry_attrs['l'] = entry_attrs['locality'] (dn, entry_attrs_old) = ldap.get_entry( dn, ['objectclass', 'krbprincipalname'] ) if 'krbprincipalname' in entry_attrs_old: msg = 'Principal name already set, it is unchangeable.' raise errors.ACIError(info=msg) obj_classes = entry_attrs_old['objectclass'] if 'krbprincipalaux' not in obj_classes: obj_classes.append('krbprincipalaux') entry_attrs['objectclass'] = obj_classes oldcert = x509.normalize_certificate(oldcert) try: serial = x509.get_serial_number(oldcert, x509.DER) serial = unicode(serial) try: result = api.Command['cert_show'](serial)['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke']( 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 # modifying the host. self.log.info("Problem decoding certificate %s" % nsprerr.args[1]) else: raise nsprerr
entry_attrs['userpassword'] = ipa_generate_password(characters=host_pwd_chars) setattr(context, 'randompassword', entry_attrs['userpassword'])
obj_classes = entry_attrs['objectclass'] else: dn, ['objectclass'] ) obj_classes.append('ieee802device') entry_attrs['objectclass'] = obj_classes
parts = keys[-1].split('.') domain = unicode('.'.join(parts[1:])) try: result = api.Command['dnszone_show'](domain)['result'] domain = result['idnsname'][0] except errors.NotFound: self.obj.handle_not_found(*keys) update_sshfp_record(domain, unicode(parts[0]), entry_attrs)
obj_classes = entry_attrs['objectclass'] else: obj_classes.append('ipasshhost')
if 'objectclass' not in entry_attrs: entry_attrs_old = ldap.get_entry(dn, ['objectclass']) entry_attrs['objectclass'] = entry_attrs_old['objectclass'] if 'krbticketpolicyaux' not in entry_attrs['objectclass']: entry_attrs['objectclass'].append('krbticketpolicyaux')
entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword')) # If an OTP is set there is no keytab, at least not one # fetched anywhere. entry_attrs['has_keytab'] = False
entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
'%(count)d host matched', '%(count)d hosts matched', 0 )
# "managing" membership has to be added and processed separately
attrs_list.remove('locality') attrs_list.append('l') except errors.NotFound: self.obj.handle_not_found(pkey)
# There is no host managing _all_ hosts in --man-hosts (filter, '(objectclass=disabled)'), ldap.MATCH_ALL )
except errors.NotFound: self.obj.handle_not_found(pkey)
(not_hosts, ldap.MATCH_NONE)):
(filter, hosts_filter), ldap.MATCH_ALL )
return truncated # If an OTP is set there is no keytab, at least not one # fetched anywhere. entry_attrs['has_keytab'] = False
Str('out?', doc=_('file to store certificate in'), ), )
# If an OTP is set there is no keytab, at least not one # fetched anywhere. entry_attrs['has_keytab'] = False
util.check_writable_file(options['out']) result = super(host_show, self).forward(*keys, **options) if 'usercertificate' in result['result']: x509.write_certificate(result['result']['usercertificate'][0], options['out']) result['summary'] = _('Certificate stored in file \'%(file)s\'') % dict(file=options['out']) return result else: raise errors.NoCertificateError(entry=keys[-1]) else:
# If we aren't given a fqdn, find it hostentry = api.Command['host_show'](keys[-1])['result'] fqdn = hostentry['fqdn'][0] else:
# See if we actually do anthing here, and if not raise an exception done_work = False
truncated = True while truncated: try: ret = api.Command['service_find'](fqdn) truncated = ret['truncated'] services = ret['result'] except errors.NotFound: break else: for entry_attrs in services: principal = entry_attrs['krbprincipalname'][0] (service, hostname, realm) = split_principal(principal) if hostname.lower() == fqdn: try: api.Command['service_disable'](principal) done_work = True except errors.AlreadyInactive: pass
dn = self.obj.get_dn(*keys, **options) try: (dn, entry_attrs) = ldap.get_entry(dn, ['usercertificate']) except errors.NotFound: self.obj.handle_not_found(*keys) cert = entry_attrs.single_value('usercertificate', None) if cert: if self.api.env.enable_ra: cert = x509.normalize_certificate(cert) try: serial = unicode(x509.get_serial_number(cert, x509.DER)) try: result = api.Command['cert_show'](serial)['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke'](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 # disabling the host. self.log.info("Problem decoding certificate %s" % nsprerr.args[1]) else: raise nsprerr
# Remove the usercertificate altogether ldap.update_entry(dn, {'usercertificate': None}) done_work = True
self.obj.get_password_attributes(ldap, dn, entry_attrs) if entry_attrs['has_keytab']: ldap.remove_principal_key(dn) done_work = True
if not done_work: raise errors.AlreadyInactive()
return dict( result=True, value=keys[0], )
assert isinstance(dn, DN) self.obj.suppress_netgroup_memberof(entry_attrs) return dn
|