Skip to content

Conversation

@rjernst
Copy link
Member

@rjernst rjernst commented Jul 9, 2020

This commit adds a new api to track when gold+ features are used within
x-pack. The tracking is done internally whenever a feature is checked
against the current license. The output of the api is a list of each
used feature, which includes the name, license level, and last time it
was used. In addition to a unit test for the tracking, a rest test is
added which ensures starting up a default configured node does not
result in any features registering as used.

There are a couple features which currently do not work well with the
tracking, as they are checked in a manner that makes them look always
used. Those features will be fixed in followups, and in this PR they are
omitted from the feature usage output.

This commit adds a new api to track when gold+ features are used within
x-pack. The tracking is done internally whenever a feature is checked
against the current license. The output of the api is a list of each
used feature, which includes the name, license level, and last time it
was used. In addition to a unit test for the tracking, a rest test is
added which ensures starting up a default configured node does not
result in any features registering as used.

There are a couple features which currently do not work well with the
tracking, as they are checked in a manner that makes them look always
used. Those features will be fixed in followups, and in this PR they are
omitted from the feature usage output.
@rjernst rjernst added >refactoring :Security/License License functionality for commercial features v8.0.0 v7.9.0 labels Jul 9, 2020
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security (:Security/License)

@elasticmachine elasticmachine added the Team:Security Meta label for security team label Jul 9, 2020
Comment on lines -44 to +46
boolean shouldIntercept = licenseState.isSecurityEnabled() && licenseState.checkFeature(Feature.SECURITY_DLS_FLS);
boolean shouldIntercept = licenseState.isSecurityEnabled();
var licenseChecker = new MemoizedSupplier<>(() -> licenseState.checkFeature(Feature.SECURITY_DLS_FLS));
Copy link
Member

Choose a reason for hiding this comment

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

The existing expression of licenseState.isSecurityEnabled() && licenseState.checkFeature(Feature.SECURITY_DLS_FLS) has a potential racing issue between the two license checks. I noticed we have been somewhat inconsistent in handling this. In some places, e.g. ResizeRequestInterceptor, we use licenseState.copyCurrentLicenseState() to ensure checks are always done against the same license. In other places including this one, no special handling is done. The new changes also retain this behaviour. My question is: what is our recommend approach for this subtle issue? I am OK with either approach as long as it is consistently applied.

I am also curious about the benefit of the new MemoizedSupplier. Why do we need to check the license deeper in the loop? Isn't it sufficient and more efficient to check in the outermost if statement?

Copy link
Member Author

Choose a reason for hiding this comment

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

what is our recommend approach for this subtle issue?

It is true there is a race condition, but in practice I don't think we need to worry about it. This is an extreme edge case, where the license changes mid request. AFAIU, the copy method was added to handle the race condition in tests, not for any problem found by users. That said, the long term solution to this is to change all of our checks to use a stashed copy in the ThreadContext that would be inserted when the request is first received. That idea is described in #53909.

I am also curious about the benefit of the new MemoizedSupplier. Why do we need to check the license deeper in the loop?

We don't know if DLS/FLS is actually used until we resolve the each indices' access controls inside the loop. Thus we need to check when resolving each index. It is true this is similar to the problem described above, and could be partially solved by copying the license state. However, it also means needlessly updating the last used time in quick succession. I could go either way, but thought reducing those updates was worthwhile given the simpleness of implementing the MemoizedSupplier.

boolean dls = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions();
if (fls || dls) {
if (bulkItemRequest.request() instanceof UpdateRequest) {
if (licenseChecker.get() && bulkItemRequest.request() instanceof UpdateRequest) {
Copy link
Member

Choose a reason for hiding this comment

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

Ah I see, the license checking is moved here because we need to accurately tracking the usage, i.e. only check it when it is absolutely necessary. In this case, I wonder whether we should check it after the instanceof, i.e. bulkItemRequest.request() instanceof UpdateRequest && licenseChecker.get().


@Override
protected void doExecute(Task task, GetFeatureUsageRequest request, ActionListener<GetFeatureUsageResponse> listener) {
Map<XPackLicenseState.Feature, Long> featureUsage = licenseState.getLastUsed();
Copy link
Member

Choose a reason for hiding this comment

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

IIUC, the feature usage is reported for the local node only? So in cloud with coordinating node setup, how can user get the feature usages for nodes other than the coordinating ones?

Copy link
Member Author

Choose a reason for hiding this comment

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

Cloud will need to hit every node in the cluster, not just coordinating nodes. AFAIU they can do this (this would be on their backend infrastructure side, and so would not be going through eg load balancers). It would be similar to eg health checks that hit each node.

Copy link
Member

Choose a reason for hiding this comment

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

Ok so this API is more geared towards Cloud internal management instead of end-users. Then it is fine.

@rjernst
Copy link
Member Author

rjernst commented Jul 13, 2020

@elasticmachine run elasticsearch-ci/2

public XPackLicenseState copyCurrentLicenseState() {
return executeAgainstStatus(status ->
new XPackLicenseState(listeners, isSecurityEnabled, isSecurityExplicitlyEnabled, status));
new XPackLicenseState(listeners, isSecurityEnabled, isSecurityExplicitlyEnabled, status, lastUsed, epochMillisProvider));
Copy link
Member

Choose a reason for hiding this comment

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

This worries me a bit since a copy can now mutate the original object. It is not an issue for the time being since no such usage exists. But might be a confusing behaviour in future?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's exactly the intention. The copy is meant for read purposes, but mutation purposes must be unified, otherwise we would would not be able to check what checks have been done across copies.

Comment on lines 53 to 55
final XPackLicenseState frozenLicenseState = licenseState.copyCurrentLicenseState();
final AuditTrail auditTrail = auditTrailService.get();
if (frozenLicenseState.isSecurityEnabled()) {
Copy link
Member

Choose a reason for hiding this comment

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

We are using both frozenLicenseState here and the original licenseState with MemoizedSupplier below. Given the current change, there is no benefit of having frozenLicenseState anymore. It can probably just be removed.

ActionListener<Void> listener) {
if (requestInfo.getRequest() instanceof ResizeRequest) {
final ResizeRequest request = (ResizeRequest) requestInfo.getRequest();
final XPackLicenseState frozenLicenseState = licenseState.copyCurrentLicenseState();
Copy link
Member

Choose a reason for hiding this comment

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

Same here for using frozenLicenseState and licenseState together.

@Override
public boolean available() {
return licenseState != null && licenseState.isAllowed(Feature.VOTING_ONLY);
return licenseState != null && licenseState.checkFeature(Feature.VOTING_ONLY);
Copy link
Member

Choose a reason for hiding this comment

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

This method does not seem to be used anywhere? But the inner class VotingOnlyNodeFeatureSet#UsageInfoAction also has a availabe() method which internally uses licenseState.checkFeature. But all other XPackInfoFeatureTransportAction uses licenseState.isAllowed. Is this inconsistency intended?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've fixed the checkFeature usage, it was incorrect. I'll address the unused class in a followup.

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.

I left a few comments which would be great if they could be addressed. But none of them is critical enough to delay the approval.

@rjernst rjernst merged commit 5d6a2dd into elastic:master Jul 14, 2020
@rjernst rjernst deleted the refactor_license13 branch July 14, 2020 19:25
rjernst added a commit to rjernst/elasticsearch that referenced this pull request Jul 14, 2020
This commit adds a new api to track when gold+ features are used within
x-pack. The tracking is done internally whenever a feature is checked
against the current license. The output of the api is a list of each
used feature, which includes the name, license level, and last time it
was used. In addition to a unit test for the tracking, a rest test is
added which ensures starting up a default configured node does not
result in any features registering as used.

There are a couple features which currently do not work well with the
tracking, as they are checked in a manner that makes them look always
used. Those features will be fixed in followups, and in this PR they are
omitted from the feature usage output.
rjernst added a commit that referenced this pull request Jul 14, 2020
This commit adds a new api to track when gold+ features are used within
x-pack. The tracking is done internally whenever a feature is checked
against the current license. The output of the api is a list of each
used feature, which includes the name, license level, and last time it
was used. In addition to a unit test for the tracking, a rest test is
added which ensures starting up a default configured node does not
result in any features registering as used.

There are a couple features which currently do not work well with the
tracking, as they are checked in a manner that makes them look always
used. Those features will be fixed in followups, and in this PR they are
omitted from the feature usage output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

>refactoring :Security/License License functionality for commercial features Team:Security Meta label for security team v7.9.0 v8.0.0-alpha1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants