Skip to content
This repository was archived by the owner on Jan 24, 2024. It is now read-only.

Commit fcde386

Browse files
committed
[Schema Registry] Force authorization when kafkaEnableMultiTenantMetadata is false
### Motivation Currently when the schema registry and authorization are both enabled, the authorization is only performed when `kafkaEnableMultiTenantMetadata` is false. It's not reasonable because the role of the token must have the write permission to the schema registry topic. ### Modifications - In `performAuthorizationValidation`, do not chec `kafkaEnableMultiTenantMetadata`. - Add the `testSchemaWrongAuth` to verify that a wrong role cannot be used to create an Avro produce. - Separate the default namespace and the default schema namespace in `KafkaAuthorizationTestBase` so that the permission requirements will be clear.
1 parent 1dda879 commit fcde386

File tree

2 files changed

+37
-20
lines changed

2 files changed

+37
-20
lines changed

kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/SchemaRegistryManager.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import lombok.Getter;
4747
import lombok.extern.slf4j.Slf4j;
4848
import org.apache.pulsar.broker.PulsarService;
49+
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
4950
import org.apache.pulsar.broker.authentication.AuthenticationProvider;
5051
import org.apache.pulsar.broker.authentication.AuthenticationService;
5152
import org.apache.pulsar.broker.authentication.AuthenticationState;
@@ -135,16 +136,17 @@ public String authenticate(FullHttpRequest request) throws SchemaStorageExceptio
135136

136137
private void performAuthorizationValidation(String username, String role, String tenant)
137138
throws SchemaStorageException {
138-
if (kafkaConfig.isAuthorizationEnabled() && kafkaConfig.isKafkaEnableMultiTenantMetadata()) {
139+
if (kafkaConfig.isAuthorizationEnabled()) {
139140
KafkaPrincipal kafkaPrincipal =
140-
new KafkaPrincipal(KafkaPrincipal.USER_TYPE, role, username, null, null);
141+
new KafkaPrincipal(KafkaPrincipal.USER_TYPE, role, username, null,
142+
new AuthenticationDataSource() {});
141143
String topicName = MetadataUtils.constructSchemaRegistryTopicName(tenant, kafkaConfig);
142144
try {
143145
Boolean tenantExists =
144146
authorizer.canAccessTenantAsync(kafkaPrincipal, Resource.of(ResourceType.TENANT, tenant))
145147
.get();
146148
if (tenantExists == null || !tenantExists) {
147-
log.debug("SchemaRegistry username {} role {} tenant {} does not exist",
149+
log.debug("SchemaRegistry username {} role {} tenant {} does not exist {}",
148150
username, role, tenant, topicName);
149151
throw new SchemaStorageException("Role " + role + " cannot access topic " + topicName + " "
150152
+ "tenant " + tenant + " does not exist (wrong username?)",

tests/src/test/java/io/streamnative/pulsar/handlers/kop/security/auth/KafkaAuthorizationTestBase.java

+32-17
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public abstract class KafkaAuthorizationTestBase extends KopProtocolHandlerTestB
8484

8585
protected static final String TENANT = "KafkaAuthorizationTest";
8686
protected static final String NAMESPACE = "ns1";
87+
private static final String SCHEMA_NAMESPACE = "ns2";
8788
private static final String SHORT_TOPIC = "topic1";
8889
private static final String TOPIC = "persistent://" + TENANT + "/" + NAMESPACE + "/" + SHORT_TOPIC;
8990
private static final SecretKey secretKey = AuthTokenUtils.createSecretKey(SignatureAlgorithm.HS256);
@@ -120,7 +121,7 @@ protected void setup() throws Exception {
120121
conf.setKafkaMetadataNamespace("__kafka");
121122
conf.setKafkaTenant(TENANT);
122123
conf.setKafkaNamespace(NAMESPACE);
123-
conf.setKopSchemaRegistryNamespace(NAMESPACE);
124+
conf.setKopSchemaRegistryNamespace(SCHEMA_NAMESPACE);
124125

125126
conf.setClusterName(super.configClusterName);
126127
conf.setAuthorizationEnabled(true);
@@ -712,18 +713,16 @@ public static Object[][] tokenPrefix() {
712713
// this test creates the schema registry topic, and this may interfere with other tests
713714
@Test(timeOut = 30000, priority = 1000, dataProvider = "tokenPrefix")
714715
public void testAvroProduceAndConsumeWithAuth(boolean withTokenPrefix) throws Exception {
715-
716-
if (conf.isKafkaEnableMultiTenantMetadata()) {
717-
// ensure that the KOP metadata namespace exists and that the user can write to it
718-
// because we require "produce" permissions on the Schema Registry Topic
719-
// while working in Multi Tenant mode
720-
if (!admin.namespaces().getNamespaces(TENANT).contains(TENANT + "/" + conf.getKafkaMetadataNamespace())) {
721-
admin.namespaces().createNamespace(TENANT + "/" + conf.getKafkaMetadataNamespace());
722-
}
723-
admin.namespaces()
724-
.grantPermissionOnNamespace(TENANT + "/" + conf.getKafkaMetadataNamespace(), SIMPLE_USER,
725-
Sets.newHashSet(AuthAction.produce, AuthAction.consume));
716+
admin.namespaces().grantPermissionOnNamespace(conf.getKafkaTenant() + "/" + conf.getKafkaNamespace(),
717+
SIMPLE_USER, Sets.newHashSet(AuthAction.produce, AuthAction.consume));
718+
final String tenant = (conf.isKafkaEnableMultiTenantMetadata() ? TENANT : conf.getKafkaMetadataTenant());
719+
final var namespaces = admin.namespaces().getNamespaces(tenant);
720+
final String schemaNamespace = tenant + "/" + conf.getKopSchemaRegistryNamespace();
721+
if (!namespaces.contains(schemaNamespace)) {
722+
admin.namespaces().createNamespace(schemaNamespace);
726723
}
724+
admin.namespaces().grantPermissionOnNamespace(schemaNamespace, SIMPLE_USER,
725+
Sets.newHashSet(AuthAction.produce));
727726

728727
String topic = "SchemaRegistryTest-testAvroProduceAndConsumeWithAuth" + withTokenPrefix;
729728
IndexedRecord avroRecord = createAvroRecord();
@@ -759,7 +758,7 @@ public void testAvroProduceAndConsumeWithAuth(boolean withTokenPrefix) throws Ex
759758

760759
@Test(timeOut = 30000)
761760
public void testSchemaNoAuth() {
762-
final KafkaProducer<Integer, Object> producer = createAvroProducer(false, false);
761+
final KafkaProducer<Integer, Object> producer = createAvroProducer(false, null);
763762
try {
764763
producer.send(new ProducerRecord<>("test-avro-wrong-auth", createAvroRecord())).get();
765764
fail();
@@ -772,6 +771,22 @@ public void testSchemaNoAuth() {
772771
producer.close();
773772
}
774773

774+
@Test(timeOut = 30000)
775+
public void testSchemaWrongAuth() {
776+
final var wrongToken = AuthTokenUtils.createToken(secretKey, "wrong-user", Optional.empty());
777+
final KafkaProducer<Integer, Object> producer = createAvroProducer(false, wrongToken);
778+
try {
779+
producer.send(new ProducerRecord<>("test-avro-wrong-auth", createAvroRecord())).get();
780+
fail();
781+
} catch (Exception e) {
782+
assertTrue(e.getCause() instanceof RestClientException);
783+
var restException = (RestClientException) e.getCause();
784+
assertEquals(restException.getErrorCode(), HttpResponseStatus.FORBIDDEN.code());
785+
assertTrue(restException.getMessage().contains("cannot access topic"));
786+
}
787+
producer.close();
788+
}
789+
775790
private IndexedRecord createAvroRecord() {
776791
String userSchema = "{\"namespace\": \"example.avro\", \"type\": \"record\", "
777792
+ "\"name\": \"User\", \"fields\": [{\"name\": \"name\", \"type\": \"string\"}]}";
@@ -783,10 +798,10 @@ private IndexedRecord createAvroRecord() {
783798
}
784799

785800
private KafkaProducer<Integer, Object> createAvroProducer(boolean withTokenPrefix) {
786-
return createAvroProducer(withTokenPrefix, true);
801+
return createAvroProducer(withTokenPrefix, userToken);
787802
}
788803

789-
private KafkaProducer<Integer, Object> createAvroProducer(boolean withTokenPrefix, boolean withSchemaToken) {
804+
private KafkaProducer<Integer, Object> createAvroProducer(boolean withTokenPrefix, String schemaToken) {
790805
Properties props = new Properties();
791806
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:" + getClientPort());
792807
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
@@ -803,10 +818,10 @@ private KafkaProducer<Integer, Object> createAvroProducer(boolean withTokenPrefi
803818
props.put("security.protocol", "SASL_PLAINTEXT");
804819
props.put("sasl.mechanism", "PLAIN");
805820

806-
if (withSchemaToken) {
821+
if (schemaToken != null) {
807822
props.put(KafkaAvroSerializerConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
808823
props.put(KafkaAvroSerializerConfig.USER_INFO_CONFIG,
809-
username + ":" + (withTokenPrefix ? password : userToken));
824+
username + ":" + (withTokenPrefix ? password : schemaToken));
810825
}
811826

812827
return new KafkaProducer<>(props);

0 commit comments

Comments
 (0)