Skip to content

Commit 4ee67a2

Browse files
authored
feat(dspm): expose configuration properties for AWS/Azure DSPM Cloud Accounts (#1820)
* feat(dspm): expose config props for azure/aws dspm cloud accounts * feat(dspm): expose method to update DSPM status
1 parent cb577cf commit 4ee67a2

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

api/cloud_accounts_aws_dspm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type AwsDspmResponse struct {
5252
type AwsDspm struct {
5353
v2CommonIntegrationData
5454
awsDspmToken `json:"serverToken"`
55+
Props *DspmProps `json:"props,omitempty"`
5556
Data AwsDspmData `json:"data"`
5657
}
5758

api/cloud_accounts_azure_dspm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type AzureDspmResponse struct {
5252
type AzureDspm struct {
5353
v2CommonIntegrationData
5454
azureDspmToken `json:"serverToken"`
55+
Props *DspmProps `json:"props,omitempty"`
5556
Data AzureDspmData `json:"data"`
5657
}
5758

api/cloud_accounts_dspm_props.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package api
2+
3+
import "encoding/json"
4+
5+
// DspmProps represents the props section for DSPM integrations.
6+
//
7+
// The API returns props in two different formats depending on the endpoint:
8+
// - GET returns props as a JSON object with camelCase keys
9+
// - PATCH returns props as a JSON-encoded string with UPPER_SNAKE_CASE keys
10+
//
11+
// UnmarshalJSON handles both forms transparently.
12+
type DspmProps struct {
13+
Dspm *DspmPropsConfig `json:"dspm,omitempty"`
14+
}
15+
16+
func (p *DspmProps) UnmarshalJSON(data []byte) error {
17+
if string(data) == "null" {
18+
return nil
19+
}
20+
21+
// If data is a JSON string, unwrap it first. This handles the PATCH
22+
// response format where props is a JSON-encoded string.
23+
if len(data) > 0 && data[0] == '"' {
24+
var s string
25+
if err := json.Unmarshal(data, &s); err != nil {
26+
return err
27+
}
28+
if s == "" {
29+
return nil
30+
}
31+
data = []byte(s)
32+
}
33+
34+
// Try camelCase keys first (GET response format).
35+
// "type plain DspmProps" creates an alias without the custom UnmarshalJSON
36+
// method, avoiding infinite recursion when calling json.Unmarshal.
37+
type plain DspmProps
38+
if err := json.Unmarshal(data, (*plain)(p)); err == nil && p.Dspm != nil {
39+
return nil
40+
}
41+
42+
// Fall back to UPPER_SNAKE_CASE keys (PATCH response format)
43+
var upper struct {
44+
Dspm *dspmPropsConfigUpper `json:"DSPM,omitempty"`
45+
}
46+
if err := json.Unmarshal(data, &upper); err != nil {
47+
return err
48+
}
49+
if upper.Dspm != nil {
50+
p.Dspm = upper.Dspm.toConfig()
51+
}
52+
return nil
53+
}
54+
55+
// DspmPropsConfig contains DSPM-specific configuration
56+
type DspmPropsConfig struct {
57+
ScanIntervalHours *int `json:"scanIntervalHours,omitempty"`
58+
MaxDownloadBytes *int `json:"maxDownloadBytes,omitempty"`
59+
DatastoreFilters *DspmDatastoreFilters `json:"datastoreFilters,omitempty"`
60+
}
61+
62+
// DspmDatastoreFilters configures which datastores to include/exclude from scanning
63+
type DspmDatastoreFilters struct {
64+
FilterMode string `json:"filterMode"`
65+
DatastoreNames []string `json:"datastoreNames"`
66+
}
67+
68+
// dspmPropsConfigUpper mirrors DspmPropsConfig with UPPER_SNAKE_CASE JSON tags
69+
// for deserializing PATCH responses.
70+
type dspmPropsConfigUpper struct {
71+
ScanIntervalHours *int `json:"SCAN_INTERVAL_HOURS,omitempty"`
72+
MaxDownloadBytes *int `json:"MAX_DOWNLOAD_BYTES,omitempty"`
73+
DatastoreFilters *dspmDatastoreFiltersUpper `json:"DATASTORE_FILTERS,omitempty"`
74+
}
75+
76+
type dspmDatastoreFiltersUpper struct {
77+
FilterMode string `json:"FILTER_MODE"`
78+
DatastoreNames []string `json:"DATASTORE_NAMES"`
79+
}
80+
81+
func (u *dspmPropsConfigUpper) toConfig() *DspmPropsConfig {
82+
cfg := &DspmPropsConfig{
83+
ScanIntervalHours: u.ScanIntervalHours,
84+
MaxDownloadBytes: u.MaxDownloadBytes,
85+
}
86+
if u.DatastoreFilters != nil {
87+
cfg.DatastoreFilters = &DspmDatastoreFilters{
88+
FilterMode: u.DatastoreFilters.FilterMode,
89+
DatastoreNames: u.DatastoreFilters.DatastoreNames,
90+
}
91+
}
92+
return cfg
93+
}
94+
95+
const apiV2DspmStatus = "v2/dspm/status"
96+
97+
// DspmStatusRequest is the request body for the DSPM status endpoint
98+
type DspmStatusRequest struct {
99+
Ok bool `json:"ok"`
100+
Message string `json:"message"`
101+
}
102+
103+
// UpdateDspmStatus updates the status of a DSPM integration using server token auth.
104+
func (svc *CloudAccountsService) UpdateDspmStatus(serverToken string, status DspmStatusRequest) error {
105+
return svc.client.RequestEncoderDecoderWithToken(
106+
"POST", apiV2DspmStatus, serverToken, status, nil,
107+
)
108+
}

api/http.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,42 @@ func (c *Client) RequestEncoderDecoder(method, path string, data, v interface{})
172172
return c.RequestDecoder(method, path, body, v)
173173
}
174174

175+
// RequestEncoderDecoderWithToken performs an HTTP request using a server token
176+
// for authentication instead of the client's API key token. Used for endpoints
177+
// authenticated via ServerTokenProps (e.g., POST /api/v2/dspm/status).
178+
func (c *Client) RequestEncoderDecoderWithToken(method, path, token string, data, v interface{}) error {
179+
body, err := jsonReader(data)
180+
if err != nil {
181+
return err
182+
}
183+
184+
apiPath, err := url.Parse(c.apiPath(path))
185+
if err != nil {
186+
return err
187+
}
188+
189+
u := c.baseURL.ResolveReference(apiPath)
190+
request, err := http.NewRequest(method, u.String(), body)
191+
if err != nil {
192+
return err
193+
}
194+
195+
request.Header.Set("Authorization", token)
196+
request.Header.Set("Content-Type", "application/json")
197+
request.Header.Set("Accept", "application/json")
198+
199+
for k, v := range c.headers {
200+
request.Header.Set(k, v)
201+
}
202+
203+
res, err := c.DoDecoder(request, v)
204+
if err != nil {
205+
return err
206+
}
207+
defer res.Body.Close()
208+
return nil
209+
}
210+
175211
// Do calls request.Do() directly
176212
func (c *Client) Do(req *http.Request) (*http.Response, error) {
177213
response, err := c.c.Do(req)

0 commit comments

Comments
 (0)