-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
HLRest: model role and privileges #35128
HLRest: model role and privileges #35128
Conversation
Pinging @elastic/es-core-infra |
Pinging @elastic/es-security |
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.
I left a number of comments.
Many of them come down to questions about how we want to approach the HLRC, so it's probably worth having @hub-cap respond to the comments even if he doesn't have capacity to read the whole PR.
...in/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivileges.java
Outdated
Show resolved
Hide resolved
...in/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivileges.java
Outdated
Show resolved
Hide resolved
...in/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivileges.java
Outdated
Show resolved
Hide resolved
...in/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivileges.java
Outdated
Show resolved
Hide resolved
return builtin; | ||
} | ||
if (false == ALL_CLUSTER_PATTERN.matcher(name).matches()) { | ||
throw new IllegalArgumentException("[" + name + "] is not a cluster action privilege."); |
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.
I wonder about this - what's our general view on being this strict in the HLRC?
It means if we add a new cluster privilege then it needs to be added to the HLRC (and, theoretically to every other language client if they have this same level of validation), and customers need to upgrade to a new client just to be allowed to use a new string.
If the developers of the COBOL client did this, and needed to be notified every time a new cluster privilege was added, so they could update the client, I'd think they were crazy.
From a separation of concerns point of view, why should the client know/care which strings are understood by the server, and duplicate that logic?
Isn't it OK for the user of the client to get a server-generated error instead of a client-generated error if they use an incorrect string?
The worst case for this will be if an older client cannot even retrieve the list of roles from the cluster because one of the roles contains a privilege string that the client rejects.
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.
Interesting 🤔
It means if we add a new cluster privilege then it needs to be added to the HLRC
There is no need to update the client in this case, unless you need this new privilege that we added on the server. If you need this, then you have to change client code as well, and since you're there why not update the client library as well? (aside that COBOL developers might prefer the low level client instead)
From a separation of concerns point of view, why should the client know/care which strings are understood by the server, and duplicate that logic?
I believe it kinda should do. This is what high-level client means in my head. It enforces some types, but sometimes it enforces some values too.
Tying on a previous discussion, why should we validate for null values on the client side, at all? Because having a null username or password for example, would be invalidated on the server.
I think this particular example is vulnerable to this type of argument, because the type and value mingle. What if instead of the formatted strings, we would've had a naming scheme based on Enums
? Would we be duplicating the Enums
on the client or would we be using Strings?
The worst case for this will be if an older client cannot even retrieve the list of roles from the cluster because one of the roles contains a privilege string that the client rejects.
I agree, this is the worst case. But even if the client would be able to deserialize they would not be useful, because the client cannot possibly understand them. There is no solution to infinite forward compatibility in the high level client. You can always stick with the low level client.
The alternative we all have in our heads is using Strings. It's the alternative low level client uses.
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.
If this is something that should be enforced like this, we should turn it into an enum instead.
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.
I have made ClusterPrivilege
and IndexPrivilege
strings. The previous classes did not hold any other information besides the string value and there was no reason to validate the format of the privilege value on the client.
Being able to construct a privilege on the client side is a higher client's business.
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.
For what it's worth, I think there's friendliness in defining constants for the Strings for that developers can use IndexPrivilege.READ
, but I don't think the client needs to enforce that you only use the enumerated values.
Especially since the server doesn't even care about that...
PUT _xpack/security/role/test1
{ "cluster": [ "foobar" ] }
{"role":{"created":true}}
GET _xpack/security/role/test1
{"test1":{
"cluster":["foobar"],
"indices":[],
"applications":[],
"run_as":[],
"metadata":{},
"transient_metadata":{"enabled":true}
}}
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.
wow, this is news to me. It will throw when trying to build the role out of the role descriptors (most def - looked at the code, but did not test it), but this is unexpected... TIL
Thank you for pointing this out, to me!
...main/java/org/elasticsearch/client/security/user/privileges/ManageApplicationsPrivilege.java
Outdated
Show resolved
Hide resolved
...nt/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
Outdated
Show resolved
Hide resolved
...main/java/org/elasticsearch/client/security/user/privileges/ManageApplicationsPrivilege.java
Outdated
Show resolved
Hide resolved
...nt/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
Outdated
Show resolved
Hide resolved
...nt/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
Outdated
Show resolved
Hide resolved
I've pitched this problem of future proofing the high-level client, wrt response changes, to the core-infra folk. To be clear, the problem is that a client which ignores fields that it does not understand, while parsing a response, would create an incomplete client object, that if used in a put request, will subtly update the object on the server side (setting the ignored fields from before to null). There is no solution at the moment, but it is expected that API versioning will solve this problem since both the request as well as the response will be versioned. As the privileges are modeled right now, the client will not break for new Global Application privileges, that might be added by kibana. It will however break if we create new global privileges, outside of the application namespace (eg manage template privilege). |
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.
I like where this has gone. I left a few comments. It's probably at the point where we could merge it and then iterate of a couple of things later if you'd like.
* privileges over applications. {@code ApplicationResourcePrivileges} model | ||
* application privileges over resources. This models user privileges over | ||
* applications. Every client is responsible to manage the applications as well | ||
* as the privileges for them. |
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.
This is kind of true for now, but not really the intent.
"global" privileges are just cluster privileges that
(a) Have some sort of "parameters" (that is, they aren't just a plain string). So my expectation is that if we werer to implement it "manage_watcher" is a cluster privilege but { "manage_watches_by_name": ["albert-*"] }
is a global privilege.
(a) In order to accomodate the above, have a custom JSON format.
The only ones that exist "manage applications", so the Javadoc is true, but I don't think it explains it to someone trying to use the API.
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.
Yes, I tried to keep the balance between abstract and pragmatic documentation.
I leaned towards documenting the pragmatic, present behavior. I know it discounts the technical marvel behind, but if I had used the scope
, category
or conditional
jargon, it was my impression that javadocs will lose their benefit to the user. We can always update the javadoc when we stretch the abstraction later on. Yet, the counter argument is that we should aim for more stability in the docs as well, because these are client classes.
Noted. I'll take them to one more round of polish.
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.
Yeah, my thought was that the Javadoc is probably there to explain to users the point of "GlobalPrivileges".
The short answer is, in this release they probably don't care (as long as they handle round-trip GET + PUT correct) because it's just "application privilege management", but in the future it will do more.
} | ||
|
||
public Set<? extends GlobalScopedPrivilege> getPrivileges() { | ||
return applicationPrivileges; |
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.
I'm not sure this is quite right, but it might be - I guess it depends where you think you'd take it next.
It's obviously tricky since we're not 100% sure where we will go with "global" privileges, but let's assume the next thing we do is restict the ability to modify some cluster settings.
To do that we might end up with JSON like:
"global" : {
"application": { "manage": { "applications": [ "*" ] } },
"settings" : { "update" : { "names": [ "logger.*" ] } }
}
In that case, what does getPrivileges
return? Is it a set like:
[ ManageApplicationPrivilege("*") , UpdateClusterSettingsPrivilege("logger.*") ]
Or do we have separate getApplicationPrivileges()
and getSettingsPrivileges()
(probably not).
Or should this return a Map<String, Set<GlobalScopedPrivilege>>
where the keys are "application" and "settings" ?
If we think we only need a single Set<...> getPrivileges()
method, then maybe we only need 1 field too, and it should simply be privileges
not applicationPrivileges
.
I don't have a strong opinion (really!), but I'd like us to have an idea of how to evolve this when we add more global privileges.
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.
Thank you for the comprehensive explanation!
Really my though was that there's no way we can future proof this, the object will have to evolve in lock step with the JSON response (the parser is static, we cannot ignore unknwon fields, etc..). So I didn't gave it much though about how this "evolution" would look like. It would be breaking anyway, so who cares, design it for today.
But! I got a small idea from your Map<String, Set<GlobalScopedPrivilege>>
, stay tuned!
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.
I've decided to use a Set
. I think a map exposes internal implementation, the client should not be informed that we broken them down by category.
...l/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalScopedPrivilege.java
Outdated
Show resolved
Hide resolved
public class GlobalScopedPrivilege { | ||
|
||
private final String scope; | ||
private final Map<String, Object> privilege; |
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.
I wonder if it would help to include the "category" ("application"
, and in theory "settings"
) in this class too.
We can wait add it in the future if we need it, but I think we will, depending on how GlobalPrivileges
ends up looking.
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.
Hmmm, I cannot think of a reason why not to do that 👍
More so, since I am now leaning towards having a set in GlobalPrivileges
because a map would be exposing unnecessary implementation details.
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.
This was a very good idea. I know this is how they are modeled on the server, but I didn't give it much interest at that time.
...level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java
Outdated
Show resolved
Hide resolved
|
||
@SuppressWarnings("unchecked") | ||
public Set<String> getApplications() { | ||
return (Set<String>)getRaw().get(APPLICATIONS); |
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.
👍 I really like how you ended up handling this.
( I suspect it might need a little tweaking when we get to parsing the JSON )
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.
( I suspect it might need a little tweaking when we get to parsing the JSON )
Yeah, we parse the base class, not the concrete ManageApplicationPrivilege
.
I think we'll need a factory to actually instantiate a concrete class. I will mock one up.
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.
I've changed my mind. I'm not really sure how we'll use this class. I'll defer it to the follow-up PR (put role).
...nt/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
Outdated
Show resolved
Hide resolved
...nt/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
Outdated
Show resolved
Hide resolved
private final Set<IndicesPrivileges> indicesPrivileges; | ||
private final Set<ApplicationResourcePrivileges> applicationResourcePrivileges; | ||
private final Set<String> runAs; | ||
private final Map<String, Object> metadata; |
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.
I think that's a problem. I wonder if we should just make the server return a name?
We'll need to do something - I think a role really needs to know its name,
@tvernum I think it's worthy now !? Can you please check it again and say the word and I'll push the merge button 🔴 |
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.
I left a couple of comments. I think the Javadoc is misleading, but you can fix that up easily enough.
The other is just a suggestion.
* outside of the Elasticsearch jurisdiction. | ||
* Represents generic global cluster privileges that can be scoped by categories | ||
* and then by operations. The privilege definition, as well as the operation | ||
* identifier, are outside of the Elasticsearch jurisdiction. Categories are |
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.
I don't quite follow (or perhaps don't agree) with your point about the definition being "outside of the Elasticsearch jurisdiction". Elasticsearch definitely controls & evaluates these.
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.
I was plainly wrong in my understanding.
This is now:
Represents generic global cluster privileges that can be scoped by categories
and then further by operations. The privilege's syntactic and semantic
meaning is specific to each category and operation; there is no general
definition template. It is not permitted to define different privileges under
the same category and operation.
* as the privileges for them. | ||
* generic cluster privileges. These privileges are organized into categories. | ||
* Elasticsearch defines the set of categories. Under each category there are | ||
* operations that are under the clients jurisdiction. The privilege is hence |
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.
This is not true.
The definition
"application": { "manage": { "applications": [ "kibana-*" ] } }
Instructs Elasticsearch that this role is allowed to manage application privileges under the application names "kibana-*".
It's not an instruction for Kibana, it's enforced by Elasticsearch.
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.
I was wrong, I got carried astray by too much abstraction.
This is now:
Constructs privileges under a specific {@code category} and for some
{@code operation}. The privilege definition is flexible, it is a {@code Map},
and the semantics is bound to the {@code category} and {@code operation}.
} | ||
} | ||
|
||
@Override | ||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { | ||
final Map<String, List<GlobalOperationPrivilege>> privilegesByCategoryMap = | ||
this.privileges.stream().collect(Collectors.groupingBy(GlobalOperationPrivilege::getCategory)); |
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 just keep this Map
around?
We use it in the contructor, and in toXContent
, couldn't it just be a field?
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.
I went with your suggestion. At first, I have resisted duplicating information, but since it's immutable and private, I suppose it's OK.
run gradle build tests |
run gradle build tests |
1 similar comment
run gradle build tests |
The following "user privileges" objects have been created on the client side: * ApplicationResourcePrivileges * IndicesPrivileges * GlobalOperationPrivilege * GlobalPrivileges as well as the aggregating `Role` entity.
* master: Add docs for CCR stats API (elastic#35439) Fix the names of CCR stats endpoints in usage API (elastic#35438) Switch to default to no build qualifier (elastic#35415) Clarify S3 repository storage class parameter (elastic#35400) SQL: Fix query translation for scripted queries (elastic#35408) [TEST] Instead of ignoring the ccr downgrade to basic license qa test avoid the assertions that check the log files, because that does not work on Windows. The rest of the test is still useful and should work on Windows CI. Upgrade to Joda 2.10.1 (elastic#35410) HLRest: model role and privileges (elastic#35128)
* master: Add docs for CCR stats API (elastic#35439) Fix the names of CCR stats endpoints in usage API (elastic#35438) Switch to default to no build qualifier (elastic#35415) Clarify S3 repository storage class parameter (elastic#35400) SQL: Fix query translation for scripted queries (elastic#35408) [TEST] Instead of ignoring the ccr downgrade to basic license qa test avoid the assertions that check the log files, because that does not work on Windows. The rest of the test is still useful and should work on Windows CI. Upgrade to Joda 2.10.1 (elastic#35410) HLRest: model role and privileges (elastic#35128)
* master: Address handling of OS pretty name on some OS (elastic#35451) HLRC support for getTask (elastic#35166) upgrade to lucene-8.0.0-snapshot-6d9c714052 (elastic#35428) Add docs for CCR stats API (elastic#35439) Fix the names of CCR stats endpoints in usage API (elastic#35438) Switch to default to no build qualifier (elastic#35415) Clarify S3 repository storage class parameter (elastic#35400) SQL: Fix query translation for scripted queries (elastic#35408) [TEST] Instead of ignoring the ccr downgrade to basic license qa test avoid the assertions that check the log files, because that does not work on Windows. The rest of the test is still useful and should work on Windows CI. Upgrade to Joda 2.10.1 (elastic#35410) HLRest: model role and privileges (elastic#35128)
EDITED:
This adds the security put role API.The following "user privileges" entities have been created on the client side:
ApplicationResourcePrivileges
This is now a plain StringClusterPrivilege
This is now a plain StringIndexPrivilege
IndicesPrivileges
Replaced by the generalManageApplicationsPrivilege
GlobalPrivileges
as well as the aggregating
Role
entity.== Note: WIP
(no API call, I only have modeled the client side entities)There will be no API call in this PR, but only client side objects.
Relates: #29827