@platformatic/rdkafka 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/bench/seed.sh +0 -0
- package/binding.gyp +8 -1
- package/ci/prepublish.js +0 -1
- package/config.d.ts +126 -10
- package/configure +0 -0
- package/deps/librdkafka/.semaphore/run-all-tests.yml +77 -0
- package/deps/librdkafka/.semaphore/semaphore-integration.yml +250 -0
- package/deps/librdkafka/.semaphore/semaphore.yml +67 -44
- package/deps/librdkafka/.semaphore/verify-linux-packages.yml +41 -0
- package/deps/librdkafka/CHANGELOG.md +471 -1
- package/deps/librdkafka/CONFIGURATION.md +29 -9
- package/deps/librdkafka/INTRODUCTION.md +909 -601
- package/deps/librdkafka/LICENSES.txt +592 -1
- package/deps/librdkafka/Makefile +2 -1
- package/deps/librdkafka/_mkltmpuuWEwa.c +13 -0
- package/deps/librdkafka/configure +0 -0
- package/deps/librdkafka/debian/rules +0 -0
- package/deps/librdkafka/dev-conf.sh +0 -0
- package/deps/librdkafka/examples/consumer.c +18 -8
- package/deps/librdkafka/examples/describe_consumer_groups.c +25 -6
- package/deps/librdkafka/examples/incremental_alter_configs.c +4 -5
- package/deps/librdkafka/examples/kafkatest_verifiable_client.cpp +26 -42
- package/deps/librdkafka/examples/openssl_engine_example.cpp +3 -4
- package/deps/librdkafka/examples/producer.cpp +2 -2
- package/deps/librdkafka/examples/user_scram.c +4 -5
- package/deps/librdkafka/examples/win_ssl_cert_store.cpp +4 -3
- package/deps/librdkafka/lds-gen.py +0 -0
- package/deps/librdkafka/mklove/Makefile.base +0 -0
- package/deps/librdkafka/packaging/RELEASE.md +8 -0
- package/deps/librdkafka/packaging/alpine/build-alpine.sh +0 -0
- package/deps/librdkafka/packaging/cp/README.md +4 -1
- package/deps/librdkafka/packaging/cp/check_features.c +12 -4
- package/deps/librdkafka/packaging/cp/verify-deb.sh +6 -6
- package/deps/librdkafka/packaging/cp/verify-packages.sh +52 -31
- package/deps/librdkafka/packaging/cp/verify-rpm.sh +6 -13
- package/deps/librdkafka/packaging/debian/rules +0 -0
- package/deps/librdkafka/packaging/get_version.py +0 -0
- package/deps/librdkafka/packaging/homebrew/brew-update-pr.sh +0 -0
- package/deps/librdkafka/packaging/mingw-w64/{travis-before-install.sh → export-variables.sh} +0 -7
- package/deps/librdkafka/packaging/mingw-w64/semaphoreci-build.sh +1 -1
- package/deps/librdkafka/packaging/nuget/artifact.py +0 -0
- package/deps/librdkafka/packaging/nuget/cleanup-s3.py +0 -0
- package/deps/librdkafka/packaging/nuget/nuget.sh +0 -0
- package/deps/librdkafka/packaging/nuget/packaging.py +0 -0
- package/deps/librdkafka/packaging/nuget/push-to-nuget.sh +0 -0
- package/deps/librdkafka/packaging/nuget/release.py +0 -0
- package/deps/librdkafka/packaging/nuget/requirements.txt +3 -3
- package/deps/librdkafka/packaging/rpm/mock-on-docker.sh +0 -0
- package/deps/librdkafka/packaging/rpm/tests/run-test.sh +0 -0
- package/deps/librdkafka/packaging/rpm/tests/test-on-docker.sh +0 -0
- package/deps/librdkafka/packaging/tools/build-configurations-checks.sh +3 -1
- package/deps/librdkafka/packaging/tools/build-deb-package.sh +0 -0
- package/deps/librdkafka/packaging/tools/build-debian.sh +0 -0
- package/deps/librdkafka/packaging/tools/build-manylinux.sh +0 -0
- package/deps/librdkafka/packaging/tools/build-release-artifacts.sh +0 -0
- package/deps/librdkafka/packaging/tools/distro-build.sh +0 -0
- package/deps/librdkafka/packaging/tools/gh-release-checksums.py +0 -0
- package/deps/librdkafka/packaging/tools/rdutcoverage.sh +0 -0
- package/deps/librdkafka/packaging/tools/run-in-docker.sh +28 -0
- package/deps/librdkafka/packaging/tools/run-integration-tests.sh +31 -0
- package/deps/librdkafka/packaging/tools/run-style-check.sh +4 -0
- package/deps/librdkafka/packaging/tools/style-format.sh +6 -5
- package/deps/librdkafka/packaging/tools/update_rpcs_max_versions.py +100 -0
- package/deps/librdkafka/service.yml +154 -0
- package/deps/librdkafka/src/generate_proto.sh +0 -0
- package/deps/librdkafka/src/rd.h +145 -2
- package/deps/librdkafka/src/rdaddr.h +6 -7
- package/deps/librdkafka/src/rdatomic.h +15 -11
- package/deps/librdkafka/src/rdavg.h +3 -2
- package/deps/librdkafka/src/rdbase64.c +33 -2
- package/deps/librdkafka/src/rdbase64.h +2 -0
- package/deps/librdkafka/src/rdendian.h +10 -9
- package/deps/librdkafka/src/rdhttp.c +418 -100
- package/deps/librdkafka/src/rdhttp.h +23 -5
- package/deps/librdkafka/src/rdkafka.c +299 -145
- package/deps/librdkafka/src/rdkafka.h +89 -6
- package/deps/librdkafka/src/rdkafka_admin.c +482 -105
- package/deps/librdkafka/src/rdkafka_admin.h +42 -0
- package/deps/librdkafka/src/rdkafka_broker.c +463 -312
- package/deps/librdkafka/src/rdkafka_broker.h +68 -10
- package/deps/librdkafka/src/rdkafka_buf.c +8 -5
- package/deps/librdkafka/src/rdkafka_buf.h +4 -3
- package/deps/librdkafka/src/rdkafka_cert.c +25 -1
- package/deps/librdkafka/src/rdkafka_cert.h +4 -3
- package/deps/librdkafka/src/rdkafka_cgrp.c +468 -143
- package/deps/librdkafka/src/rdkafka_cgrp.h +36 -2
- package/deps/librdkafka/src/rdkafka_conf.c +526 -102
- package/deps/librdkafka/src/rdkafka_conf.h +68 -4
- package/deps/librdkafka/src/rdkafka_feature.c +472 -35
- package/deps/librdkafka/src/rdkafka_feature.h +2 -0
- package/deps/librdkafka/src/rdkafka_fetcher.c +22 -2
- package/deps/librdkafka/src/rdkafka_fetcher.h +2 -2
- package/deps/librdkafka/src/rdkafka_int.h +84 -46
- package/deps/librdkafka/src/rdkafka_metadata.c +221 -136
- package/deps/librdkafka/src/rdkafka_metadata.h +15 -11
- package/deps/librdkafka/src/rdkafka_metadata_cache.c +279 -73
- package/deps/librdkafka/src/rdkafka_mock.c +795 -19
- package/deps/librdkafka/src/rdkafka_mock.h +119 -0
- package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1298 -132
- package/deps/librdkafka/src/rdkafka_mock_handlers.c +345 -49
- package/deps/librdkafka/src/rdkafka_mock_int.h +180 -61
- package/deps/librdkafka/src/rdkafka_msg.c +35 -19
- package/deps/librdkafka/src/rdkafka_msg.h +3 -3
- package/deps/librdkafka/src/rdkafka_msgset_writer.c +14 -10
- package/deps/librdkafka/src/rdkafka_offset.c +33 -5
- package/deps/librdkafka/src/rdkafka_op.c +1 -1
- package/deps/librdkafka/src/rdkafka_op.h +45 -21
- package/deps/librdkafka/src/rdkafka_partition.c +118 -23
- package/deps/librdkafka/src/rdkafka_partition.h +16 -7
- package/deps/librdkafka/src/rdkafka_proto.h +4 -1
- package/deps/librdkafka/src/rdkafka_protocol.h +1 -0
- package/deps/librdkafka/src/rdkafka_queue.c +125 -48
- package/deps/librdkafka/src/rdkafka_queue.h +19 -0
- package/deps/librdkafka/src/rdkafka_range_assignor.c +1 -1
- package/deps/librdkafka/src/rdkafka_request.c +336 -64
- package/deps/librdkafka/src/rdkafka_request.h +24 -1
- package/deps/librdkafka/src/rdkafka_sasl.c +5 -3
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +14 -10
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1166 -89
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +13 -3
- package/deps/librdkafka/src/rdkafka_sasl_plain.c +3 -3
- package/deps/librdkafka/src/rdkafka_sasl_scram.c +4 -3
- package/deps/librdkafka/src/rdkafka_ssl.c +306 -81
- package/deps/librdkafka/src/rdkafka_ssl.h +22 -0
- package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4 -0
- package/deps/librdkafka/src/rdkafka_telemetry.c +80 -23
- package/deps/librdkafka/src/rdkafka_telemetry_encode.c +5 -1
- package/deps/librdkafka/src/rdkafka_telemetry_encode.h +20 -20
- package/deps/librdkafka/src/rdkafka_topic.c +220 -137
- package/deps/librdkafka/src/rdkafka_topic.h +14 -8
- package/deps/librdkafka/src/rdkafka_transport.c +59 -47
- package/deps/librdkafka/src/rdkafka_transport.h +5 -0
- package/deps/librdkafka/src/rdkafka_txnmgr.c +6 -1
- package/deps/librdkafka/src/rdmap.c +6 -1
- package/deps/librdkafka/src/rdrand.c +11 -1
- package/deps/librdkafka/src/rdtime.h +48 -17
- package/deps/librdkafka/src/rdunittest.c +42 -25
- package/deps/librdkafka/src/rdunittest.h +2 -0
- package/deps/librdkafka/src/rdwin32.h +1 -1
- package/deps/librdkafka/src/regexp.c +47 -42
- package/deps/librdkafka/src/tinycthread_extra.c +45 -21
- package/deps/librdkafka/src/tinycthread_extra.h +7 -3
- package/deps/librdkafka/src-cpp/rdkafkacpp.h +28 -2
- package/deps/librdkafka/tests/0001-multiobj.c +4 -0
- package/deps/librdkafka/tests/0004-conf.c +103 -36
- package/deps/librdkafka/tests/0009-mock_cluster.c +1 -0
- package/deps/librdkafka/tests/0011-produce_batch.c +2 -1
- package/deps/librdkafka/tests/0016-client_swname.c +18 -3
- package/deps/librdkafka/tests/0017-compression.c +6 -8
- package/deps/librdkafka/tests/0018-cgrp_term.c +8 -3
- package/deps/librdkafka/tests/0021-rkt_destroy.c +1 -0
- package/deps/librdkafka/tests/0022-consume_batch.c +4 -1
- package/deps/librdkafka/tests/0026-consume_pause.c +12 -14
- package/deps/librdkafka/tests/0028-long_topicnames.c +1 -1
- package/deps/librdkafka/tests/0029-assign_offset.c +6 -8
- package/deps/librdkafka/tests/0030-offset_commit.c +2 -2
- package/deps/librdkafka/tests/0033-regex_subscribe.c +33 -17
- package/deps/librdkafka/tests/0034-offset_reset.c +25 -4
- package/deps/librdkafka/tests/0036-partial_fetch.c +1 -0
- package/deps/librdkafka/tests/0038-performance.c +1 -0
- package/deps/librdkafka/tests/0040-io_event.c +11 -5
- package/deps/librdkafka/tests/0041-fetch_max_bytes.c +1 -0
- package/deps/librdkafka/tests/0044-partition_cnt.c +3 -2
- package/deps/librdkafka/tests/0045-subscribe_update.c +284 -20
- package/deps/librdkafka/tests/0047-partial_buf_tmout.c +1 -0
- package/deps/librdkafka/tests/0048-partitioner.c +1 -1
- package/deps/librdkafka/tests/0050-subscribe_adds.c +1 -0
- package/deps/librdkafka/tests/0051-assign_adds.c +1 -0
- package/deps/librdkafka/tests/0052-msg_timestamps.c +22 -4
- package/deps/librdkafka/tests/0053-stats_cb.cpp +12 -20
- package/deps/librdkafka/tests/0055-producer_latency.c +176 -3
- package/deps/librdkafka/tests/0056-balanced_group_mt.c +6 -2
- package/deps/librdkafka/tests/0058-log.cpp +3 -3
- package/deps/librdkafka/tests/0059-bsearch.cpp +8 -4
- package/deps/librdkafka/tests/0061-consumer_lag.cpp +25 -5
- package/deps/librdkafka/tests/0067-empty_topic.cpp +3 -0
- package/deps/librdkafka/tests/0068-produce_timeout.c +21 -23
- package/deps/librdkafka/tests/0069-consumer_add_parts.c +3 -7
- package/deps/librdkafka/tests/0075-retry.c +38 -1
- package/deps/librdkafka/tests/0076-produce_retry.c +2 -0
- package/deps/librdkafka/tests/0077-compaction.c +8 -2
- package/deps/librdkafka/tests/0080-admin_ut.c +34 -4
- package/deps/librdkafka/tests/0081-admin.c +455 -155
- package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +4 -0
- package/deps/librdkafka/tests/0083-cb_event.c +8 -3
- package/deps/librdkafka/tests/0084-destroy_flags.c +14 -18
- package/deps/librdkafka/tests/0085-headers.cpp +4 -0
- package/deps/librdkafka/tests/0086-purge.c +40 -7
- package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +1 -1
- package/deps/librdkafka/tests/0089-max_poll_interval.c +12 -7
- package/deps/librdkafka/tests/0090-idempotence.c +1 -2
- package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +5 -7
- package/deps/librdkafka/tests/0092-mixed_msgver.c +6 -0
- package/deps/librdkafka/tests/0093-holb.c +8 -5
- package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +1 -0
- package/deps/librdkafka/tests/0097-ssl_verify.cpp +262 -70
- package/deps/librdkafka/tests/0098-consumer-txn.cpp +16 -16
- package/deps/librdkafka/tests/0099-commit_metadata.c +1 -1
- package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +1 -1
- package/deps/librdkafka/tests/0102-static_group_rebalance.c +317 -16
- package/deps/librdkafka/tests/0103-transactions.c +7 -7
- package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +1 -0
- package/deps/librdkafka/tests/0105-transactions_mock.c +18 -11
- package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +35 -14
- package/deps/librdkafka/tests/0107-topic_recreate.c +2 -2
- package/deps/librdkafka/tests/0109-auto_create_topics.cpp +86 -26
- package/deps/librdkafka/tests/0110-batch_size.cpp +1 -2
- package/deps/librdkafka/tests/0112-assign_unknown_part.c +3 -14
- package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +248 -104
- package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +3 -3
- package/deps/librdkafka/tests/0115-producer_auth.cpp +4 -1
- package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +7 -5
- package/deps/librdkafka/tests/0117-mock_errors.c +15 -5
- package/deps/librdkafka/tests/0118-commit_rebalance.c +41 -8
- package/deps/librdkafka/tests/0119-consumer_auth.cpp +20 -1
- package/deps/librdkafka/tests/0120-asymmetric_subscription.c +8 -3
- package/deps/librdkafka/tests/0125-immediate_flush.c +1 -1
- package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +323 -8
- package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +1 -0
- package/deps/librdkafka/tests/0132-strategy_ordering.c +12 -4
- package/deps/librdkafka/tests/0133-ssl_keys.c +28 -6
- package/deps/librdkafka/tests/0137-barrier_batch_consume.c +24 -14
- package/deps/librdkafka/tests/0139-offset_validation_mock.c +510 -2
- package/deps/librdkafka/tests/0140-commit_metadata.cpp +1 -1
- package/deps/librdkafka/tests/0142-reauthentication.c +37 -17
- package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +39 -40
- package/deps/librdkafka/tests/0146-metadata_mock.c +76 -15
- package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +952 -0
- package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +563 -0
- package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
- package/deps/librdkafka/tests/0150-telemetry_mock.c +9 -6
- package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
- package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
- package/deps/librdkafka/tests/0153-memberid.c +128 -0
- package/deps/librdkafka/tests/CMakeLists.txt +6 -0
- package/deps/librdkafka/tests/LibrdkafkaTestApp.py +34 -9
- package/deps/librdkafka/tests/autotest.sh +0 -0
- package/deps/librdkafka/tests/broker_version_tests.py +0 -0
- package/deps/librdkafka/tests/buildbox.sh +0 -0
- package/deps/librdkafka/tests/cleanup-checker-tests.sh +0 -0
- package/deps/librdkafka/tests/cluster_testing.py +0 -0
- package/deps/librdkafka/tests/delete-test-topics.sh +0 -0
- package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
- package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
- package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +42 -101
- package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.key +41 -29
- package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +112 -37
- package/deps/librdkafka/tests/gen-ssl-certs.sh +0 -0
- package/deps/librdkafka/tests/interactive_broker_version.py +0 -0
- package/deps/librdkafka/tests/java/Makefile +2 -1
- package/deps/librdkafka/tests/java/run-class.sh +0 -0
- package/deps/librdkafka/tests/lz4_manual_test.sh +0 -0
- package/deps/librdkafka/tests/multi-broker-version-test.sh +0 -0
- package/deps/librdkafka/tests/parse-refcnt.sh +0 -0
- package/deps/librdkafka/tests/performance_plot.py +0 -0
- package/deps/librdkafka/tests/requirements.txt +1 -1
- package/deps/librdkafka/tests/run-all-tests.sh +79 -0
- package/deps/librdkafka/tests/run-consumer-tests.sh +0 -0
- package/deps/librdkafka/tests/run-producer-tests.sh +0 -0
- package/deps/librdkafka/tests/run-test-batches.py +157 -0
- package/deps/librdkafka/tests/run-test.sh +0 -0
- package/deps/librdkafka/tests/sasl_test.py +0 -0
- package/deps/librdkafka/tests/test.c +478 -18
- package/deps/librdkafka/tests/test.h +62 -13
- package/deps/librdkafka/tests/testcpp.cpp +6 -1
- package/deps/librdkafka/tests/testcpp.h +30 -2
- package/deps/librdkafka/tests/testshared.h +16 -2
- package/deps/librdkafka/tests/tools/stats/graph.py +0 -0
- package/deps/librdkafka/tests/tools/stats/to_csv.py +1 -1
- package/deps/librdkafka/tests/trivup/trivup-0.14.0.tar.gz +0 -0
- package/deps/librdkafka/tests/until-fail.sh +0 -0
- package/deps/librdkafka/tests/xxxx-metadata.cpp +1 -1
- package/deps/librdkafka/vcpkg.json +1 -1
- package/deps/librdkafka/win32/setup-msys2.ps1 +24 -8
- package/deps/librdkafka/win32/setup-vcpkg.ps1 +28 -6
- package/deps/librdkafka/win32/tests/tests.vcxproj +6 -0
- package/deps/librdkafka/win32/wingetopt.h +5 -4
- package/errors.d.ts +27 -1
- package/lib/error.js +29 -3
- package/make_docs.sh +0 -0
- package/package.json +11 -11
- package/run_docker.sh +0 -0
- package/src/callbacks.cc +6 -9
- package/src/callbacks.h +1 -5
- package/test/kafka-consumer-worker.js +10 -7
- package/test/kafka-consumer.spec.js +32 -21
- package/ci/update-version.js +0 -123
- package/deps/librdkafka/.semaphore/project.yml +0 -43
- package/deps/librdkafka/.semaphore/project_public.yml +0 -20
- package/deps/librdkafka/win32/install-openssl.ps1 +0 -33
|
@@ -51,8 +51,9 @@
|
|
|
51
51
|
*
|
|
52
52
|
* @locality Any thread.
|
|
53
53
|
*/
|
|
54
|
-
static char *
|
|
55
|
-
|
|
54
|
+
static char *
|
|
55
|
+
rd_kafka_oidc_client_credentials_build_auth_header(const char *client_id,
|
|
56
|
+
const char *client_secret) {
|
|
56
57
|
|
|
57
58
|
rd_chariov_t client_authorization_in;
|
|
58
59
|
rd_chariov_t client_authorization_out;
|
|
@@ -90,13 +91,15 @@ static char *rd_kafka_oidc_build_auth_header(const char *client_id,
|
|
|
90
91
|
*
|
|
91
92
|
* @locality Any thread.
|
|
92
93
|
*/
|
|
93
|
-
static void
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
static void
|
|
95
|
+
rd_kafka_oidc_client_credentials_build_headers(const char *client_id,
|
|
96
|
+
const char *client_secret,
|
|
97
|
+
struct curl_slist **headersp) {
|
|
96
98
|
char *authorization_base64_header;
|
|
97
99
|
|
|
98
100
|
authorization_base64_header =
|
|
99
|
-
|
|
101
|
+
rd_kafka_oidc_client_credentials_build_auth_header(client_id,
|
|
102
|
+
client_secret);
|
|
100
103
|
|
|
101
104
|
*headersp = curl_slist_append(*headersp, "Accept: application/json");
|
|
102
105
|
*headersp = curl_slist_append(*headersp, authorization_base64_header);
|
|
@@ -198,9 +201,10 @@ done:
|
|
|
198
201
|
* The post_fields_size will be returned in \p post_fields_size.
|
|
199
202
|
*
|
|
200
203
|
*/
|
|
201
|
-
static void
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
static void
|
|
205
|
+
rd_kafka_oidc_client_credentials_build_post_fields(const char *scope,
|
|
206
|
+
char **post_fields,
|
|
207
|
+
size_t *post_fields_size) {
|
|
204
208
|
size_t scope_size = 0;
|
|
205
209
|
|
|
206
210
|
if (scope)
|
|
@@ -217,6 +221,684 @@ static void rd_kafka_oidc_build_post_fields(const char *scope,
|
|
|
217
221
|
}
|
|
218
222
|
}
|
|
219
223
|
|
|
224
|
+
/**
|
|
225
|
+
* @brief Get JWT algorithm label string for the specified signing algorithm.
|
|
226
|
+
*
|
|
227
|
+
* @param token_signing_algo The algorithm enum value
|
|
228
|
+
*
|
|
229
|
+
* @returns String representation of the algorithm.
|
|
230
|
+
*
|
|
231
|
+
* @locality Any thread.
|
|
232
|
+
*/
|
|
233
|
+
static char *rd_kafka_oidc_assertion_get_algo_label(
|
|
234
|
+
const rd_kafka_oauthbearer_assertion_algorithm_t token_signing_algo) {
|
|
235
|
+
switch (token_signing_algo) {
|
|
236
|
+
case RD_KAFKA_SASL_OAUTHBEARER_ASSERTION_ALGORITHM_RS256:
|
|
237
|
+
return "RS256";
|
|
238
|
+
case RD_KAFKA_SASL_OAUTHBEARER_ASSERTION_ALGORITHM_ES256:
|
|
239
|
+
return "ES256";
|
|
240
|
+
default:
|
|
241
|
+
rd_assert(!*"Unknown JOSE algorithm");
|
|
242
|
+
return NULL;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @brief Parse a JWT template file and extract header and payload JSON
|
|
248
|
+
* objects.
|
|
249
|
+
*
|
|
250
|
+
* Reads and parses the JWT template file, which should contain a JSON object
|
|
251
|
+
* with "header" and "payload" properties.
|
|
252
|
+
*
|
|
253
|
+
* @param rk
|
|
254
|
+
* @param jwt_template_file_path Path to the template file
|
|
255
|
+
* @param header Pointer to store the parsed header JSON object
|
|
256
|
+
* @param payload Pointer to store the parsed payload JSON object
|
|
257
|
+
*
|
|
258
|
+
* @returns 0 on success, -1 on failure
|
|
259
|
+
*
|
|
260
|
+
* @locality Any thread.
|
|
261
|
+
*/
|
|
262
|
+
static int
|
|
263
|
+
rd_kafka_oidc_assertion_parse_template_file(rd_kafka_t *rk,
|
|
264
|
+
const char *jwt_template_file_path,
|
|
265
|
+
cJSON **header,
|
|
266
|
+
cJSON **payload) {
|
|
267
|
+
char *template_content = NULL;
|
|
268
|
+
cJSON *template_json = NULL;
|
|
269
|
+
int ret = -1;
|
|
270
|
+
size_t file_size;
|
|
271
|
+
|
|
272
|
+
*header = NULL;
|
|
273
|
+
*payload = NULL;
|
|
274
|
+
|
|
275
|
+
template_content =
|
|
276
|
+
rd_file_read(jwt_template_file_path, &file_size, 1024 * 1024);
|
|
277
|
+
if (!template_content) {
|
|
278
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
279
|
+
"Failed to open JWT template file: %s",
|
|
280
|
+
jwt_template_file_path);
|
|
281
|
+
return -1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (file_size == 0) {
|
|
285
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
286
|
+
"JWT template file is empty or invalid");
|
|
287
|
+
rd_free(template_content);
|
|
288
|
+
return -1;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
template_json = cJSON_Parse((char *)template_content);
|
|
292
|
+
if (!template_json) {
|
|
293
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
294
|
+
"Failed to parse JWT template JSON");
|
|
295
|
+
goto cleanup;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
cJSON *header_item = cJSON_GetObjectItem(template_json, "header");
|
|
299
|
+
cJSON *payload_item = cJSON_GetObjectItem(template_json, "payload");
|
|
300
|
+
|
|
301
|
+
if (!header_item || !payload_item) {
|
|
302
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
303
|
+
"JWT template must contain both 'header' "
|
|
304
|
+
"and 'payload' objects");
|
|
305
|
+
goto cleanup;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
*header = cJSON_Duplicate(header_item, 1);
|
|
309
|
+
*payload = cJSON_Duplicate(payload_item, 1);
|
|
310
|
+
|
|
311
|
+
if (!*header || !*payload) {
|
|
312
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
313
|
+
"Failed to duplicate header or payload objects");
|
|
314
|
+
if (*header) {
|
|
315
|
+
cJSON_Delete(*header);
|
|
316
|
+
*header = NULL;
|
|
317
|
+
}
|
|
318
|
+
goto cleanup;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
ret = 0;
|
|
322
|
+
|
|
323
|
+
cleanup:
|
|
324
|
+
if (template_content)
|
|
325
|
+
rd_free(template_content);
|
|
326
|
+
if (template_json)
|
|
327
|
+
cJSON_Delete(template_json);
|
|
328
|
+
|
|
329
|
+
return ret;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @brief Create JWT assertion.
|
|
334
|
+
*
|
|
335
|
+
* Creates a JWT token signed with the specified private key using the
|
|
336
|
+
* algorithm specified. The token can be created from a template file or
|
|
337
|
+
* will create a minimal default token if no template is provided.
|
|
338
|
+
*
|
|
339
|
+
* @param rk The rd_kafka_t instance for logging
|
|
340
|
+
* @param private_key_pem PEM formatted private key string (mutually exclusive
|
|
341
|
+
* with key_file_location)
|
|
342
|
+
* @param key_file_location Path to private key file (mutually exclusive with
|
|
343
|
+
* private_key_pem)
|
|
344
|
+
* @param passphrase Optional passphrase for encrypted private key
|
|
345
|
+
* @param token_signing_algo Algorithm to use for signing (RS256 or ES256)
|
|
346
|
+
* @param jwt_template_file Optional path to JWT template file
|
|
347
|
+
* @param subject Optional subject claim value.
|
|
348
|
+
* @param issuer Optional issuer claim value.
|
|
349
|
+
* @param audience Optional audience claim value.
|
|
350
|
+
* @param nbf `nbf` claim value to express seconds of validity in the past.
|
|
351
|
+
* @param exp `exp` claim value to express seconds of validity in the future.
|
|
352
|
+
* @param jti_include Whether to include a JTI claim (UUID)
|
|
353
|
+
*
|
|
354
|
+
* @returns Newly allocated JWT string, caller must free with rd_free(). NULL on
|
|
355
|
+
* error.
|
|
356
|
+
*
|
|
357
|
+
* @locality Any thread.
|
|
358
|
+
*/
|
|
359
|
+
static char *rd_kafka_oidc_assertion_create(
|
|
360
|
+
rd_kafka_t *rk,
|
|
361
|
+
const char *private_key_pem,
|
|
362
|
+
const char *key_file_location,
|
|
363
|
+
const char *passphrase,
|
|
364
|
+
const rd_kafka_oauthbearer_assertion_algorithm_t token_signing_algo,
|
|
365
|
+
const char *jwt_template_file,
|
|
366
|
+
const char *subject,
|
|
367
|
+
const char *issuer,
|
|
368
|
+
const char *audience,
|
|
369
|
+
const int nbf,
|
|
370
|
+
const int exp,
|
|
371
|
+
const rd_bool_t jti_include) {
|
|
372
|
+
|
|
373
|
+
char *encoded_header = NULL;
|
|
374
|
+
char *encoded_payload = NULL;
|
|
375
|
+
char *encoded_signature = NULL;
|
|
376
|
+
char *unsigned_token = NULL;
|
|
377
|
+
char *result = NULL;
|
|
378
|
+
char *header_str = NULL;
|
|
379
|
+
char *payload_str = NULL;
|
|
380
|
+
EVP_PKEY *pkey = NULL;
|
|
381
|
+
BIO *bio = NULL;
|
|
382
|
+
cJSON *header_json_obj = NULL;
|
|
383
|
+
cJSON *payload_json_obj = NULL;
|
|
384
|
+
EVP_MD_CTX *mdctx = NULL;
|
|
385
|
+
unsigned char *sig = NULL;
|
|
386
|
+
rd_chariov_t header_iov;
|
|
387
|
+
rd_chariov_t payload_iov;
|
|
388
|
+
rd_chariov_t sig_iov;
|
|
389
|
+
rd_kafka_Uuid_t jti_uuid;
|
|
390
|
+
char *jti_uuid_str = NULL;
|
|
391
|
+
|
|
392
|
+
rd_ts_t issued_at = rd_uclock() / 1000000;
|
|
393
|
+
rd_ts_t not_before = issued_at - nbf;
|
|
394
|
+
rd_ts_t expiration_time = issued_at + exp;
|
|
395
|
+
|
|
396
|
+
if (jwt_template_file) {
|
|
397
|
+
if (rd_kafka_oidc_assertion_parse_template_file(
|
|
398
|
+
rk, jwt_template_file, &header_json_obj,
|
|
399
|
+
&payload_json_obj) != 0) {
|
|
400
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
401
|
+
"Failed to process JWT template file %s",
|
|
402
|
+
jwt_template_file);
|
|
403
|
+
return NULL;
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
header_json_obj = cJSON_CreateObject();
|
|
407
|
+
payload_json_obj = cJSON_CreateObject();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* Add required header fields */
|
|
411
|
+
cJSON_DeleteItemFromObjectCaseSensitive(header_json_obj, "alg");
|
|
412
|
+
cJSON_DeleteItemFromObjectCaseSensitive(header_json_obj, "typ");
|
|
413
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj, "iat");
|
|
414
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj, "exp");
|
|
415
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj, "nbf");
|
|
416
|
+
cJSON_AddStringToObject(
|
|
417
|
+
header_json_obj, "alg",
|
|
418
|
+
rd_kafka_oidc_assertion_get_algo_label(token_signing_algo));
|
|
419
|
+
cJSON_AddStringToObject(header_json_obj, "typ", "JWT");
|
|
420
|
+
|
|
421
|
+
/* Add required payload fields */
|
|
422
|
+
cJSON_AddNumberToObject(payload_json_obj, "iat", (double)issued_at);
|
|
423
|
+
cJSON_AddNumberToObject(payload_json_obj, "exp",
|
|
424
|
+
(double)expiration_time);
|
|
425
|
+
cJSON_AddNumberToObject(payload_json_obj, "nbf", (double)not_before);
|
|
426
|
+
|
|
427
|
+
if (subject) {
|
|
428
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj,
|
|
429
|
+
"sub");
|
|
430
|
+
cJSON_AddStringToObject(payload_json_obj, "sub", subject);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (issuer) {
|
|
434
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj,
|
|
435
|
+
"iss");
|
|
436
|
+
cJSON_AddStringToObject(payload_json_obj, "iss", issuer);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (audience) {
|
|
440
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj,
|
|
441
|
+
"aud");
|
|
442
|
+
cJSON_AddStringToObject(payload_json_obj, "aud", audience);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (jti_include) {
|
|
446
|
+
jti_uuid = rd_kafka_Uuid_random();
|
|
447
|
+
jti_uuid_str = rd_kafka_Uuid_str(&jti_uuid);
|
|
448
|
+
cJSON_DeleteItemFromObjectCaseSensitive(payload_json_obj,
|
|
449
|
+
"jti");
|
|
450
|
+
cJSON_AddStringToObject(payload_json_obj, "jti", jti_uuid_str);
|
|
451
|
+
rd_free(jti_uuid_str);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
header_str = cJSON_PrintUnformatted(header_json_obj);
|
|
455
|
+
payload_str = cJSON_PrintUnformatted(payload_json_obj);
|
|
456
|
+
|
|
457
|
+
if (!header_str || !payload_str) {
|
|
458
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
459
|
+
"Failed to convert template objects to JSON");
|
|
460
|
+
goto cleanup;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
header_iov.ptr = header_str;
|
|
464
|
+
header_iov.size = strlen(header_str);
|
|
465
|
+
encoded_header = rd_base64_encode_str_urlsafe(&header_iov);
|
|
466
|
+
|
|
467
|
+
payload_iov.ptr = payload_str;
|
|
468
|
+
payload_iov.size = strlen(payload_str);
|
|
469
|
+
encoded_payload = rd_base64_encode_str_urlsafe(&payload_iov);
|
|
470
|
+
if (!encoded_header || !encoded_payload)
|
|
471
|
+
goto cleanup;
|
|
472
|
+
|
|
473
|
+
size_t unsigned_token_len =
|
|
474
|
+
strlen(encoded_header) + strlen(encoded_payload) + 2;
|
|
475
|
+
unsigned_token = rd_malloc(unsigned_token_len);
|
|
476
|
+
|
|
477
|
+
if (!unsigned_token)
|
|
478
|
+
goto cleanup;
|
|
479
|
+
rd_snprintf(unsigned_token, unsigned_token_len, "%s.%s", encoded_header,
|
|
480
|
+
encoded_payload);
|
|
481
|
+
|
|
482
|
+
if (private_key_pem) {
|
|
483
|
+
bio = BIO_new_mem_buf((void *)private_key_pem, -1);
|
|
484
|
+
} else if (key_file_location) {
|
|
485
|
+
bio = BIO_new_file(key_file_location, "r");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (!bio) {
|
|
489
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
490
|
+
"Failed to create BIO for private key");
|
|
491
|
+
goto cleanup;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (passphrase) {
|
|
495
|
+
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL,
|
|
496
|
+
(void *)passphrase);
|
|
497
|
+
} else {
|
|
498
|
+
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
|
499
|
+
}
|
|
500
|
+
BIO_free(bio);
|
|
501
|
+
bio = NULL;
|
|
502
|
+
|
|
503
|
+
if (!pkey) {
|
|
504
|
+
rd_kafka_log(rk, LOG_ERR, "JWT", "Failed to load private key");
|
|
505
|
+
goto cleanup;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
mdctx = EVP_MD_CTX_new();
|
|
509
|
+
if (!mdctx) {
|
|
510
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
511
|
+
"Failed to create message digest context");
|
|
512
|
+
goto cleanup;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const EVP_MD *md = EVP_sha256(); /* Both RS256 and ES256 use SHA-256 */
|
|
516
|
+
|
|
517
|
+
if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkey) != 1) {
|
|
518
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
519
|
+
"Failed to initialize signing context");
|
|
520
|
+
goto cleanup;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (EVP_DigestSignUpdate(mdctx, unsigned_token,
|
|
524
|
+
strlen(unsigned_token)) != 1) {
|
|
525
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
526
|
+
"Failed to update digest with token data");
|
|
527
|
+
goto cleanup;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
size_t siglen = 0;
|
|
531
|
+
if (EVP_DigestSignFinal(mdctx, NULL, &siglen) != 1) {
|
|
532
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
533
|
+
"Failed to get signature length");
|
|
534
|
+
goto cleanup;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
sig = rd_malloc(siglen);
|
|
538
|
+
if (!sig) {
|
|
539
|
+
rd_kafka_log(rk, LOG_ERR, "JWT",
|
|
540
|
+
"Failed to allocate memory for signature");
|
|
541
|
+
goto cleanup;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (EVP_DigestSignFinal(mdctx, sig, &siglen) != 1) {
|
|
545
|
+
rd_kafka_log(rk, LOG_ERR, "JWT", "Failed to create signature");
|
|
546
|
+
goto cleanup;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
sig_iov.ptr = (char *)sig;
|
|
550
|
+
sig_iov.size = siglen;
|
|
551
|
+
encoded_signature = rd_base64_encode_str_urlsafe(&sig_iov);
|
|
552
|
+
|
|
553
|
+
if (!encoded_signature)
|
|
554
|
+
goto cleanup;
|
|
555
|
+
|
|
556
|
+
size_t jwt_len = strlen(encoded_header) + strlen(encoded_payload) +
|
|
557
|
+
strlen(encoded_signature) + 3;
|
|
558
|
+
result = rd_malloc(jwt_len);
|
|
559
|
+
if (!result)
|
|
560
|
+
goto cleanup;
|
|
561
|
+
rd_snprintf(result, jwt_len, "%s.%s.%s", encoded_header,
|
|
562
|
+
encoded_payload, encoded_signature);
|
|
563
|
+
|
|
564
|
+
cleanup:
|
|
565
|
+
if (encoded_header)
|
|
566
|
+
rd_free(encoded_header);
|
|
567
|
+
if (encoded_payload)
|
|
568
|
+
rd_free(encoded_payload);
|
|
569
|
+
if (encoded_signature)
|
|
570
|
+
rd_free(encoded_signature);
|
|
571
|
+
if (unsigned_token)
|
|
572
|
+
rd_free(unsigned_token);
|
|
573
|
+
if (sig)
|
|
574
|
+
rd_free(sig);
|
|
575
|
+
|
|
576
|
+
if (header_json_obj) {
|
|
577
|
+
if (header_str)
|
|
578
|
+
free(header_str); /* cJSON_PrintUnformatted uses malloc
|
|
579
|
+
*/
|
|
580
|
+
cJSON_Delete(header_json_obj);
|
|
581
|
+
} else if (header_str) {
|
|
582
|
+
rd_free(header_str); /* rd_malloc was used */
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (payload_json_obj) {
|
|
586
|
+
if (payload_str)
|
|
587
|
+
free(payload_str); /* cJSON_PrintUnformatted uses malloc
|
|
588
|
+
*/
|
|
589
|
+
cJSON_Delete(payload_json_obj);
|
|
590
|
+
} else if (payload_str) {
|
|
591
|
+
rd_free(payload_str); /* rd_malloc was used */
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (pkey)
|
|
595
|
+
EVP_PKEY_free(pkey);
|
|
596
|
+
if (mdctx)
|
|
597
|
+
EVP_MD_CTX_free(mdctx);
|
|
598
|
+
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* @brief Build request body for JWT bearer token request.
|
|
605
|
+
*
|
|
606
|
+
* Creates a URL-encoded request body for token exchange with the JWT assertion
|
|
607
|
+
* and optional scope.
|
|
608
|
+
*
|
|
609
|
+
* @param assertion The JWT assertion to include in the request.
|
|
610
|
+
* @param scope Optional scope to include in the request (will be URL encoded).
|
|
611
|
+
*
|
|
612
|
+
* @returns Newly allocated string with the URL-encoded request body.
|
|
613
|
+
* Caller must free with rd_free(). NULL on memory allocation failure.
|
|
614
|
+
*
|
|
615
|
+
* @locality Any thread.
|
|
616
|
+
*/
|
|
617
|
+
static char *rd_kafka_oidc_jwt_bearer_build_request_body(const char *assertion,
|
|
618
|
+
const char *scope) {
|
|
619
|
+
const char *assertion_prefix =
|
|
620
|
+
"grant_type=urn:ietf:params:oauth:"
|
|
621
|
+
"grant-type:jwt-bearer"
|
|
622
|
+
"&assertion=";
|
|
623
|
+
int assertion_prefix_len = strlen(assertion_prefix) + strlen(assertion);
|
|
624
|
+
int body_size = assertion_prefix_len + 1;
|
|
625
|
+
char *scope_escaped = NULL;
|
|
626
|
+
if (scope) {
|
|
627
|
+
scope_escaped = curl_easy_escape(NULL, scope, 0);
|
|
628
|
+
body_size += strlen("&scope=") + strlen(scope_escaped);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
char *body = rd_malloc(body_size);
|
|
632
|
+
|
|
633
|
+
rd_snprintf(body, body_size, "%s%s", assertion_prefix, assertion);
|
|
634
|
+
if (scope) {
|
|
635
|
+
rd_snprintf(&body[assertion_prefix_len],
|
|
636
|
+
body_size - assertion_prefix_len, "&scope=%s",
|
|
637
|
+
scope_escaped);
|
|
638
|
+
rd_free(scope_escaped);
|
|
639
|
+
}
|
|
640
|
+
return body;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* @brief JWT assertion from file
|
|
645
|
+
*
|
|
646
|
+
* @param file_path Path to the file containing the JWT assertion.
|
|
647
|
+
*
|
|
648
|
+
* @returns Newly allocated string with the JWT assertion.
|
|
649
|
+
* Caller must free with rd_free(). NULL on error.
|
|
650
|
+
*/
|
|
651
|
+
static char *rd_kafka_oidc_assertion_read_from_file(const char *file_path) {
|
|
652
|
+
if (!file_path)
|
|
653
|
+
return NULL;
|
|
654
|
+
const size_t max_size = 1024 * 1024; /* 1MB limit */
|
|
655
|
+
return rd_file_read(file_path, NULL, max_size);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* @brief Try to validate a token field from the JSON response.
|
|
660
|
+
* Extracts and validates the token, then decodes its payload to get
|
|
661
|
+
* subject and expiration.
|
|
662
|
+
*
|
|
663
|
+
* @param rk The rd_kafka_t instance
|
|
664
|
+
* @param json The JSON response from the token endpoint
|
|
665
|
+
* @param field_name The name of the field to extract (e.g., "access_token" or
|
|
666
|
+
* "id_token")
|
|
667
|
+
* @param token_out Pointer to store the extracted token
|
|
668
|
+
* @param sub_out Pointer to store the subject from the token
|
|
669
|
+
* @param exp_out Pointer to store the expiration from the token
|
|
670
|
+
* @param errstr_out Buffer to store error message
|
|
671
|
+
* @param errstr_size Size of error message buffer
|
|
672
|
+
*
|
|
673
|
+
* @returns The extracted token or NULL on failure.
|
|
674
|
+
*/
|
|
675
|
+
static char *rd_kafka_oidc_token_try_validate(cJSON *json,
|
|
676
|
+
const char *field,
|
|
677
|
+
char **sub,
|
|
678
|
+
double *exp,
|
|
679
|
+
char *errstr,
|
|
680
|
+
size_t errstr_size) {
|
|
681
|
+
cJSON *access_token_json, *jwt_exp, *jwt_sub, *payloads = NULL;
|
|
682
|
+
char *jwt_token = NULL, *decoded_payloads = NULL;
|
|
683
|
+
const char *decode_errstr = NULL;
|
|
684
|
+
*sub = NULL;
|
|
685
|
+
|
|
686
|
+
access_token_json = cJSON_GetObjectItem(json, field);
|
|
687
|
+
|
|
688
|
+
if (!access_token_json) {
|
|
689
|
+
rd_snprintf(errstr, errstr_size,
|
|
690
|
+
"Expected JSON response with \"%s\" field", field);
|
|
691
|
+
goto fail;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
jwt_token = cJSON_GetStringValue(access_token_json);
|
|
695
|
+
if (!jwt_token) {
|
|
696
|
+
rd_snprintf(errstr, errstr_size,
|
|
697
|
+
"Expected token as a string value");
|
|
698
|
+
goto fail;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
decode_errstr =
|
|
702
|
+
rd_kafka_jwt_b64_decode_payload(jwt_token, &decoded_payloads);
|
|
703
|
+
if (decode_errstr != NULL) {
|
|
704
|
+
rd_snprintf(errstr, errstr_size,
|
|
705
|
+
"Failed to decode JWT payload: %s", decode_errstr);
|
|
706
|
+
goto fail;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
payloads = cJSON_Parse(decoded_payloads);
|
|
710
|
+
if (payloads == NULL) {
|
|
711
|
+
rd_snprintf(errstr, errstr_size,
|
|
712
|
+
"Failed to parse JSON JWT payload");
|
|
713
|
+
goto fail;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
jwt_exp = cJSON_GetObjectItem(payloads, "exp");
|
|
717
|
+
if (jwt_exp == NULL) {
|
|
718
|
+
rd_snprintf(errstr, errstr_size,
|
|
719
|
+
"Expected JSON JWT response with "
|
|
720
|
+
"\"exp\" field");
|
|
721
|
+
goto fail;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
*exp = cJSON_GetNumberValue(jwt_exp);
|
|
725
|
+
if (*exp <= 0) {
|
|
726
|
+
rd_snprintf(errstr, errstr_size,
|
|
727
|
+
"Expected JSON JWT response with "
|
|
728
|
+
"valid \"exp\" field");
|
|
729
|
+
goto fail;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
jwt_sub = cJSON_GetObjectItem(payloads, "sub");
|
|
733
|
+
if (jwt_sub == NULL) {
|
|
734
|
+
rd_snprintf(errstr, errstr_size,
|
|
735
|
+
"Expected JSON JWT response with "
|
|
736
|
+
"\"sub\" field");
|
|
737
|
+
goto fail;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
*sub = cJSON_GetStringValue(jwt_sub);
|
|
741
|
+
if (*sub == NULL) {
|
|
742
|
+
rd_snprintf(errstr, errstr_size,
|
|
743
|
+
"Expected JSON JWT response with "
|
|
744
|
+
"valid \"sub\" field");
|
|
745
|
+
goto fail;
|
|
746
|
+
}
|
|
747
|
+
*sub = rd_strdup(*sub);
|
|
748
|
+
done:
|
|
749
|
+
if (payloads)
|
|
750
|
+
cJSON_Delete(payloads);
|
|
751
|
+
if (decoded_payloads)
|
|
752
|
+
rd_free(decoded_payloads);
|
|
753
|
+
return jwt_token;
|
|
754
|
+
fail:
|
|
755
|
+
jwt_token = NULL;
|
|
756
|
+
goto done;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* @brief Implementation of JWT token refresh callback function.
|
|
761
|
+
* Creates a JWT assertion, exchanges it for an access token,
|
|
762
|
+
* and sets the token for SASL OAUTHBEARER authentication.
|
|
763
|
+
*
|
|
764
|
+
* @param rk The rd_kafka_t instance.
|
|
765
|
+
* @param oauthbearer_config The OAUTHBEARER configuration.
|
|
766
|
+
* @param opaque Opaque pointer passed to the callback.
|
|
767
|
+
*
|
|
768
|
+
* @locality rdkafka main thread
|
|
769
|
+
*/
|
|
770
|
+
void rd_kafka_oidc_token_jwt_bearer_refresh_cb(rd_kafka_t *rk,
|
|
771
|
+
const char *oauthbearer_config,
|
|
772
|
+
void *opaque) {
|
|
773
|
+
const int timeout_s = 20;
|
|
774
|
+
const int retry = 4;
|
|
775
|
+
const int retry_ms = 5 * 1000;
|
|
776
|
+
|
|
777
|
+
char *jwt_assertion = NULL;
|
|
778
|
+
char *request_body = NULL;
|
|
779
|
+
struct curl_slist *headers = NULL;
|
|
780
|
+
rd_http_error_t *herr = NULL;
|
|
781
|
+
cJSON *json = NULL;
|
|
782
|
+
char *jwt_token = NULL;
|
|
783
|
+
char set_token_errstr[512];
|
|
784
|
+
double exp = 0;
|
|
785
|
+
char **extensions = NULL;
|
|
786
|
+
char **extension_key_value = NULL;
|
|
787
|
+
size_t extension_key_value_cnt = 0;
|
|
788
|
+
size_t extension_cnt;
|
|
789
|
+
char *sub = NULL;
|
|
790
|
+
char validate_errstr[512];
|
|
791
|
+
|
|
792
|
+
if (rd_kafka_terminating(rk))
|
|
793
|
+
return;
|
|
794
|
+
|
|
795
|
+
if (rk->rk_conf.sasl.oauthbearer.assertion.file) {
|
|
796
|
+
jwt_assertion = rd_kafka_oidc_assertion_read_from_file(
|
|
797
|
+
rk->rk_conf.sasl.oauthbearer.assertion.file);
|
|
798
|
+
} else {
|
|
799
|
+
jwt_assertion = rd_kafka_oidc_assertion_create(
|
|
800
|
+
rk, rk->rk_conf.sasl.oauthbearer.assertion.private_key.pem,
|
|
801
|
+
rk->rk_conf.sasl.oauthbearer.assertion.private_key.file,
|
|
802
|
+
rk->rk_conf.sasl.oauthbearer.assertion.private_key
|
|
803
|
+
.passphrase,
|
|
804
|
+
rk->rk_conf.sasl.oauthbearer.assertion.algorithm,
|
|
805
|
+
rk->rk_conf.sasl.oauthbearer.assertion.jwt_template_file,
|
|
806
|
+
rk->rk_conf.sasl.oauthbearer.assertion.claim.subject,
|
|
807
|
+
rk->rk_conf.sasl.oauthbearer.assertion.claim.issuer,
|
|
808
|
+
rk->rk_conf.sasl.oauthbearer.assertion.claim.audience,
|
|
809
|
+
rk->rk_conf.sasl.oauthbearer.assertion.claim.not_before_s,
|
|
810
|
+
rk->rk_conf.sasl.oauthbearer.assertion.claim.expiration_s,
|
|
811
|
+
rk->rk_conf.sasl.oauthbearer.assertion.claim.jti_include);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (!jwt_assertion) {
|
|
815
|
+
rd_kafka_oauthbearer_set_token_failure(
|
|
816
|
+
rk, "Failed to create JWT assertion");
|
|
817
|
+
goto done;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
request_body = rd_kafka_oidc_jwt_bearer_build_request_body(
|
|
821
|
+
jwt_assertion, rk->rk_conf.sasl.oauthbearer.scope);
|
|
822
|
+
|
|
823
|
+
if (!request_body) {
|
|
824
|
+
rd_kafka_oauthbearer_set_token_failure(
|
|
825
|
+
rk, "Failed to build JWT request body");
|
|
826
|
+
goto done;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
headers = curl_slist_append(
|
|
830
|
+
headers, "Content-Type: application/x-www-form-urlencoded");
|
|
831
|
+
headers = curl_slist_append(headers, "Accept: application/json");
|
|
832
|
+
|
|
833
|
+
herr = rd_http_post_expect_json(
|
|
834
|
+
rk, rk->rk_conf.sasl.oauthbearer.token_endpoint_url, headers,
|
|
835
|
+
request_body, strlen(request_body), timeout_s, retry, retry_ms,
|
|
836
|
+
&json);
|
|
837
|
+
|
|
838
|
+
if (unlikely(herr != NULL)) {
|
|
839
|
+
rd_kafka_log(
|
|
840
|
+
rk, LOG_ERR, "JWT",
|
|
841
|
+
"Failed to retrieve JWT token from \"%s\": %s (%d)",
|
|
842
|
+
rk->rk_conf.sasl.oauthbearer.token_endpoint_url,
|
|
843
|
+
herr->errstr, herr->code);
|
|
844
|
+
rd_kafka_oauthbearer_set_token_failure(rk, herr->errstr);
|
|
845
|
+
rd_http_error_destroy(herr);
|
|
846
|
+
goto done;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/*
|
|
850
|
+
* RFC 7523 Section 1 says that an access token should be returned
|
|
851
|
+
* https://datatracker.ietf.org/doc/html/rfc7523#section-1
|
|
852
|
+
* Some providers (e.g. GCP) return an `id_token` instead, depending
|
|
853
|
+
* on the configured `target_audience` in the request JWT bearer token.
|
|
854
|
+
* This may be because the validation endpoint is not accessible
|
|
855
|
+
* for validating the `access_token` while the `id_token` is validated
|
|
856
|
+
* through the JWKS URL.
|
|
857
|
+
* This function will try to validate the `access_token` and then the
|
|
858
|
+
* `id_token`.
|
|
859
|
+
*/
|
|
860
|
+
jwt_token = rd_kafka_oidc_token_try_validate(json, "access_token", &sub,
|
|
861
|
+
&exp, validate_errstr,
|
|
862
|
+
sizeof(validate_errstr));
|
|
863
|
+
if (!jwt_token)
|
|
864
|
+
jwt_token = rd_kafka_oidc_token_try_validate(
|
|
865
|
+
json, "id_token", &sub, &exp, validate_errstr,
|
|
866
|
+
sizeof(validate_errstr));
|
|
867
|
+
|
|
868
|
+
if (!jwt_token) {
|
|
869
|
+
rd_kafka_oauthbearer_set_token_failure(rk, validate_errstr);
|
|
870
|
+
goto done;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
if (rk->rk_conf.sasl.oauthbearer.extensions_str) {
|
|
875
|
+
extensions =
|
|
876
|
+
rd_string_split(rk->rk_conf.sasl.oauthbearer.extensions_str,
|
|
877
|
+
',', rd_true, &extension_cnt);
|
|
878
|
+
|
|
879
|
+
extension_key_value = rd_kafka_conf_kv_split(
|
|
880
|
+
(const char **)extensions, extension_cnt,
|
|
881
|
+
&extension_key_value_cnt);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (rd_kafka_oauthbearer_set_token(
|
|
885
|
+
rk, jwt_token, (int64_t)exp * 1000, sub,
|
|
886
|
+
(const char **)extension_key_value, extension_key_value_cnt,
|
|
887
|
+
set_token_errstr,
|
|
888
|
+
sizeof(set_token_errstr)) != RD_KAFKA_RESP_ERR_NO_ERROR) {
|
|
889
|
+
rd_kafka_oauthbearer_set_token_failure(rk, validate_errstr);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
done:
|
|
893
|
+
RD_IF_FREE(sub, rd_free);
|
|
894
|
+
RD_IF_FREE(jwt_assertion, rd_free);
|
|
895
|
+
RD_IF_FREE(request_body, rd_free);
|
|
896
|
+
RD_IF_FREE(headers, curl_slist_free_all);
|
|
897
|
+
RD_IF_FREE(json, cJSON_Delete);
|
|
898
|
+
RD_IF_FREE(extensions, rd_free);
|
|
899
|
+
RD_IF_FREE(extension_key_value, rd_free);
|
|
900
|
+
/* jwt_token is freed as part of the json object */
|
|
901
|
+
}
|
|
220
902
|
|
|
221
903
|
/**
|
|
222
904
|
* @brief Implementation of Oauth/OIDC token refresh callback function,
|
|
@@ -224,37 +906,33 @@ static void rd_kafka_oidc_build_post_fields(const char *scope,
|
|
|
224
906
|
* then extract the jwt from the JSON response, and forward it to
|
|
225
907
|
* the broker.
|
|
226
908
|
*/
|
|
227
|
-
void
|
|
228
|
-
|
|
229
|
-
|
|
909
|
+
void rd_kafka_oidc_token_client_credentials_refresh_cb(
|
|
910
|
+
rd_kafka_t *rk,
|
|
911
|
+
const char *oauthbearer_config,
|
|
912
|
+
void *opaque) {
|
|
230
913
|
const int timeout_s = 20;
|
|
231
914
|
const int retry = 4;
|
|
232
915
|
const int retry_ms = 5 * 1000;
|
|
233
916
|
|
|
234
917
|
double exp;
|
|
235
918
|
|
|
236
|
-
cJSON *json
|
|
237
|
-
cJSON *payloads = NULL;
|
|
238
|
-
cJSON *parsed_token, *jwt_exp, *jwt_sub;
|
|
919
|
+
cJSON *json = NULL;
|
|
239
920
|
|
|
240
921
|
rd_http_error_t *herr;
|
|
241
922
|
|
|
242
923
|
char *jwt_token;
|
|
243
|
-
char *post_fields;
|
|
244
|
-
char *decoded_payloads = NULL;
|
|
924
|
+
char *post_fields = NULL;
|
|
245
925
|
|
|
246
926
|
struct curl_slist *headers = NULL;
|
|
247
927
|
|
|
248
928
|
const char *token_url;
|
|
249
|
-
|
|
250
|
-
const char *errstr;
|
|
929
|
+
char *sub = NULL;
|
|
251
930
|
|
|
252
931
|
size_t post_fields_size;
|
|
253
932
|
size_t extension_cnt;
|
|
254
933
|
size_t extension_key_value_cnt = 0;
|
|
255
934
|
|
|
256
935
|
char set_token_errstr[512];
|
|
257
|
-
char decode_payload_errstr[512];
|
|
258
936
|
|
|
259
937
|
char **extensions = NULL;
|
|
260
938
|
char **extension_key_value = NULL;
|
|
@@ -262,13 +940,14 @@ void rd_kafka_oidc_token_refresh_cb(rd_kafka_t *rk,
|
|
|
262
940
|
if (rd_kafka_terminating(rk))
|
|
263
941
|
return;
|
|
264
942
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
943
|
+
rd_kafka_oidc_client_credentials_build_headers(
|
|
944
|
+
rk->rk_conf.sasl.oauthbearer.client_id,
|
|
945
|
+
rk->rk_conf.sasl.oauthbearer.client_secret, &headers);
|
|
268
946
|
|
|
269
947
|
/* Build post fields */
|
|
270
|
-
|
|
271
|
-
|
|
948
|
+
rd_kafka_oidc_client_credentials_build_post_fields(
|
|
949
|
+
rk->rk_conf.sasl.oauthbearer.scope, &post_fields,
|
|
950
|
+
&post_fields_size);
|
|
272
951
|
|
|
273
952
|
token_url = rk->rk_conf.sasl.oauthbearer.token_endpoint_url;
|
|
274
953
|
|
|
@@ -286,75 +965,129 @@ void rd_kafka_oidc_token_refresh_cb(rd_kafka_t *rk,
|
|
|
286
965
|
goto done;
|
|
287
966
|
}
|
|
288
967
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
"Expected JSON JWT response with "
|
|
295
|
-
"\"access_token\" field");
|
|
968
|
+
jwt_token = rd_kafka_oidc_token_try_validate(json, "access_token", &sub,
|
|
969
|
+
&exp, set_token_errstr,
|
|
970
|
+
sizeof(set_token_errstr));
|
|
971
|
+
if (!jwt_token) {
|
|
972
|
+
rd_kafka_oauthbearer_set_token_failure(rk, set_token_errstr);
|
|
296
973
|
goto done;
|
|
297
974
|
}
|
|
298
975
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"Expected JSON "
|
|
304
|
-
"response as a value string");
|
|
305
|
-
goto done;
|
|
306
|
-
}
|
|
976
|
+
if (rk->rk_conf.sasl.oauthbearer.extensions_str) {
|
|
977
|
+
extensions =
|
|
978
|
+
rd_string_split(rk->rk_conf.sasl.oauthbearer.extensions_str,
|
|
979
|
+
',', rd_true, &extension_cnt);
|
|
307
980
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
sizeof(decode_payload_errstr),
|
|
312
|
-
"Failed to decode JWT payload: %s", errstr);
|
|
313
|
-
rd_kafka_oauthbearer_set_token_failure(rk,
|
|
314
|
-
decode_payload_errstr);
|
|
315
|
-
goto done;
|
|
981
|
+
extension_key_value = rd_kafka_conf_kv_split(
|
|
982
|
+
(const char **)extensions, extension_cnt,
|
|
983
|
+
&extension_key_value_cnt);
|
|
316
984
|
}
|
|
317
985
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
986
|
+
if (rd_kafka_oauthbearer_set_token(
|
|
987
|
+
rk, jwt_token, (int64_t)exp * 1000, sub,
|
|
988
|
+
(const char **)extension_key_value, extension_key_value_cnt,
|
|
989
|
+
set_token_errstr,
|
|
990
|
+
sizeof(set_token_errstr)) != RD_KAFKA_RESP_ERR_NO_ERROR)
|
|
991
|
+
rd_kafka_oauthbearer_set_token_failure(rk, set_token_errstr);
|
|
324
992
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
993
|
+
done:
|
|
994
|
+
RD_IF_FREE(sub, rd_free);
|
|
995
|
+
RD_IF_FREE(post_fields, rd_free);
|
|
996
|
+
RD_IF_FREE(json, cJSON_Delete);
|
|
997
|
+
RD_IF_FREE(headers, curl_slist_free_all);
|
|
998
|
+
RD_IF_FREE(extensions, rd_free);
|
|
999
|
+
RD_IF_FREE(extension_key_value, rd_free);
|
|
1000
|
+
}
|
|
333
1001
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
1002
|
+
/**
|
|
1003
|
+
* @brief Implementation of Oauth/OIDC token refresh callback function
|
|
1004
|
+
* for Azure IMDS,
|
|
1005
|
+
* will receive the JSON response after HTTP(S) GET call to token
|
|
1006
|
+
* provider, then extract the jwt from the JSON response, and
|
|
1007
|
+
* forward it to the broker.
|
|
1008
|
+
*/
|
|
1009
|
+
void rd_kafka_oidc_token_metadata_azure_imds_refresh_cb(
|
|
1010
|
+
rd_kafka_t *rk,
|
|
1011
|
+
const char *oauthbearer_config,
|
|
1012
|
+
void *opaque) {
|
|
1013
|
+
const int timeout_s = 20;
|
|
1014
|
+
const int retries = 4;
|
|
1015
|
+
const int retry_ms = 5 * 1000;
|
|
1016
|
+
|
|
1017
|
+
double exp;
|
|
1018
|
+
|
|
1019
|
+
cJSON *json = NULL;
|
|
1020
|
+
|
|
1021
|
+
rd_http_error_t *herr;
|
|
1022
|
+
|
|
1023
|
+
char *jwt_token;
|
|
1024
|
+
|
|
1025
|
+
struct curl_slist *headers = NULL;
|
|
1026
|
+
|
|
1027
|
+
const char *token_endpoint_url_initial = NULL;
|
|
1028
|
+
char *token_endpoint_url = NULL;
|
|
1029
|
+
char *sub = NULL;
|
|
1030
|
+
|
|
1031
|
+
size_t extension_cnt;
|
|
1032
|
+
size_t extension_key_value_cnt = 0;
|
|
1033
|
+
|
|
1034
|
+
char set_token_errstr[512];
|
|
1035
|
+
|
|
1036
|
+
char **extensions = NULL;
|
|
1037
|
+
char **extension_key_value = NULL;
|
|
1038
|
+
char *query = NULL;
|
|
1039
|
+
static char *headers_array[] = {"Metadata: true"};
|
|
1040
|
+
|
|
1041
|
+
if (rd_kafka_terminating(rk))
|
|
1042
|
+
return;
|
|
1043
|
+
|
|
1044
|
+
if (rk->rk_conf.sasl.oauthbearer_config)
|
|
1045
|
+
query = rd_kafka_conf_kv_get(
|
|
1046
|
+
rk->rk_conf.sasl.oauthbearer_config, "query", ',');
|
|
1047
|
+
token_endpoint_url_initial =
|
|
1048
|
+
rk->rk_conf.sasl.oauthbearer.token_endpoint_url;
|
|
1049
|
+
if (!token_endpoint_url_initial)
|
|
1050
|
+
token_endpoint_url_initial =
|
|
1051
|
+
RD_KAFKA_SASL_OAUTHBEARER_METADATA_AUTHENTICATION_URL_AZURE_IMDS;
|
|
1052
|
+
if (query && *query) {
|
|
1053
|
+
token_endpoint_url = rd_http_get_params_append(
|
|
1054
|
+
token_endpoint_url_initial, query);
|
|
1055
|
+
|
|
1056
|
+
if (token_endpoint_url == NULL) {
|
|
1057
|
+
rd_snprintf(
|
|
1058
|
+
set_token_errstr, sizeof(set_token_errstr),
|
|
1059
|
+
"Failed to append params \"%s\" to token endpoint "
|
|
1060
|
+
"URL \"%s\"",
|
|
1061
|
+
query,
|
|
1062
|
+
rk->rk_conf.sasl.oauthbearer.token_endpoint_url);
|
|
1063
|
+
rd_kafka_log(rk, LOG_ERR, "OIDC", "%s",
|
|
1064
|
+
set_token_errstr);
|
|
1065
|
+
rd_kafka_oauthbearer_set_token_failure(
|
|
1066
|
+
rk, set_token_errstr);
|
|
1067
|
+
goto done;
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
token_endpoint_url = rd_strdup(token_endpoint_url_initial);
|
|
341
1071
|
}
|
|
342
1072
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
1073
|
+
herr = rd_http_get_json(rk, token_endpoint_url, headers_array, 1,
|
|
1074
|
+
timeout_s, retries, retry_ms, &json);
|
|
1075
|
+
|
|
1076
|
+
if (unlikely(herr != NULL)) {
|
|
1077
|
+
rd_kafka_log(rk, LOG_ERR, "OIDC",
|
|
1078
|
+
"Failed to retrieve OIDC "
|
|
1079
|
+
"token from \"%s\": %s (%d)",
|
|
1080
|
+
token_endpoint_url, herr->errstr, herr->code);
|
|
1081
|
+
rd_kafka_oauthbearer_set_token_failure(rk, herr->errstr);
|
|
1082
|
+
rd_http_error_destroy(herr);
|
|
349
1083
|
goto done;
|
|
350
1084
|
}
|
|
351
1085
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
"valid \"sub\" field");
|
|
1086
|
+
jwt_token = rd_kafka_oidc_token_try_validate(json, "access_token", &sub,
|
|
1087
|
+
&exp, set_token_errstr,
|
|
1088
|
+
sizeof(set_token_errstr));
|
|
1089
|
+
if (!jwt_token) {
|
|
1090
|
+
rd_kafka_oauthbearer_set_token_failure(rk, set_token_errstr);
|
|
358
1091
|
goto done;
|
|
359
1092
|
}
|
|
360
1093
|
|
|
@@ -376,16 +1109,15 @@ void rd_kafka_oidc_token_refresh_cb(rd_kafka_t *rk,
|
|
|
376
1109
|
rd_kafka_oauthbearer_set_token_failure(rk, set_token_errstr);
|
|
377
1110
|
|
|
378
1111
|
done:
|
|
379
|
-
RD_IF_FREE(
|
|
380
|
-
RD_IF_FREE(post_fields, rd_free);
|
|
1112
|
+
RD_IF_FREE(sub, rd_free);
|
|
381
1113
|
RD_IF_FREE(json, cJSON_Delete);
|
|
382
1114
|
RD_IF_FREE(headers, curl_slist_free_all);
|
|
383
1115
|
RD_IF_FREE(extensions, rd_free);
|
|
384
1116
|
RD_IF_FREE(extension_key_value, rd_free);
|
|
385
|
-
RD_IF_FREE(
|
|
1117
|
+
RD_IF_FREE(token_endpoint_url, rd_free);
|
|
1118
|
+
RD_IF_FREE(query, rd_free);
|
|
386
1119
|
}
|
|
387
1120
|
|
|
388
|
-
|
|
389
1121
|
/**
|
|
390
1122
|
* @brief Make sure the jwt is able to be extracted from HTTP(S) response.
|
|
391
1123
|
* The JSON response after HTTP(S) call to token provider will be in
|
|
@@ -421,10 +1153,11 @@ static int ut_sasl_oauthbearer_oidc_should_succeed(void) {
|
|
|
421
1153
|
cJSON *json = NULL;
|
|
422
1154
|
char *token;
|
|
423
1155
|
cJSON *parsed_token;
|
|
1156
|
+
rd_kafka_t *rk = rd_calloc(1, sizeof(*rk));
|
|
424
1157
|
|
|
425
1158
|
RD_UT_BEGIN();
|
|
426
1159
|
|
|
427
|
-
herr = rd_http_req_init(&hreq, "");
|
|
1160
|
+
herr = rd_http_req_init(rk, &hreq, "");
|
|
428
1161
|
|
|
429
1162
|
RD_UT_ASSERT(!herr,
|
|
430
1163
|
"Expected initialize to succeed, "
|
|
@@ -460,6 +1193,7 @@ static int ut_sasl_oauthbearer_oidc_should_succeed(void) {
|
|
|
460
1193
|
rd_http_error_destroy(herr);
|
|
461
1194
|
rd_http_req_destroy(&hreq);
|
|
462
1195
|
cJSON_Delete(json);
|
|
1196
|
+
rd_free(rk);
|
|
463
1197
|
|
|
464
1198
|
RD_UT_PASS();
|
|
465
1199
|
}
|
|
@@ -476,10 +1210,11 @@ static int ut_sasl_oauthbearer_oidc_with_empty_key(void) {
|
|
|
476
1210
|
rd_http_error_t *herr;
|
|
477
1211
|
cJSON *json = NULL;
|
|
478
1212
|
cJSON *parsed_token;
|
|
1213
|
+
rd_kafka_t *rk = rd_calloc(1, sizeof(*rk));
|
|
479
1214
|
|
|
480
1215
|
RD_UT_BEGIN();
|
|
481
1216
|
|
|
482
|
-
herr = rd_http_req_init(&hreq, "");
|
|
1217
|
+
herr = rd_http_req_init(rk, &hreq, "");
|
|
483
1218
|
RD_UT_ASSERT(!herr,
|
|
484
1219
|
"Expected initialization to succeed, "
|
|
485
1220
|
"but it failed with error code: %d, error string: %s",
|
|
@@ -507,6 +1242,7 @@ static int ut_sasl_oauthbearer_oidc_with_empty_key(void) {
|
|
|
507
1242
|
rd_http_error_destroy(herr);
|
|
508
1243
|
cJSON_Delete(json);
|
|
509
1244
|
cJSON_Delete(parsed_token);
|
|
1245
|
+
rd_free(rk);
|
|
510
1246
|
RD_UT_PASS();
|
|
511
1247
|
}
|
|
512
1248
|
|
|
@@ -526,7 +1262,8 @@ static int ut_sasl_oauthbearer_oidc_post_fields(void) {
|
|
|
526
1262
|
|
|
527
1263
|
RD_UT_BEGIN();
|
|
528
1264
|
|
|
529
|
-
|
|
1265
|
+
rd_kafka_oidc_client_credentials_build_post_fields(scope, &post_fields,
|
|
1266
|
+
&post_fields_size);
|
|
530
1267
|
|
|
531
1268
|
RD_UT_ASSERT(expected_post_fields_size == post_fields_size,
|
|
532
1269
|
"Expected expected_post_fields_size is %" PRIusz
|
|
@@ -558,7 +1295,8 @@ static int ut_sasl_oauthbearer_oidc_post_fields_with_empty_scope(void) {
|
|
|
558
1295
|
|
|
559
1296
|
RD_UT_BEGIN();
|
|
560
1297
|
|
|
561
|
-
|
|
1298
|
+
rd_kafka_oidc_client_credentials_build_post_fields(scope, &post_fields,
|
|
1299
|
+
&post_fields_size);
|
|
562
1300
|
|
|
563
1301
|
RD_UT_ASSERT(expected_post_fields_size == post_fields_size,
|
|
564
1302
|
"Expected expected_post_fields_size is %" PRIusz
|
|
@@ -587,3 +1325,342 @@ int unittest_sasl_oauthbearer_oidc(void) {
|
|
|
587
1325
|
fails += ut_sasl_oauthbearer_oidc_post_fields_with_empty_scope();
|
|
588
1326
|
return fails;
|
|
589
1327
|
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* @brief Test the Base64Url encoding functionality.
|
|
1331
|
+
* Verifies that the encoding correctly handles special characters
|
|
1332
|
+
* and padding removal.
|
|
1333
|
+
*/
|
|
1334
|
+
static int ut_sasl_oauthbearer_oidc_jwt_bearer_base64url_encode(void) {
|
|
1335
|
+
/* Test cases with expected inputs and outputs */
|
|
1336
|
+
static const struct {
|
|
1337
|
+
const char *input;
|
|
1338
|
+
const char *expected_output;
|
|
1339
|
+
} test_cases[] = {
|
|
1340
|
+
/* Regular case */
|
|
1341
|
+
{"Hello, world!", "SGVsbG8sIHdvcmxkIQ"},
|
|
1342
|
+
/* Case with padding characters that should be removed */
|
|
1343
|
+
{"test", "dGVzdA"},
|
|
1344
|
+
/* Empty string */
|
|
1345
|
+
{"", ""},
|
|
1346
|
+
/* Special characters that trigger Base64 padding */
|
|
1347
|
+
{"f", "Zg"},
|
|
1348
|
+
{"fo", "Zm8"},
|
|
1349
|
+
{"foo", "Zm9v"},
|
|
1350
|
+
{"foob", "Zm9vYg"},
|
|
1351
|
+
{"fooba", "Zm9vYmE"},
|
|
1352
|
+
{"foobar", "Zm9vYmFy"},
|
|
1353
|
+
/* Characters that produce + and / in standard Base64 */
|
|
1354
|
+
{"\x3E\x3F",
|
|
1355
|
+
"Pj8"}, /* encodes to ">?" in standard Base64 with + and / */
|
|
1356
|
+
};
|
|
1357
|
+
unsigned int i;
|
|
1358
|
+
|
|
1359
|
+
RD_UT_BEGIN();
|
|
1360
|
+
|
|
1361
|
+
for (i = 0; i < RD_ARRAYSIZE(test_cases); i++) {
|
|
1362
|
+
rd_chariov_t input_iov;
|
|
1363
|
+
input_iov.ptr = (char *)test_cases[i].input;
|
|
1364
|
+
input_iov.size = strlen(test_cases[i].input);
|
|
1365
|
+
char *output = rd_base64_encode_str_urlsafe(&input_iov);
|
|
1366
|
+
|
|
1367
|
+
RD_UT_ASSERT(output != NULL,
|
|
1368
|
+
"Expected non-NULL output for input: %s",
|
|
1369
|
+
test_cases[i].input);
|
|
1370
|
+
|
|
1371
|
+
RD_UT_ASSERT(!strcmp(output, test_cases[i].expected_output),
|
|
1372
|
+
"Base64Url encoding failed: expected %s, got %s",
|
|
1373
|
+
test_cases[i].expected_output, output);
|
|
1374
|
+
|
|
1375
|
+
rd_free(output);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
RD_UT_PASS();
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* @brief Test JWT request body building.
|
|
1383
|
+
* Verifies that the request body is correctly formatted with
|
|
1384
|
+
* the required parameters.
|
|
1385
|
+
*/
|
|
1386
|
+
static int ut_sasl_oauthbearer_oidc_jwt_bearer_build_request_body(void) {
|
|
1387
|
+
const char *assertion = "test.jwt.assertion";
|
|
1388
|
+
const char *scope = "test.scope";
|
|
1389
|
+
const char *expected =
|
|
1390
|
+
"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion="
|
|
1391
|
+
"test.jwt.assertion&scope=test.scope";
|
|
1392
|
+
char *body;
|
|
1393
|
+
|
|
1394
|
+
RD_UT_BEGIN();
|
|
1395
|
+
|
|
1396
|
+
body = rd_kafka_oidc_jwt_bearer_build_request_body(assertion, scope);
|
|
1397
|
+
|
|
1398
|
+
RD_UT_ASSERT(body != NULL, "Expected non-NULL request body");
|
|
1399
|
+
|
|
1400
|
+
RD_UT_ASSERT(!strcmp(body, expected),
|
|
1401
|
+
"Request body incorrect: expected '%s', got '%s'",
|
|
1402
|
+
expected, body);
|
|
1403
|
+
|
|
1404
|
+
rd_free(body);
|
|
1405
|
+
|
|
1406
|
+
RD_UT_PASS();
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* @brief Test JWT assertion file parsing.
|
|
1411
|
+
* Verifies that the function correctly reads a JWT from a file.
|
|
1412
|
+
*/
|
|
1413
|
+
static int ut_sasl_oauthbearer_oidc_assertion_parse_from_file(void) {
|
|
1414
|
+
|
|
1415
|
+
char tempfile_path[512];
|
|
1416
|
+
FILE *tempfile;
|
|
1417
|
+
const char *test_jwt = "header.payload.signature";
|
|
1418
|
+
char *result;
|
|
1419
|
+
|
|
1420
|
+
RD_UT_BEGIN();
|
|
1421
|
+
|
|
1422
|
+
tempfile = rd_file_mkstemp("rdtmp", "wb", tempfile_path,
|
|
1423
|
+
sizeof(tempfile_path));
|
|
1424
|
+
fprintf(tempfile, "%s", test_jwt);
|
|
1425
|
+
fclose(tempfile);
|
|
1426
|
+
|
|
1427
|
+
/* Test parsing from file */
|
|
1428
|
+
result = rd_kafka_oidc_assertion_read_from_file(tempfile_path);
|
|
1429
|
+
RD_UT_ASSERT(result != NULL,
|
|
1430
|
+
"Expected non-NULL result from parsing file");
|
|
1431
|
+
RD_UT_ASSERT(!strcmp(result, test_jwt),
|
|
1432
|
+
"Incorrect JWT parsed: expected '%s', got '%s'", test_jwt,
|
|
1433
|
+
result);
|
|
1434
|
+
|
|
1435
|
+
rd_free(result);
|
|
1436
|
+
|
|
1437
|
+
/* Test with NULL path */
|
|
1438
|
+
result = rd_kafka_oidc_assertion_read_from_file(NULL);
|
|
1439
|
+
RD_UT_ASSERT(result == NULL, "Expected NULL result with NULL path");
|
|
1440
|
+
|
|
1441
|
+
/* Test with non-existent file */
|
|
1442
|
+
result =
|
|
1443
|
+
rd_kafka_oidc_assertion_read_from_file("/non/existent/file/path");
|
|
1444
|
+
RD_UT_ASSERT(result == NULL,
|
|
1445
|
+
"Expected NULL result with non-existent file");
|
|
1446
|
+
|
|
1447
|
+
remove(tempfile_path);
|
|
1448
|
+
|
|
1449
|
+
RD_UT_PASS();
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* @brief Mock function for testing JWT template processing.
|
|
1454
|
+
* Creates a file with valid JWT template JSON.
|
|
1455
|
+
*/
|
|
1456
|
+
static char *ut_create_mock_jwt_template_file(void) {
|
|
1457
|
+
FILE *tempfile;
|
|
1458
|
+
char tempfile_path[512];
|
|
1459
|
+
|
|
1460
|
+
const char *template_json =
|
|
1461
|
+
"{\n"
|
|
1462
|
+
" \"header\": {\n"
|
|
1463
|
+
" \"kid\": \"test-key-id\"\n"
|
|
1464
|
+
" },\n"
|
|
1465
|
+
" \"payload\": {\n"
|
|
1466
|
+
" \"sub\": \"test-subject\",\n"
|
|
1467
|
+
" \"aud\": \"test-audience\"\n"
|
|
1468
|
+
" }\n"
|
|
1469
|
+
"}";
|
|
1470
|
+
|
|
1471
|
+
tempfile = rd_file_mkstemp("rdtmp", "wb", tempfile_path,
|
|
1472
|
+
sizeof(tempfile_path));
|
|
1473
|
+
if (!tempfile)
|
|
1474
|
+
return NULL;
|
|
1475
|
+
|
|
1476
|
+
fprintf(tempfile, "%s", template_json);
|
|
1477
|
+
fclose(tempfile);
|
|
1478
|
+
|
|
1479
|
+
return rd_strdup(tempfile_path);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* @brief Test JWT template file processing.
|
|
1484
|
+
* Verifies that the function correctly parses header and payload from
|
|
1485
|
+
* template.
|
|
1486
|
+
*/
|
|
1487
|
+
static int ut_sasl_oauthbearer_oidc_assertion_process_template_file(void) {
|
|
1488
|
+
char *template_path;
|
|
1489
|
+
rd_kafka_t *rk;
|
|
1490
|
+
cJSON *header = NULL, *payload = NULL;
|
|
1491
|
+
int result;
|
|
1492
|
+
|
|
1493
|
+
RD_UT_BEGIN();
|
|
1494
|
+
|
|
1495
|
+
rk = rd_calloc(1, sizeof(*rk));
|
|
1496
|
+
|
|
1497
|
+
template_path = ut_create_mock_jwt_template_file();
|
|
1498
|
+
RD_UT_ASSERT(template_path != NULL, "Failed to create template file");
|
|
1499
|
+
|
|
1500
|
+
/* Test template processing */
|
|
1501
|
+
result = rd_kafka_oidc_assertion_parse_template_file(rk, template_path,
|
|
1502
|
+
&header, &payload);
|
|
1503
|
+
RD_UT_ASSERT(result == 0, "Expected success from template processing");
|
|
1504
|
+
RD_UT_ASSERT(header != NULL, "Expected non-NULL header JSON");
|
|
1505
|
+
RD_UT_ASSERT(payload != NULL, "Expected non-NULL payload JSON");
|
|
1506
|
+
|
|
1507
|
+
/* Verify header contents */
|
|
1508
|
+
cJSON *kid = cJSON_GetObjectItem(header, "kid");
|
|
1509
|
+
RD_UT_ASSERT(kid != NULL, "Expected kid in header");
|
|
1510
|
+
RD_UT_ASSERT(cJSON_IsString(kid), "Expected kid to be string");
|
|
1511
|
+
RD_UT_ASSERT(!strcmp(cJSON_GetStringValue(kid), "test-key-id"),
|
|
1512
|
+
"Incorrect kid value");
|
|
1513
|
+
|
|
1514
|
+
/* Verify payload contents */
|
|
1515
|
+
cJSON *sub = cJSON_GetObjectItem(payload, "sub");
|
|
1516
|
+
RD_UT_ASSERT(sub != NULL, "Expected sub in payload");
|
|
1517
|
+
RD_UT_ASSERT(cJSON_IsString(sub), "Expected sub to be string");
|
|
1518
|
+
RD_UT_ASSERT(!strcmp(cJSON_GetStringValue(sub), "test-subject"),
|
|
1519
|
+
"Incorrect sub value");
|
|
1520
|
+
|
|
1521
|
+
cJSON *aud = cJSON_GetObjectItem(payload, "aud");
|
|
1522
|
+
RD_UT_ASSERT(aud != NULL, "Expected aud in payload");
|
|
1523
|
+
RD_UT_ASSERT(cJSON_IsString(aud), "Expected aud to be string");
|
|
1524
|
+
RD_UT_ASSERT(!strcmp(cJSON_GetStringValue(aud), "test-audience"),
|
|
1525
|
+
"Incorrect aud value");
|
|
1526
|
+
|
|
1527
|
+
/* Test with non-existent file */
|
|
1528
|
+
cJSON_Delete(header);
|
|
1529
|
+
cJSON_Delete(payload);
|
|
1530
|
+
header = NULL;
|
|
1531
|
+
payload = NULL;
|
|
1532
|
+
|
|
1533
|
+
result = rd_kafka_oidc_assertion_parse_template_file(
|
|
1534
|
+
rk, "/non/existent/file", &header, &payload);
|
|
1535
|
+
RD_UT_ASSERT(result == -1, "Expected failure with non-existent file");
|
|
1536
|
+
RD_UT_ASSERT(header == NULL,
|
|
1537
|
+
"Expected NULL header with failed processing");
|
|
1538
|
+
RD_UT_ASSERT(payload == NULL,
|
|
1539
|
+
"Expected NULL payload with failed processing");
|
|
1540
|
+
|
|
1541
|
+
unlink(template_path);
|
|
1542
|
+
rd_free(template_path);
|
|
1543
|
+
rd_free(rk);
|
|
1544
|
+
if (header)
|
|
1545
|
+
cJSON_Delete(header);
|
|
1546
|
+
if (payload)
|
|
1547
|
+
cJSON_Delete(payload);
|
|
1548
|
+
|
|
1549
|
+
RD_UT_PASS();
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* @brief Test JWT assertion creation with minimal approach.
|
|
1554
|
+
* Creates a simplified test that validates the format of the created
|
|
1555
|
+
* JWT.
|
|
1556
|
+
*/
|
|
1557
|
+
static int ut_sasl_oauthbearer_oidc_assertion_create(void) {
|
|
1558
|
+
rd_kafka_t *rk;
|
|
1559
|
+
char *private_key_pem;
|
|
1560
|
+
char *jwt;
|
|
1561
|
+
char *header_part, *payload_part, *signature_part;
|
|
1562
|
+
char *dot1, *dot2;
|
|
1563
|
+
|
|
1564
|
+
RD_UT_BEGIN();
|
|
1565
|
+
|
|
1566
|
+
rk = rd_calloc(1, sizeof(*rk));
|
|
1567
|
+
|
|
1568
|
+
/* Random key for signing */
|
|
1569
|
+
private_key_pem =
|
|
1570
|
+
"-----BEGIN PRIVATE KEY-----\n"
|
|
1571
|
+
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuBS7qG5Cd2voa\n"
|
|
1572
|
+
"7nSU2xaDbe6QOYU2P4bIY58SKHbFyq1iB517r61ImsWD+UfZuVxCqXRaWdxxnG/D\n"
|
|
1573
|
+
"5VGTQzBOZYlgSYxdJ1KvITXO8kj5i2zBT/LI9R9MTQ7nLFh+vQm1aM8Ts1PmA5t9\n"
|
|
1574
|
+
"zFtR9B8RfqN9kbt+2LnLY57aJxEkFC3D89D0WWT97UJWKo7/vxMqp9K9uAIL2Efo\n"
|
|
1575
|
+
"5rp9qwyPbx9LmTbfZ8Vog6mG6tAQQHSUqw0PnfhADCVCkYtkzYcyDZy3qZQFu1bY\n"
|
|
1576
|
+
"KuuMoMjssyCUL5tTHyNZju0p3Z0bSfOV/nkqHpSSjHKCeQkSKS18/7In6cfY/M4k\n"
|
|
1577
|
+
"8rM4HWkdAgMBAAECggEAFsTo2YrXxj/Dn8h5ioyMCpBUuZw9GNcBDLE0PAz9VW3q\n"
|
|
1578
|
+
"d7wlV+ypkKlnlJgGVa+SKcrARZ4iYN8mJIyZutn8tRVF/0pASmP9xppizvwWnkgm\n"
|
|
1579
|
+
"57hNPQwNl08x1v+PaK3VWl4nUh2RqbPpIXGetT9q3UAjpiduT++Nh9Y2D7cy3/Ro\n"
|
|
1580
|
+
"ritnpBDs1R6y5J3rxiE1s8kLYwhDRCPsgUg/ZtKPDTTFz42ArrFeqM91FmjHYP3t\n"
|
|
1581
|
+
"p9Uh6CIZ80D6CsMX/TnZFfhKe6EvKBSl4W6tcdFlnXW52fm/670iKSmcJ09+fzPO\n"
|
|
1582
|
+
"T1BLrkXGv51bFnlvUyJqQGVEv5+0+HUX/oTpTknMQQKBgQDbYhqip5e8r1f5v32B\n"
|
|
1583
|
+
"k1r3xtEiWU2mZoTHJu6bVeuigzVhz4pTMVZChElJ4QnhwwO0t5Oe4Su1MZtjMRw7\n"
|
|
1584
|
+
"qIE+YM2pXss25LRXbmWItuRWINzpe8omlxQSOj2tNO/67l0P4vmmrT5wkU2cG6TR\n"
|
|
1585
|
+
"ddzorO3NDA4MY4+Xdli+SHXwUQKBgQDLEMqlwyvaGjuZ30l6F13fWnEt9PNCtJsa\n"
|
|
1586
|
+
"nsdKJKyFMThdysY/PK40o2GTRRhgYa2jigN3OCYSSznRRZRlqznqL1bOLlYV6zS8\n"
|
|
1587
|
+
"TGhdLXuApyLAjZYIK4RtZJYGR9+yg8rH13uNektgW8KnHh5Ko/ptRVoEukf3SBsh\n"
|
|
1588
|
+
"f0Fib3ylDQKBgE11Bth0+bMJ6bLpNEPiphSjosVQ6ISe37R8/3Pi0y5uyxM8tqcG\n"
|
|
1589
|
+
"3WDg2gt2pAmM1CsjQcCv2cHAwQ81kLVTmkZO4W4yZOd9ulrARKMPh/EM61KYfVhA\n"
|
|
1590
|
+
"sTp6S7py3WQocr0gM2rw8gHGm7NJY1j9F0EjhVaHMhKXuGQOyehtJw7xAoGAPwuA\n"
|
|
1591
|
+
"jwRQSg+Y74XmbxRwHZcbynPhTpV6DkK7huZp9ZQ5ds0szZdOUqNi+PEbx1isKzj/\n"
|
|
1592
|
+
"KHVzRHy8f5+FmicV/QIjhjHWokl6/vcN89faHzBE1tleejzgiYIQHfUUm3zVaUQa\n"
|
|
1593
|
+
"ZOtSGaGDhpUQPIY6itBcSVl4XGqzmavDpgcNAMUCgYBFFGtG+RbSySzKfRUp3vc5\n"
|
|
1594
|
+
"8YqIdrtXfW9gc9s1+Pw8wfgrY0Rrvy+e3ClSwgGENxgxBvWvhzq2m0S8x2jdLAl1\n"
|
|
1595
|
+
"b+VLGCOpUvS4iN2yrHkoHS7BSW40wLuVooJUAaNOIEPqiv1JC75q2dhTRrANp6WB\n"
|
|
1596
|
+
"bm+7yWVTNlXYuKQqtuOkNQ==\n"
|
|
1597
|
+
"-----END PRIVATE KEY-----\n";
|
|
1598
|
+
|
|
1599
|
+
jwt = rd_kafka_oidc_assertion_create(
|
|
1600
|
+
rk, private_key_pem, NULL, NULL,
|
|
1601
|
+
RD_KAFKA_SASL_OAUTHBEARER_ASSERTION_ALGORITHM_RS256, NULL,
|
|
1602
|
+
"test-subject", "test-issuer", "test-audience", 2, 300, rd_true);
|
|
1603
|
+
|
|
1604
|
+
RD_UT_ASSERT(jwt != NULL, "Failed to create JWT assertion");
|
|
1605
|
+
|
|
1606
|
+
dot1 = strchr(jwt, '.');
|
|
1607
|
+
RD_UT_ASSERT(dot1 != NULL, "JWT missing first dot separator");
|
|
1608
|
+
|
|
1609
|
+
dot2 = strchr(dot1 + 1, '.');
|
|
1610
|
+
RD_UT_ASSERT(dot2 != NULL, "JWT missing second dot separator");
|
|
1611
|
+
|
|
1612
|
+
header_part = rd_strndup(jwt, dot1 - jwt);
|
|
1613
|
+
payload_part = rd_strndup(dot1 + 1, dot2 - (dot1 + 1));
|
|
1614
|
+
signature_part = rd_strdup(dot2 + 1);
|
|
1615
|
+
|
|
1616
|
+
RD_UT_ASSERT(strlen(header_part) > 0, "JWT header part is empty");
|
|
1617
|
+
RD_UT_ASSERT(strlen(payload_part) > 0, "JWT payload part is empty");
|
|
1618
|
+
RD_UT_ASSERT(strlen(signature_part) > 0, "JWT signature part is empty");
|
|
1619
|
+
|
|
1620
|
+
RD_UT_ASSERT(!strchr(header_part, '='),
|
|
1621
|
+
"JWT header contains padding character");
|
|
1622
|
+
RD_UT_ASSERT(!strchr(payload_part, '='),
|
|
1623
|
+
"JWT payload contains padding character");
|
|
1624
|
+
RD_UT_ASSERT(!strchr(signature_part, '='),
|
|
1625
|
+
"JWT signature contains padding character");
|
|
1626
|
+
|
|
1627
|
+
RD_UT_ASSERT(!strchr(header_part, '+'),
|
|
1628
|
+
"JWT header contains '+' character");
|
|
1629
|
+
RD_UT_ASSERT(!strchr(header_part, '/'),
|
|
1630
|
+
"JWT header contains '/' character");
|
|
1631
|
+
RD_UT_ASSERT(!strchr(payload_part, '+'),
|
|
1632
|
+
"JWT payload contains '+' character");
|
|
1633
|
+
RD_UT_ASSERT(!strchr(payload_part, '/'),
|
|
1634
|
+
"JWT payload contains '/' character");
|
|
1635
|
+
RD_UT_ASSERT(!strchr(signature_part, '+'),
|
|
1636
|
+
"JWT signature contains '+' character");
|
|
1637
|
+
RD_UT_ASSERT(!strchr(signature_part, '/'),
|
|
1638
|
+
"JWT signature contains '/' character");
|
|
1639
|
+
|
|
1640
|
+
rd_free(header_part);
|
|
1641
|
+
rd_free(payload_part);
|
|
1642
|
+
rd_free(signature_part);
|
|
1643
|
+
rd_free(jwt);
|
|
1644
|
+
rd_free(rk);
|
|
1645
|
+
|
|
1646
|
+
RD_UT_PASS();
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
int unittest_sasl_oauthbearer_oidc_jwt_bearer(void) {
|
|
1650
|
+
int fails = 0;
|
|
1651
|
+
|
|
1652
|
+
fails += ut_sasl_oauthbearer_oidc_jwt_bearer_base64url_encode();
|
|
1653
|
+
fails += ut_sasl_oauthbearer_oidc_jwt_bearer_build_request_body();
|
|
1654
|
+
|
|
1655
|
+
return fails;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
int unittest_sasl_oauthbearer_oidc_assertion(void) {
|
|
1659
|
+
int fails = 0;
|
|
1660
|
+
|
|
1661
|
+
fails += ut_sasl_oauthbearer_oidc_assertion_parse_from_file();
|
|
1662
|
+
fails += ut_sasl_oauthbearer_oidc_assertion_process_template_file();
|
|
1663
|
+
fails += ut_sasl_oauthbearer_oidc_assertion_create();
|
|
1664
|
+
|
|
1665
|
+
return fails;
|
|
1666
|
+
}
|