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

Make order setting mandatory for Realm config #51195

Merged
merged 30 commits into from
Jan 28, 2020

Conversation

ywangd
Copy link
Member

@ywangd ywangd commented Jan 19, 2020

The order parameter must be explicitly specified for each realm.
This is a breaking change and will start take effect in 8.0

Resolves: #37614

ywangd and others added 3 commits January 18, 2020 00:02
@ywangd ywangd requested a review from tvernum January 19, 2020 13:29
@ywangd
Copy link
Member Author

ywangd commented Jan 19, 2020

@tvernum This PR is still WIP. But I'd like to get a PR in early its easier for you to review and set me on the right track.

Current changes are focused on making the order parameter mandatory and fixing relevant tests. Others things remaining are:

  • No two realm should have the same order
  • Warning in 7.x
  • Docs update
  • Breaking change update

@ywangd
Copy link
Member Author

ywangd commented Jan 19, 2020

@elasticmachine update branch

@ywangd
Copy link
Member Author

ywangd commented Jan 20, 2020

@elasticmachine run elasticsearch-ci/2

this(identifier, settings, env, threadContext, null);
}

public RealmConfig(RealmIdentifier identifier, Settings settings, Environment env, ThreadContext threadContext, Integer order) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better for this constructor to be protected, and update the tests to pass through real Settings object with an order config.

I know that's going to be a pain - so please feel free to convince me if you feel otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you suggesting the caller should create a Settings object that actually includes an order? In this case, this constructor is no longer necessary? Am I getting this right?

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 initially actual prefer to not adding another constructor. But the number of call site changes made me change my mind. But I am happy to drop the constructor and make more call site changes if you favour this approach. Please let me know. Thanks

@ywangd
Copy link
Member Author

ywangd commented Jan 20, 2020

@tvernum I plan to go ahead with following changes. Please let me know if this works for you:

  • Drop the new constructor. The only constructor will mandate that the Settings object has an order setting.
  • Change relevant call sites in tests to always add order into Settings.
    Personally I think above approach works better since it does not change production code to cater tests.

To ensure no two realms having the same order, it seems that some logic needs to be added in Security.java. But shall we enforce this requirement to builtin realms (file, index) that are added automatically? Currently they are both added with default order of Integer.MAX_VALUE. It does not seem to be very meaningful to have different order values for them.

@ywangd
Copy link
Member Author

ywangd commented Jan 20, 2020

Just realised that the Settings object is also referenced from the Environment object. The following code snippet shows how we add default file realm:

realms.add(fileRealm.create(new RealmConfig(
      new RealmConfig.RealmIdentifier(FileRealmSettings.TYPE, "default_" + FileRealmSettings.TYPE),
      settings, env, threadContext)));

To add the order setting, above code would change to something like the followings:

var realmIdentifier = new RealmConfig.RealmIdentifier(FileRealmSettings.TYPE, "default_" + FileRealmSettings.TYPE);
realms.add(fileRealm.create(new RealmConfig(
      realmIdentifier,
     Settings.builder().put(settings)
           .put(RealmSettings.realmSettingPrefix(realmIdentifier) + "order", Integer.MIN_VALUE).build(),
     env, threadContext)));

Note that a new Settings object is created with the addition of order setting. But the env object still references old Settings object. Will this create a problem somewhere else in the code path?

Add tests for mandatory order setting of RealmConfig.
Fix broken tests due to unique order setting requirement.
@ywangd
Copy link
Member Author

ywangd commented Jan 20, 2020

@tvernum I would like to change my view on this again ... Sorry for the back and forth. I kept the new constructor but changed the order parameter to be of a primitive int type. The Constructor remains public as it is used by Realms and ReservedRealm.

A few reasons to go with this approach:

  • The mandatory order setting is not 100% strict, because native realms can be added without any settings. So currently it feels we have an escape hatch for this rule, which seems to make sense for another constructor.
  • When default native realms are added, they don't alter the existing Settings object. I'd like to follow this pattern.
  • The Settings object is referenced in multiple places, Realms, Environment, every realm and likely more. I am unsure what would be the consequence if they each reference a different Settings object (mostly the same content, but distinct object).
  • This approach is likely more extensible if we ever wanna make another setting mandatory or some other requirement, e.g. it could refactor to replace the int parameter with a value provider.

Other changes I have pushed are:

@ywangd ywangd requested a review from tvernum January 20, 2020 12:24
@ywangd
Copy link
Member Author

ywangd commented Jan 21, 2020

Tried to identify all places in docs that are relevant to the order setting. To my surprise, not many places need update. Here is a list of updated files for review convenience:

@ywangd
Copy link
Member Author

ywangd commented Jan 21, 2020

@elasticmachine update branch

@cbuescher cbuescher added the :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) label Jan 21, 2020
@elasticmachine
Copy link
Collaborator

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

Copy link
Contributor

@albertzaharovits albertzaharovits left a comment

Choose a reason for hiding this comment

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

I've done a first pass.
I appreciate the effort involved, but it's one of those cases where I would've made a different choice.

But I think this works and I'll LGTM when I get to look a second time.

docs/reference/migration/migrate_8_0/security.asciidoc Outdated Show resolved Hide resolved
this.enabled = getSetting(RealmSettings.ENABLED_SETTING);
if (hasSetting(RealmSettings.ORDER_SETTING.apply(type())) == false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would've probably made a different choice of where to place this check because we pass RealmConfigs around in too many places. For example, this:

  • will throw an exception in InternalRealms.getBootstrapChecks so the stacktrace for it is funky.
  • will throw an exception in the SamlMetadataCommand which is not nice, although it might not happen in practice too often.

I would've probably removed the order and enabled local vars and do the checks in Realms#initRealms. But there might be subtle problems in that case as well.

Nevertheless, this does the job, and looks OK, to me.

Nit: we use the reverse form for negative condition, i.e. false == <function> (unless we are in some few older files that use a different convention in which case we blend with the background).

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 am also tempted to move the logic into Reamls#initRealms (see below) comments for details. But your examples show that by itself is not sufficient. Both of the two examples are not covered if the logic is just in Realms#initRealms, while they do get covered if the logic is in RealmConfig. I am now again unsure about what would be the better approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I've not made myself clear

will throw an exception in InternalRealms.getBootstrapChecks so the stacktrace for it is funky.

I mean that there is a minor inconvenience that the validation that the order parameter is defined is performed inside a different code than the code that deals with realm construction.

this.threadContext = threadContext;
this.enabled = getSetting(RealmSettings.ENABLED_SETTING);
this.order = order;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not to dwell on it too much, but just because we need this new constructor is a smell. In this case, the order local var is sometimes the value from the settings, and sometimes a different one passed by this constructor. I know this is only used in tests, but it's still not nice to have to deal with it (Integer.MAX_VALUE).

Copy link
Member Author

Choose a reason for hiding this comment

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

The new constructor is mostly used by test code, but it is also used by production code, see Realms and ReservedRealm.

I also think adding a new constructor is not ideal due to some of the concerns you mention. It however seems to be a better choice comparing to the other choice, i.e. Add order into the settings parameter on caller side so that it can just keep using the existing constructor.

This approach should work fine for tests. But in production code, the settings object is referenced in many other classes, e.g. Environment, Realms, etc. So when adding FileRealm (in Realms.java), if we modify the settings object to add order, we will need to do something like the follows:

var newSettings = Settings.Builder().put(settings).build();
... new RealmConfig(..., newSetings, ...), ...;

So the newSettings is now passed into RealmConfig which will store a reference to it. Now we have two Settings object across the application. They are mostly identical, only differs in the order parameter. And this is just FileRealm, the same applies to native realm, for which we will have a third settings. I personally think this is worse than adding a new constructor. That is why the new constructor is added so we can avoid manipulating the settings object.

With above being said, I realise it is making an assumption that the logic for mandating the order setting resides in the RealmConfig class. If we can move away from this assumption, like you suggested, enforce the logic in Realms.initRealms, we can probably make the change set much smaller.

Initially I thought about adding the logic into Security but that was too high level. So we decided to add it in RealmConfig itself, where it is low level the logic is close to its natural owner. But maybe we need something in between. Realms could be the right candidate especially it is where the order uniqueness is enforced.

Copy link
Contributor

Choose a reason for hiding this comment

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

My choice would also be to only inspect the order setting inRealms#initRealms (where the order value duplication is checked). That's because the order is only really used in that place, specifying the initial order in the realm chain (which would then change as we reorder the chain during authentications), so it's not suitable as a final local variable.
This way we would also avoid having to specify the order parameter for the "default" realms (file and native) which is simply ignored anyways.

Your approach works as well and I can't argue strongly against it, so this LGTM to me even though I would've approached it differently.

@ywangd
Copy link
Member Author

ywangd commented Jan 24, 2020

Following discussion with Tim, I have made the following changes:

  • Removed the new constructor of RealmConfig
  • Still kept the logic of checking mandatory order setting in RealmConfig. I think this class is still overall the better place for enforcing the check because it is the choke point for many different upstream use cases, including some command line tools. So we will have a consistent report on the missing order setting no matter what the entry point is.
  • Since RealmConfig no longer has a special constructor for handling missing order setting, it is now the caller's responsibility to ensure this setting exists for reserved, file and native realms (when they are added automatically). I am aware that this approach creates new settings object along the way. This is however an existing issue in other places. A separate issue (Move configuration of special realms to additionalSettings #51387) has been created to tackle it.
  • The approach to ensure uniqueness of realm orders remains the same (i.e. in Realms#initRealms)
  • Again most of the changes are for test classes (making sure each of them has an order setting configured).

Copy link
Contributor

@albertzaharovits albertzaharovits left a comment

Choose a reason for hiding this comment

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

LGTM, thank you Yang!

`order` attribute to control the order in which the realms are consulted during
the `url` and `order` of the LDAP server, and set `user_search.base_dn` to the
container DN where the users are searched for.
The `order` attribute to control the order in which the realms are consulted during
authentication. See <<ref-ldap-settings>> for all of the options you can set for
Copy link
Contributor

Choose a reason for hiding this comment

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

This last phrase doesn't make sense, I would avoid describing again what order does.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do thanks

@@ -73,7 +72,7 @@ realms you specify are used for authentication. If you also want to use the
.. Add a realm configuration to `elasticsearch.yml` in the
`xpack.security.authc.realms.ldap` namespace. At a minimum, you must specify
the `url` of the LDAP server, and specify at least one template with the
`user_dn_templates` option. If you are configuring multiple realms, you should
`user_dn_templates` option. If you are configuring multiple realms, you must
also explicitly set the `order` attribute to control the order in which the
realms are consulted during authentication.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would remove

If you are configuring multiple realms, you must ...

altogether.

Copy link
Member Author

Choose a reason for hiding this comment

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

Somehow missed this occurrence ... Will remove. Thanks

@albertzaharovits
Copy link
Contributor

@ywangd I just remembered, we need to follow-up this PR with a another one that adds a deprecation check in https://github.com/elastic/elasticsearch/blob/7.x/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/NodeDeprecationChecks.java . This should inform users that realm definition sections without the order parameter are invalid. The PR should be based against the 7.x branch (these checks do not exist on master).

@ywangd
Copy link
Member Author

ywangd commented Jan 27, 2020

@albertzaharovits Warning/Deprecation message for 7.x is on my to-do list. Thanks for pointing me to the right file to start with.

ywangd added a commit to ywangd/elasticsearch that referenced this pull request Jan 28, 2020
Issue warnings for both missing realm order and duplicated realm orders.
The changes are to help users prepare for migration to next major
release.
Copy link
Contributor

@tvernum tvernum left a comment

Choose a reason for hiding this comment

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

LGTM

@ywangd ywangd merged commit 83a819a into elastic:master Jan 28, 2020
ywangd added a commit that referenced this pull request Jan 31, 2020
The changes are to help users prepare for migration to next major
release (v8.0.0) regarding to the break change of realm order config.

Warnings are added for when:
* A realm does not have an order config
* Multiple realms have the same order config

The warning messages are added to both deprecation API and loggings.
The main reasons for doing this are: 1) there is currently no automatic relay
between the two; 2) deprecation API is under basic and we need logging
for OSS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>breaking :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) v8.0.0-alpha1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Require order setting for each realm
7 participants