Skip to content

Conversation

@cwperks
Copy link
Member

@cwperks cwperks commented May 15, 2023

Description

Companion PR in Core: opensearch-project/OpenSearch#7573
Companion PR in Job Scheduler: opensearch-project/job-scheduler#394

This PR implements one of the methods introduced in a new interface in core (See companion PR) around Scheduled Job Identity Management. This PR shows an implementation of saveUserDetails which is present on an interface called ScheduledJobIdentityManager. This method can be called within Job Scheduler to save user info into an index owned by the security plugin that tracks user info alongside the job_id and job_index which is a reference to where the scheduled job details are stored in indices owned by respective plugins that extend job scheduler.

With this security model, plugins and extensions will not need to store user information alongside job details as many plugins do today. With this model, an Identity plugin can implement 3 methods:

  1. ScheduledJobIdentityManager.saveUserDetails(String jobId, String jobIndex)
  2. ScheduledJobIdentityManager.deleteUserDetails(String jobId, String jobIndex)
  3. ScheduledJobIdentityManager.issueAccessTokenOnBehalfOfUser(String jobId, String jobIndex)

to implement scheduled job security. For the security plugin, that will mean creating a new protected index (.opendistro-security-scheduled-job-identity in this Draft PR) and keeping a full catalog of all registered scheduled jobs (jobId and jobIndex as compound key for referential integrity) and the User information of the user that owns the job.

  • Category (Enhancement, New feature, Bug fix, Test fix, Refactoring, Maintenance, Documentation)

New Feature

Issues Resolved

#2528
#2626

Is this a backport? If so, please add backport PR # and/or commits #

Check List

  • New functionality includes testing
  • New functionality has been documented
  • Commits are signed per the DCO using --signoff

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.

cwperks added 10 commits May 10, 2023 22:07
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
…ross indices

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@cwperks
Copy link
Member Author

cwperks commented May 15, 2023

@peternied I'm curious on your thoughts to this approach for scheduled job security - this approach offloads the responsibility of storing user info on a plugin/extension.

Signed-off-by: Craig Perkins <cwperx@amazon.com>
cwperks added 2 commits May 16, 2023 12:05
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@cwperks
Copy link
Member Author

cwperks commented May 16, 2023

@joshpalis Tagging to keep you in the loop. This is a Draft PR that shows the approach to scheduled job security for extensions. It would be useful for plugins today as well and offloads plugins needing to keep track of user info themselves.

cwperks added 2 commits May 16, 2023 13:44
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
} else if (totalHits == 1) {
logger.info("Scheduled Job Identity already exists in " + SCHEDULED_JOB_IDENTITY_INDEX + " for job with jobId " + jobId);
} else {
final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
Copy link
Member Author

Choose a reason for hiding this comment

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

@davidlago Made a good comment that if an extension/plugin is writing to its Jobs Index that the user in the threadcontext may not be the right user to persist for the job identity index. I'm looking into ways that the correct user information can be provided.

Copy link
Member

@DarshitChanpura DarshitChanpura left a comment

Choose a reason for hiding this comment

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

took a first pass and left some comments.

private final String indexName;
private final String mapping;

SecurityIndex(String name, Supplier<String> mappingSupplier) {
Copy link
Member

Choose a reason for hiding this comment

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

Why use Supplier here? Asking because we are just returning a String and not a custom class object

// keep track of whether the mapping version is up-to-date
private EnumMap<SecurityIndex, IndexState> indexStates;

class IndexState {
Copy link
Member

Choose a reason for hiding this comment

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

Could you please explain the purpose of this class?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is based off some similar code in AD. I will take a deeper dive and reply back with findings.

if (createdResponse.isAcknowledged()) {
IndexState indexState = indexStates.computeIfAbsent(index, IndexState::new);
if (Boolean.FALSE.equals(indexState.mappingUpToDate)) {
indexState.mappingUpToDate = Boolean.TRUE;
Copy link
Member

Choose a reason for hiding this comment

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

Why set it to true if it was false? Asking because i'm not sure where are we updating the mapping

cwperks added 6 commits May 23, 2023 15:15
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Copy link
Member

@peternied peternied left a comment

Choose a reason for hiding this comment

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

Nice work getting Security to support Identity!

}
final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
if (user == null) {
return NamedPrincipal.UNAUTHENTICATED;
Copy link
Member

Choose a reason for hiding this comment

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

When there is no user in the thread context isn't this when we are running as 'root' in OpenSearch? I really like that we'd be patching this hole, but want to double check there aren't implications

Copy link
Member Author

@cwperks cwperks Jun 6, 2023

Choose a reason for hiding this comment

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

That's partially true, what really defines superuser is:

  1. Lack of User in the threadcontext
  2. Lack of Origin in the threadcontext or Origin === LOCAL
  3. No injected roles

See https://github.com/opensearch-project/security/blob/main/src/main/java/org/opensearch/security/filter/SecurityFilter.java#L258-L266 - this is the block that skips PrivilegeEvaluation after a plugin stashes its threadcontext and executes a TransportAction

I think you are correct though that if a plugin calls on getSubject() and there is no User in the threadcontext that that would mean that the plugin is in superuser mode.

Copy link
Member

Choose a reason for hiding this comment

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

While I don't like the behavior of falling back to superuser on a null, I think we should keep the relationship subject <-> security user consistent otherwise we might have logging that says the user is unauthenticated alongside system-only changes.

Note; I think changing the default behavior would be very good for the security posture, but we would likely need to make this a breaking change ala 3.0.

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 about None since that's technically the case? If the threadcontext is stashed then there is no subject. Contrast that to doing clientcert auth with a certificate that matches an admin_dn configured in opensearch.yml. That would truly be the root user.

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'm fine with either approach - I had to implement the IdentityPlugin as part of this PR so this code is tangentially related to the meat of the PR

Copy link
Member

Choose a reason for hiding this comment

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

While tangentially related there are a lot of implications of these values that can create confusion, if you'd prefer not to address this now, how about a follow up issue?

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed this to ROOT and introduced that NamedPrincipal in the companion PR for core.

Working on providing those data flow diagrams now for job creation, job execution and job deletion.

cwperks added 3 commits June 6, 2023 13:55
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Copy link
Collaborator

@RyanL1997 RyanL1997 left a comment

Choose a reason for hiding this comment

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

Hi @cwperks, thanks for putting this together! According to this comment, I would like to ask some of the general questions about this approach for information storage.

private final Instant lastUpdateTime;
private final User user;

public ScheduledJobIdentity(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Refer to the idea of information storage, is this the identity of the job? (including all the basic informations). I'm asking this because I was thinking, if we create a token management index, what kinds of information we should store? Should it be some general information from the claims? (such as, iat, default exp or the token itself?)

cwperks added 12 commits June 20, 2023 11:12
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@cwperks
Copy link
Member Author

cwperks commented Jul 27, 2023

Closing for now. Will re-open at a later date with updates.

@cwperks cwperks closed this Jul 27, 2023
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.

4 participants