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

org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallbackHandler Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.kafka.common.security.oauthbearer;

import java.io.IOException;
import java.security.Key;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
import org.apache.kafka.common.security.oauthbearer.internals.secured.AccessTokenValidator;
import org.apache.kafka.common.security.oauthbearer.internals.secured.AccessTokenValidatorFactory;
import org.apache.kafka.common.security.oauthbearer.internals.secured.CloseableVerificationKeyResolver;
import org.apache.kafka.common.security.oauthbearer.internals.secured.JaasOptionsUtils;
import org.apache.kafka.common.security.oauthbearer.internals.secured.RefreshingHttpsJwksVerificationKeyResolver;
import org.apache.kafka.common.security.oauthbearer.internals.secured.ValidateException;
import org.apache.kafka.common.security.oauthbearer.internals.secured.VerificationKeyResolverFactory;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwx.JsonWebStructure;
import org.jose4j.lang.UnresolvableKeyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

* OAuthBearerValidatorCallbackHandler is an {@link AuthenticateCallbackHandler} that * accepts {@link OAuthBearerValidatorCallback} and {@link OAuthBearerExtensionsValidatorCallback} * callbacks to implement OAuth/OIDC validation. This callback handler is intended only to be used * on the Kafka broker side as it will receive a {@link OAuthBearerValidatorCallback} that includes * the JWT provided by the Kafka client. That JWT is validated in terms of format, expiration, * signature, and audience and issuer (if desired). This callback handler is the broker side of the * OAuth functionality, whereas {@link OAuthBearerLoginCallbackHandler} is used by clients. *

* *

* This {@link AuthenticateCallbackHandler} is enabled in the broker configuration by setting the * {@link org.apache.kafka.common.config.internals.BrokerSecurityConfigs#SASL_SERVER_CALLBACK_HANDLER_CLASS_CONFIG} * like so: * * * listener.name..oauthbearer.sasl.server.callback.handler.class=org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallbackHandler * *

* *

* The JAAS configuration for OAuth is also needed. If using OAuth for inter-broker communication, * the options are those specified in {@link OAuthBearerLoginCallbackHandler}. *

* *

* The configuration option * {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_JWKS_ENDPOINT_URL} * is also required in order to contact the OAuth/OIDC provider to retrieve the JWKS for use in * JWT signature validation. For example: * * * listener.name..oauthbearer.sasl.oauthbearer.jwks.endpoint.url=https://example.com/oauth2/v1/keys * * * Please see the OAuth/OIDC providers documentation for the JWKS endpoint URL. *

* *

* The following is a list of all the configuration options that are available for the broker * validation callback handler: * *

    *
  • {@link org.apache.kafka.common.config.internals.BrokerSecurityConfigs#SASL_SERVER_CALLBACK_HANDLER_CLASS_CONFIG}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_JAAS_CONFIG}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_CLOCK_SKEW_SECONDS}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_EXPECTED_AUDIENCE}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_EXPECTED_ISSUER}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_JWKS_ENDPOINT_REFRESH_MS}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_JWKS_ENDPOINT_RETRY_BACKOFF_MAX_MS}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_JWKS_ENDPOINT_RETRY_BACKOFF_MS}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_JWKS_ENDPOINT_URL}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_SCOPE_CLAIM_NAME}
  • *
  • {@link org.apache.kafka.common.config.SaslConfigs#SASL_OAUTHBEARER_SUB_CLAIM_NAME}
  • *
*

*/ public class OAuthBearerValidatorCallbackHandler implements AuthenticateCallbackHandler { private static final Logger log = LoggerFactory.getLogger(OAuthBearerValidatorCallbackHandler.class); /** * Because a {@link CloseableVerificationKeyResolver} instance can spawn threads and issue * HTTP(S) calls ({@link RefreshingHttpsJwksVerificationKeyResolver}), we only want to create * a new instance for each particular set of configuration. Because each set of configuration * may have multiple instances, we want to reuse the single instance. */ private static final Map VERIFICATION_KEY_RESOLVER_CACHE = new HashMap<>(); private CloseableVerificationKeyResolver verificationKeyResolver; private AccessTokenValidator accessTokenValidator; private boolean isInitialized = false; @Override public void configure(Map configs, String saslMechanism, List jaasConfigEntries) { Map moduleOptions = JaasOptionsUtils.getOptions(saslMechanism, jaasConfigEntries); CloseableVerificationKeyResolver verificationKeyResolver; // Here's the logic which keeps our VerificationKeyResolvers down to a single instance. synchronized (VERIFICATION_KEY_RESOLVER_CACHE) { VerificationKeyResolverKey key = new VerificationKeyResolverKey(configs, moduleOptions); verificationKeyResolver = VERIFICATION_KEY_RESOLVER_CACHE.computeIfAbsent(key, k -> new RefCountingVerificationKeyResolver(VerificationKeyResolverFactory.create(configs, saslMechanism, moduleOptions))); } AccessTokenValidator accessTokenValidator = AccessTokenValidatorFactory.create(configs, saslMechanism, verificationKeyResolver); init(verificationKeyResolver, accessTokenValidator); } public void init(CloseableVerificationKeyResolver verificationKeyResolver, AccessTokenValidator accessTokenValidator) { this.verificationKeyResolver = verificationKeyResolver; this.accessTokenValidator = accessTokenValidator; try { verificationKeyResolver.init(); } catch (Exception e) { throw new KafkaException("The OAuth validator configuration encountered an error when initializing the VerificationKeyResolver", e); } isInitialized = true; } @Override public void close() { if (verificationKeyResolver != null) { try { verificationKeyResolver.close(); } catch (Exception e) { log.error(e.getMessage(), e); } } } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { checkInitialized(); for (Callback callback : callbacks) { if (callback instanceof OAuthBearerValidatorCallback) { handleValidatorCallback((OAuthBearerValidatorCallback) callback); } else if (callback instanceof OAuthBearerExtensionsValidatorCallback) { handleExtensionsValidatorCallback((OAuthBearerExtensionsValidatorCallback) callback); } else { throw new UnsupportedCallbackException(callback); } } } private void handleValidatorCallback(OAuthBearerValidatorCallback callback) { checkInitialized(); OAuthBearerToken token; try { token = accessTokenValidator.validate(callback.tokenValue()); callback.token(token); } catch (ValidateException e) { log.warn(e.getMessage(), e); callback.error("invalid_token", null, null); } } private void handleExtensionsValidatorCallback(OAuthBearerExtensionsValidatorCallback extensionsValidatorCallback) { checkInitialized(); extensionsValidatorCallback.inputExtensions().map().forEach((extensionName, v) -> extensionsValidatorCallback.valid(extensionName)); } private void checkInitialized() { if (!isInitialized) throw new IllegalStateException(String.format("To use %s, first call the configure or init method", getClass().getSimpleName())); } /** * VkrKey is a simple structure which encapsulates the criteria for different * sets of configuration. This will allow us to use this object as a key in a {@link Map} * to keep a single instance per key. */ private static class VerificationKeyResolverKey { private final Map configs; private final Map moduleOptions; public VerificationKeyResolverKey(Map configs, Map moduleOptions) { this.configs = configs; this.moduleOptions = moduleOptions; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } VerificationKeyResolverKey that = (VerificationKeyResolverKey) o; return configs.equals(that.configs) && moduleOptions.equals(that.moduleOptions); } @Override public int hashCode() { return Objects.hash(configs, moduleOptions); } } /** * RefCountingVerificationKeyResolver allows us to share a single * {@link CloseableVerificationKeyResolver} instance between multiple * {@link AuthenticateCallbackHandler} instances and perform the lifecycle methods the * appropriate number of times. */ private static class RefCountingVerificationKeyResolver implements CloseableVerificationKeyResolver { private final CloseableVerificationKeyResolver delegate; private final AtomicInteger count = new AtomicInteger(0); public RefCountingVerificationKeyResolver(CloseableVerificationKeyResolver delegate) { this.delegate = delegate; } @Override public Key resolveKey(JsonWebSignature jws, List nestingContext) throws UnresolvableKeyException { return delegate.resolveKey(jws, nestingContext); } @Override public void init() throws IOException { if (count.incrementAndGet() == 1) delegate.init(); } @Override public void close() throws IOException { if (count.decrementAndGet() == 0) delegate.close(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy