diff options
-rw-r--r-- | ACI.txt | 4 | ||||
-rw-r--r-- | API.txt | 65 | ||||
-rw-r--r-- | VERSION | 4 | ||||
-rw-r--r-- | install/share/dns.ldif | 6 | ||||
-rw-r--r-- | install/updates/40-dns.update | 6 | ||||
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/util.py | 53 | ||||
-rw-r--r-- | ipaserver/plugins/dns.py | 41 | ||||
-rw-r--r-- | ipaserver/plugins/dnsserver.py | 183 | ||||
-rw-r--r-- | ipaserver/plugins/host.py | 30 | ||||
-rw-r--r-- | ipaserver/plugins/server.py | 1 |
11 files changed, 336 insertions, 58 deletions
@@ -63,6 +63,10 @@ aci: (targetattr = "createtimestamp || entryusn || idnsallowsyncptr || idnsforwa dn: dc=ipa,dc=example aci: (targetattr = "idnsallowsyncptr || idnsforwarders || idnsforwardpolicy || idnspersistentsearch || idnszonerefresh")(target = "ldap:///cn=dns,dc=ipa,dc=example")(targetfilter = "(objectclass=idnsConfigObject)")(version 3.0;acl "permission:System: Write DNS Configuration";allow (write) groupdn = "ldap:///cn=System: Write DNS Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: dc=ipa,dc=example +aci: (targetattr = "idnsforwarders || idnsforwardpolicy || idnssoamname || idnssubstitutionvariable")(targetfilter = "(objectclass=idnsServerConfigObject)")(version 3.0;acl "permission:System: Modify DNS Servers Configuration";allow (write) groupdn = "ldap:///cn=System: Modify DNS Servers Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: dc=ipa,dc=example +aci: (targetattr = "createtimestamp || entryusn || idnsforwarders || idnsforwardpolicy || idnsserverid || idnssoamname || idnssubstitutionvariable || modifytimestamp || objectclass")(targetfilter = "(objectclass=idnsServerConfigObject)")(version 3.0;acl "permission:System: Read DNS Servers Configuration";allow (compare,read,search) groupdn = "ldap:///cn=System: Read DNS Servers Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: dc=ipa,dc=example aci: (target = "ldap:///idnsname=*,cn=dns,dc=ipa,dc=example")(version 3.0;acl "permission:System: Add DNS Entries";allow (add) groupdn = "ldap:///cn=System: Add DNS Entries,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: dc=ipa,dc=example aci: (targetattr = "ipaprivatekey || ipapublickey || ipasecretkey || ipasecretkeyref || ipawrappingkey || ipawrappingmech || ipk11allowedmechanisms || ipk11alwaysauthenticate || ipk11alwayssensitive || ipk11checkvalue || ipk11copyable || ipk11decrypt || ipk11derive || ipk11destroyable || ipk11distrusted || ipk11encrypt || ipk11enddate || ipk11extractable || ipk11id || ipk11keygenmechanism || ipk11keytype || ipk11label || ipk11local || ipk11modifiable || ipk11neverextractable || ipk11private || ipk11publickeyinfo || ipk11sensitive || ipk11sign || ipk11signrecover || ipk11startdate || ipk11subject || ipk11trusted || ipk11uniqueid || ipk11unwrap || ipk11unwraptemplate || ipk11verify || ipk11verifyrecover || ipk11wrap || ipk11wraptemplate || ipk11wrapwithtrusted || objectclass")(target = "ldap:///cn=keys,cn=sec,cn=dns,dc=ipa,dc=example")(version 3.0;acl "permission:System: Manage DNSSEC keys";allow (all) groupdn = "ldap:///cn=System: Manage DNSSEC keys,cn=permissions,cn=pbac,dc=ipa,dc=example";) @@ -1499,6 +1499,71 @@ arg: Str('name') arg: Str('value') option: Str('version?') output: Output('result') +command: dnsserver_add +args: 1,8,3 +arg: Str('idnsserverid', cli_name='hostname') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('idnsforwarders*', cli_name='forwarder') +option: StrEnum('idnsforwardpolicy?', cli_name='forward_policy', values=[u'only', u'first', u'none']) +option: DNSNameParam('idnssoamname?', cli_name='soa_mname_override') +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: dnsserver_del +args: 1,2,3 +arg: Str('idnsserverid+', cli_name='hostname') +option: Flag('continue', autofill=True, cli_name='continue', default=False) +option: Str('version?') +output: Output('result', type=[<type 'dict'>]) +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: ListOfPrimaryKeys('value') +command: dnsserver_find +args: 1,10,4 +arg: Str('criteria?') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('idnsforwarders*', autofill=False, cli_name='forwarder') +option: StrEnum('idnsforwardpolicy?', autofill=False, cli_name='forward_policy', values=[u'only', u'first', u'none']) +option: Str('idnsserverid?', autofill=False, cli_name='hostname') +option: DNSNameParam('idnssoamname?', autofill=False, cli_name='soa_mname_override') +option: Flag('pkey_only?', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Int('sizelimit?', autofill=False) +option: Int('timelimit?', autofill=False) +option: Str('version?') +output: Output('count', type=[<type 'int'>]) +output: ListOfEntries('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: Output('truncated', type=[<type 'bool'>]) +command: dnsserver_mod +args: 1,10,3 +arg: Str('idnsserverid', cli_name='hostname') +option: Str('addattr*', cli_name='addattr') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('delattr*', cli_name='delattr') +option: Str('idnsforwarders*', autofill=False, cli_name='forwarder') +option: StrEnum('idnsforwardpolicy?', autofill=False, cli_name='forward_policy', values=[u'only', u'first', u'none']) +option: DNSNameParam('idnssoamname?', autofill=False, cli_name='soa_mname_override') +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('setattr*', cli_name='setattr') +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') +command: dnsserver_show +args: 1,4,3 +arg: Str('idnsserverid', cli_name='hostname') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('rights', autofill=True, default=False) +option: Str('version?') +output: Entry('result') +output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) +output: PrimaryKey('value') command: dnszone_add args: 1,28,3 arg: DNSNameParam('idnsname', cli_name='name') @@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=185 -# Last change: mbasti - added command dns-update-system-records +IPA_API_VERSION_MINOR=186 +# Last change: mbasti - added dnsserver-* commands diff --git a/install/share/dns.ldif b/install/share/dns.ldif index 6cee47867..6a8524b51 100644 --- a/install/share/dns.ldif +++ b/install/share/dns.ldif @@ -12,3 +12,9 @@ aci: (targetattr = "*")(version 3.0; acl "Allow read access"; allow (read,search aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";) aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";) aci: (targetattr = "a6record || aaaarecord || afsdbrecord || aplrecord || arecord || certrecord || cn || cnamerecord || dhcidrecord || dlvrecord || dnamerecord || dnsclass || dnsttl || dsrecord || hinforecord || hiprecord || idnsallowdynupdate || idnsallowquery || idnsallowsyncptr || idnsallowtransfer || idnsforwarders || idnsforwardpolicy || idnsname || idnssecinlinesigning || idnssoaexpire || idnssoaminimum || idnssoamname || idnssoarefresh || idnssoaretry || idnssoarname || idnssoaserial || idnsupdatepolicy || idnszoneactive || ipseckeyrecord || keyrecord || kxrecord || locrecord || mdrecord || minforecord || mxrecord || naptrrecord || nsecrecord || nsec3paramrecord || nsrecord || nxtrecord || ptrrecord || rprecord || rrsigrecord || sigrecord || spfrecord || srvrecord || sshfprecord || tlsarecord || txtrecord || unknownrecord ")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Update DNS entries in a zone";allow (write) userattr = "parent[0,1].managedby#GROUPDN";) + +dn: cn=servers,cn=dns,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: servers diff --git a/install/updates/40-dns.update b/install/updates/40-dns.update index 4c0824b83..50f8b79ec 100644 --- a/install/updates/40-dns.update +++ b/install/updates/40-dns.update @@ -33,3 +33,9 @@ default: nsslapd-plugintype: preoperation default: nsslapd-pluginvendor: Red Hat, Inc. default: nsslapd-pluginversion: 1.0 default: nsslapd-plugin-depends-on-type: database + +# add dns servers container +dn: cn=servers,cn=dns,$SUFFIX +default: objectClass: nsContainer +default: objectClass: top +default: cn: servers diff --git a/ipalib/constants.py b/ipalib/constants.py index 05ba1adbb..bcddb5b97 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -123,6 +123,7 @@ DEFAULT_CONFIG = ( ('container_caacl', DN(('cn', 'caacls'), ('cn', 'ca'))), ('container_locations', DN(('cn', 'locations'), ('cn', 'etc'))), ('container_ca', DN(('cn', 'cas'), ('cn', 'ca'))), + ('container_dnsservers', DN(('cn', 'servers'), ('cn', 'dns'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipalib/util.py b/ipalib/util.py index 4b5f11509..68d11fc6c 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -898,3 +898,56 @@ class classproperty(object): def getter(self, fget): self.fget = fget return self + + +def normalize_hostname(hostname): + """Use common fqdn form without the trailing dot""" + if hostname.endswith(u'.'): + hostname = hostname[:-1] + hostname = hostname.lower() + return hostname + + +def hostname_validator(ugettext, value): + try: + validate_hostname(value) + except ValueError as e: + return _('invalid domain-name: %s') % unicode(e) + + return None + + +def ipaddr_validator(ugettext, ipaddr, ip_version=None): + try: + ip = netaddr.IPAddress(str(ipaddr), flags=netaddr.INET_PTON) + + if ip_version is not None: + if ip.version != ip_version: + return _( + 'invalid IP address version (is %(value)d, must be ' + '%(required_value)d)!') % dict( + value=ip.version, + required_value=ip_version + ) + except (netaddr.AddrFormatError, ValueError): + return _('invalid IP address format') + return None + + +def validate_bind_forwarder(ugettext, forwarder): + ip_address, sep, port = forwarder.partition(u' port ') + + ip_address_validation = ipaddr_validator(ugettext, ip_address) + + if ip_address_validation is not None: + return ip_address_validation + + if sep: + try: + port = int(port) + if port < 0 or port > 65535: + raise ValueError() + except ValueError: + return _('%(port)s is not a valid port' % dict(port=port)) + + return None diff --git a/ipaserver/plugins/dns.py b/ipaserver/plugins/dns.py index e9eb1f98f..094f8eb57 100644 --- a/ipaserver/plugins/dns.py +++ b/ipaserver/plugins/dns.py @@ -66,7 +66,9 @@ from ipalib.util import (normalize_zonemgr, EDNS0UnsupportedError, DNSSECValidationError, validate_dnssec_zone_forwarder_step1, validate_dnssec_zone_forwarder_step2, - verify_host_resolvable) + verify_host_resolvable, + validate_bind_forwarder, + ipaddr_validator) from ipapython.dn import DN from ipapython.ipautil import CheckedIPAddress from ipapython.dnsutil import check_zone_overlap @@ -373,23 +375,12 @@ def _reverse_zone_name(netstr): else: return None -def _validate_ipaddr(ugettext, ipaddr, ip_version=None): - try: - ip = netaddr.IPAddress(str(ipaddr), flags=netaddr.INET_PTON) - - if ip_version is not None: - if ip.version != ip_version: - return _('invalid IP address version (is %(value)d, must be %(required_value)d)!') \ - % dict(value=ip.version, required_value=ip_version) - except (netaddr.AddrFormatError, ValueError): - return _('invalid IP address format') - return None def _validate_ip4addr(ugettext, ipaddr): - return _validate_ipaddr(ugettext, ipaddr, 4) + return ipaddr_validator(ugettext, ipaddr, 4) def _validate_ip6addr(ugettext, ipaddr): - return _validate_ipaddr(ugettext, ipaddr, 6) + return ipaddr_validator(ugettext, ipaddr, 6) def _validate_ipnet(ugettext, ipnet): try: @@ -457,24 +448,6 @@ def _normalize_bind_aci(bind_acis): acis += u';' return acis -def _validate_bind_forwarder(ugettext, forwarder): - ip_address, sep, port = forwarder.partition(u' port ') - - ip_address_validation = _validate_ipaddr(ugettext, ip_address) - - if ip_address_validation is not None: - return ip_address_validation - - if sep: - try: - port = int(port) - if port < 0 or port > 65535: - raise ValueError() - except ValueError: - return _('%(port)s is not a valid port' % dict(port=port)) - - return None - def _validate_nsec3param_record(ugettext, value): _nsec3param_pattern = (r'^(?P<alg>\d+) (?P<flags>\d+) (?P<iter>\d+) ' r'(?P<salt>([0-9a-fA-F]{2})+|-)$') @@ -2001,7 +1974,7 @@ class DNSZoneBase(LDAPObject): attribute=True, ), Str('idnsforwarders*', - _validate_bind_forwarder, + validate_bind_forwarder, cli_name='forwarder', label=_('Zone forwarders'), doc=_('Per-zone forwarders. A custom port can be specified ' @@ -4043,7 +4016,7 @@ class dnsconfig(LDAPObject): takes_params = ( Str('idnsforwarders*', - _validate_bind_forwarder, + validate_bind_forwarder, cli_name='forwarder', label=_('Global forwarders'), doc=_('Global forwarders. A custom port can be specified for each ' diff --git a/ipaserver/plugins/dnsserver.py b/ipaserver/plugins/dnsserver.py new file mode 100644 index 000000000..f22d6943e --- /dev/null +++ b/ipaserver/plugins/dnsserver.py @@ -0,0 +1,183 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# + +from __future__ import absolute_import + +from ipalib import ( + _, + ngettext, + api, + DNSNameParam, + Str, + StrEnum, +) +from ipalib.frontend import Local +from ipalib.plugable import Registry +from ipalib.util import ( + normalize_hostname, + hostname_validator, + validate_bind_forwarder, +) +from ipaserver.plugins.baseldap import ( + LDAPObject, + LDAPRetrieve, + LDAPUpdate, + LDAPSearch, + LDAPCreate, + LDAPDelete, +) + + +__doc__ = _(""" +DNS server configuration +""") + _(""" +Manipulate DNS server configuration +""") + _(""" +EXAMPLES: +""") + _(""" + Show configuration of a specific DNS server: + ipa dnsserver-show +""") + _(""" + Update configuration of a specific DNS server: + ipa dnsserver-mod +""") + + +register = Registry() + +dnsserver_object_class = ['top', 'idnsServerConfigObject'] + +@register() +class dnsserver(LDAPObject): + """ + DNS Servers + """ + container_dn = api.env.container_dnsservers + object_name = _('DNS server') + object_name_plural = _('DNS servers') + object_class = dnsserver_object_class + default_attributes = [ + 'idnsServerId', + 'idnsSOAmName', + 'idnsForwarders', + 'idnsForwardPolicy', + ] + label = _('DNS Servers') + label_singular = _('DNS Server') + + permission_filter_objectclasses = ['idnsServerConfigObject'] + + managed_permissions = { + 'System: Read DNS Servers Configuration': { + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', + 'idnsServerId', + 'idnsSOAmName', + 'idnsForwarders', + 'idnsForwardPolicy', + 'idnsSubstitutionVariable', + }, + 'ipapermlocation': api.env.basedn, + 'default_privileges': { + 'DNS Servers', + 'DNS Administrators' + }, + }, + 'System: Modify DNS Servers Configuration': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'idnsSOAmName', + 'idnsForwarders', + 'idnsForwardPolicy', + 'idnsSubstitutionVariable', + }, + 'ipapermlocation': api.env.basedn, + 'default_privileges': {'DNS Administrators'}, + }, + } + + takes_params = ( + Str( + 'idnsserverid', + hostname_validator, + cli_name='hostname', + primary_key=True, + label=_('Server name'), + doc=_('DNS Server name'), + normalizer=normalize_hostname, + ), + DNSNameParam( + 'idnssoamname?', + cli_name='soa_mname_override', + label=_('SOA mname override'), + doc=_('SOA mname (authoritative server) override'), + ), + Str( + 'idnsforwarders*', + validate_bind_forwarder, + cli_name='forwarder', + label=_('Forwarders'), + doc=_( + 'Per-server forwarders. A custom port can be specified ' + 'for each forwarder using a standard format ' + '"IP_ADDRESS port PORT"' + ), + ), + StrEnum( + 'idnsforwardpolicy?', + cli_name='forward_policy', + label=_('Forward policy'), + doc=_( + 'Per-server conditional forwarding policy. Set to "none" to ' + 'disable forwarding to global forwarder for this zone. In ' + 'that case, conditional zone forwarders are disregarded.' + ), + values=(u'only', u'first', u'none'), + ), + ) + + +@register() +class dnsserver_mod(LDAPUpdate): + __doc__ = _('Modify DNS server configuration') + + msg_summary = _('Modified DNS server "%(value)s"') + + +@register() +class dnsserver_find(LDAPSearch): + __doc__ = _('Search for DNS servers.') + + msg_summary = ngettext( + '%(count)d DNS server matched', + '%(count)d DNS servers matched', 0 + ) + + +@register() +class dnsserver_show(LDAPRetrieve): + __doc__=_('Display configuration of a DNS server.') + + +@register() +class dnsserver_add(LDAPCreate, Local): + """ + Only for internal use, this is not part of public API on purpose. + Be careful in future this will be transformed to public API call + """ + __doc__ = _('Add a new DNS server.') + + msg_summary = _('Added new DNS server "%(value)s"') + + +@register() +class dnsserver_del(LDAPDelete, Local): + """ + Only for internal use, this is not part of public API on purpose. + Be careful in future this will be transformed to public API call + """ + __doc__ = _('Delete a DNS server') + + msg_summary = _('Deleted DNS server "%(value)s"') diff --git a/ipaserver/plugins/host.py b/ipaserver/plugins/host.py index e59e0fa93..15805a3d2 100644 --- a/ipaserver/plugins/host.py +++ b/ipaserver/plugins/host.py @@ -44,10 +44,13 @@ from ipalib import x509 from ipalib import output from ipalib.request import context from ipalib.util import (normalize_sshpubkey, validate_sshpubkey_no_options, - convert_sshpubkey_post, validate_hostname, + convert_sshpubkey_post, add_sshpubkey_to_attrs_pre, remove_sshpubkey_from_output_post, - remove_sshpubkey_from_output_list_post) + remove_sshpubkey_from_output_list_post, + normalize_hostname, + hostname_validator, +) from ipapython.ipautil import ipa_generate_password, CheckedIPAddress from ipapython.dnsutil import DNSName from ipapython.ssh import SSHPublicKey @@ -271,23 +274,6 @@ def validate_ipaddr(ugettext, ipaddr): return None -def normalize_hostname(hostname): - """Use common fqdn form without the trailing dot""" - if hostname.endswith(u'.'): - hostname = hostname[:-1] - hostname = hostname.lower() - return hostname - - -def _hostname_validator(ugettext, value): - try: - validate_hostname(value) - except ValueError as e: - return _('invalid domain-name: %s') % unicode(e) - - return None - - @register() class host(LDAPObject): """ @@ -465,7 +451,7 @@ class host(LDAPObject): label_singular = _('Host') takes_params = ( - Str('fqdn', _hostname_validator, + Str('fqdn', hostname_validator, cli_name='hostname', label=_('Host name'), primary_key=True, @@ -734,7 +720,7 @@ class host_del(LDAPDelete): def pre_callback(self, ldap, dn, *keys, **options): assert isinstance(dn, DN) # If we aren't given a fqdn, find it - if _hostname_validator(None, keys[-1]) is not None: + if hostname_validator(None, keys[-1]) is not None: hostentry = api.Command['host_show'](keys[-1])['result'] fqdn = hostentry['fqdn'][0] else: @@ -1093,7 +1079,7 @@ class host_disable(LDAPQuery): ldap = self.obj.backend # If we aren't given a fqdn, find it - if _hostname_validator(None, keys[-1]) is not None: + if hostname_validator(None, keys[-1]) is not None: hostentry = api.Command['host_show'](keys[-1])['result'] fqdn = hostentry['fqdn'][0] else: diff --git a/ipaserver/plugins/server.py b/ipaserver/plugins/server.py index 3b58c6b2f..344756f00 100644 --- a/ipaserver/plugins/server.py +++ b/ipaserver/plugins/server.py @@ -237,6 +237,7 @@ class server_mod(LDAPUpdate): if not result.get('value'): self.add_message(messages.AutomaticDNSRecordsUpdateFailed()) self.obj.convert_location(entry_attrs, **options) + return dn |