Skip to content

Commit 87686d0

Browse files
Added password support for SSL private key of the cached schema registry client (#1516)
* Steps to Test: 1) Run certify.sh 2) Manually run .env.sh code 3) docker-compose up [tests/docker] 4) Wait for the docker instance to be listening for requests(roughly 30 seconds) 5) run the command -> python3 integration_test.py --avro-https testconf.json In mac Systems the certify.sh takes the hostname and it then collides when https connection is to be made since the hostname should be mentioned in the keychain - so either change hostname command to localhost literal and then run on Mac, otherwise in Linux system it should be fine.
1 parent 17029ad commit 87686d0

File tree

7 files changed

+63
-6
lines changed

7 files changed

+63
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
SASL PLAIN/SCRAM credentials that will be used for subsequent (new) connections to a broker (#1511).
77
- Wheels for Linux / arm64 (#1496).
88
- Added support for Default num_partitions in CreateTopics Admin API.
9+
- Added support for password protected private key in CachedSchemaRegistryClient.
910

1011
## v2.0.2
1112

src/confluent_kafka/avro/cached_schema_registry_client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#
2222
import logging
2323
import warnings
24+
import urllib3
25+
import json
2426
from collections import defaultdict
2527

2628
from requests import Session, utils
@@ -54,6 +56,7 @@ class CachedSchemaRegistryClient(object):
5456
Use CachedSchemaRegistryClient(dict: config) instead.
5557
Existing params ca_location, cert_location and key_location will be replaced with their librdkafka equivalents:
5658
`ssl.ca.location`, `ssl.certificate.location` and `ssl.key.location` respectively.
59+
The support for password protected private key is via the Config only using 'ssl.key.password' field.
5760
5861
Errors communicating to the server will result in a ClientError being raised.
5962
@@ -109,6 +112,9 @@ def __init__(self, url, max_schemas_per_subject=1000, ca_location=None, cert_loc
109112
self.url = utils.urldefragauth(self.url)
110113

111114
self._session = s
115+
key_password = conf.pop('ssl.key.password', None)
116+
self._is_key_password_provided = not key_password
117+
self._https_session = self._make_https_session(s.cert[0], s.cert[1], ca_path, s.auth, key_password)
112118

113119
self.auto_register_schemas = conf.pop("auto.register.schemas", True)
114120

@@ -128,6 +134,29 @@ def close(self):
128134
# Constructor exceptions may occur prior to _session being set.
129135
if hasattr(self, '_session'):
130136
self._session.close()
137+
if hasattr(self, '_https_session'):
138+
self._https_session.clear()
139+
140+
@staticmethod
141+
def _make_https_session(cert_location, key_location, ca_certs_path, auth, key_password):
142+
https_session = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=ca_certs_path,
143+
cert_file=cert_location, key_file=key_location, key_password=key_password)
144+
https_session.auth = auth
145+
return https_session
146+
147+
def _send_https_session_request(self, url, method, headers, body):
148+
request_headers = {'Accept': ACCEPT_HDR}
149+
auth = self._https_session.auth
150+
if body:
151+
body = json.dumps(body).encode('UTF-8')
152+
request_headers["Content-Length"] = str(len(body))
153+
request_headers["Content-Type"] = "application/vnd.schemaregistry.v1+json"
154+
if auth[0] != '' and auth[1] != '':
155+
request_headers.update(urllib3.make_headers(basic_auth=auth[0] + ":" +
156+
auth[1]))
157+
request_headers.update(headers)
158+
response = self._https_session.request(method, url, headers=request_headers, body=body)
159+
return response
131160

132161
@staticmethod
133162
def _configure_basic_auth(url, conf):
@@ -158,6 +187,13 @@ def _send_request(self, url, method='GET', body=None, headers={}):
158187
if method not in VALID_METHODS:
159188
raise ClientError("Method {} is invalid; valid methods include {}".format(method, VALID_METHODS))
160189

190+
if url.startswith('https') and self._is_key_password_provided:
191+
response = self._send_https_session_request(url, method, headers, body)
192+
try:
193+
return json.loads(response.data), response.status
194+
except ValueError:
195+
return response.content, response.status
196+
161197
_headers = {'Accept': ACCEPT_HDR}
162198
if body:
163199
_headers["Content-Length"] = str(len(body))

tests/docker/.env.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ export MY_SCHEMA_REGISTRY_SSL_URL_ENV=https://$(hostname -f):8082
1313
export MY_SCHEMA_REGISTRY_SSL_CA_LOCATION_ENV=$TLS/ca-cert
1414
export MY_SCHEMA_REGISTRY_SSL_CERTIFICATE_LOCATION_ENV=$TLS/client.pem
1515
export MY_SCHEMA_REGISTRY_SSL_KEY_LOCATION_ENV=$TLS/client.key
16+
export MY_SCHEMA_REGISTRY_SSL_KEY_WITH_PASSWORD_LOCATION_ENV=$TLS/client_with_password.key
17+
export MY_SCHEMA_REGISTRY_SSL_KEY_PASSWORD="abcdefgh"

tests/docker/bin/certify.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ echo "Creating client cert..."
2424
${PY_DOCKER_BIN}/gen-ssl-certs.sh client ${TLS}/ca-cert ${TLS}/ ${HOST} ${HOST}
2525

2626
echo "Creating key ..."
27+
cp ${TLS}/client.key ${TLS}/client_with_password.key
2728
openssl rsa -in ${TLS}/client.key -out ${TLS}/client.key -passin pass:${PASS}
2829

tests/docker/docker-compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ services:
2121
KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer
2222
KAFKA_SUPER_USERS: "User:ANONYMOUS"
2323
schema-registry:
24-
image: confluentinc/cp-schema-registry:7.1.0
24+
image: confluentinc/cp-schema-registry
2525
depends_on:
2626
- zookeeper
2727
- kafka
@@ -42,7 +42,7 @@ services:
4242
SCHEMA_REGISTRY_SSL_TRUSTSTORE_PASSWORD: abcdefgh
4343
SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: zookeeper:2181
4444
schema-registry-basic-auth:
45-
image: confluentinc/cp-schema-registry:7.1.0
45+
image: confluentinc/cp-schema-registry
4646
depends_on:
4747
- zookeeper
4848
- kafka

tests/integration/integration_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,16 @@ def print_usage(exitcode, reason=None):
12541254
if 'avro-https' in modes:
12551255
print('=' * 30, 'Verifying AVRO with HTTPS', '=' * 30)
12561256
verify_avro_https(testconf.get('avro-https', None))
1257+
key_with_password_conf = testconf.get("avro-https-key-with-password", None)
1258+
print('=' * 30, 'Verifying AVRO with HTTPS Flow with Password',
1259+
'Protected Private Key of Cached-Schema-Registry-Client', '=' * 30)
1260+
verify_avro_https(key_with_password_conf)
1261+
print('Verifying Error with Wrong Password of Password Protected Private Key of Cached-Schema-Registry-Client')
1262+
try:
1263+
key_with_password_conf['schema.registry.ssl.key.password'] += '->wrongpassword'
1264+
verify_avro_https(key_with_password_conf)
1265+
except Exception:
1266+
print("Wrong Password Gives Error -> Successful")
12571267

12581268
if 'avro-basic-auth' in modes:
12591269
print("=" * 30, 'Verifying AVRO with Basic Auth', '=' * 30)

tests/integration/testconf.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@
33
"bootstrap.servers": "$MY_BOOTSTRAP_SERVER_ENV",
44
"schema.registry.url": "$MY_SCHEMA_REGISTRY_URL_ENV",
55
"avro-https": {
6-
"schema.registry.url": "$MY_SCHEMA_REGISTRY_SSL_URL_ENV",
7-
"schema.registry.ssl.ca.location": "$MY_SCHEMA_REGISTRY_SSL_CA_LOCATION_ENV",
8-
"schema.registry.ssl.certificate.location": "$MY_SCHEMA_REGISTRY_SSL_CERTIFICATE_LOCATION_ENV",
9-
"schema.registry.ssl.key.location": "$MY_SCHEMA_REGISTRY_SSL_KEY_LOCATION_ENV"
6+
"schema.registry.url": "$MY_SCHEMA_REGISTRY_SSL_URL_ENV",
7+
"schema.registry.ssl.ca.location": "$MY_SCHEMA_REGISTRY_SSL_CA_LOCATION_ENV",
8+
"schema.registry.ssl.certificate.location": "$MY_SCHEMA_REGISTRY_SSL_CERTIFICATE_LOCATION_ENV",
9+
"schema.registry.ssl.key.location": "$MY_SCHEMA_REGISTRY_SSL_KEY_LOCATION_ENV"
1010
},
1111
"avro-basic-auth": {
1212
"schema.registry.url": "http://localhost:8083",
1313
"schema.registry.basic.auth.user.info": "ckp_tester:test_secret",
1414
"sasl.username": "ckp_tester",
1515
"sasl.password": "test_secret"
16+
},
17+
"avro-https-key-with-password": {
18+
"schema.registry.url": "$MY_SCHEMA_REGISTRY_SSL_URL_ENV",
19+
"schema.registry.ssl.ca.location": "$MY_SCHEMA_REGISTRY_SSL_CA_LOCATION_ENV",
20+
"schema.registry.ssl.certificate.location": "$MY_SCHEMA_REGISTRY_SSL_CERTIFICATE_LOCATION_ENV",
21+
"schema.registry.ssl.key.location": "$MY_SCHEMA_REGISTRY_SSL_KEY_WITH_PASSWORD_LOCATION_ENV",
22+
"schema.registry.ssl.key.password": "$MY_SCHEMA_REGISTRY_SSL_KEY_PASSWORD"
1623
}
1724
}

0 commit comments

Comments
 (0)