Skip to content

Commit cd52d4d

Browse files
Refactor the CyberArk identity client to take an HTTP client
So that we can more easily pass in an HTTP client which is configured with CA certificates to connect to an httptest TLS server. Signed-off-by: Richard Wall <[email protected]>
1 parent 57d3435 commit cd52d4d

File tree

7 files changed

+75
-105
lines changed

7 files changed

+75
-105
lines changed

pkg/internal/cyberark/dataupload/dataupload_test.go

Lines changed: 17 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
)
@@ -138,6 +141,10 @@ func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
138141
subdomain := os.Getenv("ARK_SUBDOMAIN")
139142
username := os.Getenv("ARK_USERNAME")
140143
secret := os.Getenv("ARK_SECRET")
144+
serviceDiscoveryAPI := os.Getenv("ARK_DISCOVERY_API")
145+
if serviceDiscoveryAPI == "" {
146+
serviceDiscoveryAPI = servicediscovery.ProdDiscoveryEndpoint
147+
}
141148

142149
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
143150
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 +161,19 @@ func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
154161

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

157-
var (
158-
identityClient *identity.Client
159-
err error
164+
var rootCAs *x509.CertPool
165+
httpClient := http_client.NewDefaultClient(version.UserAgent(), rootCAs)
166+
httpClient.Transport = transport.NewDebuggingRoundTripper(httpClient.Transport, transport.DebugByContext)
167+
168+
discoveryClient := servicediscovery.New(
169+
servicediscovery.WithHTTPClient(httpClient),
170+
servicediscovery.WithCustomEndpoint(serviceDiscoveryAPI),
160171
)
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-
}
172+
173+
identityAPI, err := discoveryClient.DiscoverIdentityAPIURL(ctx, subdomain)
167174
require.NoError(t, err)
168175

176+
identityClient := identity.New(httpClient, identityAPI, subdomain)
169177
err = identityClient.LoginUsernamePassword(ctx, username, []byte(secret))
170178
require.NoError(t, err)
171179

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: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,30 @@ 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.
1923

2024
const (
21-
subdomainFlag = "subdomain"
22-
usernameFlag = "username"
23-
passwordEnv = "TESTIDENTITY_PASSWORD"
25+
subdomainFlag = "subdomain"
26+
usernameFlag = "username"
27+
passwordEnv = "ARK_SECRET"
28+
serviceDiscoveryAPIEnv = "ARK_DISCOVERY_API"
2429
)
2530

2631
var (
@@ -41,16 +46,30 @@ func run(ctx context.Context) error {
4146
if password == "" {
4247
return fmt.Errorf("no password provided in %s", passwordEnv)
4348
}
44-
sdClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
4549

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

68+
client := identity.New(httpClient, identityAPI, subdomain)
69+
5170
err = client.LoginUsernamePassword(ctx, username, []byte(password))
5271
if err != nil {
53-
return err
72+
return fmt.Errorf("while performing login with username and password: %s", err)
5473
}
5574

5675
return nil
@@ -61,7 +80,7 @@ func main() {
6180

6281
flagSet := flag.NewFlagSet("test", flag.ExitOnError)
6382
klog.InitFlags(flagSet)
64-
_ = flagSet.Parse([]string{"--v", "5"})
83+
_ = flagSet.Parse([]string{"--v", "6"})
6584

6685
logger := klog.Background()
6786

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)