@@ -15,6 +15,7 @@ import (
1515 "k8s.io/apimachinery/pkg/runtime"
1616
1717 arkapi "github.com/jetstack/preflight/internal/cyberark/api"
18+ "github.com/jetstack/preflight/internal/cyberark/identity"
1819 "github.com/jetstack/preflight/pkg/version"
1920)
2021
@@ -33,13 +34,19 @@ type CyberArkClient struct {
3334 baseURL string
3435 httpClient * http.Client
3536
36- authenticateRequest func (req * http.Request ) error
37+ tenantUUID string
38+
39+ authenticateRequest identity.RequestAuthenticator
3740}
3841
39- func New (httpClient * http.Client , baseURL string , authenticateRequest func (req * http.Request ) error ) * CyberArkClient {
42+ // New creates a new CyberArkClient. The tenant UUID is best sourced from service discovery along with the base URL.
43+ func New (httpClient * http.Client , baseURL string , tenantUUID string , authenticateRequest identity.RequestAuthenticator ) * CyberArkClient {
4044 return & CyberArkClient {
41- baseURL : baseURL ,
42- httpClient : httpClient ,
45+ baseURL : baseURL ,
46+ httpClient : httpClient ,
47+
48+ tenantUUID : tenantUUID ,
49+
4350 authenticateRequest : authenticateRequest ,
4451 }
4552}
@@ -102,13 +109,6 @@ type Snapshot struct {
102109// has been received intact.
103110// Read [Checking object integrity for data uploads in Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity-upload.html),
104111// to learn more.
105- //
106- // TODO(wallrj): There is a bug in the AWS backend:
107- // [S3 Presigned PutObjectCommand URLs ignore Sha256 Hash when uploading](https://github.com/aws/aws-sdk/issues/480)
108- // ...which means that the `x-amz-checksum-sha256` request header is optional.
109- // If you omit that header, it is possible to PUT any data.
110- // There is a work around listed in that issue which we have shared with the
111- // CyberArk API team.
112112func (c * CyberArkClient ) PutSnapshot (ctx context.Context , snapshot Snapshot ) error {
113113 if snapshot .ClusterID == "" {
114114 return fmt .Errorf ("programmer mistake: the snapshot cluster ID cannot be left empty" )
@@ -119,10 +119,12 @@ func (c *CyberArkClient) PutSnapshot(ctx context.Context, snapshot Snapshot) err
119119 if err := json .NewEncoder (io .MultiWriter (encodedBody , hash )).Encode (snapshot ); err != nil {
120120 return err
121121 }
122+
122123 checksum := hash .Sum (nil )
123124 checksumHex := hex .EncodeToString (checksum )
124125 checksumBase64 := base64 .StdEncoding .EncodeToString (checksum )
125- presignedUploadURL , err := c .retrievePresignedUploadURL (ctx , checksumHex , snapshot .ClusterID )
126+
127+ presignedUploadURL , username , err := c .retrievePresignedUploadURL (ctx , checksumHex , snapshot .ClusterID , int64 (encodedBody .Len ()))
126128 if err != nil {
127129 return fmt .Errorf ("while retrieving snapshot upload URL: %s" , err )
128130 }
@@ -132,7 +134,21 @@ func (c *CyberArkClient) PutSnapshot(ctx context.Context, snapshot Snapshot) err
132134 if err != nil {
133135 return err
134136 }
137+
135138 req .Header .Set ("X-Amz-Checksum-Sha256" , checksumBase64 )
139+ req .Header .Set ("X-Amz-Server-Side-Encryption" , "AES256" )
140+
141+ q := url.Values {}
142+
143+ q .Add ("agent_version" , snapshot .AgentVersion )
144+ q .Add ("tenant_id" , c .tenantUUID )
145+ q .Add ("upload_type" , "k8s_snapshot" )
146+ q .Add ("uploader_id" , snapshot .ClusterID )
147+ q .Add ("username" , username )
148+ q .Add ("vendor" , "k8s" )
149+
150+ req .Header .Set ("X-Amz-Tagging" , q .Encode ())
151+
136152 version .SetUserAgent (req )
137153
138154 res , err := c .httpClient .Do (req )
@@ -152,44 +168,57 @@ func (c *CyberArkClient) PutSnapshot(ctx context.Context, snapshot Snapshot) err
152168 return nil
153169}
154170
155- func (c * CyberArkClient ) retrievePresignedUploadURL (ctx context.Context , checksum string , clusterID string ) (string , error ) {
171+ // RetrievePresignedUploadURLRequest is the JSON body sent to the inventory API to request a presigned upload URL.
172+ type RetrievePresignedUploadURLRequest struct {
173+ ClusterID string `json:"cluster_id"`
174+ Checksum string `json:"checksum_sha256"`
175+
176+ // AgentVersion is the v-prefixed version of the agent uploading the snapshot.
177+ // Note that the backend relies on this version being v-prefixed semver.
178+ AgentVersion string `json:"agent_version"`
179+
180+ // FileSize is the size of the data we'll upload in bytes
181+ FileSize int64 `json:"file_size"`
182+ }
183+
184+ func (c * CyberArkClient ) retrievePresignedUploadURL (ctx context.Context , checksum string , clusterID string , fileSize int64 ) (string , string , error ) {
156185 uploadURL , err := url .JoinPath (c .baseURL , apiPathSnapshotLinks )
157186 if err != nil {
158- return "" , err
187+ return "" , "" , err
159188 }
160189
161- request := struct {
162- ClusterID string `json:"cluster_id"`
163- Checksum string `json:"checksum_sha256"`
164- AgentVersion string `json:"agent_version"`
165- }{
190+ request := RetrievePresignedUploadURLRequest {
166191 ClusterID : clusterID ,
167192 Checksum : checksum ,
168193 AgentVersion : version .PreflightVersion ,
194+ FileSize : fileSize ,
169195 }
170196
171197 encodedBody := & bytes.Buffer {}
172198 if err := json .NewEncoder (encodedBody ).Encode (request ); err != nil {
173- return "" , err
199+ return "" , "" , err
174200 }
175201
176202 req , err := http .NewRequestWithContext (ctx , http .MethodPost , uploadURL , encodedBody )
177203 if err != nil {
178- return "" , err
204+ return "" , "" , err
179205 }
180206
181207 req .Header .Set ("Content-Type" , "application/json" )
182- if err := c .authenticateRequest (req ); err != nil {
183- return "" , fmt .Errorf ("failed to authenticate request: %s" , err )
208+
209+ username , err := c .authenticateRequest (req )
210+ if err != nil {
211+ return "" , "" , fmt .Errorf ("failed to authenticate request: %s" , err )
184212 }
213+
185214 version .SetUserAgent (req )
186215
187216 // Add telemetry headers
188217 arkapi .SetTelemetryRequestHeader (req )
189218
190219 res , err := c .httpClient .Do (req )
191220 if err != nil {
192- return "" , err
221+ return "" , "" , err
193222 }
194223 defer res .Body .Close ()
195224
@@ -198,7 +227,7 @@ func (c *CyberArkClient) retrievePresignedUploadURL(ctx context.Context, checksu
198227 if len (body ) == 0 {
199228 body = []byte (`<empty body>` )
200229 }
201- return "" , fmt .Errorf ("received response with status code %d: %s" , code , bytes .TrimSpace (body ))
230+ return "" , "" , fmt .Errorf ("received response with status code %d: %s" , code , bytes .TrimSpace (body ))
202231 }
203232
204233 response := struct {
@@ -207,11 +236,11 @@ func (c *CyberArkClient) retrievePresignedUploadURL(ctx context.Context, checksu
207236
208237 if err := json .NewDecoder (io .LimitReader (res .Body , maxRetrievePresignedUploadURLBodySize )).Decode (& response ); err != nil {
209238 if err == io .ErrUnexpectedEOF {
210- return "" , fmt .Errorf ("rejecting JSON response from server as it was too large or was truncated" )
239+ return "" , "" , fmt .Errorf ("rejecting JSON response from server as it was too large or was truncated" )
211240 }
212241
213- return "" , fmt .Errorf ("failed to parse JSON from otherwise successful request to start data upload: %s" , err )
242+ return "" , "" , fmt .Errorf ("failed to parse JSON from otherwise successful request to start data upload: %s" , err )
214243 }
215244
216- return response .URL , nil
245+ return response .URL , username , nil
217246}
0 commit comments