@@ -13,6 +13,7 @@ import (
13
13
)
14
14
15
15
const (
16
+ // ProdDiscoveryAPIBaseURL is the base URL for the production CyberArk Service Discovery API
16
17
ProdDiscoveryAPIBaseURL = "https://platform-discovery.cyberark.cloud/api/v2/"
17
18
18
19
// identityServiceName is the name of the identity service we're looking for in responses from the Service Discovery API
@@ -32,7 +33,9 @@ type Client struct {
32
33
baseURL string
33
34
}
34
35
35
- // New creates a new CyberArk Service Discovery client, configurable with ClientOpt
36
+ // New creates a new CyberArk Service Discovery client. If the ARK_DISCOVERY_API
37
+ // environment variable is set, it is used as the base URL for the service
38
+ // discovery API. Otherwise, the production URL is used.
36
39
func New (httpClient * http.Client ) * Client {
37
40
baseURL := os .Getenv ("ARK_DISCOVERY_API" )
38
41
if baseURL == "" {
@@ -46,25 +49,43 @@ func New(httpClient *http.Client) *Client {
46
49
return client
47
50
}
48
51
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 ) {
52
+ // ServiceEndpoint represents a single service endpoint returned by the CyberArk
53
+ // Service Discovery API. The JSON field names here must match the field names
54
+ // returned by the Service Discovery API. Currently, we only care about the
55
+ // "api" field. Other fields are intentionally ignored here.
56
+ type ServiceEndpoint struct {
57
+ API string `json:"api"`
58
+ }
59
+
60
+ // Services represents the relevant services returned by the CyberArk Service
61
+ // Discovery API for a given subdomain. Currently, we only care about the
62
+ // Identity API and the Discovery Context API. Other services are intentionally
63
+ // ignored here. The JSON field names here must match the field names returned
64
+ // by the Service Discovery API.
65
+ type Services struct {
66
+ Identity ServiceEndpoint `json:"identity_administration"`
67
+ DiscoveryContext ServiceEndpoint `json:"discoverycontext"`
68
+ }
69
+
70
+ // DiscoverServices fetches from the service discovery service for a given subdomain
71
+ // and parses the CyberArk Identity API URL and Inventory API URL.
72
+ func (c * Client ) DiscoverServices (ctx context.Context , subdomain string ) (* Services , error ) {
52
73
endpoint , err := url .JoinPath (c .baseURL , "services" , "subdomain" , subdomain )
53
74
if err != nil {
54
- return "" , fmt .Errorf ("failed to build a valid URL for subdomain %s; possibly an invalid endpoint: %s" , subdomain , err )
75
+ return nil , fmt .Errorf ("failed to build a valid URL for subdomain %s; possibly an invalid endpoint: %s" , subdomain , err )
55
76
}
56
77
57
78
request , err := http .NewRequestWithContext (ctx , http .MethodGet , endpoint , nil )
58
79
if err != nil {
59
- return "" , fmt .Errorf ("failed to initialise request to %s: %s" , endpoint , err )
80
+ return nil , fmt .Errorf ("failed to initialise request to %s: %s" , endpoint , err )
60
81
}
61
82
62
83
request .Header .Set ("Accept" , "application/json" )
63
84
version .SetUserAgent (request )
64
85
65
86
resp , err := c .client .Do (request )
66
87
if err != nil {
67
- return "" , fmt .Errorf ("failed to perform HTTP request: %s" , err )
88
+ return nil , fmt .Errorf ("failed to perform HTTP request: %s" , err )
68
89
}
69
90
70
91
defer resp .Body .Close ()
@@ -73,32 +94,26 @@ func (c *Client) DiscoverIdentityAPIURL(ctx context.Context, subdomain string) (
73
94
// a 404 error is returned with an empty JSON body "{}" if the subdomain is unknown; at the time of writing, we haven't observed
74
95
// any other errors and so we can't special case them
75
96
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 )
97
+ return nil , fmt .Errorf ("got an HTTP 404 response from service discovery; maybe the subdomain %q is incorrect or does not exist?" , subdomain )
77
98
}
78
99
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
100
+ return nil , fmt .Errorf ("got unexpected status code %s from request to service discovery API" , resp .Status )
85
101
}
86
102
87
- decodedResponse := make ( map [ string ] ServiceEndpoint )
103
+ var services Services
88
104
89
- err = json .NewDecoder (io .LimitReader (resp .Body , maxDiscoverBodySize )).Decode (& decodedResponse )
105
+ err = json .NewDecoder (io .LimitReader (resp .Body , maxDiscoverBodySize )).Decode (& services )
90
106
if err != nil {
91
107
if err == io .ErrUnexpectedEOF {
92
- return "" , fmt .Errorf ("rejecting JSON response from server as it was too large or was truncated" )
108
+ return nil , fmt .Errorf ("rejecting JSON response from server as it was too large or was truncated" )
93
109
}
94
110
95
- return "" , fmt .Errorf ("failed to parse JSON from otherwise successful request to service discovery endpoint: %s" , err )
111
+ return nil , fmt .Errorf ("failed to parse JSON from otherwise successful request to service discovery endpoint: %s" , err )
96
112
}
97
113
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 )
114
+ if services .Identity .API == "" {
115
+ 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 )
101
116
}
102
117
103
- return identityService . API , nil
118
+ return & services , nil
104
119
}
0 commit comments