KAFKA-6841: Add support for Prefixed ACLs#5079
KAFKA-6841: Add support for Prefixed ACLs#5079piyushvijay wants to merge 7 commits intoapache:trunkfrom
Conversation
There was a problem hiding this comment.
Hey @piyushvijay,
Thanks for the PR! I know this is not complete yet, but thought I'd chime in with some comments.
The feature freeze approaching fast, (it's next Tuesday). I know you've got other things on your plate to work on besides this. The timezone difference is also not helping us with the round-trips on reviews. All of which leads me to thinking I can step in and help.
How about this: when I come in on Tuesday, after the bank holiday, (UK morning), I'll review the PR again in what ever state it is. Then, rather than play ping-pong with you, I'll just get stuck in and make any remaining tweaks that are needed to get this across the finish line. How does that sound?
So, could you make sure you push any changes before Tuesday morning please @piyushvijay?
Thanks,
Andy
There was a problem hiding this comment.
Looks like this could just fall through.
There was a problem hiding this comment.
Add a comment explaining 'v1 same as v0, request has additional resource_name_type'
There was a problem hiding this comment.
it has DESCRIBE_ACLS_RESOURCE_V1 instead of DESCRIBE_ACLS_RESOURCE_V0
There was a problem hiding this comment.
'valueInZK' is poor name here - it's the name of the wildcard-suffixed pattern.
'intput' is actually a concrete resource name.
valueInZK won't have the '*' suffix, (as discussed on the thread). - update the java doc accordingly too.
Personally, I see a lot of scope for this being called in code where it shouldn't, (as it currently is). A more defensive API might be for the signature to be:
public static boolean matchWildcardSuffixed(Resource wildcard, Resource concrete)
public static boolean matchWildcardSuffixed(ResourceFilter wildcard, Resource concrete)
And then to have the method throw if the wrong types of wildcard and concrete are passed in, i.e. wildcard should always have type WILDCARD_SUFFIXED and concrete should always to LITERAL
There was a problem hiding this comment.
anti-pattern:
if (...) {
return x;
} else if (...) {
return y;
} else {
return z;
}
Can be simplified to:
if (...) {
return x;
}
if (...) {
return y;
}
return z;
There was a problem hiding this comment.
need to change the split(Separator, 2) to split(Separator, 3) me thinks.
There was a problem hiding this comment.
Need to only call the wildcard-suffix matching logic if the valueInZk is wildcard-suffix
There was a problem hiding this comment.
As per the mail thread, we should use 'Prefixed' rather than 'WildcardSuffixed' everywhere.
There was a problem hiding this comment.
I think we decided to use 'WildcardSuffixed' ;)
There was a problem hiding this comment.
The last suggestion on the thread was from @cmccabe and was for 'prefixed'. I'm also happy with this as its short! Wildcard Suffixed is a bit of a mouthful and is less intuitive - a good test for this is to try and explain how "wildcard suffixed ACLs" interact with literal and wildcard acls. It ends up being really confusing, as we've got 'wildcard-suffixed' and 'wildcard' ACLs - much easy to discuss and digest if its 'literal', 'prefixed' and 'wildcard' ACLs. :D
There was a problem hiding this comment.
Yes, I think "prefixed" makes sense. Everyone who talks about this talks about it as "prefix" support. it's even in the name of the KIP and the JIRA.
There was a problem hiding this comment.
it has DESCRIBE_ACLS_RESOURCE_V1 instead of DESCRIBE_ACLS_RESOURCE_V0
a497ea2 to
1af6609
Compare
|
Hi @big-andy-coates, Looks in a pretty good shape now to merge today and then iterate. Thanks for the offer I think it's manageable now. Please let me know if you have any concerns (expect some failing tests which I'll fix in the morning). I still need to add more tests and documentation (which can be done after Tuesday as per the release doc). Thanks for the comments and the offer. Two things:
|
This is a WIP and require major refactoring for storing Acls at dual location on ZK. I'm forward-porting it from 1.0.x and code has changed significantly since then. I'll post a new version tomorrow but this is for very early preview. Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
This is a WIP. This change includs improvements to support storing Acls at dual location on ZK. Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
This is a WIP. This change includes improvements to the matching method, critical bug fixes and addresses comments from the previous review. Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
This change includes fixes for hung tests and improvements to the matching method. Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
Fix a bug in process notification due to change in Resource.toString Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
Fix unit tests. Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
|
@big-andy-coates rebased and fixed all unit tests, except one which is related to matching in deleteAcls as pointed above. |
There was a problem hiding this comment.
Hi @piyushvijay,
Thanks for the new version!
I see the KIP affecting three main areas:
- How the authorizer stores and evaluates ACLs
- How users create/read/update/delete ACLs via the kafka-acls.sh script
- How users create/read/update/delete ACLs via the broker API / AdminClient.
The PR at the moment focuses almost exclusively on only the first point, and I think we need to be thinking about the implications on the other two as well, as these are key to the overall design making sense.
We need to see the changes to the CLI. I think we discussed a new command line switch to control the name type, which needs adding. We then need to think about how the other commands should work, e.g. the add/remove commands need to work for literal and prefixed resource names, but what about list? Personally, I would think it most useful if list just took a concrete resource name, (i.e. no resourceNameType), and then listed all the ACLs that affect it.
How do you see the DESCRIBE_ACLS and DELETE_ACLS broker calls working? Your current implementation is passing a RESOURCE_NAME_TYPE. However, like the CLI, I don't think DESCRIBE_ACLS should take a resource name type as I think it should work on concrete resources. We could maybe accept an optional resource name type, which allows ACLs within a specific ZK node to be listed, but should also accept a null resource name type, which should be taken to mean 'return all acls affecting this concrete resource'. The delete side could do something similar to allow users to delete only ACLS on literal, or only on prefixed, resources, or pass some 'Any' marked to delete all.
I'm concerned about the lack of tests for the new functionality, and lack of any documentation changes. I realise that you're probably thinking that this can be done later, but I think actually going through the motions of documenting both the CLI and API/adminClient changes will bring to light issues and ambiguities. The documentation can also drive the functional testing.
I've also been thinking about the changes to the Authorizer itself. The code at the moment is a bit of a mess, (that's the existing code, not yours!), and we're having to add a ResourceNameType all over the place, including the Resource call passed to the Authorizer methods. I think it would help if the resource name type was optional, i.e. could be null/Any, rather than defaulting to literal. Resources with a null, (or maybe ANY), name type should be treated as a 'concrete' resource, and hence be affected by both ACLs on Literal and Prefixed resources.
This change to Resource would also allow the getAcls call to be used to get ACLs for a specific path in ZK, e.g. getAcls(Resource(Group, "foo*", Literal)) or get all ACLS for a specific concrete resource, e.g. getAcls(Resource(Group, "foo*, Any)). The Authorizers remove methods would also handle this optional name type.
Oh, and we should definitely rename this feature 'prefixed' rather than 'wildcard-suffixed'. :D
In summary, I think there is still a chunk of work to do! So, again, I'll offer to pick this up. What I'm thinking is that you finished off what you think needs doing for the main authorize call work. (Going though my comments, adding tests, etc). Then I could pick up the other areas of work.
checkstyle/suppressions.xml
Outdated
|
|
||
| <suppress checks="CyclomaticComplexity" | ||
| files="(ConsumerCoordinator|Fetcher|Sender|KafkaProducer|BufferPool|ConfigDef|RecordAccumulator|KerberosLogin|AbstractRequest|AbstractResponse|Selector|SslFactory|SslTransportLayer|SaslClientAuthenticator|SaslClientCallbackHandler).java"/> | ||
| files="(ConsumerCoordinator|Fetcher|Sender|KafkaProducer|BufferPool|ConfigDef|RecordAccumulator|KerberosLogin|AbstractRequest|AbstractResponse|Selector|SslFactory|SslTransportLayer|SaslClientAuthenticator|SaslClientCallbackHandler|SecurityUtils).java"/> |
There was a problem hiding this comment.
Don't add more exclusions! Fix the code!
| super(name, Type.INT8, docString, false, null); | ||
| } | ||
| public Int8(String name, String docString, byte byt) { | ||
| super(name, Type.INT8, docString, true, byt); |
There was a problem hiding this comment.
nit: rename byt to defaultValue
|
|
||
| public Resource(ResourceType type, String name) { | ||
| /** | ||
| * @param type resource type |
There was a problem hiding this comment.
Would be good to document which of these, if any, can be null and what that means. (Same for other versions of the constructor and other classes of the same name, ResourceFilter and the AclBindings* family).
Also, enforce this with null checks Objects.requireNotNull(param, "param")
| Resource resource = (Resource) o; | ||
|
|
||
| return type == resource.type && name.equals(resource.name); | ||
| return type == resource.type && name.equals(resource.name) && resourceNameType.equals(resource.resourceNameType); |
There was a problem hiding this comment.
Use Objects.equals for non-primitives.
| public int hashCode() { | ||
| int result = type.hashCode(); | ||
| result = 31 * result + name.hashCode(); | ||
| result = 31 * result + resourceNameType.hashCode(); |
There was a problem hiding this comment.
Switch this to Objects.hashCode()
| } | ||
|
|
||
| @Test | ||
| def testAuthorizeTrueOnWildCardAcl(): Unit = { |
There was a problem hiding this comment.
This test needs splitting - a unit test should test one thing.
Splits:
- shouldDenyAccessIfNoAclsPresent
- shouldAllowAccessIfWildcardAclPresent
- shouldDenyAccessWhenAclRemoved
- shouldAllowAccessIfPrefixedAclMatches
Then add more positive & negative tests to cover new prefixed authorise functionality. e.g.
- shouldAllowAccessIfPrefixedAclIsExactMatch.
- shouldDenyAccessIfPrefixedAclDoesNotMatch.
- shouldThrowFromAuthorizeIfNotConcreteResource
etc.
| } | ||
|
|
||
| @Test | ||
| def testMatchPrincipal(): Unit = { |
There was a problem hiding this comment.
PR should not change principal handling...
| } | ||
|
|
||
| @Test | ||
| def testGetAcls(): Unit = { |
There was a problem hiding this comment.
Split into many tests.
| } | ||
|
|
||
| @Test | ||
| def testGetAclsPrincipal(): Unit = { |
There was a problem hiding this comment.
PR should not change principal handling...
| import java.util.concurrent.{Callable, Executors, TimeUnit} | ||
| import javax.net.ssl.X509TrustManager | ||
|
|
||
| import javax.net.ssl.X509TrustManager |
There was a problem hiding this comment.
white space only change.
junrao
left a comment
There was a problem hiding this comment.
@piyushvijay : Thanks for the patch. A few comments below. Also, during the rolling upgrade, it's possible that some brokers can get the changes through the prefix acl change path, but some others can't. So, at the minimum, we probably want to document in upgrade.html that the prefix acl feature won't work fully until the whole cluster is upgraded.
| */ | ||
| def matchPrincipal(valueInAcl: KafkaPrincipal, input: KafkaPrincipal): Boolean = { | ||
| (valueInAcl.getPrincipalType == input.getPrincipalType || valueInAcl.getPrincipalType == Acl.WildCardString) && | ||
| (valueInAcl.getName.equals(input.getName) || valueInAcl.getName.equals(Acl.WildCardString)) |
There was a problem hiding this comment.
Agree with @big-andy-coates, it seems that we agreed not to extend the prefix support for principals. The principal type is currently intended for things like groups. So, we probably don't want to overload it with wildcard. Finally, we have to make sure that * principal set in the current way still works.
| ANY((byte) 1), | ||
|
|
||
| /** | ||
| * A Kafka topic. |
There was a problem hiding this comment.
The comment is accurate. It should be sth like "A concrete type such as a specific Kafka topic".
| LITERAL((byte) 2), | ||
|
|
||
| /** | ||
| * A consumer group. |
| || SecurityUtils.matchWildcardSuffixedString(stored.name() + WILDCARD_MARKER, input.name() + WILDCARD_MARKER); | ||
| case LITERAL: | ||
| return input.name() == null | ||
| || SecurityUtils.matchWildcardSuffixedString(stored.name() + WILDCARD_MARKER, input.name()); |
There was a problem hiding this comment.
Hmm, this is a bit weird. The stores.name() should have * as the suffix already, right?
There was a problem hiding this comment.
Hey @junrao,
It was discussed on the dev channel that we shouldn't store or pass around the '' the end as this then requires places to validate the resource name ends in a '' on API calls and when loading from ZK.
Also, with the rebrand of this from 'wildcard-suffixed' to simply 'prefixed' then I think we can drop the '' completely. e.g. the user would add an ACL to any topic resource the has the prefix 'foo'. Look mum, no asterisks! This also helps separate this from the current 'wildcard' support i.e. ''.
There was a problem hiding this comment.
Ok. Then the KIP wiki needs to be updated.
| || stored.name().equals(WILDCARD_MARKER); | ||
| case WILDCARD_SUFFIXED: | ||
| return input.name() == null | ||
| || SecurityUtils.matchWildcardSuffixedString(stored.name(), input.name() + WILDCARD_MARKER); |
There was a problem hiding this comment.
If the filter is of WILDCARD_SUFFIXED, we'd expect its name to be suffixed with* already, right?
| OPERATION, | ||
| PERMISSION_TYPE)))); | ||
|
|
||
| private static final Schema CREATE_ACLS_REQUEST_V1 = new Schema( |
There was a problem hiding this comment.
It would be useful to add a comment on what has changed in v1. Ditto in other changed requests.
| ERROR_MESSAGE, | ||
| RESOURCE_TYPE, | ||
| RESOURCE_NAME, | ||
| RESOURCE_NAME_TYPE, |
There was a problem hiding this comment.
Since we are adding a new field here, toStruct(short version) and DeleteAclsResponse(Struct) need to be adjusted to take into account whether the new field is present or not.
|
|
||
| private static final Schema DESCRIBE_ACLS_RESOURCE_V1 = new Schema( | ||
| RESOURCE_TYPE, | ||
| RESOURCE_NAME, |
There was a problem hiding this comment.
Similar to the above, since we are adding a new field here, toStruct(short version) and DescribeAclsResponse(Struct) need to be adjusted to take into account whether the new field is present or not.
|
|
||
| private def updateAclChangedFlag(resource: Resource) { | ||
| zkClient.createAclChangeNotification(resource.toString) | ||
| zkClient.createAclChangeNotification(AclStore.fromResource(resource), resource.toString) |
There was a problem hiding this comment.
Since resource.toString() doesn't encode resourceNameType, we need to obtain resourceNameType from the acl change path in AclChangedNotificationHandler.
| val KafkaAclPath = "/kafka-acl" | ||
| val KafkaAclChangesPath = "/kafka-acl-changes" | ||
| val KafkaWildcardSuffixedAclPath = "/kafka-wildcard-acl" | ||
| val KafkaWildcardSuffixedAclChangesPath = "/kafka-wildcard-acl-changes" |
There was a problem hiding this comment.
We should just use the one defined in ZkData.
There was a problem hiding this comment.
Given the existing KafkaAclPath and KafkaAclChangePath exist in ZkUtils already, I think it makes sense to add the new ones in here as well. (I know myself, as a user of Kafka, I've used this class to pick up path constants).
I'll make the ZkData uses reference ZkUtils, so the strings are only defined in one place.
How does that sound?
There was a problem hiding this comment.
It's just that our plan is to phase out ZkUtils since it uses the old 101tec.ZkClient api.
There was a problem hiding this comment.
Also ZkUtils is not a public API class even though lots of people use it. So, we kept it as it is for now to give people a chance to upgrade to the AdminClient, but we don't want to add anything new to it.
|
@big-andy-coates and @big-andy-coates A couple of other comments. (1) Currently, we have 2 almost identical Resource class, one in o.a.k.c.resource and another in o.a.k.c.requests. @ijuma mentioned that they were there because the code was added in parallel and we should just keep one. Since we are evolving Resource, perhaps we can just keep o.a.k.c.resource.Resource since it's public. We probably don't want to introduce ResourceNameType in 2 different packages. (2) The KIP proposes to propagate prefix acl changes through a separate path /kafka-wildcard-acl-changes in ZK. Another approach is to propagate the change on the existing path /kafka-acl-changes, but extend the value from a string to a JSON that specifies the resource name type. This has the slight benefit that in the future, if we want to add some other forms of wildcard, we don't need to introduce a separate change propagation path. To prevent old brokers from breaking during upgrade, we can fail the prefix acl requests before the inter.broker.protocol has been upgraded to 2.0. |
|
Hey @junrao, Thanks for the feedback!
I've a local change that removes the need for the two Though... TBH, the I can do this in a separate PR and we can discuss more there.
Nice idea! I think even if we fail any prefix acl requests if the interbroker protocol is < 2.0, we should still write the JSON values to a different path, e.g. /kafka-acl-changes-v2, as otherwise we could end up with a mixture of string / Json values for a time, and this might have tricky edge cases with downgrades. Of do you think this won't be an issue? Again, I propose we do this in a follow up PR. |
Minor refactoring and fixes. Details are in KIP-290: https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+Prefixed+ACLs
|
If @piyushvijay, it looks like you're starting to work on this PR again... but I've already moved the code forward, as agreed, in a separate PR: #5117. (I've only worked in a separate PR as I don't have rights to push to your PR). Things are going to get real messy if you're making changes in your own PR too! We are very very close to the deadline, so I really think we need to be focusing on one PR at the moment! Can I suggest that you review the changes in my PR and, assuming your happy and @jun is happy, we can look to merge it. I have absolutely no issues with you cloning my PR branch, once everyone is happy, and creating your own PR so that you get the credit of the commit if you want. Just make that very clear on my PR thread, and call this out to @junrao and @ijuma, to ensure they don't merge my PR. |
|
Let's keep moving your PR and we can discuss the credit part later ;) |
Details are in KIP-290:
https://cwiki.apache.org/confluence/display/KAFKA/KIP-290%3A+Support+for+wildcard+suffixed+ACLs
More detailed description of your change,
if necessary. The PR title and PR message become
the squashed commit message, so use a separate
comment to ping reviewers.
Summary of testing strategy (including rationale)
for the feature or bug fix. Unit and/or integration
tests are expected for any behaviour change and
system tests should be considered for larger changes.
Committer Checklist (excluded from commit message)