-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: make SchemaRegistry permission validations on KSQL requests #7773
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright 2019 Confluent Inc. | ||
* | ||
* Licensed under the Confluent Community License (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.confluent.io/confluent-community-license | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package io.confluent.ksql.security; | ||
|
||
import static java.util.Objects.requireNonNull; | ||
|
||
public enum AuthObjectType { | ||
TOPIC("Topic"), | ||
SUBJECT("Subject"); | ||
|
||
private final String name; | ||
|
||
AuthObjectType(final String name) { | ||
this.name = requireNonNull(name, "name"); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
|
||
package io.confluent.ksql.security; | ||
|
||
import io.confluent.ksql.execution.ddl.commands.KsqlTopic; | ||
import io.confluent.ksql.metastore.MetaStore; | ||
import io.confluent.ksql.metastore.model.DataSource; | ||
import io.confluent.ksql.name.SourceName; | ||
|
@@ -24,8 +25,13 @@ | |
import io.confluent.ksql.parser.tree.PrintTopic; | ||
import io.confluent.ksql.parser.tree.Query; | ||
import io.confluent.ksql.parser.tree.Statement; | ||
import io.confluent.ksql.serde.FormatFactory; | ||
import io.confluent.ksql.serde.FormatInfo; | ||
import io.confluent.ksql.serde.SerdeFeature; | ||
import io.confluent.ksql.topic.SourceTopicsExtractor; | ||
import io.confluent.ksql.util.KsqlConstants; | ||
import io.confluent.ksql.util.KsqlException; | ||
import java.util.Set; | ||
import org.apache.kafka.common.acl.AclOperation; | ||
|
||
/** | ||
|
@@ -69,10 +75,9 @@ private void validateQuery( | |
final MetaStore metaStore, | ||
final Query query | ||
) { | ||
final SourceTopicsExtractor extractor = new SourceTopicsExtractor(metaStore); | ||
extractor.process(query, null); | ||
for (String kafkaTopic : extractor.getSourceTopics()) { | ||
accessValidator.checkAccess(securityContext, kafkaTopic, AclOperation.READ); | ||
for (KsqlTopic ksqlTopic : extractQueryTopics(query, metaStore)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a comment but a question for my own understanding: it seems
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Initially, the validation was part of this |
||
checkTopicAccess(securityContext, ksqlTopic.getKafkaTopicName(), AclOperation.READ); | ||
checkSchemaAccess(securityContext, ksqlTopic, AclOperation.READ); | ||
} | ||
} | ||
|
||
|
@@ -94,7 +99,7 @@ private void validateCreateAsSelect( | |
|
||
// At this point, the topic should have been created by the TopicCreateInjector | ||
final String kafkaTopic = getCreateAsSelectSinkTopic(metaStore, createAsSelect); | ||
accessValidator.checkAccess(securityContext, kafkaTopic, AclOperation.WRITE); | ||
checkTopicAccess(securityContext, kafkaTopic, AclOperation.WRITE); | ||
} | ||
|
||
private void validateInsertInto( | ||
|
@@ -111,22 +116,28 @@ private void validateInsertInto( | |
validateQuery(securityContext, metaStore, insertInto.getQuery()); | ||
|
||
final String kafkaTopic = getSourceTopicName(metaStore, insertInto.getTarget()); | ||
accessValidator.checkAccess(securityContext, kafkaTopic, AclOperation.WRITE); | ||
checkTopicAccess(securityContext, kafkaTopic, AclOperation.WRITE); | ||
} | ||
|
||
private void validatePrintTopic( | ||
final KsqlSecurityContext securityContext, | ||
final PrintTopic printTopic | ||
) { | ||
accessValidator.checkAccess(securityContext, printTopic.getTopic(), AclOperation.READ); | ||
checkTopicAccess(securityContext, printTopic.getTopic(), AclOperation.READ); | ||
|
||
// SchemaRegistry permissions cannot be validated here because the schema is guessed when | ||
// printing the topic by obtaining the first row and attempt to find the right schema | ||
} | ||
|
||
private void validateCreateSource( | ||
final KsqlSecurityContext securityContext, | ||
final CreateSource createSource | ||
) { | ||
final String sourceTopic = createSource.getProperties().getKafkaTopic(); | ||
accessValidator.checkAccess(securityContext, sourceTopic, AclOperation.READ); | ||
checkTopicAccess(securityContext, sourceTopic, AclOperation.READ); | ||
|
||
// SchemaRegistry permissions are validated when SchemaRegisterInjector is called during CREATE | ||
// operations. There's no need to validate the user has READ permissions here. | ||
} | ||
|
||
private String getSourceTopicName(final MetaStore metaStore, final SourceName streamOrTable) { | ||
|
@@ -146,4 +157,39 @@ private String getCreateAsSelectSinkTopic( | |
return createAsSelect.getProperties().getKafkaTopic() | ||
.orElseGet(() -> getSourceTopicName(metaStore, createAsSelect.getName())); | ||
} | ||
|
||
private Set<KsqlTopic> extractQueryTopics(final Query query, final MetaStore metaStore) { | ||
final SourceTopicsExtractor extractor = new SourceTopicsExtractor(metaStore); | ||
extractor.process(query, null); | ||
return extractor.getSourceTopics(); | ||
} | ||
|
||
private void checkTopicAccess( | ||
final KsqlSecurityContext securityContext, | ||
final String resourceName, | ||
final AclOperation operation | ||
) { | ||
accessValidator.checkTopicAccess(securityContext, resourceName, operation); | ||
} | ||
|
||
private void checkSchemaAccess( | ||
final KsqlSecurityContext securityContext, | ||
final KsqlTopic ksqlTopic, | ||
final AclOperation operation | ||
) { | ||
|
||
if (formatSupportsSchemaInference(ksqlTopic.getKeyFormat().getFormatInfo())) { | ||
accessValidator.checkSubjectAccess(securityContext, | ||
KsqlConstants.getSRSubject(ksqlTopic.getKafkaTopicName(), true), operation); | ||
} | ||
|
||
if (formatSupportsSchemaInference(ksqlTopic.getValueFormat().getFormatInfo())) { | ||
accessValidator.checkSubjectAccess(securityContext, | ||
KsqlConstants.getSRSubject(ksqlTopic.getKafkaTopicName(), false), operation); | ||
} | ||
} | ||
|
||
private static boolean formatSupportsSchemaInference(final FormatInfo format) { | ||
return FormatFactory.of(format).supportsFeature(SerdeFeature.SCHEMA_INFERENCE); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also have a config for enabling SR auth?