Skip to content

Commit 5c461bd

Browse files
ceacheStephenSorriaux
authored andcommitted
feat(core): run SASL Kerberos tests as part of build
* Install debian packages for KDC as part of Travis init. * Setup a loopback mini KDC for running tests. * Run SASL tests as part of Travis builds. * Improve harness cluster to support: * Reconfiguration when environment changes. * Different JAAS configurations (DIGEST/GSSAPI). * Moved SASL tests into own module, with specially configured harness. * Bumped default timeout to 15 sec to mitigate false negatives on Travis.
1 parent 7a0d54e commit 5c461bd

File tree

11 files changed

+431
-175
lines changed

11 files changed

+431
-175
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ addons:
44
apt:
55
packages:
66
- libevent-dev
7+
- krb5-kdc
8+
- krb5-admin-server
79
cache:
810
pip: true
911
directories:

ensure-zookeeper-env.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44

55
HERE=`pwd`
66
ZOO_BASE_DIR="$HERE/zookeeper"
7-
ZOOKEEPER_VERSION=${ZOOKEEPER_VERSION:-3.4.10}
7+
ZOOKEEPER_VERSION=${ZOOKEEPER_VERSION:-3.4.13}
88
ZOOKEEPER_PATH="$ZOO_BASE_DIR/$ZOOKEEPER_VERSION"
99
ZOOKEEPER_PREFIX=${ZOOKEEPER_PREFIX}
1010
ZOOKEEPER_SUFFIX=${ZOOKEEPER_SUFFIX}

init_krb5.sh

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
KRB5KDC=$(which krb5kdc || true)
6+
KDB5_UTIL=$(which kdb5_util || true)
7+
KADMIN=$(which kadmin.local || true)
8+
9+
if [ $# -lt 2 ]; then
10+
echo "Usage $0 TARGET_DIR CMD..."
11+
exit 1
12+
fi
13+
WRK_DIR=$1
14+
shift
15+
16+
# Check installed packages
17+
if [ -z ${KRB5KDC:+x} ] || [ -z ${KDB5_UTIL:+x} ] || [ -z ${KADMIN:+x} ]; then
18+
echo "Missing Kerberos utilities, skipping environment setup."
19+
exec $@
20+
fi
21+
22+
if [ -e ${WRK_DIR} ]; then
23+
echo "Working directory kdc already exists!"
24+
exit 1
25+
fi
26+
27+
WRK_DIR=$(readlink -f ${WRK_DIR})
28+
KDC_DIR="${WRK_DIR}/krb5kdc"
29+
30+
###############################################################################
31+
# Cleanup handlers
32+
33+
function kdclogs {
34+
echo "Kerberos environment logs:"
35+
tail -v -n50 ${KDC_DIR}/*.log
36+
}
37+
38+
function killkdc {
39+
if [ -e ${KDC_DIR}/kdc.pid ]; then
40+
echo "Terminating KDC server listening on ${KDC_PORT}..."
41+
kill -TERM $(cat ${KDC_DIR}/kdc.pid)
42+
fi
43+
rm -vfr ${WRK_DIR}
44+
}
45+
trap killkdc EXIT
46+
trap kdclogs ERR
47+
48+
###############################################################################
49+
export KRB5_TEST_ENV=${WRK_DIR}
50+
export KRB5_CONFIG=${WRK_DIR}/krb5.conf
51+
52+
KDC_PORT=$((${RANDOM}+1024))
53+
mkdir -vp ${WRK_DIR}
54+
mkdir -vp ${KDC_DIR}
55+
56+
cat <<EOF >${WRK_DIR}/krb5.conf
57+
[logging]
58+
default = FILE:${KDC_DIR}/krb5libs.log
59+
kdc = FILE:${KDC_DIR}/krb5kdc.log
60+
admin_server = FILE:${KDC_DIR}/kadmind.log
61+
62+
[libdefaults]
63+
dns_lookup_realm = false
64+
ticket_lifetime = 24h
65+
renew_lifetime = 7d
66+
forwardable = true
67+
rdns = false
68+
default_realm = KAZOOTEST.ORG
69+
#default_ccache_name = KEYRING:persistent:%{uid}
70+
71+
[realms]
72+
KAZOOTEST.ORG = {
73+
database_name = ${KDC_DIR}/principal
74+
admin_keytab = FILE:${KDC_DIR}/kadm5.keytab
75+
key_stash_file = ${KDC_DIR}/stash
76+
kdc_listen = 127.0.0.1:${KDC_PORT}
77+
kdc_tcp_listen = 127.0.0.1:${KDC_PORT}
78+
kdc = 127.0.0.1:${KDC_PORT}
79+
default_domain = kazootest.org
80+
}
81+
82+
[domain_realm]
83+
.kazootest.org = KAZOOTEST.ORG
84+
kazootest.org = KAZOOTEST.ORG
85+
EOF
86+
87+
cat <<EOF | ${KDB5_UTIL} create -s
88+
passwd123
89+
passwd123
90+
EOF
91+
92+
cat <<EOF | ${KADMIN}
93+
add_principal -randkey [email protected]
94+
ktadd -k ${WRK_DIR}/client.keytab -norandkey [email protected]
95+
add_principal -randkey zookeeper/[email protected]
96+
ktadd -k ${WRK_DIR}/server.keytab -norandkey zookeeper/[email protected]
97+
quit
98+
EOF
99+
100+
# Starting KDC
101+
echo "Starting KDC listening on ${KDC_PORT}..."
102+
KRB5_KDC_PROFILE=${KRB5_CONFIG} ${KRB5KDC} \
103+
-P ${KDC_DIR}/kdc.pid \
104+
-p ${KDC_PORT} \
105+
-r KAZOOTEST.ORG
106+
107+
# Execute the next command
108+
$@

kazoo/testing/common.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ class ManagedZooKeeper(object):
7575
Hudson/Buildbot context, to ensure more test robustness."""
7676

7777
def __init__(self, software_path, server_info, peers=(), classpath=None,
78-
configuration_entries=[], java_system_properties=[]):
78+
configuration_entries=[], java_system_properties=[],
79+
jaas_config=None):
7980
"""Define the ZooKeeper test instance.
8081
8182
@param install_path: The path to the install for ZK
@@ -90,6 +91,7 @@ def __init__(self, software_path, server_info, peers=(), classpath=None,
9091
self._running = False
9192
self.configuration_entries = configuration_entries
9293
self.java_system_properties = java_system_properties
94+
self.jaas_config = jaas_config
9395

9496
def run(self):
9597
"""Run the ZooKeeper instance under a temporary directory.
@@ -99,7 +101,7 @@ def run(self):
99101
if self.running:
100102
return
101103
config_path = os.path.join(self.working_path, "zoo.cfg")
102-
jass_config_path = os.path.join(self.working_path, "jaas.conf")
104+
jaas_config_path = os.path.join(self.working_path, "jaas.conf")
103105
log_path = os.path.join(self.working_path, "log")
104106
log4j_path = os.path.join(self.working_path, "log4j.properties")
105107
data_path = os.path.join(self.working_path, "data")
@@ -126,7 +128,6 @@ def run(self):
126128
self.server_info.admin_port,
127129
"\n".join(self.configuration_entries))) # NOQA
128130

129-
130131
# setup a replicated setup if peers are specified
131132
if self.peers:
132133
servers_cfg = []
@@ -146,14 +147,8 @@ def run(self):
146147
with open(os.path.join(data_path, "myid"), "w") as myid_file:
147148
myid_file.write(str(self.server_info.server_id))
148149
# Write JAAS configuration
149-
with open(jass_config_path, "w") as jaas_file:
150-
jaas_file.write("""
151-
Server {
152-
org.apache.zookeeper.server.auth.DigestLoginModule required
153-
user_super="super_secret"
154-
user_jaasuser="jaas_password";
155-
};""")
156-
150+
with open(jaas_config_path, "w") as jaas_file:
151+
jaas_file.write(self.jaas_config or '')
157152
with open(log4j_path, "w") as log4j:
158153
log4j.write("""
159154
# DEFAULT: console appender only
@@ -186,7 +181,7 @@ def run(self):
186181
"-Djava.awt.headless=true",
187182

188183
# JAAS configuration for SASL authentication
189-
"-Djava.security.auth.login.config=%s" % jass_config_path,
184+
"-Djava.security.auth.login.config=%s" % jaas_config_path,
190185
] + self.java_system_properties + [
191186
"org.apache.zookeeper.server.quorum.QuorumPeerMain",
192187
config_path,
@@ -279,12 +274,12 @@ class ZookeeperCluster(object):
279274
def __init__(self, install_path=None, classpath=None,
280275
size=3, port_offset=20000, observer_start_id=-1,
281276
configuration_entries=[],
282-
java_system_properties=[]):
277+
java_system_properties=[],
278+
jaas_config=None):
283279
self._install_path = install_path
284280
self._classpath = classpath
285281
self._servers = []
286282

287-
288283
# Calculate ports and peer group
289284
port = port_offset
290285
peers = []
@@ -309,7 +304,10 @@ def __init__(self, install_path=None, classpath=None,
309304
self._install_path, server_info, server_peers,
310305
classpath=self._classpath,
311306
configuration_entries=configuration_entries,
312-
java_system_properties=java_system_properties))
307+
java_system_properties=java_system_properties,
308+
jaas_config=jaas_config
309+
)
310+
)
313311

314312
def __getitem__(self, k):
315313
return self._servers[k]

kazoo/testing/harness.py

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,104 @@
1616
log = logging.getLogger(__name__)
1717

1818
CLUSTER = None
19+
CLUSTER_CONF = None
20+
CLUSTER_DEFAULTS = {
21+
"ZOOKEEPER_PORT_OFFSET": 20000,
22+
"ZOOKEEPER_CLUSTER_SIZE": 3,
23+
"ZOOKEEPER_OBSERVER_START_ID": -1,
24+
}
1925

2026

2127
def get_global_cluster():
22-
global CLUSTER
23-
if CLUSTER is None:
24-
ZK_HOME = os.environ.get("ZOOKEEPER_PATH")
25-
ZK_CLASSPATH = os.environ.get("ZOOKEEPER_CLASSPATH")
26-
ZK_PORT_OFFSET = int(os.environ.get("ZOOKEEPER_PORT_OFFSET", 20000))
27-
ZK_CLUSTER_SIZE = int(os.environ.get("ZOOKEEPER_CLUSTER_SIZE", 3))
28-
ZK_VERSION = os.environ.get("ZOOKEEPER_VERSION")
29-
if '-' in ZK_VERSION:
30-
# Ignore pre-release markers like -alpha
31-
ZK_VERSION = ZK_VERSION.split('-')[0]
32-
ZK_VERSION = tuple([int(n) for n in ZK_VERSION.split('.')])
33-
34-
ZK_OBSERVER_START_ID = int(
35-
os.environ.get("ZOOKEEPER_OBSERVER_START_ID", -1))
36-
37-
assert ZK_HOME or ZK_CLASSPATH or ZK_VERSION, (
38-
"Either ZOOKEEPER_PATH or ZOOKEEPER_CLASSPATH or "
39-
"ZOOKEEPER_VERSION environment variable must be defined.\n"
40-
"For deb package installations this is /usr/share/java")
41-
42-
if ZK_VERSION >= (3, 5):
43-
additional_configuration_entries = [
44-
"4lw.commands.whitelist=*",
45-
"reconfigEnabled=true"
46-
]
47-
# If defines, this sets the superuser password to "test"
48-
additional_java_system_properties = [
49-
"-Dzookeeper.DigestAuthenticationProvider.superDigest="
50-
"super:D/InIHSb7yEEbrWz8b9l71RjZJU="
51-
]
28+
global CLUSTER, CLUSTER_CONF
29+
cluster_conf = {
30+
k: os.environ.get(k, CLUSTER_DEFAULTS.get(k))
31+
for k in ["ZOOKEEPER_PATH",
32+
"ZOOKEEPER_CLASSPATH",
33+
"ZOOKEEPER_PORT_OFFSET",
34+
"ZOOKEEPER_CLUSTER_SIZE",
35+
"ZOOKEEPER_VERSION",
36+
"ZOOKEEPER_OBSERVER_START_ID",
37+
"ZOOKEEPER_JAAS_AUTH"]
38+
}
39+
if CLUSTER is not None:
40+
if CLUSTER_CONF == cluster_conf:
41+
return CLUSTER
5242
else:
53-
additional_configuration_entries = []
54-
additional_java_system_properties = []
55-
CLUSTER = ZookeeperCluster(
56-
install_path=ZK_HOME,
57-
classpath=ZK_CLASSPATH,
58-
port_offset=ZK_PORT_OFFSET,
59-
size=ZK_CLUSTER_SIZE,
60-
observer_start_id=ZK_OBSERVER_START_ID,
61-
configuration_entries=additional_configuration_entries,
62-
java_system_properties=additional_java_system_properties
63-
)
64-
atexit.register(lambda cluster: cluster.terminate(), CLUSTER)
43+
log.info('Config change detected. Reconfiguring cluster...')
44+
CLUSTER.terminate()
45+
CLUSTER = None
46+
# Create a new cluster
47+
ZK_HOME = cluster_conf.get("ZOOKEEPER_PATH")
48+
ZK_CLASSPATH = cluster_conf.get("ZOOKEEPER_CLASSPATH")
49+
ZK_PORT_OFFSET = int(cluster_conf.get("ZOOKEEPER_PORT_OFFSET"))
50+
ZK_CLUSTER_SIZE = int(cluster_conf.get("ZOOKEEPER_CLUSTER_SIZE"))
51+
ZK_VERSION = cluster_conf.get("ZOOKEEPER_VERSION")
52+
if '-' in ZK_VERSION:
53+
# Ignore pre-release markers like -alpha
54+
ZK_VERSION = ZK_VERSION.split('-')[0]
55+
ZK_VERSION = tuple([int(n) for n in ZK_VERSION.split('.')])
56+
ZK_OBSERVER_START_ID = int(cluster_conf.get("ZOOKEEPER_OBSERVER_START_ID"))
57+
58+
assert ZK_HOME or ZK_CLASSPATH or ZK_VERSION, (
59+
"Either ZOOKEEPER_PATH or ZOOKEEPER_CLASSPATH or "
60+
"ZOOKEEPER_VERSION environment variable must be defined.\n"
61+
"For deb package installations this is /usr/share/java")
62+
63+
if ZK_VERSION >= (3, 5):
64+
additional_configuration_entries = [
65+
"4lw.commands.whitelist=*",
66+
"reconfigEnabled=true"
67+
]
68+
# If defined, this sets the superuser password to "test"
69+
additional_java_system_properties = [
70+
"-Dzookeeper.DigestAuthenticationProvider.superDigest="
71+
"super:D/InIHSb7yEEbrWz8b9l71RjZJU="
72+
]
73+
else:
74+
additional_configuration_entries = []
75+
additional_java_system_properties = []
76+
ZOOKEEPER_JAAS_AUTH = cluster_conf.get("ZOOKEEPER_JAAS_AUTH")
77+
if ZOOKEEPER_JAAS_AUTH == "digest":
78+
jaas_config = """
79+
Server {
80+
org.apache.zookeeper.server.auth.DigestLoginModule required
81+
user_super="super_secret"
82+
user_jaasuser="jaas_password";
83+
};"""
84+
elif ZOOKEEPER_JAAS_AUTH == "gssapi":
85+
# Configure Zookeeper to use our test KDC.
86+
additional_java_system_properties += [
87+
"-Djava.security.krb5.conf=%s" % os.path.expandvars(
88+
"${KRB5_CONFIG}"
89+
),
90+
"-Dsun.security.krb5.debug=true",
91+
]
92+
jaas_config = """
93+
Server {
94+
com.sun.security.auth.module.Krb5LoginModule required
95+
debug=true
96+
useKeyTab=true
97+
keyTab="%s"
98+
storeKey=true
99+
useTicketCache=false
100+
principal="zookeeper/[email protected]";
101+
};""" % os.path.expandvars("${KRB5_TEST_ENV}/server.keytab")
102+
else:
103+
jaas_config = None
104+
105+
CLUSTER = ZookeeperCluster(
106+
install_path=ZK_HOME,
107+
classpath=ZK_CLASSPATH,
108+
port_offset=ZK_PORT_OFFSET,
109+
size=ZK_CLUSTER_SIZE,
110+
observer_start_id=ZK_OBSERVER_START_ID,
111+
configuration_entries=additional_configuration_entries,
112+
java_system_properties=additional_java_system_properties,
113+
jaas_config=jaas_config
114+
)
115+
CLUSTER_CONF = cluster_conf
116+
atexit.register(lambda cluster: cluster.terminate(), CLUSTER)
65117
return CLUSTER
66118

67119

@@ -90,6 +142,7 @@ def test_something_else(self):
90142
something_that_needs_zk_servers(self.servers)
91143
92144
"""
145+
DEFAULT_CLIENT_TIMEOUT = 15
93146

94147
def __init__(self, *args, **kw):
95148
super(KazooTestHarness, self).__init__(*args, **kw)
@@ -109,8 +162,10 @@ def _get_nonchroot_client(self):
109162
self._clients.append(c)
110163
return c
111164

112-
def _get_client(self, **kwargs):
113-
c = KazooClient(self.hosts, **kwargs)
165+
def _get_client(self, **client_options):
166+
if 'timeout' not in client_options:
167+
client_options['timeout'] = self.DEFAULT_CLIENT_TIMEOUT
168+
c = KazooClient(self.hosts, **client_options)
114169
self._clients.append(c)
115170
return c
116171

@@ -139,7 +194,7 @@ def setup_zookeeper(self, **client_options):
139194
namespace = "/kazootests" + uuid.uuid4().hex
140195
self.hosts = self.servers + namespace
141196
if 'timeout' not in client_options:
142-
client_options['timeout'] = 0.8
197+
client_options['timeout'] = self.DEFAULT_CLIENT_TIMEOUT
143198
self.client = self._get_client(**client_options)
144199
self.client.start()
145200
self.client.ensure_path("/")

0 commit comments

Comments
 (0)