listener.name.mtls.ssl.truststore.password=mypassword
# SASL mechanisms
- sasl.enabled.mechanisms=PLAIN,SCRAM-SHA-256,SCRAM-SHA-512
+ sasl.enabled.mechanisms=PLAIN,SCRAM-SHA-256,SCRAM-SHA-512,GSSAPI
+ sasl.kerberos.service.name=kafka
sasl.mechanism.inter.broker.protocol=PLAIN
inter.broker.listener.name=PLAINTEXT
username="admin" \
password="admin-secret";
+ # SASL over SSL with GSSAPI mechanism
+ listener.name.sasl_ssl.gssapi.sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
+ useKeyTab=true \
+ storeKey=true \
+ keyTab="/etc/krb5-keytabs/kafka.service.keytab" \
+ principal="kafka/192.168.1.100@REALM";
+
# PLAINTEXT SASL with PLAIN mechanism
listener.name.sasl_plaintext.plain.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
username="admin" \
username="admin" \
password="admin-secret";
-3. Start Zookeeper and Kafka.
+ # PLAINTEXT SASL with GSSAPI mechanism
+ listener.name.sasl_plaintext.gssapi.sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
+ useKeyTab=true \
+ storeKey=true \
+ keyTab="/etc/krb5-keytabs/kafka.service.keytab" \
+ principal="kafka/192.168.1.100@REALM";
+
+3. Kerberos setup::
+
+ librdkafka (inside RGW) builds the broker SPN (Service Principal Name, basically the
+ principal that represents the Kafka broker) as ``kafka/<broker-host>@REALM``,
+ where ``<broker-host>`` is whatever appears in ``advertised.listeners``.
+ Since step 2 uses the broker's IP in ``advertised.listeners``, the
+ broker principal must be tied to that same IP (or hostname — they must match).
+
+ The examples below use realm ``REALM.COM`` and broker IP ``192.168.1.100``.
+ Substitute your own values.
+
+ a. Install Kerberos and SASL/GSSAPI packages.
+
+ Ubuntu/Debian::
+
+ sudo apt update
+ sudo DEBIAN_FRONTEND=noninteractive apt install -y krb5-kdc krb5-admin-server krb5-user \
+ libsasl2-modules-gssapi-mit libsasl2-modules
+
+ Fedora/RHEL::
+
+ sudo dnf install -y krb5-server krb5-workstation cyrus-sasl-gssapi
+
+ b. Configure ``/etc/krb5.conf``.
+
+ Reference contents::
+
+ [libdefaults]
+ default_realm = REALM.COM
+ dns_lookup_realm = false
+ dns_lookup_kdc = false
+ rdns = false
+ forwardable = true
+ ticket_lifetime = 24h
+ renew_lifetime = 7d
+ [realms]
+ REALM.COM = {
+ kdc = 192.168.1.100
+ admin_server = 192.168.1.100
+ }
+
+ [domain_realm]
+ .realm.com = REALM.COM
+ realm.com = REALM.COM
-4. Start RGW vstart cluster with cleartext parameter set to true::
+ c. Bootstrap the KDC (first-time setup only)::
+
+ sudo kdb5_util create -s -r REALM.COM # set + remember master password
+ echo '*/admin@REALM.COM *' | sudo tee /etc/krb5kdc/kadm5.acl # allow admin principal to do anything
+
+ # start and enable KDC and admin server
+ Ubuntu/Debian::
+ sudo systemctl enable --now krb5-kdc krb5-admin-server
+ sudo systemctl status krb5-kdc krb5-admin-server
+
+ Fedora/RHEL::
+ sudo systemctl enable --now krb5kdc kadmin
+ sudo systemctl status krb5kdc kadmin
+
+ d. Create the broker and RGW principals + keytabs::
+
+ sudo mkdir -p /etc/krb5-keytabs # to store the keytabs
+ sudo kadmin.local -q "addprinc admin/admin"
+ sudo kadmin.local -q "addprinc -randkey kafka/192.168.1.100"
+ sudo kadmin.local -q "addprinc -randkey rgw/192.168.1.100"
+ sudo kadmin.local -q "ktadd -k /etc/krb5-keytabs/kafka.service.keytab kafka/192.168.1.100"
+ sudo kadmin.local -q "ktadd -k /etc/krb5-keytabs/rgw.service.keytab rgw/192.168.1.100"
+
+ sudo chmod 640 /etc/krb5-keytabs/*.keytab
+ sudo chown root:$USER /etc/krb5-keytabs/*.keytab # Make keytabs readable by the user that runs the Kafka broker
+ # AND the user that runs vstart (often the same user)
+
+ e. Verify the keytabs and get a TGT for the RGW principal::
+
+ klist -kt /etc/krb5-keytabs/kafka.service.keytab
+ klist -kt /etc/krb5-keytabs/rgw.service.keytab
+ kinit -kt /etc/krb5-keytabs/rgw.service.keytab rgw/192.168.1.100@REALM.COM
+ klist # should show a valid TGT
+
+ f. Make sure to add the Kafka principal, RGW principal, and keytab to your ``bntests.conf``.
+
+4. Start Zookeeper and Kafka.
+
+5. Start RGW vstart cluster with cleartext parameter set to true::
cd /path/to/ceph/build
- MON=1 OSD=1 MDS=0 MGR=0 RGW=1 ../src/vstart.sh -n -d -o "rgw_allow_notification_secrets_in_cleartext=true"
+ MON=1 OSD=1 MDS=0 MGR=0 RGW=1 ../src/vstart.sh -n -d -o "rgw_allow_notification_secrets_in_cleartext=true" -o "rgw_kafka_sasl_kerberos_service_name=kafka"
-5. Run the tests::
+6. Run the tests::
cd /path/to/ceph
KAFKA_DIR=/path/to/kafka BNTESTS_CONF=/path/to/bntests.conf python -m pytest -s /path/to/ceph/src/test/rgw/bucket_notification/test_bn.py -v -m 'kafka_security_test'
+ For GSSAPI tests, ensure a valid ticket cache already exists for RGW principal
+ (for example, via ``kinit``).
+
==============
RabbitMQ Tests
==============
get_config_zonegroup,
get_config_cluster,
get_access_key,
- get_secret_key
+ get_secret_key,
+ get_kerberos_config,
)
from .api import PSTopicS3, \
KAFKA_TEST_USER = 'alice'
KAFKA_TEST_PASSWORD = 'alice-secret'
+
+def get_kerberos_env():
+ service_name, principal, keytab = get_kerberos_config()
+ return service_name, principal, keytab
+
+
def setup_scram_users_via_kafka_configs(mechanism: str) -> None:
"""to setup SCRAM users using kafka-configs.sh after Kafka is running."""
if not mechanism.startswith('SCRAM'):
if ca_cert:
base_config['ssl_cafile'] = ca_cert
base_config['sasl_mechanism'] = mechanism
- base_config.update({
- 'sasl_plain_username': KAFKA_TEST_USER,
- 'sasl_plain_password': KAFKA_TEST_PASSWORD,
- })
+ if mechanism == 'GSSAPI':
+ kerberos_service_name, _, _ = get_kerberos_env()
+ if kerberos_service_name:
+ base_config['sasl_kerberos_service_name'] = kerberos_service_name
+ else:
+ base_config.update({
+ 'sasl_plain_username': KAFKA_TEST_USER,
+ 'sasl_plain_password': KAFKA_TEST_PASSWORD,
+ })
elif effective_protocol == 'SASL_PLAINTEXT':
base_config['security_protocol'] = 'SASL_PLAINTEXT'
base_config['sasl_mechanism'] = mechanism
- base_config.update({
- 'sasl_plain_username': KAFKA_TEST_USER,
- 'sasl_plain_password': KAFKA_TEST_PASSWORD,
- })
+ if mechanism == 'GSSAPI':
+ kerberos_service_name, _, _ = get_kerberos_env()
+ if kerberos_service_name:
+ base_config['sasl_kerberos_service_name'] = kerberos_service_name
+ else:
+ base_config.update({
+ 'sasl_plain_username': KAFKA_TEST_USER,
+ 'sasl_plain_password': KAFKA_TEST_PASSWORD,
+ })
remaining_retries = 10
while remaining_retries > 0:
if mechanism.startswith('SCRAM'):
setup_scram_users_via_kafka_configs(mechanism)
time.sleep(2) # Allow time for SCRAM config to propagate
+ elif mechanism == 'GSSAPI':
+ service_name, principal, keytab = get_kerberos_env()
+ missing = []
+ if not service_name:
+ missing.append('service_name')
+ if not principal:
+ missing.append('principal')
+ if not keytab:
+ missing.append('keytab')
+ if missing:
+ pytest.skip('Missing GSSAPI options in [kerberos] section of BNTESTS_CONF: ' + ', '.join(missing))
+ if not os.path.isfile(keytab):
+ pytest.skip(f'[kerberos] keytab does not exist: {keytab}')
conn = connection()
zonegroup = get_config_zonegroup()
topic_name = bucket_name+'_topic'
# create topic
if security_type == 'SASL_SSL':
- if not use_topic_attrs_for_creds:
- endpoint_address = 'kafka://alice:alice-secret@' + default_kafka_server + ':9094'
- else:
+ if mechanism == 'GSSAPI' or use_topic_attrs_for_creds:
endpoint_address = 'kafka://' + default_kafka_server + ':9094'
+ else:
+ endpoint_address = 'kafka://alice:alice-secret@' + default_kafka_server + ':9094'
elif security_type == 'SSL':
if use_mtls:
endpoint_address = 'kafka://' + default_kafka_server + ':9096'
else:
endpoint_address = 'kafka://' + default_kafka_server + ':9093'
elif security_type == 'SASL_PLAINTEXT':
- endpoint_address = 'kafka://alice:alice-secret@' + default_kafka_server + ':9095'
+ if mechanism == 'GSSAPI':
+ endpoint_address = 'kafka://' + default_kafka_server + ':9095'
+ else:
+ endpoint_address = 'kafka://alice:alice-secret@' + default_kafka_server + ':9095'
else:
assert False, 'unknown security method '+security_type
if security_type == 'SASL_PLAINTEXT':
endpoint_args = 'push-endpoint='+endpoint_address+'&kafka-ack-level=broker&use-ssl=false&mechanism='+mechanism
+ if mechanism == 'GSSAPI':
+ kerberos_service_name, kerberos_principal, kerberos_keytab = get_kerberos_env()
+ if kerberos_service_name:
+ endpoint_args += '&sasl-kerberos-service-name=' + kerberos_service_name
+ if kerberos_principal:
+ endpoint_args += '&sasl-kerberos-principal=' + kerberos_principal
+ if kerberos_keytab:
+ endpoint_args += '&sasl-kerberos-keytab=' + kerberos_keytab
elif security_type == 'SASL_SSL':
kafka_cert_dir = _kafka_cert_dir()
endpoint_args = 'push-endpoint='+endpoint_address+'&kafka-ack-level=broker&use-ssl=true&ca-location='+kafka_cert_dir+'/y-ca.crt&mechanism='+mechanism
- if use_topic_attrs_for_creds:
+ if mechanism == 'GSSAPI':
+ kerberos_service_name, kerberos_principal, kerberos_keytab = get_kerberos_env()
+ if kerberos_service_name:
+ endpoint_args += '&sasl-kerberos-service-name=' + kerberos_service_name
+ if kerberos_principal:
+ endpoint_args += '&sasl-kerberos-principal=' + kerberos_principal
+ if kerberos_keytab:
+ endpoint_args += '&sasl-kerberos-keytab=' + kerberos_keytab
+ elif use_topic_attrs_for_creds:
endpoint_args += '&user-name=alice&password=alice-secret'
else:
kafka_cert_dir = _kafka_cert_dir()
kafka_security('SASL_SSL', mechanism='SCRAM-SHA-512')
+@pytest.mark.kafka_security_test
+def test_notification_kafka_security_sasl_gssapi():
+ kafka_security('SASL_PLAINTEXT', mechanism='GSSAPI')
+
+
+@pytest.mark.kafka_security_test
+def test_notification_kafka_security_ssl_sasl_gssapi():
+ kafka_security('SASL_SSL', mechanism='GSSAPI')
+
+
@pytest.mark.kafka_security_test
def test_notification_kafka_security_ssl_mtls():
"""test mTLS client certificate authentication to Kafka"""