Search in sources :

Example 56 with KafkaListenerAuthenticationOAuth

use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi-kafka-operator by strimzi.

the class KafkaCluster method getNonDataVolumes.

/**
 * Generates list of non-data volumes used by Kafka Pods. This includes tmp volumes, mounted secrets and config
 * maps.
 *
 * @param isOpenShift   Indicates whether we are on OpenShift or not
 *
 * @return              List of nondata volumes used by the ZooKeeper pods
 */
private List<Volume> getNonDataVolumes(boolean isOpenShift) {
    List<Volume> volumeList = new ArrayList<>();
    if (rack != null || isExposedWithNodePort()) {
        volumeList.add(VolumeUtils.createEmptyDirVolume(INIT_VOLUME_NAME, "1Mi", "Memory"));
    }
    volumeList.add(createTempDirVolume());
    volumeList.add(VolumeUtils.createSecretVolume(CLUSTER_CA_CERTS_VOLUME, AbstractModel.clusterCaCertSecretName(cluster), isOpenShift));
    volumeList.add(VolumeUtils.createSecretVolume(BROKER_CERTS_VOLUME, KafkaCluster.brokersSecretName(cluster), isOpenShift));
    volumeList.add(VolumeUtils.createSecretVolume(CLIENT_CA_CERTS_VOLUME, KafkaCluster.clientsCaCertSecretName(cluster), isOpenShift));
    volumeList.add(VolumeUtils.createConfigMapVolume(logAndMetricsConfigVolumeName, ancillaryConfigMapName));
    volumeList.add(VolumeUtils.createEmptyDirVolume("ready-files", "1Ki", "Memory"));
    for (GenericKafkaListener listener : listeners) {
        if (listener.isTls() && listener.getConfiguration() != null && listener.getConfiguration().getBrokerCertChainAndKey() != null) {
            CertAndKeySecretSource secretSource = listener.getConfiguration().getBrokerCertChainAndKey();
            Map<String, String> items = new HashMap<>(2);
            items.put(secretSource.getKey(), "tls.key");
            items.put(secretSource.getCertificate(), "tls.crt");
            volumeList.add(VolumeUtils.createSecretVolume("custom-" + ListenersUtils.identifier(listener) + "-certs", secretSource.getSecretName(), items, isOpenShift));
        }
        if (isListenerWithOAuth(listener)) {
            KafkaListenerAuthenticationOAuth oauth = (KafkaListenerAuthenticationOAuth) listener.getAuth();
            volumeList.addAll(AuthenticationUtils.configureOauthCertificateVolumes("oauth-" + ListenersUtils.identifier(listener), oauth.getTlsTrustedCertificates(), isOpenShift));
        }
        if (isListenerWithCustomAuth(listener)) {
            KafkaListenerAuthenticationCustom custom = (KafkaListenerAuthenticationCustom) listener.getAuth();
            volumeList.addAll(AuthenticationUtils.configureGenericSecretVolumes("custom-listener-" + ListenersUtils.identifier(listener), custom.getSecrets(), isOpenShift));
        }
    }
    if (authorization instanceof KafkaAuthorizationKeycloak) {
        KafkaAuthorizationKeycloak keycloakAuthz = (KafkaAuthorizationKeycloak) authorization;
        volumeList.addAll(AuthenticationUtils.configureOauthCertificateVolumes("authz-keycloak", keycloakAuthz.getTlsTrustedCertificates(), isOpenShift));
    }
    return volumeList;
}
Also used : GenericKafkaListener(io.strimzi.api.kafka.model.listener.arraylistener.GenericKafkaListener) Volume(io.fabric8.kubernetes.api.model.Volume) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) KafkaListenerAuthenticationOAuth(io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth) KafkaAuthorizationKeycloak(io.strimzi.api.kafka.model.KafkaAuthorizationKeycloak) KafkaListenerAuthenticationCustom(io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationCustom) CertAndKeySecretSource(io.strimzi.api.kafka.model.CertAndKeySecretSource)

Example 57 with KafkaListenerAuthenticationOAuth

use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi-kafka-operator by strimzi.

the class ListenersValidator method validateOauth.

/**
 * Validates provided OAuth configuration. Throws InvalidResourceException when OAuth configuration contains forbidden combinations.
 *
 * @param errors    List where any found errors will be added
 * @param listener  The listener where OAuth authentication should be validated
 */
@SuppressWarnings({ "checkstyle:BooleanExpressionComplexity", "checkstyle:NPathComplexity", "checkstyle:CyclomaticComplexity" })
private static void validateOauth(Set<String> errors, GenericKafkaListener listener) {
    if (isListenerWithOAuth(listener)) {
        KafkaListenerAuthenticationOAuth oAuth = (KafkaListenerAuthenticationOAuth) listener.getAuth();
        String listenerName = listener.getName();
        if (!oAuth.isEnablePlain() && !oAuth.isEnableOauthBearer()) {
            errors.add("listener " + listenerName + ": At least one of 'enablePlain', 'enableOauthBearer' has to be set to 'true'");
        }
        boolean hasJwksRefreshSecondsValidInput = oAuth.getJwksRefreshSeconds() != null && oAuth.getJwksRefreshSeconds() > 0;
        boolean hasJwksExpirySecondsValidInput = oAuth.getJwksExpirySeconds() != null && oAuth.getJwksExpirySeconds() > 0;
        boolean hasJwksMinRefreshPauseSecondsValidInput = oAuth.getJwksMinRefreshPauseSeconds() != null && oAuth.getJwksMinRefreshPauseSeconds() >= 0;
        if (oAuth.getIntrospectionEndpointUri() == null && oAuth.getJwksEndpointUri() == null) {
            errors.add("listener " + listenerName + ": Introspection endpoint URI or JWKS endpoint URI has to be specified");
        }
        if (oAuth.getValidIssuerUri() == null && oAuth.isCheckIssuer()) {
            errors.add("listener " + listenerName + ": Valid Issuer URI has to be specified or 'checkIssuer' set to 'false'");
        }
        if (oAuth.getConnectTimeoutSeconds() != null && oAuth.getConnectTimeoutSeconds() <= 0) {
            errors.add("listener " + listenerName + ": 'connectTimeoutSeconds' needs to be a positive integer (set to: " + oAuth.getConnectTimeoutSeconds() + ")");
        }
        if (oAuth.getReadTimeoutSeconds() != null && oAuth.getReadTimeoutSeconds() <= 0) {
            errors.add("listener " + listenerName + ": 'readTimeoutSeconds' needs to be a positive integer (set to: " + oAuth.getReadTimeoutSeconds() + ")");
        }
        if (oAuth.isCheckAudience() && oAuth.getClientId() == null) {
            errors.add("listener " + listenerName + ": 'clientId' has to be configured when 'checkAudience' is 'true'");
        }
        String customCheckQuery = oAuth.getCustomClaimCheck();
        if (customCheckQuery != null) {
            try {
                JsonPathFilterQuery.parse(customCheckQuery);
            } catch (Exception e) {
                errors.add("listener " + listenerName + ": 'customClaimCheck' value not a valid JsonPath filter query - " + e.getMessage());
            }
        }
        if (oAuth.getIntrospectionEndpointUri() != null && (oAuth.getClientId() == null || oAuth.getClientSecret() == null)) {
            errors.add("listener " + listenerName + ": Introspection Endpoint URI needs to be configured together with 'clientId' and 'clientSecret'");
        }
        if (oAuth.getUserInfoEndpointUri() != null && oAuth.getIntrospectionEndpointUri() == null) {
            errors.add("listener " + listenerName + ": User Info Endpoint URI can only be used if Introspection Endpoint URI is also configured");
        }
        if (oAuth.getJwksEndpointUri() == null && (hasJwksRefreshSecondsValidInput || hasJwksExpirySecondsValidInput || hasJwksMinRefreshPauseSecondsValidInput)) {
            errors.add("listener " + listenerName + ": 'jwksRefreshSeconds', 'jwksExpirySeconds' and 'jwksMinRefreshPauseSeconds' can only be used together with 'jwksEndpointUri'");
        }
        if (oAuth.getJwksRefreshSeconds() != null && !hasJwksRefreshSecondsValidInput) {
            errors.add("listener " + listenerName + ": 'jwksRefreshSeconds' needs to be a positive integer (set to: " + oAuth.getJwksRefreshSeconds() + ")");
        }
        if (oAuth.getJwksExpirySeconds() != null && !hasJwksExpirySecondsValidInput) {
            errors.add("listener " + listenerName + ": 'jwksExpirySeconds' needs to be a positive integer (set to: " + oAuth.getJwksExpirySeconds() + ")");
        }
        if (oAuth.getJwksMinRefreshPauseSeconds() != null && !hasJwksMinRefreshPauseSecondsValidInput) {
            errors.add("listener " + listenerName + ": 'jwksMinRefreshPauseSeconds' needs to be a positive integer or zero (set to: " + oAuth.getJwksMinRefreshPauseSeconds() + ")");
        }
        if ((hasJwksExpirySecondsValidInput && hasJwksRefreshSecondsValidInput && oAuth.getJwksExpirySeconds() < oAuth.getJwksRefreshSeconds() + 60) || (!hasJwksExpirySecondsValidInput && hasJwksRefreshSecondsValidInput && KafkaListenerAuthenticationOAuth.DEFAULT_JWKS_EXPIRY_SECONDS < oAuth.getJwksRefreshSeconds() + 60) || (hasJwksExpirySecondsValidInput && !hasJwksRefreshSecondsValidInput && oAuth.getJwksExpirySeconds() < KafkaListenerAuthenticationOAuth.DEFAULT_JWKS_REFRESH_SECONDS + 60)) {
            errors.add("listener " + listenerName + ": The refresh interval has to be at least 60 seconds shorter then the expiry interval specified in `jwksExpirySeconds`");
        }
        if (!oAuth.isAccessTokenIsJwt()) {
            if (oAuth.getJwksEndpointUri() != null) {
                errors.add("listener " + listenerName + ": 'accessTokenIsJwt' can not be 'false' when 'jwksEndpointUri' is set");
            }
            if (!oAuth.isCheckAccessTokenType()) {
                errors.add("listener " + listenerName + ": 'checkAccessTokenType' can not be set to 'false' when 'accessTokenIsJwt' is 'false'");
            }
        }
        if (!oAuth.isCheckAccessTokenType() && oAuth.getIntrospectionEndpointUri() != null) {
            errors.add("listener " + listenerName + ": 'checkAccessTokenType' can not be set to 'false' when 'introspectionEndpointUri' is set");
        }
        if (oAuth.getValidTokenType() != null && oAuth.getIntrospectionEndpointUri() == null) {
            errors.add("listener " + listenerName + ": 'validTokenType' can only be used when 'introspectionEndpointUri' is set");
        }
        String groupsQuery = oAuth.getGroupsClaim();
        if (groupsQuery != null) {
            try {
                JsonPathQuery.parse(groupsQuery);
            } catch (Exception e) {
                errors.add("listener " + listenerName + ": 'groupsClaim' value not a valid JsonPath query - " + e.getMessage());
            }
        }
    }
}
Also used : KafkaListenerAuthenticationOAuth(io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth)

Example 58 with KafkaListenerAuthenticationOAuth

use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi-kafka-operator by strimzi.

the class OauthScopeIsolatedST method testClientScopeKafkaSetIncorrectly.

@IsolatedTest("Modification of shared Kafka cluster")
void testClientScopeKafkaSetIncorrectly(ExtensionContext extensionContext) throws UnexpectedException {
    final String kafkaClientsName = mapWithKafkaClientNames.get(extensionContext.getDisplayName());
    final String clusterName = mapWithClusterNames.get(extensionContext.getDisplayName());
    final String producerName = OAUTH_PRODUCER_NAME + "-" + clusterName;
    final String consumerName = OAUTH_CONSUMER_NAME + "-" + clusterName;
    final String topicName = mapWithTestTopics.get(extensionContext.getDisplayName());
    final LabelSelector kafkaSelector = KafkaResource.getLabelSelector(oauthClusterName, KafkaResources.kafkaStatefulSetName(oauthClusterName));
    KafkaClients oauthInternalClientChecksJob = new KafkaClientsBuilder().withNamespaceName(INFRA_NAMESPACE).withProducerName(producerName).withConsumerName(consumerName).withBootstrapAddress(KafkaResources.bootstrapServiceName(oauthClusterName) + ":" + scopeListenerPort).withTopicName(topicName).withMessageCount(MESSAGE_COUNT).withAdditionalConfig(additionalOauthConfig).build();
    // re-configuring Kafka listener to have client scope assigned to null
    KafkaResource.replaceKafkaResourceInSpecificNamespace(oauthClusterName, kafka -> {
        List<GenericKafkaListener> scopeListeners = kafka.getSpec().getKafka().getListeners().stream().filter(listener -> listener.getName().equals(scopeListener)).collect(Collectors.toList());
        ((KafkaListenerAuthenticationOAuth) scopeListeners.get(0).getAuth()).setClientScope(null);
        kafka.getSpec().getKafka().getListeners().set(0, scopeListeners.get(0));
    }, INFRA_NAMESPACE);
    RollingUpdateUtils.waitForComponentAndPodsReady(INFRA_NAMESPACE, kafkaSelector, 1);
    // verification phase client should fail here because clientScope is set to 'null'
    resourceManager.createResource(extensionContext, KafkaTopicTemplates.topic(oauthClusterName, topicName, INFRA_NAMESPACE).build());
    resourceManager.createResource(extensionContext, oauthInternalClientChecksJob.producerStrimzi());
    // client should fail because the listener requires scope: 'test' in JWT token but was (the listener) temporarily
    // configured without clientScope resulting in a JWT token without the scope claim when using the clientId and
    // secret passed via SASL/PLAIN to obtain an access token in client's name.
    ClientUtils.waitForClientTimeout(producerName, INFRA_NAMESPACE, MESSAGE_COUNT);
    JobUtils.deleteJobWithWait(INFRA_NAMESPACE, producerName);
    // rollback previous configuration
    // re-configuring Kafka listener to have client scope assigned to 'test'
    KafkaResource.replaceKafkaResourceInSpecificNamespace(oauthClusterName, kafka -> {
        List<GenericKafkaListener> scopeListeners = kafka.getSpec().getKafka().getListeners().stream().filter(listener -> listener.getName().equals(scopeListener)).collect(Collectors.toList());
        ((KafkaListenerAuthenticationOAuth) scopeListeners.get(0).getAuth()).setClientScope("test");
        kafka.getSpec().getKafka().getListeners().set(0, scopeListeners.get(0));
    }, INFRA_NAMESPACE);
    RollingUpdateUtils.waitForComponentAndPodsReady(INFRA_NAMESPACE, kafkaSelector, 1);
}
Also used : KafkaClientsBuilder(io.strimzi.systemtest.kafkaclients.internalClients.KafkaClientsBuilder) ParallelTest(io.strimzi.systemtest.annotations.ParallelTest) CoreMatchers(org.hamcrest.CoreMatchers) GenericKafkaListener(io.strimzi.api.kafka.model.listener.arraylistener.GenericKafkaListener) KafkaClientsTemplates(io.strimzi.systemtest.templates.crd.KafkaClientsTemplates) LabelSelector(io.fabric8.kubernetes.api.model.LabelSelector) KafkaConnectTemplates(io.strimzi.systemtest.templates.crd.KafkaConnectTemplates) CONNECT(io.strimzi.systemtest.Constants.CONNECT) KafkaResource(io.strimzi.systemtest.resources.crd.KafkaResource) Level(org.apache.logging.log4j.Level) ResourceManager.kubeClient(io.strimzi.systemtest.resources.ResourceManager.kubeClient) ExtensionContext(org.junit.jupiter.api.extension.ExtensionContext) INFRA_NAMESPACE(io.strimzi.systemtest.Constants.INFRA_NAMESPACE) AfterAll(org.junit.jupiter.api.AfterAll) PodUtils(io.strimzi.systemtest.utils.kubeUtils.objects.PodUtils) KafkaResources(io.strimzi.api.kafka.model.KafkaResources) KubeClusterResource(io.strimzi.test.k8s.KubeClusterResource) BeforeAll(org.junit.jupiter.api.BeforeAll) Tag(org.junit.jupiter.api.Tag) MatcherAssert.assertThat(org.hamcrest.MatcherAssert.assertThat) StUtils(io.strimzi.systemtest.utils.StUtils) KafkaTemplates(io.strimzi.systemtest.templates.crd.KafkaTemplates) RollingUpdateUtils(io.strimzi.systemtest.utils.RollingUpdateUtils) IsolatedSuite(io.strimzi.systemtest.annotations.IsolatedSuite) KafkaClients(io.strimzi.systemtest.kafkaclients.internalClients.KafkaClients) JobUtils(io.strimzi.systemtest.utils.kubeUtils.controllers.JobUtils) KafkaClientsBuilder(io.strimzi.systemtest.kafkaclients.internalClients.KafkaClientsBuilder) GenericKafkaListenerBuilder(io.strimzi.api.kafka.model.listener.arraylistener.GenericKafkaListenerBuilder) UnexpectedException(java.rmi.UnexpectedException) OAUTH(io.strimzi.systemtest.Constants.OAUTH) Collectors(java.util.stream.Collectors) KafkaListenerAuthenticationOAuth(io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth) ClientUtils(io.strimzi.systemtest.utils.ClientUtils) IsolatedTest(io.strimzi.systemtest.annotations.IsolatedTest) KeycloakUtils(io.strimzi.systemtest.utils.specific.KeycloakUtils) List(java.util.List) KafkaListenerType(io.strimzi.api.kafka.model.listener.arraylistener.KafkaListenerType) KafkaTopicTemplates(io.strimzi.systemtest.templates.crd.KafkaTopicTemplates) REGRESSION(io.strimzi.systemtest.Constants.REGRESSION) KafkaConnectResources(io.strimzi.api.kafka.model.KafkaConnectResources) GenericKafkaListener(io.strimzi.api.kafka.model.listener.arraylistener.GenericKafkaListener) KafkaClients(io.strimzi.systemtest.kafkaclients.internalClients.KafkaClients) LabelSelector(io.fabric8.kubernetes.api.model.LabelSelector) KafkaListenerAuthenticationOAuth(io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth) IsolatedTest(io.strimzi.systemtest.annotations.IsolatedTest)

Aggregations

KafkaListenerAuthenticationOAuth (io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth)58 KafkaListenerAuthenticationOAuthBuilder (io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuthBuilder)44 ParallelTest (io.strimzi.test.annotations.ParallelTest)44 GenericKafkaListener (io.strimzi.api.kafka.model.listener.arraylistener.GenericKafkaListener)12 ArrayList (java.util.ArrayList)10 KafkaAuthorizationKeycloak (io.strimzi.api.kafka.model.KafkaAuthorizationKeycloak)6 KafkaListenerAuthenticationCustom (io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationCustom)6 VolumeMount (io.fabric8.kubernetes.api.model.VolumeMount)4 CertAndKeySecretSource (io.strimzi.api.kafka.model.CertAndKeySecretSource)4 KafkaResources (io.strimzi.api.kafka.model.KafkaResources)4 List (java.util.List)4 Collectors (java.util.stream.Collectors)4 CoreMatchers.containsString (org.hamcrest.CoreMatchers.containsString)3 EnvVar (io.fabric8.kubernetes.api.model.EnvVar)2 LabelSelector (io.fabric8.kubernetes.api.model.LabelSelector)2 Volume (io.fabric8.kubernetes.api.model.Volume)2 ContainerEnvVar (io.strimzi.api.kafka.model.ContainerEnvVar)2 CruiseControlSpec (io.strimzi.api.kafka.model.CruiseControlSpec)2 KafkaAuthorization (io.strimzi.api.kafka.model.KafkaAuthorization)2 KafkaAuthorizationCustom (io.strimzi.api.kafka.model.KafkaAuthorizationCustom)2