Skip to content

Commit 787f14e

Browse files
author
Mladen Rusev
committed
Deprecate old service-discovery API and impl new one
1 parent 2d030d4 commit 787f14e

File tree

7 files changed

+278
-109
lines changed

7 files changed

+278
-109
lines changed

.envrc.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export ARK_SUBDOMAIN= # your CyberArk tenant subdomain
1818
export ARK_USERNAME= # your CyberArk username
1919
export ARK_SECRET= # your CyberArk password
2020
# OPTIONAL: the URL for the CyberArk Discovery API if not using the production environment
21-
export ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
21+
export ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/tenant-discovery/public

pkg/internal/cyberark/client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestCyberArkClient_PutSnapshot_MockAPI(t *testing.T) {
4848
// ARK_SUBDOMAIN should be your tenant subdomain.
4949
//
5050
// To test against a tenant on the integration platform, also set:
51-
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
51+
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/tenant-discovery/public
5252
//
5353
// To enable verbose request logging:
5454
//

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
// the login is successful.
2323
//
2424
// To test against a tenant on the integration platform, set:
25-
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
25+
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/tenant-discovery/public
2626
const (
2727
subdomainFlag = "subdomain"
2828
usernameFlag = "username"

pkg/internal/cyberark/servicediscovery/discovery.go

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
"net/http"
99
"net/url"
1010
"os"
11+
"path"
1112

1213
"github.com/jetstack/preflight/pkg/version"
1314
)
1415

1516
const (
1617
// ProdDiscoveryAPIBaseURL is the base URL for the production CyberArk Service Discovery API
17-
ProdDiscoveryAPIBaseURL = "https://platform-discovery.cyberark.cloud/api/v2/"
18+
ProdDiscoveryAPIBaseURL = "https://platform-discovery.cyberark.cloud/"
1819

1920
// IdentityServiceName is the name of the identity service we're looking for in responses from the Service Discovery API
2021
// We were told to use the identity_administration field, not the identity_user_portal field.
@@ -53,32 +54,52 @@ func New(httpClient *http.Client) *Client {
5354
return client
5455
}
5556

57+
// DiscoveryResponse represents the full JSON response returned by the CyberArk api/tenant-discovery/public API
58+
// The API is documented here https://ca-il-confluence.il.cyber-ark.com/spaces/EV/pages/575618345/Updated+PD+APIs+doc
59+
type DiscoveryResponse struct {
60+
Region string `json:"region"`
61+
DRRegion string `json:"dr_region"`
62+
Subdomain string `json:"subdomain"`
63+
PlatformID string `json:"platform_id"`
64+
IdentityID string `json:"identity_id"`
65+
DefaultURL string `json:"default_url"`
66+
TenantFlags map[string]string `json:"tenant_flags"`
67+
Services []Service `json:"services"`
68+
}
69+
70+
type Service struct {
71+
ServiceName string `json:"service_name"`
72+
Region string `json:"region"`
73+
Endpoints []ServiceEndpoint `json:"endpoints"`
74+
}
75+
5676
// ServiceEndpoint represents a single service endpoint returned by the CyberArk
5777
// Service Discovery API. The JSON field names here must match the field names
58-
// returned by the Service Discovery API. Currently, we only care about the
59-
// "api" field. Other fields are intentionally ignored here.
78+
// returned by the Service Discovery API.
6079
type ServiceEndpoint struct {
61-
API string `json:"api"`
80+
IsActive bool `json:"is_active"`
81+
Type string `json:"type"`
82+
UI string `json:"ui"`
83+
API string `json:"api"`
6284
}
6385

64-
// Services represents the relevant services returned by the CyberArk Service
65-
// Discovery API for a given subdomain. Currently, we only care about the
66-
// Identity API and the Discovery Context API. Other services are intentionally
67-
// ignored here. The JSON field names here must match the field names returned
68-
// by the Service Discovery API.
86+
// This is a convenience struct to hold the two ServiceEndpoints we care about.
87+
// Currently, we only care about the Identity API and the Discovery Context API.
6988
type Services struct {
70-
Identity ServiceEndpoint `json:"identity_administration"`
71-
DiscoveryContext ServiceEndpoint `json:"discoverycontext"`
89+
Identity ServiceEndpoint
90+
DiscoveryContext ServiceEndpoint
7291
}
7392

7493
// DiscoverServices fetches from the service discovery service for a given subdomain
7594
// and parses the CyberArk Identity API URL and Inventory API URL.
7695
func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Services, error) {
77-
endpoint, err := url.JoinPath(c.baseURL, "services", "subdomain", subdomain)
96+
u, err := url.Parse(c.baseURL)
7897
if err != nil {
79-
return nil, fmt.Errorf("failed to build a valid URL for subdomain %s; possibly an invalid endpoint: %s", subdomain, err)
98+
return nil, fmt.Errorf("invalid base URL for service discovery: %w", err)
8099
}
81-
100+
u.Path = path.Join(u.Path, "api/tenant-discovery/public")
101+
u.RawQuery = url.Values{"bySubdomain": []string{subdomain}}.Encode()
102+
endpoint := u.String()
82103
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
83104
if err != nil {
84105
return nil, fmt.Errorf("failed to initialise request to %s: %s", endpoint, err)
@@ -104,19 +125,42 @@ func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Servi
104125
return nil, fmt.Errorf("got unexpected status code %s from request to service discovery API", resp.Status)
105126
}
106127

107-
var services Services
108-
err = json.NewDecoder(io.LimitReader(resp.Body, maxDiscoverBodySize)).Decode(&services)
128+
var discoveryResp DiscoveryResponse
129+
err = json.NewDecoder(io.LimitReader(resp.Body, maxDiscoverBodySize)).Decode(&discoveryResp)
109130
if err != nil {
110131
if err == io.ErrUnexpectedEOF {
111132
return nil, fmt.Errorf("rejecting JSON response from server as it was too large or was truncated")
112133
}
113-
114134
return nil, fmt.Errorf("failed to parse JSON from otherwise successful request to service discovery endpoint: %s", err)
115135
}
136+
var identityAPI, discoveryContextAPI string
137+
for _, svc := range discoveryResp.Services {
138+
switch svc.ServiceName {
139+
case IdentityServiceName:
140+
for _, ep := range svc.Endpoints {
141+
if ep.Type == "main" && ep.IsActive && ep.API != "" {
142+
identityAPI = ep.API
143+
break
144+
}
145+
}
146+
case DiscoveryContextServiceName:
147+
for _, ep := range svc.Endpoints {
148+
if ep.Type == "main" && ep.IsActive && ep.API != "" {
149+
discoveryContextAPI = ep.API
150+
break
151+
}
152+
}
153+
}
154+
}
116155

117-
if services.Identity.API == "" {
118-
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)
156+
if identityAPI == "" {
157+
return nil, fmt.Errorf("didn't find %s in service discovery response, "+
158+
"which may indicate a suspended tenant; unable to detect CyberArk Identity API URL", IdentityServiceName)
119159
}
160+
//TODO: Should add a check for discoveryContextAPI too?
120161

121-
return &services, nil
162+
return &Services{
163+
Identity: ServiceEndpoint{API: identityAPI},
164+
DiscoveryContext: ServiceEndpoint{API: discoveryContextAPI},
165+
}, nil
122166
}

pkg/internal/cyberark/servicediscovery/mock.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ const (
2323
MockDiscoverySubdomain = "venafi-test"
2424

2525
mockIdentityAPIURL = "https://ajp5871.id.integration-cyberark.cloud"
26-
mockDiscoveryContextAPIURL = "https://venafi-test.inventory.integration-cyberark.cloud/api"
26+
mockDiscoveryContextAPIURL = "https://venafi-test.inventory.integration-cyberark.cloud/"
27+
prefix = "/api/tenant-discovery/public?bySubdomain="
2728
)
2829

2930
//go:embed testdata/discovery_success.json.template
@@ -77,7 +78,7 @@ func (mds *mockDiscoveryServer) ServeHTTP(w http.ResponseWriter, r *http.Request
7778
return
7879
}
7980

80-
if !strings.HasPrefix(r.URL.String(), "/services/subdomain/") {
81+
if !strings.HasPrefix(r.URL.String(), prefix) {
8182
// This was observed by making a request to /api/v2/services/asd
8283
// Normally, we'd expect 404 Not Found but we match the observed response here
8384
w.WriteHeader(http.StatusForbidden)
@@ -97,15 +98,30 @@ func (mds *mockDiscoveryServer) ServeHTTP(w http.ResponseWriter, r *http.Request
9798
return
9899
}
99100

100-
subdomain := strings.TrimPrefix(r.URL.String(), "/services/subdomain/")
101+
subdomain := strings.TrimPrefix(r.URL.String(), prefix)
101102

102103
switch subdomain {
103104
case MockDiscoverySubdomain:
104105
_, _ = w.Write([]byte(mds.successResponse))
105106

106107
case "no-identity":
107108
// return a snippet of valid service discovery JSON, but don't include the identity service
108-
_, _ = w.Write([]byte(`{"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"}}`))
109+
_, _ = w.Write([]byte(`{
110+
"services": [
111+
{
112+
"service_name": "data_privacy",
113+
"region": "us-east-1",
114+
"endpoints": [
115+
{
116+
"is_active": true,
117+
"type": "main",
118+
"ui": "https://ui.dataprivacy.integration-cyberark.cloud/",
119+
"api": "https://us-east-1.dataprivacy.integration-cyberark.cloud/api"
120+
}
121+
]
122+
}
123+
]
124+
}`))
109125

110126
case "bad-request":
111127
// test how the client handles a random unexpected response

pkg/internal/cyberark/servicediscovery/testdata/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ All data in this folder is derived from an unauthenticated endpoint accessible f
44

55
To get the original data:
66

7+
NOTE: This API is not implemented yet as of 02.09.2025 but is expected to be finalised by end of PI3 2025.
78
```bash
8-
curl -fsSL "${ARK_DISCOVERY_API}/services/subdomain/${ARK_SUBDOMAIN}" | jq
9+
curl -fsSL "${ARK_DISCOVERY_API}/api/tenant-discovery/public?bySubdomain=${ARK_SUBDOMAIN}" | jq
910
```
1011

1112
Then replace `identity_administration.api` with `{{ .Identity.API }}` and

0 commit comments

Comments
 (0)