Skip to content

Commit ff781e3

Browse files
feat(servicediscovery): enhance service discovery to return multiple APIs
- Updated `DiscoverIdentityAPIURL` to `DiscoverServices` for broader API support - Added `Services` struct to encapsulate multiple service endpoints - Refactored dependent modules to use the new `DiscoverServices` method - Adjusted tests and mock data to align with the updated service discovery logic - Removed unused `platformDomain` variable in `dataupload_test.go`
1 parent a49662f commit ff781e3

File tree

7 files changed

+154
-49
lines changed

7 files changed

+154
-49
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
@@ -125,7 +125,6 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
125125
// TestPostDataReadingsWithOptionsWithRealAPI demonstrates that the dataupload code works with the real inventory API.
126126
// An API token is obtained by authenticating with the ARK_USERNAME and ARK_SECRET from the environment.
127127
// ARK_SUBDOMAIN should be your tenant subdomain.
128-
// ARK_PLATFORM_DOMAIN should be either integration-cyberark.cloud or cyberark.cloud
129128
//
130129
// To test against a tenant on the integration platform, also set:
131130
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
@@ -135,36 +134,32 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
135134
// go test ./pkg/internal/cyberark/dataupload/... \
136135
// -v -count 1 -run TestPostDataReadingsWithOptionsWithRealAPI -args -testing.v 6
137136
func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
138-
platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN")
139137
subdomain := os.Getenv("ARK_SUBDOMAIN")
140138
username := os.Getenv("ARK_USERNAME")
141139
secret := os.Getenv("ARK_SECRET")
142140

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

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

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

158153
discoveryClient := servicediscovery.New(httpClient)
159154

160-
identityAPI, err := discoveryClient.DiscoverIdentityAPIURL(ctx, subdomain)
155+
services, err := discoveryClient.DiscoverServices(ctx, subdomain)
161156
require.NoError(t, err)
162157

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

167-
cyberArkClient, err := dataupload.New(httpClient, inventoryAPI, identityClient.AuthenticateRequest)
162+
cyberArkClient, err := dataupload.New(httpClient, services.DiscoveryContext.API, identityClient.AuthenticateRequest)
168163
require.NoError(t, err)
169164

170165
err = cyberArkClient.PostDataReadingsWithOptions(ctx, api.DataReadingsPost{}, dataupload.Options{

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: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package servicediscovery
33
import (
44
"fmt"
55
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
69
)
710

811
func Test_DiscoverIdentityAPIURL(t *testing.T) {
@@ -47,21 +50,26 @@ func Test_DiscoverIdentityAPIURL(t *testing.T) {
4750
t.Run(name, func(t *testing.T) {
4851
ctx := t.Context()
4952

50-
httpClient := MockDiscoveryServer(t, mockIdentityAPIURL)
53+
httpClient := MockDiscoveryServer(t, Services{
54+
Identity: ServiceEndpoint{
55+
API: mockIdentityAPIURL,
56+
},
57+
DiscoveryContext: ServiceEndpoint{
58+
API: mockDiscoveryContextAPIURL,
59+
},
60+
})
5161

5262
client := New(httpClient)
5363

54-
apiURL, err := client.DiscoverIdentityAPIURL(ctx, testSpec.subdomain)
55-
if err != nil {
56-
if err.Error() != testSpec.expectedError.Error() {
57-
t.Errorf("expectedError=%v\nobservedError=%v", testSpec.expectedError, err)
58-
}
64+
services, err := client.DiscoverServices(ctx, testSpec.subdomain)
65+
if testSpec.expectedError != nil {
66+
assert.EqualError(t, err, testSpec.expectedError.Error())
67+
assert.Nil(t, services)
68+
return
5969
}
60-
61-
// NB: we don't exit here because we also want to check the API URL is empty in the event of an error
62-
63-
if apiURL != testSpec.expectedURL {
64-
t.Errorf("expected API URL=%s\nobserved API URL=%s", testSpec.expectedURL, apiURL)
70+
require.NoError(t, err)
71+
if services.Identity.API != testSpec.expectedURL {
72+
t.Errorf("expected API URL=%s\nobserved API URL=%s", testSpec.expectedURL, services.Identity.API)
6573
}
6674
})
6775
}

pkg/internal/cyberark/servicediscovery/mock.go

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

23-
mockIdentityAPIURL = "https://ajp5871.id.integration-cyberark.cloud"
23+
mockIdentityAPIURL = "https://ajp5871.id.integration-cyberark.cloud"
24+
mockDiscoveryContextAPIURL = "https://venafi-test.inventory.integration-cyberark.cloud/api"
2425
)
2526

2627
//go:embed testdata/discovery_success.json.template
@@ -31,12 +32,12 @@ type mockDiscoveryServer struct {
3132
}
3233

3334
// MockDiscoveryServer returns a mocked discovery server with a default value for the Identity API.
34-
func MockDiscoveryServer(t *testing.T, identityAPIURL string) *http.Client {
35+
func MockDiscoveryServer(t *testing.T, services Services) *http.Client {
3536
tmpl := template.Must(template.New("mockDiscoverySuccess").Parse(discoverySuccessTemplate))
3637

3738
buf := &bytes.Buffer{}
3839

39-
err := tmpl.Execute(buf, struct{ IdentityAPIURL string }{identityAPIURL})
40+
err := tmpl.Execute(buf, services)
4041
if err != nil {
4142
panic(err)
4243
}
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)