-
Notifications
You must be signed in to change notification settings - Fork 24
Deprecate service/discovery API and implement the new one #706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
787f14e
6134f1a
ebcf00b
b8dba8c
e97b2e7
429790e
f532eb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,13 +8,14 @@ import ( | |
| "net/http" | ||
| "net/url" | ||
| "os" | ||
| "path" | ||
|
|
||
| "github.com/jetstack/preflight/pkg/version" | ||
| ) | ||
|
|
||
| const ( | ||
| // ProdDiscoveryAPIBaseURL is the base URL for the production CyberArk Service Discovery API | ||
| ProdDiscoveryAPIBaseURL = "https://platform-discovery.cyberark.cloud/api/v2/" | ||
| ProdDiscoveryAPIBaseURL = "https://platform-discovery.cyberark.cloud/" | ||
|
|
||
| // IdentityServiceName is the name of the identity service we're looking for in responses from the Service Discovery API | ||
| // We were told to use the identity_administration field, not the identity_user_portal field. | ||
|
|
@@ -53,32 +54,54 @@ func New(httpClient *http.Client) *Client { | |
| return client | ||
| } | ||
|
|
||
| // DiscoveryResponse represents the full JSON response returned by the CyberArk api/tenant-discovery/public API | ||
| // The API is documented here https://ca-il-confluence.il.cyber-ark.com/spaces/EV/pages/575618345/Updated+PD+APIs+doc | ||
| type DiscoveryResponse struct { | ||
| Region string `json:"region"` | ||
| DRRegion string `json:"dr_region"` | ||
| Subdomain string `json:"subdomain"` | ||
| TenantID string `json:"tenant_id"` | ||
| PlatformID string `json:"platform_id"` | ||
| IdentityID string `json:"identity_id"` | ||
| DefaultURL string `json:"default_url"` | ||
| TenantFlags map[string]interface{} `json:"tenant_flags"` | ||
| Services []Service `json:"services"` | ||
| } | ||
|
|
||
| type Service struct { | ||
| ServiceName string `json:"service_name"` | ||
| ServiceSubdomains []string `json:"service_subdomains"` | ||
| Region string `json:"region"` | ||
| Endpoints []ServiceEndpoint `json:"endpoints"` | ||
| } | ||
|
|
||
| // ServiceEndpoint represents a single service endpoint returned by the CyberArk | ||
| // Service Discovery API. The JSON field names here must match the field names | ||
| // returned by the Service Discovery API. Currently, we only care about the | ||
| // "api" field. Other fields are intentionally ignored here. | ||
| // returned by the Service Discovery API. | ||
| type ServiceEndpoint struct { | ||
| API string `json:"api"` | ||
| IsActive bool `json:"is_active"` | ||
| Type string `json:"type"` | ||
| UI string `json:"ui"` | ||
| API string `json:"api"` | ||
| } | ||
|
|
||
| // Services represents the relevant services returned by the CyberArk Service | ||
| // Discovery API for a given subdomain. Currently, we only care about the | ||
| // Identity API and the Discovery Context API. Other services are intentionally | ||
| // ignored here. The JSON field names here must match the field names returned | ||
| // by the Service Discovery API. | ||
| // This is a convenience struct to hold the two ServiceEndpoints we care about. | ||
| // Currently, we only care about the Identity API and the Discovery Context API. | ||
| type Services struct { | ||
| Identity ServiceEndpoint `json:"identity_administration"` | ||
| DiscoveryContext ServiceEndpoint `json:"discoverycontext"` | ||
| Identity ServiceEndpoint | ||
| DiscoveryContext ServiceEndpoint | ||
| } | ||
|
|
||
| // DiscoverServices fetches from the service discovery service for a given subdomain | ||
| // and parses the CyberArk Identity API URL and Inventory API URL. | ||
| func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Services, error) { | ||
| endpoint, err := url.JoinPath(c.baseURL, "services", "subdomain", subdomain) | ||
| u, err := url.Parse(c.baseURL) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to build a valid URL for subdomain %s; possibly an invalid endpoint: %s", subdomain, err) | ||
| return nil, fmt.Errorf("invalid base URL for service discovery: %w", err) | ||
| } | ||
|
|
||
| u.Path = path.Join(u.Path, "api/public/tenant-discovery") | ||
| u.RawQuery = url.Values{"bySubdomain": []string{subdomain}}.Encode() | ||
| endpoint := u.String() | ||
| request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to initialise request to %s: %s", endpoint, err) | ||
|
|
@@ -104,19 +127,42 @@ func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Servi | |
| return nil, fmt.Errorf("got unexpected status code %s from request to service discovery API", resp.Status) | ||
| } | ||
|
|
||
| var services Services | ||
| err = json.NewDecoder(io.LimitReader(resp.Body, maxDiscoverBodySize)).Decode(&services) | ||
| var discoveryResp DiscoveryResponse | ||
| err = json.NewDecoder(io.LimitReader(resp.Body, maxDiscoverBodySize)).Decode(&discoveryResp) | ||
| if err != nil { | ||
| if err == io.ErrUnexpectedEOF { | ||
| return nil, fmt.Errorf("rejecting JSON response from server as it was too large or was truncated") | ||
| } | ||
|
|
||
| return nil, fmt.Errorf("failed to parse JSON from otherwise successful request to service discovery endpoint: %s", err) | ||
| } | ||
| var identityAPI, discoveryContextAPI string | ||
| for _, svc := range discoveryResp.Services { | ||
| switch svc.ServiceName { | ||
| case IdentityServiceName: | ||
| for _, ep := range svc.Endpoints { | ||
| if ep.Type == "main" && ep.IsActive && ep.API != "" { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was not sure if I should check the Type and IsActive but I've added them. I don't understand the difference between Type main and crdr.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure either. Let's merge as-is and adapt it if we learn more from the API team. |
||
| identityAPI = ep.API | ||
| break | ||
| } | ||
| } | ||
| case DiscoveryContextServiceName: | ||
| for _, ep := range svc.Endpoints { | ||
| if ep.Type == "main" && ep.IsActive && ep.API != "" { | ||
| discoveryContextAPI = ep.API | ||
| break | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if services.Identity.API == "" { | ||
| 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) | ||
| if identityAPI == "" { | ||
| 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) | ||
| } | ||
| //TODO: Should add a check for discoveryContextAPI too? | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We check for identityAPI but not for discoveryContextAPI. Was this intentional?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about adding it, but didn't know what actionable message to return if it's not found. |
||
|
|
||
| return &services, nil | ||
| return &Services{ | ||
| Identity: ServiceEndpoint{API: identityAPI}, | ||
| DiscoveryContext: ServiceEndpoint{API: discoveryContextAPI}, | ||
| }, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested this and it worked well: