Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions examples/encrypted-secrets/README.md

This file was deleted.

41 changes: 0 additions & 41 deletions examples/encrypted-secrets/config.yaml

This file was deleted.

65 changes: 0 additions & 65 deletions examples/encrypted-secrets/test.sh

This file was deleted.

2 changes: 2 additions & 0 deletions hack/ark/test-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ kubectl apply -f "${root_dir}/hack/ark/cluster-external-secret.yaml"

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

kubectl rollout status deployments/disco-agent --namespace "${NAMESPACE}"
Expand Down
8 changes: 4 additions & 4 deletions internal/cyberark/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ func TestCyberArkClient_PutSnapshot_MockAPI(t *testing.T) {
Secret: "somepassword",
}

discoveryClient := servicediscovery.New(httpClient)
discoveryClient := servicediscovery.New(httpClient, cfg.Subdomain)

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

discoveryClient := servicediscovery.New(httpClient)
discoveryClient := servicediscovery.New(httpClient, cfg.Subdomain)

serviceMap, tenantUUID, err := discoveryClient.DiscoverServices(t.Context(), cfg.Subdomain)
serviceMap, tenantUUID, err := discoveryClient.DiscoverServices(t.Context())
if err != nil {
t.Fatalf("failed to discover services: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/cyberark/identity/cmd/testidentity/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ func run(ctx context.Context) error {
var rootCAs *x509.CertPool
httpClient := http_client.NewDefaultClient(version.UserAgent(), rootCAs)

sdClient := servicediscovery.New(httpClient)
services, _, err := sdClient.DiscoverServices(ctx, subdomain)
sdClient := servicediscovery.New(httpClient, subdomain)
services, _, err := sdClient.DiscoverServices(ctx)
if err != nil {
return fmt.Errorf("while performing service discovery: %s", err)
}
Expand Down
19 changes: 15 additions & 4 deletions internal/cyberark/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"sync"
"time"

"k8s.io/klog/v2"

Expand Down Expand Up @@ -180,6 +181,7 @@ type Client struct {

tokenCached token
tokenCachedMutex sync.Mutex
tokenCachedTime time.Time
}

// token is a wrapper type for holding auth tokens we want to cache.
Expand All @@ -205,12 +207,23 @@ func New(httpClient *http.Client, baseURL string, subdomain string) *Client {
// Tokens are cached internally and are not directly accessible to code; use Client.AuthenticateRequest to add credentials
// to an *http.Request.
func (c *Client) LoginUsernamePassword(ctx context.Context, username string, password []byte) error {
// note: we hold the mutex for the whole login attempt to ensure that only one login attempt can be in flight at once,
// and to ensure that the token cache is correctly updated
c.tokenCachedMutex.Lock()
defer c.tokenCachedMutex.Unlock()

defer func() {
for i := range password {
password[i] = 0x00
}
}()

if time.Since(c.tokenCachedTime) < 15*time.Minute && c.tokenCached.Username == username {
// If the cached token is recent and for the same username, we can reuse it.
klog.FromContext(ctx).V(2).Info("reusing cached token for user", "username", username)
return nil
}

advanceRequestBody, err := c.doStartAuthentication(ctx, username)
if err != nil {
return err
Expand Down Expand Up @@ -405,15 +418,13 @@ func (c *Client) doAdvanceAuthentication(ctx context.Context, username string, p

klog.FromContext(ctx).Info("successfully completed AdvanceAuthentication request to CyberArk Identity; login complete", "username", username)

c.tokenCachedMutex.Lock()

// NB: This assumes we already hold the token cache mutex, which we do in LoginUsernamePassword, so this is safe.
c.tokenCachedTime = time.Now()
c.tokenCached = token{
Username: username,
Token: advanceAuthResponse.Result.Token,
}

c.tokenCachedMutex.Unlock()

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion internal/cyberark/identity/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestLoginUsernamePassword_RealAPI(t *testing.T) {
arktesting.SkipIfNoEnv(t)
subdomain := os.Getenv("ARK_SUBDOMAIN")
httpClient := http.DefaultClient
services, _, err := servicediscovery.New(httpClient).DiscoverServices(t.Context(), subdomain)
services, _, err := servicediscovery.New(httpClient, subdomain).DiscoverServices(t.Context())
require.NoError(t, err)

loginUsernamePasswordTests(t, func(t testing.TB) inputs {
Expand Down
50 changes: 39 additions & 11 deletions internal/cyberark/servicediscovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"net/url"
"os"
"path"
"sync"
"time"

arkapi "github.com/jetstack/preflight/internal/cyberark/api"
"github.com/jetstack/preflight/pkg/version"
Expand All @@ -35,21 +37,34 @@ const (
// users to fetch URLs for various APIs available in CyberArk. This client is specialised to
// fetch only API endpoints, since only API endpoints are required by the Venafi Kubernetes Agent currently.
type Client struct {
client *http.Client
baseURL string
client *http.Client
baseURL string
subdomain string

cachedResponse *Services
cachedTenantID string
cachedResponseTime time.Time
cachedResponseMutex sync.Mutex
}

// New creates a new CyberArk Service Discovery client. If the ARK_DISCOVERY_API
// environment variable is set, it is used as the base URL for the service
// discovery API. Otherwise, the production URL is used.
func New(httpClient *http.Client) *Client {
func New(httpClient *http.Client, subdomain string) *Client {
baseURL := os.Getenv("ARK_DISCOVERY_API")
if baseURL == "" {
baseURL = ProdDiscoveryAPIBaseURL
}

client := &Client{
client: httpClient,
baseURL: baseURL,
client: httpClient,
baseURL: baseURL,
subdomain: subdomain,

cachedResponse: nil,
cachedTenantID: "",
cachedResponseTime: time.Time{},
cachedResponseMutex: sync.Mutex{},
}

return client
Expand Down Expand Up @@ -93,17 +108,24 @@ type Services struct {
DiscoveryContext ServiceEndpoint
}

// DiscoverServices fetches from the service discovery service for a given subdomain
// DiscoverServices fetches from the service discovery service for the configured subdomain
// and parses the CyberArk Identity API URL and Inventory API URL.
// It also returns the Tenant ID UUID corresponding to the subdomain.
func (c *Client) DiscoverServices(ctx context.Context, subdomain string) (*Services, string, error) {
func (c *Client) DiscoverServices(ctx context.Context) (*Services, string, error) {
c.cachedResponseMutex.Lock()
defer c.cachedResponseMutex.Unlock()

if c.cachedResponse != nil && time.Since(c.cachedResponseTime) < 1*time.Hour {
return c.cachedResponse, c.cachedTenantID, nil
}

u, err := url.Parse(c.baseURL)
if err != nil {
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()
u.RawQuery = url.Values{"bySubdomain": []string{c.subdomain}}.Encode()

endpoint := u.String()

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

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

return &Services{
services := &Services{
Identity: ServiceEndpoint{API: identityAPI},
DiscoveryContext: ServiceEndpoint{API: discoveryContextAPI},
}, discoveryResp.TenantID, nil
}

c.cachedResponse = services
c.cachedTenantID = discoveryResp.TenantID
c.cachedResponseTime = time.Now()

return services, discoveryResp.TenantID, nil
}
4 changes: 2 additions & 2 deletions internal/cyberark/servicediscovery/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ func Test_DiscoverIdentityAPIURL(t *testing.T) {
},
})

client := New(httpClient)
client := New(httpClient, testSpec.subdomain)

services, _, err := client.DiscoverServices(ctx, testSpec.subdomain)
services, _, err := client.DiscoverServices(ctx)
if testSpec.expectedError != nil {
assert.EqualError(t, err, testSpec.expectedError.Error())
assert.Nil(t, services)
Expand Down
Loading