use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi 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);
}
use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi by strimzi.
the class KafkaCluster method getVolumeMounts.
private List<VolumeMount> getVolumeMounts() {
List<VolumeMount> volumeMountList = new ArrayList<>();
volumeMountList.addAll(VolumeUtils.createVolumeMounts(storage, mountPath, false));
volumeMountList.add(createTempDirVolumeMount());
volumeMountList.add(VolumeUtils.createVolumeMount(CLUSTER_CA_CERTS_VOLUME, CLUSTER_CA_CERTS_VOLUME_MOUNT));
volumeMountList.add(VolumeUtils.createVolumeMount(BROKER_CERTS_VOLUME, BROKER_CERTS_VOLUME_MOUNT));
volumeMountList.add(VolumeUtils.createVolumeMount(CLIENT_CA_CERTS_VOLUME, CLIENT_CA_CERTS_VOLUME_MOUNT));
volumeMountList.add(VolumeUtils.createVolumeMount(logAndMetricsConfigVolumeName, logAndMetricsConfigMountPath));
volumeMountList.add(VolumeUtils.createVolumeMount("ready-files", "/var/opt/kafka"));
if (rack != null || isExposedWithNodePort()) {
volumeMountList.add(VolumeUtils.createVolumeMount(INIT_VOLUME_NAME, INIT_VOLUME_MOUNT));
}
for (GenericKafkaListener listener : listeners) {
String identifier = ListenersUtils.identifier(listener);
if (listener.isTls() && listener.getConfiguration() != null && listener.getConfiguration().getBrokerCertChainAndKey() != null) {
volumeMountList.add(VolumeUtils.createVolumeMount("custom-" + identifier + "-certs", "/opt/kafka/certificates/custom-" + identifier + "-certs"));
}
if (isListenerWithOAuth(listener)) {
KafkaListenerAuthenticationOAuth oauth = (KafkaListenerAuthenticationOAuth) listener.getAuth();
volumeMountList.addAll(AuthenticationUtils.configureOauthCertificateVolumeMounts("oauth-" + identifier, oauth.getTlsTrustedCertificates(), OAUTH_TRUSTED_CERTS_BASE_VOLUME_MOUNT + "/oauth-" + identifier + "-certs"));
}
if (isListenerWithCustomAuth(listener)) {
KafkaListenerAuthenticationCustom custom = (KafkaListenerAuthenticationCustom) listener.getAuth();
volumeMountList.addAll(AuthenticationUtils.configureGenericSecretVolumeMounts("custom-listener-" + identifier, custom.getSecrets(), CUSTOM_AUTHN_SECRETS_VOLUME_MOUNT + "/custom-listener-" + identifier));
}
}
if (authorization instanceof KafkaAuthorizationKeycloak) {
KafkaAuthorizationKeycloak keycloakAuthz = (KafkaAuthorizationKeycloak) authorization;
volumeMountList.addAll(AuthenticationUtils.configureOauthCertificateVolumeMounts("authz-keycloak", keycloakAuthz.getTlsTrustedCertificates(), OAUTH_TRUSTED_CERTS_BASE_VOLUME_MOUNT + "/authz-keycloak-certs"));
}
return volumeMountList;
}
use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi by strimzi.
the class KafkaCluster method getEnvVars.
@Override
protected List<EnvVar> getEnvVars() {
List<EnvVar> varList = new ArrayList<>();
varList.add(buildEnvVar(ENV_VAR_KAFKA_METRICS_ENABLED, String.valueOf(isMetricsEnabled)));
varList.add(buildEnvVar(ENV_VAR_STRIMZI_KAFKA_GC_LOG_ENABLED, String.valueOf(gcLoggingEnabled)));
if (javaSystemProperties != null) {
varList.add(buildEnvVar(ENV_VAR_STRIMZI_JAVA_SYSTEM_PROPERTIES, ModelUtils.getJavaSystemPropertiesToString(javaSystemProperties)));
}
heapOptions(varList, 0.5, 5L * 1024L * 1024L * 1024L);
jvmPerformanceOptions(varList);
for (GenericKafkaListener listener : listeners) {
if (isListenerWithOAuth(listener)) {
KafkaListenerAuthenticationOAuth oauth = (KafkaListenerAuthenticationOAuth) listener.getAuth();
if (oauth.getClientSecret() != null) {
varList.add(buildEnvVarFromSecret("STRIMZI_" + ListenersUtils.envVarIdentifier(listener) + "_OAUTH_CLIENT_SECRET", oauth.getClientSecret().getSecretName(), oauth.getClientSecret().getKey()));
}
}
}
if (isJmxEnabled()) {
varList.add(buildEnvVar(ENV_VAR_KAFKA_JMX_ENABLED, "true"));
if (isJmxAuthenticated) {
varList.add(buildEnvVarFromSecret(ENV_VAR_KAFKA_JMX_USERNAME, jmxSecretName(cluster), SECRET_JMX_USERNAME_KEY));
varList.add(buildEnvVarFromSecret(ENV_VAR_KAFKA_JMX_PASSWORD, jmxSecretName(cluster), SECRET_JMX_PASSWORD_KEY));
}
}
// Add shared environment variables used for all containers
varList.addAll(getRequiredEnvVars());
// Add user defined environment variables to the Kafka broker containers
addContainerEnvsToExistingEnvs(varList, templateKafkaContainerEnvVars);
return varList;
}
use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi 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());
}
}
}
}
use of io.strimzi.api.kafka.model.listener.KafkaListenerAuthenticationOAuth in project strimzi by strimzi.
the class KafkaClusterOAuthValidationTest method testOAuthValidationWithGroupsClaim.
@ParallelTest
public void testOAuthValidationWithGroupsClaim() {
assertThrows(InvalidResourceException.class, () -> {
KafkaListenerAuthenticationOAuth auth = new KafkaListenerAuthenticationOAuthBuilder().withValidIssuerUri("http://valid-issuer").withJwksEndpointUri("http://jwks-endpoint").withGroupsClaim("['bad'.'query']").build();
ListenersValidator.validate(Reconciliation.DUMMY_RECONCILIATION, 3, getListeners(auth));
});
}
Aggregations