Skip to content
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

Add ability for plugins to inject roles #560

Merged
merged 6 commits into from
Aug 6, 2020

Conversation

skkosuri-amzn
Copy link
Contributor

Issue #, if available:
opendistro-for-elasticsearch/alerting#215

Description of changes:
This is used to inject opendistro-roles into the request when there is no user involved, like periodic plugin background jobs. The roles injection is done using thread-context at transport layer only. You can't inject roles using REST api. Using this we can enforce fine-grained-access-control for the transport layer calls plugins make.

Format for the injected string: user_name|role_1,role_2. User name is ignored. And roles are opendistro-roles.
threadContext.putTransient("opendistro_security_injected_roles", "bones|role_1,role_2"); The request will be evaluated using role1 and role2. These changes are similar to User injection functionality.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@skkosuri-amzn skkosuri-amzn requested a review from a team as a code owner July 14, 2020 06:15
@codecov
Copy link

codecov bot commented Jul 14, 2020

Codecov Report

Merging #560 into master will increase coverage by 0.10%.
The diff coverage is 86.11%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #560      +/-   ##
============================================
+ Coverage     63.15%   63.25%   +0.10%     
- Complexity     3050     3064      +14     
============================================
  Files           233      234       +1     
  Lines         16560    16592      +32     
  Branches       2989     2996       +7     
============================================
+ Hits          10458    10496      +38     
+ Misses         4551     4544       -7     
- Partials       1551     1552       +1     
Impacted Files Coverage Δ Complexity Δ
...lasticsearch/security/support/ConfigConstants.java 93.75% <ø> (ø) 5.00 <0.00> (ø)
...earch/security/privileges/PrivilegesEvaluator.java 65.64% <50.00%> (-0.26%) 74.00 <0.00> (+2.00) ⬇️
...oforelasticsearch/security/auth/RolesInjector.java 84.61% <84.61%> (ø) 7.00 <7.00> (?)
...arch/security/filter/OpenDistroSecurityFilter.java 72.18% <100.00%> (+4.21%) 48.00 <0.00> (+3.00)
...earch/security/resolver/IndexResolverReplacer.java 58.60% <0.00%> (+0.26%) 80.00% <0.00%> (+1.00%)
...security/configuration/DlsFlsFilterLeafReader.java 58.91% <0.00%> (+0.74%) 58.00% <0.00%> (ø%)
.../dlic/auth/ldap2/LDAPConnectionFactoryFactory.java 57.25% <0.00%> (+0.76%) 23.00% <0.00%> (ø%)
...ecurity/configuration/ConfigurationRepository.java 70.49% <0.00%> (+1.09%) 22.00% <0.00%> (ø%)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5be858c...295ee18. Read the comment docs.

@@ -100,7 +100,10 @@
public static final String OPENDISTRO_SECURITY_USER_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX+"user_header";

public static final String OPENDISTRO_SECURITY_INJECTED_USER = "injected_user";


public static final String OPENDISTRO_SECURITY_INJECTED_ROLES_ENABLED = "opendistro_security_injected_roles_enabled";
Copy link
Contributor

Choose a reason for hiding this comment

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

Please follow the common format for other settings (opendistro_security.enable_inject_roles)

@@ -961,6 +961,7 @@ public Settings additionalSettings() {
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered));
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered));
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false, Property.NodeScope, Property.Filtered));
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_ENABLED, true, Property.NodeScope, Property.Filtered));
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the risk of enabling role injection by default? If there is no risk, why is it necessary to introduce enable settings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed OPENDISTRO_SECURITY_INJECTED_ROLES_ENABLED, its enabled all the time.With that Settings are not required, removed.

public final User getUser() {
return user;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: new line

…he time.With that Settings are not required, removed.Refactored RolesInjector.java. Moved RolesInjectorPlugin class to the RolesInjectorPlugin. Added exception message for roles injection missing permissions.
@skkosuri-amzn
Copy link
Contributor Author

@vrozov addressed the comments and published revision.

public Set<String> injectUserAndRoles(final ThreadContext ctx) {

if(log.isDebugEnabled()){
log.debug("Injected role str: "+ ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES));
Copy link
Contributor

Choose a reason for hiding this comment

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

Use smart logging. Log after checking for null.

log.debug("Injected role str: "+ ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES));
}
String injectedStr = ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES);
if (Strings.isNullOrEmpty(injectedStr)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Empty string should probably be considered the way as malformed, only check for null is necessary.

if(log.isDebugEnabled()){
log.debug("Injected role str: "+ ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES));
}
String injectedStr = ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES);
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to injectedUserAndRoles

if (Strings.isNullOrEmpty(injectedStr)) {
return null;
}
User user = parseUser(injectedStr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can the string be parsed once?

log.error("Roles must not be null, injected string was '{}.' Roles injection failed.", injectedStr);
return null;
}
return new HashSet<>(Arrays.asList(strs[1].split(",")));
Copy link
Contributor

Choose a reason for hiding this comment

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

Use ImmutableSet.copyOf().

return;

threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS,
new TransportAddress(InetAddress.getLoopbackAddress(), 9300));
Copy link
Contributor

Choose a reason for hiding this comment

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

What if it is not a default port? Is it necessary to set remote address when roles are injected?

@skkosuri-amzn skkosuri-amzn force-pushed the master branch 2 times, most recently from 6119e1d to e9c0a0f Compare August 5, 2020 14:55
}

if(log.isDebugEnabled()){
log.debug("Injected roles: "+ injectedUserAndRoles);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: please use smart logging:

Suggested change
log.debug("Injected roles: "+ injectedUserAndRoles);
log.debug("Injected roles: {}", injectedUserAndRoles);

return null;
}

if(log.isDebugEnabled()){
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the check is not necessary with smart logging.

" Roles injection failed.", injectedUserAndRoles);
return null;
}
if (Strings.isNullOrEmpty(strs[0].trim())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: trim() does not return null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks.

listener.onFailure(new ElasticsearchSecurityException("no permissions for " + pres.getMissingPrivileges()+ " and "+user, RestStatus.FORBIDDEN));
String err = (injectedRoles == null) ?
String.format("no permissions for %s and %s" , pres.getMissingPrivileges(), user) :
String.format("no permissions for %s and associated roles %s ", pres.getMissingPrivileges(), injectedRoles);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: trim extra space in the error message.

"invalid",
"role_1,role_2",
" | ",
" "
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add "|" use case.

log.error("Roles must not be null, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);
return null;
}
Set<String> roles = new HashSet<>(ImmutableSet.copyOf(strs[1].split(",")));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Set<String> roles = new HashSet<>(ImmutableSet.copyOf(strs[1].split(",")));
Set<String> roles = ImmutableSet.copyOf(strs[1].split(","));

}

if (StringUtils.isEmpty(StringUtils.trim(strs[0]))) {
log.error("Username must not be null, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
log.error("Username must not be null, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);
log.error("Username must be provided, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);

User user = new User(strs[0]);

if (strs.length < 2 || StringUtils.isEmpty(StringUtils.trim(strs[0]))) {
log.error("Roles must not be null, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
log.error("Roles must not be null, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);
log.error("Roles must be provided, injected string was '{}.' Roles injection failed.", injectedUserAndRoles);

Copy link
Contributor

@vrozov vrozov left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -112,6 +114,7 @@ public OpenDistroSecurityFilter(final Settings settings, final PrivilegesEvaluat
this.compatConfig = compatConfig;
this.indexResolverReplacer = indexResolverReplacer;
this.immutableIndicesMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList()));
this.rolesInjector = new RolesInjector();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: This can be injected via constructor ? or the class function can be made static as it has no state (other than logger) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initially we had injected via constructor, then we separated to make it clean. Would prefer not to change this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants