-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-7007: All ACL changes should use single /kafka-acl-changes path #5161
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
Changes from 1 commit
16f5597
259df49
6013925
b29bd45
237db81
9185513
5769d56
7046b0c
b919bd1
3280303
ae50f4e
23bb972
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 |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ import kafka.security.auth.{Acl, Literal, Prefixed, Resource, ResourceNameType, | |
| import kafka.server.{ConfigType, DelegationTokenManager} | ||
| import kafka.utils.Json | ||
| import org.apache.kafka.common.TopicPartition | ||
| import org.apache.kafka.common.errors.UnsupportedVersionException | ||
| import org.apache.kafka.common.network.ListenerName | ||
| import org.apache.kafka.common.security.auth.SecurityProtocol | ||
| import org.apache.kafka.common.security.token.delegation.{DelegationToken, TokenInformation} | ||
|
|
@@ -41,6 +42,7 @@ import scala.beans.BeanProperty | |
| import scala.collection.JavaConverters._ | ||
| import scala.collection.mutable.ArrayBuffer | ||
| import scala.collection.{Seq, breakOut} | ||
| import scala.util.{Failure, Success, Try} | ||
|
|
||
| // This file contains objects for encoding/decoding data stored in ZooKeeper nodes (znodes). | ||
|
|
||
|
|
@@ -460,30 +462,30 @@ case class ZkAclStore(nameType: ResourceNameType) { | |
| val aclPath: String = nameType match { | ||
| case Literal => "/kafka-acl" | ||
| case Prefixed => "/kafka-prefixed-acl" | ||
| case _ => throw new IllegalArgumentException("Unknown name type:" + nameType) | ||
| } | ||
|
|
||
| val aclChangePath: String = nameType match { | ||
| case Literal => "/kafka-acl-changes" | ||
| case Prefixed => "/kafka-prefixed-acl-changes" | ||
| case _ => throw new IllegalArgumentException("Unknown name type:" + nameType) | ||
| case _ => throw new IllegalArgumentException("Invalid name type:" + nameType) | ||
| } | ||
|
|
||
| def path(resourceType: ResourceType) = s"$aclPath/$resourceType" | ||
|
|
||
| def path(resourceType: ResourceType, resourceName: String): String = s"$aclPath/$resourceType/$resourceName" | ||
|
|
||
| def changeSequenceZNode: AclChangeNotificationSequenceZNode = AclChangeNotificationSequenceZNode(this) | ||
|
|
||
| def decode(notificationMessage: Array[Byte]): Resource = AclChangeNotificationSequenceZNode.decode(nameType, notificationMessage) | ||
| } | ||
|
|
||
| object ZkAclStore { | ||
| val stores: Seq[ZkAclStore] = ResourceNameType.values | ||
| .map(nameType => ZkAclStore(nameType)) | ||
|
|
||
| val securePaths: Seq[String] = stores | ||
| .flatMap(store => List(store.aclPath, store.aclChangePath)) | ||
| .flatMap(store => List(store.aclPath)) ++ Seq(AclChangeNotificationZNode.path) | ||
| } | ||
|
|
||
| @deprecated("There are now multiple roots for ACLs within ZK. Use ZkAclStore", "2.0") | ||
| object AclZNode { | ||
| def path = "/kafka-acl" | ||
| } | ||
|
|
||
| @deprecated("There are now multiple roots for ACLs within ZK. Use ZkAclStore", "2.0") | ||
| object ResourceTypeZNode { | ||
|
Contributor
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. AclZNode and ResourceTypeZNode are not part of the public API. We can just remove them.
Contributor
Author
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. Done. |
||
| def path(resourceType: String) = s"${AclZNode.path}/$resourceType" | ||
| } | ||
|
|
||
| object ResourceZNode { | ||
|
|
@@ -493,26 +495,54 @@ object ResourceZNode { | |
| def decode(bytes: Array[Byte], stat: Stat): VersionedAcls = VersionedAcls(Acl.fromBytes(bytes), stat.getVersion) | ||
| } | ||
|
|
||
| object AclChangeNotificationZNode { | ||
| def path = "/kafka-acl-changes" | ||
| } | ||
|
|
||
| case class AclChangeEvent(@BeanProperty @JsonProperty("version") version: Int, | ||
|
Contributor
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 class is no longer used.
Contributor
Author
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. Done. |
||
| @BeanProperty @JsonProperty("resourceType") resourceType: String, | ||
| @BeanProperty @JsonProperty("name") name: String, | ||
| @BeanProperty @JsonProperty("resourceNameType") resourceNameType: String) { | ||
| if (version > AclChangeEvent.currentVersion) | ||
| throw new UnsupportedVersionException(s"Acl change event received for unsupported version: $version") | ||
|
|
||
| def toResource : Try[Resource] = { | ||
| for { | ||
| resType <- Try(ResourceType.fromString(resourceType)) | ||
| nameType <- Try(ResourceNameType.fromString(resourceNameType)) | ||
| resource = Resource(resType, name, nameType) | ||
| } yield resource | ||
| } | ||
| } | ||
|
|
||
| object AclChangeEvent { | ||
| val currentVersion: Int = 1 | ||
|
Contributor
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 no longer needed?
Contributor
Author
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. No, and done. |
||
| } | ||
|
|
||
| object AclChangeNotificationSequenceZNode { | ||
| val Separator = ":" | ||
| def SequenceNumberPrefix = "acl_changes_" | ||
|
|
||
| def encode(resource: Resource): Array[Byte] = { | ||
| (resource.resourceType.name + Separator + resource.name).getBytes(UTF_8) | ||
| } | ||
| def createPath = s"${AclChangeNotificationZNode.path}/$SequenceNumberPrefix" | ||
| def deletePath(sequenceNode: String) = s"${AclChangeNotificationZNode.path}/$sequenceNode" | ||
|
|
||
| def decode(nameType: ResourceNameType, bytes: Array[Byte]): Resource = { | ||
| val str = new String(bytes, UTF_8) | ||
| str.split(Separator, 2) match { | ||
| case Array(resourceType, name, _*) => Resource(ResourceType.fromString(resourceType), name, nameType) | ||
| case _ => throw new IllegalArgumentException("expected a string in format ResourceType:ResourceName but got " + str) | ||
| def encode(resource: Resource): Array[Byte] = | ||
| Json.encodeAsBytes(AclChangeEvent( | ||
| AclChangeEvent.currentVersion, | ||
| resource.resourceType.name, | ||
| resource.name, | ||
| resource.resourceNameType.name)) | ||
|
|
||
| def decode(bytes: Array[Byte]): Resource = { | ||
| val changeEvent = Json.parseBytesAs[AclChangeEvent](bytes) match { | ||
| case Right(event) => event | ||
| case Left(e) => throw new IllegalArgumentException("Failed to parse ACL change event", e) | ||
|
Contributor
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. During the upgrade phase, the new broker may see an acl_change node of the old format. So, we want to fall back to parse the value of the node using the old format if it's not json.
Contributor
Author
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. Done |
||
| } | ||
| } | ||
| } | ||
|
|
||
| case class AclChangeNotificationSequenceZNode(store: ZkAclStore) { | ||
| def createPath = s"${store.aclChangePath}/${AclChangeNotificationSequenceZNode.SequenceNumberPrefix}" | ||
| def deletePath(sequenceNode: String) = s"${store.aclChangePath}/$sequenceNode" | ||
| changeEvent.toResource match { | ||
| case Success(r) => r | ||
| case Failure(e) => throw new IllegalArgumentException("Failed to parse ACL change event", e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| object ClusterZNode { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,7 @@ <h4><a id="upgrade_2_0_0" href="#upgrade_2_0_0">Upgrading from 0.8.x, 0.9.x, 0.1 | |
| <li>inter.broker.protocol.version=CURRENT_KAFKA_VERSION (0.11.0, 1.0, 1.1, 1.2).</li> | ||
| </ul> | ||
| </li> | ||
| <li> IMPORTANT: Do not change any ACLs during the upgrade. (The format of the ACL change event has changed and is not compatible with old brokers).</li> | ||
|
Contributor
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 a bit too restricting. To support adding acls during rolling upgrade, we probably only want write the acl_change path with the new json format if inter.broker.protocol is >= 2.0. Otherwise, if the new ACL doesn't use the new prefix feature, we switch to using the old format. Otherwise, we fail the addAcl request. Since the AclCommand still writes to ZK directly. We probably want to recommend "Not use the 2.0 AclCommand tool until all brokers are upgraded to 2.0."
Contributor
Author
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. Hi @junrao, my bad - we did discuss this. Sorry I missed this. Done now. |
||
| <li> Upgrade the brokers one at a time: shut down the broker, update the code, and restart it. </li> | ||
| <li> Once the entire cluster is upgraded, bump the protocol version by editing <code>inter.broker.protocol.version</code> and setting it to 2.0. | ||
| <li> Restart the brokers one by one for the new protocol version to take effect.</li> | ||
|
|
@@ -61,10 +62,10 @@ <h4><a id="upgrade_2_0_0" href="#upgrade_2_0_0">Upgrading from 0.8.x, 0.9.x, 0.1 | |
| Similarly for the message format version.</li> | ||
| <li>If you are using Java8 method references in your Kafka Streams code you might need to update your code to resolve method ambiguities. | ||
| Hot-swapping the jar-file only might not work.</li> | ||
| <li>ACLs should not be added to prefixed resources, | ||
| <li>ACLs should not be added during the upgrade process. | ||
|
Contributor
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. Also, if we have upgraded the whole cluster and started using the new acl_change format in ZK, downgrading the cluster will cause the processing of acl_change sequence node to fail and the sequence node not to be deleted. This means the downgraded broker can't process ACL changes forever. So, your original idea of using a new acl_change path such as /kafka-acl-changes-v2 seems better.
Contributor
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. Actually, this has the other problem. Since the AclCommand tool still writes to ZK directly, if we switch to a new acl-change path, the old version of AclCommand won't work once the brokers are upgraded. Given the complexity in dealing with upgrades/downgrades, maybe the current approach of using 2 separate acl-change path is not that bad. So, perhaps we can just keep the current approach, but (1) ensure that the new acl with prefix is only added if inter.broker.protocol is new; (2) In ZkNodeChangeNotificationListener.processNotifications(), catch decoding error of individual acl change value, log it and let it go. This may help future format change. Sorry for going back and forth on this one.
Contributor
Author
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. Hummm.... Good points @junrao. I have another suggestion... pre-2.0 behaviour uses a colon-separated string to write the change notification in the format With my recent changes the toString representation of Given this new format, rather than switching to JSON we could just write out change notifications out as strings still, with either two or three parts, depending on inter.broker.protocol.version. During a rolling upgrade change events would be written using two parts, which work with older brokers and are interpreted as 'literal' resources by the new brokers. (Attempts to add Prefixed would result in UVE). Once upgraded brokers start to write using three parts change events. On a downgrade older brokers would treat the 'Topic:Prefixed:Foo' as a change on a Topic called 'Prefixed:Foo', which wouldn't match anything. I'd have to check, but there's every possibility that the broker would process this without error and move on. The fact that the old broker effectively ignored the event shouldn't matter as the event must be old to be in this format. Thoughts?
Contributor
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. Yes, that seems to be a good idea. The only confusion is that if someone has a group named "Literal:weirdgroup" or "Prefixed:weirdgroup". Since that's unlikely, I think what you suggested is probably the best approach given what we have now.
Contributor
Author
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. Great. Thinking on this more, I think we should consider not adding the backwards compatibility logic, I.e. the new brokers should always write using the three part name. If we do add this logic it will need to be in the code base for a long long time, as there could always be some user upgrading. If we don’t add it, then the edge case this affects is very small / unlikely and the impact minimal: the old broker would only gave incorrect acts until it was itself upgraded, which should be a small window. What do you think @junrao? |
||
| <p><b>NOTE:</b> Any ACLs added to prefixed resources, | ||
| (added in <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+Prefixed+ACLs">KIP-290</a>), | ||
| until all brokers in the cluster have been updated. | ||
| <p><b>NOTE:</b> any prefixed ACLs added to a cluster will be ignored should the cluster be downgraded again. | ||
| will be ignored should the cluster be downgraded again. | ||
| </li> | ||
| </ol> | ||
|
|
||
|
|
||
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.
unused imports Failure, Success
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.
Done.