Skip to content

Commit 49fefca

Browse files
committed
add support for fetching keys from a JWKS endpoint
This requires changing a few function signatures and plumbing some things together. Notably, I don't want to have a second service discovery client and send duplicate calls off, so I shared the service discovery client from the CyberArk client and added caching of responses to the service discovery client. I also had to share credentials for auth. Also removes encrypted-secrets example The machinehub mode is required for key fetching, but doesn't play nicely with one shot mode and the example hangs. Secret encryption is covered in the e2e tests, so just remove the example for simplicity Signed-off-by: Ashley Davis <ashley.davis@cyberark.com>
1 parent d9a7b26 commit 49fefca

File tree

22 files changed

+955
-318
lines changed

22 files changed

+955
-318
lines changed

examples/encrypted-secrets/README.md

Lines changed: 0 additions & 47 deletions
This file was deleted.

examples/encrypted-secrets/config.yaml

Lines changed: 0 additions & 41 deletions
This file was deleted.

examples/encrypted-secrets/test.sh

Lines changed: 0 additions & 65 deletions
This file was deleted.

hack/ark/test-e2e.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ kubectl apply -f "${root_dir}/hack/ark/cluster-external-secret.yaml"
101101

102102
# We use a non-existent tag and omit the `--version` flag, to work around a Helm
103103
# v4 bug. See: https://github.com/helm/helm/issues/31600
104+
# TODO: shouldn't need to set config.sendSecretValues because it will default to true in future
104105
helm upgrade agent "oci://${ARK_CHART}:NON_EXISTENT_TAG@${ARK_CHART_DIGEST}" \
105106
--install \
106107
--wait \
@@ -113,6 +114,7 @@ helm upgrade agent "oci://${ARK_CHART}:NON_EXISTENT_TAG@${ARK_CHART_DIGEST}" \
113114
--set config.clusterName="e2e-test-cluster" \
114115
--set config.clusterDescription="A temporary cluster for E2E testing. Contact @wallrj-cyberark." \
115116
--set config.period=60s \
117+
--set config.sendSecretValues=true \
116118
--set-json "podLabels={\"disco-agent.cyberark.cloud/test-id\": \"${RANDOM}\"}"
117119

118120
kubectl rollout status deployments/disco-agent --namespace "${NAMESPACE}"

internal/cyberark/client_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ func TestCyberArkClient_PutSnapshot_MockAPI(t *testing.T) {
3232
Secret: "somepassword",
3333
}
3434

35-
discoveryClient := servicediscovery.New(httpClient)
35+
discoveryClient := servicediscovery.New(httpClient, cfg.Subdomain)
3636

37-
serviceMap, tenantUUID, err := discoveryClient.DiscoverServices(t.Context(), cfg.Subdomain)
37+
serviceMap, tenantUUID, err := discoveryClient.DiscoverServices(t.Context())
3838
if err != nil {
3939
t.Fatalf("failed to discover mock services: %v", err)
4040
}
@@ -76,9 +76,9 @@ func TestCyberArkClient_PutSnapshot_RealAPI(t *testing.T) {
7676
cfg, err := cyberark.LoadClientConfigFromEnvironment()
7777
require.NoError(t, err)
7878

79-
discoveryClient := servicediscovery.New(httpClient)
79+
discoveryClient := servicediscovery.New(httpClient, cfg.Subdomain)
8080

81-
serviceMap, tenantUUID, err := discoveryClient.DiscoverServices(t.Context(), cfg.Subdomain)
81+
serviceMap, tenantUUID, err := discoveryClient.DiscoverServices(t.Context())
8282
if err != nil {
8383
t.Fatalf("failed to discover services: %v", err)
8484
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ func run(ctx context.Context) error {
5050
var rootCAs *x509.CertPool
5151
httpClient := http_client.NewDefaultClient(version.UserAgent(), rootCAs)
5252

53-
sdClient := servicediscovery.New(httpClient)
54-
services, _, err := sdClient.DiscoverServices(ctx, subdomain)
53+
sdClient := servicediscovery.New(httpClient, subdomain)
54+
services, _, err := sdClient.DiscoverServices(ctx)
5555
if err != nil {
5656
return fmt.Errorf("while performing service discovery: %s", err)
5757
}

internal/cyberark/identity/identity_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestLoginUsernamePassword_RealAPI(t *testing.T) {
5353
arktesting.SkipIfNoEnv(t)
5454
subdomain := os.Getenv("ARK_SUBDOMAIN")
5555
httpClient := http.DefaultClient
56-
services, _, err := servicediscovery.New(httpClient).DiscoverServices(t.Context(), subdomain)
56+
services, _, err := servicediscovery.New(httpClient, subdomain).DiscoverServices(t.Context())
5757
require.NoError(t, err)
5858

5959
loginUsernamePasswordTests(t, func(t testing.TB) inputs {

internal/cyberark/servicediscovery/discovery.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"net/url"
1010
"os"
1111
"path"
12+
"sync"
13+
"time"
1214

1315
arkapi "github.com/jetstack/preflight/internal/cyberark/api"
1416
"github.com/jetstack/preflight/pkg/version"
@@ -35,21 +37,34 @@ const (
3537
// users to fetch URLs for various APIs available in CyberArk. This client is specialised to
3638
// fetch only API endpoints, since only API endpoints are required by the Venafi Kubernetes Agent currently.
3739
type Client struct {
38-
client *http.Client
39-
baseURL string
40+
client *http.Client
41+
baseURL string
42+
subdomain string
43+
44+
cachedResponse *Services
45+
cachedTenantID string
46+
cachedResponseTime time.Time
47+
cachedResponseMutex sync.Mutex
4048
}
4149

4250
// New creates a new CyberArk Service Discovery client. If the ARK_DISCOVERY_API
4351
// environment variable is set, it is used as the base URL for the service
4452
// discovery API. Otherwise, the production URL is used.
45-
func New(httpClient *http.Client) *Client {
53+
func New(httpClient *http.Client, subdomain string) *Client {
4654
baseURL := os.Getenv("ARK_DISCOVERY_API")
4755
if baseURL == "" {
4856
baseURL = ProdDiscoveryAPIBaseURL
4957
}
58+
5059
client := &Client{
51-
client: httpClient,
52-
baseURL: baseURL,
60+
client: httpClient,
61+
baseURL: baseURL,
62+
subdomain: subdomain,
63+
64+
cachedResponse: nil,
65+
cachedTenantID: "",
66+
cachedResponseTime: time.Time{},
67+
cachedResponseMutex: sync.Mutex{},
5368
}
5469

5570
return client
@@ -93,17 +108,24 @@ type Services struct {
93108
DiscoveryContext ServiceEndpoint
94109
}
95110

96-
// DiscoverServices fetches from the service discovery service for a given subdomain
111+
// DiscoverServices fetches from the service discovery service for the configured subdomain
97112
// and parses the CyberArk Identity API URL and Inventory API URL.
98113
// It also returns the Tenant ID UUID corresponding to the subdomain.
99-
func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Services, string, error) {
114+
func (c *Client) DiscoverServices(ctx context.Context) (*Services, string, error) {
115+
c.cachedResponseMutex.Lock()
116+
defer c.cachedResponseMutex.Unlock()
117+
118+
if c.cachedResponse != nil && time.Since(c.cachedResponseTime) < 1*time.Hour {
119+
return c.cachedResponse, c.cachedTenantID, nil
120+
}
121+
100122
u, err := url.Parse(c.baseURL)
101123
if err != nil {
102124
return nil, "", fmt.Errorf("invalid base URL for service discovery: %w", err)
103125
}
104126

105127
u.Path = path.Join(u.Path, "api/public/tenant-discovery")
106-
u.RawQuery = url.Values{"bySubdomain": []string{subdomain}}.Encode()
128+
u.RawQuery = url.Values{"bySubdomain": []string{c.subdomain}}.Encode()
107129

108130
endpoint := u.String()
109131

@@ -127,7 +149,7 @@ func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Servi
127149
// a 404 error is returned with an empty JSON body "{}" if the subdomain is unknown; at the time of writing, we haven't observed
128150
// any other errors and so we can't special case them
129151
if resp.StatusCode == http.StatusNotFound {
130-
return nil, "", fmt.Errorf("got an HTTP 404 response from service discovery; maybe the subdomain %q is incorrect or does not exist?", subdomain)
152+
return nil, "", fmt.Errorf("got an HTTP 404 response from service discovery; maybe the subdomain %q is incorrect or does not exist?", c.subdomain)
131153
}
132154

133155
return nil, "", fmt.Errorf("got unexpected status code %s from request to service discovery API", resp.Status)
@@ -167,8 +189,14 @@ func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Servi
167189
}
168190
//TODO: Should add a check for discoveryContextAPI too?
169191

170-
return &Services{
192+
services := &Services{
171193
Identity: ServiceEndpoint{API: identityAPI},
172194
DiscoveryContext: ServiceEndpoint{API: discoveryContextAPI},
173-
}, discoveryResp.TenantID, nil
195+
}
196+
197+
c.cachedResponse = services
198+
c.cachedTenantID = discoveryResp.TenantID
199+
c.cachedResponseTime = time.Now()
200+
201+
return services, discoveryResp.TenantID, nil
174202
}

internal/cyberark/servicediscovery/discovery_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ func Test_DiscoverIdentityAPIURL(t *testing.T) {
6464
},
6565
})
6666

67-
client := New(httpClient)
67+
client := New(httpClient, testSpec.subdomain)
6868

69-
services, _, err := client.DiscoverServices(ctx, testSpec.subdomain)
69+
services, _, err := client.DiscoverServices(ctx)
7070
if testSpec.expectedError != nil {
7171
assert.EqualError(t, err, testSpec.expectedError.Error())
7272
assert.Nil(t, services)

0 commit comments

Comments
 (0)