Dynamically Enable/Disable multi-tenancy, private tenant and set Defa…#2444
Dynamically Enable/Disable multi-tenancy, private tenant and set Defa…#2444abhivka7 wants to merge 2 commits intoopensearch-project:mainfrom
Conversation
…ult Tenant Signed-off-by: Abhi Kalra <abhivka@amazon.com>
stephen-crawford
left a comment
There was a problem hiding this comment.
Looks good, just a couple comments and general questions. I left a note for cleaning the style stuff. Thank you for you contribution.
| tenancy_config: | ||
| multitenancy_enabled: true | ||
| private_tenant_enabled: true | ||
| default_tenant: "" No newline at end of file |
There was a problem hiding this comment.
You can use ./gradlew spotlessApply to automatically correct style
There was a problem hiding this comment.
Thanks for the sugestion @scrawfor99 . I used ./gradlew spotlessApply as per your suggestion. It corrected style for some files but not tenancy_config.yml. Do you need any specific change in this file that you want me to do manually?
| ConfigHelper.uploadFile(client, cd+"roles_mapping.yml", securityIndex, CType.ROLESMAPPING, DEFAULT_CONFIG_VERSION); | ||
| ConfigHelper.uploadFile(client, cd+"internal_users.yml", securityIndex, CType.INTERNALUSERS, DEFAULT_CONFIG_VERSION); | ||
| ConfigHelper.uploadFile(client, cd+"action_groups.yml", securityIndex, CType.ACTIONGROUPS, DEFAULT_CONFIG_VERSION); | ||
| final boolean populateEmptyIfFileMissing = true; |
There was a problem hiding this comment.
Should this be moved to a config constants file?
There was a problem hiding this comment.
Sure will do in next commit. Thanks for the suggestion.
There was a problem hiding this comment.
With this, would populateEmptyIfFileMissing always be true? When would it be false?
There was a problem hiding this comment.
It was already always true. We just moved the line of declaring this variable. From next commit onwards, we can change this from config constants file.
|
|
||
| final SecurityDynamicConfiguration<?> configuration = load(getConfigName(), true); | ||
| filter(configuration); | ||
| successResponse(channel,configuration); |
There was a problem hiding this comment.
If you want to do anything special for responses you will need to add onResponse logic here.
There was a problem hiding this comment.
@scrawfor99 We do not want to do anything special with our responses. I don't think we need that logic here.
| final Resolved requestedResolved, final Map<String, Boolean> tenants) { | ||
|
|
||
| final boolean enabled = config.isDashboardsMultitenancyEnabled();//config.dynamic.kibana.multitenancy_enabled; | ||
| final boolean enabled = tenancyconfig.isDashboardsMultitenancyEnabled(); |
There was a problem hiding this comment.
Thanks @anijain-Amazon for finding this miss. Will update in next commit.
There was a problem hiding this comment.
With this change, would config.dynamic.kibana.multitenancy_enabled be ignored? Would this work in mixed clusters? Based on my understanding, security index is node-scoped. If there're 2 settings(the old and the new), could there be 2 different values(false and true) of multitenancy_enabled setting in a mixed cluster?
There was a problem hiding this comment.
@cliu123 going forward we will only use config.dynamic.kibana.multitenancy_enabled and this will get priority over config.dynamic.kibana.multitenancy_enabled.
If there're 2 settings(the old and the new), could there be 2 different values(false and true) of multitenancy_enabled setting in a mixed cluster?
In this case we will give priority to new settings.
| try { | ||
| final XContentBuilder builder = channel.newBuilder(); | ||
| builder.startObject(); | ||
| builder.endObject(); |
There was a problem hiding this comment.
Were we not calling builder.endObject(); before ?
There was a problem hiding this comment.
No we were not doing it. That was a miss I guess.
| // validate additional settings, if any | ||
| AbstractConfigurationValidator validator = getValidator(request, request.content()); | ||
| if (!validator.validate()) { | ||
| if (validator!= null && !validator.validate()) { |
There was a problem hiding this comment.
If we change the condition to validator == null || !validator.validate() then the use of checks in the lines below is not required.
There was a problem hiding this comment.
This was added while testing and I forgot to remove it. Thanks @anijain-Amazon for pointing this out. Will remove this check condition in next commit.
|
|
||
| @Override | ||
| protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ | ||
| successResponse(channel); |
There was a problem hiding this comment.
If this is not doing anything, shouldn't it be notImplemented as well ?
There was a problem hiding this comment.
We want to enable PUT request for tenancyConfig. That's why we are not using notImplemented .
There was a problem hiding this comment.
For my understanding, why is it returning successResponse directly?
cwperks
left a comment
There was a problem hiding this comment.
@abhivka7 Thank you for the PR! Overall the PR looks good, but I wanted to run an idea by you to ensure that introducing a new tenantconfig.yml file is the right way to go.
I see that config.yml already provides:
@Override
public boolean isDashboardsMultitenancyEnabled() {
return config.dynamic.kibana.multitenancy_enabled;
}
was any consideration given to also adding private_tenant_enabled and default_tenant to this section of config.yml? I think that could potentially simplify this PR. The beginning of config.yml could look like:
config:
dynamic:
# Set filtered_alias_mode to 'disallow' to forbid more than 2 filtered aliases per index
# Set filtered_alias_mode to 'warn' to allow more than 2 filtered aliases per index but warns about it (default)
# Set filtered_alias_mode to 'nowarn' to allow more than 2 filtered aliases per index silently
#filtered_alias_mode: warn
#do_not_fail_on_forbidden: false
multitenancy:
enabled: true
private_tenant_enabled: true
default_tenant: ''
then to get these into SecurityInfoAction you could follow a pattern similar to https://github.com/opensearch-project/security/blob/main/src/main/java/org/opensearch/security/auth/BackendRegistry.java#L152-L169 to subscribe to changes in this config.
| private static String[] getTypes(boolean legacy) { | ||
| if (legacy) { | ||
| return new String[]{"config", "roles", "rolesmapping", "internalusers", "actiongroups", "nodesdn", "audit"}; | ||
| return new String[]{"config", "roles", "rolesmapping", "internalusers", "actiongroups", "nodesdn", "audit","tenancyconfig"}; |
There was a problem hiding this comment.
This legacy block is only for CType for ES6. This can be removed from this block.
There was a problem hiding this comment.
Thanks for pointing this out @cwperks. Will remove this in next commit.
| final boolean populateEmptyIfFileMissing = true; | ||
| if(DEFAULT_CONFIG_VERSION == 2) { | ||
| ConfigHelper.uploadFile(client, cd+"tenants.yml", securityIndex, CType.TENANTS, DEFAULT_CONFIG_VERSION); | ||
| ConfigHelper.uploadFile(client, cd+"tenancy_config.yml", securityIndex, CType.TENANCYCONFIG, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); |
There was a problem hiding this comment.
Should this only be populated if the file exists? I see tenants.yml does not create this config entry if no tenants.yml file is supplied.
| // validate additional settings, if any | ||
| AbstractConfigurationValidator validator = getValidator(request, request.content()); | ||
| if (!validator.validate()) { | ||
| if (validator!= null && !validator.validate()) { |
There was a problem hiding this comment.
I see that this PR introduces a validator. When will the validator ever be null? Are the changes in this file needed?
There was a problem hiding this comment.
This was added while testing and I forgot to remove it. Thanks @cwperks for pointing this out. Will remove this check condition in next commit.
shikharj05
left a comment
There was a problem hiding this comment.
Thank you for the work, added few comments.
| new Route(Method.GET, "/tenancyconfig/"), | ||
| new Route(Method.PUT, "/tenancyconfig/"), | ||
| new Route(Method.PATCH, "/tenancyconfig/") | ||
| ) |
There was a problem hiding this comment.
Is DELETE not required?
There was a problem hiding this comment.
I don't think there will be a use case where we require DELETE. We should not delete tenancy_config and we can replace existing config using PATCH.
Correct me if I am wrong @devardee
|
|
||
| public boolean isDashboardsMultitenancyEnabled() { return this.tenancyConfig.multitenancy_enabled; }; | ||
| public boolean isDashboardsPrivateTenantEnabled() { return this.tenancyConfig.private_tenant_enabled; }; | ||
| public String dashboardsDefaultTenant() { return this.tenancyConfig.default_tenant; }; |
There was a problem hiding this comment.
nit: rename to getDashboardsDefaultTenant
There was a problem hiding this comment.
Ok. Will change in next commit.
| final WhitelistingSettings whitelist = (WhitelistingSettings) cr.getConfiguration(CType.WHITELIST).getCEntry("config"); | ||
| final AllowlistingSettings allowlist = (AllowlistingSettings) cr.getConfiguration(CType.ALLOWLIST).getCEntry("config"); | ||
| final AuditConfig audit = (AuditConfig)cr.getConfiguration(CType.AUDIT).getCEntry("config"); | ||
| final TenancyConfigModel tcm ; |
There was a problem hiding this comment.
Thanks @shikharj05 for pointing this out. Will fix in next commit.
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
|
||
| public class TenancyConfigV7 { |
There was a problem hiding this comment.
implement a toString() method?
There was a problem hiding this comment.
Sure. Will do in next commit.
| } | ||
|
|
||
| @Test | ||
| public void testTenancyConfigAPIUpdate() throws Exception { |
There was a problem hiding this comment.
these can be split into different tests for each property
There was a problem hiding this comment.
Thanks for the suggestion @shikharj05 . I will split these and update in next commit.
There was a problem hiding this comment.
I would suggest to keep it in one test, as the test sets up a cluster before executing the test and tears down after. This will impact on the performance of the tests.
|
@abhivka7 @shikharj05 See comment above. Have you considered doing this inside of i.e. |
@cwperks We were initially working on the same approach that you suggested. But going forward, we plan on introducing some other fields and features for multi-tenancy. Therefore we plan on separating this file and introducing a new one. |
| ConfigHelper.uploadFile(client, cd+"roles_mapping.yml", securityIndex, CType.ROLESMAPPING, DEFAULT_CONFIG_VERSION); | ||
| ConfigHelper.uploadFile(client, cd+"internal_users.yml", securityIndex, CType.INTERNALUSERS, DEFAULT_CONFIG_VERSION); | ||
| ConfigHelper.uploadFile(client, cd+"action_groups.yml", securityIndex, CType.ACTIONGROUPS, DEFAULT_CONFIG_VERSION); | ||
| final boolean populateEmptyIfFileMissing = true; |
There was a problem hiding this comment.
With this, would populateEmptyIfFileMissing always be true? When would it be false?
| //intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias involved | ||
| final boolean dashboardsIndexOnly = !user.getName().equals(dashboardsServerUsername) && resolveToDashboardsIndexOrAlias(requestedResolved, dashboardsIndexName); | ||
| final boolean isTraceEnabled = log.isTraceEnabled(); | ||
|
|
There was a problem hiding this comment.
Please remove the empty line and avoid format-only changes.
| final Resolved requestedResolved, final Map<String, Boolean> tenants) { | ||
|
|
||
| final boolean enabled = config.isDashboardsMultitenancyEnabled();//config.dynamic.kibana.multitenancy_enabled; | ||
| final boolean enabled = tenancyconfig.isDashboardsMultitenancyEnabled(); |
There was a problem hiding this comment.
With this change, would config.dynamic.kibana.multitenancy_enabled be ignored? Would this work in mixed clusters? Based on my understanding, security index is node-scoped. If there're 2 settings(the old and the new), could there be 2 different values(false and true) of multitenancy_enabled setting in a mixed cluster?
|
|
||
| final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); | ||
|
|
There was a problem hiding this comment.
Please remove format-only changes.
|
|
||
| public class SecurityDynamicConfiguration<T> implements ToXContent { | ||
|
|
There was a problem hiding this comment.
Please remove format-only changes.
| sdc = new SecurityDynamicConfiguration<T>(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Please remove format-only changes.
| public String server_username = "kibanaserver"; | ||
| public String opendistro_role = null; | ||
| public String index = ".kibana"; | ||
|
|
There was a problem hiding this comment.
Please remove format-only changes.
| .id(CType.TENANTS.toLCString()) | ||
| .setRefreshPolicy(RefreshPolicy.IMMEDIATE) | ||
| .source(CType.TENANTS.toLCString(), FileHelper.readYamlContent(prefix+securityTenants))); | ||
| } |
There was a problem hiding this comment.
Please remove format-only changes.
| final Resolved requestedResolved, final Map<String, Boolean> tenants) { | ||
|
|
||
| final boolean enabled = config.isDashboardsMultitenancyEnabled();//config.dynamic.kibana.multitenancy_enabled; | ||
| final boolean enabled = tenancyconfig.isDashboardsMultitenancyEnabled(); |
There was a problem hiding this comment.
Hi @abhivka7, thanks for this contribution. I see the point that you are trying to extend the original multi-tenancy feature with this change. So do we need a feature flag for this dynamically enable/disable multi-tenancy feature? Or it will be applied directly by enable multi tenancy?
There was a problem hiding this comment.
We don't meed a flag for these changes. We will keep multi-tenancy enabled for new domains and changes can be applied directly.
We want to decouple tenancy related configuration from securityconfig for the following reasons:
cc: @peternied , @shikharj05, @abhivka7 |
|
@devardee, there is already a multi-tenancy key ( I'm not sure I follow the concerns on the migrate API. For this section I am assuming the new section in What I am thinking is changing the then inside This new section could be created in Would this approach be feasible? I think what's presented in this PR will work, but I also like the idea of less configuration files. |
|
Thank you for the reply @devardee
There is an existing setting already in the security configuration, removing/invalidating this setting is a breaking change - that is a con with this proposal.
I'm not sure how this is a problem. Could you provide more background on what the migrate api is and how it is impacted with this change vs reusing the existing config?
Could you provide reference to what future enhancements are? Without more context this argument does not inform the current choice. I am in aligned with Craig's proposal that reusing the existing systems. |
DarshitChanpura
left a comment
There was a problem hiding this comment.
Thank you @abhivka7 for adding this PR. One generic comment is to add a brief javadoc for the new class added and check/add a new-line at the end of all modified files.
|
|
||
| @Override | ||
| protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ | ||
| successResponse(channel); |
There was a problem hiding this comment.
For my understanding, why is it returning successResponse directly?
| builder.field("principal", (String)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL)); | ||
| builder.field("peer_certificates", certs != null && certs.length > 0 ? certs.length + "" : "0"); | ||
| builder.field("sso_logout_url", (String)threadContext.getTransient(ConfigConstants.SSO_LOGOUT_URL)); | ||
| builder.field("tenancy_enabled", evaluator.multitenancyEnabled()); |
There was a problem hiding this comment.
the name tenancy_enabled is slightly misleading. Can we have multi_tenancy_enabled instead?
There was a problem hiding this comment.
Ok will make this change.
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
|
||
| public class TenancyConfigV7 { |
There was a problem hiding this comment.
Why the name V7 here? There is only one version of this class. We can move this class out of v7 folder and rename it to TenancyConfig
| public boolean multitenancyEnabled() { | ||
| return privilegesInterceptor.getClass() != PrivilegesInterceptor.class | ||
| && dcm.isDashboardsMultitenancyEnabled(); | ||
| && tcm.isDashboardsMultitenancyEnabled(); |
There was a problem hiding this comment.
Can we remove this method from DynamicConfigModel and its inheritor DynamicConfigModelV7 since it is not used anymore?
| } | ||
|
|
||
| @Test | ||
| public void testTenancyConfigAPIUpdate() throws Exception { |
There was a problem hiding this comment.
I would suggest to keep it in one test, as the test sets up a cluster before executing the test and tears down after. This will impact on the performance of the tests.
|
Checking back in with this pull request. There are still a number of outstanding comments. I have written up a proposal that I think might simplify the issues around proxying config data from place to place. I'll see about getting a proof of concept up and running as I think it would be a far better model isolating intention from the storage medium of the configuration value. Love to get feedback on this - thanks! |
|
I have created a draft pull request featuring a version of this dynamic configuration that would support this scenario and shore up the limitations that have been called in the comments. |
|
Created another PR for these changes: #2607. Closing this one. |
|
I'm closing this PR out since #2607 is where this functionality is being worked on |
…ult Tenant
Description
These changes are required to dynamically enable/disable multi-tenancy, private tenant and set a default tenant.
Earlier users had to change YAMl files in each node and restart node to enable/disable multi-tenancy and private tenant. With our changes, users will be able to change these settings dynamically. We will also give admins the option to set a default tenant for all users for first time log in.
More details can be found here:
[RFC] Dynamically Configurable Tenancy Feature OpenSearch#5853
[FEATURE] Enable/Disable Multi-Tenancy, Private Tenant and set Default Tenant in OS Dashboards. security-dashboards-plugin#1302
Issues Resolved
opensearch-project/security-dashboards-plugin#1302
Is this a backport? If so, please add backport PR # and/or commits #
Nope
Testing
We have done manual testing for all changes on our local server. We have also written some unit tests for our api changes. We will write more unit tests and integration tests based on reviewer feedbacks soon.
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.