All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.strimzi.kafka.oauth.server.authorizer.Configuration Maven / Gradle / Ivy

/*
 * Copyright 2017-2023, Strimzi authors.
 * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
 */
package io.strimzi.kafka.oauth.server.authorizer;

import io.strimzi.kafka.oauth.client.ClientConfig;
import io.strimzi.kafka.oauth.common.Config;
import io.strimzi.kafka.oauth.common.ConfigException;
import io.strimzi.kafka.oauth.common.ConfigUtil;
import io.strimzi.kafka.oauth.server.OAuthKafkaPrincipalBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Collectors;

import static io.strimzi.kafka.oauth.client.ClientConfig.OAUTH_TOKEN_ENDPOINT_URI;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_CONNECT_TIMEOUT_SECONDS;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_ENABLE_METRICS;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_READ_TIMEOUT_SECONDS;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_SSL_SECURE_RANDOM_IMPLEMENTATION;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_SSL_TRUSTSTORE_CERTIFICATES;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_SSL_TRUSTSTORE_LOCATION;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_SSL_TRUSTSTORE_PASSWORD;
import static io.strimzi.kafka.oauth.common.Config.OAUTH_SSL_TRUSTSTORE_TYPE;
import static io.strimzi.kafka.oauth.common.Config.isTrue;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_CLIENT_ID;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_CONNECT_TIMEOUT_SECONDS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_DELEGATE_TO_KAFKA_ACL;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_ENABLE_METRICS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_GRANTS_GC_PERIOD_SECONDS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_GRANTS_MAX_IDLE_TIME_SECONDS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_GRANTS_REFRESH_PERIOD_SECONDS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_GRANTS_REFRESH_POOL_SIZE;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_HTTP_RETRIES;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_KAFKA_CLUSTER_NAME;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_READ_TIMEOUT_SECONDS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_REUSE_GRANTS;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_SSL_SECURE_RANDOM_IMPLEMENTATION;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_CERTIFICATES;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_LOCATION;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_PASSWORD;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_TYPE;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_TOKEN_ENDPOINT_URI;
import static io.strimzi.kafka.oauth.server.authorizer.AuthzConfig.STRIMZI_AUTHORIZATION_INCLUDE_ACCEPT_HEADER;

/**
 * The classes used to parse and store Authorizer configuration.
 * It is used to compare two configurations for equality and used by KeycloakAuthorizer to maintain a single instance of active Authorizer
 * in the JVM even when multiple authorizers are instantiated from a single authorizer configuration.
 */
@SuppressWarnings({"deprecation"})
public class Configuration {

    private static final Logger log = LoggerFactory.getLogger(Configuration.class);
    private static final String PRINCIPAL_BUILDER_CLASS = OAuthKafkaPrincipalBuilder.class.getName();
    private static final String DEPRECATED_PRINCIPAL_BUILDER_CLASS = JwtKafkaPrincipalBuilder.class.getName();

    private final Map configMap;

    private final List logs = new LinkedList<>();


    private final boolean reuseGrants;
    private final String clientId;
    private final String clusterName;
    private final boolean delegateToKafkaACL;
    private final int grantsRefreshPeriodSeconds;
    private final int grantsMaxIdleTimeSeconds;
    private final int grantsRefreshPoolSize;
    private final int gcPeriodSeconds;
    private boolean isKRaft;
    private String truststore;
    private String truststoreData;
    private String truststorePassword;
    private String truststoreType;
    private String prng;
    private String certificateHostCheckAlgorithm;
    private List superUsers = Collections.emptyList();
    private int httpRetries;
    private boolean enableMetrics;
    private URI tokenEndpointUrl;
    private int connectTimeoutSeconds;
    private int readTimeoutSeconds;
    private boolean includeAcceptHeader;

    /**
     * Create a new Configuration instance
     * 

* If some configuration is invalid in a way that it can't be automatically fixed, the {@link ConfigException} is thrown. * * @param configs Configuration map passed by Kafka broker to {@link org.apache.kafka.server.authorizer.Authorizer#configure} method */ Configuration(Map configs) { this.configMap = configs; AuthzConfig authzConfig = convertToAuthzConfig(configs); String pbclass = (String) configMap.get("principal.builder.class"); if (!PRINCIPAL_BUILDER_CLASS.equals(pbclass) && !DEPRECATED_PRINCIPAL_BUILDER_CLASS.equals(pbclass)) { throw new ConfigException("This authorizer requires " + PRINCIPAL_BUILDER_CLASS + " as 'principal.builder.class'"); } if (DEPRECATED_PRINCIPAL_BUILDER_CLASS.equals(pbclass)) { logs.add(new Log(Log.Level.WARNING, "The '" + DEPRECATED_PRINCIPAL_BUILDER_CLASS + "' class has been deprecated, and may be removed in the future. Please use '" + PRINCIPAL_BUILDER_CLASS + "' as 'principal.builder.class' instead.")); } configureTokenEndpoint(authzConfig); clientId = ConfigUtil.getConfigWithFallbackLookup(authzConfig, STRIMZI_AUTHORIZATION_CLIENT_ID, ClientConfig.OAUTH_CLIENT_ID); if (clientId == null) { throw new ConfigException("OAuth client id ('" + STRIMZI_AUTHORIZATION_CLIENT_ID + "') not set."); } configureSSLFactory(authzConfig); configureHostnameVerifier(authzConfig); configureHttpTimeouts(authzConfig); String clusterName = authzConfig.getValue(STRIMZI_AUTHORIZATION_KAFKA_CLUSTER_NAME); if (clusterName == null) { clusterName = "kafka-cluster"; } this.clusterName = clusterName; delegateToKafkaACL = authzConfig.getValueAsBoolean(STRIMZI_AUTHORIZATION_DELEGATE_TO_KAFKA_ACL, false); configureSuperUsers(configs); // Number of threads that can perform token endpoint requests at the same time grantsRefreshPoolSize = authzConfig.getValueAsInt(STRIMZI_AUTHORIZATION_GRANTS_REFRESH_POOL_SIZE, 5); if (grantsRefreshPoolSize < 1) { throw new ConfigException("Invalid value of '" + STRIMZI_AUTHORIZATION_GRANTS_REFRESH_POOL_SIZE + "': " + grantsRefreshPoolSize + ". Has to be >= 1."); } // Less or equal zero means to never refresh grantsRefreshPeriodSeconds = authzConfig.getValueAsInt(STRIMZI_AUTHORIZATION_GRANTS_REFRESH_PERIOD_SECONDS, 60); grantsMaxIdleTimeSeconds = configureGrantsMaxIdleTimeSeconds(authzConfig); gcPeriodSeconds = configureGcPeriodSeconds(authzConfig); reuseGrants = authzConfig.getValueAsBoolean(STRIMZI_AUTHORIZATION_REUSE_GRANTS, true); includeAcceptHeader = ConfigUtil.getDefaultBooleanConfigWithFallbackLookup(authzConfig, STRIMZI_AUTHORIZATION_INCLUDE_ACCEPT_HEADER, Config.OAUTH_INCLUDE_ACCEPT_HEADER, true); configureHttpRetries(authzConfig); configureMetrics(authzConfig); } /** * When a new instance of the Configuration is created some configuration options may generate warnings. * Use this method to print those warning to the log. *

* This method decouples configuration creation from logging warnings. */ public void printLogs() { for (Log line: logs) { if (line.level == Log.Level.WARNING) { log.warn(line.message); } else { log.debug(line.message); } } } private int configureGrantsMaxIdleTimeSeconds(AuthzConfig config) { int grantsMaxIdleTimeSeconds = config.getValueAsInt(STRIMZI_AUTHORIZATION_GRANTS_MAX_IDLE_TIME_SECONDS, 300); if (grantsMaxIdleTimeSeconds <= 0) { logs.add(new Log(Log.Level.WARNING, "'" + STRIMZI_AUTHORIZATION_GRANTS_MAX_IDLE_TIME_SECONDS + "' set to invalid value: " + grantsMaxIdleTimeSeconds + " (should be a positive number), using the default value: 300 seconds")); grantsMaxIdleTimeSeconds = 300; } return grantsMaxIdleTimeSeconds; } private int configureGcPeriodSeconds(AuthzConfig config) { int gcPeriodSeconds = config.getValueAsInt(STRIMZI_AUTHORIZATION_GRANTS_GC_PERIOD_SECONDS, 300); if (gcPeriodSeconds <= 0) { logs.add(new Log(Log.Level.WARNING, "'" + STRIMZI_AUTHORIZATION_GRANTS_GC_PERIOD_SECONDS + "' set to invalid value: " + gcPeriodSeconds + ", using the default value: 300 seconds")); gcPeriodSeconds = 300; } return gcPeriodSeconds; } private void configureSuperUsers(Map configs) { String users = (String) configs.get("super.users"); if (users != null) { superUsers = Arrays.stream(users.split(";")) .map(UserSpec::of) .collect(Collectors.toList()); } } private void configureSSLFactory(AuthzConfig config) { truststore = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_LOCATION, OAUTH_SSL_TRUSTSTORE_LOCATION); truststoreData = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_CERTIFICATES, OAUTH_SSL_TRUSTSTORE_CERTIFICATES); truststorePassword = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_PASSWORD, OAUTH_SSL_TRUSTSTORE_PASSWORD); truststoreType = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_TYPE, OAUTH_SSL_TRUSTSTORE_TYPE); prng = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_SSL_SECURE_RANDOM_IMPLEMENTATION, OAUTH_SSL_SECURE_RANDOM_IMPLEMENTATION); } private void configureHostnameVerifier(AuthzConfig config) { String hostCheck = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM, OAUTH_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM); if (hostCheck == null) { hostCheck = "HTTPS"; } this.certificateHostCheckAlgorithm = hostCheck; } private void configureHttpRetries(AuthzConfig config) { httpRetries = config.getValueAsInt(STRIMZI_AUTHORIZATION_HTTP_RETRIES, 0); if (httpRetries < 0) { throw new ConfigException("Invalid value of '" + STRIMZI_AUTHORIZATION_HTTP_RETRIES + "': " + httpRetries + ". Has to be >= 0."); } } private void configureMetrics(AuthzConfig config) { String enableMetricsString = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_ENABLE_METRICS, OAUTH_ENABLE_METRICS); try { enableMetrics = enableMetricsString != null && isTrue(enableMetricsString); } catch (Exception e) { throw new ConfigException("Bad boolean value for key: " + STRIMZI_AUTHORIZATION_ENABLE_METRICS + ", value: " + enableMetricsString); } } private void configureTokenEndpoint(AuthzConfig config) { String endpoint = ConfigUtil.getConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_TOKEN_ENDPOINT_URI, OAUTH_TOKEN_ENDPOINT_URI); if (endpoint == null) { throw new ConfigException("OAuth2 Token Endpoint ('" + STRIMZI_AUTHORIZATION_TOKEN_ENDPOINT_URI + "') not set."); } try { tokenEndpointUrl = new URI(endpoint); } catch (URISyntaxException e) { throw new ConfigException("Specified token endpoint uri is invalid: " + endpoint); } } private void configureHttpTimeouts(AuthzConfig config) { List warnings = new LinkedList<>(); connectTimeoutSeconds = ConfigUtil.getTimeoutConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_CONNECT_TIMEOUT_SECONDS, OAUTH_CONNECT_TIMEOUT_SECONDS, warnings); readTimeoutSeconds = ConfigUtil.getTimeoutConfigWithFallbackLookup(config, STRIMZI_AUTHORIZATION_READ_TIMEOUT_SECONDS, OAUTH_READ_TIMEOUT_SECONDS, warnings); for (String message: warnings) { logs.add(new Log(Log.Level.WARNING, message)); } } /** * This method extracts the key=value configuration entries relevant for KeycloakRBACAuthorizer from * Kafka properties configuration file (server.properties) and wraps them with AuthzConfig instance. *

* Any new config options have to be added here in order to become visible, otherwise they will be ignored. * * @param configs Kafka configs map * @return Config object */ static AuthzConfig convertToCommonConfig(Map configs) { Properties p = new Properties(); // If you add a new config property, make sure to add it to this list // otherwise it won't be picked String[] keys = { STRIMZI_AUTHORIZATION_GRANTS_REFRESH_PERIOD_SECONDS, STRIMZI_AUTHORIZATION_GRANTS_REFRESH_POOL_SIZE, STRIMZI_AUTHORIZATION_GRANTS_MAX_IDLE_TIME_SECONDS, STRIMZI_AUTHORIZATION_GRANTS_GC_PERIOD_SECONDS, STRIMZI_AUTHORIZATION_HTTP_RETRIES, STRIMZI_AUTHORIZATION_REUSE_GRANTS, STRIMZI_AUTHORIZATION_DELEGATE_TO_KAFKA_ACL, STRIMZI_AUTHORIZATION_KAFKA_CLUSTER_NAME, STRIMZI_AUTHORIZATION_CLIENT_ID, Config.OAUTH_CLIENT_ID, STRIMZI_AUTHORIZATION_TOKEN_ENDPOINT_URI, OAUTH_TOKEN_ENDPOINT_URI, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_LOCATION, OAUTH_SSL_TRUSTSTORE_LOCATION, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_CERTIFICATES, OAUTH_SSL_TRUSTSTORE_CERTIFICATES, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_PASSWORD, OAUTH_SSL_TRUSTSTORE_PASSWORD, STRIMZI_AUTHORIZATION_SSL_TRUSTSTORE_TYPE, OAUTH_SSL_TRUSTSTORE_TYPE, STRIMZI_AUTHORIZATION_SSL_SECURE_RANDOM_IMPLEMENTATION, OAUTH_SSL_SECURE_RANDOM_IMPLEMENTATION, STRIMZI_AUTHORIZATION_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM, OAUTH_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM, STRIMZI_AUTHORIZATION_CONNECT_TIMEOUT_SECONDS, OAUTH_CONNECT_TIMEOUT_SECONDS, STRIMZI_AUTHORIZATION_READ_TIMEOUT_SECONDS, OAUTH_READ_TIMEOUT_SECONDS, STRIMZI_AUTHORIZATION_ENABLE_METRICS, OAUTH_ENABLE_METRICS, STRIMZI_AUTHORIZATION_INCLUDE_ACCEPT_HEADER, Config.OAUTH_INCLUDE_ACCEPT_HEADER }; // Copy over the keys for (String key: keys) { ConfigUtil.putIfNotNull(p, key, configs.get(key)); } return new AuthzConfig(p); } AuthzConfig convertToAuthzConfig(Map configs) { AuthzConfig config = Configuration.convertToCommonConfig(configs); isKRaft = detectKRaft(configs); if (isKRaft) { logs.add(new Log(Log.Level.DEBUG, "Detected KRaft mode ('process.roles' configured)")); } return config; } private boolean detectKRaft(Map configs) { // auto-detect KRaft mode Object prop = configs.get("process.roles"); String processRoles = prop != null ? String.valueOf(prop) : null; return processRoles != null && processRoles.length() > 0; } boolean isKRaft() { return isKRaft; } String getTruststore() { return truststore; } String getTruststoreData() { return truststoreData; } String getTruststorePassword() { return truststorePassword; } String getTruststoreType() { return truststoreType; } String getPrng() { return prng; } String getCertificateHostCheckAlgorithm() { return certificateHostCheckAlgorithm; } boolean isDelegateToKafkaACL() { return delegateToKafkaACL; } URI getTokenEndpointUrl() { return tokenEndpointUrl; } String getClientId() { return clientId; } boolean isReuseGrants() { return reuseGrants; } String getClusterName() { return clusterName; } int getGrantsRefreshPeriodSeconds() { return grantsRefreshPeriodSeconds; } int getGrantsMaxIdleTimeSeconds() { return grantsMaxIdleTimeSeconds; } int getGrantsRefreshPoolSize() { return grantsRefreshPoolSize; } int getGcPeriodSeconds() { return gcPeriodSeconds; } List getSuperUsers() { return superUsers; } int getHttpRetries() { return httpRetries; } boolean isEnableMetrics() { return enableMetrics; } int getConnectTimeoutSeconds() { return connectTimeoutSeconds; } int getReadTimeoutSeconds() { return readTimeoutSeconds; } Map getConfigMap() { return configMap; } boolean getIncludeAcceptHeader() { return includeAcceptHeader; } private static class Log { Level level; String message; Log(Level level, String message) { if (level == null) { throw new IllegalArgumentException("level is null"); } this.level = level; this.message = message; } enum Level { WARNING, DEBUG } } @SuppressWarnings({"checkstyle:CyclomaticComplexity"}) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Configuration that = (Configuration) o; return reuseGrants == that.reuseGrants && delegateToKafkaACL == that.delegateToKafkaACL && grantsRefreshPeriodSeconds == that.grantsRefreshPeriodSeconds && grantsMaxIdleTimeSeconds == that.grantsMaxIdleTimeSeconds && grantsRefreshPoolSize == that.grantsRefreshPoolSize && gcPeriodSeconds == that.gcPeriodSeconds && isKRaft == that.isKRaft && httpRetries == that.httpRetries && enableMetrics == that.enableMetrics && connectTimeoutSeconds == that.connectTimeoutSeconds && readTimeoutSeconds == that.readTimeoutSeconds && includeAcceptHeader == that.includeAcceptHeader && Objects.equals(clientId, that.clientId) && Objects.equals(clusterName, that.clusterName) && Objects.equals(truststore, that.truststore) && Objects.equals(truststoreData, that.truststoreData) && Objects.equals(truststorePassword, that.truststorePassword) && Objects.equals(truststoreType, that.truststoreType) && Objects.equals(prng, that.prng) && Objects.equals(certificateHostCheckAlgorithm, that.certificateHostCheckAlgorithm) && Objects.equals(superUsers, that.superUsers) && Objects.equals(tokenEndpointUrl, that.tokenEndpointUrl); } @Override public int hashCode() { return Objects.hash(reuseGrants, clientId, clusterName, delegateToKafkaACL, grantsRefreshPeriodSeconds, grantsMaxIdleTimeSeconds, grantsRefreshPoolSize, gcPeriodSeconds, isKRaft, truststore, truststoreData, truststorePassword, truststoreType, prng, certificateHostCheckAlgorithm, superUsers, httpRetries, enableMetrics, tokenEndpointUrl, connectTimeoutSeconds, readTimeoutSeconds, includeAcceptHeader); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy