Skip to content

[Connector] Adding internal route for requesting ad-hoc ServiceNow access token#131171

Merged
ymao1 merged 44 commits intoelastic:mainfrom
ymao1:connectors/servicenow-token-route
May 6, 2022
Merged

[Connector] Adding internal route for requesting ad-hoc ServiceNow access token#131171
ymao1 merged 44 commits intoelastic:mainfrom
ymao1:connectors/servicenow-token-route

Conversation

@ymao1
Copy link
Copy Markdown
Contributor

@ymao1 ymao1 commented Apr 28, 2022

Towards #130306

Summary

Addresses item 6 from #130306: Expose new actions API HTTP route, which allow to fetch an access token for not saved connectors.

This PR adds an internal route to request an ad-hoc access token from a ServiceNow instance without requiring a saved connector. This allows the Create ServiceNow connector UI to request information from the api/x_elas2_inc_int/elastic_api/health endpoint during the ServiceNow connector creation process.

To Verify

  1. Run this PR and submit the following POST request:
POST https://localhost:5601/internal/actions/connector/_servicenow_access_token
{
  "apiUrl": "https://ven04334.service-now.com/",
  "config": {
    "clientId": <clientId>,
    "userIdentifierValue": "elastic_integration",
    "jwtKeyId": <jwtKeyId>
  },
  "secrets": {
    "clientSecret": <clientSecret>,
    "privateKey": <privateKey>
  }
}
  1. Verify you get an access token in response in the form: Bearer <accessToken>

Checklist

ymao1 added 27 commits April 21, 2022 12:17
…ationBase and ExternalIncidentServiceSecretConfiguration
… Still need to update unit tests for services
@ymao1 ymao1 changed the title Connectors/servicenow token route [Connector] Adding internal route for requesting ad-hoc ServiceNow access token Apr 28, 2022
@mikecote mikecote self-requested a review May 4, 2022 14:46
Copy link
Copy Markdown
Contributor

@mikecote mikecote left a comment

Choose a reason for hiding this comment

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

I finished my review, the overall structure looks great but I have two questions before giving it a 👍

Comment thread x-pack/plugins/actions/server/routes/get_oauth_access_token.ts Outdated
Comment thread x-pack/plugins/actions/server/routes/get_oauth_access_token.ts Outdated
@jportner
Copy link
Copy Markdown
Contributor

jportner commented May 5, 2022

@jportner We are adding this internal endpoint for generating ad-hoc access tokens for querying the health API for ServiceNow instances. Wanted to make sure @elastic/kibana-security was aware and wondering if there are any concerns y'all might have.

Thanks for the ping, I'll review today

@jportner jportner self-requested a review May 5, 2022 15:35
Copy link
Copy Markdown
Contributor

@jportner jportner left a comment

Choose a reason for hiding this comment

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

I responded to one of Mike's comments, other than that I don't have any concerns about this PR.

I think the overall approach makes sense 👍

There's an SSRF risk here, but I think it's pretty minimal, with the combination of:

  • Authorization checks (as suggested in one of the comments above)
  • The optional hostname allow-list for connectors
  • Additional URL validation (as suggested in my comment below)
  • The inability for users to specify arbitrary parameters with the token request
  • The logic which does not return the response body to users, it parses the response from the remote server and only returns the access token to users

In reviewing this, I noticed a couple small problems outside of the scope of this PR:

1. URL validation

Here's the implementation for the tokenUrl validation:

function isAllowed({ allowedHosts }: ActionsConfig, hostname: string | null): boolean {
const allowed = new Set(allowedHosts);
if (allowed.has(AllowedHosts.Any)) return true;
if (hostname && allowed.has(hostname)) return true;
return false;
}
function isHostnameAllowedInUri(config: ActionsConfig, uri: string): boolean {
return pipe(
tryCatch(() => url.parse(uri)),
map((parsedUrl) => parsedUrl.hostname),
mapNullable((hostname) => isAllowed(config, hostname)),
getOrElse<boolean>(() => false)
);
}

This code ignores network-path references (see URL Confusion Vulnerabilities). In other words, a URL of //my-domain.com/foo would be parsed and the validation function would treat as if it has no hostname, but it actually has a hostname of my-domain.com (in terms of how HTTP clients typically behave).

Fortunately the validation function fails securely; if an allow-list is configured and a URL doesn't contain a hostname, it fails to validate. However, I think you should change the usage of url.parse to include the slashesDenoteHost parameter:

diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts
index 35e08bb5cfe..49f1d1fd544 100644
--- a/x-pack/plugins/actions/server/actions_config.ts
+++ b/x-pack/plugins/actions/server/actions_config.ts
@@ -76,7 +76,7 @@ function isAllowed({ allowedHosts }: ActionsConfig, hostname: string | null): bo
 
 function isHostnameAllowedInUri(config: ActionsConfig, uri: string): boolean {
   return pipe(
-    tryCatch(() => url.parse(uri)),
+    tryCatch(() => url.parse(uri, false /* parseQueryString */, true /* slashesDenoteHost */)),
     map((parsedUrl) => parsedUrl.hostname),
     mapNullable((hostname) => isAllowed(config, hostname)),
     getOrElse<boolean>(() => false)

2. JWT assertions

The code to create JWT assertions allows for consumers to specify custom claims that would override the existing claims (subject, audience, issuer, etc.):

export function createJWTAssertion(
logger: Logger,
privateKey: string,
privateKeyPassword: string | null,
reservedClaims: JWTClaims,
customClaims?: Record<string, string>
): string {
const { subject, audience, issuer, expireInMilliseconds, keyId } = reservedClaims;
const iat = Math.floor(Date.now() / 1000);
const headerObj = { algorithm: 'RS256' as Algorithm, ...(keyId ? { keyid: keyId } : {}) };
const payloadObj = {
sub: subject, // subject claim identifies the principal that is the subject of the JWT
aud: audience, // audience claim identifies the recipients that the JWT is intended for
iss: issuer, // issuer claim identifies the principal that issued the JWT
iat, // issued at claim identifies the time at which the JWT was issued
exp: iat + (expireInMilliseconds ?? 3600), // expiration time claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing
...(customClaims ?? {}),
};

It doesn't appear that this customClaims option is being used anywhere. I'm not sure why it was added, but IMO if you want to keep it you should ensure that it can't be used as a vector to overwrite the other claims:

diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.ts
index b33a2d17ed9..2fcb17c1e18 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.ts
@@ -29,12 +29,12 @@ export function createJWTAssertion(
   const headerObj = { algorithm: 'RS256' as Algorithm, ...(keyId ? { keyid: keyId } : {}) };
 
   const payloadObj = {
+    ...(customClaims ?? {}),
     sub: subject, // subject claim identifies the principal that is the subject of the JWT
     aud: audience, // audience claim identifies the recipients that the JWT is intended for
     iss: issuer, // issuer claim identifies the principal that issued the JWT
     iat, // issued at claim identifies the time at which the JWT was issued
     exp: iat + (expireInMilliseconds ?? 3600), // expiration time claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing
-    ...(customClaims ?? {}),
   };
 
   try {

This will prevent us from accidentally introducing a vulnerability in the future 😄

Comment thread x-pack/plugins/actions/server/routes/get_oauth_access_token.ts Outdated
@ymao1
Copy link
Copy Markdown
Contributor Author

ymao1 commented May 6, 2022

@jportner Thanks for reviewing! I've pushed changes based on your feedback:

1. URL validation

Added slashesDenoteHost in this commit: 769176f

2. JWT assertions

Removed ability to specify customClaims in this commit: b02b197. As you said, we are not currently using this so it makes sense to remove and re-add if the need ever comes up in the future.

@ymao1 ymao1 requested review from jportner and mikecote May 6, 2022 17:24
Copy link
Copy Markdown
Contributor

@mikecote mikecote left a comment

Choose a reason for hiding this comment

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

Changes LGTM!

Copy link
Copy Markdown
Contributor

@jportner jportner left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for the quick turnaround!

@ymao1 ymao1 enabled auto-merge (squash) May 6, 2022 17:35
@kibana-ci
Copy link
Copy Markdown

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #5 / analytics instrumented events from the browser Core Context Providers should have the properties provided by the "license info" context provider

Metrics [docs]

Public APIs missing exports

Total count of every type that is part of your API that should be exported but is not. This will cause broken links in the API documentation system. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats exports for more detailed information.

id before after diff
actions 11 12 +1

History

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

cc @ymao1

@ymao1 ymao1 merged commit a6f7c46 into elastic:main May 6, 2022
@kibanamachine kibanamachine added the backport:skip This PR does not require backporting label May 6, 2022
@ymao1 ymao1 deleted the connectors/servicenow-token-route branch May 6, 2022 19:41
jloleysens added a commit to jloleysens/kibana that referenced this pull request May 9, 2022
…hromium-to-print-pdf-part-1

* 'main' of github.com:elastic/kibana: (59 commits)
  [Cloud Posture] Enabled findings group by feature (elastic#131780)
  [EBT] Fix `userId` generation (elastic#131701)
  [RAM] Add shareable rule tag filter (elastic#130710)
  Optimize package installation performance, phase 2 (elastic#131627)
  [Screenshotting] instrument for benchmark tests using new EventLogger class (elastic#130356)
  [Connector] Adding internal route for requesting ad-hoc ServiceNow access token (elastic#131171)
  [ci] bump kibana-buildkite-library (elastic#131754)
  [Synthetics] UI clean up (elastic#131598)
  [RsponseOps] Fix flaky rules list test (elastic#131567)
  [Cases] Add severity field to create case (elastic#131626)
  [Discover] Monospace font in Document Explorer (elastic#131513)
  Sessions tab improvements (elastic#131583)
  Add cloud icon "ess-icon" at the end of the config keys in "alerting" documentation (elastic#131735)
  [DOCS] Updates deprecation text for legacy APIs (elastic#131741)
  [ci] break out skip patterns so they can change without triggering CI (elastic#131726)
  Adjust search session management page font size (elastic#131291)
  [Unified search] Fix uptime css problem (elastic#131730)
  [Actionable Observability] Link to filtered rules page (elastic#131629)
  Add openAPI specifications for cases endpoint (elastic#131275)
  Display rule API key owner to users who can manage API keys (elastic#131662)
  ...

# Conflicts:
#	x-pack/plugins/screenshotting/server/formats/pdf/index.ts
#	x-pack/plugins/screenshotting/server/screenshots/observable.ts
kertal pushed a commit to kertal/kibana that referenced this pull request May 24, 2022
…cess token (elastic#131171)

* Adding new OAuth fields to ServiceNow ExternalIncidentServiceConfigurationBase and ExternalIncidentServiceSecretConfiguration

* Creating new function in ConnectorTokenClient for updating or replacing token

* Update servicenow executors to get Oauth access tokens if configured. Still need to update unit tests for services

* Creating wrapper function for createService to only create one axios instance

* Fixing translation check error

* Adding migration for adding isOAuth to service now connectors

* Fixing unit tests

* Fixing functional test

* Not requiring privateKeyPassword

* Fixing tests

* Adding functional tests for connector creation

* Adding functional tests

* Fixing functional test

* PR feedback

* Adding route for requesting access token using OAuth credentials

* Fixing test

* Adding functional test

* Fixing functional test

* Fixing checks

* Using existing private key

* Refactoring get access token utilities to be more generic

* Checking tokenurl against allowlist

* Restricting access to users with ability to update connectors

* Adding slashesDenotesHost parameter to url.parse

* Removing ability to specify custom claims for jwt assertion

* Verifying that token url contains hostname and uses https

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting Feature:Actions/ConnectorTypes Issues related to specific Connector Types on the Actions Framework release_note:skip Skip the PR/issue when compiling release notes Team:ResponseOps Platform ResponseOps team (formerly the Cases and Alerting teams) t// v8.3.0

Projects

No open projects

Development

Successfully merging this pull request may close these issues.

8 participants