Skip to content

Commit 44a2618

Browse files
authored
x-pack/filebeat/input/entityanalytics/provider/okta: fix OAuth2 jwk_json token refresh (#51079)
The legacy Okta entity analytics provider did not store the JWK bytes when oauth2.jwk_json was configured. The jwkData variable was only assigned in the jwk_file branch, so the oktaTokenSource received nil for its oktaJWK field. Combined with Token() unconditionally regenerating the JWT on every call (rather than checking the cached token), the first API request failed with "error decoding JWK: unexpected end of JSON input". Store the JWK bytes for the jwk_json case and add a token validity check to avoid unnecessary JWT regeneration, matching the pattern already used by clientSecretTokenSource and the entcollect jwtTokenSource. Fixes #50949 Assisted-By: Cursor
1 parent c07b72b commit 44a2618

3 files changed

Lines changed: 91 additions & 1 deletion

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# REQUIRED
2+
# Kind can be one of:
3+
# - breaking-change: a change to previously-documented behavior
4+
# - deprecation: functionality that is being removed in a later release
5+
# - bug-fix: fixes a problem in a previous version
6+
# - enhancement: extends functionality but does not break or fix existing behavior
7+
# - feature: new functionality
8+
# - known-issue: problems that we are aware of in a given version
9+
# - security: impacts on the security of a product or a user’s deployment.
10+
# - upgrade: important information for someone upgrading from a prior version
11+
# - other: does not fit into any of the other categories
12+
kind: bug-fix
13+
14+
summary: Fix Okta entity analytics OAuth2 jwk_json token refresh failure.
15+
16+
description: |
17+
The legacy Okta entity analytics provider did not store the JWK bytes when
18+
oauth2.jwk_json was configured, causing token refresh to fail with
19+
"error decoding JWK: unexpected end of JSON input". Also add a cached-token
20+
validity check to avoid regenerating the JWT on every API request.
21+
22+
component: filebeat
23+
24+
issue: https://github.com/elastic/beats/issues/50949
25+
26+
# AUTOMATED
27+
# OPTIONAL to manually add other PR URLs
28+
# PR URL: A link the PR that added the changeset.
29+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
30+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
31+
# Please provide it if you are adding a fragment for a different PR.
32+
# pr: https://github.com/owner/repo/1234
33+
34+
# AUTOMATED
35+
# OPTIONAL to manually add other issue URLs
36+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
37+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
38+
# issue: https://github.com/owner/repo/1234

x-pack/filebeat/input/entityanalytics/provider/okta/oauth2.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ func (o *oAuth2Config) fetchOktaOauthClient(ctx context.Context, client *http.Cl
9494
return nil, fmt.Errorf("failed to generate Okta JWT: %w", err)
9595
}
9696
case o.OktaJWKJSON != nil:
97-
oktaJWT, err = generateOktaJWT(o.OktaJWKJSON, oauthConfig)
97+
jwkData = []byte(o.OktaJWKJSON)
98+
oktaJWT, err = generateOktaJWT(jwkData, oauthConfig)
9899
if err != nil {
99100
return nil, fmt.Errorf("failed to generate Okta JWT: %w", err)
100101
}
@@ -194,6 +195,10 @@ func (ts *oktaTokenSource) Token() (*oauth2.Token, error) {
194195
ts.mu.Lock()
195196
defer ts.mu.Unlock()
196197

198+
if ts.token != nil && ts.token.Valid() {
199+
return ts.token, nil
200+
}
201+
197202
var oktaJWT string
198203
var err error
199204
if ts.oktaJWKPEM != "" {
@@ -208,6 +213,7 @@ func (ts *oktaTokenSource) Token() (*oauth2.Token, error) {
208213
if err != nil {
209214
return nil, fmt.Errorf("error exchanging Okta JWT for bearer token: %w", err)
210215
}
216+
ts.token = token
211217

212218
return token, nil
213219
}

x-pack/filebeat/input/entityanalytics/provider/okta/oauth2_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// or more contributor license agreements. Licensed under the Elastic License;
33
// you may not use this file except in compliance with the Elastic License.
44

5+
//nolint:gosec // There are no secrets in this file.
56
package okta
67

78
import (
@@ -447,3 +448,48 @@ func TestOktaTokenSource_Token(t *testing.T) {
447448
})
448449
}
449450
}
451+
452+
// TestFetchOktaOauthClient_JWKJSONRoundTrip exercises the full
453+
// fetchOktaOauthClient path with jwk_json to verify that the JWK
454+
// bytes survive into the token source for refresh. Previously
455+
// jwkData was only stored for the jwk_file case, causing token
456+
// refresh to fail with "unexpected end of JSON input".
457+
func TestFetchOktaOauthClient_JWKJSONRoundTrip(t *testing.T) {
458+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
459+
switch r.URL.Path {
460+
case "/token":
461+
w.Header().Set("Content-Type", "application/json")
462+
_ = json.NewEncoder(w).Encode(map[string]any{
463+
"access_token": "mock-access-token",
464+
"token_type": "Bearer",
465+
"expires_in": 3600,
466+
})
467+
case "/resource":
468+
w.WriteHeader(http.StatusOK)
469+
default:
470+
w.WriteHeader(http.StatusNotFound)
471+
}
472+
}))
473+
defer srv.Close()
474+
475+
cfg := &oAuth2Config{
476+
ClientID: "test-client-id",
477+
Scopes: []string{"okta.users.read"},
478+
TokenURL: srv.URL + "/token",
479+
OktaJWKJSON: common.JSONBlob(testOktaJWKJSON),
480+
}
481+
482+
client, err := cfg.fetchOktaOauthClient(context.Background(), srv.Client())
483+
if err != nil {
484+
t.Fatalf("fetchOktaOauthClient() error: %v", err)
485+
}
486+
487+
resp, err := client.Get(srv.URL + "/resource") //nolint:noctx // No need for a context here.
488+
if err != nil {
489+
t.Fatalf("GET /resource error: %v", err)
490+
}
491+
resp.Body.Close()
492+
if resp.StatusCode != http.StatusOK {
493+
t.Errorf("GET /resource status = %d; want %d", resp.StatusCode, http.StatusOK)
494+
}
495+
}

0 commit comments

Comments
 (0)