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

x-pack/filebeat/input/entityanalytics/provider/okta: add user group membership support #39815

Merged
merged 3 commits into from
Jun 6, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
x-pack/filebeat/input/entityanalytics/provider/okta: add user group m…
…embership support
efd6 committed Jun 6, 2024
commit 298596aae3f858f8212d87e0e3552d6db792144f
16 changes: 14 additions & 2 deletions x-pack/filebeat/input/entityanalytics/provider/okta/okta.go
Original file line number Diff line number Diff line change
@@ -385,15 +385,16 @@ func (p *oktaInput) doFetchUsers(ctx context.Context, state *stateStore, fullSyn

if fullSync {
for _, u := range batch {
state.storeUser(u)
p.addGroup(ctx, u, state)
if u.LastUpdated.After(lastUpdated) {
lastUpdated = u.LastUpdated
}
}
} else {
users = grow(users, len(batch))
for _, u := range batch {
users = append(users, state.storeUser(u))
su := p.addGroup(ctx, u, state)
users = append(users, su)
if u.LastUpdated.After(lastUpdated) {
lastUpdated = u.LastUpdated
}
@@ -424,6 +425,17 @@ func (p *oktaInput) doFetchUsers(ctx context.Context, state *stateStore, fullSyn
return users, nil
}

func (p *oktaInput) addGroup(ctx context.Context, u okta.User, state *stateStore) *User {
su := state.storeUser(u)
groups, _, err := okta.GetUserGroupDetails(ctx, p.client, p.cfg.OktaDomain, p.cfg.OktaToken, u.ID, p.lim, p.cfg.LimitWindow)
if err != nil {
p.logger.Warnf("failed to get user group membership for %s: %v", u.ID, err)
return su
}
su.Groups = groups
return su
}

// doFetchDevices handles fetching device and associated user identities from Okta.
// If fullSync is true, then any existing deltaLink will be ignored, forcing a full
// synchronization from Okta.
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import (

"golang.org/x/time/rate"

"github.com/elastic/beats/v7/x-pack/filebeat/input/entityanalytics/provider/okta/internal/okta"
"github.com/elastic/elastic-agent-libs/logp"
)

@@ -49,11 +50,13 @@ func TestOktaDoFetch(t *testing.T) {
window = time.Minute
key = "token"
users = `[{"id":"USERID","status":"STATUS","created":"2023-05-14T13:37:20.000Z","activated":null,"statusChanged":"2023-05-15T01:50:30.000Z","lastLogin":"2023-05-15T01:59:20.000Z","lastUpdated":"2023-05-15T01:50:32.000Z","passwordChanged":"2023-05-15T01:50:32.000Z","type":{"id":"typeid"},"profile":{"firstName":"name","lastName":"surname","mobilePhone":null,"secondEmail":null,"login":"[email protected]","email":"[email protected]"},"credentials":{"password":{"value":"secret"},"emails":[{"value":"[email protected]","status":"VERIFIED","type":"PRIMARY"}],"provider":{"type":"OKTA","name":"OKTA"}},"_links":{"self":{"href":"https://localhost/api/v1/users/USERID"}}}]`
groups = `[{"id":"USERID","profile":{"description":"All users in your organization","name":"Everyone"}}]`
devices = `[{"id":"DEVICEID","status":"STATUS","created":"2019-10-02T18:03:07.000Z","lastUpdated":"2019-10-02T18:03:07.000Z","profile":{"displayName":"Example Device name 1","platform":"WINDOWS","serialNumber":"XXDDRFCFRGF3M8MD6D","sid":"S-1-11-111","registered":true,"secureHardwarePresent":false,"diskEncryptionType":"ALL_INTERNAL_VOLUMES"},"resourceType":"UDDevice","resourceDisplayName":{"value":"Example Device name 1","sensitive":false},"resourceAlternateId":null,"resourceId":"DEVICEID","_links":{"activate":{"href":"https://localhost/api/v1/devices/DEVICEID/lifecycle/activate","hints":{"allow":["POST"]}},"self":{"href":"https://localhost/api/v1/devices/DEVICEID","hints":{"allow":["GET","PATCH","PUT"]}},"users":{"href":"https://localhost/api/v1/devices/DEVICEID/users","hints":{"allow":["GET"]}}}}]`
)

data := map[string]string{
"users": users,
"groups": groups,
"devices": devices,
}

@@ -63,6 +66,14 @@ func TestOktaDoFetch(t *testing.T) {
if err != nil {
t.Fatalf("failed to unmarshal user data: %v", err)
}
var wantGroups []okta.Group
err = json.Unmarshal([]byte(groups), &wantGroups)
if err != nil {
t.Fatalf("failed to unmarshal user data: %v", err)
}
for i, u := range wantUsers {
wantUsers[i].Groups = append(u.Groups, wantGroups...)
}
}
var wantDevices []Device
if test.wantDevices {
@@ -83,6 +94,12 @@ func TestOktaDoFetch(t *testing.T) {
w.Header().Add("x-rate-limit-remaining", "49")
w.Header().Add("x-rate-limit-reset", fmt.Sprint(time.Now().Add(time.Minute).Unix()))

if strings.HasPrefix(r.URL.Path, "/api/v1/users") && strings.HasSuffix(r.URL.Path, "groups") {
// Give the groups if this is a get user groups request.
userid := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/api/v1/users/"), "/groups")
fmt.Fprintln(w, strings.ReplaceAll(data["groups"], "USERID", userid))
return
}
if strings.HasPrefix(r.URL.Path, "/api/v1/device") && strings.HasSuffix(r.URL.Path, "users") {
// Give one user if this is a get device users request.
fmt.Fprintln(w, data["users"])
@@ -158,9 +175,15 @@ func TestOktaDoFetch(t *testing.T) {
t.Errorf("unexpected number of results: got:%d want:%d", len(got), wantCount(repeats, test.wantUsers))
}
for i, g := range got {
if wantID := fmt.Sprintf("userid%d", i+1); g.ID != wantID {
wantID := fmt.Sprintf("userid%d", i+1)
if g.ID != wantID {
t.Errorf("unexpected user ID for user %d: got:%s want:%s", i, g.ID, wantID)
}
for j, gg := range g.Groups {
if gg.ID != wantID {
t.Errorf("unexpected used ID for user group %d in %d: got:%s want:%s", j, i, gg.ID, wantID)
}
}
if g.State != wantStates[g.ID] {
t.Errorf("unexpected user state for user %s: got:%s want:%s", g.ID, g.State, wantStates[g.ID])
}
Original file line number Diff line number Diff line change
@@ -39,7 +39,8 @@ const (

type User struct {
okta.User `json:"properties"`
State State `json:"state"`
Groups []okta.Group `json:"groups"`
State State `json:"state"`
}

type Device struct {