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

Adding Global Access Agreement #139217

Merged
merged 35 commits into from
Sep 6, 2022

Conversation

kc13greiner
Copy link
Contributor

@kc13greiner kc13greiner commented Aug 22, 2022

Summary

Adding a config for a default, global access agreement message for all configured authc providers that do no have a local access agreement message

Release Notes

A default Access Agreement can now be set for all authentication providers at the xpack.security level

@kc13greiner kc13greiner added Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! release_note:feature Makes this part of the condensed release notes v8.5.0 ci:cloud-deploy Create or update a Cloud deployment labels Aug 22, 2022
@kc13greiner kc13greiner marked this pull request as ready for review August 22, 2022 18:29
@kc13greiner kc13greiner requested a review from a team as a code owner August 22, 2022 18:29
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-security (Team:Security)

@kc13greiner
Copy link
Contributor Author

@legrego @azasypkin Should the docs be updated to reflect this as well?

@legrego
Copy link
Member

legrego commented Aug 22, 2022

@kc13greiner yes, docs changes need to be done to teach our users about this new setting:

  • docs/user/security/access-agreement.asciidoc
  • docs/settings/security-settings.asciidoc

Additionally, we will need to add a corresponding changes to allow this new setting to be configured by end users within the Elastic Cloud console. We'll follow up with you separately about that.

@azasypkin
Copy link
Member

ACK: will review today

@legrego legrego removed their request for review August 24, 2022 13:03
Copy link
Member

@azasypkin azasypkin left a comment

Choose a reason for hiding this comment

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

Looks good! Just two questions.

@@ -237,6 +238,7 @@ export const ConfigSchema = schema.object({
}),
authc: schema.object({
selector: schema.object({ enabled: schema.maybe(schema.boolean()) }),
accessAgreement: schema.maybe(schema.object({ message: schema.string() })),
Copy link
Member

@azasypkin azasypkin Aug 24, 2022

Choose a reason for hiding this comment

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

question: I'm wondering if it would make sense to move accessAgreement one level above authc, like we did with global session since it's not strictly related to the authentication and it's fine if provider's config overrides any config, whether it's auth related or not?

What do you think @kc13greiner @legrego?

Copy link
Member

Choose a reason for hiding this comment

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

question: I'm wondering if it would make sense to move accessAgreement one level above authc, like we did with global session since it's not strictly related to the authentication and it's fine if provider's config overrides any config, whether it's auth related or not?

Hmm, that's a good question! I could make an argument either way, but since we have session at the global level, I think it would be slightly more consistent to move accessAgreement one level up.

I don't have a strong opinion here, so I'm happy to defer to you both.

(Also @/kurt should be @kc13greiner)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good to me! Ill move it up

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me! Ill move it up

Awesome, thanks!

(Also @/kurt should be @kc13greiner)

😬 ugh

for (const [type, providerGroup] of Object.entries(providers)) {
for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) {
const hasLocalAccessAgreement: boolean = !!accessAgreement?.message;
Copy link
Member

Choose a reason for hiding this comment

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

question: wouldn't it be easier (implementation/readability) if we keep global and provider-specific accessAgreement configs separate and just slightly update Authenticator.shouldRedirectToAccessAgreement and /security/access_agreement route handler to account for the global access agreement message if provider doesn't have any? It'd also allow us to diverge these configs in the future if we want to (e.g. we can restrict global access agreement settings that providers can or cannot override, like we did for xpack.session.cleanupInterval).

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, good call! Ill make the change

@@ -814,20 +815,26 @@ export class Authenticator {
*/
private shouldRedirectToAccessAgreement(sessionValue: SessionValue | null) {
// Request should be redirected to Access Agreement UI only if all following conditions are met:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@azasypkin Can you help me clean up the comments in this function?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, let me try:

// Request can be redirected (not API call)
// And it's not a request to the Access Agreement UI itself

We can remove these two - since we check for canRedirectRequest and access agreement URL before we invoke this function.

// If user doesn't have an active session or if they already acknowledged
// access agreement (based on the flag we store in the session) - bail out.
if (sessionValue == null || sessionValue.accessAgreementAcknowledged) {
  return false;
}

// If access agreement is neither enabled globally (for all providers)
// nor for the provider that authenticated user request - bail out.
const providerConfig = this.options.config.authc.providers as Record<string, any>)[
  sessionValue.provider.type
]?.[sessionValue.provider.name];
if (!this.options.config.accessAgreement && !providerConfig?.accessAgreement) {
  return false;
}

// Check if the current license allows access agreement.
return this.options.license.getFeatures().allowAccessAgreement;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perfect! Fixed in latest commit.

@@ -343,6 +343,7 @@ export class AuthenticationService {
license: this.license,
session,
isElasticCloudDeployment,
accessAgreement: { message: config.accessAgreement?.message },
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think you can just add accessAgreement property directly to the config argument above (we do the same with authc).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in latest commit!

@@ -814,20 +815,26 @@ export class Authenticator {
*/
private shouldRedirectToAccessAgreement(sessionValue: SessionValue | null) {
// Request should be redirected to Access Agreement UI only if all following conditions are met:
Copy link
Member

Choose a reason for hiding this comment

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

Sure, let me try:

// Request can be redirected (not API call)
// And it's not a request to the Access Agreement UI itself

We can remove these two - since we check for canRedirectRequest and access agreement URL before we invoke this function.

// If user doesn't have an active session or if they already acknowledged
// access agreement (based on the flag we store in the session) - bail out.
if (sessionValue == null || sessionValue.accessAgreementAcknowledged) {
  return false;
}

// If access agreement is neither enabled globally (for all providers)
// nor for the provider that authenticated user request - bail out.
const providerConfig = this.options.config.authc.providers as Record<string, any>)[
  sessionValue.provider.type
]?.[sessionValue.provider.name];
if (!this.options.config.accessAgreement && !providerConfig?.accessAgreement) {
  return false;
}

// Check if the current license allows access agreement.
return this.options.license.getFeatures().allowAccessAgreement;

let accessAgreement: string = '';

if (sessionValue) {
const localAccessAgreement =
Copy link
Member

Choose a reason for hiding this comment

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

optional nit: we used "global vs provider-specific" terminology in the past, but I'm happy to change that if you think "local" makes more sense.

const accessAgreementMessage = providerSpecificAccessAgreement?.message ??
 globalAccessAgreement?.message ?? '';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

Copy link
Member

@azasypkin azasypkin left a comment

Choose a reason for hiding this comment

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

Looks great, just two more minor questions and I think we're good to go!

@@ -235,6 +236,7 @@ export const ConfigSchema = schema.object({
hostname: schema.maybe(schema.string({ hostname: true })),
port: schema.maybe(schema.number({ min: 0, max: 65535 })),
}),
accessAgreement: schema.maybe(schema.object({ message: schema.maybe(schema.string()) })),
Copy link
Member

Choose a reason for hiding this comment

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

question: is there any value in making message optional (schema.maybe) in addition to already optional accessAgreement top-level property? It feels like message would be always required if access agreement is required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe I have it how you're recommending, please double check I got it right!

Comment on lines 357 to 370
const hasProviderSpecificAccessAgreement: boolean = !!accessAgreement?.message;
const hasGlobalAccessAgreement: boolean = !!config.accessAgreement?.message;

if (!enabled) {
delete providerGroup![name];
} else {
const hasAccessAgreement: boolean =
hasProviderSpecificAccessAgreement || hasGlobalAccessAgreement;

sortedProviders.push({
type: type as any,
name,
order,
hasAccessAgreement: !!accessAgreement?.message,
hasAccessAgreement,
Copy link
Member

Choose a reason for hiding this comment

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

question: do we really need this change? I'm just trying to reduce changes only to those that are relevant to the change we'd like to make and are absolutely necessary (makes reviewing much easier), and it looks like we only need change in line 239 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The security_usage_collector.ts references that field. Do you think the change would be better there or should I remove it?

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 didn't realize that we introduced that field just for usage collector! I'd still make this check directly in the security_usage_collector.ts (the place that actually needs these two settings to be merged to a single accessAgreementEnabled field), but since hasAccessAgreement is used solely for the usage collector feel free to pick the option you like the most.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ill make the change in the collector!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Member

@azasypkin azasypkin left a comment

Choose a reason for hiding this comment

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

LGTM, great job!

for (const [type, providerGroup] of Object.entries(providers)) {
for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) {
if (!enabled) {
delete providerGroup![name];
} else {
const hasAccessAgreement: boolean = !!accessAgreement?.message;
Copy link
Member

Choose a reason for hiding this comment

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

optional nit: boolean is inferred automatically.

Suggested change
const hasAccessAgreement: boolean = !!accessAgreement?.message;
const hasAccessAgreement = !!accessAgreement?.message;

@azasypkin
Copy link
Member

Sorry @kc13greiner, I forgot to leave a note during review: would you mind allowing this new setting in our docker image (setting needs to be included in https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)?

@kc13greiner kc13greiner requested a review from a team as a code owner September 1, 2022 15:43
Copy link
Member

@jbudz jbudz left a comment

Choose a reason for hiding this comment

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

kibana-docker

@kc13greiner
Copy link
Contributor Author

@gchaps I think this PR is finally ready - do the docs changes look alright?

Copy link
Contributor

@gchaps gchaps left a comment

Choose a reason for hiding this comment

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

A few minor comments but otherwise LGTM.

@@ -162,6 +162,15 @@ Adds a message accessible at the login UI with additional help information for t
xpack.security.authc.selector.enabled {ess-icon}::
Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured.

[float]
[[authentication-access-agreement-settings]]
==== Configure a default access agreement
Copy link
Contributor

Choose a reason for hiding this comment

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

Other sections include this as a lead-in sentence:

You can configure the following settings in the kibana.yml file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oo! I'll add that in, thanks!!

docs/user/security/access-agreement.asciidoc Outdated Show resolved Hide resolved
docs/user/security/access-agreement.asciidoc Outdated Show resolved Hide resolved
@kc13greiner kc13greiner removed the ci:cloud-deploy Create or update a Cloud deployment label Sep 6, 2022
@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@kc13greiner kc13greiner merged commit c6fb0bc into elastic:main Sep 6, 2022
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Sep 6, 2022
@kc13greiner kc13greiner deleted the feature/global_access_agreement branch September 6, 2022 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting release_note:feature Makes this part of the condensed release notes Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! v8.5.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a Kibana setting that applies an access agreement to all realms/providers
8 participants