Skip to content

Commit 0b4b1aa

Browse files
Merge pull request #698 from jetstack/VC-43403-identity-client
[VC-43403] Refactor the CyberArk identity client to take an HTTP client
2 parents 57d3435 + 4114d53 commit 0b4b1aa

File tree

7 files changed

+81
-106
lines changed

7 files changed

+81
-106
lines changed

pkg/internal/cyberark/dataupload/dataupload_test.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/jetstack/venafi-connection-lib/http_client"
1213
"github.com/stretchr/testify/require"
14+
"k8s.io/client-go/transport"
1315
"k8s.io/klog/v2"
1416
"k8s.io/klog/v2/ktesting"
1517

1618
"github.com/jetstack/preflight/api"
1719
"github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
1820
"github.com/jetstack/preflight/pkg/internal/cyberark/identity"
1921
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
22+
"github.com/jetstack/preflight/pkg/version"
2023

2124
_ "k8s.io/klog/v2/ktesting/init"
2225
)
@@ -129,6 +132,9 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
129132
// ARK_SUBDOMAIN should be your tenant subdomain.
130133
// ARK_PLATFORM_DOMAIN should be either integration-cyberark.cloud or cyberark.cloud
131134
//
135+
// To test against a tenant on the integration platform, also set:
136+
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
137+
//
132138
// To enable verbose request logging:
133139
//
134140
// go test ./pkg/internal/cyberark/dataupload/... \
@@ -138,6 +144,10 @@ func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
138144
subdomain := os.Getenv("ARK_SUBDOMAIN")
139145
username := os.Getenv("ARK_USERNAME")
140146
secret := os.Getenv("ARK_SECRET")
147+
serviceDiscoveryAPI := os.Getenv("ARK_DISCOVERY_API")
148+
if serviceDiscoveryAPI == "" {
149+
serviceDiscoveryAPI = servicediscovery.ProdDiscoveryEndpoint
150+
}
141151

142152
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
143153
t.Skip("Skipping because one of the following environment variables is unset or empty: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
@@ -154,18 +164,19 @@ func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
154164

155165
serviceURL := fmt.Sprintf("https://%s%s%s.%s", subdomain, separator, discoveryContextServiceName, platformDomain)
156166

157-
var (
158-
identityClient *identity.Client
159-
err error
167+
var rootCAs *x509.CertPool
168+
httpClient := http_client.NewDefaultClient(version.UserAgent(), rootCAs)
169+
httpClient.Transport = transport.NewDebuggingRoundTripper(httpClient.Transport, transport.DebugByContext)
170+
171+
discoveryClient := servicediscovery.New(
172+
servicediscovery.WithHTTPClient(httpClient),
173+
servicediscovery.WithCustomEndpoint(serviceDiscoveryAPI),
160174
)
161-
if platformDomain == "cyberark.cloud" {
162-
identityClient, err = identity.New(ctx, subdomain)
163-
} else {
164-
discoveryClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
165-
identityClient, err = identity.NewWithDiscoveryClient(ctx, discoveryClient, subdomain)
166-
}
175+
176+
identityAPI, err := discoveryClient.DiscoverIdentityAPIURL(ctx, subdomain)
167177
require.NoError(t, err)
168178

179+
identityClient := identity.New(httpClient, identityAPI, subdomain)
169180
err = identityClient.LoginUsernamePassword(ctx, username, []byte(secret))
170181
require.NoError(t, err)
171182

pkg/internal/cyberark/identity/advance_authentication_test.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,11 @@ func Test_IdentityAdvanceAuthentication(t *testing.T) {
9999
t.Run(name, func(t *testing.T) {
100100
ctx := t.Context()
101101

102-
identityServer := MockIdentityServer()
103-
defer identityServer.Close()
102+
identityAPI, httpClient := MockIdentityServer(t)
104103

105-
mockDiscoveryServer := servicediscovery.MockDiscoveryServerWithCustomAPIURL(identityServer.Server.URL)
106-
defer mockDiscoveryServer.Close()
104+
client := New(httpClient, identityAPI, servicediscovery.MockDiscoverySubdomain)
107105

108-
discoveryClient := servicediscovery.New(servicediscovery.WithCustomEndpoint(mockDiscoveryServer.Server.URL))
109-
110-
client, err := NewWithDiscoveryClient(ctx, discoveryClient, servicediscovery.MockDiscoverySubdomain)
111-
if err != nil {
112-
t.Errorf("failed to create identity client: %s", err)
113-
return
114-
}
115-
116-
err = client.doAdvanceAuthentication(ctx, testSpec.username, &testSpec.password, testSpec.advanceBody)
106+
err := client.doAdvanceAuthentication(ctx, testSpec.username, &testSpec.password, testSpec.advanceBody)
117107
if testSpec.expectedError != err {
118108
if testSpec.expectedError == nil {
119109
t.Errorf("didn't expect an error but got %v", err)

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,32 @@ package main
22

33
import (
44
"context"
5+
"crypto/x509"
56
"flag"
67
"fmt"
78
"os"
89
"os/signal"
910

11+
"github.com/jetstack/venafi-connection-lib/http_client"
12+
"k8s.io/client-go/transport"
1013
"k8s.io/klog/v2"
1114

1215
"github.com/jetstack/preflight/pkg/internal/cyberark/identity"
1316
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
17+
"github.com/jetstack/preflight/pkg/version"
1418
)
1519

1620
// This is a trivial CLI application for testing our identity client end-to-end.
1721
// It's not intended for distribution; it simply allows us to run our client and check
1822
// the login is successful.
19-
23+
//
24+
// To test against a tenant on the integration platform, set:
25+
// ARK_DISCOVERY_API=https://platform-discovery.integration-cyberark.cloud/api/v2
2026
const (
21-
subdomainFlag = "subdomain"
22-
usernameFlag = "username"
23-
passwordEnv = "TESTIDENTITY_PASSWORD"
27+
subdomainFlag = "subdomain"
28+
usernameFlag = "username"
29+
passwordEnv = "ARK_SECRET"
30+
serviceDiscoveryAPIEnv = "ARK_DISCOVERY_API"
2431
)
2532

2633
var (
@@ -41,16 +48,30 @@ func run(ctx context.Context) error {
4148
if password == "" {
4249
return fmt.Errorf("no password provided in %s", passwordEnv)
4350
}
44-
sdClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
4551

46-
client, err := identity.NewWithDiscoveryClient(ctx, sdClient, subdomain)
52+
serviceDiscoveryAPI := os.Getenv(serviceDiscoveryAPIEnv)
53+
if serviceDiscoveryAPI == "" {
54+
serviceDiscoveryAPI = servicediscovery.ProdDiscoveryEndpoint
55+
}
56+
57+
var rootCAs *x509.CertPool
58+
httpClient := http_client.NewDefaultClient(version.UserAgent(), rootCAs)
59+
httpClient.Transport = transport.NewDebuggingRoundTripper(httpClient.Transport, transport.DebugByContext)
60+
61+
sdClient := servicediscovery.New(
62+
servicediscovery.WithHTTPClient(httpClient),
63+
servicediscovery.WithCustomEndpoint(serviceDiscoveryAPI),
64+
)
65+
identityAPI, err := sdClient.DiscoverIdentityAPIURL(ctx, subdomain)
4766
if err != nil {
48-
return err
67+
return fmt.Errorf("while performing service discovery: %s", err)
4968
}
5069

70+
client := identity.New(httpClient, identityAPI, subdomain)
71+
5172
err = client.LoginUsernamePassword(ctx, username, []byte(password))
5273
if err != nil {
53-
return err
74+
return fmt.Errorf("while performing login with username and password: %s", err)
5475
}
5576

5677
return nil
@@ -61,7 +82,7 @@ func main() {
6182

6283
flagSet := flag.NewFlagSet("test", flag.ExitOnError)
6384
klog.InitFlags(flagSet)
64-
_ = flagSet.Parse([]string{"--v", "5"})
85+
_ = flagSet.Parse([]string{"--v", "6"})
6586

6687
logger := klog.Background()
6788

pkg/internal/cyberark/identity/identity.go

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ import (
1212
"time"
1313

1414
"github.com/cenkalti/backoff/v5"
15-
"k8s.io/client-go/transport"
1615
"k8s.io/klog/v2"
1716

18-
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
1917
"github.com/jetstack/preflight/pkg/logs"
2018
"github.com/jetstack/preflight/pkg/version"
2119
)
@@ -177,10 +175,9 @@ type advanceAuthenticationResponseResult struct {
177175
// Client is an client for interacting with the CyberArk Identity API and performing a login using a username and password.
178176
// For context on the behaviour of this client, see the Python SDK: https://github.com/cyberark/ark-sdk-python/blob/3be12c3f2d3a2d0407025028943e584b6edc5996/ark_sdk_python/auth/identity/ark_identity.py
179177
type Client struct {
180-
client *http.Client
181-
182-
endpoint string
183-
subdomain string
178+
httpClient *http.Client
179+
baseURL string
180+
subdomain string
184181

185182
tokenCached token
186183
tokenCachedMutex sync.Mutex
@@ -190,39 +187,15 @@ type Client struct {
190187
type token string
191188

192189
// New returns an initialized CyberArk Identity client using a default service discovery client.
193-
// NB: This function performs service discovery when called, in order to ensure that all Identity
194-
// clients are created with a valid Identity API URL. This function blocks on the network call to
195-
// the discovery service.
196-
func New(ctx context.Context, subdomain string) (*Client, error) {
197-
return NewWithDiscoveryClient(ctx, servicediscovery.New(), subdomain)
198-
}
199-
200-
// NewWithDiscoveryClient returns an initialized CyberArk Identity client using the given service discovery client.
201-
// NB: This function performs service discovery when called, in order to ensure that all Identity
202-
// clients are created with a valid Identity API URL. This function blocks on the network call to
203-
// the discovery service.
204-
func NewWithDiscoveryClient(ctx context.Context, discoveryClient *servicediscovery.Client, subdomain string) (*Client, error) {
205-
if discoveryClient == nil {
206-
return nil, fmt.Errorf("must provide a non-nil discovery client to the Identity Client")
207-
}
208-
209-
endpoint, err := discoveryClient.DiscoverIdentityAPIURL(ctx, subdomain)
210-
if err != nil {
211-
return nil, err
212-
}
213-
190+
func New(httpClient *http.Client, baseURL string, subdomain string) *Client {
214191
return &Client{
215-
client: &http.Client{
216-
Timeout: 10 * time.Second,
217-
Transport: transport.NewDebuggingRoundTripper(http.DefaultTransport, transport.DebugByContext),
218-
},
219-
220-
endpoint: endpoint,
221-
subdomain: subdomain,
192+
httpClient: httpClient,
193+
baseURL: baseURL,
194+
subdomain: subdomain,
222195

223196
tokenCached: "",
224197
tokenCachedMutex: sync.Mutex{},
225-
}, nil
198+
}
226199
}
227200

228201
// LoginUsernamePassword performs a blocking call to fetch an auth token from CyberArk Identity using the given username and password.
@@ -282,7 +255,7 @@ func (c *Client) doStartAuthentication(ctx context.Context, username string) (ad
282255
return response, fmt.Errorf("failed to marshal JSON for request to StartAuthentication endpoint: %s", err)
283256
}
284257

285-
endpoint, err := url.JoinPath(c.endpoint, "Security", "StartAuthentication")
258+
endpoint, err := url.JoinPath(c.baseURL, "Security", "StartAuthentication")
286259
if err != nil {
287260
return response, fmt.Errorf("failed to create URL for request to CyberArk Identity StartAuthentication: %s", err)
288261
}
@@ -294,7 +267,7 @@ func (c *Client) doStartAuthentication(ctx context.Context, username string) (ad
294267

295268
setIdentityHeaders(request)
296269

297-
httpResponse, err := c.client.Do(request)
270+
httpResponse, err := c.httpClient.Do(request)
298271
if err != nil {
299272
return response, fmt.Errorf("failed to perform HTTP request to start authentication: %s", err)
300273
}
@@ -391,7 +364,7 @@ func (c *Client) doAdvanceAuthentication(ctx context.Context, username string, p
391364
return backoff.Permanent(fmt.Errorf("failed to marshal JSON for request to AdvanceAuthentication endpoint: %s", err))
392365
}
393366

394-
endpoint, err := url.JoinPath(c.endpoint, "Security", "AdvanceAuthentication")
367+
endpoint, err := url.JoinPath(c.baseURL, "Security", "AdvanceAuthentication")
395368
if err != nil {
396369
return backoff.Permanent(fmt.Errorf("failed to create URL for request to CyberArk Identity AdvanceAuthentication: %s", err))
397370
}
@@ -403,7 +376,7 @@ func (c *Client) doAdvanceAuthentication(ctx context.Context, username string, p
403376

404377
setIdentityHeaders(request)
405378

406-
httpResponse, err := c.client.Do(request)
379+
httpResponse, err := c.httpClient.Do(request)
407380
if err != nil {
408381
return fmt.Errorf("failed to perform HTTP request to advance authentication: %s", err)
409382
}

pkg/internal/cyberark/identity/mock.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"fmt"
77
"net/http"
88
"net/http/httptest"
9+
"testing"
10+
11+
"k8s.io/client-go/transport"
912

1013
"github.com/jetstack/preflight/pkg/version"
1114

@@ -51,22 +54,17 @@ var (
5154
advanceAuthenticationFailureResponse string
5255
)
5356

54-
type mockIdentityServer struct {
55-
Server *httptest.Server
56-
}
57+
type mockIdentityServer struct{}
5758

58-
// MockIdentityServer returns a mocked CyberArk Identity server.
59-
// The returned server should be Closed by the caller after use.
60-
func MockIdentityServer() *mockIdentityServer {
59+
// MockIdentityServer returns a URL of a mocked CyberArk identity server and an
60+
// HTTP client with the CA certs needed to connect to it..
61+
func MockIdentityServer(t *testing.T) (string, *http.Client) {
6162
mis := &mockIdentityServer{}
62-
63-
mis.Server = httptest.NewServer(mis)
64-
65-
return mis
66-
}
67-
68-
func (mis *mockIdentityServer) Close() {
69-
mis.Server.Close()
63+
server := httptest.NewTLSServer(mis)
64+
t.Cleanup(server.Close)
65+
httpClient := server.Client()
66+
httpClient.Transport = transport.NewDebuggingRoundTripper(httpClient.Transport, transport.DebugByContext)
67+
return server.URL, httpClient
7068
}
7169

7270
func (mis *mockIdentityServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {

pkg/internal/cyberark/identity/start_authentication_test.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,9 @@ func Test_IdentityStartAuthentication(t *testing.T) {
4040
t.Run(name, func(t *testing.T) {
4141
ctx := t.Context()
4242

43-
identityServer := MockIdentityServer()
44-
defer identityServer.Close()
43+
identityServer, httpClient := MockIdentityServer(t)
4544

46-
mockDiscoveryServer := servicediscovery.MockDiscoveryServerWithCustomAPIURL(identityServer.Server.URL)
47-
defer mockDiscoveryServer.Close()
48-
49-
discoveryClient := servicediscovery.New(servicediscovery.WithCustomEndpoint(mockDiscoveryServer.Server.URL))
50-
51-
client, err := NewWithDiscoveryClient(ctx, discoveryClient, servicediscovery.MockDiscoverySubdomain)
52-
if err != nil {
53-
t.Errorf("failed to create identity client: %s", err)
54-
return
55-
}
45+
client := New(httpClient, identityServer, servicediscovery.MockDiscoverySubdomain)
5646

5747
advanceBody, err := client.doStartAuthentication(ctx, testSpec.username)
5848
if err != nil {

pkg/internal/cyberark/servicediscovery/discovery.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import (
1515
)
1616

1717
const (
18-
prodDiscoveryEndpoint = "https://platform-discovery.cyberark.cloud/api/v2/"
19-
integrationDiscoveryEndpoint = "https://platform-discovery.integration-cyberark.cloud/api/v2/"
18+
ProdDiscoveryEndpoint = "https://platform-discovery.cyberark.cloud/api/v2/"
2019

2120
// identityServiceName is the name of the identity service we're looking for in responses from the Service Discovery API
2221
// We were told to use the identity_administration field, not the identity_user_portal field.
@@ -45,13 +44,6 @@ func WithHTTPClient(httpClient *http.Client) ClientOpt {
4544
}
4645
}
4746

48-
// WithIntegrationEndpoint sets the discovery client to use the integration testing endpoint rather than production
49-
func WithIntegrationEndpoint() ClientOpt {
50-
return func(c *Client) {
51-
c.endpoint = integrationDiscoveryEndpoint
52-
}
53-
}
54-
5547
// WithCustomEndpoint sets the endpoint to a custom URL without checking that the URL is a CyberArk Service Discovery
5648
// server.
5749
func WithCustomEndpoint(endpoint string) ClientOpt {
@@ -67,7 +59,7 @@ func New(clientOpts ...ClientOpt) *Client {
6759
Timeout: 10 * time.Second,
6860
Transport: transport.NewDebuggingRoundTripper(http.DefaultTransport, transport.DebugByContext),
6961
},
70-
endpoint: prodDiscoveryEndpoint,
62+
endpoint: ProdDiscoveryEndpoint,
7163
}
7264

7365
for _, opt := range clientOpts {

0 commit comments

Comments
 (0)