Skip to content

Conversation

@grcevski
Copy link
Contributor

This PR adds support for /_security/role_mapping for file based settings. We create similar reserved cluster state information about the role mappings, even though they are not part of the cluster state.

Since the role_mappings are not saved in the cluster state, we needed to create separate reserved state action wrapper for any non-cluster state updates.

Relates to #89183

@grcevski grcevski added >enhancement :Core/Infra/Core Core issues without another label Team:Core/Infra Meta label for core/infra team Team:Security Meta label for security team v8.5.0 labels Aug 26, 2022
@elasticsearchmachine elasticsearchmachine removed the Team:Security Meta label for security team label Aug 26, 2022
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

@elasticsearchmachine
Copy link
Collaborator

Hi @grcevski, I've created a changelog YAML for you.

* An extension of the {@link HandledTransportAction} class, which wraps the doExecute call with a check for clashes
* with the reserved cluster state.
*/
public abstract class ReservedStateAwareHandledTransportAction<Request extends ActionRequest, Response extends ActionResponse> extends
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the role mappings don't use TransportMasterNodeAction (which is cluster state specific) we add this small wrapper class to ensure the reserved state information is checked before allowing REST actions to create/update/delete role mappings.

format("Failed to process request [%s] with errors: [%s]", request, String.join(", ", errors))
);
}
ReservedClusterStateService.validateForReservedState(state, handlerName.get(), modifiedKeys(request), request.toString());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this check is common now, I moved it to ReservedClusterStateService to be reused.


// package private for testing
boolean supportsImmutableState() {
boolean supportsReservedState() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are simple renames, which were somehow missed before. We did rename from immutable to reserved.

* @param modified the set of modified keys by the related request
* @param request a string representation of the request for error reporting purposes
*/
public static void validateForReservedState(ClusterState state, String handlerName, Set<String> modified, String request) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved here from TransportMasterNodeAction for reuse.

return new ExpressionRoleMapping(name, rules, roles, roleTemplates, metadata, enabled);
}

public static PutRoleMappingRequest fromMapping(ExpressionRoleMapping mapping) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper method used by the reserved handler and test to create the request from the parsed mappings.

}

@SuppressWarnings("unchecked")
public Collection<PutRoleMappingRequest> prepare(Object input) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We convert from expression role mappings to a request to satisfy the native store API contract.

/**
* Mock Security Provider implementation for the {@link ReservedClusterStateHandlerProvider} service interface
*/
public class LocalReservedSecurityStateHandlerProvider implements ReservedClusterStateHandlerProvider {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mock version of the SPI handler provider so I could test properly with integration tests.

@grcevski
Copy link
Contributor Author

@elasticsearchmachine run elasticsearch-ci/part-1

@ywangd ywangd self-requested a review August 29, 2022 22:54
Copy link
Member

@ywangd ywangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great. I don't think I'll find anything major. There are places that can use future refactoring. But I think the changes are plenty already for this PR. So we can wait.

Since there are many changes, I just need some more time to double check and read through the tests. But I'd like to make it clear that I am progressing on it by providing some minor optional feedback.

I also have a question: Will there only be a single file-state-update task to run at a time, i.e. it is not possible that two threads observed file changes and start parallel updating?

return NAME;
}

public Collection<PutRoleMappingRequest> prepare(List<ExpressionRoleMapping> roleMappings) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this method be private?

return new TransformState(prevState.state(), prevState.keys(), ((l) -> nonStateTransform(requests, prevState, l)));
}

private Void nonStateTransform(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Is there a reason to use Void instead of void? I think using void is simpler because there is no need to return null in the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% I don't remember now why I made it Void, must've been an iteration I had when the consumer was a function.

@ywangd
Copy link
Member

ywangd commented Sep 15, 2022

I think we could use a few more tests. For example, making sure TransportPutRoleMappingAction actually respects the reserved keys. Or maybe this can be tested with ReservedStateAwareHandledTransportAction? Or maybe it is already tested somewhere else since it is extraction refactoring? It's not entirely clear to me.

@grcevski
Copy link
Contributor Author

I also have a question: Will there only be a single file-state-update task to run at a time, i.e. it is not possible that two threads observed file changes and start parallel updating?

File based settings doesn't allow more than one update to run at the same time. So while no synchronization exists in the reserved state service, the file based settings service uses a latch to wait on until all async actions inside the reserved state service to finish, before it checks for new file updates. While an update is going on on certain file contents, the JDK watcher service can queue new file update changes, which will get processed one by one after the previous in-flight update finishes. We have a test for some of this logic in FileSettingsServiceTest.testStopWorksInMiddleOfProcessing and testStopWorksIfProcessingDidntReturnYet.

Co-authored-by: Yang Wang <[email protected]>
@grcevski
Copy link
Contributor Author

I think we could use a few more tests. For example, making sure TransportPutRoleMappingAction actually respects the reserved keys. Or maybe this can be tested with ReservedStateAwareHandledTransportAction? Or maybe it is already tested somewhere else since it is extraction refactoring? It's not entirely clear to me.

Some of this is tested in RoleMappingFileSettingsIT.assertRoleMappingsSaveOK. At the end the method checks that a REST call cannot update any of the reserved role mappings, but this is a good call-out, I'll add explicit tests for validation of the keys in TransportPutRoleMappingAction, similarly as I've done for TransportPutLifecycleActionTests.

I also have tests that perform the validation on TransportMasterNodeAction (TransportMasterNodeActionTests.testRejectImmutableConflictClusterStateUpdate), but not on the ReservedStateAwareHandledTransportAction which I introduced in the refactor. I'll add tests here too, thanks for bringing it up.

Copy link
Member

@ywangd ywangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for the iterations. I appreciate your patience and dedication to get this across the line. I learnt a lot in the process.

It would be nice if we could have tests for following two scenarios:

  1. Role mappings pass validation but fail to write to index. A easy way to achieve this is to close the security index.
  2. Role mappings succeed, but final cluster state update fails. I am not sure how easy or hard to force cluster state failure at that point.

Happy for them to be follow-ups.

Also my previous experience with Java's WatchService is that it does not work if the file system is NFS. Maybe this is no longer the case? Or maybe this feature does not need work with NFS? Anyhow, even it is an issue, it is not part of this PR. I am bringing it up just in case.

"roles": [ "kibana_user" ],
"rules": { "field": { "username": "*" } },
"metadata": {
"uuid" : "b9a59ba9-6b92-4be2-bb8d-02bb270cb3a7"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a reserved metadata key/value here, e.g. "_foo": "something"?

Comment on lines 271 to 273
writeJSONFile(internalCluster().getMasterName(), emptyJSON);
boolean awaitSuccessful = savedClusterState.v1().await(20, TimeUnit.SECONDS);
assertTrue(awaitSuccessful);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the empty JSON file is processed, do the role mappings get deleted? If so, I think we should assert the role mappings are now gone and nothing is reserved.

@grcevski
Copy link
Contributor Author

It would be nice if we could have tests for following two scenarios:

  1. Role mappings pass validation but fail to write to index. A easy way to achieve this is to close the security index.
  2. Role mappings succeed, but final cluster state update fails. I am not sure how easy or hard to force cluster state failure at that point.

Good idea, I'm going to make a test for 1. I think I can test 2. with some mocked methods, but I guess in that case we are simply testing the flaw in our atomicity across the two stores. Perhaps we can add a test here if we ever fix this situation in the future.

Also my previous experience with Java's WatchService is that it does not work if the file system is NFS. Maybe this is no longer the case? Or maybe this feature does not need work with NFS? Anyhow, even it is an issue, it is not part of this PR. I am bringing it up just in case.

Thanks for this info, I hadn't tested with an NFS file system. I think it's not part of the requirements, but we should definitely document is as a limitation. I'll follow-up with an investigation here.

@grcevski grcevski merged commit 2b3dbde into elastic:main Sep 20, 2022
@grcevski grcevski deleted the operator/role_mapping branch September 20, 2022 14:34
@grcevski
Copy link
Contributor Author

Thanks for all the help Yang!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Core/Infra/Core Core issues without another label >enhancement Team:Core/Infra Meta label for core/infra team v8.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants