Skip to content

Commit b82d7ce

Browse files
CyberArk(servicediscovery): consolidate service discovery into unified API
- Replaced `DiscoverIdentityAPIURL` with `DiscoverServices` to fetch all service endpoints in a single call. - Updated `Services` struct to include `Identity` and `DiscoveryContext` endpoints. - Refactored dependent modules (`dataupload`, `identity`) to use the new `DiscoverServices` method. - Adjusted tests and mock server to align with the new service discovery implementation. - Removed redundant `platformDomain` logic from `dataupload_test.go`.
1 parent 3fa015d commit b82d7ce

File tree

7 files changed

+156
-51
lines changed

7 files changed

+156
-51
lines changed

pkg/internal/cyberark/dataupload/dataupload.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424
// apiPathSnapshotLinks is the URL path of the snapshot-links endpoint of the inventory API.
2525
// This endpoint returns an AWS presigned URL.
2626
// TODO(wallrj): Link to CyberArk API documentation when it is published.
27-
apiPathSnapshotLinks = "/api/ingestions/kubernetes/snapshot-links"
27+
apiPathSnapshotLinks = "/ingestions/kubernetes/snapshot-links"
2828
)
2929

3030
type CyberArkClient struct {

pkg/internal/cyberark/dataupload/dataupload_test.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
124124
// TestPostDataReadingsWithOptionsWithRealAPI demonstrates that the dataupload code works with the real inventory API.
125125
// An API token is obtained by authenticating with the ARK_USERNAME and ARK_SECRET from the environment.
126126
// ARK_SUBDOMAIN should be your tenant subdomain.
127-
// ARK_PLATFORM_DOMAIN should be either integration-cyberark.cloud or cyberark.cloud
128127
//
129128
// To test against a tenant on the integration platform, also set:
130129
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
@@ -134,36 +133,32 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
134133
// go test ./pkg/internal/cyberark/dataupload/... \
135134
// -v -count 1 -run TestPostDataReadingsWithOptionsWithRealAPI -args -testing.v 6
136135
func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
137-
platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN")
138136
subdomain := os.Getenv("ARK_SUBDOMAIN")
139137
username := os.Getenv("ARK_USERNAME")
140138
secret := os.Getenv("ARK_SECRET")
141139

142-
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
143-
t.Skip("Skipping because one of the following environment variables is unset or empty: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
140+
if subdomain == "" || username == "" || secret == "" {
141+
t.Skip("Skipping because one of the following environment variables is unset or empty: ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
144142
return
145143
}
146144

147145
logger := ktesting.NewLogger(t, ktesting.DefaultConfig)
148146
ctx := klog.NewContext(t.Context(), logger)
149147

150-
// TODO(wallrj): get this from the servicediscovery API instead.
151-
inventoryAPI := fmt.Sprintf("https://%s.inventory.%s", subdomain, platformDomain)
152-
153148
var rootCAs *x509.CertPool
154149
httpClient := http_client.NewDefaultClient(version.UserAgent(), rootCAs)
155150
httpClient.Transport = transport.NewDebuggingRoundTripper(httpClient.Transport, transport.DebugByContext)
156151

157152
discoveryClient := servicediscovery.New(httpClient)
158153

159-
identityAPI, err := discoveryClient.DiscoverIdentityAPIURL(ctx, subdomain)
154+
services, err := discoveryClient.DiscoverServices(ctx, subdomain)
160155
require.NoError(t, err)
161156

162-
identityClient := identity.New(httpClient, identityAPI, subdomain)
157+
identityClient := identity.New(httpClient, services.Identity.API, subdomain)
163158
err = identityClient.LoginUsernamePassword(ctx, username, []byte(secret))
164159
require.NoError(t, err)
165160

166-
cyberArkClient := dataupload.New(httpClient, inventoryAPI, identityClient.AuthenticateRequest)
161+
cyberArkClient := dataupload.New(httpClient, services.DiscoveryContext.API, identityClient.AuthenticateRequest)
167162
err = cyberArkClient.PostDataReadingsWithOptions(ctx, api.DataReadingsPost{}, dataupload.Options{
168163
ClusterName: "bb068932-c80d-460d-88df-34bc7f3f3297",
169164
})

pkg/internal/cyberark/identity/cmd/testidentity/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ func run(ctx context.Context) error {
5353
httpClient.Transport = transport.NewDebuggingRoundTripper(httpClient.Transport, transport.DebugByContext)
5454

5555
sdClient := servicediscovery.New(httpClient)
56-
identityAPI, err := sdClient.DiscoverIdentityAPIURL(ctx, subdomain)
56+
services, err := sdClient.DiscoverServices(ctx, subdomain)
5757
if err != nil {
5858
return fmt.Errorf("while performing service discovery: %s", err)
5959
}
6060

61-
client := identity.New(httpClient, identityAPI, subdomain)
61+
client := identity.New(httpClient, services.Identity.API, subdomain)
6262

6363
err = client.LoginUsernamePassword(ctx, username, []byte(password))
6464
if err != nil {

pkg/internal/cyberark/servicediscovery/discovery.go

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,35 @@ func New(httpClient *http.Client) *Client {
4646
return client
4747
}
4848

49-
// DiscoverIdentityAPIURL fetches from the service discovery service for a given subdomain
50-
// and parses the CyberArk Identity API URL.
51-
func (c *Client) DiscoverIdentityAPIURL(ctx context.Context, subdomain string) (string, error) {
49+
type ServiceEndpoint struct {
50+
API string `json:"api"`
51+
// NB: other fields are intentionally ignored here; we only care about the API URL
52+
}
53+
54+
type Services struct {
55+
Identity ServiceEndpoint `json:"identity_administration"`
56+
DiscoveryContext ServiceEndpoint `json:"discoverycontext"`
57+
}
58+
59+
// DiscoverServices fetches from the service discovery service for a given subdomain
60+
// and parses the CyberArk Identity API URL and Inventory API URL.
61+
func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Services, error) {
5262
endpoint, err := url.JoinPath(c.baseURL, "services", "subdomain", subdomain)
5363
if err != nil {
54-
return "", fmt.Errorf("failed to build a valid URL for subdomain %s; possibly an invalid endpoint: %s", subdomain, err)
64+
return nil, fmt.Errorf("failed to build a valid URL for subdomain %s; possibly an invalid endpoint: %s", subdomain, err)
5565
}
5666

5767
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
5868
if err != nil {
59-
return "", fmt.Errorf("failed to initialise request to %s: %s", endpoint, err)
69+
return nil, fmt.Errorf("failed to initialise request to %s: %s", endpoint, err)
6070
}
6171

6272
request.Header.Set("Accept", "application/json")
6373
version.SetUserAgent(request)
6474

6575
resp, err := c.client.Do(request)
6676
if err != nil {
67-
return "", fmt.Errorf("failed to perform HTTP request: %s", err)
77+
return nil, fmt.Errorf("failed to perform HTTP request: %s", err)
6878
}
6979

7080
defer resp.Body.Close()
@@ -73,32 +83,26 @@ func (c *Client) DiscoverIdentityAPIURL(ctx context.Context, subdomain string) (
7383
// a 404 error is returned with an empty JSON body "{}" if the subdomain is unknown; at the time of writing, we haven't observed
7484
// any other errors and so we can't special case them
7585
if resp.StatusCode == http.StatusNotFound {
76-
return "", fmt.Errorf("got an HTTP 404 response from service discovery; maybe the subdomain %q is incorrect or does not exist?", subdomain)
86+
return nil, fmt.Errorf("got an HTTP 404 response from service discovery; maybe the subdomain %q is incorrect or does not exist?", subdomain)
7787
}
7888

79-
return "", fmt.Errorf("got unexpected status code %s from request to service discovery API", resp.Status)
80-
}
81-
82-
type ServiceEndpoint struct {
83-
API string `json:"api"`
84-
// NB: other fields are intentionally ignored here; we only care about the API URL
89+
return nil, fmt.Errorf("got unexpected status code %s from request to service discovery API", resp.Status)
8590
}
8691

87-
decodedResponse := make(map[string]ServiceEndpoint)
92+
var services Services
8893

89-
err = json.NewDecoder(io.LimitReader(resp.Body, maxDiscoverBodySize)).Decode(&decodedResponse)
94+
err = json.NewDecoder(io.LimitReader(resp.Body, maxDiscoverBodySize)).Decode(&services)
9095
if err != nil {
9196
if err == io.ErrUnexpectedEOF {
92-
return "", fmt.Errorf("rejecting JSON response from server as it was too large or was truncated")
97+
return nil, fmt.Errorf("rejecting JSON response from server as it was too large or was truncated")
9398
}
9499

95-
return "", fmt.Errorf("failed to parse JSON from otherwise successful request to service discovery endpoint: %s", err)
100+
return nil, fmt.Errorf("failed to parse JSON from otherwise successful request to service discovery endpoint: %s", err)
96101
}
97102

98-
identityService, ok := decodedResponse[identityServiceName]
99-
if !ok {
100-
return "", fmt.Errorf("didn't find %s in service discovery response, which may indicate a suspended tenant; unable to detect CyberArk Identity API URL", identityServiceName)
103+
if services.Identity.API == "" {
104+
return nil, fmt.Errorf("didn't find %s in service discovery response, which may indicate a suspended tenant; unable to detect CyberArk Identity API URL", identityServiceName)
101105
}
102106

103-
return identityService.API, nil
107+
return &services, nil
104108
}

pkg/internal/cyberark/servicediscovery/discovery_test.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"testing"
66

7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
79
"k8s.io/klog/v2"
810
"k8s.io/klog/v2/ktesting"
911

@@ -53,21 +55,26 @@ func Test_DiscoverIdentityAPIURL(t *testing.T) {
5355
logger := ktesting.NewLogger(t, ktesting.DefaultConfig)
5456
ctx := klog.NewContext(t.Context(), logger)
5557

56-
httpClient := MockDiscoveryServer(t, mockIdentityAPIURL)
58+
httpClient := MockDiscoveryServer(t, Services{
59+
Identity: ServiceEndpoint{
60+
API: mockIdentityAPIURL,
61+
},
62+
DiscoveryContext: ServiceEndpoint{
63+
API: mockDiscoveryContextAPIURL,
64+
},
65+
})
5766

5867
client := New(httpClient)
5968

60-
apiURL, err := client.DiscoverIdentityAPIURL(ctx, testSpec.subdomain)
61-
if err != nil {
62-
if err.Error() != testSpec.expectedError.Error() {
63-
t.Errorf("expectedError=%v\nobservedError=%v", testSpec.expectedError, err)
64-
}
69+
services, err := client.DiscoverServices(ctx, testSpec.subdomain)
70+
if testSpec.expectedError != nil {
71+
assert.EqualError(t, err, testSpec.expectedError.Error())
72+
assert.Nil(t, services)
73+
return
6574
}
66-
67-
// NB: we don't exit here because we also want to check the API URL is empty in the event of an error
68-
69-
if apiURL != testSpec.expectedURL {
70-
t.Errorf("expected API URL=%s\nobserved API URL=%s", testSpec.expectedURL, apiURL)
75+
require.NoError(t, err)
76+
if services.Identity.API != testSpec.expectedURL {
77+
t.Errorf("expected API URL=%s\nobserved API URL=%s", testSpec.expectedURL, services.Identity.API)
7178
}
7279
})
7380
}

pkg/internal/cyberark/servicediscovery/mock.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const (
2222
// MockDiscoverySubdomain is the subdomain for which the MockDiscoveryServer will return a success response
2323
MockDiscoverySubdomain = "venafi-test"
2424

25-
mockIdentityAPIURL = "https://ajp5871.id.integration-cyberark.cloud"
25+
mockIdentityAPIURL = "https://ajp5871.id.integration-cyberark.cloud"
26+
mockDiscoveryContextAPIURL = "https://venafi-test.inventory.integration-cyberark.cloud/api"
2627
)
2728

2829
//go:embed testdata/discovery_success.json.template
@@ -41,15 +42,16 @@ type mockDiscoveryServer struct {
4142
// server.
4243
//
4344
// The mock server will return a successful response when the subdomain is
44-
// `MockDiscoverySubdomain`, and the identity API URL in that response will be
45-
// `identityAPIURL`.
45+
// `MockDiscoverySubdomain`, and the API URLs in the response will match those
46+
// supplied in `services`.
4647
// Other subdomains, can be used to trigger various failure responses.
48+
//
4749
// The returned HTTP client has a transport which logs requests and responses
4850
// depending on log level of the logger supplied in the context.
49-
func MockDiscoveryServer(t testing.TB, identityAPIURL string) *http.Client {
51+
func MockDiscoveryServer(t *testing.T, services Services) *http.Client {
5052
tmpl := template.Must(template.New("mockDiscoverySuccess").Parse(discoverySuccessTemplate))
5153
buf := &bytes.Buffer{}
52-
err := tmpl.Execute(buf, struct{ IdentityAPIURL string }{identityAPIURL})
54+
err := tmpl.Execute(buf, services)
5355
if err != nil {
5456
panic(err)
5557
}
Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,98 @@
1-
{"data_privacy": {"ui": "https://ui.dataprivacy.integration-cyberark.cloud/", "api": "https://us-east-1.dataprivacy.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-data_privacy.integration-cyberark.cloud", "region": "us-east-1"}, "secrets_manager": {"ui": "https://ui.test-conjur.cloud", "api": "https://venafi-test.secretsmgr.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-secrets_manager.integration-cyberark.cloud", "region": "us-east-2"}, "idaptive_risk_analytics": {"ui": "https://ajp5871-my.analytics.idaptive.qa", "api": "https://ajp5871-my.analytics.idaptive.qa", "bootstrap": "https://venafi-test-idaptive_risk_analytics.integration-cyberark.cloud", "region": "US-East-Pod"}, "component_manager": {"ui": "https://ui-connectormanagement.connectormanagement.integration-cyberark.cloud", "api": "https://venafi-test.connectormanagement.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-component_manager.integration-cyberark.cloud", "region": "us-east-1"}, "recording": {"ui": "https://us-east-1.rec-ui.recording.integration-cyberark.cloud", "api": "https://venafi-test.recording.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-recording.integration-cyberark.cloud", "region": "us-east-1"}, "identity_user_portal": {"ui": "https://ajp5871.id.integration-cyberark.cloud", "api": "https://ajp5871.id.integration-cyberark.cloud", "bootstrap": "https://venafi-test-identity_user_portal.integration-cyberark.cloud/my", "region": "US-East-Pod"}, "userportal": {"ui": "https://us-east-1.ui.userportal.integration-cyberark.cloud/", "api": "https://venafi-test.api.userportal.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-userportal.integration-cyberark.cloud", "region": "us-east-1"}, "cloud_onboarding": {"ui": "https://ui-cloudonboarding.cloudonboarding.integration-cyberark.cloud/", "api": "https://venafi-test.cloudonboarding.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-cloud_onboarding.integration-cyberark.cloud", "region": "us-east-1"}, "identity_administration": {"ui": "https://ajp5871.id.integration-cyberark.cloud", "api": "{{ .IdentityAPIURL }}", "bootstrap": "https://venafi-test-identity_administration.integration-cyberark.cloud/admin", "region": "US-East-Pod"}, "adminportal": {"ui": "https://ui-adminportal.adminportal.integration-cyberark.cloud", "api": "https://venafi-test.adminportal.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-adminportal.integration-cyberark.cloud", "region": "us-east-1"}, "analytics": {"ui": "https://venafi-test.analytics.integration-cyberark.cloud/", "api": "https://venafi-test.analytics.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-analytics.integration-cyberark.cloud", "region": "us-east-1"}, "session_monitoring": {"ui": "https://us-east-1.sm-ui.sessionmonitoring.integration-cyberark.cloud", "api": "https://venafi-test.sessionmonitoring.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-session_monitoring.integration-cyberark.cloud", "region": "us-east-1"}, "audit": {"ui": "https://ui.audit-ui.integration-cyberark.cloud", "api": "https://venafi-test.audit.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-audit.integration-cyberark.cloud", "region": "us-east-1"}, "fmcdp": {"ui": "https://tagtig.io/", "api": "https://tagtig.io/api", "bootstrap": "https://venafi-test-fmcdp.integration-cyberark.cloud", "region": "us-east-1"}, "featureadopt": {"ui": "https://ui-featureadopt.featureadopt.integration-cyberark.cloud/", "api": "https://us-east-1-featureadopt.featureadopt.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-featureadopt.integration-cyberark.cloud", "region": "us-east-1"}}
1+
{
2+
"data_privacy": {
3+
"ui": "https://ui.dataprivacy.integration-cyberark.cloud/",
4+
"api": "https://us-east-1.dataprivacy.integration-cyberark.cloud/api",
5+
"bootstrap": "https://venafi-test-data_privacy.integration-cyberark.cloud",
6+
"region": "us-east-1"
7+
},
8+
"secrets_manager": {
9+
"ui": "https://ui.test-conjur.cloud",
10+
"api": "https://venafi-test.secretsmgr.integration-cyberark.cloud/api",
11+
"bootstrap": "https://venafi-test-secrets_manager.integration-cyberark.cloud",
12+
"region": "us-east-2"
13+
},
14+
"idaptive_risk_analytics": {
15+
"ui": "https://ajp5871-my.analytics.idaptive.qa",
16+
"api": "https://ajp5871-my.analytics.idaptive.qa",
17+
"bootstrap": "https://venafi-test-idaptive_risk_analytics.integration-cyberark.cloud",
18+
"region": "US-East-Pod"
19+
},
20+
"component_manager": {
21+
"ui": "https://ui-connectormanagement.connectormanagement.integration-cyberark.cloud",
22+
"api": "https://venafi-test.connectormanagement.integration-cyberark.cloud/api",
23+
"bootstrap": "https://venafi-test-component_manager.integration-cyberark.cloud",
24+
"region": "us-east-1"
25+
},
26+
"recording": {
27+
"ui": "https://us-east-1.rec-ui.recording.integration-cyberark.cloud",
28+
"api": "https://venafi-test.recording.integration-cyberark.cloud/api",
29+
"bootstrap": "https://venafi-test-recording.integration-cyberark.cloud",
30+
"region": "us-east-1"
31+
},
32+
"identity_user_portal": {
33+
"ui": "https://ajp5871.id.integration-cyberark.cloud",
34+
"api": "https://ajp5871.id.integration-cyberark.cloud",
35+
"bootstrap": "https://venafi-test-identity_user_portal.integration-cyberark.cloud/my",
36+
"region": "US-East-Pod"
37+
},
38+
"userportal": {
39+
"ui": "https://us-east-1.ui.userportal.integration-cyberark.cloud/",
40+
"api": "https://venafi-test.api.userportal.integration-cyberark.cloud/api",
41+
"bootstrap": "https://venafi-test-userportal.integration-cyberark.cloud",
42+
"region": "us-east-1"
43+
},
44+
"cloud_onboarding": {
45+
"ui": "https://ui-cloudonboarding.cloudonboarding.integration-cyberark.cloud/",
46+
"api": "https://venafi-test.cloudonboarding.integration-cyberark.cloud/api",
47+
"bootstrap": "https://venafi-test-cloud_onboarding.integration-cyberark.cloud",
48+
"region": "us-east-1"
49+
},
50+
"identity_administration": {
51+
"ui": "https://ajp5871.id.integration-cyberark.cloud",
52+
"api": "{{ .Identity.API }}",
53+
"bootstrap": "https://venafi-test-identity_administration.integration-cyberark.cloud/admin",
54+
"region": "US-East-Pod"
55+
},
56+
"adminportal": {
57+
"ui": "https://ui-adminportal.adminportal.integration-cyberark.cloud",
58+
"api": "https://venafi-test.adminportal.integration-cyberark.cloud/api",
59+
"bootstrap": "https://venafi-test-adminportal.integration-cyberark.cloud",
60+
"region": "us-east-1"
61+
},
62+
"analytics": {
63+
"ui": "https://venafi-test.analytics.integration-cyberark.cloud/",
64+
"api": "https://venafi-test.analytics.integration-cyberark.cloud/api",
65+
"bootstrap": "https://venafi-test-analytics.integration-cyberark.cloud",
66+
"region": "us-east-1"
67+
},
68+
"session_monitoring": {
69+
"ui": "https://us-east-1.sm-ui.sessionmonitoring.integration-cyberark.cloud",
70+
"api": "https://venafi-test.sessionmonitoring.integration-cyberark.cloud/api",
71+
"bootstrap": "https://venafi-test-session_monitoring.integration-cyberark.cloud",
72+
"region": "us-east-1"
73+
},
74+
"audit": {
75+
"ui": "https://ui.audit-ui.integration-cyberark.cloud",
76+
"api": "https://venafi-test.audit.integration-cyberark.cloud/api",
77+
"bootstrap": "https://venafi-test-audit.integration-cyberark.cloud",
78+
"region": "us-east-1"
79+
},
80+
"fmcdp": {
81+
"ui": "https://tagtig.io/",
82+
"api": "https://tagtig.io/api",
83+
"bootstrap": "https://venafi-test-fmcdp.integration-cyberark.cloud",
84+
"region": "us-east-1"
85+
},
86+
"featureadopt": {
87+
"ui": "https://ui-featureadopt.featureadopt.integration-cyberark.cloud/",
88+
"api": "https://us-east-1-featureadopt.featureadopt.integration-cyberark.cloud/api",
89+
"bootstrap": "https://venafi-test-featureadopt.integration-cyberark.cloud",
90+
"region": "us-east-1"
91+
},
92+
"discoverycontext": {
93+
"ui": "https://ui-inventory.inventory.integration-cyberark.cloud/",
94+
"api": "{{ .DiscoveryContext.API }}",
95+
"bootstrap": "https://venafi-test-discoverycontext.integration-cyberark.cloud",
96+
"region": "us-east-1"
97+
}
98+
}

0 commit comments

Comments
 (0)