Imported Upstream version 4.6.2
This commit is contained in:
23
ipatests/test_integration/__init__.py
Normal file
23
ipatests/test_integration/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ipatests.util
|
||||
|
||||
|
||||
ipatests.util.check_ipaclient_unittests()
|
||||
ipatests.util.check_no_ipaapi()
|
||||
83
ipatests/test_integration/base.py
Normal file
83
ipatests/test_integration/base.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Base class for FreeIPA integration tests"""
|
||||
|
||||
import pytest
|
||||
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from pytest_sourceorder import ordered
|
||||
|
||||
|
||||
@ordered
|
||||
@pytest.mark.usefixtures('mh')
|
||||
@pytest.mark.usefixtures('integration_logs')
|
||||
class IntegrationTest(object):
|
||||
num_replicas = 0
|
||||
num_clients = 0
|
||||
num_ad_domains = 0
|
||||
required_extra_roles = []
|
||||
topology = None
|
||||
domain_level = None
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def host_by_role(cls, role):
|
||||
for domain in cls.get_domains():
|
||||
try:
|
||||
return domain.host_by_role(role)
|
||||
except LookupError:
|
||||
pass
|
||||
raise LookupError(role)
|
||||
|
||||
@classmethod
|
||||
def get_all_hosts(cls):
|
||||
return ([cls.master] + cls.replicas + cls.clients +
|
||||
[cls.host_by_role(r) for r in cls.required_extra_roles])
|
||||
|
||||
@classmethod
|
||||
def get_domains(cls):
|
||||
return [cls.domain] + cls.ad_domains
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
if cls.domain_level is not None:
|
||||
domain_level = cls.domain_level
|
||||
else:
|
||||
domain_level = cls.master.config.domain_level
|
||||
if cls.topology is None:
|
||||
return
|
||||
else:
|
||||
tasks.install_topo(cls.topology,
|
||||
cls.master, cls.replicas,
|
||||
cls.clients, domain_level)
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
tasks.uninstall_master(cls.master)
|
||||
for replica in cls.replicas:
|
||||
tasks.uninstall_master(replica)
|
||||
for client in cls.clients:
|
||||
tasks.uninstall_client(client)
|
||||
558
ipatests/test_integration/create_caless_pki.py
Normal file
558
ipatests/test_integration/create_caless_pki.py
Normal file
@@ -0,0 +1,558 @@
|
||||
# Copyright (c) 2015-2017, Jan Cholasta <jcholast@redhat.com>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
||||
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import itertools
|
||||
import os
|
||||
import os.path
|
||||
import six
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.x509.oid import NameOID
|
||||
from pyasn1.type import univ, char, namedtype, tag
|
||||
from pyasn1.codec.der import encoder as der_encoder
|
||||
from pyasn1.codec.native import decoder as native_decoder
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
DAY = datetime.timedelta(days=1)
|
||||
YEAR = 365 * DAY
|
||||
|
||||
# we get the variables from ca_less test
|
||||
domain = None
|
||||
realm = None
|
||||
server1 = None
|
||||
server2 = None
|
||||
client = None
|
||||
password = None
|
||||
cert_dir = None
|
||||
|
||||
CertInfo = collections.namedtuple('CertInfo', 'nick key cert counter')
|
||||
|
||||
|
||||
class PrincipalName(univ.Sequence):
|
||||
'''See RFC 4120 for details'''
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'name-type',
|
||||
univ.Integer().subtype(
|
||||
explicitTag=tag.Tag(
|
||||
tag.tagClassContext,
|
||||
tag.tagFormatSimple,
|
||||
0,
|
||||
),
|
||||
),
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'name-string',
|
||||
univ.SequenceOf(char.GeneralString()).subtype(
|
||||
explicitTag=tag.Tag(
|
||||
tag.tagClassContext,
|
||||
tag.tagFormatSimple,
|
||||
1,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class KRB5PrincipalName(univ.Sequence):
|
||||
'''See RFC 4556 for details'''
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'realm',
|
||||
char.GeneralString().subtype(
|
||||
explicitTag=tag.Tag(
|
||||
tag.tagClassContext,
|
||||
tag.tagFormatSimple,
|
||||
0,
|
||||
),
|
||||
),
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'principalName',
|
||||
PrincipalName().subtype(
|
||||
explicitTag=tag.Tag(
|
||||
tag.tagClassContext,
|
||||
tag.tagFormatSimple,
|
||||
1,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def profile_ca(builder, ca_nick, ca):
|
||||
now = datetime.datetime.utcnow()
|
||||
|
||||
builder = builder.not_valid_before(now)
|
||||
builder = builder.not_valid_after(now + 10 * YEAR)
|
||||
|
||||
crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.KeyUsage(
|
||||
digital_signature=True,
|
||||
content_commitment=True,
|
||||
key_encipherment=False,
|
||||
data_encipherment=False,
|
||||
key_agreement=False,
|
||||
key_cert_sign=True,
|
||||
crl_sign=True,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
),
|
||||
critical=True,
|
||||
)
|
||||
builder = builder.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=None),
|
||||
critical=True,
|
||||
)
|
||||
builder = builder.add_extension(
|
||||
x509.CRLDistributionPoints([
|
||||
x509.DistributionPoint(
|
||||
full_name=[x509.UniformResourceIdentifier(crl_uri)],
|
||||
relative_name=None,
|
||||
crl_issuer=None,
|
||||
reasons=None,
|
||||
),
|
||||
]),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
public_key = builder._public_key
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectKeyIdentifier.from_public_key(public_key),
|
||||
critical=False,
|
||||
)
|
||||
# here we get "ca" only for "ca1/subca" CA
|
||||
if not ca:
|
||||
builder = builder.add_extension(
|
||||
x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key),
|
||||
critical=False,
|
||||
)
|
||||
else:
|
||||
ski = ca.cert.extensions.get_extension_for_class(
|
||||
x509.SubjectKeyIdentifier)
|
||||
builder = builder.add_extension(
|
||||
x509.AuthorityKeyIdentifier
|
||||
.from_issuer_subject_key_identifier(ski),
|
||||
critical=False,
|
||||
)
|
||||
return builder
|
||||
|
||||
|
||||
def profile_server(builder, ca_nick, ca,
|
||||
warp=datetime.timedelta(days=0), dns_name=None,
|
||||
badusage=False, wildcard=False):
|
||||
now = datetime.datetime.utcnow() + warp
|
||||
|
||||
builder = builder.not_valid_before(now)
|
||||
builder = builder.not_valid_after(now + YEAR)
|
||||
|
||||
crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.CRLDistributionPoints([
|
||||
x509.DistributionPoint(
|
||||
full_name=[x509.UniformResourceIdentifier(crl_uri)],
|
||||
relative_name=None,
|
||||
crl_issuer=None,
|
||||
reasons=None,
|
||||
),
|
||||
]),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
if dns_name is not None:
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectAlternativeName([x509.DNSName(dns_name)]),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
if badusage:
|
||||
builder = builder.add_extension(
|
||||
x509.KeyUsage(
|
||||
digital_signature=False,
|
||||
content_commitment=False,
|
||||
key_encipherment=False,
|
||||
data_encipherment=True,
|
||||
key_agreement=True,
|
||||
key_cert_sign=False,
|
||||
crl_sign=False,
|
||||
encipher_only=False,
|
||||
decipher_only=False
|
||||
),
|
||||
critical=False
|
||||
)
|
||||
|
||||
if wildcard:
|
||||
names = [x509.DNSName(u'*.' + domain)]
|
||||
server_split = server1.split('.', 1)
|
||||
if len(server_split) == 2 and domain != server_split[1]:
|
||||
names.append(x509.DNSName(u'*.' + server_split[1]))
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectAlternativeName(names),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
return builder
|
||||
|
||||
|
||||
def profile_kdc(builder, ca_nick, ca,
|
||||
warp=datetime.timedelta(days=0), dns_name=None,
|
||||
badusage=False):
|
||||
now = datetime.datetime.utcnow() + warp
|
||||
|
||||
builder = builder.not_valid_before(now)
|
||||
builder = builder.not_valid_after(now + YEAR)
|
||||
|
||||
crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.ExtendedKeyUsage([x509.ObjectIdentifier('1.3.6.1.5.2.3.5')]),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
name = {
|
||||
'realm': realm,
|
||||
'principalName': {
|
||||
'name-type': 2,
|
||||
'name-string': ['krbtgt', realm],
|
||||
},
|
||||
}
|
||||
name = native_decoder.decode(name, asn1Spec=KRB5PrincipalName())
|
||||
name = der_encoder.encode(name)
|
||||
|
||||
names = [x509.OtherName(x509.ObjectIdentifier('1.3.6.1.5.2.2'), name)]
|
||||
if dns_name is not None:
|
||||
names += [x509.DNSName(dns_name)]
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectAlternativeName(names),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.CRLDistributionPoints([
|
||||
x509.DistributionPoint(
|
||||
full_name=[x509.UniformResourceIdentifier(crl_uri)],
|
||||
relative_name=None,
|
||||
crl_issuer=None,
|
||||
reasons=None,
|
||||
),
|
||||
]),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
if badusage:
|
||||
builder = builder.add_extension(
|
||||
x509.KeyUsage(
|
||||
digital_signature=False,
|
||||
content_commitment=False,
|
||||
key_encipherment=False,
|
||||
data_encipherment=True,
|
||||
key_agreement=True,
|
||||
key_cert_sign=False,
|
||||
crl_sign=False,
|
||||
encipher_only=False,
|
||||
decipher_only=False
|
||||
),
|
||||
critical=False
|
||||
)
|
||||
|
||||
return builder
|
||||
|
||||
|
||||
def gen_cert(profile, nick_base, subject, ca=None, **kwargs):
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend(),
|
||||
)
|
||||
public_key = key.public_key()
|
||||
|
||||
counter = itertools.count(1)
|
||||
|
||||
if ca is not None:
|
||||
ca_nick, ca_key, ca_cert, ca_counter = ca
|
||||
nick = os.path.join(ca_nick, nick_base)
|
||||
issuer = ca_cert.subject
|
||||
else:
|
||||
nick = ca_nick = nick_base
|
||||
ca_key = key
|
||||
ca_counter = counter
|
||||
issuer = subject
|
||||
|
||||
serial = next(ca_counter)
|
||||
|
||||
builder = x509.CertificateBuilder()
|
||||
builder = builder.serial_number(serial)
|
||||
builder = builder.issuer_name(issuer)
|
||||
builder = builder.subject_name(subject)
|
||||
builder = builder.public_key(public_key)
|
||||
builder = profile(builder, ca_nick, ca, **kwargs)
|
||||
|
||||
cert = builder.sign(
|
||||
private_key=ca_key,
|
||||
algorithm=hashes.SHA256(),
|
||||
backend=default_backend(),
|
||||
)
|
||||
|
||||
key_pem = key.private_bytes(
|
||||
serialization.Encoding.PEM,
|
||||
serialization.PrivateFormat.PKCS8,
|
||||
serialization.BestAvailableEncryption(password.encode()),
|
||||
)
|
||||
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(os.path.join(cert_dir, nick)))
|
||||
except OSError:
|
||||
pass
|
||||
with open(os.path.join(cert_dir, nick + '.key'), 'wb') as f:
|
||||
f.write(key_pem)
|
||||
with open(os.path.join(cert_dir, nick + '.crt'), 'wb') as f:
|
||||
f.write(cert_pem)
|
||||
|
||||
return CertInfo(nick, key, cert, counter)
|
||||
|
||||
|
||||
def revoke_cert(ca, serial):
|
||||
now = datetime.datetime.utcnow()
|
||||
|
||||
crl_builder = x509.CertificateRevocationListBuilder()
|
||||
crl_builder = crl_builder.issuer_name(ca.cert.subject)
|
||||
crl_builder = crl_builder.last_update(now)
|
||||
crl_builder = crl_builder.next_update(now + DAY)
|
||||
|
||||
crl_filename = os.path.join(cert_dir, ca.nick + '.crl')
|
||||
|
||||
try:
|
||||
f = open(crl_filename, 'rb')
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
with f:
|
||||
crl_pem = f.read()
|
||||
|
||||
crl = x509.load_pem_x509_crl(crl_pem, default_backend())
|
||||
|
||||
for revoked_cert in crl:
|
||||
crl_builder = crl_builder.add_revoked_certificate(revoked_cert)
|
||||
|
||||
builder = x509.RevokedCertificateBuilder()
|
||||
builder = builder.serial_number(serial)
|
||||
builder = builder.revocation_date(now)
|
||||
|
||||
revoked_cert = builder.build(default_backend())
|
||||
|
||||
crl_builder = crl_builder.add_revoked_certificate(revoked_cert)
|
||||
|
||||
crl = crl_builder.sign(
|
||||
private_key=ca.key,
|
||||
algorithm=hashes.SHA256(),
|
||||
backend=default_backend(),
|
||||
)
|
||||
|
||||
crl_pem = crl.public_bytes(serialization.Encoding.PEM)
|
||||
|
||||
with open(crl_filename, 'wb') as f:
|
||||
f.write(crl_pem)
|
||||
|
||||
|
||||
def gen_server_certs(nick_base, hostname, org, ca=None):
|
||||
gen_cert(profile_server, nick_base,
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca
|
||||
)
|
||||
gen_cert(profile_server, nick_base + u'-badname',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'not-' + hostname)
|
||||
]),
|
||||
ca
|
||||
)
|
||||
gen_cert(profile_server, nick_base + u'-altname',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'alt-' + hostname)
|
||||
]),
|
||||
ca, dns_name=hostname
|
||||
)
|
||||
gen_cert(profile_server, nick_base + u'-expired',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
|
||||
u'Expired'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca, warp=-2 * YEAR
|
||||
)
|
||||
gen_cert(profile_server, nick_base + u'-badusage',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
|
||||
u'Bad Usage'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca, badusage=True
|
||||
)
|
||||
revoked = gen_cert(profile_server, nick_base + u'-revoked',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
|
||||
u'Revoked'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca
|
||||
)
|
||||
revoke_cert(ca, revoked.cert.serial_number)
|
||||
|
||||
|
||||
def gen_kdc_certs(nick_base, hostname, org, ca=None):
|
||||
gen_cert(profile_kdc, nick_base + u'-kdc',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca
|
||||
)
|
||||
gen_cert(profile_kdc, nick_base + u'-kdc-badname',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'not-' + hostname)
|
||||
]),
|
||||
ca
|
||||
)
|
||||
gen_cert(profile_kdc, nick_base + u'-kdc-altname',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'alt-' + hostname)
|
||||
]),
|
||||
ca, dns_name=hostname
|
||||
)
|
||||
gen_cert(profile_kdc, nick_base + u'-kdc-expired',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
|
||||
u'Expired KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca, warp=-2 * YEAR
|
||||
)
|
||||
gen_cert(profile_kdc, nick_base + u'-kdc-badusage',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
|
||||
u'Bad Usage KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca, badusage=True
|
||||
)
|
||||
revoked = gen_cert(profile_kdc, nick_base + u'-kdc-revoked',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
|
||||
u'Revoked KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, hostname)
|
||||
]),
|
||||
ca
|
||||
)
|
||||
revoke_cert(ca, revoked.cert.serial_number)
|
||||
|
||||
|
||||
def gen_subtree(nick_base, org, ca=None):
|
||||
subca = gen_cert(profile_ca, nick_base,
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'CA')
|
||||
]),
|
||||
ca
|
||||
)
|
||||
gen_cert(profile_server, u'wildcard',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'*.' + domain)
|
||||
]),
|
||||
subca, wildcard=True
|
||||
)
|
||||
gen_server_certs(u'server', server1, org, subca)
|
||||
gen_server_certs(u'replica', server2, org, subca)
|
||||
gen_server_certs(u'client', client, org, subca)
|
||||
gen_cert(profile_kdc, u'kdcwildcard',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u'*.' + domain)
|
||||
]),
|
||||
subca
|
||||
)
|
||||
gen_kdc_certs(u'server', server1, org, subca)
|
||||
gen_kdc_certs(u'replica', server2, org, subca)
|
||||
gen_kdc_certs(u'client', client, org, subca)
|
||||
return subca
|
||||
|
||||
|
||||
def create_pki():
|
||||
|
||||
gen_cert(profile_server, u'server-selfsign',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Self-signed'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, server1)
|
||||
])
|
||||
)
|
||||
gen_cert(profile_server, u'replica-selfsign',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Self-signed'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, server2)
|
||||
])
|
||||
)
|
||||
gen_cert(profile_server, u'noca',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'No-CA'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, server1)
|
||||
])
|
||||
)
|
||||
gen_cert(profile_kdc, u'server-kdc-selfsign',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Self-signed'),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, server1)
|
||||
])
|
||||
)
|
||||
gen_cert(profile_kdc, u'replica-kdc-selfsign',
|
||||
x509.Name([
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Self-signed'),
|
||||
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'KDC'),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, server2)
|
||||
])
|
||||
)
|
||||
ca1 = gen_subtree(u'ca1', u'Example Organization')
|
||||
gen_subtree(u'subca', u'Subsidiary Example Organization', ca1)
|
||||
gen_subtree(u'ca2', u'Other Example Organization')
|
||||
ca3 = gen_subtree(u'ca3', u'Unknown Organization')
|
||||
os.unlink(os.path.join(cert_dir, ca3.nick + '.key'))
|
||||
os.unlink(os.path.join(cert_dir, ca3.nick + '.crt'))
|
||||
155
ipatests/test_integration/create_external_ca.py
Normal file
155
ipatests/test_integration/create_external_ca.py
Normal file
@@ -0,0 +1,155 @@
|
||||
#
|
||||
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
import datetime
|
||||
import six
|
||||
|
||||
|
||||
class ExternalCA(object):
|
||||
"""
|
||||
Provide external CA for testing
|
||||
"""
|
||||
def create_ca(self, cn='example.test'):
|
||||
"""Create root CA.
|
||||
|
||||
:returns: bytes -- Root CA in PEM format.
|
||||
"""
|
||||
self.ca_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend(),
|
||||
)
|
||||
|
||||
self.ca_public_key = self.ca_key.public_key()
|
||||
|
||||
subject = self.issuer = x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, six.text_type(cn)),
|
||||
])
|
||||
|
||||
builder = x509.CertificateBuilder()
|
||||
builder = builder.subject_name(subject)
|
||||
builder = builder.issuer_name(self.issuer)
|
||||
builder = builder.public_key(self.ca_public_key)
|
||||
builder = builder.serial_number(x509.random_serial_number())
|
||||
builder = builder.not_valid_before(datetime.datetime.utcnow())
|
||||
builder = builder.not_valid_after(
|
||||
datetime.datetime.utcnow() + datetime.timedelta(days=365)
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.KeyUsage(
|
||||
digital_signature=False,
|
||||
content_commitment=False,
|
||||
key_encipherment=False,
|
||||
data_encipherment=False,
|
||||
key_agreement=False,
|
||||
key_cert_sign=True,
|
||||
crl_sign=True,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=None),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectKeyIdentifier.from_public_key(self.ca_public_key),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.AuthorityKeyIdentifier.from_issuer_public_key(
|
||||
self.ca_public_key
|
||||
),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
cert = builder.sign(self.ca_key, hashes.SHA256(), default_backend())
|
||||
|
||||
return cert.public_bytes(serialization.Encoding.PEM)
|
||||
|
||||
def sign_csr(self, ipa_csr):
|
||||
"""Sign certificate CSR.
|
||||
|
||||
:param ipa_csr: CSR in PEM format.
|
||||
:type ipa_csr: bytes.
|
||||
:returns: bytes -- Signed CA in PEM format.
|
||||
"""
|
||||
csr_tbs = x509.load_pem_x509_csr(ipa_csr, default_backend())
|
||||
|
||||
csr_public_key = csr_tbs.public_key()
|
||||
csr_subject = csr_tbs.subject
|
||||
|
||||
builder = x509.CertificateBuilder()
|
||||
builder = builder.public_key(csr_public_key)
|
||||
builder = builder.subject_name(csr_subject)
|
||||
builder = builder.serial_number(x509.random_serial_number())
|
||||
builder = builder.issuer_name(self.issuer)
|
||||
builder = builder.not_valid_before(datetime.datetime.utcnow())
|
||||
builder = builder.not_valid_after(
|
||||
datetime.datetime.utcnow() + datetime.timedelta(days=365))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.KeyUsage(
|
||||
digital_signature=False,
|
||||
content_commitment=False,
|
||||
key_encipherment=False,
|
||||
data_encipherment=False,
|
||||
key_agreement=False,
|
||||
key_cert_sign=True,
|
||||
crl_sign=True,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectKeyIdentifier.from_public_key(csr_public_key),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.AuthorityKeyIdentifier.from_issuer_public_key(
|
||||
self.ca_public_key
|
||||
),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=1),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
cert = builder.sign(
|
||||
private_key=self.ca_key,
|
||||
algorithm=hashes.SHA256(),
|
||||
backend=default_backend(),
|
||||
)
|
||||
|
||||
return cert.public_bytes(serialization.Encoding.PEM)
|
||||
135
ipatests/test_integration/test_advise.py
Normal file
135
ipatests/test_integration/test_advise.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# Authors:
|
||||
# Gabe Alford <redhatrises@gmail.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# FIXME: Pylint errors
|
||||
# pylint: disable=no-member
|
||||
|
||||
import re
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
|
||||
|
||||
def run_advice(master, advice_id, advice_regex, raiseerr):
|
||||
# Obtain the advice from the server
|
||||
tasks.kinit_admin(master)
|
||||
result = master.run_command(['ipa-advise', advice_id],
|
||||
raiseonerr=raiseerr)
|
||||
|
||||
if not result.stdout_text:
|
||||
advice = result.stderr_text
|
||||
else:
|
||||
advice = result.stdout_text
|
||||
|
||||
assert re.search(advice_regex, advice, re.S)
|
||||
|
||||
|
||||
class TestAdvice(IntegrationTest):
|
||||
"""
|
||||
Tests ipa-advise output.
|
||||
"""
|
||||
advice_id = None
|
||||
raiseerr = None
|
||||
advice_regex = ''
|
||||
topology = 'line'
|
||||
|
||||
def test_invalid_advice(self):
|
||||
advice_id = 'invalid-advise-param'
|
||||
advice_regex = "invalid[\s]+\'advice\'.*"
|
||||
raiseerr = False
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_FedoraAuthconfig(self):
|
||||
advice_id = 'config-fedora-authconfig'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"authconfig[\s]+\-\-enableldap[\s]+" \
|
||||
"\-\-ldapserver\=.*[\s]+\-\-enablerfc2307bis[\s]+" \
|
||||
"\-\-enablekrb5"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_FreeBSDNSSPAM(self):
|
||||
advice_id = 'config-freebsd-nss-pam-ldapd'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"pkg_add[\s]+\-r[\s]+nss\-pam\-ldapd[\s]+curl.*" \
|
||||
"\/usr\/local\/etc\/rc\.d\/nslcd[\s]+restart"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_GenericNSSPAM(self):
|
||||
advice_id = 'config-generic-linux-nss-pam-ldapd'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"apt\-get[\s]+\-y[\s]+install[\s]+curl[\s]+openssl[\s]+" \
|
||||
"libnss\-ldapd[\s]+libpam\-ldapd[\s]+nslcd.*" \
|
||||
"service[\s]+nscd[\s]+stop[\s]+\&\&[\s]+service[\s]+" \
|
||||
"nslcd[\s]+restart"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_GenericSSSDBefore19(self):
|
||||
advice_id = 'config-generic-linux-sssd-before-1-9'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"apt\-get[\s]+\-y[\s]+install sssd curl openssl.*" \
|
||||
"service[\s]+sssd[\s]+start"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_RedHatNSS(self):
|
||||
advice_id = 'config-redhat-nss-ldap'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"yum[\s]+install[\s]+\-y[\s]+curl[\s]+openssl[\s]+nss_ldap" \
|
||||
"[\s]+authconfig.*authconfig[\s]+\-\-updateall" \
|
||||
"[\s]+\-\-enableldap[\s]+\-\-enableldaptls"\
|
||||
"[\s]+\-\-enableldapauth[\s]+" \
|
||||
"\-\-ldapserver=.*[\s]+\-\-ldapbasedn=.*"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_RedHatNSSPAM(self):
|
||||
advice_id = 'config-redhat-nss-pam-ldapd'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"yum[\s]+install[\s]+\-y[\s]+curl[\s]+openssl[\s]+" \
|
||||
"nss\-pam\-ldapd[\s]+pam_ldap[\s]+authconfig.*" \
|
||||
"authconfig[\s]+\-\-updateall[\s]+\-\-enableldap"\
|
||||
"[\s]+\-\-enableldaptls[\s]+\-\-enableldapauth[\s]+" \
|
||||
"\-\-ldapserver=.*[\s]+\-\-ldapbasedn=.*"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
|
||||
|
||||
def test_advice_RedHatSSSDBefore19(self):
|
||||
advice_id = 'config-redhat-sssd-before-1-9'
|
||||
advice_regex = "\#\!\/bin\/sh.*" \
|
||||
"yum[\s]+install[\s]+\-y[\s]+sssd[\s]+authconfig[\s]+" \
|
||||
"curl[\s]+openssl.*service[\s]+sssd[\s]+start"
|
||||
raiseerr = True
|
||||
|
||||
run_advice(self.master, advice_id, advice_regex, raiseerr)
|
||||
415
ipatests/test_integration/test_backup_and_restore.py
Normal file
415
ipatests/test_integration/test_backup_and_restore.py
Normal file
@@ -0,0 +1,415 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import contextlib
|
||||
|
||||
from ipapython.dn import DN
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.test_integration.test_dnssec import wait_until_record_is_signed
|
||||
from ipatests.util import assert_deepequal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def assert_entries_equal(a, b):
|
||||
assert_deepequal(a.dn, b.dn)
|
||||
assert_deepequal(dict(a), dict(b))
|
||||
|
||||
|
||||
def assert_results_equal(a, b):
|
||||
def to_dict(r):
|
||||
return {
|
||||
'stdout': r.stdout_text,
|
||||
'stderr': r.stderr_text,
|
||||
'returncode': r.returncode,
|
||||
}
|
||||
assert_deepequal(to_dict(a), to_dict(b))
|
||||
|
||||
|
||||
def check_admin_in_ldap(host):
|
||||
ldap = host.ldap_connect()
|
||||
basedn = host.domain.basedn
|
||||
user_dn = DN(('uid', 'admin'), ('cn', 'users'), ('cn', 'accounts'), basedn)
|
||||
entry = ldap.get_entry(user_dn)
|
||||
print(entry)
|
||||
assert entry.dn == user_dn
|
||||
assert entry['uid'] == ['admin']
|
||||
|
||||
entry.pop('krbLastSuccessfulAuth', None)
|
||||
|
||||
return entry
|
||||
|
||||
|
||||
def check_admin_in_cli(host):
|
||||
result = host.run_command(['ipa', 'user-show', 'admin'])
|
||||
assert 'User login: admin' in result.stdout_text, result.stdout_text
|
||||
return result
|
||||
|
||||
|
||||
def check_admin_in_id(host):
|
||||
result = host.run_command(['id', 'admin'])
|
||||
assert 'admin' in result.stdout_text, result.stdout_text
|
||||
return result
|
||||
|
||||
|
||||
def check_certs(host):
|
||||
result = host.run_command(['ipa', 'cert-find'])
|
||||
assert re.search('^Number of entries returned [1-9]\d*$',
|
||||
result.stdout_text, re.MULTILINE), result.stdout_text
|
||||
return result
|
||||
|
||||
|
||||
def check_dns(host):
|
||||
result = host.run_command(['host', host.hostname, 'localhost'])
|
||||
return result
|
||||
|
||||
|
||||
def check_kinit(host):
|
||||
result = host.run_command(['kinit', 'admin'],
|
||||
stdin_text=host.config.admin_password)
|
||||
return result
|
||||
|
||||
|
||||
CHECKS = [
|
||||
(check_admin_in_ldap, assert_entries_equal),
|
||||
(check_admin_in_cli, assert_results_equal),
|
||||
(check_admin_in_id, assert_results_equal),
|
||||
(check_certs, assert_results_equal),
|
||||
(check_dns, assert_results_equal),
|
||||
(check_kinit, assert_results_equal),
|
||||
]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def restore_checker(host):
|
||||
"""Check that the IPA at host works the same at context enter and exit"""
|
||||
tasks.kinit_admin(host)
|
||||
|
||||
results = []
|
||||
for check, assert_func in CHECKS:
|
||||
logger.info('Storing result for %s', check)
|
||||
results.append(check(host))
|
||||
|
||||
yield
|
||||
|
||||
for (check, assert_func), expected in zip(CHECKS, results):
|
||||
logger.info('Checking result for %s', check)
|
||||
got = check(host)
|
||||
assert_func(expected, got)
|
||||
|
||||
|
||||
def backup(host):
|
||||
"""Run backup on host, return the path to the backup directory"""
|
||||
result = host.run_command(['ipa-backup', '-v'])
|
||||
|
||||
# Get the backup location from the command's output
|
||||
for line in result.stderr_text.splitlines():
|
||||
prefix = ('ipa.ipaserver.install.ipa_backup.Backup: '
|
||||
'INFO: Backed up to ')
|
||||
if line.startswith(prefix):
|
||||
backup_path = line[len(prefix):].strip()
|
||||
logger.info('Backup path for %s is %s', host, backup_path)
|
||||
return backup_path
|
||||
else:
|
||||
raise AssertionError('Backup directory not found in output')
|
||||
|
||||
|
||||
|
||||
class TestBackupAndRestore(IntegrationTest):
|
||||
topology = 'star'
|
||||
|
||||
def test_full_backup_and_restore(self):
|
||||
"""backup, uninstall, restore"""
|
||||
with restore_checker(self.master):
|
||||
backup_path = backup(self.master)
|
||||
|
||||
self.master.run_command(['ipa-server-install',
|
||||
'--uninstall',
|
||||
'-U'])
|
||||
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
|
||||
def test_full_backup_and_restore_with_removed_users(self):
|
||||
"""regression test for https://fedorahosted.org/freeipa/ticket/3866"""
|
||||
with restore_checker(self.master):
|
||||
backup_path = backup(self.master)
|
||||
|
||||
logger.info('Backup path for %s is %s', self.master, backup_path)
|
||||
|
||||
self.master.run_command(['ipa-server-install',
|
||||
'--uninstall',
|
||||
'-U'])
|
||||
|
||||
homedir = os.path.join(self.master.config.test_dir,
|
||||
'testuser_homedir')
|
||||
self.master.run_command(['useradd', 'ipatest_user1',
|
||||
'--system',
|
||||
'-d', homedir])
|
||||
try:
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
finally:
|
||||
self.master.run_command(['userdel', 'ipatest_user1'])
|
||||
|
||||
def test_full_backup_and_restore_with_selinux_booleans_off(self):
|
||||
"""regression test for https://fedorahosted.org/freeipa/ticket/4157"""
|
||||
with restore_checker(self.master):
|
||||
backup_path = backup(self.master)
|
||||
|
||||
logger.info('Backup path for %s is %s', self.master, backup_path)
|
||||
|
||||
self.master.run_command(['ipa-server-install',
|
||||
'--uninstall',
|
||||
'-U'])
|
||||
|
||||
self.master.run_command([
|
||||
'setsebool', '-P',
|
||||
'httpd_can_network_connect=off',
|
||||
'httpd_manage_ipa=off',
|
||||
])
|
||||
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
|
||||
result = self.master.run_command([
|
||||
'getsebool',
|
||||
'httpd_can_network_connect',
|
||||
'httpd_manage_ipa',
|
||||
])
|
||||
assert 'httpd_can_network_connect --> on' in result.stdout_text
|
||||
assert 'httpd_manage_ipa --> on' in result.stdout_text
|
||||
|
||||
|
||||
class BaseBackupAndRestoreWithDNS(IntegrationTest):
|
||||
"""
|
||||
Abstract class for DNS restore tests
|
||||
"""
|
||||
topology = 'star'
|
||||
|
||||
example_test_zone = "example.test."
|
||||
example2_test_zone = "example2.test."
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
|
||||
def _full_backup_restore_with_DNS_zone(self, reinstall=False):
|
||||
"""backup, uninstall, restore"""
|
||||
with restore_checker(self.master):
|
||||
|
||||
self.master.run_command([
|
||||
'ipa', 'dnszone-add',
|
||||
self.example_test_zone,
|
||||
])
|
||||
|
||||
tasks.resolve_record(self.master.ip, self.example_test_zone)
|
||||
|
||||
backup_path = backup(self.master)
|
||||
|
||||
self.master.run_command(['ipa-server-install',
|
||||
'--uninstall',
|
||||
'-U'])
|
||||
|
||||
if reinstall:
|
||||
tasks.install_master(self.master, setup_dns=True)
|
||||
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
|
||||
tasks.resolve_record(self.master.ip, self.example_test_zone)
|
||||
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command([
|
||||
'ipa', 'dnszone-add',
|
||||
self.example2_test_zone,
|
||||
])
|
||||
|
||||
tasks.resolve_record(self.master.ip, self.example2_test_zone)
|
||||
|
||||
|
||||
class TestBackupAndRestoreWithDNS(BaseBackupAndRestoreWithDNS):
|
||||
def test_full_backup_and_restore_with_DNS_zone(self):
|
||||
"""backup, uninstall, restore"""
|
||||
self._full_backup_restore_with_DNS_zone(reinstall=False)
|
||||
|
||||
|
||||
class TestBackupReinstallRestoreWithDNS(BaseBackupAndRestoreWithDNS):
|
||||
def test_full_backup_reinstall_restore_with_DNS_zone(self):
|
||||
"""backup, uninstall, reinstall, restore"""
|
||||
self._full_backup_restore_with_DNS_zone(reinstall=True)
|
||||
|
||||
|
||||
class BaseBackupAndRestoreWithDNSSEC(IntegrationTest):
|
||||
"""
|
||||
Abstract class for DNSSEC restore tests
|
||||
"""
|
||||
topology = 'star'
|
||||
|
||||
example_test_zone = "example.test."
|
||||
example2_test_zone = "example2.test."
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--dnssec-master",
|
||||
"--forwarder", cls.master.config.dns_forwarder,
|
||||
"-U",
|
||||
]
|
||||
cls.master.run_command(args)
|
||||
|
||||
def _full_backup_and_restore_with_DNSSEC_zone(self, reinstall=False):
|
||||
with restore_checker(self.master):
|
||||
|
||||
self.master.run_command([
|
||||
'ipa', 'dnszone-add',
|
||||
self.example_test_zone,
|
||||
'--dnssec', 'true',
|
||||
])
|
||||
|
||||
assert (
|
||||
wait_until_record_is_signed(
|
||||
self.master.ip, self.example_test_zone)
|
||||
), "Zone is not signed"
|
||||
|
||||
backup_path = backup(self.master)
|
||||
|
||||
self.master.run_command(['ipa-server-install',
|
||||
'--uninstall',
|
||||
'-U'])
|
||||
|
||||
if reinstall:
|
||||
tasks.install_master(self.master, setup_dns=True)
|
||||
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
|
||||
assert (
|
||||
wait_until_record_is_signed(
|
||||
self.master.ip, self.example_test_zone)
|
||||
), "Zone is not signed after restore"
|
||||
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command([
|
||||
'ipa', 'dnszone-add',
|
||||
self.example2_test_zone,
|
||||
'--dnssec', 'true',
|
||||
])
|
||||
|
||||
assert (
|
||||
wait_until_record_is_signed(
|
||||
self.master.ip, self.example2_test_zone)
|
||||
), "A new zone is not signed"
|
||||
|
||||
|
||||
class TestBackupAndRestoreWithDNSSEC(BaseBackupAndRestoreWithDNSSEC):
|
||||
def test_full_backup_and_restore_with_DNSSEC_zone(self):
|
||||
"""backup, uninstall, restore"""
|
||||
self._full_backup_and_restore_with_DNSSEC_zone(reinstall=False)
|
||||
|
||||
|
||||
class TestBackupReinstallRestoreWithDNSSEC(BaseBackupAndRestoreWithDNSSEC):
|
||||
def test_full_backup_reinstall_restore_with_DNSSEC_zone(self):
|
||||
"""backup, uninstall, install, restore"""
|
||||
self._full_backup_and_restore_with_DNSSEC_zone(reinstall=True)
|
||||
|
||||
|
||||
class BaseBackupAndRestoreWithKRA(IntegrationTest):
|
||||
"""
|
||||
Abstract class for KRA restore tests
|
||||
"""
|
||||
topology = 'star'
|
||||
|
||||
vault_name = "ci_test_vault"
|
||||
vault_password = "password"
|
||||
vault_data = "SSBsb3ZlIENJIHRlc3RzCg=="
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
|
||||
|
||||
def _full_backup_restore_with_vault(self, reinstall=False):
|
||||
with restore_checker(self.master):
|
||||
# create vault
|
||||
self.master.run_command([
|
||||
"ipa", "vault-add",
|
||||
self.vault_name,
|
||||
"--password", self.vault_password,
|
||||
"--type", "symmetric",
|
||||
])
|
||||
|
||||
# archive secret
|
||||
self.master.run_command([
|
||||
"ipa", "vault-archive",
|
||||
self.vault_name,
|
||||
"--password", self.vault_password,
|
||||
"--data", self.vault_data,
|
||||
])
|
||||
|
||||
# retrieve secret
|
||||
self.master.run_command([
|
||||
"ipa", "vault-retrieve",
|
||||
self.vault_name,
|
||||
"--password", self.vault_password,
|
||||
])
|
||||
|
||||
backup_path = backup(self.master)
|
||||
|
||||
self.master.run_command(['ipa-server-install',
|
||||
'--uninstall',
|
||||
'-U'])
|
||||
|
||||
if reinstall:
|
||||
tasks.install_master(self.master, setup_dns=True)
|
||||
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
|
||||
tasks.kinit_admin(self.master)
|
||||
# retrieve secret after restore
|
||||
self.master.run_command([
|
||||
"ipa", "vault-retrieve",
|
||||
self.vault_name,
|
||||
"--password", self.vault_password,
|
||||
])
|
||||
|
||||
|
||||
class TestBackupAndRestoreWithKRA(BaseBackupAndRestoreWithKRA):
|
||||
def test_full_backup_restore_with_vault(self):
|
||||
"""backup, uninstall, restore"""
|
||||
self._full_backup_restore_with_vault(reinstall=False)
|
||||
|
||||
class TestBackupReinstallRestoreWithKRA(BaseBackupAndRestoreWithKRA):
|
||||
def test_full_backup_reinstall_restore_with_vault(self):
|
||||
"""backup, uninstall, reinstall, restore"""
|
||||
self._full_backup_restore_with_vault(reinstall=True)
|
||||
1630
ipatests/test_integration/test_caless.py
Normal file
1630
ipatests/test_integration/test_caless.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
DIRSRV_CONFIG_MODS = """
|
||||
# https://fedorahosted.org/freeipa/ticket/4949
|
||||
dn: cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
changetype: modify
|
||||
replace: nsslapd-db-locks
|
||||
nsslapd-db-locks: 100000
|
||||
|
||||
# https://fedorahosted.org/freeipa/ticket/1930
|
||||
dn: cn=config
|
||||
changetype: modify
|
||||
replace: nsslapd-allow-unauthenticated-binds
|
||||
nsslapd-allow-unauthenticated-binds: off
|
||||
-
|
||||
replace: nsslapd-require-secure-binds
|
||||
nsslapd-require-secure-binds: off
|
||||
-
|
||||
replace: nsslapd-allow-anonymous-access
|
||||
nsslapd-allow-anonymous-access: off
|
||||
-
|
||||
replace: nsslapd-minssf
|
||||
nsslapd-minssf: 0
|
||||
|
||||
# https://fedorahosted.org/freeipa/ticket/4048
|
||||
dn: cn=config
|
||||
changetype: modify
|
||||
replace: nssslapd-maxbersize
|
||||
nssslapd-maxbersize: 209715201
|
||||
|
||||
dn: cn=userRoot,cn=ldbm database,cn=plugins,cn=config
|
||||
changetype: modify
|
||||
replace: nsslapd-cachememsize
|
||||
nsslapd-cachememsize: 10485761
|
||||
|
||||
dn: cn=config,cn=ldbm database,cn=plugins,cn=config
|
||||
changetype: modify
|
||||
replace: nsslapd-import_cachesize
|
||||
nsslapd-import_cachesize: 20000001
|
||||
-
|
||||
replace: nsslapd-dbcachesize
|
||||
nsslapd-dbcachesize: 10000001
|
||||
"""
|
||||
|
||||
CONFIG_LDIF_PATH = "/root/dirsrv-config-mod.ldif"
|
||||
|
||||
|
||||
class TestCustomInstallMaster(IntegrationTest):
|
||||
"""
|
||||
Install master with customized DS config
|
||||
"""
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
# just prepare LDIF file on both master and replica
|
||||
cls.master.put_file_contents(CONFIG_LDIF_PATH, DIRSRV_CONFIG_MODS)
|
||||
|
||||
def test_customized_ds_install_master(self):
|
||||
tasks.install_master(self.master, setup_dns=False, extra_args=[
|
||||
'--dirsrv-config-file', CONFIG_LDIF_PATH
|
||||
])
|
||||
|
||||
|
||||
class TestCustomInstallReplica(IntegrationTest):
|
||||
"""
|
||||
Install replica with customized DS config
|
||||
"""
|
||||
topology = 'star'
|
||||
num_replicas = 1
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
# just prepare LDIF file on both master and replica
|
||||
cls.replicas[0].put_file_contents(CONFIG_LDIF_PATH, DIRSRV_CONFIG_MODS)
|
||||
tasks.install_master(cls.master)
|
||||
|
||||
def test_customized_ds_install_replica(self):
|
||||
tasks.install_replica(
|
||||
self.master, self.replicas[0], setup_ca=False,
|
||||
extra_args=['--dirsrv-config-file', CONFIG_LDIF_PATH])
|
||||
451
ipatests/test_integration/test_dns_locations.py
Normal file
451
ipatests/test_integration/test_dns_locations.py
Normal file
@@ -0,0 +1,451 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
import logging
|
||||
import time
|
||||
import pytest
|
||||
import six
|
||||
import dns.resolver
|
||||
import dns.rrset
|
||||
import dns.rdatatype
|
||||
import dns.rdataclass
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipapython.dnsutil import DNSName
|
||||
from ipalib.constants import IPA_CA_RECORD
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
IPA_DEFAULT_MASTER_SRV_REC = (
|
||||
# srv record name, port
|
||||
(DNSName(u'_ldap._tcp'), 389),
|
||||
(DNSName(u'_kerberos._tcp'), 88),
|
||||
(DNSName(u'_kerberos._udp'), 88),
|
||||
(DNSName(u'_kerberos-master._tcp'), 88),
|
||||
(DNSName(u'_kerberos-master._udp'), 88),
|
||||
(DNSName(u'_kpasswd._tcp'), 464),
|
||||
(DNSName(u'_kpasswd._udp'), 464),
|
||||
)
|
||||
|
||||
IPA_DEFAULT_ADTRUST_SRV_REC = (
|
||||
# srv record name, port
|
||||
(DNSName(u'_ldap._tcp.Default-First-Site-Name._sites.dc._msdcs'), 389),
|
||||
(DNSName(u'_ldap._tcp.dc._msdcs'), 389),
|
||||
(DNSName(u'_kerberos._tcp.Default-First-Site-Name._sites.dc._msdcs'), 88),
|
||||
(DNSName(u'_kerberos._udp.Default-First-Site-Name._sites.dc._msdcs'), 88),
|
||||
(DNSName(u'_kerberos._tcp.dc._msdcs'), 88),
|
||||
(DNSName(u'_kerberos._udp.dc._msdcs'), 88),
|
||||
)
|
||||
|
||||
# we keep NTP SRV record separated as NTP is "optional"
|
||||
IPA_DEFAULT_NTP_SRV_REC = (
|
||||
# srv record name, port
|
||||
(DNSName("_ntp._udp"), 123),
|
||||
)
|
||||
|
||||
IPA_CA_A_REC = (
|
||||
(DNSName(six.text_type(IPA_CA_RECORD))),
|
||||
)
|
||||
|
||||
|
||||
def resolve_records_from_server(rname, rtype, nameserver):
|
||||
error = None
|
||||
res = dns.resolver.Resolver()
|
||||
res.nameservers = [nameserver]
|
||||
res.lifetime = 30
|
||||
logger.info("Query: %s %s, nameserver %s", rname, rtype, nameserver)
|
||||
# lets try to query 3x
|
||||
for _i in range(3):
|
||||
try:
|
||||
ans = res.query(rname, rtype)
|
||||
logger.info("Answer: %s", ans.rrset)
|
||||
return ans.rrset
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.Timeout) as e:
|
||||
error = e
|
||||
time.sleep(10)
|
||||
|
||||
pytest.fail("Query: {} {}, nameserver {} failed due to {}".format(
|
||||
rname, rtype, nameserver, error))
|
||||
|
||||
|
||||
def _gen_expected_srv_rrset(rname, port, servers, ttl=86400):
|
||||
rdata_list = [
|
||||
"{prio} {weight} {port} {servername}".format(
|
||||
prio=prio,
|
||||
weight=weight,
|
||||
port=port,
|
||||
servername=servername.make_absolute()
|
||||
)
|
||||
for prio, weight, servername in servers
|
||||
]
|
||||
return dns.rrset.from_text_list(
|
||||
rname, ttl, dns.rdataclass.IN, dns.rdatatype.SRV, rdata_list
|
||||
)
|
||||
|
||||
|
||||
def _gen_expected_a_rrset(rname, servers, ttl=86400):
|
||||
return dns.rrset.from_text_list(rname, ttl, dns.rdataclass.IN,
|
||||
dns.rdatatype.A, servers)
|
||||
|
||||
|
||||
class TestDNSLocations(IntegrationTest):
|
||||
"""Simple test if SRV DNS records for IPA locations are generated properly
|
||||
|
||||
Topology:
|
||||
* 3 servers (replica0 --- master --- replica1)
|
||||
replica0 with NTP/no CA, master with ADtrust installed later,
|
||||
replica1 without NTP/with CA
|
||||
* 2 locations (prague, paris)
|
||||
"""
|
||||
num_replicas = 2
|
||||
topology = 'star'
|
||||
|
||||
LOC_PRAGUE = u'prague'
|
||||
LOC_PARIS = u'paris'
|
||||
|
||||
PRIO_HIGH = 0
|
||||
PRIO_LOW = 50
|
||||
WEIGHT = 100
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
cls.domain = DNSName(cls.master.domain.name).make_absolute()
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
tasks.install_replica(cls.master, cls.replicas[0], setup_dns=True,
|
||||
setup_ca=False)
|
||||
tasks.install_replica(cls.master, cls.replicas[1], setup_dns=True,
|
||||
setup_ca=True, extra_args=(['--no-ntp']))
|
||||
|
||||
for host in (cls.master, cls.replicas[0], cls.replicas[1]):
|
||||
ldap = host.ldap_connect()
|
||||
tasks.wait_for_replication(ldap)
|
||||
|
||||
# give time to named to retrieve new records
|
||||
time.sleep(20)
|
||||
|
||||
@classmethod
|
||||
def delete_update_system_records(cls, rnames):
|
||||
filepath = '/tmp/ipa.nsupdate'
|
||||
|
||||
cls.master.run_command([
|
||||
'ipa', 'dns-update-system-records', '--dry-run', '--out', filepath
|
||||
])
|
||||
|
||||
for name in rnames:
|
||||
cls.master.run_command([
|
||||
'ipa', 'dnsrecord-del', str(cls.domain), str(name),
|
||||
'--del-all'])
|
||||
|
||||
time.sleep(15)
|
||||
# allow unauthenticates nsupdate (no need to testing authentication)
|
||||
cls.master.run_command([
|
||||
'ipa', 'dnszone-mod', str(cls.domain),
|
||||
'--update-policy=grant * wildcard *;'
|
||||
], raiseonerr=False)
|
||||
|
||||
cls.master.run_command(['nsupdate', '-g', filepath])
|
||||
time.sleep(15)
|
||||
|
||||
def _test_A_rec_against_server(self, server_ip, domain, expected_servers,
|
||||
rec_list=IPA_CA_A_REC):
|
||||
for rname in rec_list:
|
||||
name_abs = rname.derelativize(domain)
|
||||
expected = _gen_expected_a_rrset(name_abs, expected_servers)
|
||||
query = resolve_records_from_server(
|
||||
name_abs, 'A', server_ip)
|
||||
|
||||
assert expected == query, (
|
||||
"Expected and received DNS data do not match on server "
|
||||
"with IP: '{}' for name '{}' (expected:\n{}\ngot:\n{})".
|
||||
format(server_ip, name_abs, expected, query))
|
||||
|
||||
def _test_SRV_rec_against_server(self, server_ip, domain, expected_servers,
|
||||
rec_list=IPA_DEFAULT_MASTER_SRV_REC):
|
||||
for rname, port in rec_list:
|
||||
name_abs = rname.derelativize(domain)
|
||||
expected = _gen_expected_srv_rrset(
|
||||
name_abs, port, expected_servers)
|
||||
query = resolve_records_from_server(
|
||||
name_abs, 'SRV', server_ip)
|
||||
|
||||
assert expected == query, (
|
||||
"Expected and received DNS data do not match on server "
|
||||
"with IP: '{}' for name '{}' (expected:\n{}\ngot:\n{})".
|
||||
format(server_ip, name_abs, expected, query))
|
||||
|
||||
def test_without_locations(self):
|
||||
"""Servers are not in locations, this tests if basic system records
|
||||
are generated properly"""
|
||||
expected_servers = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
for ip in (self.master.ip, self.replicas[0].ip, self.replicas[1].ip):
|
||||
self._test_SRV_rec_against_server(ip, self.domain,
|
||||
expected_servers)
|
||||
|
||||
def test_nsupdate_without_locations(self):
|
||||
"""Test nsupdate file generated by dns-update-system-records
|
||||
Remove all records and the use nsupdate to restore state and test if
|
||||
all record are there as expected"""
|
||||
|
||||
self.delete_update_system_records(rnames=(r[0] for r in
|
||||
IPA_DEFAULT_MASTER_SRV_REC))
|
||||
self.test_without_locations()
|
||||
|
||||
def test_one_replica_in_location(self):
|
||||
"""Put one replica to location and test if records changed properly
|
||||
"""
|
||||
|
||||
# create location prague, replica0 --> location prague
|
||||
self.master.run_command([
|
||||
'ipa', 'location-add', self.LOC_PRAGUE
|
||||
])
|
||||
self.master.run_command([
|
||||
'ipa', 'server-mod', self.replicas[0].hostname,
|
||||
'--location', self.LOC_PRAGUE
|
||||
])
|
||||
tasks.restart_named(self.replicas[0])
|
||||
|
||||
servers_without_loc = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_without_loc = DNSName(self.master.domain.name).make_absolute()
|
||||
|
||||
servers_prague_loc = (
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_prague_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PRAGUE)) +
|
||||
DNSName(self.master.domain.name).make_absolute()
|
||||
)
|
||||
|
||||
self._test_SRV_rec_against_server(
|
||||
self.replicas[0].ip, domain_prague_loc, servers_prague_loc)
|
||||
|
||||
for ip in (self.master.ip, self.replicas[1].ip):
|
||||
self._test_SRV_rec_against_server(
|
||||
ip, domain_without_loc, servers_without_loc)
|
||||
|
||||
def test_two_replicas_in_location(self):
|
||||
"""Put second replica to location and test if records changed properly
|
||||
"""
|
||||
|
||||
# create location paris, replica1 --> location prague
|
||||
self.master.run_command(['ipa', 'location-add', self.LOC_PARIS])
|
||||
self.master.run_command([
|
||||
'ipa', 'server-mod', self.replicas[1].hostname, '--location',
|
||||
self.LOC_PARIS])
|
||||
tasks.restart_named(self.replicas[1])
|
||||
|
||||
servers_without_loc = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_without_loc = DNSName(self.master.domain.name).make_absolute()
|
||||
|
||||
servers_prague_loc = (
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_prague_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PRAGUE)) + DNSName(
|
||||
self.master.domain.name).make_absolute())
|
||||
|
||||
servers_paris_loc = (
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_paris_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PARIS)) + DNSName(
|
||||
self.master.domain.name).make_absolute())
|
||||
|
||||
self._test_SRV_rec_against_server(
|
||||
self.replicas[0].ip, domain_prague_loc, servers_prague_loc)
|
||||
|
||||
self._test_SRV_rec_against_server(
|
||||
self.replicas[1].ip, domain_paris_loc, servers_paris_loc)
|
||||
|
||||
self._test_SRV_rec_against_server(
|
||||
self.master.ip, domain_without_loc, servers_without_loc)
|
||||
|
||||
def test_all_servers_in_location(self):
|
||||
"""Put master (as second server) to location and test if records
|
||||
changed properly
|
||||
"""
|
||||
|
||||
# master --> location paris
|
||||
self.master.run_command([
|
||||
'ipa', 'server-mod', self.master.hostname, '--location',
|
||||
self.LOC_PARIS])
|
||||
tasks.restart_named(self.master)
|
||||
|
||||
servers_prague_loc = (
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_prague_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PRAGUE)) + DNSName(
|
||||
self.master.domain.name).make_absolute())
|
||||
|
||||
servers_paris_loc = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_paris_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PARIS)) + DNSName(
|
||||
self.master.domain.name).make_absolute())
|
||||
|
||||
self._test_SRV_rec_against_server(
|
||||
self.replicas[0].ip, domain_prague_loc, servers_prague_loc)
|
||||
|
||||
for ip in (self.replicas[1].ip, self.master.ip):
|
||||
self._test_SRV_rec_against_server(ip, domain_paris_loc,
|
||||
servers_paris_loc)
|
||||
|
||||
def test_change_weight(self):
|
||||
"""Change weight of master and test if records changed properly
|
||||
"""
|
||||
|
||||
new_weight = 2000
|
||||
|
||||
self.master.run_command([
|
||||
'ipa', 'server-mod', self.master.hostname, '--service-weight',
|
||||
str(new_weight)
|
||||
])
|
||||
|
||||
# all servers must be restarted
|
||||
tasks.restart_named(self.master, self.replicas[0], self.replicas[1])
|
||||
|
||||
servers_prague_loc = (
|
||||
(self.PRIO_LOW, new_weight, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_prague_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PRAGUE)) + DNSName(
|
||||
self.master.domain.name).make_absolute())
|
||||
|
||||
servers_paris_loc = (
|
||||
(self.PRIO_HIGH, new_weight, DNSName(self.master.hostname)),
|
||||
(self.PRIO_LOW, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[1].hostname)),
|
||||
)
|
||||
domain_paris_loc = (
|
||||
DNSName('{}._locations'.format(self.LOC_PARIS)) + DNSName(
|
||||
self.master.domain.name).make_absolute())
|
||||
|
||||
self._test_SRV_rec_against_server(
|
||||
self.replicas[0].ip, domain_prague_loc, servers_prague_loc)
|
||||
|
||||
for ip in (self.replicas[1].ip, self.master.ip):
|
||||
self._test_SRV_rec_against_server(ip, domain_paris_loc,
|
||||
servers_paris_loc)
|
||||
|
||||
def test_restore_locations_and_weight(self):
|
||||
"""Restore locations and weight. Not just for test purposes but also
|
||||
for the following tests"""
|
||||
|
||||
for hostname in (self.master.hostname, self.replicas[0].hostname,
|
||||
self.replicas[1].hostname):
|
||||
self.master.run_command(['ipa', 'server-mod', hostname,
|
||||
'--location='''])
|
||||
|
||||
self.master.run_command(['ipa', 'location-del', self.LOC_PRAGUE])
|
||||
self.master.run_command(['ipa', 'location-del', self.LOC_PARIS])
|
||||
|
||||
self.master.run_command([
|
||||
'ipa', 'server-mod', self.master.hostname, '--service-weight',
|
||||
str(self.WEIGHT)
|
||||
])
|
||||
|
||||
tasks.restart_named(self.master, self.replicas[0], self.replicas[1])
|
||||
time.sleep(5)
|
||||
|
||||
def test_ipa_ca_records(self):
|
||||
""" Test ipa-ca dns records with firstly removing the records and then
|
||||
using the nsupdate generated by dns-update-system-records"""
|
||||
self.delete_update_system_records(rnames=IPA_CA_A_REC)
|
||||
|
||||
expected_servers = (self.master.ip, self.replicas[1].ip)
|
||||
|
||||
for ip in (self.master.ip, self.replicas[0].ip, self.replicas[1].ip):
|
||||
self._test_A_rec_against_server(ip, self.domain, expected_servers)
|
||||
|
||||
def test_ntp_srv_records(self):
|
||||
""" Test NTP dns records with firstly removing the records and then
|
||||
using the nsupdate generated by dns-update-system-records."""
|
||||
self.delete_update_system_records(rnames=(r[0] for r in
|
||||
IPA_DEFAULT_NTP_SRV_REC))
|
||||
|
||||
# we installed NTP only on master and replica[0]
|
||||
expected_servers = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.replicas[0].hostname)),
|
||||
)
|
||||
|
||||
for ip in (self.master.ip, self.replicas[0].ip, self.replicas[1].ip):
|
||||
self._test_SRV_rec_against_server(ip, self.domain,
|
||||
expected_servers,
|
||||
rec_list=IPA_DEFAULT_NTP_SRV_REC)
|
||||
|
||||
def test_adtrust_system_records(self):
|
||||
""" Test ADTrust dns records with firstly installing a trust then
|
||||
removing the records and using the nsupdate generated by
|
||||
dns-update-system-records."""
|
||||
self.master.run_command(['ipa-adtrust-install', '-U',
|
||||
'--enable-compat', '--netbios-name', 'IPA',
|
||||
'-a', self.master.config.admin_password,
|
||||
'--add-sids'])
|
||||
# lets re-kinit after adtrust-install and restart named
|
||||
tasks.kinit_admin(self.master)
|
||||
tasks.restart_named(self.master)
|
||||
time.sleep(5)
|
||||
self.delete_update_system_records(rnames=(r[0] for r in
|
||||
IPA_DEFAULT_ADTRUST_SRV_REC))
|
||||
|
||||
expected_servers = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
)
|
||||
|
||||
for ip in (self.master.ip, self.replicas[0].ip, self.replicas[1].ip):
|
||||
self._test_SRV_rec_against_server(
|
||||
ip, self.domain, expected_servers,
|
||||
rec_list=IPA_DEFAULT_ADTRUST_SRV_REC)
|
||||
|
||||
def test_remove_replica_with_ca(self):
|
||||
"""Test ipa-ca dns records after removing the replica with CA"""
|
||||
tasks.uninstall_replica(self.master, self.replicas[1])
|
||||
|
||||
self.delete_update_system_records(rnames=IPA_CA_A_REC)
|
||||
|
||||
expected_servers = (self.master.ip,)
|
||||
|
||||
self._test_A_rec_against_server(self.master.ip, self.domain,
|
||||
expected_servers)
|
||||
|
||||
def test_remove_replica_with_ntp(self):
|
||||
"""Test NTP dns records after removing the replica with NTP"""
|
||||
tasks.uninstall_replica(self.master, self.replicas[0])
|
||||
|
||||
self.delete_update_system_records(rnames=(r[0] for r in
|
||||
IPA_DEFAULT_NTP_SRV_REC))
|
||||
|
||||
expected_servers = (
|
||||
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
|
||||
)
|
||||
|
||||
self._test_SRV_rec_against_server(self.master.ip, self.domain,
|
||||
expected_servers,
|
||||
rec_list=IPA_DEFAULT_NTP_SRV_REC)
|
||||
566
ipatests/test_integration/test_dnssec.py
Normal file
566
ipatests/test_integration/test_dnssec.py
Normal file
@@ -0,0 +1,566 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
import dns.dnssec
|
||||
import dns.resolver
|
||||
import dns.name
|
||||
import time
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
test_zone = "dnssec.test."
|
||||
test_zone_repl = "dnssec-replica.test."
|
||||
root_zone = "."
|
||||
example_test_zone = "example.test."
|
||||
example2_test_zone = "example2.test."
|
||||
example3_test_zone = "example3.test."
|
||||
|
||||
|
||||
def resolve_with_dnssec(nameserver, query, rtype="SOA"):
|
||||
res = dns.resolver.Resolver()
|
||||
res.nameservers = [nameserver]
|
||||
res.lifetime = 10 # wait max 10 seconds for reply
|
||||
# enable Authenticated Data + Checking Disabled flags
|
||||
res.set_flags(dns.flags.AD | dns.flags.CD)
|
||||
|
||||
# enable EDNS v0 + enable DNSSEC-Ok flag
|
||||
res.use_edns(0, dns.flags.DO, 0)
|
||||
|
||||
ans = res.query(query, rtype)
|
||||
return ans
|
||||
|
||||
|
||||
def get_RRSIG_record(nameserver, query, rtype="SOA"):
|
||||
ans = resolve_with_dnssec(nameserver, query, rtype=rtype)
|
||||
return ans.response.find_rrset(
|
||||
ans.response.answer, dns.name.from_text(query),
|
||||
dns.rdataclass.IN, dns.rdatatype.RRSIG,
|
||||
dns.rdatatype.from_text(rtype))
|
||||
|
||||
|
||||
def is_record_signed(nameserver, query, rtype="SOA"):
|
||||
try:
|
||||
get_RRSIG_record(nameserver, query, rtype=rtype)
|
||||
except KeyError:
|
||||
return False
|
||||
except dns.exception.DNSException:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def wait_until_record_is_signed(nameserver, record, rtype="SOA",
|
||||
timeout=100):
|
||||
"""
|
||||
Returns True if record is signed, or False on timeout
|
||||
:param nameserver: nameserver to query
|
||||
:param record: query
|
||||
:param log: logger
|
||||
:param rtype: record type
|
||||
:param timeout:
|
||||
:return: True if records is signed, False if timeout
|
||||
"""
|
||||
logger.info("Waiting for signed %s record of %s from server %s (timeout "
|
||||
"%s sec)", rtype, record, nameserver, timeout)
|
||||
wait_until = time.time() + timeout
|
||||
while time.time() < wait_until:
|
||||
if is_record_signed(nameserver, record, rtype=rtype):
|
||||
return True
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
|
||||
class TestInstallDNSSECLast(IntegrationTest):
|
||||
"""Simple DNSSEC test
|
||||
|
||||
Install a server and a replica with DNS, then reinstall server
|
||||
as DNSSEC master
|
||||
"""
|
||||
num_replicas = 1
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
tasks.install_replica(cls.master, cls.replicas[0], setup_dns=True)
|
||||
|
||||
def test_install_dnssec_master(self):
|
||||
"""Both master and replica have DNS installed"""
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--dnssec-master",
|
||||
"--forwarder", self.master.config.dns_forwarder,
|
||||
"-U",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
def test_if_zone_is_signed_master(self):
|
||||
# add zone with enabled DNSSEC signing on master
|
||||
args = [
|
||||
"ipa",
|
||||
"dnszone-add", test_zone,
|
||||
"--skip-overlap-check",
|
||||
"--dnssec", "true",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
# test master
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, test_zone, timeout=100
|
||||
), "Zone %s is not signed (master)" % test_zone
|
||||
|
||||
# test replica
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, test_zone, timeout=200
|
||||
), "DNS zone %s is not signed (replica)" % test_zone
|
||||
|
||||
def test_if_zone_is_signed_replica(self):
|
||||
# add zone with enabled DNSSEC signing on replica
|
||||
args = [
|
||||
"ipa",
|
||||
"dnszone-add", test_zone_repl,
|
||||
"--skip-overlap-check",
|
||||
"--dnssec", "true",
|
||||
]
|
||||
self.replicas[0].run_command(args)
|
||||
|
||||
# test replica
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, test_zone_repl, timeout=300
|
||||
), "Zone %s is not signed (replica)" % test_zone_repl
|
||||
|
||||
# we do not need to wait, on master zones should be singed faster
|
||||
# than on replicas
|
||||
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, test_zone_repl, timeout=5
|
||||
), "DNS zone %s is not signed (master)" % test_zone
|
||||
|
||||
def test_disable_reenable_signing_master(self):
|
||||
|
||||
dnskey_old = resolve_with_dnssec(self.master.ip, test_zone,
|
||||
rtype="DNSKEY").rrset
|
||||
|
||||
# disable DNSSEC signing of zone on master
|
||||
args = [
|
||||
"ipa",
|
||||
"dnszone-mod", test_zone,
|
||||
"--dnssec", "false",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
|
||||
|
||||
# test master
|
||||
assert not is_record_signed(
|
||||
self.master.ip, test_zone
|
||||
), "Zone %s is still signed (master)" % test_zone
|
||||
|
||||
# test replica
|
||||
assert not is_record_signed(
|
||||
self.replicas[0].ip, test_zone
|
||||
), "DNS zone %s is still signed (replica)" % test_zone
|
||||
|
||||
# reenable DNSSEC signing
|
||||
args = [
|
||||
"ipa",
|
||||
"dnszone-mod", test_zone,
|
||||
"--dnssec", "true",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
# test master
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, test_zone, timeout=100
|
||||
), "Zone %s is not signed (master)" % test_zone
|
||||
|
||||
# test replica
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, test_zone, timeout=200
|
||||
), "DNS zone %s is not signed (replica)" % test_zone
|
||||
|
||||
dnskey_new = resolve_with_dnssec(self.master.ip, test_zone,
|
||||
rtype="DNSKEY").rrset
|
||||
assert dnskey_old != dnskey_new, "DNSKEY should be different"
|
||||
|
||||
def test_disable_reenable_signing_replica(self):
|
||||
|
||||
dnskey_old = resolve_with_dnssec(self.replicas[0].ip, test_zone_repl,
|
||||
rtype="DNSKEY").rrset
|
||||
|
||||
# disable DNSSEC signing of zone on replica
|
||||
args = [
|
||||
"ipa",
|
||||
"dnszone-mod", test_zone_repl,
|
||||
"--dnssec", "false",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
|
||||
|
||||
# test master
|
||||
assert not is_record_signed(
|
||||
self.master.ip, test_zone_repl
|
||||
), "Zone %s is still signed (master)" % test_zone_repl
|
||||
|
||||
# test replica
|
||||
assert not is_record_signed(
|
||||
self.replicas[0].ip, test_zone_repl
|
||||
), "DNS zone %s is still signed (replica)" % test_zone_repl
|
||||
|
||||
# reenable DNSSEC signing
|
||||
args = [
|
||||
"ipa",
|
||||
"dnszone-mod", test_zone_repl,
|
||||
"--dnssec", "true",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
# test master
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, test_zone_repl, timeout=100
|
||||
), "Zone %s is not signed (master)" % test_zone_repl
|
||||
|
||||
# test replica
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, test_zone_repl, timeout=200
|
||||
), "DNS zone %s is not signed (replica)" % test_zone_repl
|
||||
|
||||
dnskey_new = resolve_with_dnssec(self.replicas[0].ip, test_zone_repl,
|
||||
rtype="DNSKEY").rrset
|
||||
assert dnskey_old != dnskey_new, "DNSKEY should be different"
|
||||
|
||||
|
||||
class TestInstallDNSSECFirst(IntegrationTest):
|
||||
"""Simple DNSSEC test
|
||||
|
||||
Install the server with DNSSEC and then install the replica with DNS
|
||||
"""
|
||||
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(TestInstallDNSSECFirst, cls).uninstall(mh)
|
||||
|
||||
def test_sign_root_zone(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, timeout=100
|
||||
), "Zone %s is not signed (master)" % root_zone
|
||||
|
||||
# test replica
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, root_zone, timeout=300
|
||||
), "Zone %s is not signed (replica)" % root_zone
|
||||
|
||||
def test_chain_of_trust(self):
|
||||
"""
|
||||
Validate signed DNS records, using our own signed root zone
|
||||
:return:
|
||||
"""
|
||||
|
||||
# add test zone
|
||||
args = [
|
||||
"ipa", "dnszone-add", example_test_zone, "--dnssec", "true",
|
||||
"--skip-overlap-check",
|
||||
]
|
||||
|
||||
self.master.run_command(args)
|
||||
|
||||
# delegation
|
||||
args = [
|
||||
"ipa", "dnsrecord-add", root_zone, example_test_zone,
|
||||
"--ns-rec=" + self.master.hostname
|
||||
]
|
||||
self.master.run_command(args)
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, example_test_zone, timeout=100
|
||||
), "Zone %s is not signed (master)" % example_test_zone
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, example_test_zone, timeout=200
|
||||
), "Zone %s is not signed (replica)" % example_test_zone
|
||||
|
||||
# GET DNSKEY records from zone
|
||||
ans = resolve_with_dnssec(self.master.ip, example_test_zone,
|
||||
rtype="DNSKEY")
|
||||
dnskey_rrset = ans.response.get_rrset(
|
||||
ans.response.answer,
|
||||
dns.name.from_text(example_test_zone),
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.DNSKEY)
|
||||
assert dnskey_rrset, "No DNSKEY records received"
|
||||
|
||||
logger.debug("DNSKEY records returned: %s", dnskey_rrset.to_text())
|
||||
|
||||
# generate DS records
|
||||
ds_records = []
|
||||
for key_rdata in dnskey_rrset:
|
||||
if key_rdata.flags != 257:
|
||||
continue # it is not KSK
|
||||
ds_records.append(dns.dnssec.make_ds(example_test_zone, key_rdata,
|
||||
'sha256'))
|
||||
assert ds_records, ("No KSK returned from the %s zone" %
|
||||
example_test_zone)
|
||||
|
||||
logger.debug("DS records for %s created: %r", example_test_zone,
|
||||
ds_records)
|
||||
|
||||
# add DS records to root zone
|
||||
args = [
|
||||
"ipa", "dnsrecord-add", root_zone, example_test_zone,
|
||||
# DS record requires to coexists with NS
|
||||
"--ns-rec", self.master.hostname,
|
||||
]
|
||||
for ds in ds_records:
|
||||
args.append("--ds-rec")
|
||||
args.append(ds.to_text())
|
||||
|
||||
self.master.run_command(args)
|
||||
|
||||
# wait until DS records it replicated
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, example_test_zone, timeout=100,
|
||||
rtype="DS"
|
||||
), "No DS record of '%s' returned from replica" % example_test_zone
|
||||
|
||||
# extract DSKEY from root zone
|
||||
ans = resolve_with_dnssec(self.master.ip, root_zone,
|
||||
rtype="DNSKEY")
|
||||
dnskey_rrset = ans.response.get_rrset(ans.response.answer,
|
||||
dns.name.from_text(root_zone),
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.DNSKEY)
|
||||
assert dnskey_rrset, "No DNSKEY records received"
|
||||
|
||||
logger.debug("DNSKEY records returned: %s", dnskey_rrset.to_text())
|
||||
|
||||
# export trust keys for root zone
|
||||
root_key_rdatas = []
|
||||
for key_rdata in dnskey_rrset:
|
||||
if key_rdata.flags != 257:
|
||||
continue # it is not KSK
|
||||
root_key_rdatas.append(key_rdata)
|
||||
|
||||
assert root_key_rdatas, "No KSK returned from the root zone"
|
||||
|
||||
root_keys_rrset = dns.rrset.from_rdata_list(dnskey_rrset.name,
|
||||
dnskey_rrset.ttl,
|
||||
root_key_rdatas)
|
||||
logger.debug("Root zone trusted key: %s", root_keys_rrset.to_text())
|
||||
|
||||
# set trusted key for our root zone
|
||||
self.master.put_file_contents(paths.DNSSEC_TRUSTED_KEY,
|
||||
root_keys_rrset.to_text() + '\n')
|
||||
self.replicas[0].put_file_contents(paths.DNSSEC_TRUSTED_KEY,
|
||||
root_keys_rrset.to_text() + '\n')
|
||||
|
||||
# verify signatures
|
||||
time.sleep(1)
|
||||
args = [
|
||||
"drill", "@localhost", "-k",
|
||||
paths.DNSSEC_TRUSTED_KEY, "-S",
|
||||
example_test_zone, "SOA"
|
||||
]
|
||||
|
||||
# test if signature chains are valid
|
||||
self.master.run_command(args)
|
||||
self.replicas[0].run_command(args)
|
||||
|
||||
|
||||
class TestMigrateDNSSECMaster(IntegrationTest):
|
||||
"""test DNSSEC master migration
|
||||
|
||||
Install a server and a replica with DNS, then reinstall server
|
||||
as DNSSEC master
|
||||
Test:
|
||||
* migrate dnssec master to replica
|
||||
* create new zone
|
||||
* verify if zone is signed on all replicas
|
||||
* add new replica
|
||||
* add new zone
|
||||
* test if new zone is signed on all replicas
|
||||
"""
|
||||
num_replicas = 2
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
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)
|
||||
|
||||
def test_migrate_dnssec_master(self):
|
||||
"""Both master and replica have DNS installed"""
|
||||
backup_filename = "/var/lib/ipa/ipa-kasp.db.backup"
|
||||
replica_backup_filename = "/tmp/ipa-kasp.db.backup"
|
||||
|
||||
# add test zone
|
||||
args = [
|
||||
"ipa", "dnszone-add", example_test_zone, "--dnssec", "true",
|
||||
"--skip-overlap-check",
|
||||
]
|
||||
|
||||
self.master.run_command(args)
|
||||
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, example_test_zone, timeout=100
|
||||
), "Zone %s is not signed (master)" % example_test_zone
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, example_test_zone, timeout=200
|
||||
), "Zone %s is not signed (replica)" % example_test_zone
|
||||
|
||||
dnskey_old = resolve_with_dnssec(self.master.ip, example_test_zone,
|
||||
rtype="DNSKEY").rrset
|
||||
|
||||
# migrate dnssec master to replica
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--disable-dnssec-master",
|
||||
"--forwarder", self.master.config.dns_forwarder,
|
||||
"--force",
|
||||
"-U",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
# move content of "ipa-kasp.db.backup" to replica
|
||||
kasp_db_backup = self.master.get_file_contents(backup_filename)
|
||||
self.replicas[0].put_file_contents(replica_backup_filename,
|
||||
kasp_db_backup)
|
||||
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--dnssec-master",
|
||||
"--kasp-db", replica_backup_filename,
|
||||
"--forwarder", self.master.config.dns_forwarder,
|
||||
"-U",
|
||||
]
|
||||
self.replicas[0].run_command(args)
|
||||
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, example_test_zone, timeout=100
|
||||
), "Zone %s is not signed after migration (master)" % example_test_zone
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, example_test_zone, timeout=200
|
||||
), "Zone %s is not signed after migration (replica)" % example_test_zone
|
||||
|
||||
# test if dnskey are the same
|
||||
dnskey_new = resolve_with_dnssec(self.master.ip, example_test_zone,
|
||||
rtype="DNSKEY").rrset
|
||||
assert dnskey_old == dnskey_new, "DNSKEY should be the same"
|
||||
|
||||
# add test zone
|
||||
args = [
|
||||
"ipa", "dnszone-add", example2_test_zone, "--dnssec", "true",
|
||||
"--skip-overlap-check",
|
||||
]
|
||||
self.replicas[0].run_command(args)
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, example2_test_zone, timeout=100
|
||||
), ("Zone %s is not signed after migration (replica - dnssec master)"
|
||||
% example2_test_zone)
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, example2_test_zone, timeout=200
|
||||
), ("Zone %s is not signed after migration (master)"
|
||||
% example2_test_zone)
|
||||
|
||||
# add new replica
|
||||
tasks.install_replica(self.master, self.replicas[1], setup_dns=True)
|
||||
|
||||
# test if originial zones are signed on new replica
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[1].ip, example_test_zone, timeout=200
|
||||
), ("Zone %s is not signed (new replica)"
|
||||
% example_test_zone)
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[1].ip, example2_test_zone, timeout=200
|
||||
), ("Zone %s is not signed (new replica)"
|
||||
% example2_test_zone)
|
||||
|
||||
# add new zone to new replica
|
||||
args = [
|
||||
"ipa", "dnszone-add", example3_test_zone, "--dnssec", "true",
|
||||
"--skip-overlap-check",
|
||||
]
|
||||
self.replicas[1].run_command(args)
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[1].ip, example3_test_zone, timeout=200
|
||||
), ("Zone %s is not signed (new replica)"
|
||||
% example3_test_zone)
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, example3_test_zone, timeout=200
|
||||
), ("Zone %s is not signed (replica)"
|
||||
% example3_test_zone)
|
||||
# wait until zone is signed
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, example3_test_zone, timeout=200
|
||||
), ("Zone %s is not signed (master)"
|
||||
% example3_test_zone)
|
||||
73
ipatests/test_integration/test_external_ca.py
Normal file
73
ipatests/test_integration/test_external_ca.py
Normal file
@@ -0,0 +1,73 @@
|
||||
#
|
||||
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.test_integration.create_external_ca import ExternalCA
|
||||
|
||||
|
||||
class TestExternalCA(IntegrationTest):
|
||||
"""
|
||||
Test of FreeIPA server installation with exernal CA
|
||||
"""
|
||||
@tasks.collect_logs
|
||||
def test_external_ca(self):
|
||||
# Step 1 of ipa-server-install
|
||||
self.master.run_command([
|
||||
'ipa-server-install', '-U',
|
||||
'-a', self.master.config.admin_password,
|
||||
'-p', self.master.config.dirman_password,
|
||||
'--setup-dns', '--no-forwarders',
|
||||
'-n', self.master.domain.name,
|
||||
'-r', self.master.domain.realm,
|
||||
'--domain-level=%i' % self.master.config.domain_level,
|
||||
'--external-ca'
|
||||
])
|
||||
|
||||
test_dir = self.master.config.test_dir
|
||||
|
||||
# Get IPA CSR as bytes
|
||||
ipa_csr = self.master.get_file_contents('/root/ipa.csr')
|
||||
|
||||
external_ca = ExternalCA()
|
||||
# Create root CA
|
||||
root_ca = external_ca.create_ca()
|
||||
# Sign CSR
|
||||
ipa_ca = external_ca.sign_csr(ipa_csr)
|
||||
|
||||
root_ca_fname = os.path.join(test_dir, 'root_ca.crt')
|
||||
ipa_ca_fname = os.path.join(test_dir, 'ipa_ca.crt')
|
||||
|
||||
# Transport certificates (string > file) to master
|
||||
self.master.put_file_contents(root_ca_fname, root_ca)
|
||||
self.master.put_file_contents(ipa_ca_fname, ipa_ca)
|
||||
|
||||
# Step 2 of ipa-server-install
|
||||
self.master.run_command([
|
||||
'ipa-server-install',
|
||||
'-a', self.master.config.admin_password,
|
||||
'-p', self.master.config.dirman_password,
|
||||
'--external-cert-file', ipa_ca_fname,
|
||||
'--external-cert-file', root_ca_fname
|
||||
])
|
||||
|
||||
# Make sure IPA server is working properly
|
||||
tasks.kinit_admin(self.master)
|
||||
result = self.master.run_command(['ipa', 'user-show', 'admin'])
|
||||
assert 'User login: admin' in result.stdout_text
|
||||
311
ipatests/test_integration/test_forced_client_reenrollment.py
Normal file
311
ipatests/test_integration/test_forced_client_reenrollment.py
Normal file
@@ -0,0 +1,311 @@
|
||||
# Authors:
|
||||
# Ana Krivokapic <akrivoka@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from ipaplatform.paths import paths
|
||||
import pytest
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CLIENT_KEYTAB = paths.KRB5_KEYTAB
|
||||
|
||||
|
||||
class TestForcedClientReenrollment(IntegrationTest):
|
||||
"""
|
||||
Forced client re-enrollment
|
||||
http://www.freeipa.org/page/V3/Forced_client_re-enrollment#Test_Plan
|
||||
"""
|
||||
num_replicas = 1
|
||||
num_clients = 1
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestForcedClientReenrollment, cls).install(mh)
|
||||
tasks.install_master(cls.master)
|
||||
|
||||
cls.client_dom = cls.clients[0].hostname.split('.', 1)[1]
|
||||
if cls.client_dom != cls.master.domain.name:
|
||||
# In cases where client is managed by upstream DNS server we
|
||||
# overlap its zone so we can save DNS records (e.g. SSHFP) for
|
||||
# comparison.
|
||||
servers = [cls.master] + cls.replicas
|
||||
tasks.add_dns_zone(cls.master, cls.client_dom,
|
||||
skip_overlap_check=True,
|
||||
dynamic_update=True,
|
||||
add_a_record_hosts=servers
|
||||
)
|
||||
|
||||
tasks.install_replica(cls.master, cls.replicas[0], setup_ca=False)
|
||||
cls.BACKUP_KEYTAB = os.path.join(
|
||||
cls.master.config.test_dir,
|
||||
'krb5.keytab'
|
||||
)
|
||||
|
||||
def test_reenroll_with_force_join(self, client):
|
||||
"""
|
||||
Client re-enrollment using admin credentials (--force-join)
|
||||
"""
|
||||
sshfp_record_pre = self.get_sshfp_record()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry()
|
||||
self.reenroll_client(force_join=True)
|
||||
sshfp_record_post = self.get_sshfp_record()
|
||||
assert sshfp_record_pre == sshfp_record_post
|
||||
|
||||
def test_reenroll_with_keytab(self, client):
|
||||
"""
|
||||
Client re-enrollment using keytab
|
||||
"""
|
||||
self.backup_keytab()
|
||||
sshfp_record_pre = self.get_sshfp_record()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry()
|
||||
self.restore_keytab()
|
||||
self.reenroll_client(keytab=self.BACKUP_KEYTAB)
|
||||
sshfp_record_post = self.get_sshfp_record()
|
||||
assert sshfp_record_pre == sshfp_record_post
|
||||
|
||||
def test_reenroll_with_both_force_join_and_keytab(self, client):
|
||||
"""
|
||||
Client re-enrollment using both --force-join and --keytab options
|
||||
"""
|
||||
self.backup_keytab()
|
||||
sshfp_record_pre = self.get_sshfp_record()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry()
|
||||
self.restore_keytab()
|
||||
self.reenroll_client(force_join=True, keytab=self.BACKUP_KEYTAB)
|
||||
sshfp_record_post = self.get_sshfp_record()
|
||||
assert sshfp_record_pre == sshfp_record_post
|
||||
|
||||
def test_reenroll_to_replica(self, client):
|
||||
"""
|
||||
Client re-enrollment using keytab, to a replica
|
||||
"""
|
||||
self.backup_keytab()
|
||||
sshfp_record_pre = self.get_sshfp_record()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry()
|
||||
self.restore_keytab()
|
||||
self.reenroll_client(keytab=self.BACKUP_KEYTAB, to_replica=True)
|
||||
sshfp_record_post = self.get_sshfp_record()
|
||||
assert sshfp_record_pre == sshfp_record_post
|
||||
|
||||
def test_try_to_reenroll_with_disabled_host(self, client):
|
||||
"""
|
||||
Client re-enrollment using keytab, with disabled host
|
||||
"""
|
||||
self.backup_keytab()
|
||||
self.disable_client_host_entry()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry(enabled=False)
|
||||
self.restore_keytab()
|
||||
self.reenroll_client(keytab=self.BACKUP_KEYTAB, expect_fail=True)
|
||||
|
||||
def test_try_to_reenroll_with_uninstalled_host(self, client):
|
||||
"""
|
||||
Client re-enrollment using keytab, with uninstalled host
|
||||
"""
|
||||
self.backup_keytab()
|
||||
self.uninstall_client()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry(enabled=False)
|
||||
self.restore_keytab()
|
||||
self.reenroll_client(keytab=self.BACKUP_KEYTAB, expect_fail=True)
|
||||
|
||||
def test_try_to_reenroll_with_deleted_host(self, client):
|
||||
"""
|
||||
Client re-enrollment using keytab, with deleted host
|
||||
"""
|
||||
self.backup_keytab()
|
||||
self.delete_client_host_entry()
|
||||
self.restore_client()
|
||||
self.check_client_host_entry(not_found=True)
|
||||
self.restore_keytab()
|
||||
self.reenroll_client(keytab=self.BACKUP_KEYTAB, expect_fail=True)
|
||||
|
||||
def test_try_to_reenroll_with_incorrect_keytab(self, client):
|
||||
"""
|
||||
Client re-enrollment using keytab, with incorrect keytab file
|
||||
"""
|
||||
EMPTY_KEYTAB = os.path.join(
|
||||
self.clients[0].config.test_dir,
|
||||
'empty.keytab'
|
||||
)
|
||||
self.restore_client()
|
||||
self.check_client_host_entry()
|
||||
self.clients[0].run_command(['touch', EMPTY_KEYTAB])
|
||||
self.reenroll_client(keytab=EMPTY_KEYTAB, expect_fail=True)
|
||||
|
||||
def uninstall_client(self):
|
||||
self.clients[0].run_command(
|
||||
['ipa-client-install', '--uninstall', '-U'],
|
||||
set_env=False,
|
||||
raiseonerr=False
|
||||
)
|
||||
|
||||
def restore_client(self):
|
||||
client = self.clients[0]
|
||||
|
||||
client.run_command([
|
||||
'iptables',
|
||||
'-A', 'INPUT',
|
||||
'-j', 'ACCEPT',
|
||||
'-p', 'tcp',
|
||||
'--dport', '22'
|
||||
])
|
||||
for host in [self.master] + self.replicas:
|
||||
client.run_command([
|
||||
'iptables',
|
||||
'-A', 'INPUT',
|
||||
'-j', 'REJECT',
|
||||
'-p', 'all',
|
||||
'--source', host.ip
|
||||
])
|
||||
self.uninstall_client()
|
||||
client.run_command(['iptables', '-F'])
|
||||
|
||||
def reenroll_client(self, keytab=None, to_replica=False, force_join=False,
|
||||
expect_fail=False):
|
||||
server = self.replicas[0] if to_replica else self.master
|
||||
client = self.clients[0]
|
||||
|
||||
self.fix_resolv_conf(client, server)
|
||||
|
||||
args = [
|
||||
'ipa-client-install', '-U',
|
||||
'--server', server.hostname,
|
||||
'--domain', server.domain.name
|
||||
]
|
||||
if force_join:
|
||||
args.append('--force-join')
|
||||
if keytab:
|
||||
args.extend(['--keytab', keytab])
|
||||
else:
|
||||
args.extend([
|
||||
'-p', client.config.admin_name,
|
||||
'-w', client.config.admin_password
|
||||
])
|
||||
|
||||
result = client.run_command(
|
||||
args,
|
||||
set_env=False,
|
||||
raiseonerr=not expect_fail
|
||||
)
|
||||
assert 'IPA Server: %s' % server.hostname in result.stderr_text
|
||||
|
||||
if expect_fail:
|
||||
err_msg = "Kerberos authentication failed: "
|
||||
assert result.returncode == 1
|
||||
assert err_msg in result.stderr_text
|
||||
elif force_join and keytab:
|
||||
warn_msg = ("Option 'force-join' has no additional effect "
|
||||
"when used with together with option 'keytab'.")
|
||||
assert warn_msg in result.stderr_text
|
||||
|
||||
def check_client_host_entry(self, enabled=True, not_found=False):
|
||||
result = self.master.run_command(
|
||||
['ipa', 'host-show', self.clients[0].hostname],
|
||||
raiseonerr=not not_found
|
||||
)
|
||||
|
||||
if not_found:
|
||||
assert result.returncode == 2
|
||||
assert 'host not found' in result.stderr_text
|
||||
elif enabled:
|
||||
assert 'Certificate:' not in result.stdout_text
|
||||
assert 'Keytab: True' in result.stdout_text
|
||||
else:
|
||||
assert 'Certificate:' not in result.stdout_text
|
||||
assert 'Keytab: False' in result.stdout_text
|
||||
|
||||
def disable_client_host_entry(self):
|
||||
self.master.run_command(
|
||||
['ipa', 'host-disable', self.clients[0].hostname]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete_client_host_entry(cls):
|
||||
try:
|
||||
cls.master.run_command(
|
||||
['ipa', 'host-del', cls.clients[0].hostname]
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode != 2:
|
||||
raise
|
||||
|
||||
def get_sshfp_record(self):
|
||||
sshfp_record = ''
|
||||
client_host = self.clients[0].hostname.split('.')[0]
|
||||
|
||||
result = self.master.run_command(
|
||||
['ipa', 'dnsrecord-show', self.client_dom, client_host]
|
||||
)
|
||||
|
||||
lines = result.stdout_text.splitlines()
|
||||
for line in lines:
|
||||
if 'SSHFP record:' in line:
|
||||
sshfp_record = line.replace('SSHFP record:', '').strip()
|
||||
|
||||
assert sshfp_record, 'SSHFP record not found'
|
||||
|
||||
sshfp_record = set(sshfp_record.split(', '))
|
||||
logger.debug("SSHFP record for host %s: %s",
|
||||
client_host, str(sshfp_record))
|
||||
|
||||
return sshfp_record
|
||||
|
||||
def backup_keytab(self):
|
||||
contents = self.clients[0].get_file_contents(CLIENT_KEYTAB)
|
||||
self.master.put_file_contents(self.BACKUP_KEYTAB, contents)
|
||||
|
||||
def restore_keytab(self):
|
||||
contents = self.master.get_file_contents(self.BACKUP_KEYTAB)
|
||||
self.clients[0].put_file_contents(self.BACKUP_KEYTAB, contents)
|
||||
|
||||
@classmethod
|
||||
def fix_resolv_conf(cls, client, server):
|
||||
"""
|
||||
Put server's ip address at the top of resolv.conf
|
||||
"""
|
||||
contents = client.get_file_contents(paths.RESOLV_CONF,
|
||||
encoding='utf-8')
|
||||
nameserver = 'nameserver %s\n' % server.ip
|
||||
|
||||
if not contents.startswith(nameserver):
|
||||
contents = nameserver + contents.replace(nameserver, '')
|
||||
client.put_file_contents(paths.RESOLV_CONF, contents)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(request):
|
||||
# Here we call "fix_resolv_conf" method before every ipa-client-install so
|
||||
# we get the client pointing to ipa master as DNS server.
|
||||
request.cls.fix_resolv_conf(request.cls.clients[0], request.cls.master)
|
||||
tasks.install_client(request.cls.master, request.cls.clients[0])
|
||||
|
||||
def teardown_client():
|
||||
tasks.uninstall_client(request.cls.clients[0])
|
||||
request.cls.delete_client_host_entry()
|
||||
request.addfinalizer(teardown_client)
|
||||
58
ipatests/test_integration/test_http_kdc_proxy.py
Normal file
58
ipatests/test_integration/test_http_kdc_proxy.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import six
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
class TestHttpKdcProxy(IntegrationTest):
|
||||
topology = "line"
|
||||
num_clients = 1
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestHttpKdcProxy, cls).install(mh)
|
||||
# Block access from client to master's port 88
|
||||
cls.clients[0].run_command([
|
||||
'iptables', '-A', 'OUTPUT', '-p', 'tcp',
|
||||
'--dport', '88', '-j', 'DROP'])
|
||||
cls.clients[0].run_command([
|
||||
'iptables', '-A', 'OUTPUT', '-p', 'udp',
|
||||
'--dport', '88', '-j', 'DROP'])
|
||||
cls.clients[0].run_command([
|
||||
'ip6tables', '-A', 'OUTPUT', '-p', 'tcp',
|
||||
'--dport', '88', '-j', 'DROP'])
|
||||
cls.clients[0].run_command([
|
||||
'ip6tables', '-A', 'OUTPUT', '-p', 'udp',
|
||||
'--dport', '88', '-j', 'DROP'])
|
||||
# configure client
|
||||
cls.clients[0].run_command(
|
||||
"sed -i 's/ kdc = .*$/ kdc = https:\/\/%s\/KdcProxy/' %s" % (
|
||||
cls.master.hostname, paths.KRB5_CONF)
|
||||
)
|
||||
cls.clients[0].run_command(
|
||||
"sed -i 's/master_kdc = .*$/master_kdc"
|
||||
" = https:\/\/%s\/KdcProxy/' %s" % (
|
||||
cls.master.hostname, paths.KRB5_CONF)
|
||||
)
|
||||
# Workaround for https://fedorahosted.org/freeipa/ticket/6443
|
||||
cls.clients[0].run_command(['systemctl', 'restart', 'sssd.service'])
|
||||
# End of workaround
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
super(TestHttpKdcProxy, cls).uninstall(mh)
|
||||
cls.clients[0].run_command(['iptables', '-F'])
|
||||
|
||||
def test_http_kdc_proxy_works(self):
|
||||
result = tasks.kinit_admin(self.clients[0], raiseonerr=False)
|
||||
assert(result.returncode == 0), (
|
||||
"Unable to kinit using KdcProxy: %s" % result.stderr_text
|
||||
)
|
||||
238
ipatests/test_integration/test_idviews.py
Normal file
238
ipatests/test_integration/test_idviews.py
Normal file
@@ -0,0 +1,238 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration.env_config import get_global_config
|
||||
from ipaplatform.paths import paths
|
||||
config = get_global_config()
|
||||
|
||||
|
||||
class TestCertsInIDOverrides(IntegrationTest):
|
||||
topology = "line"
|
||||
num_ad_domains = 1
|
||||
adview = 'Default Trust View'
|
||||
cert_re = re.compile('Certificate: (?P<cert>.*?)\\s+.*')
|
||||
adcert1 = 'MyCert1'
|
||||
adcert2 = 'MyCert2'
|
||||
adcert1_file = adcert1 + '.crt'
|
||||
adcert2_file = adcert2 + '.crt'
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
super(TestCertsInIDOverrides, cls).uninstall(mh)
|
||||
cls.master.run_command(['rm', '-rf', cls.reqdir], raiseonerr=False)
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestCertsInIDOverrides, cls).install(mh)
|
||||
cls.ad = config.ad_domains[0].ads[0]
|
||||
cls.ad_domain = cls.ad.domain.name
|
||||
cls.aduser = "testuser@%s" % cls.ad_domain
|
||||
|
||||
master = cls.master
|
||||
# A setup for test_dbus_user_lookup
|
||||
master.run_command(['dnf', 'install', '-y', 'sssd-dbus'],
|
||||
raiseonerr=False)
|
||||
# The tasks.modify_sssd_conf way did not work because
|
||||
# sssd_domain.set_option knows nothing about 'services' parameter of
|
||||
# the sssd config file. Therefore I am using sed approach
|
||||
master.run_command(
|
||||
"sed -i '/^services/ s/$/, ifp/' %s" % paths.SSSD_CONF)
|
||||
master.run_command(
|
||||
"sed -i 's/= 7/= 0xFFF0/' %s" % paths.SSSD_CONF, raiseonerr=False)
|
||||
master.run_command(['systemctl', 'restart', 'sssd.service'])
|
||||
# End of setup for test_dbus_user_lookup
|
||||
|
||||
# AD-related stuff
|
||||
tasks.install_adtrust(master)
|
||||
tasks.sync_time(master, cls.ad)
|
||||
tasks.establish_trust_with_ad(cls.master, cls.ad_domain,
|
||||
extra_args=['--range-type',
|
||||
'ipa-ad-trust'])
|
||||
|
||||
cls.reqdir = os.path.join(master.config.test_dir, "certs")
|
||||
cls.reqfile1 = os.path.join(cls.reqdir, "test1.csr")
|
||||
cls.reqfile2 = os.path.join(cls.reqdir, "test2.csr")
|
||||
cls.pwname = os.path.join(cls.reqdir, "pwd")
|
||||
|
||||
# Create a NSS database folder
|
||||
master.run_command(['mkdir', cls.reqdir], raiseonerr=False)
|
||||
# Create an empty password file
|
||||
master.run_command(["touch", cls.pwname], raiseonerr=False)
|
||||
|
||||
# Initialize NSS database
|
||||
tasks.run_certutil(master, ["-N", "-f", cls.pwname], cls.reqdir)
|
||||
# Now generate self-signed certs for a windows user
|
||||
stdin_text = string.digits+string.ascii_letters[2:] + '\n'
|
||||
tasks.run_certutil(master, ['-S', '-s',
|
||||
"cn=%s,dc=ad,dc=test" % cls.adcert1, '-n',
|
||||
cls.adcert1, '-x', '-t', 'CT,C,C', '-v',
|
||||
'120', '-m', '1234'],
|
||||
cls.reqdir, stdin=stdin_text)
|
||||
tasks.run_certutil(master, ['-S', '-s',
|
||||
"cn=%s,dc=ad,dc=test" % cls.adcert2, '-n',
|
||||
cls.adcert2, '-x', '-t', 'CT,C,C', '-v',
|
||||
'120', '-m', '1234'],
|
||||
cls.reqdir, stdin=stdin_text)
|
||||
|
||||
# Export the previously generated cert
|
||||
tasks.run_certutil(master, ['-L', '-n', cls.adcert1, '-a', '>',
|
||||
cls.adcert1_file], cls.reqdir)
|
||||
tasks.run_certutil(master, ['-L', '-n', cls.adcert2, '-a', '>',
|
||||
cls.adcert2_file], cls.reqdir)
|
||||
cls.cert1_base64 = cls.master.run_command(
|
||||
"openssl x509 -outform der -in %s | base64 -w 0" % cls.adcert1_file
|
||||
).stdout_text
|
||||
cls.cert2_base64 = cls.master.run_command(
|
||||
"openssl x509 -outform der -in %s | base64 -w 0" % cls.adcert2_file
|
||||
).stdout_text
|
||||
cls.cert1_pem = cls.master.run_command(
|
||||
"openssl x509 -in %s -outform pem" % cls.adcert1_file
|
||||
).stdout_text
|
||||
cls.cert2_pem = cls.master.run_command(
|
||||
"openssl x509 -in %s -outform pem" % cls.adcert2_file
|
||||
).stdout_text
|
||||
|
||||
def test_certs_in_idoverrides_ad_users(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Certs_in_ID_overrides/Test_Plan
|
||||
#Test_case:_Manipulate_certificate_in_ID_override_entry
|
||||
"""
|
||||
master = self.master
|
||||
master.run_command(['ipa', 'idoverrideuser-add',
|
||||
self.adview, self.aduser])
|
||||
master.run_command(['ipa', 'idoverrideuser-add-cert',
|
||||
self.adview, self.aduser,
|
||||
"--certificate=%s" % self.cert1_base64])
|
||||
master.run_command(['ipa', 'idoverrideuser-add-cert',
|
||||
self.adview, self.aduser,
|
||||
"--certificate=%s" % self.cert2_base64])
|
||||
result = master.run_command(['ipa', 'idoverrideuser-show',
|
||||
self.adview, self.aduser])
|
||||
assert(self.cert1_base64 in result.stdout_text and
|
||||
self.cert2_base64 in result.stdout_text), (
|
||||
"idoverrideuser-show does not show all user certificates")
|
||||
master.run_command(['ipa', 'idoverrideuser-remove-cert',
|
||||
self.adview, self.aduser,
|
||||
"--certificate=%s" % self.cert2_base64])
|
||||
|
||||
def test_dbus_user_lookup(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Certs_in_ID_overrides/Test_Plan
|
||||
#Test_case:_User_lookup_by_certificate
|
||||
"""
|
||||
|
||||
master = self.master
|
||||
userpath_re = re.compile('.*object path "(.*?)".*')
|
||||
|
||||
result0 = master.run_command([
|
||||
'dbus-send', '--system', '--print-reply',
|
||||
'--dest=org.freedesktop.sssd.infopipe',
|
||||
'/org/freedesktop/sssd/infopipe/Users',
|
||||
'org.freedesktop.sssd.infopipe.Users.FindByCertificate',
|
||||
"string:%s" % self.cert1_pem])
|
||||
assert("object path" in result0.stdout_text), (
|
||||
"command output did not contain expected"
|
||||
"string:\n\n%s" % result0.stdout_text)
|
||||
userpath = userpath_re.findall(result0.stdout_text)[0]
|
||||
result1 = master.run_command(
|
||||
"dbus-send --system --print-reply"
|
||||
" --dest=org.freedesktop.sssd.infopipe"
|
||||
" %s org.freedesktop.DBus.Properties.Get"
|
||||
" string:\"org.freedesktop.sssd.infopipe.Users.User\""
|
||||
" string:\"name\"" % userpath, raiseonerr=False)
|
||||
assert(self.aduser in result1.stdout_text)
|
||||
result2 = master.run_command(
|
||||
"dbus-send --system --print-reply"
|
||||
" --dest=org.freedesktop.sssd.infopipe"
|
||||
" %s org.freedesktop.DBus.Properties.GetAll"
|
||||
" string:\"org.freedesktop.sssd.infopipe.Users.User\"" % userpath
|
||||
)
|
||||
assert('dict entry' in result2.stdout_text)
|
||||
|
||||
|
||||
class TestRulesWithServicePrincipals(IntegrationTest):
|
||||
"""
|
||||
https://fedorahosted.org/freeipa/ticket/6146
|
||||
"""
|
||||
|
||||
topology = 'star'
|
||||
num_replicas = 0
|
||||
service_certprofile = 'caIPAserviceCert'
|
||||
caacl = 'test_caacl'
|
||||
keytab = "replica.keytab"
|
||||
csr = "my.csr"
|
||||
csr_conf = "replica.cnf"
|
||||
|
||||
@classmethod
|
||||
def prepare_config(cls):
|
||||
template = """
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[req_distinguished_name]
|
||||
commonName = %s
|
||||
|
||||
[ v3_req ]
|
||||
|
||||
# Extensions to add to a certificate request
|
||||
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = %s
|
||||
DNS.2 = %s
|
||||
EOF
|
||||
"""
|
||||
|
||||
contents = template % (cls.replica, cls.replica, cls.master.hostname)
|
||||
cls.master.run_command("cat <<EOF > %s\n%s" % (cls.csr_conf, contents))
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestRulesWithServicePrincipals, cls).install(mh)
|
||||
master = cls.master
|
||||
tasks.kinit_admin(master)
|
||||
cls.replica = "replica.%s" % master.domain.name
|
||||
master.run_command(['ipa', 'host-add', cls.replica, '--force'])
|
||||
cls.service_name = "svc/%s" % master.hostname
|
||||
cls.replica_service_name = "svc/%s" % cls.replica
|
||||
master.run_command("ipa service-add %s" % cls.service_name)
|
||||
master.run_command("ipa service-add %s --force" %
|
||||
cls.replica_service_name)
|
||||
master.run_command("ipa service-add-host %s --hosts %s" % (
|
||||
cls.service_name, cls.replica))
|
||||
master.run_command("ipa caacl-add %s --desc \"test\"" % cls.caacl)
|
||||
master.run_command("ipa caacl-add-host %s --hosts %s" % (cls.caacl,
|
||||
cls.replica))
|
||||
master.run_command("ipa caacl-add-service %s --services"
|
||||
" svc/`hostname`" % cls.caacl)
|
||||
master.run_command("ipa-getkeytab -p host/%s@%s -k %s" % (
|
||||
cls.replica, master.domain.realm, cls.keytab))
|
||||
master.run_command("kinit -kt %s host/%s" % (cls.keytab, cls.replica))
|
||||
|
||||
# Prepare a CSR
|
||||
|
||||
cls.prepare_config()
|
||||
stdin_text = "qwerty\nqwerty\n%s\n" % cls.replica
|
||||
|
||||
master.run_command(['openssl', 'req', '-config', cls.csr_conf, '-new',
|
||||
'-out', cls.csr], stdin_text=stdin_text)
|
||||
|
||||
def test_rules_with_service_principals(self):
|
||||
result = self.master.run_command(['ipa', 'cert-request', self.csr,
|
||||
'--principal', "svc/%s@%s" % (
|
||||
self.replica,
|
||||
self.master.domain.realm),
|
||||
'--profile-id',
|
||||
self.service_certprofile],
|
||||
raiseonerr=False)
|
||||
assert(result.returncode == 0), (
|
||||
'Failed to add a cert to custom certprofile')
|
||||
318
ipatests/test_integration/test_installation.py
Normal file
318
ipatests/test_integration/test_installation.py
Normal file
@@ -0,0 +1,318 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
Module provides tests which testing ability of various subsystems to be
|
||||
installed.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from ipalib.constants import DOMAIN_LEVEL_0
|
||||
from ipatests.pytest_plugins.integration.env_config import get_global_config
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
config = get_global_config()
|
||||
|
||||
class InstallTestBase1(IntegrationTest):
|
||||
|
||||
num_replicas = 3
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False)
|
||||
|
||||
def test_replica0_ca_less_install(self):
|
||||
tasks.install_replica(self.master, self.replicas[0], setup_ca=False)
|
||||
|
||||
def test_replica0_ipa_ca_install(self):
|
||||
tasks.install_ca(self.replicas[0])
|
||||
|
||||
def test_replica0_ipa_kra_install(self):
|
||||
tasks.install_kra(self.replicas[0], first_instance=True)
|
||||
|
||||
def test_replica0_ipa_dns_install(self):
|
||||
tasks.install_dns(self.replicas[0])
|
||||
|
||||
def test_replica1_with_ca_install(self):
|
||||
tasks.install_replica(self.master, self.replicas[1], setup_ca=True)
|
||||
|
||||
def test_replica1_ipa_kra_install(self):
|
||||
tasks.install_kra(self.replicas[1])
|
||||
|
||||
def test_replica1_ipa_dns_install(self):
|
||||
tasks.install_dns(self.replicas[1])
|
||||
|
||||
def test_replica2_with_ca_kra_install(self):
|
||||
tasks.install_replica(self.master, self.replicas[2], setup_ca=True,
|
||||
setup_kra=True)
|
||||
|
||||
def test_replica2_ipa_dns_install(self):
|
||||
tasks.install_dns(self.replicas[2])
|
||||
|
||||
|
||||
class InstallTestBase2(IntegrationTest):
|
||||
|
||||
num_replicas = 3
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False)
|
||||
|
||||
def test_replica0_with_ca_kra_dns_install(self):
|
||||
tasks.install_replica(self.master, self.replicas[0], setup_ca=True,
|
||||
setup_kra=True, setup_dns=True)
|
||||
|
||||
def test_replica1_with_ca_dns_install(self):
|
||||
tasks.install_replica(self.master, self.replicas[1], setup_ca=True,
|
||||
setup_dns=True)
|
||||
|
||||
def test_replica1_ipa_kra_install(self):
|
||||
tasks.install_kra(self.replicas[1])
|
||||
|
||||
def test_replica2_with_dns_install(self):
|
||||
tasks.install_replica(self.master, self.replicas[2], setup_ca=False,
|
||||
setup_dns=True)
|
||||
|
||||
def test_replica2_ipa_ca_install(self):
|
||||
tasks.install_ca(self.replicas[2])
|
||||
|
||||
def test_replica2_ipa_kra_install(self):
|
||||
tasks.install_kra(self.replicas[2])
|
||||
|
||||
|
||||
class ADTrustInstallTestBase(IntegrationTest):
|
||||
"""
|
||||
Base test for builtin AD trust installation im combination with other
|
||||
components
|
||||
"""
|
||||
num_replicas = 2
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False)
|
||||
|
||||
def install_replica(self, replica, **kwargs):
|
||||
tasks.install_replica(self.master, replica, setup_adtrust=True,
|
||||
**kwargs)
|
||||
|
||||
def test_replica0_only_adtrust(self):
|
||||
self.install_replica(self.replicas[0], setup_ca=False)
|
||||
|
||||
def test_replica1_all_components_adtrust(self):
|
||||
self.install_replica(self.replicas[1], setup_ca=True)
|
||||
|
||||
|
||||
##
|
||||
# Master X Replicas installation tests
|
||||
##
|
||||
|
||||
@pytest.mark.xfail(reason="FreeIPA ticket 7008")
|
||||
class TestInstallWithCA1(InstallTestBase1):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False)
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica1_ipa_kra_install(self):
|
||||
super(TestInstallWithCA1, self).test_replica1_ipa_kra_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica2_with_ca_kra_install(self):
|
||||
super(TestInstallWithCA1, self).test_replica2_with_ca_kra_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica2_ipa_dns_install(self):
|
||||
super(TestInstallWithCA1, self).test_replica2_ipa_dns_install()
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="FreeIPA ticket 7008")
|
||||
class TestInstallWithCA2(InstallTestBase2):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False)
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica0_with_ca_kra_dns_install(self):
|
||||
super(TestInstallWithCA2, self).test_replica0_with_ca_kra_dns_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica1_ipa_kra_install(self):
|
||||
super(TestInstallWithCA2, self).test_replica1_ipa_kra_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica2_ipa_kra_install(self):
|
||||
super(TestInstallWithCA2, self).test_replica2_ipa_kra_install()
|
||||
|
||||
|
||||
class TestInstallWithCA_KRA1(InstallTestBase1):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False, setup_kra=True)
|
||||
|
||||
def test_replica0_ipa_kra_install(self):
|
||||
tasks.install_kra(self.replicas[0], first_instance=False)
|
||||
|
||||
|
||||
class TestInstallWithCA_KRA2(InstallTestBase2):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=False, setup_kra=True)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="FreeIPA ticket 7008")
|
||||
class TestInstallWithCA_DNS1(InstallTestBase1):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica1_ipa_kra_install(self):
|
||||
super(TestInstallWithCA_DNS1, self).test_replica1_ipa_kra_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica2_with_ca_kra_install(self):
|
||||
super(TestInstallWithCA_DNS1, self).test_replica2_with_ca_kra_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica2_ipa_dns_install(self):
|
||||
super(TestInstallWithCA_DNS1, self).test_replica2_ipa_dns_install()
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="FreeIPA ticket 7008")
|
||||
class TestInstallWithCA_DNS2(InstallTestBase2):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica0_with_ca_kra_dns_install(self):
|
||||
super(
|
||||
TestInstallWithCA_DNS2, self
|
||||
).test_replica0_with_ca_kra_dns_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica1_ipa_kra_install(self):
|
||||
super(TestInstallWithCA_DNS2, self).test_replica1_ipa_kra_install()
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
def test_replica2_ipa_kra_install(self):
|
||||
super(TestInstallWithCA_DNS2, self).test_replica2_ipa_kra_install()
|
||||
|
||||
|
||||
@pytest.mark.cs_acceptance
|
||||
class TestInstallWithCA_KRA_DNS1(InstallTestBase1):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
|
||||
|
||||
def test_replica0_ipa_kra_install(self):
|
||||
tasks.install_kra(self.replicas[0], first_instance=False)
|
||||
|
||||
|
||||
class TestInstallWithCA_KRA_DNS2(InstallTestBase2):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
|
||||
|
||||
|
||||
class TestADTrustInstall(ADTrustInstallTestBase):
|
||||
"""
|
||||
Tests built-in AD trust installation in various combinations (see the base
|
||||
class for more details) against plain IPA master (no DNS, no KRA, no AD
|
||||
trust)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TestADTrustInstallWithDNS_KRA_ADTrust(ADTrustInstallTestBase):
|
||||
"""
|
||||
Tests built-in AD trust installation in various combinations (see the base
|
||||
class for more details) against fully equipped (DNS, CA, KRA, ADtrust)
|
||||
master. Additional two test cases were added to test interplay including
|
||||
KRA installer
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True, setup_kra=True,
|
||||
setup_adtrust=True)
|
||||
|
||||
def test_replica1_all_components_adtrust(self):
|
||||
self.install_replica(self.replicas[1], setup_ca=True, setup_kra=True)
|
||||
|
||||
|
||||
##
|
||||
# Rest of master installation tests
|
||||
##
|
||||
|
||||
class TestInstallMaster(IntegrationTest):
|
||||
|
||||
num_replicas = 0
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
pass
|
||||
|
||||
def test_install_master(self):
|
||||
tasks.install_master(self.master, setup_dns=False)
|
||||
|
||||
def test_install_kra(self):
|
||||
tasks.install_kra(self.master, first_instance=True)
|
||||
|
||||
def test_install_dns(self):
|
||||
tasks.install_dns(self.master)
|
||||
|
||||
|
||||
class TestInstallMasterKRA(IntegrationTest):
|
||||
|
||||
num_replicas = 0
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
pass
|
||||
|
||||
def test_install_master(self):
|
||||
tasks.install_master(self.master, setup_dns=False, setup_kra=True)
|
||||
|
||||
def test_install_dns(self):
|
||||
tasks.install_dns(self.master)
|
||||
|
||||
|
||||
class TestInstallMasterDNS(IntegrationTest):
|
||||
|
||||
num_replicas = 0
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
pass
|
||||
|
||||
def test_install_master(self):
|
||||
tasks.install_master(self.master, setup_dns=True)
|
||||
|
||||
def test_install_kra(self):
|
||||
tasks.install_kra(self.master, first_instance=True)
|
||||
191
ipatests/test_integration/test_kerberos_flags.py
Normal file
191
ipatests/test_integration/test_kerberos_flags.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# Authors:
|
||||
# Ana Krivokapic <akrivoka@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
|
||||
class TestKerberosFlags(IntegrationTest):
|
||||
"""
|
||||
Test Kerberos Flags
|
||||
http://www.freeipa.org/page/V3/Kerberos_Flags#Test_Plan
|
||||
"""
|
||||
topology = 'line'
|
||||
num_clients = 1
|
||||
|
||||
def test_set_flag_with_host_add(self):
|
||||
host = 'host.example.com'
|
||||
host_service = 'host/%s' % host
|
||||
host_keytab = '/tmp/host.keytab'
|
||||
|
||||
for trusted in (True, False, None):
|
||||
self.add_object('host', host, trusted=trusted, force=True)
|
||||
self.check_flag_cli('host', host, trusted=trusted)
|
||||
self.rekinit()
|
||||
self.getkeytab(host_service, host_keytab)
|
||||
self.kvno(host_service)
|
||||
self.check_flag_klist(host_service, trusted=trusted)
|
||||
self.del_object('host', host)
|
||||
|
||||
def test_set_and_clear_flag_with_host_mod(self):
|
||||
client_hostname = self.clients[0].hostname
|
||||
host_service = 'host/%s' % client_hostname
|
||||
|
||||
self.kvno(host_service)
|
||||
self.check_flag_cli('host', client_hostname, trusted=False)
|
||||
self.check_flag_klist(host_service, trusted=False)
|
||||
|
||||
for trusted in (True, False):
|
||||
self.mod_object_cli('host', client_hostname, trusted=trusted)
|
||||
self.check_flag_cli('host', client_hostname, trusted=trusted)
|
||||
self.rekinit()
|
||||
self.kvno(host_service)
|
||||
self.check_flag_klist(host_service, trusted=trusted)
|
||||
|
||||
for trusted in (True, False):
|
||||
self.mod_service_kadmin_local(host_service, trusted=trusted)
|
||||
self.check_flag_cli('host', client_hostname, trusted=trusted)
|
||||
self.rekinit()
|
||||
self.kvno(host_service)
|
||||
self.check_flag_klist(host_service, trusted=trusted)
|
||||
|
||||
def test_set_flag_with_service_add(self):
|
||||
ftp_service = 'ftp/%s' % self.master.hostname
|
||||
ftp_keytab = '/tmp/ftp.keytab'
|
||||
|
||||
for trusted in (True, False, None):
|
||||
self.add_object('service', ftp_service, trusted=trusted)
|
||||
self.check_flag_cli('service', ftp_service, trusted=trusted)
|
||||
self.rekinit()
|
||||
self.getkeytab(ftp_service, ftp_keytab)
|
||||
self.kvno(ftp_service)
|
||||
self.check_flag_klist(ftp_service, trusted=trusted)
|
||||
self.del_object('service', ftp_service)
|
||||
|
||||
def test_set_and_clear_flag_with_service_mod(self):
|
||||
http_service = 'HTTP/%s' % self.master.hostname
|
||||
|
||||
self.kvno(http_service)
|
||||
self.check_flag_cli('service', http_service, trusted=False)
|
||||
self.check_flag_klist(http_service, trusted=False)
|
||||
|
||||
for trusted in (True, False):
|
||||
self.mod_object_cli('service', http_service, trusted=trusted)
|
||||
self.check_flag_cli('service', http_service, trusted=trusted)
|
||||
self.rekinit()
|
||||
self.kvno(http_service)
|
||||
self.check_flag_klist(http_service, trusted=trusted)
|
||||
|
||||
for trusted in (True, False):
|
||||
self.mod_service_kadmin_local(http_service, trusted=trusted)
|
||||
self.check_flag_cli('service', http_service, trusted=trusted)
|
||||
self.rekinit()
|
||||
self.kvno(http_service)
|
||||
self.check_flag_klist(http_service, trusted=trusted)
|
||||
|
||||
def test_try_to_set_flag_using_unexpected_values(self):
|
||||
http_service = 'HTTP/%s' % self.master.hostname
|
||||
invalid_values = ['blah', 'yes', 'y', '2', '1.0', '$']
|
||||
|
||||
for v in invalid_values:
|
||||
self.mod_object_cli('service', http_service, trusted=v,
|
||||
expect_fail=True)
|
||||
|
||||
def add_object(self, object_type, object_id, trusted=None, force=False):
|
||||
args = ['ipa', '%s-add' % object_type, object_id]
|
||||
|
||||
if trusted is True:
|
||||
args.extend(['--ok-as-delegate', '1'])
|
||||
elif trusted is False:
|
||||
args.extend(['--ok-as-delegate', '0'])
|
||||
|
||||
if force:
|
||||
args.append('--force')
|
||||
|
||||
self.master.run_command(args)
|
||||
|
||||
def del_object(self, object_type, object_id):
|
||||
self.master.run_command(['ipa', '%s-del' % object_type, object_id])
|
||||
|
||||
def mod_object_cli(self, object_type, object_id, trusted,
|
||||
expect_fail=False):
|
||||
args = ['ipa', '%s-mod' % object_type, object_id]
|
||||
|
||||
if trusted is True:
|
||||
args.extend(['--ok-as-delegate', '1'])
|
||||
elif trusted is False:
|
||||
args.extend(['--ok-as-delegate', '0'])
|
||||
else:
|
||||
args.extend(['--ok-as-delegate', trusted])
|
||||
|
||||
result = self.master.run_command(args, raiseonerr=not expect_fail)
|
||||
|
||||
if expect_fail:
|
||||
stderr_text = "invalid 'ipakrbokasdelegate': must be True or False"
|
||||
assert result.returncode == 1
|
||||
assert stderr_text in result.stderr_text
|
||||
|
||||
def mod_service_kadmin_local(self, service, trusted):
|
||||
sign = '+' if trusted else '-'
|
||||
stdin_text = '\n'.join([
|
||||
'modify_principal %sok_as_delegate %s' % (sign, service),
|
||||
'q',
|
||||
''
|
||||
])
|
||||
self.master.run_command('kadmin.local', stdin_text=stdin_text)
|
||||
|
||||
def check_flag_cli(self, object_type, object_id, trusted):
|
||||
result = self.master.run_command(
|
||||
['ipa', '%s-show' % object_type, object_id, '--all']
|
||||
)
|
||||
|
||||
if trusted:
|
||||
assert 'Trusted for delegation: True' in result.stdout_text
|
||||
else:
|
||||
assert 'Trusted for delegation: False' in result.stdout_text
|
||||
|
||||
def check_flag_klist(self, service, trusted):
|
||||
result = self.master.run_command(['klist', '-f'])
|
||||
output_lines = result.stdout_text.split('\n')
|
||||
flags = ''
|
||||
|
||||
for line, next_line in zip(output_lines, output_lines[1:]):
|
||||
if service in line:
|
||||
flags = next_line.replace('Flags:', '').strip()
|
||||
|
||||
if trusted:
|
||||
assert 'O' in flags
|
||||
else:
|
||||
assert 'O' not in flags
|
||||
|
||||
def rekinit(self):
|
||||
self.master.run_command(['kdestroy'])
|
||||
tasks.kinit_admin(self.master)
|
||||
|
||||
def getkeytab(self, service, keytab):
|
||||
result = self.master.run_command([
|
||||
'ipa-getkeytab',
|
||||
'-s', self.master.hostname,
|
||||
'-p', service,
|
||||
'-k', keytab
|
||||
])
|
||||
assert 'Keytab successfully retrieved' in result.stderr_text
|
||||
|
||||
def kvno(self, service):
|
||||
self.master.run_command(['kvno', service])
|
||||
566
ipatests/test_integration/test_legacy_clients.py
Normal file
566
ipatests/test_integration/test_legacy_clients.py
Normal file
@@ -0,0 +1,566 @@
|
||||
# Authors:
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# FIXME: Pylint errors
|
||||
# pylint: disable=no-member
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
import nose
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
# importing test_trust under different name to avoid nose executing the test
|
||||
# base class imported from this module
|
||||
from ipatests.test_integration import test_trust as trust_tests
|
||||
|
||||
|
||||
class BaseTestLegacyClient(object):
|
||||
"""
|
||||
Tests legacy client support.
|
||||
"""
|
||||
|
||||
advice_id = None
|
||||
backup_files = ['/etc/sysconfig/authconfig',
|
||||
'/etc/pam.d',
|
||||
'/etc/openldap/cacerts',
|
||||
'/etc/openldap/ldap.conf',
|
||||
'/etc/nsswitch.conf',
|
||||
paths.SSSD_CONF]
|
||||
|
||||
homedir_template = "/home/{domain}/{username}"
|
||||
required_extra_roles = ()
|
||||
optional_extra_roles = ()
|
||||
|
||||
# Actual test classes need to override these attributes to set the expected
|
||||
# values on the UID and GID results, since this varies with the usage of the
|
||||
# POSIX and non-POSIX ID ranges
|
||||
|
||||
testuser_uid_regex = None
|
||||
testuser_gid_regex = None
|
||||
subdomain_testuser_uid_regex = None
|
||||
subdomain_testuser_gid_regex = None
|
||||
treedomain_testuser_uid_regex = None
|
||||
treedomain_testuser_gid_regex = None
|
||||
|
||||
# To allow custom validation dependent on the trust type
|
||||
posix_trust = False
|
||||
|
||||
def test_apply_advice(self):
|
||||
# Obtain the advice from the server
|
||||
tasks.kinit_admin(self.master)
|
||||
result = self.master.run_command(['ipa-advise', self.advice_id])
|
||||
advice = result.stdout_text
|
||||
|
||||
# Apply the advice on the legacy client
|
||||
advice_path = os.path.join(self.legacy_client.config.test_dir,
|
||||
'advice.sh')
|
||||
self.legacy_client.put_file_contents(advice_path, advice)
|
||||
result = self.legacy_client.run_command(['bash', '-x', '-e',
|
||||
advice_path])
|
||||
|
||||
# Restart SSHD to load new PAM configuration
|
||||
self.legacy_client.run_command([paths.SBIN_SERVICE, 'sshd', 'restart'])
|
||||
|
||||
def clear_sssd_caches(self):
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
tasks.clear_sssd_cache(self.legacy_client)
|
||||
|
||||
def test_getent_ipa_user(self):
|
||||
self.clear_sssd_caches()
|
||||
result = self.legacy_client.run_command(['getent', 'passwd', 'admin'])
|
||||
|
||||
admin_regex = "admin:\*:(\d+):(\d+):"\
|
||||
"Administrator:/home/admin:/bin/bash"
|
||||
|
||||
assert re.search(admin_regex, result.stdout_text)
|
||||
|
||||
def test_getent_ipa_group(self):
|
||||
self.clear_sssd_caches()
|
||||
result = self.legacy_client.run_command(['getent', 'group', 'admins'])
|
||||
|
||||
admin_group_regex = "admins:\*:(\d+):admin"
|
||||
|
||||
assert re.search(admin_group_regex, result.stdout_text)
|
||||
|
||||
def test_id_ipa_user(self):
|
||||
self.clear_sssd_caches()
|
||||
result = self.legacy_client.run_command(['id', 'admin'])
|
||||
|
||||
uid_regex = "uid=(\d+)\(admin\)"
|
||||
gid_regex = "gid=(\d+)\(admins\)"
|
||||
groups_regex = "groups=(\d+)\(admins\)"
|
||||
|
||||
assert re.search(uid_regex, result.stdout_text)
|
||||
assert re.search(gid_regex, result.stdout_text)
|
||||
assert re.search(groups_regex, result.stdout_text)
|
||||
|
||||
def test_getent_ad_user(self):
|
||||
self.clear_sssd_caches()
|
||||
testuser = 'testuser@%s' % self.ad.domain.name
|
||||
result = self.legacy_client.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
testuser_regex = "testuser@%s:\*:%s:%s:"\
|
||||
"Test User:%s:/bin/sh"\
|
||||
% (re.escape(self.ad.domain.name),
|
||||
self.testuser_uid_regex,
|
||||
self.testuser_gid_regex,
|
||||
self.homedir_template.format(
|
||||
username='testuser',
|
||||
domain=re.escape(self.ad.domain.name))
|
||||
)
|
||||
|
||||
assert re.search(testuser_regex, result.stdout_text)
|
||||
|
||||
def test_getent_ad_group(self):
|
||||
self.clear_sssd_caches()
|
||||
testgroup = 'testgroup@%s' % self.ad.domain.name
|
||||
result = self.legacy_client.run_command(['getent', 'group', testgroup])
|
||||
|
||||
testgroup_regex = "%s:\*:%s:" % (testgroup, self.testuser_gid_regex)
|
||||
assert re.search(testgroup_regex, result.stdout_text)
|
||||
|
||||
def test_id_ad_user(self):
|
||||
self.clear_sssd_caches()
|
||||
testuser = 'testuser@%s' % self.ad.domain.name
|
||||
testgroup = 'testgroup@%s' % self.ad.domain.name
|
||||
|
||||
result = self.legacy_client.run_command(['id', testuser])
|
||||
|
||||
# Only for POSIX trust testing does the testuser belong to the
|
||||
# testgroup
|
||||
group_name = '\(%s\)' % testgroup if self.posix_trust else ''
|
||||
|
||||
uid_regex = "uid=%s\(%s\)" % (self.testuser_uid_regex, testuser)
|
||||
gid_regex = "gid=%s%s" % (self.testuser_gid_regex, group_name)
|
||||
groups_regex = "groups=%s%s" % (self.testuser_gid_regex, group_name)
|
||||
|
||||
assert re.search(uid_regex, result.stdout_text)
|
||||
assert re.search(gid_regex, result.stdout_text)
|
||||
assert re.search(groups_regex, result.stdout_text)
|
||||
|
||||
def test_login_ipa_user(self):
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on %s'
|
||||
% self.master.hostname)
|
||||
|
||||
result = self.master.run_command(
|
||||
'sshpass -p %s '
|
||||
'ssh '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-l admin '
|
||||
'%s '
|
||||
'"echo test"' %
|
||||
(self.legacy_client.config.admin_password,
|
||||
self.legacy_client.hostname))
|
||||
|
||||
assert "test" in result.stdout_text
|
||||
|
||||
def test_login_ad_user(self):
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on %s'
|
||||
% self.master.hostname)
|
||||
|
||||
testuser = 'testuser@%s' % self.ad.domain.name
|
||||
result = self.master.run_command(
|
||||
'sshpass -p Secret123 '
|
||||
'ssh '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-l %s '
|
||||
'%s '
|
||||
'"echo test"' %
|
||||
(testuser, self.legacy_client.hostname))
|
||||
|
||||
assert "test" in result.stdout_text
|
||||
|
||||
def test_login_disabled_ipa_user(self):
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on %s'
|
||||
% self.master.hostname)
|
||||
|
||||
self.clear_sssd_caches()
|
||||
|
||||
result = self.master.run_command(
|
||||
'sshpass -p %s '
|
||||
'ssh '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-l disabledipauser '
|
||||
'%s '
|
||||
'"echo test"'
|
||||
% (self.legacy_client.config.admin_password,
|
||||
self.legacy_client.external_hostname),
|
||||
raiseonerr=False)
|
||||
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_login_disabled_ad_user(self):
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on %s'
|
||||
% self.master.hostname)
|
||||
|
||||
testuser = 'disabledaduser@%s' % self.ad.domain.name
|
||||
result = self.master.run_command(
|
||||
'sshpass -p Secret123 '
|
||||
'ssh '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-l %s '
|
||||
'%s '
|
||||
'"echo test"' %
|
||||
(testuser, self.legacy_client.external_hostname),
|
||||
raiseonerr=False)
|
||||
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_getent_subdomain_ad_user(self):
|
||||
if not self.ad_subdomain:
|
||||
raise nose.SkipTest('AD for the subdomain is not available.')
|
||||
|
||||
self.clear_sssd_caches()
|
||||
testuser = 'subdomaintestuser@%s' % self.ad_subdomain
|
||||
result = self.legacy_client.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
testuser_regex = "subdomaintestuser@%s:\*:%s:%s:"\
|
||||
"Subdomaintest User:%s:"\
|
||||
"/bin/sh"\
|
||||
% (re.escape(self.ad_subdomain),
|
||||
self.subdomain_testuser_uid_regex,
|
||||
self.subdomain_testuser_gid_regex,
|
||||
self.homedir_template.format(
|
||||
username='subdomaintestuser',
|
||||
domain=re.escape(self.ad_subdomain))
|
||||
)
|
||||
|
||||
assert re.search(testuser_regex, result.stdout_text)
|
||||
|
||||
def test_getent_subdomain_ad_group(self):
|
||||
if not self.ad_subdomain:
|
||||
raise nose.SkipTest('AD for the subdomain is not available.')
|
||||
|
||||
self.clear_sssd_caches()
|
||||
testgroup = 'subdomaintestgroup@%s' % self.ad_subdomain
|
||||
result = self.legacy_client.run_command(['getent', 'group', testgroup])
|
||||
|
||||
testgroup_stdout = "%s:\*:%s:" % (testgroup,
|
||||
self.subdomain_testuser_gid_regex)
|
||||
assert re.search(testgroup_stdout, result.stdout_text)
|
||||
|
||||
def test_id_subdomain_ad_user(self):
|
||||
if not self.ad_subdomain:
|
||||
raise nose.SkipTest('AD for the subdomain is not available.')
|
||||
|
||||
self.clear_sssd_caches()
|
||||
testuser = 'subdomaintestuser@%s' % self.ad_subdomain
|
||||
testgroup = 'subdomaintestgroup@%s' % self.ad_subdomain
|
||||
|
||||
result = self.legacy_client.run_command(['id', testuser])
|
||||
|
||||
# Only for POSIX trust testing does the testuser belong to the
|
||||
# testgroup
|
||||
group_name = '\(%s\)' % testgroup if self.posix_trust else ''
|
||||
|
||||
uid_regex = "uid=%s\(%s\)" % (self.subdomain_testuser_uid_regex,
|
||||
testuser)
|
||||
gid_regex = "gid=%s%s" % (self.subdomain_testuser_gid_regex,
|
||||
group_name)
|
||||
groups_regex = "groups=%s%s" % (self.subdomain_testuser_gid_regex,
|
||||
group_name)
|
||||
|
||||
assert re.search(uid_regex, result.stdout_text)
|
||||
assert re.search(gid_regex, result.stdout_text)
|
||||
assert re.search(groups_regex, result.stdout_text)
|
||||
|
||||
def test_login_subdomain_ad_user(self):
|
||||
if not self.ad_subdomain:
|
||||
raise nose.SkipTest('AD for the subdomain is not available.')
|
||||
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on %s'
|
||||
% self.master.hostname)
|
||||
|
||||
testuser = 'subdomaintestuser@%s' % self.ad_subdomain
|
||||
result = self.master.run_command(
|
||||
'sshpass -p Secret123 '
|
||||
'ssh '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-l %s '
|
||||
'%s '
|
||||
'"echo test"' %
|
||||
(testuser, self.legacy_client.external_hostname))
|
||||
|
||||
assert "test" in result.stdout_text
|
||||
|
||||
def test_login_disabled_subdomain_ad_user(self):
|
||||
if not self.ad_subdomain:
|
||||
raise nose.SkipTest('AD for the subdomain is not available.')
|
||||
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on %s'
|
||||
% self.master.hostname)
|
||||
|
||||
testuser = 'subdomaindisabledaduser@%s' % self.ad_subdomain
|
||||
result = self.master.run_command(
|
||||
'sshpass -p Secret123 '
|
||||
'ssh '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-l %s '
|
||||
'%s '
|
||||
'"echo test"' %
|
||||
(testuser, self.legacy_client.external_hostname),
|
||||
raiseonerr=False)
|
||||
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_getent_treedomain_ad_user(self):
|
||||
if not self.ad_treedomain:
|
||||
raise nose.SkipTest('AD tree root domain is not available.')
|
||||
|
||||
self.clear_sssd_caches()
|
||||
testuser = 'treetestuser@{0}'.format(self.ad_treedomain)
|
||||
result = self.legacy_client.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
testuser_regex = ("treetestuser@{0}:\*:{1}:{2}:TreeTest User:"
|
||||
"/home/{0}/treetestuser:/bin/sh".format(
|
||||
re.escape(self.ad_treedomain),
|
||||
self.treedomain_testuser_uid_regex,
|
||||
self.treedomain_testuser_gid_regex))
|
||||
|
||||
assert re.search(testuser_regex, result.stdout_text)
|
||||
|
||||
def test_getent_treedomain_ad_group(self):
|
||||
if not self.ad_treedomain:
|
||||
raise nose.SkipTest('AD tree root domain is not available')
|
||||
|
||||
self.clear_sssd_caches()
|
||||
testgroup = 'treetestgroup@{0}'.format(self.ad_treedomain)
|
||||
result = self.legacy_client.run_command(['getent', 'group', testgroup])
|
||||
|
||||
testgroup_stdout = "{0}:\*:{1}:".format(
|
||||
testgroup, self.treedomain_testuser_gid_regex)
|
||||
|
||||
assert re.search(testgroup_stdout, result.stdout_text)
|
||||
|
||||
def test_id_treedomain_ad_user(self):
|
||||
if not self.ad_treedomain:
|
||||
raise nose.SkipTest('AD tree root domain is not available')
|
||||
|
||||
self.clear_sssd_caches()
|
||||
|
||||
testuser = 'treetestuser@{0}'.format(self.ad_treedomain)
|
||||
testgroup = 'treetestgroup@{0}'.format(self.ad_treedomain)
|
||||
|
||||
result = self.legacy_client.run_command(['id', testuser])
|
||||
|
||||
# Only for POSIX trust testing does the testuser belong to the
|
||||
# testgroup
|
||||
|
||||
group_name = '\({}\)'.format(testgroup) if self.posix_trust else ''
|
||||
|
||||
uid_regex = "uid={0}\({1}\)".format(
|
||||
self.treedomain_testuser_uid_regex, testuser)
|
||||
|
||||
gid_regex = "gid={0}{1}".format(
|
||||
self.treedomain_testuser_gid_regex, group_name)
|
||||
|
||||
group_regex = "groups={0}{1}".format(
|
||||
self.treedomain_testuser_gid_regex, group_name)
|
||||
|
||||
assert re.search(uid_regex, result.stdout_text)
|
||||
assert re.search(gid_regex, result.stdout_text)
|
||||
assert re.search(group_regex, result.stdout_text)
|
||||
|
||||
def test_login_treedomain_ad_user(self):
|
||||
if not self.ad_treedomain:
|
||||
raise nose.SkipTest('AD tree root domain is not available.')
|
||||
|
||||
if not self.master.transport.file_exists('/usr/bin/sshpass'):
|
||||
raise nose.SkipTest('Package sshpass not available on {}'.format(
|
||||
self.master.hostname))
|
||||
|
||||
result = self.master.run_command(
|
||||
'sshpass -p {0} ssh -o StrictHostKeyChecking=no '
|
||||
'-l admin {1} "echo test"'.format(
|
||||
self.legacy_client.config.admin_password,
|
||||
self.legacy_client.external_hostname))
|
||||
|
||||
assert "test" in result.stdout_text
|
||||
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(BaseTestLegacyClient, cls).install(mh)
|
||||
|
||||
tasks.kinit_admin(cls.master)
|
||||
|
||||
password_confirmation = (
|
||||
cls.master.config.admin_password +
|
||||
'\n' +
|
||||
cls.master.config.admin_password
|
||||
)
|
||||
|
||||
cls.master.run_command(['ipa', 'user-add', 'disabledipauser',
|
||||
'--first', 'disabled',
|
||||
'--last', 'ipauser',
|
||||
'--password'],
|
||||
stdin_text=password_confirmation)
|
||||
|
||||
cls.master.run_command(['ipa', 'user-disable', 'disabledipauser'])
|
||||
|
||||
cls.ad = cls.ad_domains[0].ads[0]
|
||||
|
||||
cls.legacy_client = cls.host_by_role(cls.required_extra_roles[0])
|
||||
|
||||
# Determine whether the subdomain AD is available
|
||||
try:
|
||||
child_ad = cls.host_by_role(cls.optional_extra_roles[0])
|
||||
cls.ad_subdomain = '.'.join(
|
||||
child_ad.hostname.split('.')[1:])
|
||||
except LookupError:
|
||||
cls.ad_subdomain = None
|
||||
|
||||
# Determine whether the tree domain AD is available
|
||||
try:
|
||||
cls.tree_ad = cls.host_by_role(cls.optional_extra_roles[1])
|
||||
cls.ad_treedomain = '.'.join(
|
||||
cls.tree_ad.hostname.split('.')[1:])
|
||||
except LookupError:
|
||||
cls.ad_treedomain = None
|
||||
|
||||
tasks.apply_common_fixes(cls.legacy_client)
|
||||
|
||||
for f in cls.backup_files:
|
||||
tasks.backup_file(cls.legacy_client, f)
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
cls.master.run_command(['ipa', 'user-del', 'disabledipauser'],
|
||||
raiseonerr=False)
|
||||
|
||||
# Remove information about trust from AD, if domain was defined
|
||||
if hasattr(cls, 'ad_domain'):
|
||||
tasks.remove_trust_info_from_ad(cls.master, cls.ad_domain)
|
||||
|
||||
# Also unapply fixes on the legacy client, if defined
|
||||
if hasattr(cls, 'legacy_client'):
|
||||
tasks.unapply_fixes(cls.legacy_client)
|
||||
|
||||
super(BaseTestLegacyClient, cls).uninstall(mh)
|
||||
|
||||
|
||||
# Base classes with attributes that are specific for each legacy client test
|
||||
|
||||
class BaseTestLegacySSSDBefore19RedHat(object):
|
||||
|
||||
advice_id = 'config-redhat-sssd-before-1-9'
|
||||
required_extra_roles = ['legacy_client_sssd_redhat']
|
||||
optional_extra_roles = ['ad_subdomain', 'ad_treedomain']
|
||||
|
||||
|
||||
class BaseTestLegacyNssPamLdapdRedHat(object):
|
||||
|
||||
advice_id = 'config-redhat-nss-pam-ldapd'
|
||||
required_extra_roles = ['legacy_client_nss_pam_ldapd_redhat']
|
||||
optional_extra_roles = ['ad_subdomain', 'ad_treedomain']
|
||||
|
||||
def clear_sssd_caches(self):
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
class BaseTestLegacyNssLdapRedHat(object):
|
||||
|
||||
advice_id = 'config-redhat-nss-ldap'
|
||||
required_extra_roles = ['legacy_client_nss_ldap_redhat']
|
||||
optional_extra_roles = ['ad_subdomain', 'ad_treedomain']
|
||||
|
||||
def clear_sssd_caches(self):
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
# Base classes that join legacy client specific steps with steps required
|
||||
# to setup IPA with trust (both with and without using the POSIX attributes)
|
||||
|
||||
class BaseTestLegacyClientPosix(BaseTestLegacyClient,
|
||||
trust_tests.TestEnforcedPosixADTrust):
|
||||
|
||||
testuser_uid_regex = '10042'
|
||||
testuser_gid_regex = '10047'
|
||||
subdomain_testuser_uid_regex = '10142'
|
||||
subdomain_testuser_gid_regex = '10147'
|
||||
treedomain_testuser_uid_regex = '10242'
|
||||
treedomain_testuser_gid_regex = '10247'
|
||||
posix_trust = True
|
||||
|
||||
def test_remove_trust_with_posix_attributes(self):
|
||||
pass
|
||||
|
||||
|
||||
class BaseTestLegacyClientNonPosix(BaseTestLegacyClient,
|
||||
trust_tests.TestBasicADTrust):
|
||||
|
||||
testuser_uid_regex = '(?!10042)(\d+)'
|
||||
testuser_gid_regex = '(?!10047)(\d+)'
|
||||
subdomain_testuser_uid_regex = '(?!10142)(\d+)'
|
||||
subdomain_testuser_gid_regex = '(?!10147)(\d+)'
|
||||
treedomain_testuser_uid_regex = '(?!10242)(\d+)'
|
||||
treedomain_testuser_gid_regex = '(?!10247)(\d+)'
|
||||
|
||||
def test_remove_nonposix_trust(self):
|
||||
pass
|
||||
|
||||
|
||||
class BaseTestSSSDMixin(object):
|
||||
|
||||
def test_apply_advice(self):
|
||||
super(BaseTestSSSDMixin, self).test_apply_advice()
|
||||
tasks.setup_sssd_debugging(self.legacy_client)
|
||||
|
||||
|
||||
# Tests definitions themselves. Beauty. Just pure beauty.
|
||||
|
||||
class TestLegacySSSDBefore19RedHatNonPosix(BaseTestSSSDMixin,
|
||||
BaseTestLegacySSSDBefore19RedHat,
|
||||
BaseTestLegacyClientNonPosix):
|
||||
pass
|
||||
|
||||
|
||||
class TestLegacyNssPamLdapdRedHatNonPosix(BaseTestLegacyNssPamLdapdRedHat,
|
||||
BaseTestLegacyClientNonPosix):
|
||||
pass
|
||||
|
||||
|
||||
class TestLegacyNssLdapRedHatNonPosix(BaseTestLegacyNssLdapRedHat,
|
||||
BaseTestLegacyClientNonPosix):
|
||||
pass
|
||||
|
||||
|
||||
class TestLegacySSSDBefore19RedHatPosix(BaseTestSSSDMixin,
|
||||
BaseTestLegacySSSDBefore19RedHat,
|
||||
BaseTestLegacyClientPosix):
|
||||
pass
|
||||
|
||||
|
||||
class TestLegacyNssPamLdapdRedHatPosix(BaseTestLegacyNssPamLdapdRedHat,
|
||||
BaseTestLegacyClientPosix):
|
||||
pass
|
||||
|
||||
|
||||
class TestLegacyNssLdapRedHatPosix(BaseTestLegacyNssLdapRedHat,
|
||||
BaseTestLegacyClientPosix):
|
||||
pass
|
||||
169
ipatests/test_integration/test_netgroup.py
Normal file
169
ipatests/test_integration/test_netgroup.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#
|
||||
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import pytest
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration.tasks import clear_sssd_cache
|
||||
|
||||
|
||||
test_data = []
|
||||
for i in range(3):
|
||||
data = {
|
||||
'user': {
|
||||
'login': 'testuser_{}'.format(i),
|
||||
'first': 'Test_{}'.format(i),
|
||||
'last': 'User_{}'.format(i),
|
||||
},
|
||||
'netgroup': 'testgroup_{}'.format(i),
|
||||
'nested_netgroup': 'testgroup_{}'.format(i-1) if i > 0 else None
|
||||
}
|
||||
test_data.append(data)
|
||||
members = [d['user']['login'] for d in test_data]
|
||||
test_data[-1]['netgroup_nested_members'] = members
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def three_netgroups(request):
|
||||
"""Prepare basic netgroups with users"""
|
||||
|
||||
for d in test_data:
|
||||
request.cls.master.run_command(['ipa', 'user-add', d['user']['login'],
|
||||
'--first', d['user']['first'],
|
||||
'--last', d['user']['last']],
|
||||
raiseonerr=False)
|
||||
|
||||
request.cls.master.run_command(['ipa', 'netgroup-add', d['netgroup']],
|
||||
raiseonerr=False)
|
||||
|
||||
user_opt = '--users={u[login]}'.format(u=d['user'])
|
||||
request.cls.master.run_command(['ipa', 'netgroup-add-member', user_opt,
|
||||
d['netgroup']], raiseonerr=False)
|
||||
|
||||
def teardown_three_netgroups():
|
||||
"""Clean basic netgroups with users"""
|
||||
for d in test_data:
|
||||
request.cls.master.run_command(['ipa', 'user-del',
|
||||
d['user']['login']],
|
||||
raiseonerr=False)
|
||||
|
||||
request.cls.master.run_command(['ipa', 'netgroup-del',
|
||||
d['netgroup']],
|
||||
raiseonerr=False)
|
||||
|
||||
request.addfinalizer(teardown_three_netgroups)
|
||||
|
||||
|
||||
class TestNetgroups(IntegrationTest):
|
||||
"""
|
||||
Test Netgroups
|
||||
"""
|
||||
|
||||
topology = 'line'
|
||||
|
||||
def check_users_in_netgroups(self):
|
||||
"""Check if users are in groups, no nested things"""
|
||||
master = self.master
|
||||
clear_sssd_cache(master)
|
||||
|
||||
for d in test_data:
|
||||
result = master.run_command(['getent', 'passwd',
|
||||
d['user']['login']], raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
|
||||
user = '{u[first]} {u[last]}'.format(u=d['user'])
|
||||
assert user in result.stdout_text
|
||||
|
||||
result = master.run_command(['getent', 'netgroup',
|
||||
d['netgroup']], raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
|
||||
netgroup = '(-,{},{})'.format(d['user']['login'],
|
||||
self.master.domain.name)
|
||||
assert netgroup in result.stdout_text
|
||||
|
||||
def check_nested_netgroup_hierarchy(self):
|
||||
"""Check if nested netgroups hierarchy is complete"""
|
||||
master = self.master
|
||||
clear_sssd_cache(master)
|
||||
|
||||
for d in test_data:
|
||||
result = master.run_command(['getent', 'netgroup', d['netgroup']],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
|
||||
for member in d['netgroup_nested_members']:
|
||||
if not member:
|
||||
continue
|
||||
|
||||
netgroup = '(-,{},{})'.format(member, self.master.domain.name)
|
||||
assert netgroup in result.stdout_text
|
||||
|
||||
def prepare_nested_netgroup_hierarchy(self):
|
||||
"""Prepares nested netgroup hierarchy from basic netgroups"""
|
||||
for d in test_data:
|
||||
if not d['nested_netgroup']:
|
||||
continue
|
||||
|
||||
netgroups_opt = '--netgroups={}'.format(d['nested_netgroup'])
|
||||
self.master.run_command(['ipa', 'netgroup-add-member',
|
||||
netgroups_opt, d['netgroup']])
|
||||
|
||||
def test_add_nested_netgroup(self, three_netgroups):
|
||||
"""Test of adding nested groups"""
|
||||
self.check_users_in_netgroups()
|
||||
self.prepare_nested_netgroup_hierarchy()
|
||||
self.check_nested_netgroup_hierarchy()
|
||||
|
||||
def test_remove_nested_netgroup(self, three_netgroups):
|
||||
"""Test of removing nested groups"""
|
||||
master = self.master
|
||||
|
||||
trinity = ['(-,{},{})'.format(d['user']['login'],
|
||||
self.master.domain.name)
|
||||
for d in test_data]
|
||||
|
||||
self.check_users_in_netgroups()
|
||||
self.prepare_nested_netgroup_hierarchy()
|
||||
self.check_nested_netgroup_hierarchy()
|
||||
|
||||
# Removing of testgroup_1 from testgroup_2
|
||||
netgroups_opt = '--netgroups={n[netgroup]}'.format(n=test_data[0])
|
||||
result = self.master.run_command(['ipa', 'netgroup-remove-member',
|
||||
netgroups_opt,
|
||||
test_data[1]['netgroup']],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
clear_sssd_cache(master)
|
||||
|
||||
result = master.run_command(['getent', 'netgroup',
|
||||
test_data[1]['netgroup']],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
assert trinity[1] in result.stdout_text
|
||||
|
||||
result = master.run_command(['getent', 'netgroup',
|
||||
test_data[2]['netgroup']],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
assert trinity[0] not in result.stdout_text
|
||||
assert trinity[1] in result.stdout_text
|
||||
assert trinity[2] in result.stdout_text
|
||||
|
||||
# Removing of testgroup_2 from testgroup_3
|
||||
netgroups_opt = '--netgroups={n[netgroup]}'.format(n=test_data[1])
|
||||
result = self.master.run_command(['ipa', 'netgroup-remove-member',
|
||||
netgroups_opt,
|
||||
test_data[2]['netgroup']],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
clear_sssd_cache(master)
|
||||
|
||||
result = master.run_command(['getent', 'netgroup',
|
||||
test_data[2]['netgroup']],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
assert trinity[0] not in result.stdout_text
|
||||
assert trinity[1] not in result.stdout_text
|
||||
assert trinity[2] in result.stdout_text
|
||||
54
ipatests/test_integration/test_ordering.py
Normal file
54
ipatests/test_integration/test_ordering.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Test the ordering of tests
|
||||
|
||||
IPA integration tests, marked with `@ordered`, require tests to be run
|
||||
in a specific order:
|
||||
- Base classes first
|
||||
- Within a class, test methods are ordered according to source line
|
||||
"""
|
||||
|
||||
from pytest_sourceorder import ordered
|
||||
|
||||
|
||||
@ordered
|
||||
class TestBase(object):
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.value = 'unchanged'
|
||||
|
||||
def test_d_first(self):
|
||||
type(self).value = 'changed once'
|
||||
|
||||
|
||||
class TestChild(TestBase):
|
||||
def test_b_third(self):
|
||||
assert type(self).value == 'changed twice'
|
||||
type(self).value = 'changed thrice'
|
||||
|
||||
def test_a_fourth(self):
|
||||
assert type(self).value == 'changed thrice'
|
||||
|
||||
|
||||
def test_c_second(self):
|
||||
assert type(self).value == 'changed once'
|
||||
type(self).value = 'changed twice'
|
||||
TestBase.test_c_second = test_c_second
|
||||
del test_c_second
|
||||
529
ipatests/test_integration/test_replica_promotion.py
Normal file
529
ipatests/test_integration/test_replica_promotion.py
Normal file
@@ -0,0 +1,529 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import time
|
||||
from tempfile import NamedTemporaryFile
|
||||
import textwrap
|
||||
import pytest
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.pytest_plugins.integration.tasks import (
|
||||
assert_error, replicas_cleanup)
|
||||
from ipalib.constants import DOMAIN_LEVEL_0
|
||||
from ipalib.constants import DOMAIN_LEVEL_1
|
||||
from ipalib.constants import DOMAIN_SUFFIX_NAME
|
||||
|
||||
|
||||
class ReplicaPromotionBase(IntegrationTest):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
||||
|
||||
def test_kra_install_master(self):
|
||||
result1 = tasks.install_kra(self.master,
|
||||
first_instance=True,
|
||||
raiseonerr=False)
|
||||
assert result1.returncode == 0, result1.stderr_text
|
||||
tasks.kinit_admin(self.master)
|
||||
result2 = self.master.run_command(["ipa", "vault-find"],
|
||||
raiseonerr=False)
|
||||
found = result2.stdout_text.find("0 vaults matched")
|
||||
assert(found > 0), result2.stdout_text
|
||||
|
||||
|
||||
class TestReplicaPromotionLevel0(ReplicaPromotionBase):
|
||||
|
||||
topology = 'star'
|
||||
num_replicas = 1
|
||||
domain_level = DOMAIN_LEVEL_0
|
||||
|
||||
@replicas_cleanup
|
||||
def test_promotion_disabled(self):
|
||||
"""
|
||||
Testcase:
|
||||
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#Test_case:
|
||||
_Make_sure_the_feature_is_unavailable_under_domain_level_0
|
||||
"""
|
||||
client = self.replicas[0]
|
||||
tasks.install_client(self.master, client)
|
||||
args = ['ipa-replica-install', '-U',
|
||||
'-p', self.master.config.dirman_password,
|
||||
'-w', self.master.config.admin_password,
|
||||
'--ip-address', client.ip]
|
||||
result = client.run_command(args, raiseonerr=False)
|
||||
assert_error(result,
|
||||
'You must provide a file generated by ipa-replica-prepare'
|
||||
' to create a replica when the domain is at level 0', 1)
|
||||
|
||||
@pytest.mark.xfail(reason="Ticket N 6274")
|
||||
@replicas_cleanup
|
||||
def test_backup_restore(self):
|
||||
"""
|
||||
TestCase:
|
||||
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#Test_case:
|
||||
_ipa-restore_after_domainlevel_raise_restores_original_domain_level
|
||||
"""
|
||||
command = ["ipa", "topologysegment-find", DOMAIN_SUFFIX_NAME]
|
||||
tasks.install_replica(self.master, self.replicas[0])
|
||||
backup_file = tasks.ipa_backup(self.master)
|
||||
self.master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
|
||||
# We need to give the server time to merge 2 one-way segments into one
|
||||
time.sleep(10)
|
||||
result = self.master.run_command(command)
|
||||
found1 = result.stdout_text.rfind("1 segment matched")
|
||||
assert(found1 > 0), result.stdout_text
|
||||
tasks.ipa_restore(self.master, backup_file)
|
||||
result2 = self.master.run_command(command, raiseonerr=False)
|
||||
found2 = result2.stdout_text.rfind("0 segments matched")
|
||||
assert(found2 > 0), result2.stdout_text
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Ticket N 6274")
|
||||
class TestKRAInstall(IntegrationTest):
|
||||
"""
|
||||
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
|
||||
#Test_case:_ipa-kra-install_with_replica_file_works_only_on_domain_level_0
|
||||
"""
|
||||
topology = 'star'
|
||||
domain_level = DOMAIN_LEVEL_0
|
||||
num_replicas = 2
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
||||
|
||||
def test_kra_install_without_replica_file(self):
|
||||
master = self.master
|
||||
replica1 = self.replicas[0]
|
||||
replica2 = self.replicas[1]
|
||||
tasks.install_kra(master, first_instance=True)
|
||||
tasks.install_replica(master, replica1)
|
||||
result1 = tasks.install_kra(replica1,
|
||||
domain_level=DOMAIN_LEVEL_1,
|
||||
raiseonerr=False)
|
||||
assert_error(result1, "A replica file is required", 1)
|
||||
tasks.install_kra(replica1,
|
||||
domain_level=DOMAIN_LEVEL_0,
|
||||
raiseonerr=True)
|
||||
# Now prepare the replica file, copy it to the client and raise
|
||||
# domain level on master to test the reverse situation
|
||||
tasks.replica_prepare(master, replica2)
|
||||
master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
|
||||
tasks.install_replica(master, replica2)
|
||||
result2 = tasks.install_kra(replica2,
|
||||
domain_level=DOMAIN_LEVEL_0,
|
||||
raiseonerr=False)
|
||||
assert_error(result2, "No replica file is required", 1)
|
||||
tasks.install_kra(replica2)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Ticket N 6274")
|
||||
class TestCAInstall(IntegrationTest):
|
||||
topology = 'star'
|
||||
domain_level = DOMAIN_LEVEL_0
|
||||
num_replicas = 2
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
||||
|
||||
def test_ca_install_without_replica_file(self):
|
||||
"""
|
||||
TestCase:
|
||||
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#Test_case:
|
||||
_ipa-ca-install_with_replica_file_works_only_on_domain_level_0
|
||||
"""
|
||||
master = self.master
|
||||
replica1 = self.replicas[0]
|
||||
replica2 = self.replicas[1]
|
||||
for replica in self.replicas:
|
||||
tasks.install_replica(master, replica, setup_ca=False,
|
||||
setup_dns=True)
|
||||
result1 = tasks.install_ca(replica1,
|
||||
domain_level=DOMAIN_LEVEL_1,
|
||||
raiseonerr=False)
|
||||
assert_error(result1, "If you wish to replicate CA to this host,"
|
||||
" please re-run 'ipa-ca-install'\nwith a"
|
||||
" replica file generated on an existing CA"
|
||||
" master as argument.", 1)
|
||||
|
||||
tasks.install_ca(replica1, domain_level=DOMAIN_LEVEL_0)
|
||||
# Now prepare the replica file, copy it to the client and raise
|
||||
# domain level on master to test the reverse situation
|
||||
master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
|
||||
result2 = tasks.install_ca(replica2,
|
||||
domain_level=DOMAIN_LEVEL_0,
|
||||
raiseonerr=False)
|
||||
assert_error(result2, 'Too many parameters provided.'
|
||||
' No replica file is required', 1)
|
||||
tasks.install_ca(replica2, domain_level=DOMAIN_LEVEL_1)
|
||||
|
||||
|
||||
class TestReplicaPromotionLevel1(ReplicaPromotionBase):
|
||||
"""
|
||||
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#
|
||||
Test_case:_Make_sure_the_old_workflow_is_disabled_at_domain_level_1
|
||||
"""
|
||||
|
||||
topology = 'star'
|
||||
num_replicas = 1
|
||||
domain_level = DOMAIN_LEVEL_1
|
||||
|
||||
@replicas_cleanup
|
||||
def test_replica_prepare_disabled(self):
|
||||
replica = self.replicas[0]
|
||||
args = ['ipa-replica-prepare',
|
||||
'-p', self.master.config.dirman_password,
|
||||
'--ip-address', replica.ip,
|
||||
replica.hostname]
|
||||
result = self.master.run_command(args, raiseonerr=False)
|
||||
assert_error(result, "Replica creation using 'ipa-replica-prepare'"
|
||||
" 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,
|
||||
'--server', self.master.hostname,
|
||||
'-U'])
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Ticket N 6274")
|
||||
class TestReplicaManageCommands(IntegrationTest):
|
||||
topology = "star"
|
||||
num_replicas = 2
|
||||
domain_level = DOMAIN_LEVEL_0
|
||||
|
||||
def test_replica_manage_commands(self):
|
||||
"""
|
||||
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
|
||||
#Test_case:_ipa-replica-manage_connect_is_deprecated_in_domain_level_1
|
||||
"""
|
||||
master = self.master
|
||||
replica1 = self.replicas[0]
|
||||
replica2 = self.replicas[1]
|
||||
|
||||
result1 = master.run_command(["ipa-replica-manage",
|
||||
"connect",
|
||||
replica1.hostname,
|
||||
replica2.hostname],
|
||||
raiseonerr=False)
|
||||
assert result1.returncode == 0, result1.stderr_text
|
||||
result2 = master.run_command(["ipa-replica-manage",
|
||||
"disconnect",
|
||||
replica1.hostname,
|
||||
replica2.hostname],
|
||||
raiseonerr=False)
|
||||
assert result2.returncode == 0, result2.stderr_text
|
||||
master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
|
||||
result3 = master.run_command(["ipa-replica-manage",
|
||||
"connect",
|
||||
replica1.hostname,
|
||||
replica2.hostname],
|
||||
raiseonerr=False)
|
||||
assert_error(result3, 'Creation of IPA replication agreement is'
|
||||
' deprecated with managed IPA replication'
|
||||
' topology. Please use `ipa topologysegment-*`'
|
||||
' commands to manage the topology', 1)
|
||||
segment = tasks.create_segment(master, replica1, replica2)
|
||||
result4 = master.run_command(["ipa-replica-manage",
|
||||
"disconnect",
|
||||
replica1.hostname,
|
||||
replica2.hostname],
|
||||
raiseonerr=False)
|
||||
assert_error(result4, 'Removal of IPA replication agreement is'
|
||||
' 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',
|
||||
'--server', self.master.hostname],
|
||||
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,
|
||||
'-U'])
|
||||
|
||||
|
||||
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_error(result, "Removal of '%s' leads to disconnected"
|
||||
" topology" % self.replicas[0].hostname, 1)
|
||||
self.replicas[0].run_command(['ipa-server-install', '--uninstall',
|
||||
'-U', '--ignore-topology-disconnect'])
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Ticket N 6274")
|
||||
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_replicas = 1
|
||||
domain_name = 'exxample.test'
|
||||
domain_level = DOMAIN_LEVEL_1
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, domain_level=cls.domain_level)
|
||||
|
||||
def teardown_method(self, method):
|
||||
self.replicas[0].run_command(['ipa-client-install',
|
||||
'--uninstall', '-U'],
|
||||
raiseonerr=False)
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(['ipa', 'host-del',
|
||||
self.replicas[0].hostname],
|
||||
raiseonerr=False)
|
||||
|
||||
def test_wrong_client_domain(self):
|
||||
client = self.replicas[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.replicas[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')
|
||||
|
||||
|
||||
class TestRenewalMaster(IntegrationTest):
|
||||
|
||||
topology = 'star'
|
||||
num_replicas = 1
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
super(TestRenewalMaster, cls).uninstall(mh)
|
||||
|
||||
def test_replica_not_marked_as_renewal_master(self):
|
||||
"""
|
||||
https://fedorahosted.org/freeipa/ticket/5902
|
||||
"""
|
||||
master = self.master
|
||||
replica = self.replicas[0]
|
||||
result = master.run_command(["ipa", "config-show"]).stdout_text
|
||||
assert("IPA CA renewal master: %s" % master.hostname in result), (
|
||||
"Master hostname not found among CA renewal masters"
|
||||
)
|
||||
assert("IPA CA renewal master: %s" % replica.hostname not in result), (
|
||||
"Replica hostname found among CA renewal masters"
|
||||
)
|
||||
|
||||
def test_manual_renewal_master_transfer(self):
|
||||
replica = self.replicas[0]
|
||||
replica.run_command(['ipa', 'config-mod',
|
||||
'--ca-renewal-master-server', replica.hostname])
|
||||
result = self.master.run_command(["ipa", "config-show"]).stdout_text
|
||||
assert("IPA CA renewal master: %s" % replica.hostname in result), (
|
||||
"Replica hostname not found among CA renewal masters"
|
||||
)
|
||||
|
||||
def test_automatic_renewal_master_transfer_ondelete(self):
|
||||
# Test that after master uninstallation, replica overtakes the cert
|
||||
# renewal master role
|
||||
tasks.uninstall_master(self.replicas[0])
|
||||
result = self.master.run_command(['ipa', 'config-show']).stdout_text
|
||||
assert("IPA CA renewal master: %s" % self.master.hostname in result), (
|
||||
"Master hostname not found among CA renewal masters"
|
||||
)
|
||||
|
||||
|
||||
class TestReplicaInstallWithExistingEntry(IntegrationTest):
|
||||
"""replica install might fail because of existing entry for replica like
|
||||
`cn=ipa-http-delegation,cn=s4u2proxy,cn=etc,$SUFFIX` etc. The situation
|
||||
may arise due to incorrect uninstall of replica.
|
||||
|
||||
https://pagure.io/freeipa/issue/7174"""
|
||||
|
||||
num_replicas = 1
|
||||
|
||||
def test_replica_install_with_existing_entry(self):
|
||||
master = self.master
|
||||
tasks.install_master(master)
|
||||
replica = self.replicas[0]
|
||||
tf = NamedTemporaryFile()
|
||||
ldif_file = tf.name
|
||||
base_dn = "dc=%s" % (",dc=".join(replica.domain.name.split(".")))
|
||||
# adding entry for replica on master so that master will have it before
|
||||
# replica installtion begins and creates a situation for pagure-7174
|
||||
entry_ldif = textwrap.dedent("""
|
||||
dn: cn=ipa-http-delegation,cn=s4u2proxy,cn=etc,{base_dn}
|
||||
changetype: modify
|
||||
add: memberPrincipal
|
||||
memberPrincipal: HTTP/{hostname}@{realm}
|
||||
|
||||
dn: cn=ipa-ldap-delegation-targets,cn=s4u2proxy,cn=etc,{base_dn}
|
||||
changetype: modify
|
||||
add: memberPrincipal
|
||||
memberPrincipal: ldap/{hostname}@{realm}""").format(
|
||||
base_dn=base_dn, hostname=replica.hostname,
|
||||
realm=replica.domain.name.upper())
|
||||
master.put_file_contents(ldif_file, entry_ldif)
|
||||
arg = ['ldapmodify',
|
||||
'-h', master.hostname,
|
||||
'-p', '389', '-D',
|
||||
str(master.config.dirman_dn), # pylint: disable=no-member
|
||||
'-w', master.config.dirman_password,
|
||||
'-f', ldif_file]
|
||||
master.run_command(arg)
|
||||
|
||||
tasks.install_replica(master, replica)
|
||||
180
ipatests/test_integration/test_replication_layouts.py
Normal file
180
ipatests/test_integration/test_replication_layouts.py
Normal file
@@ -0,0 +1,180 @@
|
||||
#
|
||||
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import time
|
||||
import pytest
|
||||
from ipalib.constants import DOMAIN_LEVEL_0
|
||||
from ipatests.pytest_plugins.integration.env_config import get_global_config
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
config = get_global_config()
|
||||
|
||||
class LayoutsBaseTest(IntegrationTest):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
# tests use custom installation
|
||||
pass
|
||||
|
||||
def replication_is_working(self):
|
||||
test_user = 'replication-testuser'
|
||||
self.master.run_command(
|
||||
['ipa', 'user-add', test_user, '--first', 'test', '--last', 'user']
|
||||
)
|
||||
|
||||
time.sleep(60) # make sure the replication of user is done
|
||||
|
||||
for r in self.replicas:
|
||||
r.run_command(['ipa', 'user-show', test_user])
|
||||
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
class TestLineTopologyWithoutCA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_line_topology_without_ca(self):
|
||||
tasks.install_topo('line', self.master, self.replicas, [],
|
||||
setup_replica_cas=False)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestLineTopologyWithCA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_line_topology_with_ca(self):
|
||||
tasks.install_topo('line', self.master, self.replicas, [],
|
||||
setup_replica_cas=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestLineTopologyWithCAKRA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_line_topology_with_ca_kra(self):
|
||||
tasks.install_topo('line', self.master, self.replicas, [],
|
||||
setup_replica_cas=True, setup_replica_kras=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestStarTopologyWithoutCA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_star_topology_without_ca(self):
|
||||
tasks.install_topo('star', self.master, self.replicas, [],
|
||||
setup_replica_cas=False)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestStarTopologyWithCA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_star_topology_with_ca(self):
|
||||
tasks.install_topo('star', self.master, self.replicas, [],
|
||||
setup_replica_cas=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestStarTopologyWithCAKRA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_star_topology_with_ca_kra(self):
|
||||
tasks.install_topo('star', self.master, self.replicas, [],
|
||||
setup_replica_cas=True, setup_replica_kras=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestCompleteTopologyWithoutCA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_complete_topology_without_ca(self):
|
||||
tasks.install_topo('complete', self.master, self.replicas, [],
|
||||
setup_replica_cas=False)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestCompleteTopologyWithCA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_complete_topology_with_ca(self):
|
||||
tasks.install_topo('complete', self.master, self.replicas, [],
|
||||
setup_replica_cas=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestCompleteTopologyWithCAKRA(LayoutsBaseTest):
|
||||
|
||||
num_replicas = 3
|
||||
|
||||
def test_complete_topology_with_ca_kra(self):
|
||||
tasks.install_topo('complete', self.master, self.replicas, [],
|
||||
setup_replica_cas=True, setup_replica_kras=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
class Test2ConnectedTopologyWithoutCA(LayoutsBaseTest):
|
||||
num_replicas = 33
|
||||
|
||||
def test_2_connected_topology_without_ca(self):
|
||||
tasks.install_topo('2-connected', self.master, self.replicas, [],
|
||||
setup_replica_cas=False)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class Test2ConnectedTopologyWithCA(LayoutsBaseTest):
|
||||
num_replicas = 33
|
||||
|
||||
def test_2_connected_topology_with_ca(self):
|
||||
tasks.install_topo('2-connected', self.master, self.replicas, [],
|
||||
setup_replica_cas=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class Test2ConnectedTopologyWithCAKRA(LayoutsBaseTest):
|
||||
num_replicas = 33
|
||||
|
||||
def test_2_connected_topology_with_ca_kra(self):
|
||||
tasks.install_topo('2-connected', self.master, self.replicas, [],
|
||||
setup_replica_cas=True, setup_replica_kras=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == DOMAIN_LEVEL_0,
|
||||
reason='does not work on DOMAIN_LEVEL_0 by design')
|
||||
class TestDoubleCircleTopologyWithoutCA(LayoutsBaseTest):
|
||||
num_replicas = 29
|
||||
|
||||
def test_2_connected_topology_with_ca(self):
|
||||
tasks.install_topo('double-circle', self.master, self.replicas, [],
|
||||
setup_replica_cas=False)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestDoubleCircleTopologyWithCA(LayoutsBaseTest):
|
||||
num_replicas = 29
|
||||
|
||||
def test_2_connected_topology_with_ca(self):
|
||||
tasks.install_topo('double-circle', self.master, self.replicas, [],
|
||||
setup_replica_cas=True)
|
||||
self.replication_is_working()
|
||||
|
||||
|
||||
class TestDoubleCircleTopologyWithCAKRA(LayoutsBaseTest):
|
||||
num_replicas = 29
|
||||
|
||||
def test_2_connected_topology_with_ca_kra(self):
|
||||
tasks.install_topo('double-circle', self.master, self.replicas, [],
|
||||
setup_replica_cas=True, setup_replica_kras=True)
|
||||
self.replication_is_working()
|
||||
302
ipatests/test_integration/test_server_del.py
Normal file
302
ipatests/test_integration/test_server_del.py
Normal file
@@ -0,0 +1,302 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from itertools import permutations
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipalib.constants import DOMAIN_LEVEL_1, DOMAIN_SUFFIX_NAME, CA_SUFFIX_NAME
|
||||
|
||||
REMOVAL_ERR_TEMPLATE = ("Removal of '{hostname}' leads to disconnected "
|
||||
"topology in suffix '{suffix}'")
|
||||
|
||||
|
||||
def check_master_removal(host, hostname_to_remove,
|
||||
force=False,
|
||||
ignore_topology_disconnect=False,
|
||||
ignore_last_of_role=False):
|
||||
result = tasks.run_server_del(
|
||||
host,
|
||||
hostname_to_remove,
|
||||
force=force,
|
||||
ignore_topology_disconnect=ignore_topology_disconnect,
|
||||
ignore_last_of_role=ignore_last_of_role)
|
||||
|
||||
assert result.returncode == 0
|
||||
if force:
|
||||
assert ("Forcing removal of {hostname}".format(
|
||||
hostname=hostname_to_remove) in result.stderr_text)
|
||||
|
||||
if ignore_topology_disconnect:
|
||||
assert "Ignoring topology connectivity errors." in result.stderr_text
|
||||
|
||||
if ignore_last_of_role:
|
||||
assert ("Ignoring these warnings and proceeding with removal" in
|
||||
result.stderr_text)
|
||||
|
||||
tasks.assert_error(
|
||||
host.run_command(
|
||||
['ipa', 'server-show', hostname_to_remove], raiseonerr=False
|
||||
),
|
||||
"{}: server not found".format(hostname_to_remove),
|
||||
returncode=2
|
||||
)
|
||||
|
||||
|
||||
def check_removal_disconnects_topology(
|
||||
host, hostname_to_remove,
|
||||
affected_suffixes=(DOMAIN_SUFFIX_NAME,)):
|
||||
result = tasks.run_server_del(host, hostname_to_remove)
|
||||
assert len(affected_suffixes) <= 2
|
||||
|
||||
err_messages_by_suffix = {
|
||||
CA_SUFFIX_NAME: REMOVAL_ERR_TEMPLATE.format(
|
||||
hostname=hostname_to_remove,
|
||||
suffix=CA_SUFFIX_NAME
|
||||
),
|
||||
DOMAIN_SUFFIX_NAME: REMOVAL_ERR_TEMPLATE.format(
|
||||
hostname=hostname_to_remove,
|
||||
suffix=DOMAIN_SUFFIX_NAME
|
||||
)
|
||||
}
|
||||
|
||||
for suffix in err_messages_by_suffix:
|
||||
if suffix in affected_suffixes:
|
||||
tasks.assert_error(
|
||||
result, err_messages_by_suffix[suffix], returncode=1)
|
||||
else:
|
||||
assert err_messages_by_suffix[suffix] not in result.stderr_text
|
||||
|
||||
|
||||
class ServerDelBase(IntegrationTest):
|
||||
num_replicas = 2
|
||||
num_clients = 1
|
||||
domain_level = DOMAIN_LEVEL_1
|
||||
topology = 'star'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(ServerDelBase, cls).install(mh)
|
||||
|
||||
cls.client = cls.clients[0]
|
||||
cls.replica1 = cls.replicas[0]
|
||||
cls.replica2 = cls.replicas[1]
|
||||
|
||||
|
||||
class TestServerDel(ServerDelBase):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestServerDel, cls).install(mh)
|
||||
# prepare topologysegments for negative test cases
|
||||
# it should look like this for DOMAIN_SUFFIX_NAME:
|
||||
# master
|
||||
# /
|
||||
# /
|
||||
# /
|
||||
# replica1------- replica2
|
||||
# and like this for CA_SUFFIX_NAME
|
||||
# master
|
||||
# \
|
||||
# \
|
||||
# \
|
||||
# replica1------- replica2
|
||||
|
||||
tasks.create_segment(cls.client, cls.replica1, cls.replica2)
|
||||
tasks.create_segment(cls.client, cls.replica1, cls.replica2,
|
||||
suffix=CA_SUFFIX_NAME)
|
||||
|
||||
# try to delete all relevant segment connecting master and replica1/2
|
||||
segment_name_fmt = '{p[0].hostname}-to-{p[1].hostname}'
|
||||
for domain_pair in permutations((cls.master, cls.replica2)):
|
||||
tasks.destroy_segment(
|
||||
cls.client, segment_name_fmt.format(p=domain_pair))
|
||||
|
||||
for ca_pair in permutations((cls.master, cls.replica1)):
|
||||
tasks.destroy_segment(
|
||||
cls.client, segment_name_fmt.format(p=ca_pair),
|
||||
suffix=CA_SUFFIX_NAME)
|
||||
|
||||
def test_removal_of_nonexistent_master_raises_error(self):
|
||||
"""
|
||||
tests that removal of non-existent master raises an error
|
||||
"""
|
||||
hostname = u'bogus-master.bogus.domain'
|
||||
err_message = "{}: server not found".format(hostname)
|
||||
tasks.assert_error(
|
||||
tasks.run_server_del(self.client, hostname),
|
||||
err_message,
|
||||
returncode=2
|
||||
)
|
||||
|
||||
def test_forced_removal_of_nonexistent_master(self):
|
||||
"""
|
||||
tests that removal of non-existent master with '--force' does not raise
|
||||
an error
|
||||
"""
|
||||
hostname = u'bogus-master.bogus.domain'
|
||||
result = tasks.run_server_del(self.client, hostname, force=True)
|
||||
assert result.returncode == 0
|
||||
assert ('Deleted IPA server "{}"'.format(hostname) in
|
||||
result.stdout_text)
|
||||
|
||||
assert ("Server has already been deleted" in result.stderr_text)
|
||||
|
||||
def test_removal_of_replica1_disconnects_domain_topology(self):
|
||||
"""
|
||||
tests that given the used topology, attempted removal of replica1 fails
|
||||
with disconnected DOMAIN topology but not CA
|
||||
"""
|
||||
|
||||
check_removal_disconnects_topology(
|
||||
self.client,
|
||||
self.replica1.hostname,
|
||||
affected_suffixes=(DOMAIN_SUFFIX_NAME,)
|
||||
)
|
||||
|
||||
def test_removal_of_replica2_disconnects_ca_topology(self):
|
||||
"""
|
||||
tests that given the used topology, attempted removal of replica2 fails
|
||||
with disconnected CA topology but not DOMAIN
|
||||
"""
|
||||
|
||||
check_removal_disconnects_topology(
|
||||
self.client,
|
||||
self.replica2.hostname,
|
||||
affected_suffixes=(CA_SUFFIX_NAME,)
|
||||
)
|
||||
|
||||
def test_ignore_topology_disconnect_replica1(self):
|
||||
"""
|
||||
tests that removal of replica1 with '--ignore-topology-disconnect'
|
||||
destroys master for good
|
||||
"""
|
||||
check_master_removal(
|
||||
self.client,
|
||||
self.replica1.hostname,
|
||||
ignore_topology_disconnect=True
|
||||
)
|
||||
|
||||
# reinstall the replica
|
||||
tasks.uninstall_master(self.replica1)
|
||||
tasks.install_replica(self.master, self.replica1, setup_ca=True)
|
||||
|
||||
def test_ignore_topology_disconnect_replica2(self):
|
||||
"""
|
||||
tests that removal of replica2 with '--ignore-topology-disconnect'
|
||||
destroys master for good
|
||||
"""
|
||||
check_master_removal(
|
||||
self.client,
|
||||
self.replica2.hostname,
|
||||
ignore_topology_disconnect=True
|
||||
)
|
||||
|
||||
# reinstall the replica
|
||||
tasks.uninstall_master(self.replica2)
|
||||
tasks.install_replica(self.master, self.replica2, setup_ca=True)
|
||||
|
||||
def test_removal_of_master_disconnects_both_topologies(self):
|
||||
"""
|
||||
tests that master removal will now raise errors in both suffixes.
|
||||
"""
|
||||
check_removal_disconnects_topology(
|
||||
self.client,
|
||||
self.master.hostname,
|
||||
affected_suffixes=(CA_SUFFIX_NAME, DOMAIN_SUFFIX_NAME)
|
||||
)
|
||||
|
||||
def test_removal_of_replica1(self):
|
||||
"""
|
||||
tests the removal of replica1 which should now pass without errors
|
||||
"""
|
||||
check_master_removal(
|
||||
self.client,
|
||||
self.replica1.hostname
|
||||
)
|
||||
|
||||
def test_removal_of_replica2(self):
|
||||
"""
|
||||
tests the removal of replica2 which should now pass without errors
|
||||
"""
|
||||
check_master_removal(
|
||||
self.client,
|
||||
self.replica2.hostname
|
||||
)
|
||||
|
||||
|
||||
class TestLastServices(ServerDelBase):
|
||||
"""
|
||||
Test the checks for last services during server-del and their bypassing
|
||||
using when forcing the removal
|
||||
"""
|
||||
num_replicas = 1
|
||||
domain_level = DOMAIN_LEVEL_1
|
||||
topology = 'line'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_topo(
|
||||
cls.topology, cls.master, cls.replicas, [],
|
||||
domain_level=cls.domain_level, setup_replica_cas=False)
|
||||
|
||||
def test_removal_of_master_raises_error_about_last_ca(self):
|
||||
"""
|
||||
test that removal of master fails on the last
|
||||
"""
|
||||
tasks.assert_error(
|
||||
tasks.run_server_del(self.replicas[0], self.master.hostname),
|
||||
"Deleting this server is not allowed as it would leave your "
|
||||
"installation without a CA.",
|
||||
1
|
||||
)
|
||||
|
||||
def test_install_ca_on_replica1(self):
|
||||
"""
|
||||
Install CA on replica so that we can test DNS-related checks
|
||||
"""
|
||||
tasks.install_ca(self.replicas[0], domain_level=self.domain_level)
|
||||
|
||||
def test_removal_of_master_raises_error_about_last_dns(self):
|
||||
"""
|
||||
Now server-del should complain about the removal of last DNS server
|
||||
"""
|
||||
tasks.assert_error(
|
||||
tasks.run_server_del(self.replicas[0], self.master.hostname),
|
||||
"Deleting this server will leave your installation "
|
||||
"without a DNS.",
|
||||
1
|
||||
)
|
||||
|
||||
def test_install_dns_on_replica1_and_dnssec_on_master(self):
|
||||
"""
|
||||
install DNS server on replica and DNSSec on master
|
||||
"""
|
||||
tasks.install_dns(self.replicas[0])
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--dnssec-master",
|
||||
"--forwarder", self.master.config.dns_forwarder,
|
||||
"-U",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
|
||||
def test_removal_of_master_raises_error_about_dnssec(self):
|
||||
tasks.assert_error(
|
||||
tasks.run_server_del(self.replicas[0], self.master.hostname),
|
||||
"Replica is active DNSSEC key master. Uninstall "
|
||||
"could break your DNS system. Please disable or replace "
|
||||
"DNSSEC key master first.",
|
||||
1
|
||||
)
|
||||
|
||||
def test_forced_removal_of_master(self):
|
||||
"""
|
||||
Tests that we can still force remove the master using
|
||||
'--ignore-last-of-role'
|
||||
"""
|
||||
check_master_removal(
|
||||
self.replicas[0], self.master.hostname,
|
||||
ignore_last_of_role=True
|
||||
)
|
||||
138
ipatests/test_integration/test_service_permissions.py
Normal file
138
ipatests/test_integration/test_service_permissions.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
|
||||
class TestServicePermissions(IntegrationTest):
|
||||
topology = 'star'
|
||||
|
||||
def test_service_as_user_admin(self):
|
||||
"""Test that a service in User Administrator role can manage users"""
|
||||
|
||||
service_name = 'testservice/%s@%s' % (self.master.hostname,
|
||||
self.master.domain.realm)
|
||||
keytab_file = os.path.join(self.master.config.test_dir,
|
||||
'testservice_keytab')
|
||||
|
||||
# Prepare a service
|
||||
|
||||
self.master.run_command(['ipa', 'service-add', service_name])
|
||||
|
||||
self.master.run_command(['ipa-getkeytab',
|
||||
'-p', service_name,
|
||||
'-k', keytab_file,
|
||||
'-s', self.master.hostname])
|
||||
|
||||
# Check that the service cannot add a user
|
||||
|
||||
self.master.run_command(['kdestroy'])
|
||||
self.master.run_command(['kinit', '-k', service_name,
|
||||
'-t', keytab_file])
|
||||
|
||||
result = self.master.run_command(['ipa', 'role-add-member',
|
||||
'User Administrator',
|
||||
'--service', service_name],
|
||||
raiseonerr=False)
|
||||
assert result.returncode > 0
|
||||
|
||||
# Add service to User Administrator role
|
||||
|
||||
self.master.run_command(['kdestroy'])
|
||||
tasks.kinit_admin(self.master)
|
||||
|
||||
self.master.run_command(['ipa', 'role-add-member',
|
||||
'User Administrator',
|
||||
'--service', service_name])
|
||||
|
||||
# Check that the service now can add a user
|
||||
|
||||
self.master.run_command(['kdestroy'])
|
||||
self.master.run_command(['kinit', '-k', service_name,
|
||||
'-t', keytab_file])
|
||||
|
||||
self.master.run_command(['ipa', 'user-add', 'tuser',
|
||||
'--first', 'a', '--last', 'b', '--random'])
|
||||
|
||||
# Clean up
|
||||
|
||||
self.master.run_command(['kdestroy'])
|
||||
tasks.kinit_admin(self.master)
|
||||
|
||||
self.master.run_command(['ipa', 'service-del', service_name])
|
||||
self.master.run_command(['ipa', 'user-del', 'tuser'])
|
||||
|
||||
|
||||
class TestServiceAuthenticationIndicators(IntegrationTest):
|
||||
topology = 'star'
|
||||
|
||||
def test_service_access(self):
|
||||
""" Test that user is granted access when authenticated using
|
||||
credentials that are sufficient for a service, and denied access
|
||||
when using insufficient credentials"""
|
||||
|
||||
service_name = 'testservice/%s@%s' % (self.master.hostname,
|
||||
self.master.domain.realm)
|
||||
|
||||
keytab_file = os.path.join(self.master.config.test_dir,
|
||||
'testservice_keytab')
|
||||
|
||||
# Prepare a service without authentication indicator
|
||||
self.master.run_command(['ipa', 'service-add', service_name])
|
||||
|
||||
self.master.run_command(['ipa-getkeytab',
|
||||
'-p', service_name,
|
||||
'-k', keytab_file])
|
||||
|
||||
# Set authentication-type for admin user
|
||||
self.master.run_command(['ipa', 'user-mod', 'admin',
|
||||
'--user-auth-type=password',
|
||||
'--user-auth-type=otp'])
|
||||
|
||||
# Authenticate
|
||||
self.master.run_command(['kinit', '-k', service_name,
|
||||
'-t', keytab_file])
|
||||
|
||||
# Verify access to service is granted
|
||||
result = self.master.run_command(['kvno', service_name],
|
||||
raiseonerr=False)
|
||||
assert result.returncode == 0
|
||||
|
||||
# Obtain admin ticket to be able to update service
|
||||
tasks.kinit_admin(self.master)
|
||||
|
||||
# Modify service to have authentication indicator
|
||||
self.master.run_command(['ipa', 'service-mod', service_name,
|
||||
'--auth-ind=otp'])
|
||||
|
||||
self.master.run_command(['ipa-getkeytab',
|
||||
'-p', service_name,
|
||||
'-k', keytab_file])
|
||||
|
||||
# Authenticate
|
||||
self.master.run_command(['kinit', '-k', service_name,
|
||||
'-t', keytab_file])
|
||||
|
||||
# Verify access to service is rejected
|
||||
result = self.master.run_command(['kvno', service_name],
|
||||
raiseonerr=False)
|
||||
assert result.returncode > 0
|
||||
79
ipatests/test_integration/test_simple_replication.py
Normal file
79
ipatests/test_integration/test_simple_replication.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import pytest
|
||||
|
||||
from ipapython.dn import DN
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
|
||||
@pytest.mark.ds_acceptance
|
||||
class TestSimpleReplication(IntegrationTest):
|
||||
"""Simple replication test
|
||||
|
||||
Install a server and a replica, then add an user on one host and ensure
|
||||
it is also present on the other one.
|
||||
"""
|
||||
num_replicas = 1
|
||||
topology = 'star'
|
||||
|
||||
def check_replication(self, source_host, dest_host, login):
|
||||
source_host.run_command(['ipa', 'user-add', login,
|
||||
'--first', 'test',
|
||||
'--last', 'user'])
|
||||
|
||||
source_ldap = source_host.ldap_connect()
|
||||
tasks.wait_for_replication(source_ldap)
|
||||
|
||||
ldap = dest_host.ldap_connect()
|
||||
tasks.wait_for_replication(ldap)
|
||||
|
||||
# Check using LDAP
|
||||
basedn = dest_host.domain.basedn
|
||||
user_dn = DN(('uid', login), ('cn', 'users'), ('cn', 'accounts'),
|
||||
basedn)
|
||||
entry = ldap.get_entry(user_dn)
|
||||
print(entry)
|
||||
assert entry.dn == user_dn
|
||||
assert entry['uid'] == [login]
|
||||
|
||||
# Check using CLI
|
||||
result = dest_host.run_command(['ipa', 'user-show', login])
|
||||
assert 'User login: %s' % login in result.stdout_text
|
||||
|
||||
def test_user_replication_to_replica(self):
|
||||
"""Test user replication master -> replica"""
|
||||
self.check_replication(self.master, self.replicas[0], 'testuser1')
|
||||
|
||||
def test_user_replication_to_master(self):
|
||||
"""Test user replication replica -> master"""
|
||||
self.check_replication(self.replicas[0], self.master, 'testuser2')
|
||||
|
||||
def test_replica_removal(self):
|
||||
"""Test replica removal"""
|
||||
result = self.master.run_command(['ipa-replica-manage', 'list'])
|
||||
assert self.replicas[0].hostname in result.stdout_text
|
||||
# has to be run with --force, there is no --unattended
|
||||
self.master.run_command(['ipa-replica-manage', 'del',
|
||||
self.replicas[0].hostname, '--force'])
|
||||
result = self.master.run_command(['ipa-replica-manage', 'list'])
|
||||
assert self.replicas[0].hostname not in result.stdout_text
|
||||
682
ipatests/test_integration/test_sudo.py
Normal file
682
ipatests/test_integration/test_sudo.py
Normal file
@@ -0,0 +1,682 @@
|
||||
# Authors:
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration.tasks import (
|
||||
clear_sssd_cache, get_host_ip_with_hostmask, modify_sssd_conf)
|
||||
|
||||
|
||||
class TestSudo(IntegrationTest):
|
||||
"""
|
||||
Test Sudo
|
||||
http://www.freeipa.org/page/V4/Sudo_Integration#Test_Plan
|
||||
"""
|
||||
num_clients = 1
|
||||
topology = 'line'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestSudo, cls).install(mh)
|
||||
|
||||
cls.client = cls.clients[0]
|
||||
cls.clientname = cls.client.run_command(
|
||||
['hostname', '-s']).stdout_text.strip()
|
||||
|
||||
for i in range(1, 3):
|
||||
# Add 1. and 2. testing user
|
||||
cls.master.run_command(['ipa', 'user-add',
|
||||
'testuser%d' % i,
|
||||
'--first', 'Test',
|
||||
'--last', 'User%d' % i])
|
||||
|
||||
# Add 1. and 2. testing groups
|
||||
cls.master.run_command(['ipa', 'group-add',
|
||||
'testgroup%d' % i,
|
||||
'--desc', '"%d. testing group"' % i])
|
||||
|
||||
# Add respective members to each group
|
||||
cls.master.run_command(['ipa', 'group-add-member',
|
||||
'testgroup%d' % i,
|
||||
'--users', 'testuser%d' % i])
|
||||
|
||||
# Add hostgroup containing the client
|
||||
cls.master.run_command(['ipa', 'hostgroup-add',
|
||||
'testhostgroup',
|
||||
'--desc', '"Contains client"'])
|
||||
|
||||
# Add the client to the host group
|
||||
cls.master.run_command(['ipa', 'hostgroup-add-member',
|
||||
'testhostgroup',
|
||||
'--hosts', cls.client.hostname])
|
||||
|
||||
# Create local user and local group he's member of
|
||||
cls.client.run_command(['groupadd', 'localgroup'])
|
||||
cls.client.run_command(['useradd',
|
||||
'-M',
|
||||
'-G', 'localgroup',
|
||||
'localuser'])
|
||||
|
||||
# Create sudorule 'defaults' for not requiring authentication
|
||||
cls.master.run_command(['ipa', 'sudorule-add', 'defaults'])
|
||||
cls.master.run_command(['ipa', 'sudorule-add-option',
|
||||
'defaults',
|
||||
'--sudooption', "!authenticate"])
|
||||
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, mh):
|
||||
cls.client.run_command(['groupdel', 'localgroup'], raiseonerr=False)
|
||||
cls.client.run_command(['userdel', 'localuser'], raiseonerr=False)
|
||||
super(TestSudo, cls).uninstall(mh)
|
||||
|
||||
def list_sudo_commands(self, user, raiseonerr=False, verbose=False):
|
||||
clear_sssd_cache(self.client)
|
||||
list_flag = '-ll' if verbose else '-l'
|
||||
return self.client.run_command(
|
||||
'su -c "sudo %s -n" %s' % (list_flag, user),
|
||||
raiseonerr=raiseonerr)
|
||||
|
||||
def reset_rule_categories(self, safe_delete=True):
|
||||
if safe_delete:
|
||||
# Remove and then add the rule back, since the deletion of some
|
||||
# entries might cause setting categories to ALL to fail
|
||||
# and therefore cause false negatives in the tests
|
||||
self.master.run_command(['ipa', 'sudorule-del', 'testrule'])
|
||||
self.master.run_command(['ipa', 'sudorule-add', 'testrule'])
|
||||
self.master.run_command(['ipa', 'sudorule-add-option',
|
||||
'testrule',
|
||||
'--sudooption', "!authenticate"])
|
||||
|
||||
# Reset testrule to allow everything
|
||||
result = self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--usercat=all',
|
||||
'--hostcat=all',
|
||||
'--cmdcat=all',
|
||||
'--runasusercat=all',
|
||||
'--runasgroupcat=all'],
|
||||
raiseonerr=False)
|
||||
|
||||
return result
|
||||
|
||||
def test_nisdomainname(self):
|
||||
result = self.client.run_command('nisdomainname')
|
||||
assert self.client.domain.name in result.stdout_text
|
||||
|
||||
def test_add_sudo_commands(self):
|
||||
# Group: Readers
|
||||
self.master.run_command(['ipa', 'sudocmd-add', '/usr/bin/cat'])
|
||||
self.master.run_command(['ipa', 'sudocmd-add', '/usr/bin/tail'])
|
||||
|
||||
# No group
|
||||
self.master.run_command(['ipa', 'sudocmd-add', '/usr/bin/yum'])
|
||||
|
||||
def test_add_sudo_command_groups(self):
|
||||
self.master.run_command(['ipa', 'sudocmdgroup-add', 'readers',
|
||||
'--desc', '"Applications that read"'])
|
||||
|
||||
self.master.run_command(['ipa', 'sudocmdgroup-add-member', 'readers',
|
||||
'--sudocmds', '/usr/bin/cat'])
|
||||
|
||||
self.master.run_command(['ipa', 'sudocmdgroup-add-member', 'readers',
|
||||
'--sudocmds', '/usr/bin/tail'])
|
||||
|
||||
def test_create_allow_all_rule(self):
|
||||
# Create rule that allows everything
|
||||
self.master.run_command(['ipa', 'sudorule-add',
|
||||
'testrule',
|
||||
'--usercat=all',
|
||||
'--hostcat=all',
|
||||
'--cmdcat=all',
|
||||
'--runasusercat=all',
|
||||
'--runasgroupcat=all'])
|
||||
|
||||
# Add !authenticate option
|
||||
self.master.run_command(['ipa', 'sudorule-add-option',
|
||||
'testrule',
|
||||
'--sudooption', "!authenticate"])
|
||||
|
||||
def test_add_sudo_rule(self):
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result1.stdout_text
|
||||
|
||||
result2 = self.list_sudo_commands("testuser2")
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result2.stdout_text
|
||||
|
||||
def test_sudo_rule_restricted_to_one_user_setup(self):
|
||||
# Configure the rule to not apply to anybody
|
||||
self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--usercat='])
|
||||
|
||||
# Add the testuser1 to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-user',
|
||||
'testrule',
|
||||
'--users', 'testuser1'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_user(self):
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result1.stdout_text
|
||||
|
||||
result2 = self.list_sudo_commands("testuser2", raiseonerr=False)
|
||||
assert result2.returncode != 0
|
||||
assert "Sorry, user testuser2 may not run sudo on {}.".format(
|
||||
self.clientname) in result2.stderr_text
|
||||
|
||||
def test_sudo_rule_restricted_to_one_user_without_defaults_rule(self):
|
||||
# Verify password is requested with the 'defaults' sudorule disabled
|
||||
self.master.run_command(['ipa', 'sudorule-disable', 'defaults'])
|
||||
|
||||
result3 = self.list_sudo_commands("testuser2", raiseonerr=False)
|
||||
assert result3.returncode != 0
|
||||
assert "sudo: a password is required" in result3.stderr_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_user(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_one_user_teardown(self):
|
||||
# Remove the testuser1 from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-user',
|
||||
'testrule',
|
||||
'--users', 'testuser1'])
|
||||
self.master.run_command(['ipa', 'sudorule-enable', 'defaults'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_group_setup(self):
|
||||
# Add the testgroup2 to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-user',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_group(self):
|
||||
result1 = self.list_sudo_commands("testuser1", raiseonerr=False)
|
||||
assert result1.returncode != 0
|
||||
assert "Sorry, user testuser1 may not run sudo on {}.".format(
|
||||
self.clientname) in result1.stderr_text
|
||||
|
||||
result2 = self.list_sudo_commands("testuser2")
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result2.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_user_group(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_one_group_teardown(self):
|
||||
# Remove the testgroup2 from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-user',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_host_negative_setup(self):
|
||||
# Reset testrule configuration
|
||||
self.reset_rule_categories()
|
||||
|
||||
# Configure the rule to not apply anywhere
|
||||
self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--hostcat='])
|
||||
|
||||
# Add the master to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hosts', self.master.hostname])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_host_negative(self):
|
||||
result1 = self.list_sudo_commands("testuser1", raiseonerr=False)
|
||||
assert result1.returncode != 0
|
||||
assert "Sorry, user testuser1 may not run sudo on {}.".format(
|
||||
self.clientname) in result1.stderr_text
|
||||
|
||||
def test_sudo_rule_restricted_to_one_host_negative_teardown(self):
|
||||
# Remove the master from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-host',
|
||||
'testrule',
|
||||
'--hosts', self.master.hostname])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_host_setup(self):
|
||||
# Configure the rulle to not apply anywhere
|
||||
self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--hostcat='], raiseonerr=False)
|
||||
|
||||
# Add the master to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hosts', self.client.hostname])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_host(self):
|
||||
result1 = self.list_sudo_commands("testuser1", raiseonerr=False)
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_host(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_one_host_teardown(self):
|
||||
# Remove the master from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-host',
|
||||
'testrule',
|
||||
'--hosts', self.client.hostname])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostgroup_setup(self):
|
||||
# Add the testhostgroup to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hostgroups', 'testhostgroup'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostgroup(self):
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_host_group(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostgroup_teardown(self):
|
||||
# Remove the testhostgroup from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-host',
|
||||
'testrule',
|
||||
'--hostgroups', 'testhostgroup'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostmask_setup(self):
|
||||
# We need to detect the hostmask first
|
||||
full_ip = get_host_ip_with_hostmask(self.client)
|
||||
|
||||
# Make a note for the next test, which needs to be skipped
|
||||
# if hostmask detection failed
|
||||
self.__class__.skip_hostmask_based = False
|
||||
|
||||
if not full_ip:
|
||||
self.__class__.skip_hostmask_based = True
|
||||
raise pytest.skip("Hostmask could not be detected")
|
||||
|
||||
self.master.run_command(['ipa', '-n', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hostmask', full_ip])
|
||||
|
||||
# SSSD >= 1.13.3-3 uses native IPA schema instead of compat entries to
|
||||
# pull in sudoers. Since native schema does not (yet) support
|
||||
# hostmasks, we need to point ldap_sudo_search_base to the old schema
|
||||
domain = self.client.domain
|
||||
modify_sssd_conf(
|
||||
self.client,
|
||||
domain.name,
|
||||
{
|
||||
'ldap_sudo_search_base': 'ou=sudoers,{}'.format(domain.basedn)
|
||||
},
|
||||
provider_subtype='sudo'
|
||||
)
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostmask(self):
|
||||
if self.__class__.skip_hostmask_based:
|
||||
raise pytest.skip("Hostmask could not be detected")
|
||||
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert "(ALL : ALL) NOPASSWD: ALL" in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_host_mask(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostmask_teardown(self):
|
||||
if self.__class__.skip_hostmask_based:
|
||||
raise pytest.skip("Hostmask could not be detected")
|
||||
|
||||
# Detect the hostmask first to delete the hostmask based rule
|
||||
full_ip = get_host_ip_with_hostmask(self.client)
|
||||
|
||||
# Remove the client's hostmask from the rule
|
||||
self.master.run_command(['ipa', '-n', 'sudorule-remove-host',
|
||||
'testrule',
|
||||
'--hostmask', full_ip])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostmask_negative_setup(self):
|
||||
# Add the master's hostmask to the rule
|
||||
ip = self.master.ip
|
||||
self.master.run_command(['ipa', '-n', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hostmask', '%s/32' % ip])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostmask_negative(self):
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert result1.returncode != 0
|
||||
assert "Sorry, user testuser1 may not run sudo on {}.".format(
|
||||
self.clientname) in result1.stderr_text
|
||||
|
||||
def test_sudo_rule_restricted_to_one_hostmask_negative_teardown(self):
|
||||
# Remove the master's hostmask from the rule
|
||||
ip = self.master.ip
|
||||
self.master.run_command(['ipa', '-n', 'sudorule-remove-host',
|
||||
'testrule',
|
||||
'--hostmask', '%s/32' % ip])
|
||||
|
||||
# reset ldap_sudo_search_base back to the default value, the old
|
||||
# schema is not needed for the upcoming tests
|
||||
domain = self.client.domain
|
||||
modify_sssd_conf(
|
||||
self.client,
|
||||
domain.name,
|
||||
{
|
||||
'ldap_sudo_search_base': None
|
||||
},
|
||||
provider_subtype='sudo'
|
||||
)
|
||||
|
||||
def test_sudo_rule_restricted_to_one_command_setup(self):
|
||||
# Reset testrule configuration
|
||||
self.reset_rule_categories()
|
||||
|
||||
# Configure the rule to not allow any command
|
||||
self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--cmdcat='])
|
||||
|
||||
# Add the yum command to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-allow-command',
|
||||
'testrule',
|
||||
'--sudocmds', '/usr/bin/yum'])
|
||||
|
||||
def test_sudo_rule_restricted_to_one_command(self):
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert "(ALL : ALL) NOPASSWD: /usr/bin/yum" in result1.stdout_text
|
||||
|
||||
def test_sudo_rule_restricted_to_command_and_command_group_setup(self):
|
||||
# Add the readers command group to the rule
|
||||
self.master.run_command(['ipa', 'sudorule-add-allow-command',
|
||||
'testrule',
|
||||
'--sudocmdgroups', 'readers'])
|
||||
|
||||
def test_sudo_rule_restricted_to_command_and_command_group(self):
|
||||
result1 = self.list_sudo_commands("testuser1")
|
||||
assert "(ALL : ALL) NOPASSWD:" in result1.stdout_text
|
||||
assert "/usr/bin/yum" in result1.stdout_text
|
||||
assert "/usr/bin/tail" in result1.stdout_text
|
||||
assert "/usr/bin/cat" in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_command(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_command_and_command_group_teardown(self):
|
||||
# Remove the yum command from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-allow-command',
|
||||
'testrule',
|
||||
'--sudocmds', '/usr/bin/yum'])
|
||||
|
||||
# Remove the readers command group from the rule
|
||||
self.master.run_command(['ipa', 'sudorule-remove-allow-command',
|
||||
'testrule',
|
||||
'--sudocmdgroups', 'readers'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_user_setup(self):
|
||||
# Reset testrule configuration
|
||||
self.reset_rule_categories()
|
||||
|
||||
# Configure the rule to not allow running commands as anybody
|
||||
self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--runasusercat='])
|
||||
|
||||
self.master.run_command(['ipa', 'sudorule-mod',
|
||||
'testrule',
|
||||
'--runasgroupcat='])
|
||||
|
||||
# Allow running commands as testuser2
|
||||
self.master.run_command(['ipa', 'sudorule-add-runasuser',
|
||||
'testrule',
|
||||
'--users', 'testuser2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_user(self):
|
||||
result1 = self.list_sudo_commands("testuser1", verbose=True)
|
||||
assert "RunAsUsers: testuser2" in result1.stdout_text
|
||||
assert "RunAsGroups:" not in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_runasuser(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_user_teardown(self):
|
||||
# Remove permission to run commands as testuser2
|
||||
self.master.run_command(['ipa', 'sudorule-remove-runasuser',
|
||||
'testrule',
|
||||
'--users', 'testuser2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_local_user_setup(self):
|
||||
# Allow running commands as testuser2
|
||||
self.master.run_command(['ipa', 'sudorule-add-runasuser',
|
||||
'testrule',
|
||||
'--users', 'localuser'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_local_user(self):
|
||||
result1 = self.list_sudo_commands("testuser1", verbose=True)
|
||||
assert "RunAsUsers: localuser" in result1.stdout_text
|
||||
assert "RunAsGroups:" not in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_runasuser_local(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_user_local_tear(self):
|
||||
# Remove permission to run commands as testuser2
|
||||
self.master.run_command(['ipa', 'sudorule-remove-runasuser',
|
||||
'testrule',
|
||||
'--users', 'localuser'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_users_from_group_setup(self):
|
||||
# Allow running commands as users from testgroup2
|
||||
self.master.run_command(['ipa', 'sudorule-add-runasuser',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_users_from_group(self):
|
||||
result1 = self.list_sudo_commands("testuser1", verbose=True)
|
||||
assert "RunAsUsers: %testgroup2" in result1.stdout_text
|
||||
assert "RunAsGroups:" not in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_runasuser_group(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_users_from_group_teardown(self):
|
||||
# Remove permission to run commands as testuser2
|
||||
self.master.run_command(['ipa', 'sudorule-remove-runasuser',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_run_as_users_from_local_group_setup(self):
|
||||
# Allow running commands as users from localgroup
|
||||
self.master.run_command(['ipa', 'sudorule-add-runasuser',
|
||||
'testrule',
|
||||
'--groups', 'localgroup'])
|
||||
|
||||
def test_sudo_rule_restricted_to_run_as_users_from_local_group(self):
|
||||
result1 = self.list_sudo_commands("testuser1", verbose=True)
|
||||
assert "RunAsUsers: %localgroup" in result1.stdout_text
|
||||
assert "RunAsGroups:" not in result1.stdout_text
|
||||
|
||||
def test_set_category_to_all_with_valid_entries_runasuser_group_local(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_run_as_users_from_local_group_tear(self):
|
||||
# Remove permission to run commands as testuser2
|
||||
self.master.run_command(['ipa', 'sudorule-remove-runasuser',
|
||||
'testrule',
|
||||
'--groups', 'localgroup'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_group_setup(self):
|
||||
# Allow running commands as testgroup2
|
||||
self.master.run_command(['ipa', 'sudorule-add-runasgroup',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_group(self):
|
||||
result1 = self.list_sudo_commands("testuser1", verbose=True)
|
||||
assert "RunAsUsers: testuser1" in result1.stdout_text
|
||||
assert "RunAsGroups: testgroup2" in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_runasgroup(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_group_teardown(self):
|
||||
# Remove permission to run commands as testgroup2
|
||||
self.master.run_command(['ipa', 'sudorule-remove-runasgroup',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_local_group_setup(self):
|
||||
# Allow running commands as testgroup2
|
||||
self.master.run_command(['ipa', 'sudorule-add-runasgroup',
|
||||
'testrule',
|
||||
'--groups', 'localgroup'])
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_local_group(self):
|
||||
result1 = self.list_sudo_commands("testuser1", verbose=True)
|
||||
assert "RunAsUsers: testuser1" in result1.stdout_text
|
||||
assert "RunAsGroups: localgroup" in result1.stdout_text
|
||||
|
||||
def test_setting_category_to_all_with_valid_entries_runasgroup_local(self):
|
||||
result = self.reset_rule_categories(safe_delete=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_sudo_rule_restricted_to_running_as_single_local_group_tear(self):
|
||||
# Remove permission to run commands as testgroup2
|
||||
self.master.run_command(['ipa', 'sudorule-remove-runasgroup',
|
||||
'testrule',
|
||||
'--groups', 'localgroup'])
|
||||
|
||||
def test_category_all_validation_setup(self):
|
||||
# Reset testrule configuration
|
||||
self.reset_rule_categories()
|
||||
|
||||
def test_category_all_validation_user(self):
|
||||
# Add the testuser1 to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-user',
|
||||
'testrule',
|
||||
'--users', 'testuser1'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_user_group(self):
|
||||
# Try to add the testgroup2 to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-user',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_user_local(self):
|
||||
# Try to add the local user to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-user',
|
||||
'testrule',
|
||||
'--users', 'localuser'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_host(self):
|
||||
# Try to add the master to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hosts', self.master.hostname],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_host_group(self):
|
||||
# Try to add the testhostgroup to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hostgroups', 'testhostgroup'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_host_mask(self):
|
||||
# Try to add the client's /24 hostmask to the rule
|
||||
ip = self.client.ip
|
||||
result = self.master.run_command(['ipa', '-n', 'sudorule-add-host',
|
||||
'testrule',
|
||||
'--hostmask', '%s/24' % ip],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_command_allow(self):
|
||||
# Try to add the yum command to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-allow-command',
|
||||
'testrule',
|
||||
'--sudocmds', '/usr/bin/yum'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_command_allow_group(self):
|
||||
# Try to add the readers command group to the rule
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-allow-command',
|
||||
'testrule',
|
||||
'--sudocmdgroups', 'readers'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_command_deny(self):
|
||||
# Try to add the yum command to the rule
|
||||
# This SHOULD be allowed
|
||||
self.master.run_command(['ipa', 'sudorule-add-deny-command',
|
||||
'testrule',
|
||||
'--sudocmds', '/usr/bin/yum'],
|
||||
raiseonerr=False)
|
||||
|
||||
self.master.run_command(['ipa', 'sudorule-remove-deny-command',
|
||||
'testrule',
|
||||
'--sudocmds', '/usr/bin/yum'],
|
||||
raiseonerr=False)
|
||||
|
||||
def test_category_all_validation_command_deny_group(self):
|
||||
# Try to add the readers command group to the rule
|
||||
# This SHOULD be allowed
|
||||
self.master.run_command(['ipa', 'sudorule-add-deny-command',
|
||||
'testrule',
|
||||
'--sudocmdgroups', 'readers'])
|
||||
|
||||
self.master.run_command(['ipa', 'sudorule-remove-deny-command',
|
||||
'testrule',
|
||||
'--sudocmdgroups', 'readers'])
|
||||
|
||||
def test_category_all_validation_runasuser(self):
|
||||
# Try to allow running commands as testuser2
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-runasuser',
|
||||
'testrule',
|
||||
'--users', 'testuser2'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_runasuser_group(self):
|
||||
# Try to allow running commands as users from testgroup2
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-runasuser',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_category_all_validation_runasgroup(self):
|
||||
# Try to allow running commands as testgroup2
|
||||
result = self.master.run_command(['ipa', 'sudorule-add-runasgroup',
|
||||
'testrule',
|
||||
'--groups', 'testgroup2'],
|
||||
raiseonerr=False)
|
||||
assert result.returncode != 0
|
||||
448
ipatests/test_integration/test_testconfig.py
Normal file
448
ipatests/test_integration/test_testconfig.py
Normal file
@@ -0,0 +1,448 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import copy
|
||||
|
||||
from ipatests.pytest_plugins.integration import config
|
||||
from ipapython.ipautil import write_tmp_file
|
||||
from ipatests.util import assert_deepequal
|
||||
from ipalib.constants import MAX_DOMAIN_LEVEL
|
||||
|
||||
DEFAULT_OUTPUT_DICT = {
|
||||
"nis_domain": "ipatest",
|
||||
"test_dir": "/root/ipatests",
|
||||
"ad_admin_name": "Administrator",
|
||||
"ipv6": False,
|
||||
"ssh_key_filename": "~/.ssh/id_rsa",
|
||||
"ssh_username": "root",
|
||||
"admin_name": "admin",
|
||||
"ad_admin_password": "Secret123",
|
||||
"ssh_password": None,
|
||||
"dns_forwarder": "8.8.8.8",
|
||||
"domains": [],
|
||||
"dirman_dn": "cn=Directory Manager",
|
||||
"dirman_password": "Secret123",
|
||||
"ntp_server": "ntp.clock.test",
|
||||
"admin_password": "Secret123",
|
||||
"domain_level": MAX_DOMAIN_LEVEL,
|
||||
"log_journal_since": "-1h",
|
||||
}
|
||||
|
||||
DEFAULT_OUTPUT_ENV = {
|
||||
"IPATEST_DIR": "/root/ipatests",
|
||||
"IPA_ROOT_SSH_KEY": "~/.ssh/id_rsa",
|
||||
"IPA_ROOT_SSH_PASSWORD": "",
|
||||
"ADMINID": "admin",
|
||||
"ADMINPW": "Secret123",
|
||||
"ROOTDN": "cn=Directory Manager",
|
||||
"ROOTDNPWD": "Secret123",
|
||||
"DNSFORWARD": "8.8.8.8",
|
||||
"NISDOMAIN": "ipatest",
|
||||
"NTPSERVER": "ntp.clock.test",
|
||||
"ADADMINID": "Administrator",
|
||||
"ADADMINPW": "Secret123",
|
||||
"IPv6SETUP": "",
|
||||
"IPADEBUG": "",
|
||||
"DOMAINLVL": str(MAX_DOMAIN_LEVEL),
|
||||
"LOG_JOURNAL_SINCE": "-1h",
|
||||
}
|
||||
|
||||
DEFAULT_INPUT_ENV = {
|
||||
'NTPSERVER': 'ntp.clock.test',
|
||||
}
|
||||
|
||||
DEFAULT_INPUT_DICT = {
|
||||
'ntp_server': 'ntp.clock.test',
|
||||
'domains': [],
|
||||
}
|
||||
|
||||
|
||||
def extend_dict(defaults, *others, **kwargs):
|
||||
result = dict(defaults)
|
||||
for other in others:
|
||||
result.update(other)
|
||||
result.update(kwargs)
|
||||
return copy.deepcopy(result)
|
||||
|
||||
|
||||
class CheckConfig(object):
|
||||
def check_config(self, conf):
|
||||
pass
|
||||
|
||||
def get_input_env(self):
|
||||
return extend_dict(DEFAULT_INPUT_ENV, self.extra_input_env)
|
||||
|
||||
def get_output_env(self):
|
||||
return extend_dict(DEFAULT_OUTPUT_ENV, self.extra_output_env)
|
||||
|
||||
def get_input_dict(self):
|
||||
return extend_dict(DEFAULT_INPUT_DICT, self.extra_input_dict)
|
||||
|
||||
def get_output_dict(self):
|
||||
return extend_dict(DEFAULT_OUTPUT_DICT, self.extra_output_dict)
|
||||
|
||||
def test_env_to_dict(self):
|
||||
conf = config.Config.from_env(self.get_input_env())
|
||||
assert_deepequal(self.get_output_dict(), conf.to_dict())
|
||||
self.check_config(conf)
|
||||
|
||||
def test_env_to_env(self):
|
||||
conf = config.Config.from_env(self.get_input_env())
|
||||
assert_deepequal(self.get_output_env(), dict(conf.to_env()))
|
||||
self.check_config(conf)
|
||||
|
||||
def test_dict_to_env(self):
|
||||
conf = config.Config.from_dict(self.get_input_dict())
|
||||
assert_deepequal(self.get_output_env(), dict(conf.to_env()))
|
||||
self.check_config(conf)
|
||||
|
||||
def test_dict_to_dict(self):
|
||||
conf = config.Config.from_dict(self.get_input_dict())
|
||||
assert_deepequal(self.get_output_dict(), conf.to_dict())
|
||||
self.check_config(conf)
|
||||
|
||||
def test_env_roundtrip(self):
|
||||
conf = config.Config.from_env(self.get_output_env())
|
||||
assert_deepequal(self.get_output_env(), dict(conf.to_env()))
|
||||
self.check_config(conf)
|
||||
|
||||
def test_dict_roundtrip(self):
|
||||
conf = config.Config.from_dict(self.get_output_dict())
|
||||
assert_deepequal(self.get_output_dict(), conf.to_dict())
|
||||
self.check_config(conf)
|
||||
|
||||
def test_from_json_file(self):
|
||||
file = write_tmp_file(json.dumps(self.get_input_dict()))
|
||||
conf = config.Config.from_env({'IPATEST_JSON_CONFIG': file.name})
|
||||
assert_deepequal(self.get_output_dict(), conf.to_dict())
|
||||
self.check_config(conf)
|
||||
|
||||
# Settings to override:
|
||||
extra_input_dict = {}
|
||||
extra_input_env = {}
|
||||
extra_output_dict = {}
|
||||
extra_output_env = {}
|
||||
|
||||
|
||||
class TestEmptyConfig(CheckConfig):
|
||||
extra_input_dict = {}
|
||||
extra_input_env = {}
|
||||
extra_output_dict = {}
|
||||
extra_output_env = {}
|
||||
|
||||
|
||||
class TestMinimalConfig(CheckConfig):
|
||||
extra_input_dict = dict(
|
||||
domains=[
|
||||
dict(name='ipadomain.test', type='IPA', hosts=[
|
||||
dict(name='master', ip='192.0.2.1'),
|
||||
]),
|
||||
],
|
||||
)
|
||||
extra_input_env = dict(
|
||||
MASTER='master.ipadomain.test',
|
||||
BEAKERMASTER1_IP_env1='192.0.2.1',
|
||||
)
|
||||
extra_output_dict = dict(
|
||||
domains=[
|
||||
dict(
|
||||
type="IPA",
|
||||
name="ipadomain.test",
|
||||
hosts=[
|
||||
dict(
|
||||
name='master.ipadomain.test',
|
||||
ip="192.0.2.1",
|
||||
external_hostname="master.ipadomain.test",
|
||||
role="master",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
extra_output_env = dict(
|
||||
DOMAIN_env1="ipadomain.test",
|
||||
RELM_env1="IPADOMAIN.TEST",
|
||||
BASEDN_env1="dc=ipadomain,dc=test",
|
||||
MASTER_env1="master.ipadomain.test",
|
||||
BEAKERMASTER_env1="master.ipadomain.test",
|
||||
BEAKERMASTER_IP_env1="192.0.2.1",
|
||||
MASTER1_env1="master.ipadomain.test",
|
||||
BEAKERMASTER1_env1="master.ipadomain.test",
|
||||
BEAKERMASTER1_IP_env1="192.0.2.1",
|
||||
MASTER="master.ipadomain.test",
|
||||
BEAKERMASTER="master.ipadomain.test",
|
||||
MASTERIP="192.0.2.1",
|
||||
)
|
||||
|
||||
def check_config(self, conf):
|
||||
assert len(conf.domains) == 1
|
||||
assert conf.domains[0].name == 'ipadomain.test'
|
||||
assert conf.domains[0].type == 'IPA'
|
||||
assert len(conf.domains[0].hosts) == 1
|
||||
|
||||
master = conf.domains[0].master
|
||||
assert master == conf.domains[0].hosts[0]
|
||||
assert master.hostname == 'master.ipadomain.test'
|
||||
assert master.role == 'master'
|
||||
|
||||
assert conf.domains[0].replicas == []
|
||||
assert conf.domains[0].clients == []
|
||||
assert conf.domains[0].hosts_by_role('replica') == []
|
||||
assert conf.domains[0].host_by_role('master') == master
|
||||
|
||||
|
||||
class TestComplexConfig(CheckConfig):
|
||||
extra_input_dict = dict(
|
||||
domains=[
|
||||
dict(name='ipadomain.test', type='IPA', hosts=[
|
||||
dict(name='master', ip='192.0.2.1', role='master'),
|
||||
dict(name='replica1', ip='192.0.2.2', role='replica'),
|
||||
dict(name='replica2', ip='192.0.2.3', role='replica',
|
||||
external_hostname='r2.ipadomain.test'),
|
||||
dict(name='client1', ip='192.0.2.4', role='client'),
|
||||
dict(name='client2', ip='192.0.2.5', role='client',
|
||||
external_hostname='c2.ipadomain.test'),
|
||||
dict(name='extra', ip='192.0.2.6', role='extrarole'),
|
||||
dict(name='extram1', ip='192.0.2.7', role='extrarolem'),
|
||||
dict(name='extram2', ip='192.0.2.8', role='extrarolem',
|
||||
external_hostname='e2.ipadomain.test'),
|
||||
]),
|
||||
dict(name='addomain.test', type='AD', hosts=[
|
||||
dict(name='ad', ip='192.0.2.33', role='ad'),
|
||||
]),
|
||||
dict(name='ipadomain2.test', type='IPA', hosts=[
|
||||
dict(name='master.ipadomain2.test', ip='192.0.2.65'),
|
||||
]),
|
||||
],
|
||||
)
|
||||
extra_input_env = dict(
|
||||
MASTER='master.ipadomain.test',
|
||||
BEAKERMASTER1_IP_env1='192.0.2.1',
|
||||
REPLICA='replica1.ipadomain.test replica2.ipadomain.test',
|
||||
BEAKERREPLICA1_IP_env1='192.0.2.2',
|
||||
BEAKERREPLICA2_IP_env1='192.0.2.3',
|
||||
BEAKERREPLICA2_env1='r2.ipadomain.test',
|
||||
CLIENT='client1.ipadomain.test client2.ipadomain.test',
|
||||
BEAKERCLIENT1_IP_env1='192.0.2.4',
|
||||
BEAKERCLIENT2_IP_env1='192.0.2.5',
|
||||
BEAKERCLIENT2_env1='c2.ipadomain.test',
|
||||
TESTHOST_EXTRAROLE_env1='extra.ipadomain.test',
|
||||
BEAKEREXTRAROLE1_IP_env1='192.0.2.6',
|
||||
TESTHOST_EXTRAROLEM_env1='extram1.ipadomain.test extram2.ipadomain.test',
|
||||
BEAKEREXTRAROLEM1_IP_env1='192.0.2.7',
|
||||
BEAKEREXTRAROLEM2_IP_env1='192.0.2.8',
|
||||
BEAKEREXTRAROLEM2_env1='e2.ipadomain.test',
|
||||
|
||||
AD_env2='ad.addomain.test',
|
||||
BEAKERAD1_IP_env2='192.0.2.33',
|
||||
|
||||
MASTER_env3='master.ipadomain2.test',
|
||||
BEAKERMASTER1_IP_env3='192.0.2.65',
|
||||
)
|
||||
extra_output_dict = dict(
|
||||
domains=[
|
||||
dict(
|
||||
type="IPA",
|
||||
name="ipadomain.test",
|
||||
hosts=[
|
||||
dict(
|
||||
name='master.ipadomain.test',
|
||||
ip="192.0.2.1",
|
||||
external_hostname="master.ipadomain.test",
|
||||
role="master",
|
||||
),
|
||||
dict(
|
||||
name='replica1.ipadomain.test',
|
||||
ip="192.0.2.2",
|
||||
external_hostname="replica1.ipadomain.test",
|
||||
role="replica",
|
||||
),
|
||||
dict(
|
||||
name='replica2.ipadomain.test',
|
||||
ip="192.0.2.3",
|
||||
external_hostname="r2.ipadomain.test",
|
||||
role="replica",
|
||||
),
|
||||
dict(
|
||||
name='client1.ipadomain.test',
|
||||
ip="192.0.2.4",
|
||||
external_hostname="client1.ipadomain.test",
|
||||
role="client",
|
||||
),
|
||||
dict(
|
||||
name='client2.ipadomain.test',
|
||||
ip="192.0.2.5",
|
||||
external_hostname="c2.ipadomain.test",
|
||||
role="client",
|
||||
),
|
||||
dict(
|
||||
name='extra.ipadomain.test',
|
||||
ip="192.0.2.6",
|
||||
external_hostname="extra.ipadomain.test",
|
||||
role="extrarole",
|
||||
),
|
||||
dict(
|
||||
name='extram1.ipadomain.test',
|
||||
ip="192.0.2.7",
|
||||
external_hostname="extram1.ipadomain.test",
|
||||
role="extrarolem",
|
||||
),
|
||||
dict(
|
||||
name='extram2.ipadomain.test',
|
||||
ip="192.0.2.8",
|
||||
external_hostname="e2.ipadomain.test",
|
||||
role="extrarolem",
|
||||
),
|
||||
],
|
||||
),
|
||||
dict(
|
||||
type="AD",
|
||||
name="addomain.test",
|
||||
hosts=[
|
||||
dict(
|
||||
name='ad.addomain.test',
|
||||
ip="192.0.2.33",
|
||||
external_hostname="ad.addomain.test",
|
||||
role="ad",
|
||||
),
|
||||
],
|
||||
),
|
||||
dict(
|
||||
type="IPA",
|
||||
name="ipadomain2.test",
|
||||
hosts=[
|
||||
dict(
|
||||
name='master.ipadomain2.test',
|
||||
ip="192.0.2.65",
|
||||
external_hostname="master.ipadomain2.test",
|
||||
role="master",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
extra_output_env = extend_dict(extra_input_env,
|
||||
DOMAIN_env1="ipadomain.test",
|
||||
RELM_env1="IPADOMAIN.TEST",
|
||||
BASEDN_env1="dc=ipadomain,dc=test",
|
||||
|
||||
MASTER_env1="master.ipadomain.test",
|
||||
BEAKERMASTER_env1="master.ipadomain.test",
|
||||
BEAKERMASTER_IP_env1="192.0.2.1",
|
||||
MASTER="master.ipadomain.test",
|
||||
BEAKERMASTER="master.ipadomain.test",
|
||||
MASTERIP="192.0.2.1",
|
||||
MASTER1_env1="master.ipadomain.test",
|
||||
BEAKERMASTER1_env1="master.ipadomain.test",
|
||||
BEAKERMASTER1_IP_env1="192.0.2.1",
|
||||
|
||||
REPLICA_env1="replica1.ipadomain.test replica2.ipadomain.test",
|
||||
BEAKERREPLICA_env1="replica1.ipadomain.test r2.ipadomain.test",
|
||||
BEAKERREPLICA_IP_env1="192.0.2.2 192.0.2.3",
|
||||
REPLICA="replica1.ipadomain.test replica2.ipadomain.test",
|
||||
REPLICA1_env1="replica1.ipadomain.test",
|
||||
BEAKERREPLICA1_env1="replica1.ipadomain.test",
|
||||
BEAKERREPLICA1_IP_env1="192.0.2.2",
|
||||
REPLICA2_env1="replica2.ipadomain.test",
|
||||
BEAKERREPLICA2_env1="r2.ipadomain.test",
|
||||
BEAKERREPLICA2_IP_env1="192.0.2.3",
|
||||
SLAVE="replica1.ipadomain.test replica2.ipadomain.test",
|
||||
BEAKERSLAVE="replica1.ipadomain.test r2.ipadomain.test",
|
||||
SLAVEIP="192.0.2.2 192.0.2.3",
|
||||
|
||||
CLIENT_env1="client1.ipadomain.test client2.ipadomain.test",
|
||||
BEAKERCLIENT_env1="client1.ipadomain.test c2.ipadomain.test",
|
||||
BEAKERCLIENT='client1.ipadomain.test',
|
||||
BEAKERCLIENT2='c2.ipadomain.test',
|
||||
BEAKERCLIENT_IP_env1="192.0.2.4 192.0.2.5",
|
||||
CLIENT="client1.ipadomain.test",
|
||||
CLIENT2="client2.ipadomain.test",
|
||||
CLIENT1_env1="client1.ipadomain.test",
|
||||
BEAKERCLIENT1_env1="client1.ipadomain.test",
|
||||
BEAKERCLIENT1_IP_env1="192.0.2.4",
|
||||
CLIENT2_env1="client2.ipadomain.test",
|
||||
BEAKERCLIENT2_env1="c2.ipadomain.test",
|
||||
BEAKERCLIENT2_IP_env1="192.0.2.5",
|
||||
|
||||
TESTHOST_EXTRAROLE_env1="extra.ipadomain.test",
|
||||
BEAKEREXTRAROLE_env1="extra.ipadomain.test",
|
||||
BEAKEREXTRAROLE_IP_env1="192.0.2.6",
|
||||
TESTHOST_EXTRAROLE1_env1="extra.ipadomain.test",
|
||||
BEAKEREXTRAROLE1_env1="extra.ipadomain.test",
|
||||
BEAKEREXTRAROLE1_IP_env1="192.0.2.6",
|
||||
|
||||
TESTHOST_EXTRAROLEM_env1="extram1.ipadomain.test extram2.ipadomain.test",
|
||||
BEAKEREXTRAROLEM_env1="extram1.ipadomain.test e2.ipadomain.test",
|
||||
BEAKEREXTRAROLEM_IP_env1="192.0.2.7 192.0.2.8",
|
||||
TESTHOST_EXTRAROLEM1_env1="extram1.ipadomain.test",
|
||||
BEAKEREXTRAROLEM1_env1="extram1.ipadomain.test",
|
||||
BEAKEREXTRAROLEM1_IP_env1="192.0.2.7",
|
||||
TESTHOST_EXTRAROLEM2_env1="extram2.ipadomain.test",
|
||||
BEAKEREXTRAROLEM2_env1="e2.ipadomain.test",
|
||||
BEAKEREXTRAROLEM2_IP_env1="192.0.2.8",
|
||||
|
||||
DOMAIN_env2="addomain.test",
|
||||
RELM_env2="ADDOMAIN.TEST",
|
||||
BASEDN_env2="dc=addomain,dc=test",
|
||||
AD_env2="ad.addomain.test",
|
||||
BEAKERAD_env2="ad.addomain.test",
|
||||
BEAKERAD_IP_env2="192.0.2.33",
|
||||
AD1_env2="ad.addomain.test",
|
||||
BEAKERAD1_env2="ad.addomain.test",
|
||||
BEAKERAD1_IP_env2="192.0.2.33",
|
||||
|
||||
DOMAIN_env3="ipadomain2.test",
|
||||
RELM_env3="IPADOMAIN2.TEST",
|
||||
BASEDN_env3="dc=ipadomain2,dc=test",
|
||||
MASTER_env3="master.ipadomain2.test",
|
||||
BEAKERMASTER_env3="master.ipadomain2.test",
|
||||
BEAKERMASTER_IP_env3="192.0.2.65",
|
||||
MASTER1_env3="master.ipadomain2.test",
|
||||
BEAKERMASTER1_env3="master.ipadomain2.test",
|
||||
BEAKERMASTER1_IP_env3="192.0.2.65",
|
||||
)
|
||||
|
||||
def check_config(self, conf):
|
||||
assert len(conf.domains) == 3
|
||||
main_dom = conf.domains[0]
|
||||
(client1, client2, extra, extram1, extram2, _master,
|
||||
replica1, replica2) = sorted(main_dom.hosts, key=lambda h: h.role)
|
||||
assert main_dom.name == 'ipadomain.test'
|
||||
assert main_dom.type == 'IPA'
|
||||
|
||||
assert sorted(main_dom.roles) == ['client', 'extrarole', 'extrarolem',
|
||||
'master', 'replica']
|
||||
assert main_dom.static_roles == ('master', 'replica', 'client', 'other')
|
||||
assert sorted(main_dom.extra_roles) == ['extrarole', 'extrarolem']
|
||||
|
||||
assert main_dom.replicas == [replica1, replica2]
|
||||
assert main_dom.clients == [client1, client2]
|
||||
assert main_dom.hosts_by_role('replica') == [replica1, replica2]
|
||||
assert main_dom.hosts_by_role('extrarolem') == [extram1, extram2]
|
||||
assert main_dom.host_by_role('extrarole') == extra
|
||||
|
||||
assert extra.ip == '192.0.2.6'
|
||||
assert extram2.hostname == 'extram2.ipadomain.test'
|
||||
assert extram2.external_hostname == 'e2.ipadomain.test'
|
||||
|
||||
ad_dom = conf.domains[1]
|
||||
assert ad_dom.roles == ['ad']
|
||||
assert ad_dom.static_roles == ('ad',)
|
||||
assert ad_dom.extra_roles == []
|
||||
181
ipatests/test_integration/test_topologies.py
Normal file
181
ipatests/test_integration/test_topologies.py
Normal file
@@ -0,0 +1,181 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
|
||||
def test_topology_star():
|
||||
topo = tasks.get_topo('star')
|
||||
assert topo == tasks.star_topo
|
||||
assert list(topo('M', [1, 2, 3, 4, 5])) == [
|
||||
('M', 1),
|
||||
('M', 2),
|
||||
('M', 3),
|
||||
('M', 4),
|
||||
('M', 5),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
|
||||
|
||||
def test_topology_line():
|
||||
topo = tasks.get_topo('line')
|
||||
assert topo == tasks.line_topo
|
||||
assert list(topo('M', [1, 2, 3, 4, 5])) == [
|
||||
('M', 1),
|
||||
(1, 2),
|
||||
(2, 3),
|
||||
(3, 4),
|
||||
(4, 5),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
|
||||
|
||||
def test_topology_tree():
|
||||
topo = tasks.get_topo('tree')
|
||||
assert topo == tasks.tree_topo
|
||||
assert list(topo('M', [1, 2, 3, 4, 5])) == [
|
||||
('M', 1),
|
||||
('M', 2),
|
||||
(1, 3),
|
||||
(1, 4),
|
||||
(2, 5),
|
||||
]
|
||||
assert list(topo('M', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) == [
|
||||
('M', 1),
|
||||
('M', 2),
|
||||
(1, 3),
|
||||
(1, 4),
|
||||
(2, 5),
|
||||
(2, 6),
|
||||
(3, 7),
|
||||
(3, 8),
|
||||
(4, 9),
|
||||
(4, 10),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
|
||||
|
||||
def test_topology_tree2():
|
||||
topo = tasks.get_topo('tree2')
|
||||
assert topo == tasks.tree2_topo
|
||||
assert list(topo('M', [1, 2, 3, 4, 5])) == [
|
||||
('M', 1),
|
||||
('M', 2),
|
||||
(2, 3),
|
||||
(3, 4),
|
||||
(4, 5),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
|
||||
|
||||
def test_topology_complete():
|
||||
topo = tasks.get_topo('complete')
|
||||
assert topo == tasks.complete_topo
|
||||
assert list(topo('M', [1, 2, 3])) == [
|
||||
('M', 1),
|
||||
('M', 2),
|
||||
('M', 3),
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(2, 3),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
|
||||
|
||||
def test_topology_two_connected():
|
||||
topo = tasks.get_topo('2-connected')
|
||||
assert topo == tasks.two_connected_topo
|
||||
assert list(topo('M', [1, 2, 3, 4, 5, 6, 7, 8])) == [
|
||||
('M', 1),
|
||||
('M', 2),
|
||||
(2, 3),
|
||||
(1, 3),
|
||||
('M', 4),
|
||||
('M', 5),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(2, 4),
|
||||
(2, 7),
|
||||
(4, 8),
|
||||
(7, 8),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
|
||||
|
||||
def test_topology_double_circle_topo():
|
||||
topo = tasks.get_topo('double-circle')
|
||||
assert topo == tasks.double_circle_topo
|
||||
assert list(topo('M', list(range(1, 30)))) == [
|
||||
('M', 1),
|
||||
(1, 6),
|
||||
(1, 12),
|
||||
(6, 7),
|
||||
(7, 12),
|
||||
(7, 18),
|
||||
(12, 13),
|
||||
(13, 18),
|
||||
(13, 24),
|
||||
(18, 19),
|
||||
(19, 24),
|
||||
(19, 'M'),
|
||||
(24, 25),
|
||||
(25, 'M'),
|
||||
(25, 6),
|
||||
('M', 2),
|
||||
(2, 3),
|
||||
(2, 4),
|
||||
(2, 5),
|
||||
(3, 4),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(1, 5),
|
||||
(6, 8),
|
||||
(8, 9),
|
||||
(8, 10),
|
||||
(8, 11),
|
||||
(9, 10),
|
||||
(9, 11),
|
||||
(10, 11),
|
||||
(7, 11),
|
||||
(12, 14),
|
||||
(14, 15),
|
||||
(14, 16),
|
||||
(14, 17),
|
||||
(15, 16),
|
||||
(15, 17),
|
||||
(16, 17),
|
||||
(13, 17),
|
||||
(18, 20),
|
||||
(20, 21),
|
||||
(20, 22),
|
||||
(20, 23),
|
||||
(21, 22),
|
||||
(21, 23),
|
||||
(22, 23),
|
||||
(19, 23),
|
||||
(24, 26),
|
||||
(26, 27),
|
||||
(26, 28),
|
||||
(26, 29),
|
||||
(27, 28),
|
||||
(27, 29),
|
||||
(28, 29),
|
||||
(25, 29),
|
||||
]
|
||||
assert list(topo('M', [])) == []
|
||||
338
ipatests/test_integration/test_topology.py
Normal file
338
ipatests/test_integration/test_topology.py
Normal file
@@ -0,0 +1,338 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipatests.pytest_plugins.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"
|
||||
|
||||
|
||||
def find_segment(master, replica):
|
||||
result = master.run_command(['ipa', 'topologysegment-find',
|
||||
DOMAIN_SUFFIX_NAME]).stdout_text
|
||||
segment_re = re.compile('Left node: (?P<left>\S+)\n.*Right node: '
|
||||
'(?P<right>\S+)\n')
|
||||
allsegments = segment_re.findall(result)
|
||||
for segment in allsegments:
|
||||
if master.hostname in segment and replica.hostname in segment:
|
||||
return '-to-'.join(segment)
|
||||
|
||||
|
||||
def remove_segment(master, host1, host2):
|
||||
"""
|
||||
This removes a segment between host1 and host2 on master. The function is
|
||||
needed because test_add_remove_segment expects only one segment, but due to
|
||||
track tickete N 6250, the test_topology_updated_on_replica_install_remove
|
||||
leaves 2 topology segments
|
||||
"""
|
||||
def wrapper(func):
|
||||
def wrapped(*args, **kwargs):
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
finally:
|
||||
segment = find_segment(host1, host2)
|
||||
master.run_command(['ipa', 'topologysegment-del',
|
||||
DOMAIN_SUFFIX_NAME, segment],
|
||||
raiseonerr=False)
|
||||
return wrapped
|
||||
return wrapper
|
||||
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == 0, reason=reasoning)
|
||||
class TestTopologyOptions(IntegrationTest):
|
||||
num_replicas = 2
|
||||
topology = 'star'
|
||||
rawsegment_re = ('Segment name: (?P<name>.*?)',
|
||||
'\s+Left node: (?P<lnode>.*?)',
|
||||
'\s+Right node: (?P<rnode>.*?)',
|
||||
'\s+Connectivity: (?P<connectivity>\S+)')
|
||||
segment_re = re.compile("\n".join(rawsegment_re))
|
||||
noentries_re = re.compile("Number of entries returned (\d+)")
|
||||
segmentnames_re = re.compile('.*Segment name: (\S+?)\n.*')
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_topo(cls.topology, cls.master,
|
||||
cls.replicas[:-1],
|
||||
cls.clients)
|
||||
|
||||
def tokenize_topologies(self, command_output):
|
||||
"""
|
||||
takes an output of `ipa topologysegment-find` and returns an array of
|
||||
segment hashes
|
||||
"""
|
||||
segments = command_output.split("-----------------")[2]
|
||||
raw_segments = segments.split('\n\n')
|
||||
result = []
|
||||
for i in raw_segments:
|
||||
matched = self.segment_re.search(i)
|
||||
if matched:
|
||||
result.append({'leftnode': matched.group('lnode'),
|
||||
'rightnode': matched.group('rnode'),
|
||||
'name': matched.group('name'),
|
||||
'connectivity': matched.group('connectivity')
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@pytest.mark.xfail(reason="Trac 6250", strict=True)
|
||||
@remove_segment(config.domains[0].master,
|
||||
config.domains[0].master,
|
||||
config.domains[0].replicas[1])
|
||||
def test_topology_updated_on_replica_install_remove(self):
|
||||
"""
|
||||
Install and remove a replica and make sure topology information is
|
||||
updated on all other replicas
|
||||
Testcase: http://www.freeipa.org/page/V4/Manage_replication_topology/
|
||||
Test_plan#Test_case:
|
||||
_Replication_topology_should_be_saved_in_the_LDAP_tree
|
||||
"""
|
||||
tasks.kinit_admin(self.master)
|
||||
result1 = self.master.run_command(['ipa', 'topologysegment-find',
|
||||
DOMAIN_SUFFIX_NAME]).stdout_text
|
||||
segment_name = self.segmentnames_re.findall(result1)[0]
|
||||
assert(self.master.hostname in segment_name), (
|
||||
"Segment %s does not contain master hostname" % segment_name)
|
||||
assert(self.replicas[0].hostname in segment_name), (
|
||||
"Segment %s does not contain replica hostname" % segment_name)
|
||||
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
|
||||
# replicas
|
||||
result2 = self.master.run_command(['ipa', 'topologysegment-find',
|
||||
DOMAIN_SUFFIX_NAME])
|
||||
result3 = self.replicas[0].run_command(['ipa', 'topologysegment-find',
|
||||
DOMAIN_SUFFIX_NAME])
|
||||
result4 = self.replicas[1].run_command(['ipa', 'topologysegment-find',
|
||||
DOMAIN_SUFFIX_NAME])
|
||||
segments = self.tokenize_topologies(result2.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])
|
||||
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):
|
||||
"""
|
||||
Make sure a topology segment can be manually created and deleted
|
||||
with the influence on the real topology
|
||||
Testcase http://www.freeipa.org/page/V4/Manage_replication_topology/
|
||||
Test_plan#Test_case:_Basic_CRUD_test
|
||||
"""
|
||||
tasks.kinit_admin(self.master)
|
||||
# Install the second replica
|
||||
tasks.install_replica(self.master, self.replicas[1], setup_ca=False,
|
||||
setup_dns=False)
|
||||
# turn a star into a ring
|
||||
segment, err = tasks.create_segment(self.master,
|
||||
self.replicas[0],
|
||||
self.replicas[1])
|
||||
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]).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
|
||||
# Since segment name can be one of master-to-replica2 or
|
||||
# replica2-to-master, we need to determine the segment name dynamically
|
||||
|
||||
deleteme = find_segment(self.master, self.replicas[1])
|
||||
returncode, error = tasks.destroy_segment(self.master, deleteme)
|
||||
assert returncode == 0, error
|
||||
# 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]).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'])
|
||||
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):
|
||||
"""
|
||||
Testcase: http://www.freeipa.org/page/V4/Manage_replication_topology/
|
||||
Test_plan#Test_case:
|
||||
_Removal_of_a_topology_segment_is_allowed_only_if_there_is_at_least_one_more_segment_connecting_the_given_replica
|
||||
"""
|
||||
text = "Removal of Segment disconnects topology"
|
||||
error1 = "The system should not have let you remove the segment"
|
||||
error2 = "Wrong error message thrown during segment removal: \"%s\""
|
||||
replicas = (self.replicas[0].hostname, self.replicas[1].hostname)
|
||||
|
||||
returncode, error = tasks.destroy_segment(self.master, "%s-to-%s" % replicas)
|
||||
assert returncode != 0, error1
|
||||
assert error.count(text) == 1, error2 % error
|
||||
_newseg, err = tasks.create_segment(
|
||||
self.master, self.master, self.replicas[1])
|
||||
assert err == "", err
|
||||
returncode, error = tasks.destroy_segment(self.master, "%s-to-%s" % replicas)
|
||||
assert returncode == 0, error
|
||||
|
||||
|
||||
@pytest.mark.skipif(config.domain_level == 0, reason=reasoning)
|
||||
class TestCASpecificRUVs(IntegrationTest):
|
||||
num_replicas = 2
|
||||
topology = 'star'
|
||||
username = 'testuser'
|
||||
user_firstname = 'test'
|
||||
user_lastname = 'user'
|
||||
|
||||
def test_delete_ruvs(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Manage_replication_topology_4_4/
|
||||
Test_Plan#Test_case:_clean-ruv_subcommand
|
||||
"""
|
||||
replica = self.replicas[0]
|
||||
master = self.master
|
||||
res1 = master.run_command(['ipa-replica-manage', 'list-ruv', '-p',
|
||||
master.config.dirman_password])
|
||||
assert(res1.stdout_text.count(replica.hostname) == 2 and
|
||||
"Certificate Server Replica"
|
||||
" Update Vectors" in res1.stdout_text), (
|
||||
"CA-specific RUVs are not displayed")
|
||||
ruvid_re = re.compile(".*%s:389: (\d+).*" % replica.hostname)
|
||||
replica_ruvs = ruvid_re.findall(res1.stdout_text)
|
||||
# Find out the number of RUVids
|
||||
assert(len(replica_ruvs) == 2), (
|
||||
"The output should display 2 RUV ids of the selected replica")
|
||||
|
||||
# Block replication to preserve replica-specific RUVs
|
||||
dashed_domain = master.domain.realm.replace(".", '-')
|
||||
dirsrv_service = "dirsrv@%s.service" % dashed_domain
|
||||
replica.run_command(['systemctl', 'stop', dirsrv_service])
|
||||
try:
|
||||
master.run_command(['ipa-replica-manage', 'clean-ruv',
|
||||
replica_ruvs[1], '-p',
|
||||
master.config.dirman_password, '-f'])
|
||||
res2 = master.run_command(['ipa-replica-manage',
|
||||
'list-ruv', '-p',
|
||||
master.config.dirman_password])
|
||||
|
||||
assert(res2.stdout_text.count(replica.hostname) == 1), (
|
||||
"CA RUV of the replica is still displayed")
|
||||
master.run_command(['ipa-replica-manage', 'clean-ruv',
|
||||
replica_ruvs[0], '-p',
|
||||
master.config.dirman_password, '-f'])
|
||||
res3 = master.run_command(['ipa-replica-manage', 'list-ruv', '-p',
|
||||
master.config.dirman_password])
|
||||
assert(replica.hostname not in res3.stdout_text), (
|
||||
"replica's RUV is still displayed")
|
||||
finally:
|
||||
replica.run_command(['systemctl', 'start', dirsrv_service])
|
||||
|
||||
def test_replica_uninstall_deletes_ruvs(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Manage_replication_topology_4_4/Test_Plan
|
||||
#Test_case:_.2A-ruv_subcommands_of_ipa-replica-manage_are_extended
|
||||
_to_handle_CA-specific_RUVs
|
||||
"""
|
||||
master = self.master
|
||||
replica = self.replicas[1]
|
||||
res1 = master.run_command(['ipa-replica-manage', 'list-ruv', '-p',
|
||||
master.config.dirman_password]).stdout_text
|
||||
assert(res1.count(replica.hostname) == 2), (
|
||||
"Did not find proper number of replica hostname (%s) occurrencies"
|
||||
" in the command output: %s" % (replica.hostname, res1))
|
||||
tasks.uninstall_master(replica)
|
||||
res2 = master.run_command(['ipa-replica-manage', 'list-ruv', '-p',
|
||||
master.config.dirman_password]).stdout_text
|
||||
assert(replica.hostname not in res2), (
|
||||
"Replica RUVs were not clean during replica uninstallation")
|
||||
|
||||
|
||||
class TestReplicaManageDel(IntegrationTest):
|
||||
domain_level = 0
|
||||
topology = 'star'
|
||||
num_replicas = 3
|
||||
|
||||
def test_replica_managed_del_domlevel0(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Manage_replication_topology_4_4/
|
||||
Test_Plan#Test_case:_ipa-replica-manage_del_with_turned_off_replica
|
||||
_under_domain_level_0_keeps_ca-related_RUVs
|
||||
"""
|
||||
master = self.master
|
||||
replica = self.replicas[0]
|
||||
replica.run_command(['ipactl', 'stop'])
|
||||
master.run_command(['ipa-replica-manage', 'del', '-f', '-p',
|
||||
master.config.dirman_password, replica.hostname])
|
||||
result = master.run_command(['ipa-replica-manage', 'list-ruv',
|
||||
'-p', master.config.dirman_password])
|
||||
num_ruvs = result.stdout_text.count(replica.hostname)
|
||||
assert(num_ruvs == 1), ("Expected to find 1 replica's RUV, found %s" %
|
||||
num_ruvs)
|
||||
ruvid_re = re.compile(".*%s:389: (\d+).*" % replica.hostname)
|
||||
replica_ruvs = ruvid_re.findall(result.stdout_text)
|
||||
master.run_command(['ipa-replica-manage', 'clean-ruv', '-f',
|
||||
'-p', master.config.dirman_password,
|
||||
replica_ruvs[0]])
|
||||
result2 = master.run_command(['ipa-replica-manage', 'list-ruv',
|
||||
'-p', master.config.dirman_password])
|
||||
assert(replica.hostname not in result2.stdout_text), (
|
||||
"Replica's RUV was not properly removed")
|
||||
|
||||
def test_clean_dangling_ruv_multi_ca(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Manage_replication_topology_4_4/
|
||||
Test_Plan#Test_case:_ipa-replica-manage_clean-dangling-ruv_in_a
|
||||
_multi-CA_setup
|
||||
"""
|
||||
master = self.master
|
||||
replica = self.replicas[1]
|
||||
replica.run_command(['ipa-server-install', '--uninstall', '-U'])
|
||||
master.run_command(['ipa-replica-manage', 'del', '-f', '-p',
|
||||
master.config.dirman_password, replica.hostname])
|
||||
result1 = master.run_command(['ipa-replica-manage', 'list-ruv', '-p',
|
||||
master.config.dirman_password])
|
||||
ruvid_re = re.compile(".*%s:389: (\d+).*" % replica.hostname)
|
||||
assert(ruvid_re.search(result1.stdout_text)), (
|
||||
"Replica's RUV should not be removed under domain level 0")
|
||||
master.run_command(['ipa-replica-manage', 'clean-dangling-ruv', '-p',
|
||||
master.config.dirman_password], stdin_text="yes\n")
|
||||
result2 = master.run_command(['ipa-replica-manage', 'list-ruv', '-p',
|
||||
master.config.dirman_password])
|
||||
assert(replica.hostname not in result2.stdout_text), (
|
||||
"Replica's RUV was not removed by a clean-dangling-ruv command")
|
||||
|
||||
def test_replica_managed_del_domlevel1(self):
|
||||
"""
|
||||
http://www.freeipa.org/page/V4/Manage_replication_topology_4_4/
|
||||
Test_Plan#Test_case:_ipa-replica-manage_del_with_turned_off_replica
|
||||
_under_domain_level_1_removes_ca-related_RUVs
|
||||
"""
|
||||
master = self.master
|
||||
replica = self.replicas[2]
|
||||
master.run_command(['ipa', 'domainlevel-set', '1'])
|
||||
replica.run_command(['ipactl', 'stop'])
|
||||
master.run_command(['ipa-replica-manage', 'del', '-f', '-p',
|
||||
master.config.dirman_password, replica.hostname])
|
||||
result = master.run_command(['ipa-replica-manage', 'list-ruv',
|
||||
'-p', master.config.dirman_password])
|
||||
assert(replica.hostname not in result.stdout_text), (
|
||||
"Replica's RUV was not properly removed")
|
||||
491
ipatests/test_integration/test_trust.py
Normal file
491
ipatests/test_integration/test_trust.py
Normal file
@@ -0,0 +1,491 @@
|
||||
# Authors:
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import nose
|
||||
import re
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
|
||||
class ADTrustBase(IntegrationTest):
|
||||
"""Provides common checks for the AD trust integration testing."""
|
||||
|
||||
topology = 'line'
|
||||
num_ad_domains = 1
|
||||
optional_extra_roles = ['ad_subdomain', 'ad_treedomain']
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
if not cls.master.transport.file_exists('/usr/bin/rpcclient'):
|
||||
raise nose.SkipTest("Package samba-client not available "
|
||||
"on {}".format(cls.master.hostname))
|
||||
super(ADTrustBase, cls).install(mh)
|
||||
cls.ad = cls.ad_domains[0].ads[0]
|
||||
cls.ad_domain = cls.ad.domain.name
|
||||
cls.install_adtrust()
|
||||
cls.check_sid_generation()
|
||||
|
||||
# Determine whether the subdomain AD is available
|
||||
try:
|
||||
cls.child_ad = cls.host_by_role(cls.optional_extra_roles[0])
|
||||
cls.ad_subdomain = '.'.join(
|
||||
cls.child_ad.hostname.split('.')[1:])
|
||||
except LookupError:
|
||||
cls.ad_subdomain = None
|
||||
|
||||
# Determine whether the tree domain AD is available
|
||||
try:
|
||||
cls.tree_ad = cls.host_by_role(cls.optional_extra_roles[1])
|
||||
cls.ad_treedomain = '.'.join(
|
||||
cls.tree_ad.hostname.split('.')[1:])
|
||||
except LookupError:
|
||||
cls.ad_treedomain = None
|
||||
|
||||
cls.configure_dns_and_time()
|
||||
|
||||
@classmethod
|
||||
def install_adtrust(cls):
|
||||
"""Test adtrust support installation"""
|
||||
|
||||
tasks.install_adtrust(cls.master)
|
||||
|
||||
@classmethod
|
||||
def check_sid_generation(cls):
|
||||
"""Test SID generation"""
|
||||
|
||||
command = ['ipa', 'user-show', 'admin', '--all', '--raw']
|
||||
|
||||
# TODO: remove duplicate definition and import from common module
|
||||
_sid_identifier_authority = '(0x[0-9a-f]{1,12}|[0-9]{1,10})'
|
||||
sid_regex = 'S-1-5-21-%(idauth)s-%(idauth)s-%(idauth)s'\
|
||||
% dict(idauth=_sid_identifier_authority)
|
||||
stdout_re = re.escape(' ipaNTSecurityIdentifier: ') + sid_regex
|
||||
|
||||
tasks.run_repeatedly(cls.master, command,
|
||||
test=lambda x: re.search(stdout_re, x))
|
||||
|
||||
@classmethod
|
||||
def configure_dns_and_time(cls):
|
||||
tasks.configure_dns_for_trust(cls.master, cls.ad_domain)
|
||||
tasks.sync_time(cls.master, cls.ad)
|
||||
|
||||
def test_establish_trust(self):
|
||||
"""Tests establishing trust with Active Directory"""
|
||||
|
||||
tasks.establish_trust_with_ad(self.master, self.ad_domain,
|
||||
extra_args=['--range-type', 'ipa-ad-trust'])
|
||||
|
||||
def test_all_trustdomains_found(self):
|
||||
"""
|
||||
Tests that all trustdomains can be found.
|
||||
"""
|
||||
|
||||
if self.ad_subdomain is None:
|
||||
raise nose.SkipTest('AD subdomain is not available.')
|
||||
|
||||
result = self.master.run_command(['ipa',
|
||||
'trustdomain-find',
|
||||
self.ad_domain])
|
||||
|
||||
# Check that all trustdomains appear in the result
|
||||
assert self.ad_domain in result.stdout_text
|
||||
assert self.ad_subdomain in result.stdout_text
|
||||
assert self.ad_treedomain in result.stdout_text
|
||||
|
||||
|
||||
class ADTrustSubdomainBase(ADTrustBase):
|
||||
"""
|
||||
Base class for tests involving subdomains of trusted forests
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def configure_dns_and_time(cls):
|
||||
tasks.configure_dns_for_trust(cls.master, cls.ad_subdomain)
|
||||
tasks.sync_time(cls.master, cls.child_ad)
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(ADTrustSubdomainBase, cls).install(mh)
|
||||
if not cls.ad_subdomain:
|
||||
raise nose.SkipTest('AD subdomain is not available.')
|
||||
|
||||
|
||||
class ADTrustTreedomainBase(ADTrustBase):
|
||||
"""
|
||||
Base class for tests involving tree root domains of trusted forests
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def configure_dns_and_time(cls):
|
||||
tasks.configure_dns_for_trust(cls.master, cls.ad_treedomain)
|
||||
tasks.sync_time(cls.master, cls.tree_ad)
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(ADTrustTreedomainBase, cls).install(mh)
|
||||
if not cls.ad_treedomain:
|
||||
raise nose.SkipTest('AD tree root domain is not available.')
|
||||
|
||||
|
||||
class TestBasicADTrust(ADTrustBase):
|
||||
"""Basic Integration test for Active Directory"""
|
||||
|
||||
def test_range_properties_in_nonposix_trust(self):
|
||||
"""Check the properties of the created range"""
|
||||
|
||||
range_name = self.ad_domain.upper() + '_id_range'
|
||||
result = self.master.run_command(['ipa', 'idrange-show', range_name,
|
||||
'--all', '--raw'])
|
||||
|
||||
iparangetype_regex = r'ipaRangeType: ipa-ad-trust'
|
||||
iparangesize_regex = r'ipaIDRangeSize: 200000'
|
||||
|
||||
assert re.search(iparangetype_regex, result.stdout_text, re.IGNORECASE)
|
||||
assert re.search(iparangesize_regex, result.stdout_text, re.IGNORECASE)
|
||||
|
||||
def test_user_gid_uid_resolution_in_nonposix_trust(self):
|
||||
"""Check that user has SID-generated UID"""
|
||||
|
||||
# Using domain name since it is lowercased realm name for AD domains
|
||||
testuser = 'testuser@%s' % self.ad_domain
|
||||
result = self.master.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
# This regex checks that Test User does not have UID 10042 nor belongs
|
||||
# to the group with GID 10047
|
||||
testuser_regex = "^testuser@%s:\*:(?!10042)(\d+):(?!10047)(\d+):"\
|
||||
"Test User:/home/%s/testuser:/bin/sh$"\
|
||||
% (re.escape(self.ad_domain),
|
||||
re.escape(self.ad_domain))
|
||||
|
||||
assert re.search(testuser_regex, result.stdout_text)
|
||||
|
||||
def test_ipauser_authentication(self):
|
||||
ipauser = u'tuser'
|
||||
original_passwd = 'Secret123'
|
||||
new_passwd = 'userPasswd123'
|
||||
|
||||
# create an ipauser for this test
|
||||
self.master.run_command(['ipa', 'user-add', ipauser, '--first=Test',
|
||||
'--last=User', '--password'],
|
||||
stdin_text=original_passwd)
|
||||
|
||||
# change password for the user to be able to kinit
|
||||
tasks.ldappasswd_user_change(ipauser, original_passwd, new_passwd,
|
||||
self.master)
|
||||
|
||||
# try to kinit as ipauser
|
||||
self.master.run_command(
|
||||
['kinit', '-E', '{0}@{1}'.format(ipauser,
|
||||
self.master.domain.name)],
|
||||
stdin_text=new_passwd)
|
||||
|
||||
def test_remove_nonposix_trust(self):
|
||||
tasks.remove_trust_with_ad(self.master, self.ad_domain)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
class TestPosixADTrust(ADTrustBase):
|
||||
"""Integration test for Active Directory with POSIX support"""
|
||||
|
||||
def test_establish_trust(self):
|
||||
# Not specifying the --range-type directly, it should be detected
|
||||
tasks.establish_trust_with_ad(self.master, self.ad_domain)
|
||||
|
||||
def test_range_properties_in_posix_trust(self):
|
||||
# Check the properties of the created range
|
||||
|
||||
range_name = self.ad_domain.upper() + '_id_range'
|
||||
|
||||
result = self.master.run_command(['ipa', 'idrange-show', range_name,
|
||||
'--all', '--raw'])
|
||||
|
||||
# Check the range type and size
|
||||
iparangetype_regex = r'ipaRangeType: ipa-ad-trust-posix'
|
||||
iparangesize_regex = r'ipaIDRangeSize: 200000'
|
||||
|
||||
assert re.search(iparangetype_regex, result.stdout_text, re.IGNORECASE)
|
||||
assert re.search(iparangesize_regex, result.stdout_text, re.IGNORECASE)
|
||||
|
||||
def test_user_uid_gid_resolution_in_posix_trust(self):
|
||||
# Check that user has AD-defined UID
|
||||
|
||||
# Using domain name since it is lowercased realm name for AD domains
|
||||
testuser = 'testuser@%s' % self.ad_domain
|
||||
result = self.master.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
testuser_stdout = "testuser@%s:*:10042:10047:"\
|
||||
"Test User:/home/%s/testuser:/bin/sh"\
|
||||
% (self.ad_domain, self.ad_domain)
|
||||
|
||||
assert testuser_stdout in result.stdout_text
|
||||
|
||||
def test_user_without_posix_attributes_not_visible(self):
|
||||
# Check that user has AD-defined UID
|
||||
|
||||
# Using domain name since it is lowercased realm name for AD domains
|
||||
nonposixuser = 'nonposixuser@%s' % self.ad_domain
|
||||
result = self.master.run_command(['getent', 'passwd', nonposixuser],
|
||||
raiseonerr=False)
|
||||
|
||||
# Getent exits with 2 for non-existent user
|
||||
assert result.returncode == 2
|
||||
|
||||
def test_remove_trust_with_posix_attributes(self):
|
||||
tasks.remove_trust_with_ad(self.master, self.ad_domain)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
class TestEnforcedPosixADTrust(TestPosixADTrust):
|
||||
"""
|
||||
This test is intented to copycat PosixADTrust, since enforcing the POSIX
|
||||
trust type should not make a difference.
|
||||
"""
|
||||
|
||||
def test_establish_trust_with_posix_attributes(self):
|
||||
tasks.establish_trust_with_ad(self.master, self.ad_domain,
|
||||
extra_args=['--range-type', 'ipa-ad-trust-posix'])
|
||||
|
||||
|
||||
class TestInvalidRangeTypes(ADTrustBase):
|
||||
"""
|
||||
Tests invalid values being put into trust-add command.
|
||||
"""
|
||||
|
||||
def test_invalid_range_types(self):
|
||||
|
||||
invalid_range_types = ['ipa-local',
|
||||
'ipa-ad-winsync',
|
||||
'ipa-ipa-trust',
|
||||
'random-invalid',
|
||||
're@ll%ybad12!']
|
||||
|
||||
for range_type in invalid_range_types:
|
||||
tasks.kinit_admin(self.master)
|
||||
|
||||
result = self.master.run_command(
|
||||
['ipa', 'trust-add', '--type', 'ad', self.ad_domain, '--admin',
|
||||
'Administrator', '--range-type', range_type, '--password'],
|
||||
raiseonerr=False,
|
||||
stdin_text=self.master.config.ad_admin_password)
|
||||
|
||||
# The trust-add command is supposed to fail
|
||||
assert result.returncode == 1
|
||||
|
||||
|
||||
class TestExternalTrustWithSubdomain(ADTrustSubdomainBase):
|
||||
"""
|
||||
Test establishing external trust with subdomain
|
||||
"""
|
||||
|
||||
def test_establish_trust(self):
|
||||
""" Tests establishing external trust with Active Directory """
|
||||
tasks.establish_trust_with_ad(
|
||||
self.master, self.ad_subdomain,
|
||||
extra_args=['--range-type', 'ipa-ad-trust', '--external=True'])
|
||||
|
||||
def test_all_trustdomains_found(self):
|
||||
""" Test that only one trustdomain is found """
|
||||
result = self.master.run_command(['ipa', 'trustdomain-find',
|
||||
self.ad_subdomain])
|
||||
|
||||
assert self.ad_subdomain in result.stdout_text
|
||||
assert "Number of entries returned 1" in result.stdout_text
|
||||
|
||||
def test_user_gid_uid_resolution_in_nonposix_trust(self):
|
||||
""" Check that user has SID-generated UID """
|
||||
testuser = 'subdomaintestuser@{0}'.format(self.ad_subdomain)
|
||||
result = self.master.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
testuser_regex = ("^subdomaintestuser@{0}:\*:(?!10142)(\d+):"
|
||||
"(?!10147)(\d+):Subdomaintest User:"
|
||||
"/home/{1}/subdomaintestuser:/bin/sh$".format(
|
||||
re.escape(self.ad_subdomain),
|
||||
re.escape(self.ad_subdomain)))
|
||||
|
||||
assert re.search(testuser_regex, result.stdout_text)
|
||||
|
||||
def test_remove_nonposix_trust(self):
|
||||
tasks.remove_trust_with_ad(self.master, self.ad_subdomain)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
class TestNonexternalTrustWithSubdomain(ADTrustSubdomainBase):
|
||||
"""
|
||||
Tests that a non-external trust to a subdomain cannot be established
|
||||
"""
|
||||
def test_establish_trust(self):
|
||||
""" Tests establishing non-external trust with Active Directory """
|
||||
self.master.run_command(['kinit', '-kt', paths.HTTP_KEYTAB,
|
||||
'HTTP/%s' % self.master.hostname])
|
||||
self.master.run_command(['systemctl', 'restart', 'krb5kdc.service'])
|
||||
self.master.run_command(['kdestroy', '-A'])
|
||||
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(['klist'])
|
||||
self.master.run_command(['smbcontrol', 'all', 'debug', '100'])
|
||||
|
||||
result = self.master.run_command([
|
||||
'ipa', 'trust-add', '--type', 'ad', self.ad_subdomain, '--admin',
|
||||
'Administrator', '--password', '--range-type', 'ipa-ad-trust'
|
||||
], stdin_text=self.master.config.ad_admin_password,
|
||||
raiseonerr=False)
|
||||
|
||||
assert result != 0
|
||||
assert ("Domain '{0}' is not a root domain".format(
|
||||
self.ad_subdomain) in result.stderr_text)
|
||||
|
||||
def test_all_trustdomains_found(self):
|
||||
raise nose.SkipTest(
|
||||
'Test case unapplicable, present for inheritance reason only')
|
||||
|
||||
|
||||
class TestExternalTrustWithTreedomain(ADTrustTreedomainBase):
|
||||
"""
|
||||
Test establishing external trust with tree root domain
|
||||
"""
|
||||
|
||||
def test_establish_trust(self):
|
||||
""" Tests establishing external trust with Active Directory """
|
||||
tasks.establish_trust_with_ad(
|
||||
self.master, self.ad_treedomain,
|
||||
extra_args=['--range-type', 'ipa-ad-trust', '--external=True'])
|
||||
|
||||
def test_all_trustdomains_found(self):
|
||||
""" Test that only one trustdomain is found """
|
||||
result = self.master.run_command(['ipa', 'trustdomain-find',
|
||||
self.ad_treedomain])
|
||||
|
||||
assert self.ad_treedomain in result.stdout_text
|
||||
assert "Number of entries returned 1" in result.stdout_text
|
||||
|
||||
def test_user_gid_uid_resolution_in_nonposix_trust(self):
|
||||
""" Check that user has SID-generated UID """
|
||||
testuser = 'treetestuser@{0}'.format(self.ad_treedomain)
|
||||
result = self.master.run_command(['getent', 'passwd', testuser])
|
||||
|
||||
testuser_regex = ("^treetestuser@{0}:\*:(?!10242)(\d+):"
|
||||
"(?!10247)(\d+):TreeTest User:"
|
||||
"/home/{1}/treetestuser:/bin/sh$".format(
|
||||
re.escape(self.ad_treedomain),
|
||||
re.escape(self.ad_treedomain)))
|
||||
|
||||
assert re.search(testuser_regex, result.stdout_text)
|
||||
|
||||
def test_remove_nonposix_trust(self):
|
||||
tasks.remove_trust_with_ad(self.master, self.ad_treedomain)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
class TestNonexternalTrustWithTreedomain(ADTrustTreedomainBase):
|
||||
"""
|
||||
Tests that a non-external trust to a tree root domain cannot be established
|
||||
"""
|
||||
def test_establish_trust(self):
|
||||
""" Tests establishing non-external trust with Active Directory """
|
||||
self.master.run_command(['kinit', '-kt', paths.HTTP_KEYTAB,
|
||||
'HTTP/%s' % self.master.hostname])
|
||||
self.master.run_command(['systemctl', 'restart', 'krb5kdc.service'])
|
||||
self.master.run_command(['kdestroy', '-A'])
|
||||
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(['klist'])
|
||||
self.master.run_command(['smbcontrol', 'all', 'debug', '100'])
|
||||
|
||||
result = self.master.run_command([
|
||||
'ipa', 'trust-add', '--type', 'ad', self.ad_treedomain, '--admin',
|
||||
'Administrator', '--password', '--range-type', 'ipa-ad-trust'
|
||||
], stdin_text=self.master.config.ad_admin_password,
|
||||
raiseonerr=False)
|
||||
|
||||
assert result != 0
|
||||
assert ("Domain '{0}' is not a root domain".format(
|
||||
self.ad_treedomain) in result.stderr_text)
|
||||
|
||||
def test_all_trustdomains_found(self):
|
||||
raise nose.SkipTest(
|
||||
'Test case unapplicable, present for inheritance reason only')
|
||||
|
||||
|
||||
class TestExternalTrustWithRootDomain(ADTrustSubdomainBase):
|
||||
"""
|
||||
Test establishing external trust with root domain
|
||||
Main purpose of this test is to verify that subdomains are not
|
||||
associated with the external trust, hence all tests are skipped
|
||||
if no subdomain is specified.
|
||||
"""
|
||||
|
||||
def test_establish_trust(self):
|
||||
""" Tests establishing external trust with Active Directory """
|
||||
tasks.establish_trust_with_ad(
|
||||
self.master, self.ad_domain,
|
||||
extra_args=['--range-type', 'ipa-ad-trust', '--external=True'])
|
||||
|
||||
def test_all_trustdomains_found(self):
|
||||
""" Test that only one trustdomain is found """
|
||||
result = self.master.run_command(['ipa', 'trustdomain-find',
|
||||
self.ad_domain])
|
||||
|
||||
assert self.ad_domain in result.stdout_text
|
||||
assert "Number of entries returned 1" in result.stdout_text
|
||||
|
||||
def test_remove_nonposix_trust(self):
|
||||
tasks.remove_trust_with_ad(self.master, self.ad_domain)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
|
||||
|
||||
class TestTrustWithUPN(ADTrustBase):
|
||||
"""
|
||||
Test support of UPN for trusted domains
|
||||
"""
|
||||
|
||||
upn_suffix = 'UPNsuffix.com'
|
||||
upn_username = 'upnuser'
|
||||
upn_name = 'UPN User'
|
||||
upn_principal = '{}@{}'.format(upn_username, upn_suffix)
|
||||
upn_password = 'Secret123456'
|
||||
|
||||
def test_upn_in_nonposix_trust(self):
|
||||
""" Check that UPN is listed as trust attribute """
|
||||
result = self.master.run_command(['ipa', 'trust-show', self.ad_domain,
|
||||
'--all', '--raw'])
|
||||
|
||||
assert ("ipantadditionalsuffixes: {}".format(self.upn_suffix) in
|
||||
result.stdout_text)
|
||||
|
||||
def test_upn_user_resolution_in_nonposix_trust(self):
|
||||
""" Check that user with UPN can be resolved """
|
||||
result = self.master.run_command(['getent', 'passwd',
|
||||
self.upn_principal])
|
||||
|
||||
# result will contain AD domain, not UPN
|
||||
upnuser_regex = "^{}@{}:\*:(\d+):(\d+):{}:/home/{}/{}:/bin/sh$".format(
|
||||
self.upn_username, self.ad_domain, self.upn_name,
|
||||
self.ad_domain, self.upn_username)
|
||||
assert re.search(upnuser_regex, result.stdout_text)
|
||||
|
||||
def test_upn_user_authentication(self):
|
||||
""" Check that AD user with UPN can authenticate in IPA """
|
||||
self.master.run_command(['kinit', '-C', '-E', self.upn_principal],
|
||||
stdin_text=self.upn_password)
|
||||
|
||||
def test_remove_nonposix_trust(self):
|
||||
tasks.remove_trust_with_ad(self.master, self.ad_domain)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
142
ipatests/test_integration/test_vault.py
Normal file
142
ipatests/test_integration/test_vault.py
Normal file
@@ -0,0 +1,142 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_plugins.integration import tasks
|
||||
|
||||
WAIT_AFTER_ARCHIVE = 45 # give some time to replication
|
||||
|
||||
|
||||
class TestInstallKRA(IntegrationTest):
|
||||
"""
|
||||
Test if vault feature behaves as expected, when KRA is installed or not
|
||||
installed on replica
|
||||
"""
|
||||
num_replicas = 1
|
||||
topology = 'star'
|
||||
|
||||
vault_password = "password"
|
||||
vault_data = "SSBsb3ZlIENJIHRlc3RzCg=="
|
||||
vault_name_master = "ci_test_vault_master"
|
||||
vault_name_master2 = "ci_test_vault_master2"
|
||||
vault_name_master3 = "ci_test_vault_master3"
|
||||
vault_name_replica_without_KRA = "ci_test_vault_replica_without_kra"
|
||||
vault_name_replica_with_KRA = "ci_test_vault_replica_with_kra"
|
||||
vault_name_replica_KRA_uninstalled = "ci_test_vault_replica_KRA_uninstalled"
|
||||
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_kra=True)
|
||||
# do not install KRA on replica, it is part of test
|
||||
tasks.install_replica(cls.master, cls.replicas[0], setup_kra=False)
|
||||
|
||||
def _retrieve_secret(self, vault_names=[]):
|
||||
# try to retrieve secret from vault on both master and replica
|
||||
for vault_name in vault_names:
|
||||
self.master.run_command([
|
||||
"ipa", "vault-retrieve",
|
||||
vault_name,
|
||||
"--password", self.vault_password,
|
||||
])
|
||||
|
||||
self.replicas[0].run_command([
|
||||
"ipa", "vault-retrieve",
|
||||
vault_name,
|
||||
"--password", self.vault_password,
|
||||
])
|
||||
|
||||
def test_create_and_retrieve_vault_master(self):
|
||||
# create vault
|
||||
self.master.run_command([
|
||||
"ipa", "vault-add",
|
||||
self.vault_name_master,
|
||||
"--password", self.vault_password,
|
||||
"--type", "symmetric",
|
||||
])
|
||||
|
||||
# archive secret
|
||||
self.master.run_command([
|
||||
"ipa", "vault-archive",
|
||||
self.vault_name_master,
|
||||
"--password", self.vault_password,
|
||||
"--data", self.vault_data,
|
||||
])
|
||||
time.sleep(WAIT_AFTER_ARCHIVE)
|
||||
|
||||
self._retrieve_secret([self.vault_name_master])
|
||||
|
||||
def test_create_and_retrieve_vault_replica_without_kra(self):
|
||||
# create vault
|
||||
self.replicas[0].run_command([
|
||||
"ipa", "vault-add",
|
||||
self.vault_name_replica_without_KRA,
|
||||
"--password", self.vault_password,
|
||||
"--type", "symmetric",
|
||||
])
|
||||
|
||||
# archive secret
|
||||
self.replicas[0].run_command([
|
||||
"ipa", "vault-archive",
|
||||
self.vault_name_replica_without_KRA,
|
||||
"--password", self.vault_password,
|
||||
"--data", self.vault_data,
|
||||
])
|
||||
time.sleep(WAIT_AFTER_ARCHIVE)
|
||||
|
||||
self._retrieve_secret([self.vault_name_replica_without_KRA])
|
||||
|
||||
def test_create_and_retrieve_vault_replica_with_kra(self):
|
||||
|
||||
# install KRA on replica
|
||||
tasks.install_kra(self.replicas[0], first_instance=False)
|
||||
|
||||
# create vault
|
||||
self.replicas[0].run_command([
|
||||
"ipa", "vault-add",
|
||||
self.vault_name_replica_with_KRA,
|
||||
"--password", self.vault_password,
|
||||
"--type", "symmetric",
|
||||
])
|
||||
|
||||
# archive secret
|
||||
self.replicas[0].run_command([
|
||||
"ipa", "vault-archive",
|
||||
self.vault_name_replica_with_KRA,
|
||||
"--password", self.vault_password,
|
||||
"--data", self.vault_data,
|
||||
])
|
||||
time.sleep(WAIT_AFTER_ARCHIVE)
|
||||
|
||||
self._retrieve_secret([self.vault_name_replica_with_KRA])
|
||||
|
||||
################# master #################
|
||||
# test master again after KRA was installed on replica
|
||||
# create vault
|
||||
self.master.run_command([
|
||||
"ipa", "vault-add",
|
||||
self.vault_name_master2,
|
||||
"--password", self.vault_password,
|
||||
"--type", "symmetric",
|
||||
])
|
||||
|
||||
# archive secret
|
||||
self.master.run_command([
|
||||
"ipa", "vault-archive",
|
||||
self.vault_name_master2,
|
||||
"--password", self.vault_password,
|
||||
"--data", self.vault_data,
|
||||
])
|
||||
time.sleep(WAIT_AFTER_ARCHIVE)
|
||||
|
||||
self._retrieve_secret([self.vault_name_master2])
|
||||
|
||||
################ old vaults ###############
|
||||
# test if old vaults are still accessible
|
||||
self._retrieve_secret([
|
||||
self.vault_name_master,
|
||||
self.vault_name_replica_without_KRA,
|
||||
])
|
||||
Reference in New Issue
Block a user