Merge commit '78a6434e323ebc357472745d97627065ae5b8169'

This commit is contained in:
Timo Aaltonen 2016-08-30 01:27:49 +03:00
commit 0b2e6a526a
122 changed files with 40197 additions and 3130 deletions

View File

@ -51,7 +51,7 @@ aci: (targetattr = "cospriority")(targetfilter = "(objectclass=costemplate)")(ve
dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || krbpwdpolicyreference || modifytimestamp || objectclass")(targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Read Group Password Policy costemplate";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy costemplate,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: dc=ipa,dc=example
aci: (targetattr = "createtimestamp || entryusn || idnsallowsyncptr || idnsforwarders || idnsforwardpolicy || idnspersistentsearch || idnszonerefresh || modifytimestamp || objectclass")(target = "ldap:///cn=dns,dc=ipa,dc=example")(targetfilter = "(objectclass=idnsConfigObject)")(version 3.0;acl "permission:System: Read DNS Configuration";allow (read) groupdn = "ldap:///cn=System: Read DNS Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetattr = "createtimestamp || entryusn || idnsallowsyncptr || idnsforwarders || idnsforwardpolicy || idnspersistentsearch || idnszonerefresh || ipadnsversion || modifytimestamp || objectclass")(target = "ldap:///cn=dns,dc=ipa,dc=example")(targetfilter = "(objectclass=idnsConfigObject)")(version 3.0;acl "permission:System: Read DNS Configuration";allow (read) groupdn = "ldap:///cn=System: Read DNS Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";)
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

View File

@ -4,7 +4,7 @@
include VERSION
SUBDIRS=asn1 daemons install ipapython ipalib
CLIENTDIRS=ipapython client asn1
CLIENTDIRS=ipapython ipalib client asn1
CLIENTPYDIRS=ipaclient ipaplatform
PRJ_PREFIX=freeipa
@ -87,6 +87,11 @@ check: bootstrap-autogen server tests
(cd $$subdir && $(MAKE) check) || exit 1; \
done
client-check: client-autogen
@for subdir in $(CLIENTDIRS); do \
(cd $$subdir && $(MAKE) check) || exit 1; \
done
bootstrap-autogen: version-update client-autogen
@echo "Building IPA $(IPA_VERSION)"
cd asn1; if [ ! -e Makefile ]; then ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); fi
@ -188,7 +193,7 @@ version-update: release-update
fi
if [ "$(SKIP_API_VERSION_CHECK)" != "yes" ]; then \
./makeapi --validate; \
./makeapi --validate && \
./makeaci --validate; \
fi

View File

@ -21,7 +21,7 @@
########################################################
IPA_VERSION_MAJOR=4
IPA_VERSION_MINOR=3
IPA_VERSION_RELEASE=1
IPA_VERSION_RELEASE=2
########################################################
# For 'alpha' releases the version will be #

View File

@ -45,7 +45,7 @@ try:
import ipaclient.ntpconf
from ipapython.ipautil import (
run, user_input, CalledProcessError, file_exists, dir_exists,
realm_to_suffix)
realm_to_suffix, is_fips_enabled)
from ipaplatform.tasks import tasks
from ipaplatform import services
from ipaplatform.paths import paths
@ -54,7 +54,8 @@ try:
from ipapython.config import IPAOptionParser
from ipalib import api, errors
from ipalib import x509, certstore
from ipalib.util import verify_host_resolvable
from ipalib.util import (
normalize_hostname, validate_domain_name, verify_host_resolvable)
from ipalib.constants import CACERT
from ipapython.dn import DN
from ipapython.ssh import SSHPublicKey
@ -224,6 +225,13 @@ def parse_options():
if (options.server and not options.domain):
parser.error("--server cannot be used without providing --domain")
if options.domain:
try:
validate_domain_name(options.domain)
except ValueError as ex:
parser.error("invalid domain name: %s" % ex)
options.domain = normalize_hostname(options.domain)
if options.force_ntpd and not options.conf_ntp:
parser.error("--force-ntpd cannot be used together with --no-ntp")
@ -1750,12 +1758,16 @@ def get_server_connection_interface(server):
def client_dns(server, hostname, options):
try:
verify_host_resolvable(hostname, root_logger)
verify_host_resolvable(hostname)
dns_ok = True
except errors.DNSNotARecordError:
root_logger.warning("Hostname (%s) does not have A/AAAA record.",
hostname)
dns_ok = False
except errors.DNSResolverError as ex:
root_logger.warning("DNS resolution for hostname %s failed: %s",
hostname, ex)
dns_ok = False
if (options.dns_updates or options.all_ip_addresses or options.ip_addresses
or not dns_ok):
@ -3052,6 +3064,9 @@ def main():
if not os.getegid() == 0:
sys.exit("\nYou must be root to run ipa-client-install.\n")
if is_fips_enabled():
sys.exit("Installing IPA client in FIPS mode is not supported")
tasks.check_selinux_status()
logging_setup(options)
root_logger.debug(

View File

@ -176,6 +176,17 @@ valid for the IPA domain.
.TP
\fB\-\-request\-cert\fR
Request certificate for the machine. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host".
Using this option requires that D-Bus is properly configured or not configured
at all. In environment where this condition is not met (e.g. anaconda kickstart
chroot environment) set the system bus address to /dev/null to enable
workaround in ipa-client-install.
# env DBUS_SYSTEM_BUS_ADDRESS=unix:path=/dev/null ipa-client-install --request-cert
Note that requesting the certificate when certmonger is not running only
creates tracking request and the certmonger service must be started to be able
to track certificates.
.TP
\fB\-\-automount\-location\fR=\fILOCATION\fR
Configure automount by running ipa\-client\-automount(1) with \fILOCATION\fR as

View File

@ -42,7 +42,7 @@ Source0: freeipa-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
%if ! %{ONLY_CLIENT}
BuildRequires: 389-ds-base-devel >= 1.3.4.4
BuildRequires: 389-ds-base-devel >= 1.3.5
BuildRequires: svrcore-devel
BuildRequires: policycoreutils >= 2.1.12-5
BuildRequires: systemd-units
@ -130,7 +130,7 @@ Requires: %{name}-client = %{version}-%{release}
Requires: %{name}-admintools = %{version}-%{release}
Requires: %{name}-common = %{version}-%{release}
Requires: python2-ipaserver = %{version}-%{release}
Requires: 389-ds-base >= 1.3.4.6
Requires: 389-ds-base >= 1.3.5
Requires: openldap-clients > 2.4.35-4
Requires: nss >= 3.14.3-12.0
Requires: nss-tools >= 3.14.3-12.0
@ -140,7 +140,7 @@ Requires: cyrus-sasl-gssapi%{?_isa}
Requires: ntp
Requires: httpd >= 2.4.6-6
Requires: mod_wsgi
Requires: mod_auth_gssapi >= 1.3.0-2
Requires: mod_auth_gssapi >= 1.4.0
Requires: mod_nss >= 1.0.8-26
Requires: python-ldap >= 2.4.15
Requires: python-gssapi >= 1.1.2
@ -154,15 +154,15 @@ Requires(post): systemd-units
Requires: selinux-policy >= %{selinux_policy_version}
Requires(post): selinux-policy-base >= %{selinux_policy_version}
Requires: slapi-nis >= 0.55-1
Requires: pki-ca >= 10.2.6-13
Requires: pki-kra >= 10.2.6-13
Requires: pki-ca >= 10.2.6-19
Requires: pki-kra >= 10.2.6-19
Requires(preun): python systemd-units
Requires(postun): python systemd-units
Requires: zip
Requires: policycoreutils >= 2.1.12-5
Requires: tar
Requires(pre): certmonger >= 0.78
Requires(pre): 389-ds-base >= 1.3.4.6
Requires(pre): 389-ds-base >= 1.3.5
Requires: fontawesome-fonts
Requires: open-sans-fonts
Requires: openssl
@ -501,6 +501,7 @@ Requires: python-ldap >= 2.4.15
Requires: python-requests
Requires: python-custodia
Requires: python-dns >= 1.11.1
Requires: pyusb
Conflicts: %{alt_name}-python < %{version}
@ -550,6 +551,7 @@ Requires: python3-pyldap >= 2.4.15
Requires: python3-custodia
Requires: python3-requests
Requires: python3-dns >= 1.11.1
Requires: python3-pyusb
%description -n python3-ipalib
IPA is an integrated solution to provide centrally managed Identity (users,
@ -603,6 +605,7 @@ Requires: python-polib
Requires: python-pytest-multihost >= 0.5
Requires: python-pytest-sourceorder
Requires: ldns-utils
Requires: python-sssdconfig
Provides: %{alt_name}-tests = %{version}
Conflicts: %{alt_name}-tests
@ -634,6 +637,7 @@ Requires: python3-polib
Requires: python3-pytest-multihost >= 0.5
Requires: python3-pytest-sourceorder
Requires: ldns-utils
Requires: python3-sssdconfig
%description -n python3-ipatests
IPA is an integrated solution to provide centrally managed Identity (users,
@ -682,7 +686,11 @@ make IPA_VERSION_IS_GIT_SNAPSHOT=no %{?_smp_mflags} client
%check
%if ! %{ONLY_CLIENT}
make %{?_smp_mflags} check VERBOSE=yes
%else
make %{?_smp_mflags} client-check VERBOSE=yes
%endif # ONLY_CLIENT
%install
@ -731,6 +739,11 @@ make client-install DESTDIR=%{buildroot}
(cd ipaclient && %{__python3} setup.py install --root %{buildroot})
%endif # with_python3
# Switch shebang of /usr/bin/ipa
# XXX: ipa cli is not stable enough for enabling py3 support, keep it in py2
# in any case
sed -i -e'1s/python\(3\|$\)/python2/' %{buildroot}%{_bindir}/ipa
%find_lang %{gettext_domain}
mkdir -p %{buildroot}%{_usr}/share/ipa
@ -1115,6 +1128,7 @@ fi
%defattr(-,root,root,-)
%doc README Contributors.txt
%license COPYING
%{python_sitelib}/freeipa-*.egg-info
%dir %{python_sitelib}/ipaserver
%dir %{python_sitelib}/ipaserver/install
%dir %{python_sitelib}/ipaserver/install/plugins
@ -1353,7 +1367,6 @@ fi
%{python_sitelib}/ipaplatform/*
%{python_sitelib}/ipapython-*.egg-info
%{python_sitelib}/ipalib-*.egg-info
%{python_sitelib}/freeipa-*.egg-info
%{python_sitelib}/ipaplatform-*.egg-info

View File

@ -1,5 +1,5 @@
#
# VERSION 19 - DO NOT REMOVE THIS LINE
# VERSION 21 - DO NOT REMOVE THIS LINE
#
# This file may be overwritten on upgrades.
#
@ -65,12 +65,15 @@ WSGIScriptReloading Off
GssapiCredStore keytab:/etc/httpd/conf/ipa.keytab
GssapiCredStore client_keytab:/etc/httpd/conf/ipa.keytab
GssapiDelegCcacheDir /var/run/httpd/ipa/clientcaches
GssapiDelegCcacheUnique On
GssapiUseS4U2Proxy on
GssapiAllowedMech krb5
Require valid-user
ErrorDocument 401 /ipa/errors/unauthorized.html
WSGIProcessGroup ipa
WSGIApplicationGroup ipa
Header always append X-Frame-Options DENY
Header always append Content-Security-Policy "frame-ancestors 'none'"
</Location>
# Turn off Apache authentication for sessions

View File

@ -8,19 +8,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-07-03 04:37-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Bengali (India) (http://www.transifex.com/projects/p/freeipa/"
"language/bn_IN/)\n"
"Language: bn-IN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
msgid "Passwords do not match"
msgstr "পাসওয়ার্ড দুটি মিলছে না"

View File

@ -7,19 +7,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Catalan (http://www.transifex.com/projects/p/freeipa/language/"
"ca/)\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
msgid "Passwords do not match"
msgstr "Les contrasenyes no coincideixen"
@ -366,6 +365,9 @@ msgstr "Exclusiu"
msgid "Expression"
msgstr "Expressió"
msgid "Anyone"
msgstr "Qualsevol"
msgid "Affiliation Changed"
msgstr "L'afiliació ha canviat"
@ -441,9 +443,6 @@ msgstr "Extern"
msgid "Normal"
msgstr "Normal"
msgid "Anyone"
msgstr "Qualsevol"
msgid "Who"
msgstr "Qui"

View File

@ -4,23 +4,22 @@
#
# Translators:
# Petr Viktorin <encukou@gmail.com>, 2013
# Josef Hruška <josef.hruska@upcmail.cz>, 2015. #zanata
# Josef Hruška <hrusjos@gmail.com>, 2015. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2015-06-29 01:17-0400\n"
"Last-Translator: Josef Hruška <josef.hruska@upcmail.cz>\n"
"Last-Translator: Josef Hruška <hrusjos@gmail.com>\n"
"Language-Team: Czech (http://www.transifex.com/projects/p/freeipa/language/"
"cs/)\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "

File diff suppressed because it is too large Load Diff

View File

@ -15,19 +15,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/freeipa/language/"
"es/)\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "
@ -1174,15 +1173,9 @@ msgstr ""
msgid "Time Limit"
msgstr "Tiempo límite"
msgid "Time limit of search in seconds"
msgstr "Tiempo máximo de búsqueda en segundos"
msgid "Size Limit"
msgstr "Tamaño límite"
msgid "Maximum number of entries returned"
msgstr "Cantidad máxima de entradas obtenidas"
msgid "Kerberos keys available"
msgstr "Claves Kerberos disponibles"
@ -1420,9 +1413,6 @@ msgstr "Error"
msgid "Status"
msgstr "Estatus"
msgid "searchtimelimit must be -1 or > 1."
msgstr "searchtimelimit debe ser -1 o&gt; 1."
msgid "configuration options"
msgstr "opciones de configuración"
@ -1456,18 +1446,9 @@ msgstr "Dominio de correo electrónico por defecto"
msgid "Search time limit"
msgstr "Buscar límite de tiempo"
msgid ""
"Maximum amount of time (seconds) for a search (> 0, or -1 for unlimited)"
msgstr ""
"Máxima cantidad de tiempo (segundos) parab una búsqueda (> 0, o -1 para "
"ilimitado)"
msgid "Search size limit"
msgstr "Límite del tamaño de la búsqueda"
msgid "Maximum number of records to search (-1 is unlimited)"
msgstr "Máximo número de registros a buscar (-1 es ilimitado)"
msgid "User search fields"
msgstr "Campos de búsqueda de usuario"
@ -1597,10 +1578,6 @@ msgstr "formato de dirección no válido"
msgid "%(port)s is not a valid port"
msgstr "%(port)s no es un puerto válido"
#, python-format
msgid "DNS reverse zone for IP address %(addr)s not found"
msgstr "Zona invertida DNS para dirección IP %(addr)s no encontrada"
#, python-format
msgid "DNS zone %(zone)s not found"
msgstr "Zona DNS %(zone)s no encontrado"
@ -1832,15 +1809,15 @@ msgstr "Permitir transferencia"
msgid "Create new DNS zone (SOA record)."
msgstr "Crear nueva zona DNS (registro SOA)."
msgid "Force"
msgstr "Forzar"
msgid "Delete DNS zone (SOA record)."
msgstr "Borrar zona DNS (registro SOA)."
msgid "Modify DNS zone (SOA record)."
msgstr "Modificar zona DNS (registro SOA)."
msgid "Force"
msgstr "Forzar"
msgid "Forward zones only"
msgstr "Zonas de reenvío sólo"
@ -1937,9 +1914,6 @@ msgstr "Mostrar un recurso DNS."
msgid "Search for DNS resources."
msgstr "Buscar recursos DNS."
msgid "Resolve a host name in DNS."
msgstr "Resolver un nombre de host en DNS."
#, python-format
msgid "Found '%(value)s'"
msgstr "Ha sido encontrado '%(value)s'"
@ -2888,6 +2862,27 @@ msgstr "Directo"
msgid "Indirect"
msgstr "Indirecto"
msgid "Any Host"
msgstr "Cualquier host"
msgid "Any Service"
msgstr "Cualquier servicio"
msgid "Anyone"
msgstr "Cualquiera"
msgid "Rule status"
msgstr "Estatus de reglas"
msgid "Specified Hosts and Groups"
msgstr "Hosts y grupos especificados"
msgid "Specified Services and Groups"
msgstr "Servicios y grupos especificados"
msgid "Specified Users and Groups"
msgstr "Usuarios y grupos específicos"
msgid "AA Compromise"
msgstr "AA transacción"
@ -3071,33 +3066,12 @@ msgstr "POSIX"
msgid "Group Type"
msgstr "Tipo de grupo"
msgid "Any Host"
msgstr "Cualquier host"
msgid "Any Service"
msgstr "Cualquier servicio"
msgid "Anyone"
msgstr "Cualquiera"
msgid "Accessing"
msgstr "Acceso"
msgid "Rule status"
msgstr "Estatus de reglas"
msgid "Via Service"
msgstr "Vía de servicio"
msgid "Specified Hosts and Groups"
msgstr "Hosts y grupos especificados"
msgid "Specified Services and Groups"
msgstr "Servicios y grupos especificados"
msgid "Specified Users and Groups"
msgstr "Usuarios y grupos específicos"
msgid "Who"
msgstr "¿Quién?"
@ -4830,10 +4804,6 @@ msgstr "el dominio no está configurado"
msgid "SID is not valid"
msgstr "el SID no es válido"
#, python-format
msgid "Unable to communicate with CMS (%s)"
msgstr "No es posible comunicarse con CMS (%s)"
msgid "The hostname to register as"
msgstr "El nombre del equipo a ser registrado como"

View File

@ -7,19 +7,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Basque (http://www.transifex.com/projects/p/freeipa/language/"
"eu/)\n"
"Language: eu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, c-format
msgid "Passwords do not match!"

File diff suppressed because it is too large Load Diff

View File

@ -7,19 +7,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-07-04 05:21-0400\n"
"Last-Translator: solomonsunder <solomonsunder@gmail.com>\n"
"Language-Team: Hindi (http://www.transifex.com/projects/p/freeipa/language/"
"hi/)\n"
"Language: hi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "

View File

@ -7,19 +7,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Hungarian (http://www.transifex.com/projects/p/freeipa/"
"language/hu/)\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
msgid "Passwords do not match"
msgstr "A jelszavak nem egyeznek meg"

View File

@ -8,19 +8,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Indonesian (http://www.transifex.com/projects/p/freeipa/"
"language/id/)\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "
@ -273,7 +272,3 @@ msgstr "Pengguna \"%(value)s\" telah dihapus"
#, python-format
msgid "Modified user \"%(value)s\""
msgstr "Pengguna \"%(value)s\" telah dimodifikasi"
#, python-format
msgid "Unable to communicate with CMS (%s)"
msgstr "Tidak dapat berkomunikasi dengan CMS (%s)"

File diff suppressed because it is too large Load Diff

View File

@ -8,19 +8,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Japanese (http://www.transifex.com/projects/p/freeipa/"
"language/ja/)\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "

View File

@ -8,19 +8,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Kannada (http://www.transifex.com/projects/p/freeipa/language/"
"kn/)\n"
"Language: kn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "
@ -695,7 +694,3 @@ msgstr "\"%(value)s\" ಬಳಕೆದಾರನನ್ನು ಅಳಿಸಲಾಗ
#, python-format
msgid "Modified user \"%(value)s\""
msgstr "\"%(value)s\" ಬಳಕೆದಾರನನ್ನು ಬದಲಾಯಿಸಲಾಗಿದೆ"
#, python-format
msgid "Unable to communicate with CMS (%s)"
msgstr "CMS (%s) ಜೊತೆ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ"

16299
install/po/mr.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,19 +7,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-07-03 04:37-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Dutch (http://www.transifex.com/projects/p/freeipa/language/"
"nl/)\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, c-format
msgid "Passwords do not match!"

View File

@ -6,23 +6,23 @@
# jdennis <jdennis@redhat.com>, 2011
# Mateusz Marzantowicz <mmarzantowicz@osdf.com.pl>, 2013
# Piotr Drąg <piotrdrag@gmail.com>, 2010,2013
# Piotr Drąg <pdrag@aviary.pl>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2016-05-14 05:41-0400\n"
"Last-Translator: Piotr Drąg <pdrag@aviary.pl>\n"
"Language-Team: Polish (http://www.transifex.com/projects/p/freeipa/language/"
"pl/)\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "
@ -436,15 +436,9 @@ msgstr "Liczba usuniętych elementów"
msgid "Time Limit"
msgstr "Ograniczenie czasu"
msgid "Time limit of search in seconds"
msgstr "Ograniczenie czasu wyszukiwania w sekundach"
msgid "Size Limit"
msgstr "Ograniczenie rozmiaru"
msgid "Maximum number of entries returned"
msgstr "Maksymalna liczba zwróconych wpisów"
msgid "User"
msgstr "Użytkownik"
@ -1235,10 +1229,6 @@ msgstr "Wyłączono konto użytkownika \"%(value)s\""
msgid "Enabled user account \"%(value)s\""
msgstr "Włączono konto użytkownika \"%(value)s\""
#, python-format
msgid "Unable to communicate with CMS (%s)"
msgstr "Nie można komunikować się z CMS (%s)"
msgid "The hostname to register as"
msgstr "Nazwa komputera, pod jaką zarejestrować"
@ -1315,8 +1305,8 @@ msgstr "Nazwa serwera"
msgid "The principal to get a keytab for (ex: ftp/ftp.example.com@EXAMPLE.COM)"
msgstr ""
"Naczelnik, dla którego uzyskać tablicę kluczy (np.: ftp/ftp.przykład."
"pl@PRZYKŁAD.PL)"
"Naczelnik, dla którego uzyskać tablicę kluczy (np.: ftp/ftp.example."
"com@EXAMPLE.COM)"
msgid "Kerberos Service Principal Name"
msgstr "Nazwa naczelnika usługi Kerberos"

16299
install/po/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,23 +8,23 @@
# Azamat Hackimov <azamat.hackimov@gmail.com>, 2012
# jdennis <jdennis@redhat.com>, 2011
# Alex <alexgluck@bk.ru>, 2015. #zanata
# Martin Bašti <mbasti@redhat.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2015-08-19 10:07-0400\n"
"Last-Translator: Alex <alexgluck@bk.ru>\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2016-06-13 04:55-0400\n"
"Last-Translator: Martin Bašti <mbasti@redhat.com>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/freeipa/language/"
"ru/)\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "
@ -691,13 +691,6 @@ msgstr "Отобразить расположения автомонтирова
msgid "Search for an automount location."
msgstr "Поиск для расположения автомонтирования."
#, python-format
msgid "%(count)d automount location matched"
msgid_plural "%(count)d automount locations matched"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "Generate automount files for a specific location."
msgstr "Сгенерировать файлы автомонтирования для особого расположения."
@ -843,15 +836,9 @@ msgstr ""
msgid "Time Limit"
msgstr "Ограничение по времени"
msgid "Time limit of search in seconds"
msgstr "Ограничение времени поиска в секундах"
msgid "Size Limit"
msgstr "Ограничение по размеру"
msgid "Maximum number of entries returned"
msgstr "Максимальное количество совпадений возвращено"
msgid "Kerberos keys available"
msgstr "Kerberos ключей доступно"
@ -1067,13 +1054,6 @@ msgstr "Изменён CA ACL \"%(value)s\""
msgid "Search for CA ACLs."
msgstr "Поиск по CA ACLs."
#, python-format
msgid "%(count)d CA ACL matched"
msgid_plural "%(count)d CA ACLs matched"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "Display the properties of a CA ACL."
msgstr "Показать свойства of a CA ACL."
@ -1228,10 +1208,6 @@ msgstr "Серийный номер"
msgid "Serial number (hex)"
msgstr "Серийный номер (hex)"
#, python-format
msgid "extension %s is forbidden"
msgstr "расширение %s запрещено"
msgid "RFC822Name does not match any of user's email addresses"
msgstr "RFC822 Имя не совпадает ни с одним пользователем электронной почты"
@ -1861,10 +1837,6 @@ msgstr "неверный открытый ключ SSH"
msgid "objectclass %s not found"
msgstr "класс объектов %s не найден"
#, python-format
msgid "Unable to communicate with CMS (%s)"
msgstr "Невозможно связаться с CMS (%s)"
msgid "Hardware platform of the host (e.g. Lenovo T61)"
msgstr "Аппаратная платформа узла (например, Lenovo T61)"

View File

@ -7,19 +7,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2014-09-23 08:41-0400\n"
"Last-Translator: Petr Viktorin <encukou@gmail.com>\n"
"Language-Team: Tajik (http://www.transifex.com/projects/p/freeipa/language/"
"tg/)\n"
"Language: tg\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, c-format
msgid "Passwords do not match!"

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,18 @@
msgid ""
msgstr ""
"Project-Id-Version: ipa\n"
"Report-Msgid-Bugs-To: https://hosted.fedoraproject.org/projects/freeipa/"
"newticket\n"
"POT-Creation-Date: 2015-06-24 13:48+0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Report-Msgid-Bugs-To: https://fedorahosted.org/freeipa/newticket\n"
"POT-Creation-Date: 2016-05-31 15:43+0200\n"
"PO-Revision-Date: 2015-03-12 12:06-0400\n"
"Last-Translator: Martin Liu <martin@aws-faq.com>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/freeipa/"
"language/zh_CN/)\n"
"Language: zh-CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Zanata 3.7.3\n"
"X-Generator: Zanata 3.8.4\n"
#, python-format
msgid "Enter %(label)s again to verify: "

View File

@ -70,9 +70,11 @@ attributeTypes: ( 2.16.840.1.113730.3.8.5.25 NAME 'idnsSecKeyRevoke' DESC 'DNSKE
attributeTypes: ( 2.16.840.1.113730.3.8.5.26 NAME 'idnsSecKeySep' DESC 'DNSKEY SEP flag (equivalent to bit 15): RFC 4035' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.1' )
attributeTypes: ( 2.16.840.1.113730.3.8.5.27 NAME 'idnsSecAlgorithm' DESC 'DNSKEY algorithm: string used as mnemonic' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'IPA v4.1' )
attributeTypes: ( 2.16.840.1.113730.3.8.5.28 NAME 'idnsSecKeyRef' DESC 'PKCS#11 URI of the key' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.1' )
attributeTypes: ( 2.16.840.1.113730.3.8.11.74 NAME 'ipaDNSVersion' DESC 'IPA DNS data version' EQUALITY integerMatch ORDERING integerOrderingMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'IPA v4.3' )
objectClasses: ( 2.16.840.1.113730.3.8.6.0 NAME 'idnsRecord' DESC 'dns Record, usually a host' SUP top STRUCTURAL MUST idnsName MAY ( cn $ idnsAllowDynUpdate $ dNSTTL $ dNSClass $ aRecord $ aAAARecord $ a6Record $ nSRecord $ cNAMERecord $ pTRRecord $ sRVRecord $ tXTRecord $ mXRecord $ mDRecord $ hInfoRecord $ mInfoRecord $ aFSDBRecord $ SigRecord $ KeyRecord $ LocRecord $ nXTRecord $ nAPTRRecord $ kXRecord $ certRecord $ dNameRecord $ dSRecord $ sSHFPRecord $ rRSIGRecord $ nSECRecord $ DLVRecord $ TLSARecord $ UnknownRecord $ RPRecord $ APLRecord $ IPSECKEYRecord $ DHCIDRecord $ HIPRecord $ SPFRecord ) )
objectClasses: ( 2.16.840.1.113730.3.8.6.1 NAME 'idnsZone' DESC 'Zone class' SUP idnsRecord STRUCTURAL MUST ( idnsZoneActive $ idnsSOAmName $ idnsSOArName $ idnsSOAserial $ idnsSOArefresh $ idnsSOAretry $ idnsSOAexpire $ idnsSOAminimum ) MAY ( idnsUpdatePolicy $ idnsAllowQuery $ idnsAllowTransfer $ idnsAllowSyncPTR $ idnsForwardPolicy $ idnsForwarders $ idnsSecInlineSigning $ nSEC3PARAMRecord ) )
objectClasses: ( 2.16.840.1.113730.3.8.6.2 NAME 'idnsConfigObject' DESC 'DNS global config options' STRUCTURAL MAY ( idnsForwardPolicy $ idnsForwarders $ idnsAllowSyncPTR $ idnsZoneRefresh $ idnsPersistentSearch ) )
objectClasses: ( 2.16.840.1.113730.3.8.12.18 NAME 'ipaDNSZone' SUP top AUXILIARY MUST idnsName MAY managedBy X-ORIGIN 'IPA v3' )
objectClasses: ( 2.16.840.1.113730.3.8.6.3 NAME 'idnsForwardZone' DESC 'Forward Zone class' SUP top STRUCTURAL MUST ( idnsName $ idnsZoneActive ) MAY ( idnsForwarders $ idnsForwardPolicy ) )
objectClasses: ( 2.16.840.1.113730.3.8.6.4 NAME 'idnsSecKey' DESC 'DNSSEC key metadata' STRUCTURAL MUST ( idnsSecKeyRef $ idnsSecKeyCreated $ idnsSecAlgorithm ) MAY ( idnsSecKeyPublish $ idnsSecKeyActivate $ idnsSecKeyInactive $ idnsSecKeyDelete $ idnsSecKeyZone $ idnsSecKeyRevoke $ idnsSecKeySep $ cn ) X-ORIGIN 'IPA v4.1' )
objectClasses: ( 2.16.840.1.113730.3.8.12.36 NAME 'ipaDNSContainer' DESC 'IPA DNS container' AUXILIARY MUST ( ipaDNSVersion ) X-ORIGIN 'IPA v4.3' )

View File

@ -8,7 +8,7 @@ options {
statistics-file "data/named_stats.txt";
memstatistics-file "data/named_mem_stats.txt";
forward first;
forward $FORWARD_POLICY;
forwarders {$FORWARDERS};
// Any host is permitted to issue recursive queries

View File

@ -3,9 +3,11 @@ changetype: add
objectClass: idnsConfigObject
objectClass: nsContainer
objectClass: ipaConfigObject
objectClass: ipaDNSContainer
objectClass: top
cn: dns
ipaConfigString: DNSVersion 1
ipaDNSVersion: 2
aci: (targetattr = "*")(version 3.0; acl "Allow read access"; allow (read,search,compare) groupdn = "ldap:///cn=Read DNS Entries,cn=permissions,cn=pbac,$SUFFIX" or userattr = "parent[0,1].managedby#GROUPDN";)
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";)

View File

@ -16,7 +16,7 @@ default:nsslapd-pluginid: schema-compat-plugin
# We need to run schema-compat pre-bind callback before
# other IPA pre-bind callbacks to make sure bind DN is
# rewritten to the original entry if needed
default:nsslapd-pluginprecedence: 49
default:nsslapd-pluginprecedence: 40
default:nsslapd-pluginversion: 0.8
default:nsslapd-pluginbetxn: on
default:nsslapd-pluginvendor: redhat.com

View File

@ -52,6 +52,9 @@ def parse_options():
parser.add_option("--auto-forwarders", dest="auto_forwarders",
action="store_true", default=False,
help="Use DNS forwarders configured in /etc/resolv.conf")
parser.add_option("--forward-policy", dest="forward_policy",
choices=("first", "only"), default=None,
help="DNS forwarding policy for global forwarders")
parser.add_option("--reverse-zone", dest="reverse_zones",
default=[], action="append", metavar="REVERSE_ZONE",
help="The reverse DNS zone to use. This option can be used multiple times")
@ -96,8 +99,11 @@ def parse_options():
parser.error("You cannot specify a --auto-reverse option together with --no-reverse")
if options.unattended:
if not options.forwarders and not options.no_forwarders:
parser.error("You must specify at least one --forwarder option or --no-forwarders option")
if (not options.forwarders
and not options.no_forwarders
and not options.auto_forwarders):
parser.error("You must specify at least one option: "
"--forwarder or --no-forwarders or --auto-forwarders")
if options.kasp_db_file and not ipautil.file_exists(options.kasp_db_file):
parser.error("File %s does not exist" % options.kasp_db_file)

View File

@ -26,7 +26,7 @@ from ipaserver.install.server import Replica
ReplicaInstall = cli.install_tool(
Replica,
command_name='ipa-replica-install',
positional_arguments='replica_file',
positional_arguments=['replica_file'],
usage='%prog [options] REPLICA_FILE',
log_file_name=paths.IPAREPLICA_INSTALL_LOG,
debug_option=True,

View File

@ -38,9 +38,8 @@ from ipaserver.install import opendnssecinstance, dnskeysyncinstance
from ipapython import version, ipaldap
from ipalib import api, errors, util
from ipalib.constants import CACERT
from ipalib.util import (create_topology_graph,
get_topology_connection_errors, has_managed_topology)
from ipapython.ipa_log_manager import *
from ipalib.util import has_managed_topology, verify_host_resolvable
from ipapython.ipa_log_manager import root_logger, standard_logging_setup
from ipapython.dn import DN
from ipapython.config import IPAOptionParser
from ipaclient import ipadiscovery
@ -69,6 +68,14 @@ commands = {
"dnanextrange-set":(2, 2, "<master fqdn> <range>", "must provide a master and ID range"),
}
# tuple of commands that work with ca tree and need Directory Manager password
dirman_passwd_req_commands = ("list-ruv", "clean-ruv", "abort-clean-ruv",
"clean-dangling-ruv")
class NoRUVsFound(Exception):
pass
def parse_options():
parser = IPAOptionParser(version=version.VERSION)
@ -362,8 +369,9 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False):
else:
thisrepl = replication.ReplicationManager(realm, host, dirman_passwd)
except Exception as e:
print("Failed to connect to server %s: %s" % (host, e))
sys.exit(1)
root_logger.debug(traceback.format_exc())
raise RuntimeError("Failed to connect to server {host}: {err}"
.format(host=host, err=e))
search_filter = '(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectclass=nstombstone))'
try:
@ -371,8 +379,8 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False):
thisrepl.db_suffix, thisrepl.conn.SCOPE_SUBTREE, search_filter,
['nsds50ruv'])
except errors.NotFound:
print("No RUV records found.")
sys.exit(0)
root_logger.debug(traceback.format_exc())
raise NoRUVsFound("No RUV records found.")
servers = []
for e in entries:
@ -389,26 +397,86 @@ def get_ruv(realm, host, dirman_passwd, nolookup=False, ca=False):
return servers
def get_ruv_both_suffixes(realm, host, dirman_passwd, verbose, nolookup=False):
"""
Get RUVs for both domain and ipaca suffixes
"""
ruvs = {}
fail_gracefully = True
try:
ruvs['ca'] = get_ruv(realm, host, dirman_passwd, nolookup, True)
except (NoRUVsFound, RuntimeError) as e:
err = "Failed to get CS-RUVs from {host}: {err}".format(host=host,
err=e)
if isinstance(e, RuntimeError):
fail_gracefully = False
if verbose:
print(err)
root_logger.debug(err)
try:
ruvs['domain'] = get_ruv(realm, host, dirman_passwd, nolookup)
except (NoRUVsFound, RuntimeError) as e:
err = "Failed to get RUVs from {host}: {err}".format(host=host, err=e)
if isinstance(e, RuntimeError):
if not fail_gracefully:
raise
if verbose:
print(err)
root_logger.debug(err)
if not ruvs.keys():
raise NoRUVsFound("No RUV records found.")
return ruvs
def list_ruv(realm, host, dirman_passwd, verbose, nolookup=False):
"""
List the Replica Update Vectors on this host to get the available
replica IDs.
"""
try:
servers = get_ruv_both_suffixes(realm, host, dirman_passwd,
verbose, nolookup)
except (NoRUVsFound, RuntimeError) as e:
print(e)
sys.exit(0 if isinstance(e, NoRUVsFound) else 1)
print('Replica Update Vectors:')
if servers.get('domain'):
for netloc, rid in servers['domain']:
print("\t{name}: {id}".format(name=netloc, id=rid))
else:
print('\tNo RUVs found.')
print('Certificate Server Replica Update Vectors:')
if servers.get('ca'):
for netloc, rid in servers['ca']:
print("\t{name}: {id}".format(name=netloc, id=rid))
else:
print('\tNo CS-RUVs found.')
servers = get_ruv(realm, host, dirman_passwd, nolookup)
for (netloc, rid) in servers:
print("%s: %s" % (netloc, rid))
def get_rid_by_host(realm, sourcehost, host, dirman_passwd, nolookup=False):
"""
Try to determine the RID by host name.
"""
servers = get_ruv(realm, sourcehost, dirman_passwd, nolookup)
try:
servers = get_ruv(realm, sourcehost, dirman_passwd, nolookup)
except RuntimeError as e:
print(e)
sys.exit(1)
except NoRUVsFound as e:
print(e)
servers = []
for (netloc, rid) in servers:
if '%s:389' % host == netloc:
return int(rid)
def clean_ruv(realm, ruv, options, ca=False):
def clean_ruv(realm, ruv, options):
"""
Given an RID create a CLEANALLRUV task to clean it up.
"""
@ -417,19 +485,29 @@ def clean_ruv(realm, ruv, options, ca=False):
except ValueError:
sys.exit("Replica ID must be an integer: %s" % ruv)
servers = get_ruv(realm, options.host, options.dirman_passwd,
options.nolookup, ca=ca)
found = False
for (netloc, rid) in servers:
if ruv == int(rid):
found = True
hostname = netloc
break
try:
servers = get_ruv_both_suffixes(realm, options.host,
options.dirman_passwd,
options.verbose,
options.nolookup)
except (NoRUVsFound, RuntimeError) as e:
print(e)
sys.exit(0 if isinstance(e, NoRUVsFound) else 1)
if not found:
tree_found = None
for tree, ruvs in servers.items():
for netloc, rid in ruvs:
if ruv == int(rid):
tree_found = tree
hostname = netloc
break
if tree_found:
break
if not tree_found:
sys.exit("Replica ID %s not found" % ruv)
if ca:
if tree_found == 'ca':
print("Clean the Certificate Server Replication Update Vector for %s"
% hostname)
else:
@ -444,7 +522,7 @@ def clean_ruv(realm, ruv, options, ca=False):
if not ipautil.user_input("Continue to clean?", False):
sys.exit("Aborted")
if ca:
if tree_found == 'ca':
thisrepl = replication.get_cs_replication_manager(realm, options.host,
options.dirman_passwd)
else:
@ -453,6 +531,7 @@ def clean_ruv(realm, ruv, options, ca=False):
thisrepl.cleanallruv(ruv)
print("Cleanup task created")
def abort_clean_ruv(realm, ruv, options):
"""
Given an RID abort a CLEANALLRUV task.
@ -462,38 +541,41 @@ def abort_clean_ruv(realm, ruv, options):
except ValueError:
sys.exit("Replica ID must be an integer: %s" % ruv)
servers = get_ruv(realm, options.host, options.dirman_passwd,
options.nolookup)
found = False
for (netloc, rid) in servers:
if ruv == int(rid):
found = True
hostname = netloc
break
try:
servers = get_ruv_both_suffixes(realm, options.host,
options.dirman_passwd,
options.verbose,
options.nolookup)
except (NoRUVsFound, RuntimeError) as e:
print(e)
sys.exit(0 if isinstance(e, NoRUVsFound) else 1)
if not found:
sys.exit("Replica ID %s not found" % ruv)
tree_found = None
for tree, ruvs in servers.items():
for netloc, rid in ruvs:
if ruv == int(rid):
tree_found = tree
hostname = netloc
break
if tree_found:
break
servers = get_ruv(realm, options.host, options.dirman_passwd,
options.nolookup)
found = False
for (netloc, rid) in servers:
if ruv == int(rid):
found = True
hostname = netloc
break
if not found:
if not tree_found:
sys.exit("Replica ID %s not found" % ruv)
print("Aborting the clean Replication Update Vector task for %s" % hostname)
print()
thisrepl = replication.ReplicationManager(realm, options.host,
options.dirman_passwd)
if tree_found == 'ca':
thisrepl = replication.get_cs_replication_manager(realm, options.host,
options.dirman_passwd)
else:
thisrepl = replication.ReplicationManager(realm, options.host,
options.dirman_passwd)
thisrepl.abortcleanallruv(ruv, options.force)
print("Cleanup task stopped")
def list_clean_ruv(realm, host, dirman_passwd, verbose, nolookup=False):
"""
List all clean RUV tasks.
@ -541,15 +623,6 @@ def clean_dangling_ruvs(realm, host, options):
Cleans all RUVs and CS-RUVs that are left in the system from
uninstalled replicas
"""
# get the Directory Manager password
if not options.dirman_passwd:
options.dirman_passwd = installutils.read_password('Directory Manager',
confirm=False,
validate=False,
retry=False)
if options.dirman_passwd is None:
sys.exit('Directory Manager password is required')
conn = ipaldap.IPAdmin(host, 636, cacert=CACERT)
try:
conn.do_simple_bind(bindpw=options.dirman_passwd)
@ -604,32 +677,44 @@ def clean_dangling_ruvs(realm, host, options):
.format(host=master_cn))
offlines.add(master_cn)
continue
try:
entry = conn.get_entry(replica_dn)
ruv = (master_cn, entry.single_value.get('nsDS5ReplicaID'))
# the check whether ruv is already in ruvs is performed by set type
ruvs.add(ruv)
if(master_info['ca']):
entry = conn.get_entry(csreplica_dn)
csruv = (master_cn, entry.single_value.get('nsDS5ReplicaID'))
csruvs.add(csruv)
# get_ruv returns server names with :port which needs to be split off
ruv_list = get_ruv(realm, master_cn, options.dirman_passwd,
options.nolookup)
master_info['ruvs'] = set([
(re.sub(':\d+', '', x), y)
for (x, y) in ruv_list
])
try:
entry = conn.get_entry(replica_dn)
ruv = (master_cn, entry.single_value.get('nsDS5ReplicaID'))
# the check whether ruv is already in ruvs is performed
# by the set type
ruvs.add(ruv)
except errors.NotFound:
pass
if master_info['ca']:
ruv_list = get_ruv(realm, master_cn, options.dirman_passwd,
options.nolookup, ca=True)
try:
entry = conn.get_entry(csreplica_dn)
csruv = (master_cn,
entry.single_value.get('nsDS5ReplicaID'))
csruvs.add(csruv)
except errors.NotFound:
pass
try:
ruv_dict = get_ruv_both_suffixes(realm, master_cn,
options.dirman_passwd,
options.verbose,
options.nolookup)
except (RuntimeError, NoRUVsFound):
continue
# get_ruv_both_suffixes returns server names with :port
# This needs needs to be split off
if ruv_dict.get('domain'):
master_info['ruvs'] = set([
(re.sub(':\d+', '', x), y)
for (x, y) in ruv_dict['domain']
])
if ruv_dict.get('ca'):
master_info['csruvs'] = set([
(re.sub(':\d+', '', x), y)
for (x, y) in ruv_list
for (x, y) in ruv_dict['ca']
])
except Exception as e:
sys.exit("Failed to obtain information from '{host}': {error}"
@ -671,10 +756,6 @@ def clean_dangling_ruvs(realm, host, options):
print('\t\tid: {id}, hostname: {host}'
.format(id=csruv[1], host=csruv[0]))
# TODO: this can be removed when #5396 is fixed
if offlines:
sys.exit("ERROR: All replicas need to be online to proceed.")
if not options.force and not ipautil.user_input("Proceed with cleaning?", False):
sys.exit("Aborted")
@ -689,7 +770,7 @@ def clean_dangling_ruvs(realm, host, options):
for csruv in master_info['clean_csruv']:
if csruv[1] not in cleaned:
cleaned.add(csruv[1])
clean_ruv(realm, csruv[1], options, ca=True)
clean_ruv(realm, csruv[1], options)
def check_last_link(delrepl, realm, dirman_passwd, force):
@ -744,10 +825,14 @@ def check_last_link(delrepl, realm, dirman_passwd, force):
def enforce_host_existence(host, message=None):
if host is not None and not ipautil.host_exists(host):
if message is None:
message = "Unknown host %s" % host
if host is None:
return
try:
verify_host_resolvable(host)
except errors.DNSNotARecordError as ex:
if message is None:
message = "Unknown host %s: %s" % (host, ex)
sys.exit(message)
def ensure_last_services(conn, hostname, masters, options):
@ -876,10 +961,6 @@ def del_master_managed(realm, hostname, options):
# And pick new CA master.
ensure_last_services(api.Backend.ldap2, hostname, masters, options)
# Save the RID value before we start deleting
rid = get_rid_by_host(realm, options.host, hostname,
options.dirman_passwd, options.nolookup)
# 5. Remove master entry. Topology plugin will remove replication agreements.
try:
api.Command.server_del(hostname_u)
@ -1566,10 +1647,12 @@ def main():
if options.dirman_passwd:
dirman_passwd = options.dirman_passwd
else:
if not test_connection(realm, host, options.nolookup):
if (not test_connection(realm, host, options.nolookup) or
args[0] in dirman_passwd_req_commands):
dirman_passwd = installutils.read_password("Directory Manager",
confirm=False, validate=False, retry=False)
if dirman_passwd is None:
if dirman_passwd is None or (
not dirman_passwd and args[0] in dirman_passwd_req_commands):
sys.exit("Directory Manager password required")
options.dirman_passwd = dirman_passwd

View File

@ -31,7 +31,8 @@ from ipaserver.install.dsinstance import config_dirname
from ipaserver.install.installutils import is_ipa_configured, ScriptError
from ipalib import api, errors
from ipapython.ipaldap import IPAdmin
from ipapython.ipautil import wait_for_open_ports, wait_for_open_socket
from ipapython.ipautil import (
wait_for_open_ports, wait_for_open_socket, is_fips_enabled)
from ipapython import config, dogtag
from ipaplatform.tasks import tasks
from ipapython.dn import DN
@ -537,6 +538,9 @@ def main():
elif args[0] != "start" and args[0] != "stop" and args[0] != "restart" and args[0] != "status":
raise IpactlError("Unrecognized action [" + args[0] + "]", 2)
if is_fips_enabled():
raise IpactlError("Starting IPA server in FIPS mode is not supported")
# check if IPA is configured at all
try:
check_IPA_configuration()

View File

@ -41,6 +41,12 @@ Do not add any DNS forwarders, send non\-resolvable addresses to the DNS root se
\fB\-\-auto\-forwarders\fR
Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS.
.TP
\fB\-\-forward\-policy\fR=\fIfirst|only\fR
DNS forwarding policy for global forwarders specified using other options.
Defaults to first if no IP address belonging to a private or reserved ranges is
detected on local interfaces (RFC 6303). Defaults to only if a private
IP address is detected.
.TP
\fB\-\-reverse\-zone\fR=\fIREVERSE_ZONE\fR
The reverse DNS zone to use. This option can be used multiple times to specify multiple reverse zones.
.TP

View File

@ -149,6 +149,12 @@ Do not add any DNS forwarders. Root DNS servers will be used instead.
\fB\-\-auto\-forwarders\fR
Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS.
.TP
\fB\-\-forward\-policy\fR=\fIfirst|only\fR
DNS forwarding policy for global forwarders specified using other options.
Defaults to first if no IP address belonging to a private or reserved ranges is
detected on local interfaces (RFC 6303). Defaults to only if a private
IP address is detected.
.TP
\fB\-\-reverse\-zone\fR=\fIREVERSE_ZONE\fR
The reverse DNS zone to use. This option can be used multiple times to specify multiple reverse zones.
.TP

View File

@ -135,6 +135,7 @@ Password for the IPA system user used by the Windows PassSync plugin to synchron
.TP
\fB\-\-from\fR=\fISERVER\fR
The server to pull the data from, used by the re\-initialize and force\-sync commands.
.TP
.SH "RANGES"
IPA uses the 389\-ds Distributed Numeric Assignment (DNA) Plugin to allocate POSIX ids for users and groups. A range is created when IPA is installed and half the range is assigned to the first IPA master for the purposes of allocation.
.TP
@ -190,8 +191,11 @@ Using connect/disconnect you can manage the replication topology.
.TP
List the replication IDs in use:
# ipa\-replica\-manage list\-ruv
srv1.example.com:389: 7
srv2.example.com:389: 4
Replica Update Vectors:
srv1.example.com:389: 7
srv2.example.com:389: 4
Certificate Server Replica Update Vectors:
srv1.example.com:389: 9
.TP
Remove references to an orphaned and deleted master:
# ipa\-replica\-manage del \-\-force \-\-cleanup master.example.com

View File

@ -158,6 +158,12 @@ Do not add any DNS forwarders. Root DNS servers will be used instead.
\fB\-\-auto\-forwarders\fR
Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS.
.TP
\fB\-\-forward\-policy\fR=\fIfirst|only\fR
DNS forwarding policy for global forwarders specified using other options.
Defaults to first if no IP address belonging to a private or reserved ranges is
detected on local interfaces (RFC 6303). Defaults to only if a private
IP address is detected.
.TP
\fB\-\-reverse\-zone\fR=\fIREVERSE_ZONE\fR
The reverse DNS zone to use. This option can be used multiple times to specify multiple reverse zones.
.TP

View File

@ -38,6 +38,9 @@ Stop all of the services that make up IPA
restart
Stop then start all of the services that make up IPA
.TP
status
Provides status of all the services that make up IPA
.TP
\fB\-d\fR, \fB\-\-debug\fR
Display debugging information
.TP

View File

@ -298,6 +298,19 @@ return {
validators: ['network']
}
]
},
{
name: 'options',
fields: [
{
$type: 'checkbox',
name: 'skip_overlap_check',
label: '@i18n:objects.dnszone.skip_overlap_check',
tooltip: {
title: '@mc-opt:dnszone_add:skip_overlap_check:doc'
}
}
]
}
],
policies: [
@ -457,6 +470,19 @@ return {
]
}
]
},
{
name: 'options',
fields: [
{
$type: 'checkbox',
name: 'skip_overlap_check',
label: '@i18n:objects.dnszone.skip_overlap_check',
tooltip: {
title: '@mc-opt:dnsforwardzone_add:skip_overlap_check:doc'
}
}
]
}
],
policies: [

View File

@ -322,6 +322,7 @@
"remove_permission": "Remove Permission",
"remove_permission_confirm": "Are you sure you want to remove permission for DNS Zone ${object}?",
"skip_dns_check": "Skip DNS check",
"skip_overlap_check": "Skip overlap check",
"soamname_change_message": "Do you want to check if new authoritative nameserver address is in DNS",
"soamname_change_title": "Authoritative nameserver change"
},

View File

@ -0,0 +1,9 @@
dn: cn=ipa_pwd_extop,cn=plugins,cn=config
# DS core server provides a default plugin (passwd_modify_extop) to handle
# 1.3.6.1.4.1.4203.1.11.1 extended op (https://www.ietf.org/rfc/rfc3062.txt)
# the pluginprecedence of the passwd_modify_extop is 50 (default value)
#
# IPA delivers ipa_pwd_extop plugin to handle that extended op
# we need to make sure ipa_pwd_extop is called and so to set a lower
# precedence value
add:nsslapd-pluginprecedence: 49

View File

@ -74,7 +74,7 @@ dn: cn=Schema Compatibility,cn=plugins,cn=config
# We need to run schema-compat pre-bind callback before
# other IPA pre-bind callbacks to make sure bind DN is
# rewritten to the original entry if needed
add:nsslapd-pluginprecedence: 49
add:nsslapd-pluginprecedence: 40
dn: cn=users,cn=Schema Compatibility,cn=plugins,cn=config
add:schema-compat-entry-attribute: %ifeq("ipauniqueid","%{ipauniqueid}","objectclass=ipaOverrideTarget","")

View File

@ -2,7 +2,6 @@
# update DNS container
dn: cn=dns, $SUFFIX
addifexist: objectClass: idnsConfigObject
addifexist: objectClass: ipaConfigObject
addifexist: aci:(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";)
addifexist: aci:(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";)
addifexist: 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";)

View File

@ -3,6 +3,7 @@
# middle
plugin: update_ca_topology
plugin: update_ipaconfigstring_dnsversion_to_ipadnsversion
plugin: update_dnszones
plugin: update_dns_limits
plugin: update_sigden_extdom_broken_config
@ -16,7 +17,10 @@ plugin: update_service_principalalias
plugin: update_upload_cacrt
# last
# DNS version 1
plugin: update_master_to_dnsforwardzones
# DNS version 2
plugin: update_dnsforward_emptyzones
plugin: update_managed_post
plugin: update_managed_permissions
plugin: update_read_replication_agreements_permission

10
ipa.1
View File

@ -1,5 +1,5 @@
.\" A man page for ipa
.\" Copyright (C) 2010 Red Hat, Inc.
.\" Copyright (C) 2010-2016 Red Hat, Inc.
.\"
.\" 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
@ -16,7 +16,7 @@
.\"
.\" Author: Pavel Zuna <pzuna@redhat.com>
.\"
.TH "ipa" "1" "Jan 24 2012" "FreeIPA" "FreeIPA Manual Pages"
.TH "ipa" "1" "Apr 29 2016" "FreeIPA" "FreeIPA Manual Pages"
.SH "NAME"
ipa \- IPA command\-line interface
.SH "SYNOPSIS"
@ -174,9 +174,6 @@ Same as the previous example, except this time the users first name has to be ex
\fBipa user\-find foo \-\-first bar \-\-last foo\fR
A user with uid "foobar", first name "bar" and last name "foo" would match the search criteria.
.TP
\fBipa user\-find \-\-uuid 936407bd\-da9b\-11de\-9abd\-54520012e7cd\fR
Only the user with the specified IPA unique ID would match the search criteria.
.TP
\fBipa user\-find\fR
All users would match the search criteria (as there are none).
.SH "SERVERS"
@ -203,6 +200,5 @@ IPA default configuration file.
ipa\-client\-install(1), ipa\-compat\-manage(1), ipactl(1), ipa\-dns\-install(1),
ipa\-getcert(1), ipa\-getkeytab(1), ipa\-join(1), ipa\-ldap\-updater(1),
ipa\-nis\-manage(1), ipa\-replica\-install(1), ipa\-replica\-manage(1), ipa\-replica\-prepare(1),
ipa\-rmkeytab(1), ipa\-server\-certinstall(2), ipa\-server\-install(1), ipa\-upgradeconfig(1),
ipa\-host\-net\-manage(1)
ipa\-rmkeytab(1), ipa\-server\-certinstall(2), ipa\-server\-install(1), ipa\-server\-upgrade(1)

View File

@ -61,6 +61,8 @@ def setup_package():
package_dir = {'ipaclient': ''},
packages = ["ipaclient",
],
scripts=['../ipa'],
data_files = [('share/man/man1', ["../ipa.1"])],
)
finally:
del sys.path[0]

View File

@ -165,10 +165,11 @@ class textui(backend.Backend):
def encode_binary(self, value):
"""
Convert a binary value to base64. We know a value is binary
if it is a python str type, otherwise it is a plain string.
if it is a python bytes type, otherwise it is a plain string.
This function also converts datetime and DNSName values to string.
"""
if type(value) is bytes:
return base64.b64encode(value)
return base64.b64encode(value).decode('ascii')
elif type(value) is datetime.datetime:
return value.strftime(LDAP_GENERALIZED_TIME_FORMAT)
elif isinstance(value, DNSName):

View File

@ -261,3 +261,4 @@ REPL_AGMT_STRIP_ATTRS = ('modifiersName',
DOMAIN_SUFFIX_NAME = 'domain'
CA_SUFFIX_NAME = 'ca'
IPA_CA_RECORD = "ipa-ca"

View File

@ -93,7 +93,9 @@ current block assignments:
- **4300 - 4399** `CertificateError` and its subclasses
- **4400 - 4999** *Reserved for future use*
- **4400 - 4499** `DNSError` and (some of) its subclasses
- **4500 - 4999** *Reserved for future use*
- **5000 - 5999** `GenericError` and its subclasses
@ -1144,21 +1146,6 @@ class DefaultGroupError(ExecutionError):
errno = 4018
format = _('The default users group cannot be removed')
class DNSNotARecordError(ExecutionError):
"""
**4019** Raised when a hostname is not a DNS A/AAAA record
For example:
>>> raise DNSNotARecordError()
Traceback (most recent call last):
...
DNSNotARecordError: Host does not have corresponding DNS A/AAAA record
"""
errno = 4019
format = _('Host does not have corresponding DNS A/AAAA record')
class ManagedGroupError(ExecutionError):
"""
@ -1578,24 +1565,6 @@ class DatabaseTimeout(DatabaseError):
format = _('LDAP timeout')
class DNSDataMismatch(ExecutionError):
"""
**4212** Raised when an DNS query didn't return expected answer
in a configured time limit.
For example:
>>> raise DNSDataMismatch(expected="zone3.test. 86400 IN A 192.0.2.1", \
got="zone3.test. 86400 IN A 192.168.1.1")
Traceback (most recent call last):
...
DNSDataMismatch: DNS check failed: Expected {zone3.test. 86400 IN A 192.0.2.1} got {zone3.test. 86400 IN A 192.168.1.1}
"""
errno = 4212
format = _('DNS check failed: Expected {%(expected)s} got {%(got)s}')
class TaskTimeout(DatabaseError):
"""
**4213** Raised when an LDAP task times out
@ -1785,6 +1754,67 @@ class CertificateInvalidError(CertificateError):
format = _('%(name)s certificate is not valid')
class DNSError(ExecutionError):
"""
**4400** Base class for DNS execution errors (*4400 - 4499*).
These are typically wrapper exceptions around dns.exception.DNSException.
"""
errno = 4400
class DNSNotARecordError(DNSError):
"""
**4019** Raised when a hostname is not a DNS A/AAAA record
For example:
>>> raise DNSNotARecordError(hostname='x')
Traceback (most recent call last):
...
DNSNotARecordError: Host 'x' does not have corresponding DNS A/AAAA record
"""
errno = 4019 # this exception was defined before DNSError
format = _(
'Host \'%(hostname)s\' does not have corresponding DNS A/AAAA record')
class DNSDataMismatch(DNSError):
"""
**4212** Raised when an DNS query didn't return expected answer
in a configured time limit.
For example:
>>> raise DNSDataMismatch(expected="zone3.test. 86400 IN A 192.0.2.1", \
got="zone3.test. 86400 IN A 192.168.1.1")
Traceback (most recent call last):
...
DNSDataMismatch: DNS check failed: Expected {zone3.test. 86400 IN A 192.0.2.1} got {zone3.test. 86400 IN A 192.168.1.1}
"""
errno = 4212 # this exception was defined before DNSError
format = _('DNS check failed: Expected {%(expected)s} got {%(got)s}')
class DNSResolverError(DNSError):
"""
**4401** Wrapper around dns.exception.DNSException.
Raised when an error occured in dns.resolver.
For example:
>>> raise DNSResolverError(exception=ValueError("this is bad"))
Traceback (most recent call last):
...
DNSResolverError: this is bad
"""
errno = 4401
format = _('%(exception)s')
##############################################################################
# 5000 - 5999: Generic errors

View File

@ -350,6 +350,23 @@ class ResultFormattingError(PublicMessage):
type = "warning"
class DNSForwardPolicyConflictWithEmptyZone(PublicMessage):
"""
**13021** Forward zone 1.10.in-addr.arpa with policy "first"
will not forward anything because BIND automatically prefers
empty zone "10.in-addr.arpa.".
"""
errno = 13021
type = "warning"
format = _(
"Forwarding policy conflicts with some automatic empty zones. "
"Queries for zones specified by RFC 6303 will ignore "
"forwarding and recursion and always result in NXDOMAIN answers. "
"To override this behavior use forward policy 'only'."
)
def iter_messages(variables, base):
"""Return a tuple with all subclasses
"""

View File

@ -559,6 +559,8 @@ class Param(ReadOnly):
value = self.__kw[key]
if callable(value) and hasattr(value, '__name__'):
value = value.__name__
elif isinstance(value, six.integer_types):
value = str(value)
else:
value = repr(value)
yield '%s=%s' % (key, value)

View File

@ -108,7 +108,10 @@ class batch(Command):
result = api.Command[name](*a, **newkw)
self.info(
'%s: batch: %s(%s): SUCCESS', context.principal, name, ', '.join(api.Command[name]._repr_iter(**params))
'%s: batch: %s(%s): SUCCESS',
getattr(context, 'principal', 'UNKNOWN'),
name,
', '.join(api.Command[name]._repr_iter(**params))
)
result['error']=None
except Exception as e:

View File

@ -61,14 +61,14 @@ def _acl_make_request(principal_type, principal, ca_ref, profile_id):
req.targethost.name = ca_ref
req.service.name = profile_id
if principal_type == 'user':
req.user.name = principal
req.user.name = name
elif principal_type == 'host':
req.user.name = name
elif principal_type == 'service':
req.user.name = normalize_principal(principal)
groups = []
if principal_type == 'user':
user_obj = api.Command.user_show(principal)['result']
user_obj = api.Command.user_show(name)['result']
groups = user_obj.get('memberof_group', [])
groups += user_obj.get('memberofindirect_group', [])
elif principal_type == 'host':

View File

@ -226,7 +226,7 @@ class certprofile_show(LDAPRetrieve):
result = super(certprofile_show, self).forward(*keys, **options)
if 'out' in options and 'config' in result['result']:
with open(options['out'], 'w') as f:
with open(options['out'], 'wb') as f:
f.write(result['result'].pop('config'))
result['summary'] = (
_("Profile configuration stored in file '%(file)s'")

View File

@ -53,8 +53,11 @@ from ipalib.util import (normalize_zonemgr,
validate_dnssec_zone_forwarder_step1,
validate_dnssec_zone_forwarder_step2,
verify_host_resolvable)
from ipapython.ipautil import CheckedIPAddress, check_zone_overlap
from ipapython.dn import DN
from ipapython.ipautil import CheckedIPAddress
from ipapython.dnsutil import check_zone_overlap
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import related_to_auto_empty_zone
if six.PY3:
unicode = str
@ -1561,7 +1564,7 @@ def check_ns_rec_resolvable(zone, name, log):
# this is a DNS name relative to the zone
name = name.derelativize(zone.make_absolute())
try:
verify_host_resolvable(name, log)
verify_host_resolvable(name)
except errors.DNSNotARecordError:
raise errors.NotFound(
reason=_('Nameserver \'%(host)s\' does not have a corresponding '
@ -1744,9 +1747,11 @@ def _normalize_zone(zone):
if isinstance(zone, unicode):
# normalize only non-IDNA zones
try:
return unicode(zone.encode('ascii')).lower()
zone.encode('ascii')
except UnicodeError:
pass
else:
return zone.lower()
return zone
@ -1983,6 +1988,20 @@ def _add_warning_fw_zone_is_not_effective(api, result, fwzone, version):
)
def _add_warning_fw_policy_conflict_aez(result, fwzone, **options):
"""Warn if forwarding policy conflicts with an automatic empty zone."""
fwd_policy = result['result'].get(u'idnsforwardpolicy',
dnsforwardzone.default_forward_policy)
if (
fwd_policy != [u'only']
and related_to_auto_empty_zone(DNSName(fwzone))
):
messages.add_message(
options['version'], result,
messages.DNSForwardPolicyConflictWithEmptyZone()
)
class DNSZoneBase(LDAPObject):
"""
Base class for DNS Zone
@ -2785,11 +2804,10 @@ class dnszone_add(DNSZoneBase_add):
assert isinstance(dn, DN)
# Add entry to realmdomains
# except for our own domain, forward zones, reverse zones and root zone
# except for our own domain, reverse zones and root zone
zone = keys[0]
if (zone != DNSName(api.env.domain).make_absolute() and
not options.get('idnsforwarders') and
not zone.is_reverse() and
zone != DNSName.root):
try:
@ -4218,7 +4236,7 @@ class dns_resolve(Command):
query=args[0]
try:
verify_host_resolvable(query, self.log)
verify_host_resolvable(query)
except errors.DNSNotARecordError:
raise errors.NotFound(
reason=_('Host \'%(host)s\' not found') % {'host': query}
@ -4298,6 +4316,9 @@ class dnsconfig(LDAPObject):
cli_name='zone_refresh',
label=_('Zone refresh interval'),
),
Int('ipadnsversion?', # available only in installer/upgrade
label=_('IPA DNS version'),
),
)
managed_permissions = {
'System: Write DNS Configuration': {
@ -4324,7 +4345,7 @@ class dnsconfig(LDAPObject):
'ipapermdefaultattr': {
'objectclass',
'idnsallowsyncptr', 'idnsforwarders', 'idnsforwardpolicy',
'idnspersistentsearch', 'idnszonerefresh'
'idnspersistentsearch', 'idnszonerefresh', 'ipadnsversion'
},
'default_privileges': {'DNS Administrators', 'DNS Servers'},
},
@ -4345,11 +4366,17 @@ class dnsconfig(LDAPObject):
result['summary'] = unicode(_('Global DNS configuration is empty'))
@register()
class dnsconfig_mod(LDAPUpdate):
__doc__ = _('Modify global DNS configuration.')
def get_options(self):
"""hide ipadnsversion outside of installer/upgrade"""
for option in super(dnsconfig_mod, self).get_options():
if option.name == 'ipadnsversion':
option = option.clone(include=('installer', 'updates'))
yield option
def interactive_prompt_callback(self, kw):
# show informative message on client side
@ -4367,7 +4394,13 @@ class dnsconfig_mod(LDAPUpdate):
result = super(dnsconfig_mod, self).execute(*keys, **options)
self.obj.postprocess_result(result)
# this check makes sense only when resulting forwarders are non-empty
if result['result'].get('idnsforwarders'):
fwzone = DNSName('.')
_add_warning_fw_policy_conflict_aez(result, fwzone, **options)
if forwarders:
# forwarders were changed
for forwarder in forwarders:
try:
validate_dnssec_global_forwarder(forwarder, log=self.log)
@ -4407,6 +4440,7 @@ class dnsconfig_show(LDAPRetrieve):
return result
@register()
class dnsforwardzone(DNSZoneBase):
"""
@ -4508,6 +4542,7 @@ class dnsforwardzone(DNSZoneBase):
)
)
@register()
class dnsforwardzone_add(DNSZoneBase_add):
__doc__ = _('Create new DNS forward zone.')
@ -4538,8 +4573,10 @@ class dnsforwardzone_add(DNSZoneBase_add):
return dn
def execute(self, *keys, **options):
fwzone = keys[-1]
result = super(dnsforwardzone_add, self).execute(*keys, **options)
self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
_add_warning_fw_policy_conflict_aez(result, fwzone, **options)
if options.get('idnsforwarders'):
self.obj._warning_if_forwarders_do_not_work(
result, True, *keys, **options)
@ -4595,7 +4632,9 @@ class dnsforwardzone_mod(DNSZoneBase_mod):
return dn
def execute(self, *keys, **options):
fwzone = keys[-1]
result = super(dnsforwardzone_mod, self).execute(*keys, **options)
_add_warning_fw_policy_conflict_aez(result, fwzone, **options)
if options.get('idnsforwarders'):
self.obj._warning_if_forwarders_do_not_work(result, False, *keys,
**options)

View File

@ -44,7 +44,7 @@ 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, validate_hostname, normalize_hostname)
from ipapython.ipautil import ipa_generate_password, CheckedIPAddress
from ipapython.dnsutil import DNSName
from ipapython.ssh import SSHPublicKey
@ -267,14 +267,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)
@ -631,7 +623,7 @@ class host_add(LDAPCreate):
check_forward=True,
check_reverse=check_reverse)
if not options.get('force', False) and not 'ip_address' in options:
util.verify_host_resolvable(keys[-1], self.log)
util.verify_host_resolvable(keys[-1])
if 'locality' in entry_attrs:
entry_attrs['l'] = entry_attrs['locality']
entry_attrs['cn'] = keys[-1]

View File

@ -468,6 +468,7 @@ class i18n_messages(Command):
"remove_permission": _("Remove Permission"),
"remove_permission_confirm": _("Are you sure you want to remove permission for DNS Zone ${object}?"),
"skip_dns_check": _("Skip DNS check"),
"skip_overlap_check": _("Skip overlap check"),
"soamname_change_message": _("Do you want to check if new authoritative nameserver address is in DNS"),
"soamname_change_title": _("Authoritative nameserver change"),
},

View File

@ -368,7 +368,7 @@ class otptoken_add(LDAPCreate):
encoding = locale.getpreferredencoding(False)
try:
qr_code = qr_output.getvalue().decode(encoding)
qr_code = qr_output.getvalue().encode(encoding)
except UnicodeError:
add_message(
version,

View File

@ -78,7 +78,7 @@ def validate_radiusserver(ugettext, server):
validate_hostname(server, check_fqdn=True, allow_underscore=True)
except ValueError as e:
raise errors.ValidationError(name="ipatokenradiusserver",
error=e.message)
error=str(e))
@register()

View File

@ -551,10 +551,10 @@ class service_add(LDAPCreate):
entry_attrs['usercertificate'] = certs_der
if not options.get('force', False):
# We know the host exists if we've gotten this far but we
# really want to discourage creating services for hosts that
# don't exist in DNS.
util.verify_host_resolvable(hostname, self.log)
# We know the host exists if we've gotten this far but we
# really want to discourage creating services for hosts that
# don't exist in DNS.
util.verify_host_resolvable(hostname)
if not 'managedby' in entry_attrs:
entry_attrs['managedby'] = hostresult['dn']

View File

@ -371,6 +371,8 @@ class stageuser_add(baseuser_add):
answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl)
entry_attrs['ipatokenradiusconfiglink'] = answer
self.pre_common_callback(ldap, dn, entry_attrs, **options)
return dn
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):

View File

@ -298,7 +298,10 @@ def json_encode_binary(val, version):
new_list = [json_encode_binary(v, version) for v in val]
return new_list
elif isinstance(val, bytes):
return {'__base64__': base64.b64encode(val)}
encoded = base64.b64encode(val)
if not six.PY2:
encoded = encoded.decode('ascii')
return {'__base64__': encoded}
elif isinstance(val, Decimal):
return {'__base64__': base64.b64encode(str(val))}
elif isinstance(val, DN):

View File

@ -44,6 +44,7 @@ from ipalib.text import _
from ipapython.ssh import SSHPublicKey
from ipapython.dn import DN, RDN
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import resolve_ip_addresses
from ipapython.graph import Graph
if six.PY3:
@ -66,31 +67,14 @@ def json_serialize(obj):
return ''
return json_serialize(obj.__json__())
def verify_host_resolvable(fqdn, log):
"""
See if the hostname has a DNS A/AAAA record.
"""
if not isinstance(fqdn, DNSName):
fqdn = DNSName(fqdn)
fqdn = fqdn.make_absolute()
for rdtype in ('A', 'AAAA'):
try:
answers = resolver.query(fqdn, rdtype)
log.debug(
'IPA: found %d %s records for %s: %s' % (len(answers),
rdtype, fqdn, ' '.join(str(answer) for answer in answers))
)
except DNSException:
log.debug(
'IPA: DNS %s record lookup failed for %s' %
(rdtype, fqdn)
)
continue
else:
return
# dns lookup failed in both tries
raise errors.DNSNotARecordError()
def verify_host_resolvable(fqdn):
try:
if not resolve_ip_addresses(fqdn):
raise errors.DNSNotARecordError(hostname=fqdn)
except dns.exception.DNSException as ex:
# wrap DNSException in a PublicError
raise errors.DNSResolverError(exception=ex)
def has_soa_or_ns_record(domain):
@ -860,3 +844,11 @@ def detect_dns_zone_realm_type(api, domain):
def has_managed_topology(api):
domainlevel = api.Command['domainlevel_get']().get('result', DOMAIN_LEVEL_0)
return domainlevel > DOMAIN_LEVEL_0
def normalize_hostname(hostname):
"""Use common fqdn form without the trailing dot"""
if hostname.endswith(u'.'):
hostname = hostname[:-1]
hostname = hostname.lower()
return hostname

View File

@ -133,6 +133,7 @@ class BasePathNamespace(object):
SYSTEMD_PKI_TOMCAT_SERVICE = "/etc/systemd/system/pki-tomcatd.target.wants/pki-tomcatd@pki-tomcat.service"
DNSSEC_TRUSTED_KEY = "/etc/trusted-key.key"
HOME_DIR = "/home"
PROC_FIPS_ENABLED = "/proc/sys/crypto/fips_enabled"
ROOT_IPA_CACHE = "/root/.ipa_cache"
ROOT_PKI = "/root/.pki"
DOGTAG_ADMIN_P12 = "/root/ca-agent.p12"

View File

@ -84,13 +84,17 @@ class IPAVersion(object):
def __init__(self, version):
self.version = version
@property
def _bytes(self):
return self.version.encode('utf-8')
def __eq__(self, other):
assert isinstance(other, IPAVersion)
return _librpm.rpmvercmp(self.version, other.version) == 0
return _librpm.rpmvercmp(self._bytes, other._bytes) == 0
def __lt__(self, other):
assert isinstance(other, IPAVersion)
return _librpm.rpmvercmp(self.version, other.version) < 0
return _librpm.rpmvercmp(self._bytes, other._bytes) < 0
class RedHatTaskNamespace(BaseTaskNamespace):

View File

@ -19,10 +19,14 @@
import dns.name
import dns.exception
import dns.resolver
import copy
import six
from ipapython.ipautil import UnsafeIPAddress
from ipapython.ipa_log_manager import root_logger
if six.PY3:
unicode = str
@ -109,3 +113,254 @@ DNSName.root = DNSName(dns.name.root) # '.'
DNSName.empty = DNSName(dns.name.empty) # '@'
DNSName.ip4_rev_zone = DNSName(('in-addr', 'arpa', ''))
DNSName.ip6_rev_zone = DNSName(('ip6', 'arpa', ''))
# Empty zones are defined in various RFCs. BIND is by default serving them.
# This constat should contain everything listed in
# IANA registry "Locally-Served DNS Zones"
# URL: http://www.iana.org/assignments/locally-served-dns-zones
# + AS112 zone defined in RFC 7534. It is not in the registry for some
# reason but BIND 9.10 is serving it as automatic empty zones.
EMPTY_ZONES = [DNSName(aez).make_absolute() for aez in [
# RFC 1918
"10.IN-ADDR.ARPA", "16.172.IN-ADDR.ARPA", "17.172.IN-ADDR.ARPA",
"18.172.IN-ADDR.ARPA", "19.172.IN-ADDR.ARPA", "20.172.IN-ADDR.ARPA",
"21.172.IN-ADDR.ARPA", "22.172.IN-ADDR.ARPA", "23.172.IN-ADDR.ARPA",
"24.172.IN-ADDR.ARPA", "25.172.IN-ADDR.ARPA", "26.172.IN-ADDR.ARPA",
"27.172.IN-ADDR.ARPA", "28.172.IN-ADDR.ARPA", "29.172.IN-ADDR.ARPA",
"30.172.IN-ADDR.ARPA", "31.172.IN-ADDR.ARPA", "168.192.IN-ADDR.ARPA",
# RFC 6598
"64.100.IN-ADDR.ARPA", "65.100.IN-ADDR.ARPA", "66.100.IN-ADDR.ARPA",
"67.100.IN-ADDR.ARPA", "68.100.IN-ADDR.ARPA", "69.100.IN-ADDR.ARPA",
"70.100.IN-ADDR.ARPA", "71.100.IN-ADDR.ARPA", "72.100.IN-ADDR.ARPA",
"73.100.IN-ADDR.ARPA", "74.100.IN-ADDR.ARPA", "75.100.IN-ADDR.ARPA",
"76.100.IN-ADDR.ARPA", "77.100.IN-ADDR.ARPA", "78.100.IN-ADDR.ARPA",
"79.100.IN-ADDR.ARPA", "80.100.IN-ADDR.ARPA", "81.100.IN-ADDR.ARPA",
"82.100.IN-ADDR.ARPA", "83.100.IN-ADDR.ARPA", "84.100.IN-ADDR.ARPA",
"85.100.IN-ADDR.ARPA", "86.100.IN-ADDR.ARPA", "87.100.IN-ADDR.ARPA",
"88.100.IN-ADDR.ARPA", "89.100.IN-ADDR.ARPA", "90.100.IN-ADDR.ARPA",
"91.100.IN-ADDR.ARPA", "92.100.IN-ADDR.ARPA", "93.100.IN-ADDR.ARPA",
"94.100.IN-ADDR.ARPA", "95.100.IN-ADDR.ARPA", "96.100.IN-ADDR.ARPA",
"97.100.IN-ADDR.ARPA", "98.100.IN-ADDR.ARPA", "99.100.IN-ADDR.ARPA",
"100.100.IN-ADDR.ARPA", "101.100.IN-ADDR.ARPA",
"102.100.IN-ADDR.ARPA", "103.100.IN-ADDR.ARPA",
"104.100.IN-ADDR.ARPA", "105.100.IN-ADDR.ARPA",
"106.100.IN-ADDR.ARPA", "107.100.IN-ADDR.ARPA",
"108.100.IN-ADDR.ARPA", "109.100.IN-ADDR.ARPA",
"110.100.IN-ADDR.ARPA", "111.100.IN-ADDR.ARPA",
"112.100.IN-ADDR.ARPA", "113.100.IN-ADDR.ARPA",
"114.100.IN-ADDR.ARPA", "115.100.IN-ADDR.ARPA",
"116.100.IN-ADDR.ARPA", "117.100.IN-ADDR.ARPA",
"118.100.IN-ADDR.ARPA", "119.100.IN-ADDR.ARPA",
"120.100.IN-ADDR.ARPA", "121.100.IN-ADDR.ARPA",
"122.100.IN-ADDR.ARPA", "123.100.IN-ADDR.ARPA",
"124.100.IN-ADDR.ARPA", "125.100.IN-ADDR.ARPA",
"126.100.IN-ADDR.ARPA", "127.100.IN-ADDR.ARPA",
# RFC 5735 and RFC 5737
"0.IN-ADDR.ARPA", "127.IN-ADDR.ARPA", "254.169.IN-ADDR.ARPA",
"2.0.192.IN-ADDR.ARPA", "100.51.198.IN-ADDR.ARPA",
"113.0.203.IN-ADDR.ARPA", "255.255.255.255.IN-ADDR.ARPA",
# Local IPv6 Unicast Addresses
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
# LOCALLY ASSIGNED LOCAL ADDRESS SCOPE
"D.F.IP6.ARPA", "8.E.F.IP6.ARPA", "9.E.F.IP6.ARPA", "A.E.F.IP6.ARPA",
"B.E.F.IP6.ARPA",
# Example Prefix, RFC 3849.
"8.B.D.0.1.0.0.2.IP6.ARPA",
# RFC 7534
"EMPTY.AS112.ARPA",
]]
def assert_absolute_dnsname(name):
"""Raise AssertionError if name is not DNSName or is not absolute.
>>> assert_absolute_dnsname(DNSName('absolute.name.example.'))
>>> assert_absolute_dnsname(DNSName('relative.name.example'))
Traceback (most recent call last):
...
AssertionError: name must be absolute, ...
>>> assert_absolute_dnsname('absolute.string.example.')
Traceback (most recent call last):
...
AssertionError: name must be DNSName instance, ...
"""
assert isinstance(name, DNSName), ("name must be DNSName instance, "
"got '%s'" % type(name))
assert name.is_absolute(), "name must be absolute, got '%s'" % name
def is_auto_empty_zone(zone):
"""True if specified zone name exactly matches an automatic empty zone.
>>> is_auto_empty_zone(DNSName('in-addr.arpa.'))
False
>>> is_auto_empty_zone(DNSName('10.in-addr.arpa.'))
True
>>> is_auto_empty_zone(DNSName('1.10.in-addr.arpa.'))
False
>>> is_auto_empty_zone(DNSName('10.in-addr.arpa'))
Traceback (most recent call last):
...
AssertionError: ...
"""
assert_absolute_dnsname(zone)
return zone in EMPTY_ZONES
def inside_auto_empty_zone(name):
"""True if specified absolute name is a subdomain of an automatic empty
zone.
DNS domain is a subdomain of itself so this function
returns True for zone apexes, too.
>>> inside_auto_empty_zone(DNSName('in-addr.arpa.'))
False
>>> inside_auto_empty_zone(DNSName('10.in-addr.arpa.'))
True
>>> inside_auto_empty_zone(DNSName('1.10.in-addr.arpa.'))
True
>>> inside_auto_empty_zone(DNSName('1.10.in-addr.arpa'))
Traceback (most recent call last):
...
AssertionError: ...
"""
assert_absolute_dnsname(name)
for aez in EMPTY_ZONES:
if name.is_subdomain(aez):
return True
return False
def related_to_auto_empty_zone(name):
"""True if specified absolute name is a sub/superdomain of an automatic
empty zone.
DNS domain is a subdomain of itself so this function
returns True for zone apexes, too.
>>> related_to_auto_empty_zone(DNSName('.'))
True
>>> related_to_auto_empty_zone(DNSName('in-addr.arpa.'))
True
>>> related_to_auto_empty_zone(DNSName('10.in-addr.arpa.'))
True
>>> related_to_auto_empty_zone(DNSName('1.10.in-addr.arpa.'))
True
>>> related_to_auto_empty_zone(DNSName('unrelated.example.'))
False
>>> related_to_auto_empty_zone(DNSName('1.10.in-addr.arpa'))
Traceback (most recent call last):
...
AssertionError: ...
"""
assert_absolute_dnsname(name)
relations = {dns.name.NAMERELN_SUBDOMAIN,
dns.name.NAMERELN_EQUAL,
dns.name.NAMERELN_SUPERDOMAIN}
return any(name.fullcompare(aez)[0] in relations
for aez in EMPTY_ZONES)
def has_empty_zone_addresses(hostname):
"""Detect if given host is using IP address belonging to
an automatic empty zone.
Information from --ip-address option used in installed is lost by
the time when upgrade is run. Use IP addresses from DNS as best
approximation.
This is brain-dead and duplicates logic from DNS installer
but I did not find other way around.
"""
ip_addresses = resolve_ip_addresses(hostname)
return any(
inside_auto_empty_zone(DNSName(ip.reverse_dns))
for ip in ip_addresses
)
def resolve_rrsets(fqdn, rdtypes):
"""
Get Resource Record sets for given FQDN.
CNAME chain is followed during resolution
but CNAMEs are not returned in the resulting rrset.
:returns:
set of dns.rrset.RRset objects, can be empty
if the FQDN does not exist or if none of rrtypes exist
"""
# empty set of rdtypes would always return empty set of rrsets
assert rdtypes, "rdtypes must not be empty"
if not isinstance(fqdn, DNSName):
fqdn = DNSName(fqdn)
fqdn = fqdn.make_absolute()
rrsets = []
for rdtype in rdtypes:
try:
answer = dns.resolver.query(fqdn, rdtype)
root_logger.debug('found %d %s records for %s: %s',
len(answer), rdtype, fqdn, ' '.join(
str(rr) for rr in answer))
rrsets.append(answer.rrset)
except dns.resolver.NXDOMAIN as ex:
root_logger.debug(ex)
break # no such FQDN, do not iterate
except dns.resolver.NoAnswer as ex:
root_logger.debug(ex) # record type does not exist for given FQDN
except dns.exception.DNSException as ex:
root_logger.error('DNS query for %s %s failed: %s',
fqdn, rdtype, ex)
raise
return rrsets
def resolve_ip_addresses(fqdn):
"""Get IP addresses from DNS A/AAAA records for given host (using DNS).
:returns:
list of IP addresses as UnsafeIPAddress objects
"""
rrsets = resolve_rrsets(fqdn, ['A', 'AAAA'])
ip_addresses = set()
for rrset in rrsets:
ip_addresses.update({UnsafeIPAddress(ip) for ip in rrset})
return ip_addresses
def check_zone_overlap(zone, raise_on_error=True):
root_logger.info("Checking DNS domain %s, please wait ..." % zone)
if not isinstance(zone, DNSName):
zone = DNSName(zone).make_absolute()
# automatic empty zones always exist so checking them is pointless,
# do not report them to avoid meaningless error messages
if is_auto_empty_zone(zone):
return
try:
containing_zone = dns.resolver.zone_for_name(zone)
except dns.exception.DNSException as e:
msg = ("DNS check for domain %s failed: %s." % (zone, e))
if raise_on_error:
raise ValueError(msg)
else:
root_logger.warning(msg)
return
if containing_zone == zone:
try:
ns = [ans.to_text() for ans in dns.resolver.query(zone, 'NS')]
except dns.exception.DNSException as e:
root_logger.debug("Failed to resolve nameserver(s) for domain"
" {0}: {1}".format(zone, e))
ns = []
msg = u"DNS zone {0} already exists in DNS".format(zone)
if ns:
msg += u" and is handled by server(s): {0}".format(', '.join(ns))
raise ValueError(msg)

View File

@ -299,9 +299,9 @@ class ConfigureTool(admintool.AdminTool):
knob_cls = knob_classes[e.name]
try:
if self.positional_arguments is None:
raise IndexError
raise ValueError
index = self.positional_arguments.index(e.name)
except IndexError:
except ValueError:
cli_name = knob_cls.cli_name or e.name.replace('_', '-')
desc = "option --{0}".format(cli_name)
else:

View File

@ -459,7 +459,7 @@ class LDAPEntry(collections.MutableMapping):
def __delitem__(self, name):
name = self._get_attr_name(name)
for (altname, keyname) in self._names.items():
for (altname, keyname) in list(self._names.items()):
if keyname == name:
del self._names[altname]
@ -816,7 +816,7 @@ class LDAPClient(object):
If there is a problem loading the schema or the attribute is
not in the schema return None
"""
if isinstance(name_or_oid, unicode):
if six.PY2 and isinstance(name_or_oid, unicode):
name_or_oid = name_or_oid.encode('utf-8')
if name_or_oid in self._SINGLE_VALUE_OVERRIDE:
@ -1492,7 +1492,7 @@ class LDAPClient(object):
# pass arguments to python-ldap
with self.error_handler():
modlist = [(a, self.encode(b), self.encode(c))
modlist = [(a, str(b), self.encode(c))
for a, b, c in modlist]
self.conn.modify_s(str(entry.dn), modlist)

View File

@ -51,7 +51,6 @@ from ipapython import ipavalidate
from ipapython import config
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython.dnsutil import DNSName
SHARE_DIR = paths.USR_SHARE_IPA_DIR
PLUGINS_SHARE_DIR = paths.IPA_PLUGINS
@ -87,111 +86,132 @@ def get_domain_name():
return domain_name
class CheckedIPAddress(netaddr.IPAddress):
class UnsafeIPAddress(netaddr.IPAddress):
"""Any valid IP address with or without netmask."""
# Use inet_pton() rather than inet_aton() for IP address parsing. We
# will use the same function in IPv4/IPv6 conversions + be stricter
# and don't allow IP addresses such as '1.1.1' in the same time
netaddr_ip_flags = netaddr.INET_PTON
def __init__(self, addr):
if isinstance(addr, UnsafeIPAddress):
self._net = addr._net
super(UnsafeIPAddress, self).__init__(addr,
flags=self.netaddr_ip_flags)
return
elif isinstance(addr, netaddr.IPAddress):
self._net = None # no information about netmask
super(UnsafeIPAddress, self).__init__(addr,
flags=self.netaddr_ip_flags)
return
elif isinstance(addr, netaddr.IPNetwork):
self._net = addr
super(UnsafeIPAddress, self).__init__(self._net.ip,
flags=self.netaddr_ip_flags)
return
# option of last resort: parse it as string
self._net = None
addr = str(addr)
try:
try:
addr = netaddr.IPAddress(addr, flags=self.netaddr_ip_flags)
except netaddr.AddrFormatError:
# netaddr.IPAddress doesn't handle zone indices in textual
# IPv6 addresses. Try removing zone index and parse the
# address again.
addr, sep, foo = addr.partition('%')
if sep != '%':
raise
addr = netaddr.IPAddress(addr, flags=self.netaddr_ip_flags)
if addr.version != 6:
raise
except ValueError:
self._net = netaddr.IPNetwork(addr, flags=self.netaddr_ip_flags)
addr = self._net.ip
super(UnsafeIPAddress, self).__init__(addr,
flags=self.netaddr_ip_flags)
class CheckedIPAddress(UnsafeIPAddress):
"""IPv4 or IPv6 address with additional constraints.
Reserved or link-local addresses are never accepted.
"""
def __init__(self, addr, match_local=False, parse_netmask=True,
allow_network=False, allow_loopback=False,
allow_broadcast=False, allow_multicast=False):
super(CheckedIPAddress, self).__init__(addr)
if isinstance(addr, CheckedIPAddress):
super(CheckedIPAddress, self).__init__(addr, flags=self.netaddr_ip_flags)
self.prefixlen = addr.prefixlen
self.defaultnet = addr.defaultnet
self.interface = addr.interface
return
net = None
iface = None
defnet = False
if not parse_netmask and self._net:
raise ValueError(
"netmask and prefix length not allowed here: {}".format(addr))
if isinstance(addr, netaddr.IPNetwork):
net = addr
addr = net.ip
elif isinstance(addr, netaddr.IPAddress):
pass
else:
try:
try:
addr = netaddr.IPAddress(str(addr), flags=self.netaddr_ip_flags)
except netaddr.AddrFormatError:
# netaddr.IPAddress doesn't handle zone indices in textual
# IPv6 addresses. Try removing zone index and parse the
# address again.
if not isinstance(addr, six.string_types):
raise
addr, sep, foo = addr.partition('%')
if sep != '%':
raise
addr = netaddr.IPAddress(str(addr), flags=self.netaddr_ip_flags)
if addr.version != 6:
raise
except ValueError:
net = netaddr.IPNetwork(str(addr), flags=self.netaddr_ip_flags)
if not parse_netmask:
raise ValueError("netmask and prefix length not allowed here")
addr = net.ip
if self.version not in (4, 6):
raise ValueError("unsupported IP version {}".format(self.version))
if addr.version not in (4, 6):
raise ValueError("unsupported IP version")
if not allow_loopback and self.is_loopback():
raise ValueError("cannot use loopback IP address {}".format(addr))
if (not self.is_loopback() and self.is_reserved()) \
or self in netaddr.ip.IPV4_6TO4:
raise ValueError(
"cannot use IANA reserved IP address {}".format(addr))
if not allow_loopback and addr.is_loopback():
raise ValueError("cannot use loopback IP address")
if (not addr.is_loopback() and addr.is_reserved()) \
or addr in netaddr.ip.IPV4_6TO4:
raise ValueError("cannot use IANA reserved IP address")
if addr.is_link_local():
raise ValueError("cannot use link-local IP address")
if not allow_multicast and addr.is_multicast():
raise ValueError("cannot use multicast IP address")
if self.is_link_local():
raise ValueError(
"cannot use link-local IP address {}".format(addr))
if not allow_multicast and self.is_multicast():
raise ValueError("cannot use multicast IP address {}".format(addr))
if match_local:
if addr.version == 4:
if self.version == 4:
family = 'inet'
elif addr.version == 6:
elif self.version == 6:
family = 'inet6'
result = run(
[paths.IP, '-family', family, '-oneline', 'address', 'show'],
capture_output=True)
lines = result.output.split('\n')
iface = None
for line in lines:
fields = line.split()
if len(fields) < 4:
continue
ifnet = netaddr.IPNetwork(fields[3])
if ifnet == net or (net is None and ifnet.ip == addr):
net = ifnet
if ifnet == self._net or (self._net is None and ifnet.ip == self):
self._net = ifnet
iface = fields[1]
break
if iface is None:
raise ValueError('No network interface matches the provided IP address and netmask')
raise ValueError('no network interface matches the IP address '
'and netmask {}'.format(addr))
if net is None:
defnet = True
if addr.version == 4:
net = netaddr.IPNetwork(netaddr.cidr_abbrev_to_verbose(str(addr)))
elif addr.version == 6:
net = netaddr.IPNetwork(str(addr) + '/64')
if self._net is None:
if self.version == 4:
self._net = netaddr.IPNetwork(
netaddr.cidr_abbrev_to_verbose(str(self)))
elif self.version == 6:
self._net = netaddr.IPNetwork(str(self) + '/64')
if not allow_network and addr == net.network:
raise ValueError("cannot use IP network address")
if not allow_broadcast and addr.version == 4 and addr == net.broadcast:
raise ValueError("cannot use broadcast IP address")
if not allow_network and self == self._net.network:
raise ValueError("cannot use IP network address {}".format(addr))
if not allow_broadcast and (self.version == 4 and
self == self._net.broadcast):
raise ValueError("cannot use broadcast IP address {}".format(addr))
super(CheckedIPAddress, self).__init__(addr, flags=self.netaddr_ip_flags)
self.prefixlen = net.prefixlen
self.defaultnet = defnet
self.interface = iface
self.prefixlen = self._net.prefixlen
def is_local(self):
return self.interface is not None
def valid_ip(addr):
return netaddr.valid_ipv4(addr) or netaddr.valid_ipv6(addr)
@ -519,10 +539,14 @@ def dir_exists(filename):
except:
return False
def install_file(fname, dest):
# SELinux: use copy to keep the right context
if file_exists(dest):
os.rename(dest, dest + ".orig")
shutil.move(fname, dest)
shutil.copy(fname, dest)
os.remove(fname)
def backup_file(fname):
if file_exists(fname):
@ -1014,20 +1038,6 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
raise last_socket_error # pylint: disable=E0702
def host_exists(host):
"""
Resolve the host to see if it exists.
Returns True/False
"""
try:
socket.getaddrinfo(host, 80)
except socket.gaierror:
return False
else:
return True
def reverse_record_exists(ip_address):
"""
Checks if IP address have some reverse record somewhere.
@ -1044,96 +1054,6 @@ def reverse_record_exists(ip_address):
return True
def check_zone_overlap(zone, raise_on_error=True):
root_logger.info("Checking DNS domain %s, please wait ..." % zone)
if not isinstance(zone, DNSName):
zone = DNSName(zone).make_absolute()
# automatic empty zones always exist so checking them is pointless,
# do not report them to avoid meaningless error messages
if is_auto_empty_zone(zone):
return
try:
containing_zone = resolver.zone_for_name(zone)
except DNSException as e:
msg = ("DNS check for domain %s failed: %s." % (zone, e))
if raise_on_error:
raise ValueError(msg)
else:
root_logger.warning(msg)
return
if containing_zone == zone:
try:
ns = [ans.to_text() for ans in resolver.query(zone, 'NS')]
except DNSException as e:
root_logger.debug("Failed to resolve nameserver(s) for domain"
" {0}: {1}".format(zone, e))
ns = []
msg = u"DNS zone {0} already exists in DNS".format(zone)
if ns:
msg += u" and is handled by server(s): {0}".format(', '.join(ns))
raise ValueError(msg)
def is_auto_empty_zone(zone):
assert isinstance(zone, DNSName)
automatic_empty_zones = [DNSName(aez).make_absolute() for aez in [
# RFC 1918
"10.IN-ADDR.ARPA", "16.172.IN-ADDR.ARPA", "17.172.IN-ADDR.ARPA",
"18.172.IN-ADDR.ARPA", "19.172.IN-ADDR.ARPA", "20.172.IN-ADDR.ARPA",
"21.172.IN-ADDR.ARPA", "22.172.IN-ADDR.ARPA", "23.172.IN-ADDR.ARPA",
"24.172.IN-ADDR.ARPA", "25.172.IN-ADDR.ARPA", "26.172.IN-ADDR.ARPA",
"27.172.IN-ADDR.ARPA", "28.172.IN-ADDR.ARPA", "29.172.IN-ADDR.ARPA",
"30.172.IN-ADDR.ARPA", "31.172.IN-ADDR.ARPA", "168.192.IN-ADDR.ARPA",
# RFC 6598
"64.100.IN-ADDR.ARPA", "65.100.IN-ADDR.ARPA", "66.100.IN-ADDR.ARPA",
"67.100.IN-ADDR.ARPA", "68.100.IN-ADDR.ARPA", "69.100.IN-ADDR.ARPA",
"70.100.IN-ADDR.ARPA", "71.100.IN-ADDR.ARPA", "72.100.IN-ADDR.ARPA",
"73.100.IN-ADDR.ARPA", "74.100.IN-ADDR.ARPA", "75.100.IN-ADDR.ARPA",
"76.100.IN-ADDR.ARPA", "77.100.IN-ADDR.ARPA", "78.100.IN-ADDR.ARPA",
"79.100.IN-ADDR.ARPA", "80.100.IN-ADDR.ARPA", "81.100.IN-ADDR.ARPA",
"82.100.IN-ADDR.ARPA", "83.100.IN-ADDR.ARPA", "84.100.IN-ADDR.ARPA",
"85.100.IN-ADDR.ARPA", "86.100.IN-ADDR.ARPA", "87.100.IN-ADDR.ARPA",
"88.100.IN-ADDR.ARPA", "89.100.IN-ADDR.ARPA", "90.100.IN-ADDR.ARPA",
"91.100.IN-ADDR.ARPA", "92.100.IN-ADDR.ARPA", "93.100.IN-ADDR.ARPA",
"94.100.IN-ADDR.ARPA", "95.100.IN-ADDR.ARPA", "96.100.IN-ADDR.ARPA",
"97.100.IN-ADDR.ARPA", "98.100.IN-ADDR.ARPA", "99.100.IN-ADDR.ARPA",
"100.100.IN-ADDR.ARPA", "101.100.IN-ADDR.ARPA",
"102.100.IN-ADDR.ARPA", "103.100.IN-ADDR.ARPA",
"104.100.IN-ADDR.ARPA", "105.100.IN-ADDR.ARPA",
"106.100.IN-ADDR.ARPA", "107.100.IN-ADDR.ARPA",
"108.100.IN-ADDR.ARPA", "109.100.IN-ADDR.ARPA",
"110.100.IN-ADDR.ARPA", "111.100.IN-ADDR.ARPA",
"112.100.IN-ADDR.ARPA", "113.100.IN-ADDR.ARPA",
"114.100.IN-ADDR.ARPA", "115.100.IN-ADDR.ARPA",
"116.100.IN-ADDR.ARPA", "117.100.IN-ADDR.ARPA",
"118.100.IN-ADDR.ARPA", "119.100.IN-ADDR.ARPA",
"120.100.IN-ADDR.ARPA", "121.100.IN-ADDR.ARPA",
"122.100.IN-ADDR.ARPA", "123.100.IN-ADDR.ARPA",
"124.100.IN-ADDR.ARPA", "125.100.IN-ADDR.ARPA",
"126.100.IN-ADDR.ARPA", "127.100.IN-ADDR.ARPA",
# RFC 5735 and RFC 5737
"0.IN-ADDR.ARPA", "127.IN-ADDR.ARPA", "254.169.IN-ADDR.ARPA",
"2.0.192.IN-ADDR.ARPA", "100.51.198.IN-ADDR.ARPA",
"113.0.203.IN-ADDR.ARPA", "255.255.255.255.IN-ADDR.ARPA",
# Local IPv6 Unicast Addresses
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
# LOCALLY ASSIGNED LOCAL ADDRESS SCOPE
"D.F.IP6.ARPA", "8.E.F.IP6.ARPA", "9.E.F.IP6.ARPA", "A.E.F.IP6.ARPA",
"B.E.F.IP6.ARPA",
# Example Prefix, RFC 3849.
"8.B.D.0.1.0.0.2.IP6.ARPA",
# RFC 7534
"EMPTY.AS112.ARPA",
]]
return zone in automatic_empty_zones
def config_replace_variables(filepath, replacevars=dict(), appendvars=dict()):
"""
Take a key=value based configuration file, and write new version
@ -1557,3 +1477,22 @@ if six.PY2:
type(value).__name__))
else:
fsdecode = os.fsdecode #pylint: disable=no-member
def is_fips_enabled():
"""
Checks whether this host is FIPS-enabled.
Returns a boolean indicating if the host is FIPS-enabled, i.e. if the
file /proc/sys/crypto/fips_enabled contains a non-0 value. Otherwise,
or if the file /proc/sys/crypto/fips_enabled does not exist,
the function returns False.
"""
try:
with open(paths.PROC_FIPS_ENABLED, 'r') as f:
if f.read().strip() != '0':
return True
except IOError:
# Consider that the host is not fips-enabled if the file does not exist
pass
return False

View File

@ -18,6 +18,7 @@
#
import os
import six
from ipapython.ipautil import run
@ -45,7 +46,7 @@ def get_real_key(key):
One cannot request a key based on the description it was created with
so find the one we're looking for.
"""
assert isinstance(key, str)
assert isinstance(key, six.string_types)
result = run(['keyctl', 'search', KEYRING, KEYTYPE, key],
raiseonerr=False, capture_output=True)
if result.returncode:
@ -53,7 +54,7 @@ def get_real_key(key):
return result.raw_output.rstrip()
def get_persistent_key(key):
assert isinstance(key, str)
assert isinstance(key, six.string_types)
result = run(['keyctl', 'get_persistent', KEYRING, key],
raiseonerr=False, capture_output=True)
if result.returncode:
@ -73,7 +74,7 @@ def has_key(key):
"""
Returns True/False whether the key exists in the keyring.
"""
assert isinstance(key, str)
assert isinstance(key, six.string_types)
try:
get_real_key(key)
return True
@ -86,7 +87,7 @@ def read_key(key):
Use pipe instead of print here to ensure we always get the raw data.
"""
assert isinstance(key, str)
assert isinstance(key, six.string_types)
real_key = get_real_key(key)
result = run(['keyctl', 'pipe', real_key], raiseonerr=False,
capture_output=True)
@ -99,7 +100,7 @@ def update_key(key, value):
"""
Update the keyring data. If they key doesn't exist it is created.
"""
assert isinstance(key, str)
assert isinstance(key, six.string_types)
assert isinstance(value, bytes)
if has_key(key):
real_key = get_real_key(key)
@ -114,7 +115,7 @@ def add_key(key, value):
"""
Add a key to the kernel keyring.
"""
assert isinstance(key, str)
assert isinstance(key, six.string_types)
assert isinstance(value, bytes)
if has_key(key):
raise ValueError('key %s already exists' % key)
@ -127,7 +128,7 @@ def del_key(key):
"""
Remove a key from the keyring
"""
assert isinstance(key, str)
assert isinstance(key, six.string_types)
real_key = get_real_key(key)
result = run(['keyctl', 'unlink', real_key, KEYRING],
raiseonerr=False)

View File

@ -2,7 +2,7 @@
from __future__ import print_function
from ipaplatform.paths import paths
import ConfigParser
from six.moves.configparser import ConfigParser
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec
@ -154,7 +154,7 @@ class IPAKEMKeys(KEMKeysStore):
def __init__(self, config=None, ipaconf=paths.IPA_DEFAULT_CONF):
super(IPAKEMKeys, self).__init__(config)
conf = ConfigParser.ConfigParser()
conf = ConfigParser()
conf.read(ipaconf)
self.host = conf.get('global', 'host')
self.realm = conf.get('global', 'realm')

View File

@ -348,7 +348,7 @@ class StateFile:
"""
root_logger.debug("Saving StateFile to '%s'", self._path)
for module in self.modules.keys():
for module in list(self.modules.keys()):
if len(self.modules[module]) == 0:
del self.modules[module]

View File

@ -32,12 +32,14 @@ import six
from ipaserver.install import installutils
from ipaserver.install import service
from ipaserver.install.cainstance import IPA_CA_RECORD
from ipaserver.install import sysupgrade
from ipapython import sysrestore, ipautil, ipaldap
from ipapython.ipa_log_manager import *
from ipapython import dnsutil
from ipapython.dn import DN
import ipalib
from ipalib import api, errors
from ipalib.constants import IPA_CA_RECORD
from ipaplatform import services
from ipaplatform.constants import constants
from ipaplatform.paths import paths
@ -48,7 +50,7 @@ from ipalib.util import (validate_zonemgr_str, normalize_zonemgr,
normalize_zone, get_reverse_zone_default,
zone_is_reverse, validate_dnssec_global_forwarder,
DNSSECSignatureMissingError, EDNS0UnsupportedError,
UnresolvableRecordError, verify_host_resolvable)
UnresolvableRecordError)
from ipalib.constants import CACERT
if six.PY3:
@ -294,7 +296,7 @@ def read_reverse_zone(default, ip_address, allow_zone_overlap=False):
continue
if not allow_zone_overlap:
try:
ipautil.check_zone_overlap(zone, raise_on_error=False)
dnsutil.check_zone_overlap(zone, raise_on_error=False)
except ValueError as e:
root_logger.error("Reverse zone %s will not be used: %s"
% (zone, e))
@ -314,7 +316,7 @@ def get_auto_reverse_zones(ip_addresses):
continue
default_reverse = get_reverse_zone_default(ip)
try:
ipautil.check_zone_overlap(default_reverse)
dnsutil.check_zone_overlap(default_reverse)
except ValueError:
root_logger.info("Reverse zone %s for IP address %s already exists"
% (default_reverse, ip))
@ -461,7 +463,7 @@ def check_reverse_zones(ip_addresses, reverse_zones, options, unattended,
# isn't the zone managed by someone else
if not options.allow_zone_overlap:
try:
ipautil.check_zone_overlap(rz)
dnsutil.check_zone_overlap(rz)
except ValueError as e:
msg = "Reverse zone %s will not be used: %s" % (rz, e)
if unattended:
@ -610,8 +612,9 @@ class BindInstance(service.Service):
suffix = ipautil.dn_attribute_property('_suffix')
def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders, ntp,
reverse_zones, named_user=constants.NAMED_USER, zonemgr=None,
def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders,
forward_policy, ntp, reverse_zones,
named_user=constants.NAMED_USER, zonemgr=None,
ca_configured=None, no_dnssec_validation=False):
self.named_user = named_user
self.fqdn = fqdn
@ -619,6 +622,7 @@ class BindInstance(service.Service):
self.realm = realm_name
self.domain = domain_name
self.forwarders = forwarders
self.forward_policy = forward_policy
self.host = fqdn.split(".")[0]
self.suffix = ipautil.realm_to_suffix(self.realm)
self.ntp = ntp
@ -776,6 +780,7 @@ class BindInstance(service.Service):
REALM=self.realm,
SERVER_ID=installutils.realm_to_serverid(self.realm),
FORWARDERS=fwds,
FORWARD_POLICY=self.forward_policy,
SUFFIX=self.suffix,
OPTIONAL_NTP=optional_ntp,
ZONEMGR=self.zonemgr,
@ -872,14 +877,6 @@ class BindInstance(service.Service):
add_rr(self.domain, rname, "SRV", rdata, self.dns_backup,
api=self.api)
if not dns_zone_exists(zone, self.api):
# check if master hostname is resolvable
try:
verify_host_resolvable(fqdn, root_logger)
except errors.DNSNotARecordError:
root_logger.warning("Master FQDN (%s) is not resolvable.",
fqdn)
# Add forward and reverse records to self
for addr in addrs:
try:
@ -905,7 +902,7 @@ class BindInstance(service.Service):
if fqdn == self.fqdn:
continue
addrs = installutils.resolve_host(fqdn)
addrs = installutils.resolve_ip_addresses_nss(fqdn)
root_logger.debug("Adding DNS records for master %s" % fqdn)
self.__add_master_records(fqdn, addrs)
@ -961,7 +958,9 @@ class BindInstance(service.Service):
if dns_zone_exists(zone, self.api):
addrs = get_fwd_rr(zone, host, api=self.api)
else:
addrs = installutils.resolve_host(fqdn)
addrs = dnsutil.resolve_ip_addresses(fqdn)
# hack, will go away with locations
addrs = [str(addr) for addr in addrs]
self.__add_ipa_ca_records(fqdn, addrs, True)
@ -1031,6 +1030,12 @@ class BindInstance(service.Service):
section=NAMED_SECTION_OPTIONS,
str_val=False)
# prevent repeated upgrade on new installs
sysupgrade.set_upgrade_state(
'named.conf',
'forward_policy_conflict_with_empty_zones_handled', True
)
def __setup_resolv_conf(self):
if not self.fstore.has_file(RESOLV_CONF):
self.fstore.backup_file(RESOLV_CONF)
@ -1077,11 +1082,16 @@ class BindInstance(service.Service):
self.__add_ipa_ca_record()
def add_ipa_ca_dns_records(self, fqdn, domain_name, ca_configured=True):
if not self.api.Backend.ldap2.isconnected():
self.api.Backend.ldap2.connect(autobind=True)
host, zone = fqdn.split(".", 1)
if dns_zone_exists(zone, self.api):
addrs = get_fwd_rr(zone, host, api=self.api)
else:
addrs = installutils.resolve_host(fqdn)
addrs = dnsutil.resolve_ip_addresses(fqdn)
# hack, will go away with locations
addrs = [str(addr) for addr in addrs]
self.domain = domain_name
@ -1169,7 +1179,9 @@ class BindInstance(service.Service):
if dns_zone_exists(zone, self.api):
addrs = get_fwd_rr(zone, host, api=self.api)
else:
addrs = installutils.resolve_host(fqdn)
addrs = dnsutil.resolve_ip_addresses(fqdn)
# hack, will go away with locations
addrs = [str(addr) for addr in addrs]
for addr in addrs:
del_fwd_rr(domain_name, IPA_CA_RECORD, addr, api=self.api)

View File

@ -186,7 +186,11 @@ def install_step_1(standalone, replica_config, options):
ca.stop('pki-tomcat')
# We need to ldap_enable the CA now that DS is up and running
ca.ldap_enable('CA', host_name, dm_password, basedn, ['caRenewalMaster'])
if replica_config is None:
config = ['caRenewalMaster']
else:
config = []
ca.ldap_enable('CA', host_name, dm_password, basedn, config)
# This is done within stopped_service context, which restarts CA
ca.enable_client_auth_to_db(paths.CA_CS_CFG_PATH)

View File

@ -43,6 +43,7 @@ import pipes
from six.moves import urllib
from six.moves.configparser import ConfigParser, RawConfigParser
import ipalib.constants
from ipalib import api
from ipalib import pkcs10, x509
from ipalib import errors
@ -62,6 +63,7 @@ from ipapython.ipa_log_manager import log_mgr,\
standard_logging_setup, root_logger
from ipaserver.install import certs
from ipaserver.install import bindinstance
from ipaserver.install import dsinstance
from ipaserver.install import installutils
from ipaserver.install import ldapupdate
@ -79,10 +81,6 @@ except ImportError:
import http.client as httplib
# When IPA is installed with DNS support, this CNAME should hold all IPA
# replicas with CA configured
IPA_CA_RECORD = "ipa-ca"
# We need to reset the template because the CA uses the regular boot
# information
INF_TEMPLATE = """
@ -311,7 +309,7 @@ class CAInstance(DogtagInstance):
server_cert_name = 'Server-Cert cert-pki-ca'
def __init__(self, realm=None, ra_db=None, host_name=None,
dm_password=None, ldapi=True):
dm_password=None, ldapi=True, api=api):
super(CAInstance, self).__init__(
realm=realm,
subsystem="CA",
@ -327,6 +325,7 @@ class CAInstance(DogtagInstance):
self.cert_file = None
self.cert_chain_file = None
self.create_ra_agent_db = True
self.api = api
if realm is not None:
self.canickname = get_ca_nickname(realm)
@ -1289,7 +1288,15 @@ class CAInstance(DogtagInstance):
def __enable_instance(self):
basedn = ipautil.realm_to_suffix(self.realm)
self.ldap_enable('CA', self.fqdn, None, basedn, ['caRenewalMaster'])
self.ldap_enable('CA', self.fqdn, None, basedn)
def __update_ca_records(self):
# Install CA DNS records
if bindinstance.dns_container_exists(
api.env.host, api.env.basedn, ldapi=True, realm=api.env.realm
):
bind = bindinstance.BindInstance(ldapi=True, api=self.api)
bind.add_ipa_ca_dns_records(api.env.host, api.env.domain)
def configure_replica(self, master_host, subject_base=None,
ca_cert_bundle=None, ca_signing_algorithm=None,
@ -1359,6 +1366,7 @@ class CAInstance(DogtagInstance):
self.__restart_http_instance)
self.step("enabling CA instance", self.__enable_instance)
self.step("Updating DNS CA records", self.__update_ca_records)
self.start_creation(runtime=210)
@ -1619,14 +1627,18 @@ def configure_profiles_acl():
conn.disconnect()
return updated
def import_included_profiles():
def __get_profile_config(profile_id):
sub_dict = dict(
DOMAIN=ipautil.format_netloc(api.env.domain),
IPA_CA_RECORD=IPA_CA_RECORD,
IPA_CA_RECORD=ipalib.constants.IPA_CA_RECORD,
CRL_ISSUER='CN=Certificate Authority,o=ipaca',
SUBJECT_DN_O=dsinstance.DsInstance().find_subject_base(),
)
return ipautil.template_file(
'/usr/share/ipa/profiles/{}.cfg'.format(profile_id), sub_dict)
def import_included_profiles():
server_id = installutils.realm_to_serverid(api.env.realm)
dogtag_uri = 'ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' % server_id
conn = ldap2.ldap2(api, ldap_uri=dogtag_uri)
@ -1663,15 +1675,56 @@ def import_included_profiles():
ipacertprofilestoreissued=['TRUE' if store_issued else 'FALSE'],
)
conn.add_entry(entry)
profile_data = ipautil.template_file(
'/usr/share/ipa/profiles/{}.cfg'.format(profile_id), sub_dict)
_create_dogtag_profile(profile_id, profile_data)
# Create the profile, replacing any existing profile of same name
profile_data = __get_profile_config(profile_id)
_create_dogtag_profile(profile_id, profile_data, overwrite=True)
root_logger.info("Imported profile '%s'", profile_id)
api.Backend.ra_certprofile.override_port = None
conn.disconnect()
def repair_profile_caIPAserviceCert():
"""
A regression caused replica installation to replace the FreeIPA
version of caIPAserviceCert with the version shipped by Dogtag.
This function detects and repairs occurrences of this problem.
"""
api.Backend.ra_certprofile._read_password()
api.Backend.ra_certprofile.override_port = 8443
profile_id = 'caIPAserviceCert'
with api.Backend.ra_certprofile as profile_api:
try:
cur_config = profile_api.read_profile(profile_id).splitlines()
except errors.RemoteRetrieveError as e:
# no profile there to check/repair
api.Backend.ra_certprofile.override_port = None
return
indicators = [
"policyset.serverCertSet.1.default.params.name="
"CN=$request.req_subject_name.cn$, OU=pki-ipa, O=IPA ",
"policyset.serverCertSet.9.default.params.crlDistPointsPointName_0="
"https://ipa.example.com/ipa/crl/MasterCRL.bin",
]
need_repair = all(l in cur_config for l in indicators)
if need_repair:
root_logger.debug(
"Detected that profile '{}' has been replaced with "
"incorrect version; begin repair.".format(profile_id))
_create_dogtag_profile(
profile_id, __get_profile_config(profile_id), overwrite=True)
root_logger.debug("Repair of profile '{}' complete.".format(profile_id))
api.Backend.ra_certprofile.override_port = None
def migrate_profiles_to_ldap():
"""Migrate profiles from filesystem to LDAP.
@ -1717,12 +1770,17 @@ def migrate_profiles_to_ldap():
profile_data += '\n'
profile_data += 'profileId={}\n'.format(profile_id)
profile_data += 'classId={}\n'.format(class_id)
_create_dogtag_profile(profile_id, profile_data)
# Import the profile, but do not replace it if it already exists.
# This prevents replicas from replacing IPA-managed profiles with
# Dogtag default profiles of same name.
#
_create_dogtag_profile(profile_id, profile_data, overwrite=False)
api.Backend.ra_certprofile.override_port = None
def _create_dogtag_profile(profile_id, profile_data):
def _create_dogtag_profile(profile_id, profile_data, overwrite):
with api.Backend.ra_certprofile as profile_api:
# import the profile
try:
@ -1733,9 +1791,8 @@ def _create_dogtag_profile(profile_id, profile_data):
root_logger.debug("Error migrating '{}': {}".format(
profile_id, e))
# conflicting profile; replace it if we are
# installing IPA, but keep it for upgrades
if api.env.context == 'installer':
# profile already exists
if overwrite:
try:
profile_api.disable_profile(profile_id)
except errors.RemoteRetrieveError:

View File

@ -655,7 +655,7 @@ class CertDB(object):
subject=host,
passwd_fname=self.passwd_fname)
# Now wait for the cert to appear. Check three times then abort
certmonger.wait_for_request(reqid, timeout=15)
certmonger.wait_for_request(reqid, timeout=60)
class _CrossProcessLock(object):

View File

@ -4,7 +4,7 @@ from ipapython.secrets.kem import IPAKEMKeys
from ipapython.secrets.client import CustodiaClient
from ipaplatform.paths import paths
from ipaplatform.constants import constants
from service import SimpleServiceInstance
from ipaserver.install.service import SimpleServiceInstance
from ipapython import ipautil
from ipapython.ipa_log_manager import root_logger
from ipaserver.install import installutils

View File

@ -118,7 +118,7 @@ def install_check(standalone, api, replica, options, hostname):
domain = dnsutil.DNSName(util.normalize_zone(api.env.domain))
print("Checking DNS domain %s, please wait ..." % domain)
try:
ipautil.check_zone_overlap(domain, raise_on_error=False)
dnsutil.check_zone_overlap(domain, raise_on_error=False)
except ValueError as e:
if options.force or options.allow_zone_overlap:
root_logger.warning("%s Please make sure that the domain is "
@ -129,7 +129,7 @@ def install_check(standalone, api, replica, options, hostname):
for reverse_zone in options.reverse_zones:
try:
ipautil.check_zone_overlap(reverse_zone)
dnsutil.check_zone_overlap(reverse_zone)
except ValueError as e:
if options.force or options.allow_zone_overlap:
root_logger.warning(e.message)
@ -259,6 +259,17 @@ def install_check(standalone, api, replica, options, hostname):
ip_addresses = get_server_ip_address(hostname, options.unattended,
True, options.ip_addresses)
if not options.forward_policy:
# user did not specify policy, derive it: default is 'first' but
# if any of local IP addresses belongs to private ranges use 'only'
options.forward_policy = 'first'
for ip in ip_addresses:
if dnsutil.inside_auto_empty_zone(dnsutil.DNSName(ip.reverse_dns)):
options.forward_policy = 'only'
root_logger.debug('IP address %s belongs to a private range, '
'using forward policy only', ip)
break
if options.no_forwarders:
options.forwarders = []
elif options.forwarders or options.auto_forwarders:
@ -318,8 +329,8 @@ def install(standalone, replica, options, api=api):
bind = bindinstance.BindInstance(fstore, ldapi=True, api=api,
autobind=AUTOBIND_ENABLED)
bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
options.forwarders, conf_ntp, reverse_zones,
zonemgr=options.zonemgr,
options.forwarders, options.forward_policy, conf_ntp,
reverse_zones, zonemgr=options.zonemgr,
no_dnssec_validation=options.no_dnssec_validation,
ca_configured=options.setup_ca)

View File

@ -57,7 +57,7 @@ from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
from ipapython import certmonger
from ipapython import dnsutil
if six.PY3:
unicode = str
@ -447,23 +447,39 @@ def create_keytab(path, principal):
kadmin("ktadd -k " + path + " " + principal)
def resolve_host(host_name):
def resolve_ip_addresses_nss(fqdn):
"""Get list of IP addresses for given host (using NSS/getaddrinfo).
:returns:
list of IP addresses as UnsafeIPAddress objects
"""
# make sure the name is fully qualified
# so search path from resolv.conf does not apply
fqdn = str(dnsutil.DNSName(fqdn).make_absolute())
try:
addrinfos = socket.getaddrinfo(host_name, None,
addrinfos = socket.getaddrinfo(fqdn, None,
socket.AF_UNSPEC, socket.SOCK_STREAM)
except socket.error as ex:
if ex.errno == socket.EAI_NODATA or ex.errno == socket.EAI_NONAME:
root_logger.debug('Name %s does not have any address: %s',
fqdn, ex)
return set()
else:
raise
ip_list = []
for ai in addrinfos:
ip = ai[4][0]
if ip == "127.0.0.1" or ip == "::1":
raise HostnameLocalhost("The hostname resolves to the localhost address")
ip_list.append(ip)
return ip_list
except socket.error:
return []
# accept whatever we got from NSS
ip_addresses = set()
for ai in addrinfos:
try:
ip = ipautil.UnsafeIPAddress(ai[4][0])
except ValueError as ex:
# getaddinfo may return link-local address other similar oddities
# which are not accepted by CheckedIPAddress - skip these
root_logger.warning('Name %s resolved to an unacceptable IP '
'address %s: %s', fqdn, ai[4][0], ex)
else:
ip_addresses.add(ip)
root_logger.debug('Name %s resolved to %s', fqdn, ip_addresses)
return ip_addresses
def get_host_name(no_host_dns):
"""
@ -479,10 +495,9 @@ def get_host_name(no_host_dns):
return hostname
def get_server_ip_address(host_name, unattended, setup_dns, ip_addresses):
# Check we have a public IP that is associated with the hostname
try:
hostaddr = resolve_host(host_name)
except HostnameLocalhost:
hostaddr = resolve_ip_addresses_nss(host_name)
if hostaddr.intersection(
{ipautil.UnsafeIPAddress(ip) for ip in ['127.0.0.1', '::1']}):
print("The hostname resolves to the localhost address (127.0.0.1/::1)", file=sys.stderr)
print("Please change your /etc/hosts file so that the hostname", file=sys.stderr)
print("resolves to the ip address of your network interface.", file=sys.stderr)

View File

@ -520,6 +520,11 @@ class ReplicaPrepare(admintool.AdminTool):
if not options.no_reverse:
reverse_zone = bindinstance.find_reverse_zone(ip)
if reverse_zone is None:
self.log.warning(
"Could not find any IPA managed reverse zone. "
"Not creating PTR records")
return
try:
add_ptr_rr(reverse_zone, ip_address, self.replica_fqdn)
except errors.PublicError as e:

View File

@ -42,6 +42,7 @@ class update_ca_renewal_master(Updater):
ldap = self.api.Backend.ldap2
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
self.api.env.basedn)
dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn)
filter = '(&(cn=CA)(ipaConfigString=caRenewalMaster))'
try:
entries = ldap.get_entries(base_dn=base_dn, filter=filter,
@ -50,7 +51,27 @@ class update_ca_renewal_master(Updater):
pass
else:
self.debug("found CA renewal master %s", entries[0].dn[1].value)
return False, []
master = False
updates = []
for entry in entries:
if entry.dn == dn:
master = True
continue
updates.append({
'dn': entry.dn,
'updates': [
dict(action='remove', attr='ipaConfigString',
value='caRenewalMaster')
],
})
if master:
return False, updates
else:
return False, []
criteria = {
'cert-database': paths.HTTPD_ALIAS_DIR,
@ -95,7 +116,6 @@ class update_ca_renewal_master(Updater):
"assuming local CA is renewal slave", config)
return (False, False, [])
dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn)
update = {
'dn': dn,
'updates': [

View File

@ -27,10 +27,129 @@ from ldif import LDIFWriter
from ipalib import api, errors, util
from ipalib import Updater
from ipapython.dn import DN
from ipapython import dnsutil
from ipalib.plugins.dns import dns_container_exists
from ipapython.ipa_log_manager import *
class DNSUpdater(Updater):
backup_dir = u'/var/lib/ipa/backup/'
# override backup_filename in subclass, it will be mangled by strftime
backup_filename = None
def __init__(self, api):
super(DNSUpdater, self).__init__(api)
backup_path = u'%s%s' % (self.backup_dir, self.backup_filename)
self.backup_path = time.strftime(backup_path)
self._ldif_writer = None
self._saved_privileges = set() # store privileges only once
self.saved_zone_to_privilege = {}
def version_update_needed(self, target_version):
"""Test if IPA DNS version is smaller than target version."""
assert isinstance(target_version, int)
try:
return int(self.api.Command['dnsconfig_show'](
all=True)['result']['ipadnsversion'][0]) < target_version
except errors.NotFound:
# IPA DNS is not configured
return False
@property
def ldif_writer(self):
if not self._ldif_writer:
self.log.info('Original zones will be saved in LDIF format in '
'%s file' % self.backup_path)
self._ldif_writer = LDIFWriter(open(self.backup_path, 'w'))
return self._ldif_writer
def backup_zone(self, zone):
"""Backup zone object, its records, permissions, and privileges.
Mapping from zone to privilege (containing zone's permissions)
will be stored in saved_zone_to_privilege dict for further usage.
"""
dn = str(zone['dn'])
del zone['dn'] # dn shouldn't be as attribute in ldif
self.ldif_writer.unparse(dn, zone)
ldap = self.api.Backend.ldap2
if 'managedBy' in zone:
permission = ldap.get_entry(DN(zone['managedBy'][0]))
self.ldif_writer.unparse(str(permission.dn), dict(permission.raw))
for privilege_dn in permission.get('member', []):
# privileges can be shared by multiples zones
if privilege_dn not in self._saved_privileges:
self._saved_privileges.add(privilege_dn)
privilege = ldap.get_entry(privilege_dn)
self.ldif_writer.unparse(str(privilege.dn),
dict(privilege.raw))
# remember privileges referened by permission
if 'member' in permission:
self.saved_zone_to_privilege[
zone['idnsname'][0]
] = permission['member']
if 'idnszone' in zone['objectClass']:
# raw values are required to store into ldif
records = self.api.Command['dnsrecord_find'](zone['idnsname'][0],
all=True,
raw=True,
sizelimit=0)['result']
for record in records:
if record['idnsname'][0] == u'@':
# zone record was saved before
continue
dn = str(record['dn'])
del record['dn']
self.ldif_writer.unparse(dn, record)
class update_ipaconfigstring_dnsversion_to_ipadnsversion(Updater):
"""
IPA <= 4.3.1 used ipaConfigString "DNSVersion 1" on DNS container.
This was hard to deal with in API so from IPA 4.3.2 we are using
new ipaDNSVersion attribute with integer syntax.
Old ipaConfigString is left there for now so if someone accidentally
executes upgrade on an old replica again it will not re-upgrade the data.
"""
def execute(self, **options):
ldap = self.api.Backend.ldap2
dns_container_dn = DN(self.api.env.container_dns, self.api.env.basedn)
try:
container_entry = ldap.get_entry(dns_container_dn)
except errors.NotFound:
# DNS container not found, nothing to upgrade
return False, []
if 'ipadnscontainer' in [
o.lower() for o in container_entry['objectclass']
]:
# version data are already migrated
return False, []
self.log.debug('Migrating DNS ipaConfigString to ipaDNSVersion')
container_entry['objectclass'].append('ipadnscontainer')
version = 0
for config_option in container_entry.get("ipaConfigString", []):
matched = re.match("^DNSVersion\s+(?P<version>\d+)$",
config_option, flags=re.I)
if matched:
version = int(matched.group("version"))
else:
self.log.error(
'Failed to parse DNS version from ipaConfigString, '
'defaulting to version %s', version)
container_entry['ipadnsversion'] = version
ldap.update_entry(container_entry)
self.log.debug('ipaDNSVersion = %s', version)
return False, []
api.register(update_ipaconfigstring_dnsversion_to_ipadnsversion)
class update_dnszones(Updater):
"""
Update all zones to meet requirements in the new FreeIPA versions
@ -141,7 +260,7 @@ class update_dns_limits(Updater):
api.register(update_dns_limits)
class update_master_to_dnsforwardzones(Updater):
class update_master_to_dnsforwardzones(DNSUpdater):
"""
Update all zones to meet requirements in the new FreeIPA versions
@ -149,35 +268,22 @@ class update_master_to_dnsforwardzones(Updater):
than none, will be tranformed to forward zones.
Original masters zone will be backed up to ldif file.
This should be applied only once, and only if original version was lower than 4.0
This should be applied only once,
and only if original version was lower than 4.0
"""
backup_dir = u'/var/lib/ipa/backup/'
backup_filename = u'dns-forward-zones-backup-%Y-%m-%d-%H-%M-%S.ldif'
backup_path = u'%s%s' % (backup_dir, backup_filename)
backup_filename = u'dns-master-to-forward-zones-%Y-%m-%d-%H-%M-%S.ldif'
def execute(self, **options):
ldap = self.api.Backend.ldap2
# check LDAP if forwardzones already uses new semantics
dns_container_dn = DN(self.api.env.container_dns, self.api.env.basedn)
try:
container_entry = ldap.get_entry(dns_container_dn)
except errors.NotFound:
# DNS container not found, nothing to upgrade
if not self.version_update_needed(target_version=1):
# forwardzones already uses new semantics,
# no upgrade is required
return False, []
for config_option in container_entry.get("ipaConfigString", []):
matched = re.match("^DNSVersion\s+(?P<version>\d+)$",
config_option, flags=re.I)
if matched and int(matched.group("version")) >= 1:
# forwardzones already uses new semantics,
# no upgrade is required
return False, []
self.log.debug('Updating forward zones')
# update the DNSVersion, following upgrade can be executed only once
container_entry.setdefault(
'ipaConfigString', []).append(u"DNSVersion 1")
ldap.update_entry(container_entry)
self.api.Command['dnsconfig_mod'](ipadnsversion=1)
# Updater in IPA version from 4.0 to 4.1.2 doesn't work well, this
# should detect if update in past has been executed, and set proper
@ -217,77 +323,18 @@ class update_master_to_dnsforwardzones(Updater):
zones_to_transform.append(zone)
if zones_to_transform:
# add time to filename
self.backup_path = time.strftime(self.backup_path)
# DNs of privileges which contain dns managed permissions
privileges_to_ldif = set() # store priviledges only once
zone_to_privileges = {} # zone: [privileges cn]
self.log.info('Zones with specified forwarders with policy different'
' than none will be transformed to forward zones.')
self.log.info('Original zones will be saved in LDIF format in '
'%s file' % self.backup_path)
try:
with open(self.backup_path, 'w') as f:
writer = LDIFWriter(f)
for zone in zones_to_transform:
# save backup to ldif
try:
dn = str(zone['dn'])
del zone['dn'] # dn shouldn't be as attribute in ldif
writer.unparse(dn, zone)
if 'managedBy' in zone:
entry = ldap.get_entry(DN(zone['managedBy'][0]))
for privilege_member_dn in entry.get('member', []):
privileges_to_ldif.add(privilege_member_dn)
writer.unparse(str(entry.dn), dict(entry.raw))
# privileges where permission is used
if entry.get('member'):
zone_to_privileges[zone['idnsname'][0]] = entry['member']
# raw values are required to store into ldif
records = self.api.Command['dnsrecord_find'](
zone['idnsname'][0],
all=True,
raw=True,
sizelimit=0)['result']
for record in records:
if record['idnsname'][0] == u'@':
# zone record was saved before
continue
dn = str(record['dn'])
del record['dn']
writer.unparse(dn, record)
except Exception as e:
self.log.error('Unable to backup zone %s' %
zone['idnsname'][0])
self.log.error(traceback.format_exc())
return False, []
for privilege_dn in privileges_to_ldif:
try:
entry = ldap.get_entry(privilege_dn)
writer.unparse(str(entry.dn), dict(entry.raw))
except Exception as e:
self.log.error('Unable to backup privilege %s' %
privilege_dn)
self.log.error(traceback.format_exc())
return False, []
f.close()
except Exception:
self.log.error('Unable to create backup file')
self.log.error(traceback.format_exc())
return False, []
# update
for zone in zones_to_transform:
try:
self.backup_zone(zone)
except Exception:
self.log.error('Unable to create backup for zone, '
'terminating zone upgrade')
self.log.error(traceback.format_exc())
return False, []
# delete master zone
try:
self.api.Command['dnszone_del'](zone['idnsname'])
@ -303,7 +350,9 @@ class update_master_to_dnsforwardzones(Updater):
try:
kw = {
'idnsforwarders': zone.get('idnsforwarders', []),
'idnsforwardpolicy': zone.get('idnsforwardpolicy', [u'first'])[0]
'idnsforwardpolicy': zone.get('idnsforwardpolicy',
[u'first'])[0],
'skip_overlap_check': True,
}
self.api.Command['dnsforwardzone_add'](zone['idnsname'][0], **kw)
except Exception as e:
@ -329,9 +378,9 @@ class update_master_to_dnsforwardzones(Updater):
continue
else:
if zone['idnsname'][0] in zone_to_privileges:
if zone['idnsname'][0] in self.saved_zone_to_privilege:
privileges = [
dn[0].value for dn in zone_to_privileges[zone['idnsname'][0]]
dn[0].value for dn in self.saved_zone_to_privilege[zone['idnsname'][0]]
]
try:
self.api.Command['permission_add_member'](perm_name,
@ -352,3 +401,97 @@ class update_master_to_dnsforwardzones(Updater):
return False, []
api.register(update_master_to_dnsforwardzones)
class update_dnsforward_emptyzones(DNSUpdater):
"""
Migrate forward policies which conflict with automatic empty zones
(RFC 6303) to use forward policy = only.
BIND ignores conflicting forwarding configuration
when forwarding policy != only.
bind-dyndb-ldap 9.0+ will do the same so we have to adjust FreeIPA zones
accordingly.
"""
backup_filename = u'dns-forwarding-empty-zones-%Y-%m-%d-%H-%M-%S.ldif'
def update_zones(self):
try:
fwzones = self.api.Command.dnsforwardzone_find(all=True,
raw=True)['result']
except errors.NotFound:
# No forwardzones found, we are done
return
logged_once = False
for zone in fwzones:
if not (
dnsutil.related_to_auto_empty_zone(
dnsutil.DNSName(zone.get('idnsname')[0]))
and zone.get('idnsforwardpolicy', [u'first'])[0] != u'only'
and zone.get('idnsforwarders', []) != []
):
# this zone does not conflict with automatic empty zone
continue
if not logged_once:
self.log.info('Forward policy for zones conflicting with '
'automatic empty zones will be changed to '
'"only"')
logged_once = True
# backup
try:
self.backup_zone(zone)
except Exception:
self.log.error('Unable to create backup for zone %s, '
'terminating zone upgrade', zone['idnsname'][0])
self.log.error(traceback.format_exc())
continue
# change forward policy
try:
self.api.Command['dnsforwardzone_mod'](
zone['idnsname'][0],
idnsforwardpolicy=u'only'
)
except Exception as e:
self.log.error('Forward policy update for zone %s failed '
'(%s)' % (zone['idnsname'][0], e))
self.log.error(traceback.format_exc())
continue
self.log.debug('Zone %s was sucessfully modified to use '
'forward policy "only"', zone['idnsname'][0])
def update_global_ldap_forwarder(self):
config = self.api.Command['dnsconfig_show'](all=True,
raw=True)['result']
if (
config.get('idnsforwardpolicy', [u'first'])[0] == u'first'
and config.get('idnsforwarders', [])
):
self.log.info('Global forward policy in LDAP for all servers will '
'be changed to "only" to avoid conflicts with '
'automatic empty zones')
self.backup_zone(config)
self.api.Command['dnsconfig_mod'](idnsforwardpolicy=u'only')
def execute(self, **options):
# check LDAP if DNS subtree already uses new semantics
if not self.version_update_needed(target_version=2):
# forwardzones already use new semantics, no upgrade is required
return False, []
self.log.debug('Updating forwarding policies in LDAP '
'to avoid conflicts with automatic empty zones')
# update the DNSVersion, following upgrade can be executed only once
self.api.Command['dnsconfig_mod'](ipadnsversion=2)
self.update_zones()
if dnsutil.has_empty_zone_addresses(self.api.env.host):
self.update_global_ldap_forwarder()
return False, []
api.register(update_dnsforward_emptyzones)

View File

@ -1354,6 +1354,7 @@ class ReplicationManager(object):
'cn': ['clean %d' % replicaId],
'replica-base-dn': [self.db_suffix],
'replica-id': [replicaId],
'replica-force-cleaning': ['yes'],
}
)
try:

View File

@ -14,8 +14,7 @@ from ipapython.install import common, core
from ipapython.install.core import Knob
from ipalib.util import validate_domain_name
from ipaserver.install import bindinstance
from ipapython.ipautil import check_zone_overlap
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import check_zone_overlap
if six.PY3:
unicode = str
@ -170,6 +169,11 @@ class BaseServerDNS(common.Installable, core.Group, core.Composite):
cli_name='forwarder',
)
forward_policy = Knob(
{'only', 'first'}, None,
description=("DNS forwarding policy for global forwarders"),
)
auto_forwarders = Knob(
bool, False,
description="Use DNS forwarders configured in /etc/resolv.conf",
@ -432,6 +436,10 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
raise RuntimeError(
"You cannot specify a --no-forwarders option without the "
"--setup-dns option")
if self.dns.forward_policy:
raise RuntimeError(
"You cannot specify a --forward-policy option without the "
"--setup-dns option")
if self.dns.reverse_zones:
raise RuntimeError(
"You cannot specify a --reverse-zone option without the "

View File

@ -23,7 +23,8 @@ from ipapython.install.common import step
from ipapython.install.core import Knob
from ipapython.ipa_log_manager import root_logger
from ipapython.ipautil import (
decrypt_file, format_netloc, ipa_generate_password, run, user_input)
decrypt_file, format_netloc, ipa_generate_password, run, user_input,
is_fips_enabled)
from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
@ -437,6 +438,10 @@ def install_check(installer):
external_ca_file = installer._external_ca_file
http_ca_cert = installer._ca_cert
if is_fips_enabled():
raise RuntimeError(
"Installing IPA server in FIPS mode is not supported")
tasks.check_selinux_status()
if options.master_password:
@ -759,10 +764,11 @@ def install_check(installer):
if options.setup_dns:
print("BIND DNS server will be configured to serve IPA domain with:")
print("Forwarders: %s" % (
print("Forwarders: %s" % (
"No forwarders" if not options.forwarders
else ", ".join([str(ip) for ip in options.forwarders])
))
print('Forward policy: %s' % options.forward_policy)
print("Reverse zone(s): %s" % (
"No reverse zone" if options.no_reverse or not dns.reverse_zones
else ", ".join(str(rz) for rz in dns.reverse_zones)
@ -992,7 +998,7 @@ def install(installer):
# Create a BIND instance
bind = bindinstance.BindInstance(fstore, dm_password)
bind.setup(host_name, ip_addresses, realm_name,
domain_name, (), not options.no_ntp, (),
domain_name, (), 'first', not options.no_ntp, (),
zonemgr=options.zonemgr, ca_configured=setup_ca,
no_dnssec_validation=options.no_dnssec_validation)
bind.create_sample_bind_zone()
@ -1400,6 +1406,7 @@ class Server(BaseServer):
int, constants.MAX_DOMAIN_LEVEL,
description="IPA domain level",
cli_name='domain-level',
deprecated=True,
)
@domainlevel.validator

View File

@ -497,6 +497,10 @@ def install_check(installer):
options = installer
filename = installer.replica_file
if ipautil.is_fips_enabled():
raise RuntimeError(
"Installing IPA server in FIPS mode is not supported")
tasks.check_selinux_status()
if is_ipa_configured():
@ -939,6 +943,33 @@ def ensure_enrolled(installer):
except Exception:
sys.exit("Configuration of client side components failed!")
def promotion_check_ipa_domain(master_ldap_conn, basedn):
entry = master_ldap_conn.get_entry(basedn, ['associatedDomain'])
if not 'associatedDomain' in entry:
raise RuntimeError('IPA domain not found in LDAP.')
if len(entry['associatedDomain']) > 1:
root_logger.critical(
"Multiple IPA domains found. We are so sorry :-(, you are "
"probably experiencing this bug "
"https://fedorahosted.org/freeipa/ticket/5976. Please contact us "
"for help.")
raise RuntimeError(
'Multiple IPA domains found in LDAP database ({domains}). '
'Only one domain is allowed.'.format(
domains=u', '.join(entry['associatedDomain'])
))
if entry['associatedDomain'][0] != api.env.domain:
raise RuntimeError(
"Cannot promote this client to a replica. Local domain "
"'{local}' does not match IPA domain '{ipadomain}'. ".format(
local=api.env.domain,
ipadomain=entry['associatedDomain'][0]
))
@common_cleanup
@preserve_enrollment_state
def promote_check(installer):
@ -1137,6 +1168,8 @@ def promote_check(installer):
conn.disconnect()
conn.connect(ccache=ccache)
promotion_check_ipa_domain(conn, remote_api.env.basedn)
# Check that we don't already have a replication agreement
try:
(acn, adn) = replman.agreement_dn(config.host_name)
@ -1444,7 +1477,8 @@ def promote(installer):
ca = cainstance.CAInstance(config.realm_name, certs.NSS_DIR,
host_name=config.host_name,
dm_password=config.dirman_password)
dm_password=config.dirman_password,
api=remote_api)
ca.configure_replica(config.ca_host_name,
subject_base=config.subject_base,
ca_cert_bundle=ca_data)

View File

@ -24,6 +24,7 @@ from ipapython import ipautil, sysrestore, version, certdb
from ipapython import ipaldap
from ipapython.ipa_log_manager import *
from ipapython import certmonger
from ipapython import dnsutil
from ipapython.dn import DN
from ipaplatform.constants import constants
from ipaplatform.paths import paths
@ -793,6 +794,50 @@ def named_root_key_include():
return True
def named_update_global_forwarder_policy():
bind = bindinstance.BindInstance()
if not bindinstance.named_conf_exists() or not bind.is_configured():
# DNS service may not be configured
root_logger.info('DNS is not configured')
return False
root_logger.info('[Checking global forwarding policy in named.conf '
'to avoid conflicts with automatic empty zones]')
if sysupgrade.get_upgrade_state(
'named.conf', 'forward_policy_conflict_with_empty_zones_handled'
):
# upgrade was done already
return False
sysupgrade.set_upgrade_state(
'named.conf',
'forward_policy_conflict_with_empty_zones_handled',
True
)
if not dnsutil.has_empty_zone_addresses(api.env.host):
# guess: local server does not have IP addresses from private ranges
# so hopefully automatic empty zones are not a problem
return False
if bindinstance.named_conf_get_directive(
'forward',
section=bindinstance.NAMED_SECTION_OPTIONS,
str_val=False
) == 'only':
return False
root_logger.info('Global forward policy in named.conf will '
'be changed to "only" to avoid conflicts with '
'automatic empty zones')
bindinstance.named_conf_set_directive(
'forward',
'only',
section=bindinstance.NAMED_SECTION_OPTIONS,
str_val=False
)
return True
def certificate_renewal_update(ca, ds, http):
"""
Update certmonger certificate renewal configuration.
@ -1464,6 +1509,7 @@ def upgrade_configuration():
sub_dict['SUBJECT_BASE'] = subject_base
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
ca_running = ca.is_running()
with installutils.stopped_service('pki-tomcatd', 'pki-tomcat'):
# Dogtag must be stopped to be able to backup CS.cfg config
@ -1497,6 +1543,12 @@ def upgrade_configuration():
)
upgrade_pki(ca, fstore)
# several upgrade steps require running CA. If CA is configured,
# always run ca.start() because we need to wait until CA is really ready
# by checking status using http
if ca.is_configured():
ca.start('pki-tomcat')
certmonger_service = services.knownservices.certmonger
if ca.is_configured() and not certmonger_service.is_running():
certmonger_service.start()
@ -1616,6 +1668,7 @@ def upgrade_configuration():
named_bindkey_file_option(),
named_managed_keys_dir_option(),
named_root_key_include(),
named_update_global_forwarder_policy(),
mask_named_regular(),
fix_dyndb_ldap_workdir_permissions(),
)
@ -1658,6 +1711,9 @@ def upgrade_configuration():
ca_import_included_profiles(ca)
add_default_caacl(ca)
if ca.is_configured():
cainstance.repair_profile_caIPAserviceCert()
set_sssd_domain_option('ipa_server_mode', 'True')
if ds_running and not ds.is_running():
@ -1665,6 +1721,12 @@ def upgrade_configuration():
elif not ds_running and ds.is_running():
ds.stop(ds_serverid)
if ca.is_configured():
if ca_running and not ca.is_running():
ca.start('pki-tomcat')
elif not ca_running and ca.is_running():
ca.stop('pki-tomcat')
def upgrade_check(options):
try:

View File

@ -171,6 +171,8 @@ def fix_apache_semaphores(master):
def unapply_fixes(host):
restore_files(host)
restore_hostname(host)
# Clean ccache to prevent issues like 5741
host.run_command(['kdestroy', '-A'], raiseonerr=False)
# Clean up the test directory
host.run_command(['rm', '-rvf', host.config.test_dir])
@ -1062,7 +1064,7 @@ def add_a_records_for_hosts_in_master_domain(master):
# We don't need to take care of the zone creation since it is master
# domain
try:
verify_host_resolvable(host.hostname, log)
verify_host_resolvable(host.hostname)
log.debug("The host (%s) is resolvable." % host.domain.name)
except errors.DNSNotARecordError:
log.debug("Hostname (%s) does not have A/AAAA record. Adding new one.",

View File

@ -6,6 +6,7 @@ import dns.dnssec
import dns.resolver
import dns.name
import time
import pytest
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
@ -71,6 +72,14 @@ def wait_until_record_is_signed(nameserver, record, log, rtype="SOA",
return False
def restart_named(*args):
# A workaround for ticket N 5348
time.sleep(20) # wait till dnssec key is exported to named
for host in args:
host.run_command(["systemctl", "restart",
"named-pkcs11.service"])
class TestInstallDNSSECLast(IntegrationTest):
"""Simple DNSSEC test
@ -105,6 +114,7 @@ class TestInstallDNSSECLast(IntegrationTest):
]
self.master.run_command(args)
restart_named(self.master, self.replicas[0])
# test master
assert wait_until_record_is_signed(
self.master.ip, test_zone, self.log, timeout=100
@ -125,6 +135,7 @@ class TestInstallDNSSECLast(IntegrationTest):
]
self.replicas[0].run_command(args)
restart_named(self.replicas[0])
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, test_zone_repl, self.log, timeout=300
@ -170,8 +181,7 @@ class TestInstallDNSSECLast(IntegrationTest):
]
self.master.run_command(args)
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
restart_named(self.master)
# test master
assert wait_until_record_is_signed(
self.master.ip, test_zone, self.log, timeout=100
@ -219,7 +229,7 @@ class TestInstallDNSSECLast(IntegrationTest):
]
self.master.run_command(args)
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
restart_named(self.master, self.replicas[0])
# test master
assert wait_until_record_is_signed(
@ -235,6 +245,78 @@ class TestInstallDNSSECLast(IntegrationTest):
self.log, rtype="DNSKEY").rrset
assert dnskey_old != dnskey_new, "DNSKEY should be different"
class TestZoneSigningWithoutNamedRestart(IntegrationTest):
"""Test whether https://fedorahosted.org/freeipa/ticket/5348 is already
fixed. If the issue is not fixed, the test will expectedly fail. When
fixed, it will pass, which will cause the whole run to become "red"
"""
num_replicas = 1
topology = 'star'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False)
args = [
"ipa-dns-install",
"--dnssec-master",
"--forwarder", cls.master.config.dns_forwarder,
"-U",
]
cls.master.run_command(args)
tasks.install_replica(cls.master, cls.replicas[0], setup_dns=True)
# backup trusted key
tasks.backup_file(cls.master, paths.DNSSEC_TRUSTED_KEY)
tasks.backup_file(cls.replicas[0], paths.DNSSEC_TRUSTED_KEY)
@classmethod
def uninstall(cls, mh):
# restore trusted key
tasks.restore_files(cls.master)
tasks.restore_files(cls.replicas[0])
super(TestZoneSigningWithoutNamedRestart, cls).uninstall(mh)
@pytest.mark.xfail(strict=True)
def test_sign_root_zone_no_named_restart(self):
args = [
"ipa", "dnszone-add", root_zone, "--dnssec", "true",
"--skip-overlap-check",
]
self.master.run_command(args)
# make BIND happy: add the glue record and delegate zone
args = [
"ipa", "dnsrecord-add", root_zone, self.master.hostname,
"--a-rec=" + self.master.ip
]
self.master.run_command(args)
args = [
"ipa", "dnsrecord-add", root_zone, self.replicas[0].hostname,
"--a-rec=" + self.replicas[0].ip
]
self.master.run_command(args)
time.sleep(10) # sleep a bit until data are provided by bind-dyndb-ldap
args = [
"ipa", "dnsrecord-add", root_zone, self.master.domain.name,
"--ns-rec=" + self.master.hostname
]
self.master.run_command(args)
# test master
assert wait_until_record_is_signed(
self.master.ip, root_zone, self.log, timeout=100
), "Zone %s is not signed (master)" % root_zone
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, root_zone, self.log, timeout=300
), "Zone %s is not signed (replica)" % root_zone
class TestInstallDNSSECFirst(IntegrationTest):
"""Simple DNSSEC test
@ -288,7 +370,7 @@ class TestInstallDNSSECFirst(IntegrationTest):
"--ns-rec=" + self.master.hostname
]
self.master.run_command(args)
restart_named(self.master, self.replicas[0])
# test master
assert wait_until_record_is_signed(
self.master.ip, root_zone, self.log, timeout=100
@ -319,7 +401,7 @@ class TestInstallDNSSECFirst(IntegrationTest):
"--ns-rec=" + self.master.hostname
]
self.master.run_command(args)
restart_named(self.master, self.replicas[0])
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example_test_zone, self.log, timeout=100
@ -457,6 +539,7 @@ class TestMigrateDNSSECMaster(IntegrationTest):
self.master.run_command(args)
restart_named(self.master, self.replicas[0])
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example_test_zone, self.log, timeout=100
@ -513,7 +596,7 @@ class TestMigrateDNSSECMaster(IntegrationTest):
"--skip-overlap-check",
]
self.replicas[0].run_command(args)
restart_named(self.master, self.replicas[0])
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[0].ip, example2_test_zone, self.log, timeout=100
@ -546,7 +629,7 @@ class TestMigrateDNSSECMaster(IntegrationTest):
"--skip-overlap-check",
]
self.replicas[1].run_command(args)
restart_named(self.replicas[0], self.replicas[1])
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[1].ip, example3_test_zone, self.log, timeout=200

View File

@ -174,6 +174,18 @@ class TestReplicaPromotionLevel1(ReplicaPromotionBase):
" to generate replica file\n"
"is supported only in 0-level IPA domain", 1)
@replicas_cleanup
def test_one_command_installation(self):
"""
TestCase:
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
#Test_case:_Replica_can_be_installed_using_one_command
"""
self.replicas[0].run_command(['ipa-replica-install', '-w',
self.master.config.admin_password,
'-n', self.master.domain.name,
'-r', self.master.domain.realm])
class TestReplicaManageCommands(IntegrationTest):
topology = "star"
@ -211,7 +223,7 @@ class TestReplicaManageCommands(IntegrationTest):
' deprecated with managed IPA replication'
' topology. Please use `ipa topologysegment-*`'
' commands to manage the topology', 1)
tasks.create_segment(master, replica1, replica2)
segment = tasks.create_segment(master, replica1, replica2)
result4 = master.run_command(["ipa-replica-manage",
"disconnect",
replica1.hostname,
@ -221,3 +233,199 @@ class TestReplicaManageCommands(IntegrationTest):
' deprecated with managed IPA replication'
' topology. Please use `ipa topologysegment-*`'
' commands to manage the topology', 1)
# http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
#Test_case:_ipa-csreplica-manage_connect_is_deprecated
#_in_domain_level_1
result5 = master.run_command(['ipa-csreplica-manage', 'del',
replica1.hostname,
'-p', master.config.dirman_password],
raiseonerr=False)
assert_error(result5, "Removal of IPA CS replication agreement"
" and replication data is deprecated with"
" managed IPA replication topology", 1)
tasks.destroy_segment(master, segment[0]['name'])
result6 = master.run_command(["ipa-csreplica-manage",
"connect",
replica1.hostname,
replica2.hostname,
'-p', master.config.dirman_password],
raiseonerr=False)
assert_error(result6, "Creation of IPA CS replication agreement is"
" deprecated with managed IPA replication"
" topology", 1)
tasks.create_segment(master, replica1, replica2)
result7 = master.run_command(["ipa-csreplica-manage",
"disconnect",
replica1.hostname,
replica2.hostname,
'-p', master.config.dirman_password],
raiseonerr=False)
assert_error(result7, "Removal of IPA CS replication agreement is"
" deprecated with managed IPA"
" replication topology", 1)
class TestUnprivilegedUserPermissions(IntegrationTest):
"""
TestCase:
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
#Test_case:_Unprivileged_users_are_not_allowed_to_enroll
_and_promote_clients
"""
num_replicas = 1
domain_level = DOMAIN_LEVEL_1
@classmethod
def install(cls, mh):
cls.username = 'testuser'
tasks.install_master(cls.master, domain_level=cls.domain_level)
password = cls.master.config.dirman_password
cls.new_password = '$ome0therPaaS'
adduser_stdin_text = "%s\n%s\n" % (cls.master.config.admin_password,
cls.master.config.admin_password)
user_kinit_stdin_text = "%s\n%s\n%s\n" % (password, cls.new_password,
cls.new_password)
tasks.kinit_admin(cls.master)
cls.master.run_command(['ipa', 'user-add', cls.username, '--password',
'--first', 'John', '--last', 'Donn'],
stdin_text=adduser_stdin_text)
# Now we need to change the password for the user
cls.master.run_command(['kinit', cls.username],
stdin_text=user_kinit_stdin_text)
# And again kinit admin
tasks.kinit_admin(cls.master)
def test_client_enrollment_by_unprivileged_user(self):
replica = self.replicas[0]
result1 = replica.run_command(['ipa-client-install',
'-p', self.username,
'-w', self.new_password,
'--domain', replica.domain.name,
'--realm', replica.domain.realm, '-U'],
raiseonerr=False)
assert_error(result1, "No permission to join this host", 1)
def test_replica_promotion_by_unprivileged_user(self):
replica = self.replicas[0]
tasks.install_client(self.master, replica)
result2 = replica.run_command(['ipa-replica-install',
'-P', self.username,
'-p', self.new_password,
'-n', self.master.domain.name,
'-r', self.master.domain.realm],
raiseonerr=False)
assert_error(result2,
"Insufficient privileges to promote the server", 1)
def test_replica_promotion_after_adding_to_admin_group(self):
self.master.run_command(['ipa', 'group-add-member', 'admins',
'--users=%s' % self.username])
self.replicas[0].run_command(['ipa-replica-install',
'-P', self.username,
'-p', self.new_password,
'-n', self.master.domain.name,
'-r', self.master.domain.realm])
class TestProhibitReplicaUninstallation(IntegrationTest):
topology = 'line'
num_replicas = 2
domain_level = DOMAIN_LEVEL_1
def test_replica_uninstallation_prohibited(self):
"""
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
#Test_case:_Prohibit_ipa_server_uninstallation_from_disconnecting
_topology_segment
"""
result = self.replicas[0].run_command(['ipa-server-install',
'--uninstall', '-U'],
raiseonerr=False)
assert(result.returncode == 0), ("The replica was removed without "
"'--ignore-topology-disconnect' option")
assert("Uninstallation leads to disconnected topology"
in result.stdout_text), ("Expected error message was not found")
self.replicas[0].run_command(['ipa-server-install', '--uninstall',
'-U', '--ignore-topology-disconnect'])
class TestOldReplicaWorksAfterDomainUpgrade(IntegrationTest):
topology = 'star'
num_replicas = 1
domain_level = DOMAIN_LEVEL_0
username = 'testuser'
def test_replica_after_domain_upgrade(self):
tasks.kinit_admin(self.master)
tasks.kinit_admin(self.replicas[0])
self.master.run_command(['ipa', 'user-add', self.username,
'--first', 'test',
'--last', 'user'])
tasks.wait_for_replication(self.replicas[0].ldap_connect())
self.master.run_command(['ipa', 'domainlevel-set',
str(DOMAIN_LEVEL_1)])
result = self.replicas[0].run_command(['ipa', 'user-show',
self.username])
assert("User login: %s" % self.username in result.stdout_text), (
"A testuser was not found on replica after domain upgrade")
self.replicas[0].run_command(['ipa', 'user-del', self.username])
tasks.wait_for_replication(self.master.ldap_connect())
result1 = self.master.run_command(['ipa', 'user-show', self.username],
raiseonerr=False)
assert_error(result1, "%s: user not found" % self.username, 2)
class TestWrongClientDomain(IntegrationTest):
topology = "star"
num_clients = 1
domain_name = 'exxample.test'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, domain_level=cls.domain_level)
def teardown_method(self, method):
self.clients[0].run_command(['ipa-client-install',
'--uninstall', '-U'],
raiseonerr=False)
tasks.kinit_admin(self.master)
self.master.run_command(['ipa', 'host-del',
self.clients[0].hostname],
raiseonerr=False)
def test_wrong_client_domain(self):
client = self.clients[0]
client.run_command(['ipa-client-install', '-U',
'--domain', self.domain_name,
'--realm', self.master.domain.realm,
'-p', 'admin',
'-w', self.master.config.admin_password,
'--server', self.master.hostname,
'--force-join'])
result = client.run_command(['ipa-replica-install', '-U', '-w',
self.master.config.dirman_password],
raiseonerr=False)
assert_error(result,
"Cannot promote this client to a replica. Local domain "
"'%s' does not match IPA domain "
"'%s'" % (self.domain_name, self.master.domain.name))
def test_upcase_client_domain(self):
client = self.clients[0]
result = client.run_command(['ipa-client-install', '-U', '--domain',
self.master.domain.name.upper(), '-w',
self.master.config.admin_password,
'-p', 'admin',
'--server', self.master.hostname,
'--force-join'], raiseonerr=False)
assert(result.returncode == 0), (
'Failed to setup client with the upcase domain name')
result1 = client.run_command(['ipa-replica-install', '-U', '-w',
self.master.config.dirman_password],
raiseonerr=False)
assert(result1.returncode == 0), (
'Failed to promote the client installed with the upcase domain name')

View File

@ -3,7 +3,6 @@
#
import re
import time
import pytest
@ -11,6 +10,7 @@ from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
from ipatests.test_integration.env_config import get_global_config
from ipalib.constants import DOMAIN_SUFFIX_NAME
from ipatests.util import assert_deepequal
config = get_global_config()
reasoning = "Topology plugin disabled due to domain level 0"
@ -61,15 +61,16 @@ class TestTopologyOptions(IntegrationTest):
"""
tasks.kinit_admin(self.master)
result1 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
DOMAIN_SUFFIX_NAME]).stdout_text
first_segment_name = "%s-to-%s" % (self.master.hostname,
self.replicas[0].hostname)
output1 = result1.stdout_text
firstsegment = self.tokenize_topologies(output1)[0]
assert(firstsegment['name'] == first_segment_name)
assert(self.noentries_re.search(output1).group(1) == "1")
assert(firstsegment['leftnode'] == self.master.hostname)
assert(firstsegment['rightnode'] == self.replicas[0].hostname)
expected_segment = {
'connectivity': 'both',
'leftnode': self.master.hostname,
'name': first_segment_name,
'rightnode': self.replicas[0].hostname}
firstsegment = self.tokenize_topologies(result1)[0]
assert_deepequal(expected_segment, firstsegment)
tasks.install_replica(self.master, self.replicas[1], setup_ca=False,
setup_dns=False)
# We need to make sure topology information is consistent across all
@ -81,16 +82,17 @@ class TestTopologyOptions(IntegrationTest):
result4 = self.replicas[1].run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
segments = self.tokenize_topologies(result2.stdout_text)
assert(len(segments) == 2)
assert(result2.stdout_text == result3.stdout_text)
assert(result3.stdout_text == result4.stdout_text)
assert(len(segments) == 2), "Unexpected number of segments found"
assert_deepequal(result2.stdout_text, result3.stdout_text)
assert_deepequal(result3.stdout_text, result4.stdout_text)
# Now let's check that uninstalling the replica will update the topology
# info on the rest of replicas.
tasks.uninstall_master(self.replicas[1])
tasks.clean_replication_agreement(self.master, self.replicas[1])
result5 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
assert(self.noentries_re.search(result5.stdout_text).group(1) == "1")
num_entries = self.noentries_re.search(result5.stdout_text).group(1)
assert(num_entries == "1"), "Incorrect number of entries displayed"
def test_add_remove_segment(self):
"""
@ -110,27 +112,31 @@ class TestTopologyOptions(IntegrationTest):
assert err == "", err
# Make sure the new segment is shown by `ipa topologysegment-find`
result1 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
assert(result1.stdout_text.find(segment['name']) > 0)
DOMAIN_SUFFIX_NAME]).stdout_text
assert(segment['name'] in result1), (
"%s: segment not found" % segment['name'])
# Remove master <-> replica2 segment and make sure that the changes get
# there through replica1
deleteme = "%s-to-%s" % (self.master.hostname,
self.replicas[1].hostname)
returncode, error = tasks.destroy_segment(self.master, deleteme)
assert returncode == 0, error
# make sure replica1 does not have segment that was deleted on master
# Wait till replication ends and make sure replica1 does not have
# segment that was deleted on master
replica1_ldap = self.replicas[0].ldap_connect()
tasks.wait_for_replication(replica1_ldap)
result3 = self.replicas[0].run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
assert(result3.stdout_text.find(deleteme) < 0)
DOMAIN_SUFFIX_NAME]).stdout_text
assert(deleteme not in result3), "%s: segment still exists" % deleteme
# Create test data on master and make sure it gets all the way down to
# replica2 through replica1
self.master.run_command(['ipa', 'user-add', 'someuser',
'--first', 'test',
'--last', 'user'])
time.sleep(60) # replication requires some time
users_on_replica2 = self.replicas[1].run_command(['ipa',
'user-find'])
assert(users_on_replica2.find('someuser') > 0)
dest_ldap = self.replicas[1].ldap_connect()
tasks.wait_for_replication(dest_ldap)
result4 = self.replicas[1].run_command(['ipa', 'user-find'])
assert('someuser' in result4.stdout_text), 'User not found: someuser'
# We end up having a line topology: master <-> replica1 <-> replica2
def test_remove_the_only_connection(self):

View File

@ -28,6 +28,7 @@ import pytest
pytestmark = pytest.mark.tier0
TEST_KEY = 'ipa_test'
TEST_UNICODEKEY = u'ipa_unicode'
TEST_VALUE = b'abc123'
UPDATE_VALUE = b'123abc'
@ -49,6 +50,10 @@ class test_keyring(object):
kernel_keyring.del_key(SIZE_256)
except ValueError:
pass
try:
kernel_keyring.del_key(TEST_UNICODEKEY)
except ValueError:
pass
def test_01(self):
"""
@ -150,3 +155,13 @@ class test_keyring(object):
assert(result == SIZE_1024.encode('ascii'))
kernel_keyring.del_key(TEST_KEY)
def test_10(self):
"""
Test a unicode key
"""
kernel_keyring.add_key(TEST_UNICODEKEY, TEST_VALUE)
result = kernel_keyring.read_key(TEST_UNICODEKEY)
assert(result == TEST_VALUE)
kernel_keyring.del_key(TEST_UNICODEKEY)

View File

@ -184,9 +184,15 @@ class test_LDAPEntry(object):
assert u'cn' in e
assert u'cn' in e.keys()
assert 'CN' in e
assert 'CN' not in e.keys()
if six.PY2:
assert 'CN' not in e.keys()
else:
assert 'CN' in e.keys()
assert 'commonName' in e
assert 'commonName' not in e.keys()
if six.PY2:
assert 'commonName' not in e.keys()
else:
assert 'commonName' in e.keys()
assert e['CN'] is self.cn1
assert e['CN'] is e[u'cn']
@ -199,9 +205,15 @@ class test_LDAPEntry(object):
assert u'cn' in e
assert u'cn' in e.keys()
assert 'CN' in e
assert 'CN' not in e.keys()
if six.PY2:
assert 'CN' not in e.keys()
else:
assert 'CN' in e.keys()
assert 'commonName' in e
assert 'commonName' not in e.keys()
if six.PY2:
assert 'commonName' not in e.keys()
else:
assert 'commonName' in e.keys()
assert e['CN'] is self.cn2
assert e['CN'] is e[u'cn']
@ -281,33 +293,33 @@ class test_LDAPEntry(object):
assert e['test'] is nice
raw = e.raw['test']
assert raw == ['1', '2', '3']
assert raw == [b'1', b'2', b'3']
nice.remove(1)
assert e.raw['test'] is raw
assert raw == ['2', '3']
assert raw == [b'2', b'3']
raw.append('4')
raw.append(b'4')
assert e['test'] is nice
assert nice == [2, 3, u'4']
nice.remove(2)
raw.append('5')
raw.append(b'5')
assert nice == [3, u'4']
assert raw == ['2', '3', '4', '5']
assert raw == [b'2', b'3', b'4', b'5']
assert e['test'] is nice
assert e.raw['test'] is raw
assert nice == [3, u'4', u'5']
assert raw == ['3', '4', '5']
assert raw == [b'3', b'4', b'5']
nice.insert(0, 2)
raw.remove('4')
raw.remove(b'4')
assert nice == [2, 3, u'4', u'5']
assert raw == ['3', '5']
assert raw == [b'3', b'5']
assert e.raw['test'] is raw
assert e['test'] is nice
assert nice == [2, 3, u'5']
assert raw == ['3', '5', '2']
assert raw == [b'3', b'5', b'2']
raw = [b'a', b'b']
e.raw['test'] = raw
@ -319,5 +331,5 @@ class test_LDAPEntry(object):
assert e['test'] is nice
assert e.raw['test'] == [b'not list']
e.raw['test'].append('second')
e.raw['test'].append(b'second')
assert e['test'] == ['not list', u'second']

View File

@ -214,7 +214,10 @@ class test_jsonserver(PluginTester):
# Test with invalid JSON-data:
e = raises(errors.JSONError, o.unmarshal, 'this wont work')
assert isinstance(e.error, ValueError)
assert unicode(e.error) == 'No JSON object could be decoded'
if six.PY2:
assert unicode(e.error) == 'No JSON object could be decoded'
else:
assert str(e.error).startswith('Expecting value: ')
# Test with non-dict type:
e = raises(errors.JSONError, o.unmarshal, json.dumps([1, 2, 3]))

View File

@ -72,4 +72,4 @@ class TestTopologyPlugin(object):
entry = self.conn.get_entry(topoplugindn)
assert(set(entry.keys()) == set(pluginattrs.keys()))
for i in checkvalues:
assert(pluginattrs[i] == entry[i])
assert(set(pluginattrs[i]) == set(entry[i]))

View File

@ -68,3 +68,13 @@ DATA5 = {
('textarea', 'description', 'test-group5 desc'),
]
}
PKEY6 = 'itest-group6'
DATA6 = {
'pkey': PKEY6,
'add': [
('textbox', 'cn', PKEY6),
('textarea', 'description', 'test-group6 desc'),
('textbox', 'gidnumber', '77777'),
]
}

View File

@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
ENTITY = 'user'
PKEY = 'itest-user'
@ -63,3 +62,26 @@ DATA2 = {
('textbox', 'sn', 'OtherSurname2'),
],
}
PKEY3 = 'itest-user3'
DATA3 = {
'pkey': PKEY3,
'add': [
('textbox', 'uid', PKEY3),
('textbox', 'givenname', 'Name3'),
('textbox', 'sn', 'Surname3'),
('checkbox', 'noprivate', None),
]
}
PKEY4 = 'itest-user4'
DATA4 = {
'pkey': PKEY4,
'add': [
('textbox', 'uid', PKEY4),
('textbox', 'givenname', 'Name4'),
('textbox', 'sn', 'Surname4'),
('checkbox', 'noprivate', None),
('combobox', 'gidnumber', '77777'),
]
}

Some files were not shown because too many files have changed in this diff Show More