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

[ResponseOps] Allow users authenticated with an API keys to manage alerting rules #154189

Merged
merged 21 commits into from
Apr 11, 2023

Conversation

doakalexi
Copy link
Contributor

@doakalexi doakalexi commented Mar 31, 2023

Resolves #152140

Summary

Updates the following functions in the Rules Client to re-use the API key in context and avoid having the system invalidate them when no longer in use:

  • bulk_delete
  • bulk_edit
  • clone
  • create
  • delete
  • update
  • update_api_key

Also adds a new field to the rule SO to help determine when whether an api key was created by a user or created by us.

Checklist

To verify

  • Follow these instructions to create an api key. Make sure to copy your api key
  • Run the following
curl -X POST "http://localhost:5601/api/alerting/rule/" -H 'Authorization: ApiKey ${API_KEY}' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d'
{
  "rule_type_id": "example.pattern",
  "name": "pattern",
  "schedule": {
    "interval": "5s"
  },
  "actions": [
  ],
  "consumer": "alerts",
  "tags": [],
  "notify_when": "onActionGroupChange",
  "params": {
    "patterns": {
      "instA": " a - - a "
    }
  }
}'
  • Verify that the request returns a rule with"api_key_created_by_user":true
  • Try this with the other rules clients functions listed above to verify that you can manage alerting rules when authenticated with an api key
  • Verify that "api_key_created_by_user":false when you remove the api key header and add -u ${USERNAME}:${PASSWORD} to authenticate

@doakalexi doakalexi changed the title Alerting/auth api key [ResponseOps] Allow users authenticated with an API keys to manage alerting rules Mar 31, 2023
@doakalexi doakalexi added Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v8.8.0 release_note:feature Makes this part of the condensed release notes labels Mar 31, 2023
@doakalexi doakalexi marked this pull request as ready for review April 3, 2023 12:45
@doakalexi doakalexi requested review from a team as code owners April 3, 2023 12:45
@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops (Team:ResponseOps)

Copy link
Member

@afharo afharo left a comment

Choose a reason for hiding this comment

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

SO changes LGTM. If not needed for search/aggregations, I'd avoid adding it to the mappings.

x-pack/plugins/alerting/server/saved_objects/mappings.ts Outdated Show resolved Hide resolved
@pmuellr
Copy link
Member

pmuellr commented Apr 5, 2023

Trying out the _update_api_key API, via the following, doesn't seem to work. Am I holding it wrong?

$ curl -X POST "http://localhost:5601/internal/alerting/rule/4f0a4ec0-d3cf-11ed-8173-855da63578db/_update_api_key" -H "Authorization: ApiKey ${API_KEY}" -H 'kbn-xsrf: true'
{"statusCode":400,"error":"Bad Request","message":"Error updating API key for rule: could not create API key - Unsupported scheme \"ApiKey\" for granting API Key"}

Seems like it should not have tried to create an API key in this case, but didn't look in the code to see what was going on ...

@doakalexi
Copy link
Contributor Author

doakalexi commented Apr 5, 2023

Trying out the _update_api_key API, via the following, doesn't seem to work. Am I holding it wrong?

$ curl -X POST "http://localhost:5601/internal/alerting/rule/4f0a4ec0-d3cf-11ed-8173-855da63578db/_update_api_key" -H "Authorization: ApiKey ${API_KEY}" -H 'kbn-xsrf: true'
{"statusCode":400,"error":"Bad Request","message":"Error updating API key for rule: could not create API key - Unsupported scheme \"ApiKey\" for granting API Key"}

Seems like it should not have tried to create an API key in this case, but didn't look in the code to see what was going on ...

Resolved in this commit 47d83e6

@pmuellr
Copy link
Member

pmuellr commented Apr 5, 2023

Still reviewing, no blockers yet, however there is one potentially big thing I'd like added. Doesn't neccessarily need to be in this PR, but I think we'd want to do it before 8.8.

More function tests. :-).

What I'd like to see is a bunch of the following scenarios:

  1. create a rule
  2. make sure it's running (whatever's easiest, maybe the get event log function)
  3. modify the rule with one of the methods changed in this PR
  4. make sure the shape is correct after the modification, and check if it's still running
  5. ideally, check to make sure any API keys in the scenario are queued for invalidation / still exist / etc, as expected - this might be hard, not sure where we store those queued-for-invalidation keys

I'd like to see this where we run ever combination of methods changed, where when we create the rule, and run the method, we use every combination of api-key / login. So, something like this:

for (const createType of ['api-key', 'login']) {
  for (const mutateType of ['api-key', 'login']) {
    for (const method of ['bulk_delete', 'bulk_edit', ... ]) {
      test(createType, mutateType, method);
    }
  }
}

@doakalexi
Copy link
Contributor Author

Still reviewing, no blockers yet, however there is one potentially big thing I'd like added. Doesn't neccessarily need to be in this PR, but I think we'd want to do it before 8.8.

More function tests. :-).

I created an issue to add functional tests #154584

Copy link
Member

@pmuellr pmuellr left a comment

Choose a reason for hiding this comment

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

Lots of comments, generally LGTM. I think the most critical comment is on transform_rule_for_export.test.ts, to add some more tests. If anything else makes sense and is easy, cool. I'm interested in getting this merged soon-ish so we can start hammering on it, so if anything is "too big", I think we can do in a follow-up PR.

createdAPIKey = shouldUpdateApiKey
? await context.createAPIKey(generateAPIKeyName(ruleType.id, attributes.name))
? isAuthTypeApiKey
? await context.getAuthenticationAPIKey(name)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like the name here is just added to the return object of getAuthenticationAPIKey(), not otherwise used. AFAIK, we don't display this name anywhere in alerting UX - I think it's primary use is when you are scrolling through the API Keys UX, for the keys we generate, so you can tell what rules they were for.

If so, I think this is fine.

I just don't want to confuse customers by suggesting there is an API key with that name. Maybe we should add something to the name though anyway, in this case. Could be useful while diagnosing problems ... like prefix/postfix with "(user-created)" or similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh okay that makes sense, I can add something like that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in this commit bc37396

@@ -91,6 +91,7 @@ describe('transform rule for export', () => {
enabled: false,
apiKey: null,
apiKeyOwner: null,
apiKeyCreatedByUser: null,
Copy link
Member

Choose a reason for hiding this comment

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

Feels like we should have a test for apiKeyCreatedByUser: true in here, to make sure it's getting exported as null (like the other api key-related properties).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in this commit bc37396

@@ -8,6 +8,7 @@
import { SavedObjectsTypeMappingDefinition } from '@kbn/core/server';

export const alertMappings: SavedObjectsTypeMappingDefinition = {
dynamic: false,
Copy link
Member

Choose a reason for hiding this comment

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

@XavierM I'm guessing you're adding this in your SO refactor PR; heads up that we're doing it here to add a new property that doesn't need to be indexed.

@@ -96,17 +96,22 @@ export async function clone<Params extends RuleTypeParams = never>(
const lastRunTimestamp = new Date();
const legacyId = Semver.lt(context.kibanaVersion, '8.0.0') ? id : null;
let createdAPIKey = null;
let isAuthTypeApiKey = false;
Copy link
Member

Choose a reason for hiding this comment

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

These blocks of code are very similar between the various client methods - wonder if there's anyway we could arrange to do most of this work in a common function, to cut down on the boilerplate. Even it it was just useful for most of the methods, would probably be useful ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in this commit bc37396

The only client method I didn't update was create.ts

context.logger,
context.unsecuredSavedObjectsClient
);
throw e;
}

if (apiKeyToInvalidate) {
if (apiKeyToInvalidate && !apiKeyCreatedByUser) {
Copy link
Member

Choose a reason for hiding this comment

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

can we rename these to have a "previous" or "old" prefix. My first thought on seeing these was - which "keyCreatedByUser"? It's not the newly created one, right? No, it's not, but I had to scroll to the top to see where these were assigned from the original rule. I think putting a prefix on them will keep me from wondering in the future :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in this commit bc37396

@@ -313,6 +353,21 @@ describe('bulkDelete', () => {
);
});

test('should not mark API keys for invalidation if the user is authenticated using an api key', async () => {
unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({
statuses: [{ id: 'id3', type: 'alert', success: true }],
Copy link
Member

Choose a reason for hiding this comment

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

can we add id1 and id2 here as well, ensure that they get passed to the bulkKeyInvalidation function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in this commit bc37396

const user = await securityPluginStart.authc.getCurrentUser(request);
return user && user.authentication_type ? user.authentication_type === 'api_key' : false;
},
async getAuthenticationAPIKey(name: string) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't believe either of these methods needs to be async, technically. I'm seeing a squiggle under the await for securityPluginStart.authc.getCurrentUser(request) indicating 'await' has no effect on the type of this expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh my bad, I will fix this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in this commit bc37396

Copy link
Contributor

@mohamedhamed-ahmed mohamedhamed-ahmed left a comment

Choose a reason for hiding this comment

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

Infra changes LGTM!

@doakalexi doakalexi enabled auto-merge (squash) April 11, 2023 13:18
@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
alerting 543 544 +1

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
alerting 44.5KB 44.6KB +67.0B
Unknown metric groups

API count

id before after diff
alerting 564 565 +1

ESLint disabled line counts

id before after diff
securitySolution 433 436 +3

Total ESLint disabled count

id before after diff
securitySolution 513 516 +3

History

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

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:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v8.8.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow users authenticated with an API keys to manage alerting rules
8 participants