@@ -13,6 +13,7 @@ import (
1313)
1414
1515const (
16+ // ProdDiscoveryAPIBaseURL is the base URL for the production CyberArk Service Discovery API
1617 ProdDiscoveryAPIBaseURL = "https://platform-discovery.cyberark.cloud/api/v2/"
1718
1819 // 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 {
3233 baseURL string
3334}
3435
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.
3639func New (httpClient * http.Client ) * Client {
3740 baseURL := os .Getenv ("ARK_DISCOVERY_API" )
3841 if baseURL == "" {
@@ -46,25 +49,43 @@ func New(httpClient *http.Client) *Client {
4649 return client
4750}
4851
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 ) {
5273 endpoint , err := url .JoinPath (c .baseURL , "services" , "subdomain" , subdomain )
5374 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 )
5576 }
5677
5778 request , err := http .NewRequestWithContext (ctx , http .MethodGet , endpoint , nil )
5879 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 )
6081 }
6182
6283 request .Header .Set ("Accept" , "application/json" )
6384 version .SetUserAgent (request )
6485
6586 resp , err := c .client .Do (request )
6687 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 )
6889 }
6990
7091 defer resp .Body .Close ()
@@ -73,32 +94,26 @@ func (c *Client) DiscoverIdentityAPIURL(ctx context.Context, subdomain string) (
7394 // a 404 error is returned with an empty JSON body "{}" if the subdomain is unknown; at the time of writing, we haven't observed
7495 // any other errors and so we can't special case them
7596 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 )
7798 }
7899
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 )
85101 }
86102
87- decodedResponse := make ( map [ string ] ServiceEndpoint )
103+ var services Services
88104
89- err = json .NewDecoder (io .LimitReader (resp .Body , maxDiscoverBodySize )).Decode (& decodedResponse )
105+ err = json .NewDecoder (io .LimitReader (resp .Body , maxDiscoverBodySize )).Decode (& services )
90106 if err != nil {
91107 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" )
93109 }
94110
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 )
96112 }
97113
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 )
101116 }
102117
103- return identityService . API , nil
118+ return & services , nil
104119}
0 commit comments