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
KAFKA-14859: SCRAM ZK to KRaft migration with dual write #13628
Changes from all commits
31a6478
c6de035
ff6e498
e3abb64
efee0c4
1c9fc60
a81570d
db1b1b1
42ffa46
4663f25
8b5da1c
cfb6406
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 |
---|---|---|
|
@@ -19,9 +19,11 @@ | |
import org.apache.kafka.common.acl.AccessControlEntry; | ||
import org.apache.kafka.common.metadata.ConfigRecord; | ||
import org.apache.kafka.common.metadata.MetadataRecordType; | ||
import org.apache.kafka.common.quota.ClientQuotaEntity; | ||
import org.apache.kafka.common.resource.ResourcePattern; | ||
import org.apache.kafka.common.utils.LogContext; | ||
import org.apache.kafka.common.utils.Time; | ||
import org.apache.kafka.common.security.scram.internals.ScramCredentialUtils; | ||
import org.apache.kafka.controller.QuorumFeatures; | ||
import org.apache.kafka.image.MetadataDelta; | ||
import org.apache.kafka.image.MetadataImage; | ||
|
@@ -31,6 +33,7 @@ | |
import org.apache.kafka.image.publisher.MetadataPublisher; | ||
import org.apache.kafka.metadata.BrokerRegistration; | ||
import org.apache.kafka.metadata.authorizer.StandardAcl; | ||
import org.apache.kafka.metadata.ScramCredentialData; | ||
import org.apache.kafka.queue.EventQueue; | ||
import org.apache.kafka.queue.KafkaEventQueue; | ||
import org.apache.kafka.raft.LeaderAndEpoch; | ||
|
@@ -646,19 +649,57 @@ public void run() throws Exception { | |
}); | ||
} | ||
|
||
// For configs and client quotas, we need to send all of the data to the ZK client since we persist | ||
// everything for a given entity in a single ZK node. | ||
// For configs and client quotas, we need to send all of the data to the ZK | ||
// client since we persist everything for a given entity in a single ZK node. | ||
if (delta.configsDelta() != null) { | ||
delta.configsDelta().changes().forEach((configResource, configDelta) -> | ||
apply("Updating config resource " + configResource, migrationState -> | ||
zkMigrationClient.writeConfigs(configResource, image.configs().configMapForResource(configResource), migrationState))); | ||
} | ||
|
||
if (delta.clientQuotasDelta() != null) { | ||
delta.clientQuotasDelta().changes().forEach((clientQuotaEntity, clientQuotaDelta) -> { | ||
Map<String, Double> quotaMap = image.clientQuotas().entities().get(clientQuotaEntity).quotaMap(); | ||
apply("Updating client quota " + clientQuotaEntity, migrationState -> | ||
zkMigrationClient.writeClientQuotas(clientQuotaEntity.entries(), quotaMap, migrationState)); | ||
if ((delta.clientQuotasDelta() != null) || (delta.scramDelta() != null)) { | ||
// A list of users with scram or quota changes | ||
HashSet<String> users = new HashSet<String>(); | ||
|
||
// Populate list with users with scram changes | ||
if (delta.scramDelta() != null) { | ||
delta.scramDelta().changes().forEach((scramMechanism, changes) -> { | ||
changes.forEach((userName, changeOpt) -> users.add(userName)); | ||
}); | ||
} | ||
|
||
// Populate list with users with quota changes | ||
// and apply quota changes to all non user quota changes | ||
if (delta.clientQuotasDelta() != null) { | ||
Map<String, String> scramMap = new HashMap<String, String>(); | ||
delta.clientQuotasDelta().changes().forEach((clientQuotaEntity, clientQuotaDelta) -> { | ||
|
||
if ((clientQuotaEntity.entries().containsKey(ClientQuotaEntity.USER)) && | ||
(!clientQuotaEntity.entries().containsKey(ClientQuotaEntity.CLIENT_ID))) { | ||
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. Just so I understand -- we exclude entities like 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. That is my understanding. SCRAM records are only applied to principals and are not client specific. |
||
String userName = clientQuotaEntity.entries().get(ClientQuotaEntity.USER); | ||
// Add clientQuotaEntity to list to process at the end | ||
users.add(userName); | ||
} else { | ||
Map<String, Double> quotaMap = image.clientQuotas().entities().get(clientQuotaEntity).quotaMap(); | ||
apply("Updating client quota " + clientQuotaEntity, migrationState -> | ||
zkMigrationClient.writeClientQuotas(clientQuotaEntity.entries(), quotaMap, scramMap, migrationState)); | ||
} | ||
}); | ||
} | ||
// Update user scram and quota data for each user with changes in either. | ||
users.forEach(userName -> { | ||
Map<String, String> userScramMap = getScramCredentialStringsForUser(userName); | ||
ClientQuotaEntity clientQuotaEntity = new | ||
ClientQuotaEntity(Collections.singletonMap(ClientQuotaEntity.USER, userName)); | ||
if (image.clientQuotas() == null) { | ||
Map<String, Double> quotaMap = new HashMap<String, Double>(); | ||
apply("Updating client quota " + clientQuotaEntity, migrationState -> | ||
zkMigrationClient.writeClientQuotas(clientQuotaEntity.entries(), quotaMap, userScramMap, migrationState)); | ||
} else { | ||
Map<String, Double> quotaMap = image.clientQuotas().entities().get(clientQuotaEntity).quotaMap(); | ||
apply("Updating client quota " + clientQuotaEntity, migrationState -> | ||
zkMigrationClient.writeClientQuotas(clientQuotaEntity.entries(), quotaMap, userScramMap, migrationState)); | ||
} | ||
}); | ||
} | ||
|
||
|
@@ -712,6 +753,21 @@ public void run() throws Exception { | |
completionHandler.accept(null); | ||
} | ||
|
||
private Map<String, String> getScramCredentialStringsForUser(String userName) { | ||
Map<String, String> userScramCredentialStrings = new HashMap<String, String>(); | ||
if (image.scram() != null) { | ||
image.scram().mechanisms().forEach((scramMechanism, scramMechanismMap) -> { | ||
ScramCredentialData scramCredentialData = scramMechanismMap.get(userName); | ||
if (scramCredentialData != null) { | ||
userScramCredentialStrings.put(scramMechanism.mechanismName(), | ||
ScramCredentialUtils.credentialToString( | ||
scramCredentialData.toCredential(scramMechanism))); | ||
} | ||
}); | ||
} | ||
return userScramCredentialStrings; | ||
} | ||
|
||
private void addStandardAclToMap(Map<ResourcePattern, List<AccessControlEntry>> aclMap, StandardAcl acl) { | ||
ResourcePattern resource = new ResourcePattern(acl.resourceType(), acl.resourceName(), acl.patternType()); | ||
aclMap.computeIfAbsent(resource, __ -> new ArrayList<>()).add( | ||
|
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 log something if we get a null value here?
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.
No. It just means that there are no scram records for the user.