Skip to content

Commit 07889c7

Browse files
committed
Update the dataupload package to be compatible with the implementated inventory API
Signed-off-by: Richard Wall <[email protected]>
1 parent 9d25e21 commit 07889c7

File tree

4 files changed

+78
-19
lines changed

4 files changed

+78
-19
lines changed

pkg/internal/cyberark/dataupload/dataupload.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const (
2222
// maxRetrievePresignedUploadURLBodySize is the maximum allowed size for a response body from the
2323
// Retrieve Presigned Upload URL service.
2424
maxRetrievePresignedUploadURLBodySize = 10 * 1024
25+
26+
// apiPathSnapshotLinks is the URL path of the snapshot-links endpoint of the inventory API.
27+
// This endpoint returns an AWS presigned URL.
28+
// TODO(wallrj): Link to CyberArk API documentation when it is published.
29+
apiPathSnapshotLinks = "/api/ingestions/kubernetes/snapshot-links"
2530
)
2631

2732
type CyberArkClient struct {
@@ -51,6 +56,9 @@ func NewCyberArkClient(trustedCAs *x509.CertPool, baseURL string, authenticateRe
5156
}, nil
5257
}
5358

59+
// PostDataReadingsWithOptions PUTs the supplied payload to an [AWS presigned URL] which it obtains via the CyberArk inventory API.
60+
//
61+
// [AWS presigned URL]: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
5462
func (c *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, payload api.DataReadingsPost, opts Options) error {
5563
if opts.ClusterName == "" {
5664
return fmt.Errorf("programmer mistake: the cluster name (aka `cluster_id` in the config file) cannot be left empty")
@@ -64,15 +72,15 @@ func (c *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, payloa
6472

6573
presignedUploadURL, err := c.retrievePresignedUploadURL(ctx, hex.EncodeToString(checksum.Sum(nil)), opts)
6674
if err != nil {
67-
return err
75+
return fmt.Errorf("while retrieving snapshot upload URL: %s", err)
6876
}
6977

70-
req, err := http.NewRequestWithContext(ctx, http.MethodPost, presignedUploadURL, encodedBody)
78+
// The snapshot-links endpoint returns an AWS presigned URL which only supports the PUT verb.
79+
req, err := http.NewRequestWithContext(ctx, http.MethodPut, presignedUploadURL, encodedBody)
7180
if err != nil {
7281
return err
7382
}
7483

75-
req.Header.Set("Content-Type", "application/json")
7684
version.SetUserAgent(req)
7785

7886
res, err := c.client.Do(req)
@@ -93,19 +101,19 @@ func (c *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, payloa
93101
}
94102

95103
func (c *CyberArkClient) retrievePresignedUploadURL(ctx context.Context, checksum string, opts Options) (string, error) {
96-
uploadURL, err := url.JoinPath(c.baseURL, "/api/data/kubernetes/upload")
104+
uploadURL, err := url.JoinPath(c.baseURL, apiPathSnapshotLinks)
97105
if err != nil {
98106
return "", err
99107
}
100108

101109
request := struct {
102110
ClusterID string `json:"cluster_id"`
103-
ClusterDescription string `json:"cluster_description"`
104111
Checksum string `json:"checksum_sha256"`
112+
AgentVersion string `json:"agent_version"`
105113
}{
106114
ClusterID: opts.ClusterName,
107-
ClusterDescription: opts.ClusterDescription,
108115
Checksum: checksum,
116+
AgentVersion: version.PreflightVersion,
109117
}
110118

111119
encodedBody := &bytes.Buffer{}
@@ -120,7 +128,7 @@ func (c *CyberArkClient) retrievePresignedUploadURL(ctx context.Context, checksu
120128

121129
req.Header.Set("Content-Type", "application/json")
122130
if err := c.authenticateRequest(req); err != nil {
123-
return "", fmt.Errorf("failed to authenticate request")
131+
return "", fmt.Errorf("failed to authenticate request: %s", err)
124132
}
125133
version.SetUserAgent(req)
126134

pkg/internal/cyberark/dataupload/dataupload_test.go

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import (
55
"encoding/pem"
66
"fmt"
77
"net/http"
8+
"os"
89
"testing"
910
"time"
1011

1112
"github.com/stretchr/testify/require"
13+
"k8s.io/klog/v2"
14+
"k8s.io/klog/v2/ktesting"
1215

1316
"github.com/jetstack/preflight/api"
1417
"github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
18+
"github.com/jetstack/preflight/pkg/internal/cyberark/identity"
19+
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
1520
)
1621

1722
func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
@@ -75,7 +80,7 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
7580
opts: defaultOpts,
7681
authenticate: setToken("fail-token"),
7782
requireFn: func(t *testing.T, err error) {
78-
require.ErrorContains(t, err, "received response with status code 500: should authenticate using the correct bearer token")
83+
require.ErrorContains(t, err, "while retrieving snapshot upload URL: received response with status code 500: should authenticate using the correct bearer token")
7984
},
8085
},
8186
{
@@ -84,16 +89,16 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
8489
opts: dataupload.Options{ClusterName: "invalid-json-retrieve-presigned", ClusterDescription: defaultOpts.ClusterDescription},
8590
authenticate: setToken("success-token"),
8691
requireFn: func(t *testing.T, err error) {
87-
require.ErrorContains(t, err, "rejecting JSON response from server as it was too large or was truncated")
92+
require.ErrorContains(t, err, "while retrieving snapshot upload URL: rejecting JSON response from server as it was too large or was truncated")
8893
},
8994
},
9095
{
91-
name: "500 from server (PostData step)",
96+
name: "500 from server (RetrievePresignedUploadURL step)",
9297
payload: defaultPayload,
9398
opts: dataupload.Options{ClusterName: "invalid-response-post-data", ClusterDescription: defaultOpts.ClusterDescription},
9499
authenticate: setToken("success-token"),
95100
requireFn: func(t *testing.T, err error) {
96-
require.ErrorContains(t, err, "received response with status code 500: mock error")
101+
require.ErrorContains(t, err, "while retrieving snapshot upload URL: received response with status code 500: mock error")
97102
},
98103
},
99104
}
@@ -117,3 +122,52 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
117122
})
118123
}
119124
}
125+
126+
// TestPostDataReadingsWithOptionsWithRealAPI demonstrates that the dataupload code works with the real inventory API.
127+
// An API token is obtained by authenticating with the ARK_USERNAME and ARK_SECRET from the environment.
128+
// ARK_SUBDOMAIN should be your tenant subdomain.
129+
// ARK_PLATFORM_DOMAIN should be either integration-cyberark.cloud or cyberark.cloud
130+
func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
131+
platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN")
132+
subdomain := os.Getenv("ARK_SUBDOMAIN")
133+
username := os.Getenv("ARK_USERNAME")
134+
secret := os.Getenv("ARK_SECRET")
135+
136+
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
137+
t.Skip("Skipping because one of the following environment variables is unset or empty: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
138+
return
139+
}
140+
141+
logger := ktesting.NewLogger(t, ktesting.NewConfig())
142+
ctx := klog.NewContext(t.Context(), logger)
143+
144+
const (
145+
discoveryContextServiceName = "inventory"
146+
separator = "."
147+
)
148+
149+
serviceURL := fmt.Sprintf("https://%s%s%s.%s", subdomain, separator, discoveryContextServiceName, platformDomain)
150+
151+
var (
152+
identityClient *identity.Client
153+
err error
154+
)
155+
if platformDomain == "cyberark.cloud" {
156+
identityClient, err = identity.New(ctx, subdomain)
157+
} else {
158+
discoveryClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
159+
identityClient, err = identity.NewWithDiscoveryClient(ctx, discoveryClient, subdomain)
160+
}
161+
require.NoError(t, err)
162+
163+
err = identityClient.LoginUsernamePassword(ctx, username, []byte(secret))
164+
require.NoError(t, err)
165+
166+
cyberArkClient, err := dataupload.NewCyberArkClient(nil, serviceURL, identityClient.AuthenticateRequest)
167+
require.NoError(t, err)
168+
169+
err = cyberArkClient.PostDataReadingsWithOptions(t.Context(), api.DataReadingsPost{}, dataupload.Options{
170+
ClusterName: "bb068932-c80d-460d-88df-34bc7f3f3297",
171+
})
172+
require.NoError(t, err)
173+
}

pkg/internal/cyberark/dataupload/mock.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (mds *mockDataUploadServer) Close() {
3737

3838
func (mds *mockDataUploadServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3939
switch r.URL.Path {
40-
case "/api/data/kubernetes/upload":
40+
case apiPathSnapshotLinks:
4141
mds.handlePresignedUpload(w, r)
4242
return
4343
case "/presigned-upload":
@@ -123,7 +123,7 @@ func (mds *mockDataUploadServer) handlePresignedUpload(w http.ResponseWriter, r
123123
}
124124

125125
func (mds *mockDataUploadServer) handleUpload(w http.ResponseWriter, r *http.Request, invalidJSON bool) {
126-
if r.Method != http.MethodPost {
126+
if r.Method != http.MethodPut {
127127
w.WriteHeader(http.StatusMethodNotAllowed)
128128
_, _ = w.Write([]byte(`{"message":"method not allowed"}`))
129129
return
@@ -134,11 +134,6 @@ func (mds *mockDataUploadServer) handleUpload(w http.ResponseWriter, r *http.Req
134134
return
135135
}
136136

137-
if r.Header.Get("Content-Type") != "application/json" {
138-
http.Error(w, "should send JSON on all requests", http.StatusInternalServerError)
139-
return
140-
}
141-
142137
if invalidJSON {
143138
w.WriteHeader(http.StatusOK)
144139
w.Header().Set("Content-Type", "application/json")

pkg/version/version.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ func UserAgent() string {
2626
}
2727

2828
// SetUserAgent augments an http.Request with a standard user agent.
29+
//
30+
// TODO(wallrj): The prefix "Mozilla/5.0" is currently required by the CyberArk inventory API. Remove the prefix when CyberArk relax the API security settings.
2931
func SetUserAgent(req *http.Request) {
30-
req.Header.Set("User-Agent", UserAgent())
32+
req.Header.Set("User-Agent", "Mozilla/5.0 " + UserAgent())
3133
}

0 commit comments

Comments
 (0)