Skip to content

Commit 1a0fbfb

Browse files
sbolosandgghineadfeldickgitvivekschauhanalrosca
authored
APIGOV-32943 — Okta IDP per-scope policy lifecycle, app name templates, and scope exclude list (#1049)
* APIGOV-32943 - Extend clients Okta and remove deprecated features. First pass * APIGOV-32943 - remove policy token and priority configs according to engie * APIGOV-32943 - update for owning team and group property * APIGOV-32943 - update for scopes * APIGOV-32943 - blacklist processing * APIGOV-32943 - change to guid for query * APIGOV-32943 - get proper placement of team owner * APIGOV-32943 - update to delete policy only after list is cleared * APIGOV-32943 - udpate blacklist to exclude * idp changes * persist IDPClient on managedApp straight after RegisterClient() * fix tests * change unregister logic * add return * changes * fix deprov * APIGOV-32989 - update golang.org/x libraries (#1050) * APIGOV-32994 - Fix to create Idp resource if not already existing (#1051) * APIGOV-31191 validate cache (#1015) * APIGOV-31191 validate cache * APIGOV-31191 improvements and unit tests * APIGOV-31191 refactor tests * APIGOV-31191 validate instead of rebuild after 7 days * APIGOV-31191 get scoped resources from cache * APIGOV-31191 update shouldRebuildCache * APIGOV-31191 fix logs * APIGOV-31191 improvements * APIGOV-32372 cache rebuild optimizations (#1023) * APIGOV-32372 rebuild only failed kinds * APIGOV-32372 fix for listener/cache rebuild race condition * APIGOV-32372 merge main * APIGOV-32372 remove else statement Co-authored-by: Copilot <copilot@github.com> * APIGOV-32327 wait for clients to be ready * APIGOV-32372 improvements * APIGOV-32372 fix for unlock on unlocked mutex error * APIGOV-32372 guard against listener potential data race --------- Co-authored-by: Copilot <copilot@github.com> * APIGOV-31191 sequence and resource count pre validation * APIGOV-31191 - remove timeout on harverter event sync * APIGOV-31191 - Cache validation/rebuild refactoring (#1048) * APIGOV-31191 - remove API calls for metadata check for cache validation * APIGOV-31191 - Refactoring and cleanup - Remove entire cache flush. Flush only the kind getting processed and after the API call is successful to avoid cache inconsistency - Cleanup instance count map that should speed up building APIservice cache items - Fixes * APIGOV-31191 - check publishing lock and acquire lock while publishing * APIGOV-31191 - fixes - Fix for endless waitForCacheRebuild - Fix to keep original APIService resource instance in cache when duplicates are added to the cache - Fix to hook cache rebuild on stream client reconnection instead of harvester error and to address job pool status * APIGOV-31191 - updates - remove waitForCacheRebuild and use the error returned from harvester to trigger the job restart * APIGOV-31191 - fallback to count checks if harvester is not reachable * APIGOV-31191 - review comments + lock on harvester event sync * APIGOV-31191 - lock on harvester event sync * APIGOV-31191 - use APIServiceInstance counts to rebuild APIService cache * APIGOV-31191 - use CreateOrUpdateResource for processing APIService * APIGOV-31191 - fix to not make multiple x-agent-details update for APIService * APIGOV-31191 - clean up event listener pauser * APIGOV-31191 - fix client adapter --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Vivek Singh Chauhan <vchauhan@axway.com> * APIGOV-31452 - Send New Insights Event Formats (#1040) * APIGOV-31452 - send new insights events formats * APIGOV-31452 - updates on access request * APIGOV-31452 - add deep logging for event building * APIGOV-31452 - add missing attribs for service revision id and agent version * APIGOV-31452 - updated metric v3 events * APIGOV-31452 - update for product owner * APIGOV-31452 - fall back to apiserviceinstance by name * APIGOV-31452 - add logging for unmarshaling * APIGOV-31452 - update for embedded shape * APIGOV-31452 - match the actual _embedded.metadata.references shape from the API server * APIGOV-31452 - addition of owner * APIGOV-31452 - add SetReporter builder method to CentralMetricBuilder * APIGOV-31452 - update for published product watch topic * APIGOV-31452 - revert watch topic logic * APIGOV-31452 - get published product owner from embedded * APIGOV-31452 - update unit test coverage * APIGOV-31452 - add api metric for agents explicitly calling * APIGOV-31452 - user id path * APIGOV-31452 - update test coverage * APIGOV-31452 - update sonar fixes * APIGOV-31452 - update embed for x agent details apiservice fetch * APIGOV-31452 - update leg with consumer details * APIGOV-31452 - remove consumer details * APIGOV-31452 - review * APIGOV-31452 - set proxy funcs for on prem agents * APIGOV-31452 - add backwards compatibility for leg * APIGOV-31452 - update product block * APIGOV-31452 - update rev id for on prem * APIGOV-31452 - apiservicerev setter for some on prem agents * APIGOV-31452 - update transaction fields * APIGOV-31452 - update leg direction and proxy * APIGOV-31452 - update proxy * APIGOV-31452 - expose SetLegProxy for controller enrichment * APIGOV-31452 - update on legs * APIGOV-31452 - set owner from man app * APIGOV-31452 - product owner fix * APIGOV-31452 - update source and ip for v7 fixes * APIGOV-31452 - remove flow header * APIGOV-31452 - review * APIGOV-31452 - review and unit test coverage * APIGOV-31452 - update tests and comments * APIGOV-31452 - PR comments and proxy updates * APIGOV-31452 - update based on PR comments * fix mocks * fix rebase error --------- Co-authored-by: Jason Collins <jcollins@axway.com> * APIGOV-31452 - resolve concurrent metric collector lock contention under burst load (#1053) * APIGOV-32943 - rollback on invalid scope not on AS --------- Co-authored-by: Dragos Gabriel Ghinea <dgghinea@axway.com> Co-authored-by: Dale Feldick <dfeldick@axway.com> Co-authored-by: Vivek Singh Chauhan <vchauhan@axway.com> Co-authored-by: Alin Rosca <137875910+alrosca@users.noreply.github.com> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Jason Collins <jcollins@axway.com>
1 parent 62f103d commit 1a0fbfb

21 files changed

Lines changed: 3427 additions & 892 deletions

pkg/agent/handler/accessrequest_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,9 @@ type mockClient struct {
418418
isDeleting bool
419419
subError error
420420
deleteResCalled bool
421+
getTeamResult []defs.PlatformTeam
422+
getTeamErr error
423+
gotTeamQuery map[string]string
421424
t *testing.T
422425
}
423426

@@ -459,6 +462,11 @@ func (m *mockClient) DeleteResourceInstance(ri v1.Interface) error {
459462
return nil
460463
}
461464

465+
func (m *mockClient) GetTeam(query map[string]string) ([]defs.PlatformTeam, error) {
466+
m.gotTeamQuery = query
467+
return m.getTeamResult, m.getTeamErr
468+
}
469+
462470
type mockARProvision struct {
463471
expectedAccessDetails map[string]interface{}
464472
expectedAPIID string

pkg/agent/handler/credential.go

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -218,43 +218,52 @@ func (h *credentials) onDeleting(ctx context.Context, cred *management.Credentia
218218

219219
status := h.prov.CredentialDeprovision(provCreds)
220220

221-
h.deprovisionPostProcess(status, provCreds, logger, ctx, cred)
221+
h.deprovisionPostProcess(status, provCreds, logger, ctx, cred, app)
222222
}
223223

224-
func (h *credentials) deprovisionPostProcess(status prov.RequestStatus, provCreds *provCreds, logger log.FieldLogger, ctx context.Context, cred *management.Credential) {
225-
if status.GetStatus() == prov.Success {
226-
if provCreds.IsIDPCredential() && !isExternalCredential(cred) {
227-
err := provCreds.idpProvisioner.UnregisterClient()
228-
if err != nil {
229-
logger.
230-
WithError(err).
231-
WithField("client_id", provCreds.idpProvisioner.GetIDPCredentialData().GetClientID()).
232-
WithField("provider", provCreds.GetIDPProvider().GetName()).
233-
Warn("error deprovisioning credential request from IDP, please ask administrator to remove the client from IdP")
234-
}
235-
}
236-
237-
ri, _ := cred.AsInstance()
238-
h.client.UpdateResourceFinalizer(ri, crFinalizer, "", false)
239-
240-
// update sub resources when expire
241-
if cred.Metadata.State != v1.ResourceDeleting {
242-
cred.State.Name = v1.Inactive
243-
cred.Status.Level = prov.Success.String()
244-
cred.Status.Reasons = []v1.ResourceStatusReason{}
245-
h.client.CreateSubResource(cred.ResourceMeta, map[string]interface{}{
246-
"state": cred.State,
247-
})
248-
h.client.CreateSubResource(cred.ResourceMeta, map[string]interface{}{
249-
"status": cred.Status,
250-
})
251-
}
252-
} else {
224+
func (h *credentials) deprovisionPostProcess(status prov.RequestStatus, provCreds *provCreds, logger log.FieldLogger,
225+
ctx context.Context, cred *management.Credential, app *management.ManagedApplication) {
226+
if status.GetStatus() != prov.Success {
253227
err := errors.New(status.GetMessage())
254228
logger.WithError(err).Error("request status was not Success, skipping")
255229
h.onError(ctx, cred, err)
256230
h.client.CreateSubResource(cred.ResourceMeta, cred.SubResources)
231+
return
232+
}
233+
234+
if provCreds.IsIDPCredential() && !isExternalCredential(cred) {
235+
clientID := provCreds.idpProvisioner.GetIDPCredentialData().GetClientID()
236+
tokenURL := provCreds.GetIDPProvider().GetTokenEndpoint()
237+
providerName := provCreds.GetIDPProvider().GetName()
238+
logger = logger.WithField("client_id", clientID).WithField("provider", providerName).WithField("tokenURL", tokenURL)
239+
240+
unregisterErr := provCreds.idpProvisioner.UnregisterClient()
241+
if unregisterErr != nil {
242+
logger.WithError(unregisterErr).Warn("error deprovisioning credential request from IDP, please ask administrator to remove the client from IdP")
243+
} else {
244+
if err := removeClientIDFromManagedApp(logger, h.client, app, clientID, tokenURL); err != nil {
245+
logger.WithError(err).Warn("error removing clientID from Managed App clientIDs array")
246+
}
247+
}
248+
}
249+
250+
ri, _ := cred.AsInstance()
251+
h.client.UpdateResourceFinalizer(ri, crFinalizer, "", false)
252+
253+
// update sub resources when expire
254+
if cred.Metadata.State == v1.ResourceDeleting {
255+
return
257256
}
257+
258+
cred.State.Name = v1.Inactive
259+
cred.Status.Level = prov.Success.String()
260+
cred.Status.Reasons = []v1.ResourceStatusReason{}
261+
h.client.CreateSubResource(cred.ResourceMeta, map[string]interface{}{
262+
"state": cred.State,
263+
})
264+
h.client.CreateSubResource(cred.ResourceMeta, map[string]interface{}{
265+
"status": cred.Status,
266+
})
258267
}
259268

260269
func (h *credentials) onPending(ctx context.Context, cred *management.Credential) *management.Credential {
@@ -272,12 +281,10 @@ func (h *credentials) onPending(ctx context.Context, cred *management.Credential
272281
return cred
273282
}
274283

275-
if provCreds.IsIDPCredential() && !isExternalCredential(cred) {
276-
if err := provCreds.idpProvisioner.RegisterClient(); err != nil {
277-
logger.WithError(err).Error("error provisioning credential request with IDP")
278-
h.onError(ctx, cred, err)
279-
return cred
280-
}
284+
if err := h.registerIDPClient(cred, provCreds, app, logger); err != nil {
285+
logger.WithError(err).Error("error registering IDP client")
286+
h.onError(ctx, cred, err)
287+
return cred
281288
}
282289

283290
status, credentialData := h.provision(provCreds)
@@ -453,10 +460,9 @@ func (h *credentials) onUpdates(ctx context.Context, cred *management.Credential
453460
return cred
454461
}
455462

456-
// if IDP and rotate the agent will register a new client
457-
if action == prov.Rotate && provCreds.IsIDPCredential() && !isExternalCredential(cred) {
458-
if err := provCreds.idpProvisioner.RegisterClient(); err != nil {
459-
logger.WithError(err).Error("error provisioning credential request with IDP")
463+
if action == prov.Rotate {
464+
if err := h.registerIDPClient(cred, provCreds, app, logger); err != nil {
465+
logger.WithError(err).Error("error registering IDP client")
460466
h.onError(ctx, cred, err)
461467
return cred
462468
}
@@ -598,6 +604,24 @@ func (h *credentials) newProvCreds(cr *management.Credential, app *management.Ma
598604
return provCred, nil
599605
}
600606

607+
// registerIDPClient registers a new IDP client and (for Okta) persists the reference
608+
// on the managed app. It is a no-op when not an IDP credential or is external.
609+
func (h *credentials) registerIDPClient(cred *management.Credential, provCreds *provCreds, app *management.ManagedApplication, logger log.FieldLogger) error {
610+
if !provCreds.IsIDPCredential() || isExternalCredential(cred) {
611+
return nil
612+
}
613+
if err := provCreds.idpProvisioner.RegisterClient(); err != nil {
614+
return fmt.Errorf("error provisioning credential request with IDP: %w", err)
615+
}
616+
if provCreds.GetIDPProvider().GetConfig().GetIDPType() != oauth.TypeOkta {
617+
return nil
618+
}
619+
return persistIDPClientOnManagedApplication(logger, h.client, app,
620+
provCreds.GetIDPCredentialData().GetClientID(),
621+
provCreds.GetIDPProvider().GetTokenEndpoint(),
622+
)
623+
}
624+
601625
// GetApplicationName gets the name of the managed application
602626
func (c provCreds) GetApplicationName() string {
603627
return c.managedApp

pkg/agent/handler/credential_test.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ type mockCredProv struct {
729729
func (m *mockCredProv) CredentialProvision(cr prov.CredentialRequest) (status prov.RequestStatus, credentails prov.Credential) {
730730
m.expectedProvType = provision
731731
v := cr.(*provCreds)
732-
assert.Equal(m.t, m.expectedAppDetails, v.appDetails)
732+
assertMapContains(m.t, v.appDetails, m.expectedAppDetails)
733733
assert.Equal(m.t, m.expectedCredDetails, v.credDetails)
734734
assert.Equal(m.t, m.expectedManagedApp, v.managedApp)
735735
assert.Equal(m.t, m.expectedCredType, v.credType)
@@ -739,7 +739,7 @@ func (m *mockCredProv) CredentialProvision(cr prov.CredentialRequest) (status pr
739739
func (m *mockCredProv) CredentialDeprovision(cr prov.CredentialRequest) (status prov.RequestStatus) {
740740
m.expectedProvType = deprovision
741741
v := cr.(*provCreds)
742-
assert.Equal(m.t, m.expectedAppDetails, v.appDetails)
742+
assertMapContains(m.t, v.appDetails, m.expectedAppDetails)
743743
assert.Equal(m.t, m.expectedCredDetails, v.credDetails)
744744
assert.Equal(m.t, m.expectedManagedApp, v.managedApp)
745745
assert.Equal(m.t, m.expectedCredType, v.credType)
@@ -749,13 +749,20 @@ func (m *mockCredProv) CredentialDeprovision(cr prov.CredentialRequest) (status
749749
func (m *mockCredProv) CredentialUpdate(cr prov.CredentialRequest) (status prov.RequestStatus, credentails prov.Credential) {
750750
m.expectedProvType = update
751751
v := cr.(*provCreds)
752-
assert.Equal(m.t, m.expectedAppDetails, v.appDetails)
752+
assertMapContains(m.t, v.appDetails, m.expectedAppDetails)
753753
assert.Equal(m.t, m.expectedCredDetails, v.credDetails)
754754
assert.Equal(m.t, m.expectedManagedApp, v.managedApp)
755755
assert.Equal(m.t, m.expectedCredType, v.credType)
756756
return m.expectedStatus, &mockProvCredential{}
757757
}
758758

759+
func assertMapContains(t *testing.T, actual, expected map[string]interface{}) {
760+
t.Helper()
761+
for k, v := range expected {
762+
assert.Equal(t, v, actual[k], "expected map to contain key %q", k)
763+
}
764+
}
765+
759766
func (m *mockCredProv) GetIgnoredCredentialTypes() []string {
760767
return m.ignoredCredTypeNames
761768
}
@@ -1070,9 +1077,9 @@ func TestExternalCredentialOnPending(t *testing.T) {
10701077
}
10711078

10721079
p := &mockExternalCredProv{
1073-
t: t,
1080+
t: t,
10741081
expectedStatus: expectedStatus,
1075-
provisionErr: tc.provisionErr,
1082+
provisionErr: tc.provisionErr,
10761083
}
10771084

10781085
c := &credClient{
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
9+
defs "github.com/Axway/agent-sdk/pkg/apic/definitions"
10+
"github.com/Axway/agent-sdk/pkg/authz/oauth"
11+
"github.com/Axway/agent-sdk/pkg/util"
12+
"github.com/Axway/agent-sdk/pkg/util/log"
13+
)
14+
15+
const (
16+
oktaClientIDsAgentDetail = "oktaClientIDs"
17+
tokenURLAgentDetail = "tokenURL"
18+
)
19+
20+
// persistIDPClientOnManagedApplication stores IDP client IDs on the ManagedApplication x-agent-details
21+
// so they can be cleaned up if the app is deleted before credential-level cleanup runs.
22+
func persistIDPClientOnManagedApplication(logger log.FieldLogger, client client, app *management.ManagedApplication, clientID, tokenURL string) error {
23+
if app == nil || clientID == "" {
24+
return nil
25+
}
26+
27+
details := util.GetAgentDetails(app)
28+
if details == nil {
29+
details = make(map[string]interface{})
30+
}
31+
32+
existing := extractClientIDs(details[oktaClientIDsAgentDetail])
33+
for _, id := range existing {
34+
if id == clientID {
35+
return nil
36+
}
37+
}
38+
existing = append(existing, clientID)
39+
40+
details[oktaClientIDsAgentDetail] = existing
41+
if tokenURL != "" {
42+
details[tokenURLAgentDetail] = tokenURL
43+
}
44+
45+
if err := client.CreateSubResource(app.ResourceMeta, map[string]interface{}{defs.XAgentDetails: details}); err != nil {
46+
return fmt.Errorf("could not persist IDP client reference on managed application %s: %w", app.Name, err)
47+
}
48+
49+
if logger != nil {
50+
logger.WithField("clientID", clientID).Trace("persisted IDP client reference on managed application")
51+
}
52+
53+
return nil
54+
}
55+
56+
func removeClientIDFromManagedApp(logger log.FieldLogger, client client, app *management.ManagedApplication, clientID, tokenURL string) error {
57+
if app == nil || clientID == "" {
58+
return nil
59+
}
60+
61+
details := util.GetAgentDetails(app)
62+
if details == nil {
63+
return nil
64+
}
65+
66+
clientIDs := extractClientIDs(details[oktaClientIDsAgentDetail])
67+
if len(clientIDs) == 0 {
68+
logger.Trace("empty clientIDs agent detail")
69+
return nil
70+
}
71+
72+
for i := len(clientIDs) - 1; i >= 0; i-- {
73+
if clientIDs[i] != clientID {
74+
continue
75+
}
76+
clientIDs = append(clientIDs[:i], clientIDs[i+1:]...)
77+
details[oktaClientIDsAgentDetail] = clientIDs
78+
if err := client.CreateSubResource(app.ResourceMeta, map[string]interface{}{defs.XAgentDetails: details}); err != nil {
79+
return fmt.Errorf("could not persist IDP client reference on managed application %s: %w", app.Name, err)
80+
}
81+
return nil
82+
}
83+
84+
logger.WithField("clientID", clientID).Trace("could not find the clientID to remove from managedApp x-agent-details")
85+
return nil
86+
}
87+
88+
// cleanupManagedApplicationIDPClients removes any tracked IDP clients for a ManagedApplication.
89+
func cleanupManagedApplicationIDPClients(ctx context.Context, logger log.FieldLogger, registry oauth.IdPRegistry, app *management.ManagedApplication) error {
90+
if registry == nil || app == nil {
91+
return nil
92+
}
93+
94+
details := util.GetAgentDetails(app)
95+
if details == nil {
96+
return nil
97+
}
98+
99+
clientIDs := extractClientIDs(details[oktaClientIDsAgentDetail])
100+
if len(clientIDs) == 0 {
101+
return nil
102+
}
103+
104+
tokenURL := util.ToString(details[tokenURLAgentDetail])
105+
if tokenURL == "" {
106+
logger.Warn("no tokenURL in managed application x-agent-details, skipping IDP app cleanup")
107+
return nil
108+
}
109+
110+
provider, err := registry.GetProviderByTokenEndpoint(ctx, tokenURL)
111+
if err != nil || provider == nil {
112+
logger.WithField("tokenURL", tokenURL).Warn("no IDP provider registered for tokenURL, skipping IDP app cleanup")
113+
return nil
114+
}
115+
116+
for _, clientID := range clientIDs {
117+
if err := provider.UnregisterClient(clientID, "", "", nil, ""); err != nil {
118+
return fmt.Errorf("cleanupManagedApplicationIDPClients: failed for client %s: %w", clientID, err)
119+
}
120+
logger.WithField("clientID", clientID).Info("IDP client unregistered")
121+
}
122+
123+
return nil
124+
}
125+
126+
func extractClientIDs(raw interface{}) []string {
127+
switch v := raw.(type) {
128+
case []string:
129+
return append([]string(nil), v...)
130+
case []interface{}:
131+
ids := make([]string, 0, len(v))
132+
for _, item := range v {
133+
if s, ok := item.(string); ok && s != "" {
134+
ids = append(ids, s)
135+
}
136+
}
137+
return ids
138+
case string:
139+
if v == "" {
140+
return nil
141+
}
142+
var ids []string
143+
if err := json.Unmarshal([]byte(v), &ids); err == nil {
144+
return ids
145+
}
146+
}
147+
148+
return nil
149+
}

0 commit comments

Comments
 (0)