-
Notifications
You must be signed in to change notification settings - Fork 597
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1887 from inanna-malick/inanna/DLP-1800
DLP-1800: add support for zero trust user risk scoring
- Loading branch information
Showing
3 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:enhancement | ||
dlp: add support for zt risk behavior configuration | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package cloudflare | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/goccy/go-json" | ||
) | ||
|
||
// Behavior represents a single zt risk behavior config. | ||
type Behavior struct { | ||
Name string `json:"name,omitempty"` | ||
Description string `json:"description,omitempty"` | ||
RiskLevel RiskLevel `json:"risk_level"` | ||
Enabled *bool `json:"enabled"` | ||
} | ||
|
||
// Wrapper used to have full-fidelity repro of json structure. | ||
type Behaviors struct { | ||
Behaviors map[string]Behavior `json:"behaviors"` | ||
} | ||
|
||
// BehaviorResponse represents the response from the zt risk scoring endpoint | ||
// and contains risk behaviors for an account. | ||
type BehaviorResponse struct { | ||
Success bool `json:"success"` | ||
Result Behaviors `json:"result"` | ||
Errors []string `json:"errors"` | ||
Messages []string `json:"messages"` | ||
} | ||
|
||
// Behaviors returns all zero trust risk scoring behaviors for the provided account | ||
// | ||
// API reference: https://developers.cloudflare.com/api/operations/dlp-zt-risk-score-get-behaviors | ||
func (api *API) Behaviors(ctx context.Context, accountID string) (Behaviors, error) { | ||
uri := fmt.Sprintf("/accounts/%s/zt_risk_scoring/behaviors", accountID) | ||
|
||
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) | ||
if err != nil { | ||
return Behaviors{}, err | ||
} | ||
|
||
var r BehaviorResponse | ||
err = json.Unmarshal(res, &r) | ||
if err != nil { | ||
return Behaviors{}, fmt.Errorf("%s: %w", errUnmarshalError, err) | ||
} | ||
return r.Result, nil | ||
} | ||
|
||
// UpdateBehaviors returns all zero trust risk scoring behaviors for the provided account | ||
// NOTE: description/name updates are no-ops, risk_level [low medium high] and enabled [true/false] results in modifications | ||
// | ||
// API reference: https://developers.cloudflare.com/api/operations/dlp-zt-risk-score-put-behaviors | ||
func (api *API) UpdateBehaviors(ctx context.Context, accountID string, behaviors Behaviors) (Behaviors, error) { | ||
uri := fmt.Sprintf("/accounts/%s/zt_risk_scoring/behaviors", accountID) | ||
|
||
res, err := api.makeRequestContext(ctx, http.MethodPut, uri, behaviors) | ||
if err != nil { | ||
return Behaviors{}, err | ||
} | ||
|
||
var r BehaviorResponse | ||
err = json.Unmarshal(res, &r) | ||
if err != nil { | ||
return Behaviors{}, fmt.Errorf("%s: %w", errUnmarshalError, err) | ||
} | ||
|
||
return r.Result, nil | ||
} | ||
|
||
type RiskLevel int | ||
|
||
const ( | ||
_ RiskLevel = iota | ||
Low | ||
Medium | ||
High | ||
) | ||
|
||
func (p RiskLevel) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(p.String()) | ||
} | ||
|
||
func (p RiskLevel) String() string { | ||
return [...]string{"low", "medium", "high"}[p-1] | ||
} | ||
|
||
func (p *RiskLevel) UnmarshalJSON(data []byte) error { | ||
var ( | ||
s string | ||
err error | ||
) | ||
err = json.Unmarshal(data, &s) | ||
if err != nil { | ||
return err | ||
} | ||
v, err := RiskLevelFromString(s) | ||
if err != nil { | ||
return err | ||
} | ||
*p = *v | ||
return nil | ||
} | ||
|
||
func RiskLevelFromString(s string) (*RiskLevel, error) { | ||
s = strings.ToLower(s) | ||
var v RiskLevel | ||
switch s { | ||
case "low": | ||
v = Low | ||
case "medium": | ||
v = Medium | ||
case "high": | ||
v = High | ||
default: | ||
return nil, fmt.Errorf("unknown variant for risk level: %s", s) | ||
} | ||
return &v, nil | ||
} | ||
|
||
func (p RiskLevel) IntoRef() *RiskLevel { | ||
return &p | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package cloudflare | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
expectedBehaviors = Behaviors{ | ||
Behaviors: map[string]Behavior{ | ||
"high_dlp": { | ||
Name: "High Number of DLP Policies Triggered", | ||
Description: "User has triggered an active DLP profile in a Gateway policy fifteen times or more within one minute.", | ||
RiskLevel: Low, | ||
Enabled: BoolPtr(true), | ||
}, | ||
"imp_travel": { | ||
Name: "Impossible Travel", | ||
Description: "A user had a successful Access application log in from two locations that they could not have traveled to in that period of time.", | ||
RiskLevel: High, | ||
Enabled: BoolPtr(false), | ||
}, | ||
}, | ||
} | ||
|
||
updateBehaviors = Behaviors{ | ||
Behaviors: map[string]Behavior{ | ||
"high_dlp": { | ||
RiskLevel: Low, | ||
Enabled: BoolPtr(true), | ||
}, | ||
"imp_travel": { | ||
RiskLevel: High, | ||
Enabled: BoolPtr(false), | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
func TestBehaviors(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
handler := func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) | ||
w.Header().Set("content-type", "application/json") | ||
fmt.Fprintf(w, `{ | ||
"result": { | ||
"behaviors": { | ||
"high_dlp": { | ||
"name": "High Number of DLP Policies Triggered", | ||
"description": "User has triggered an active DLP profile in a Gateway policy fifteen times or more within one minute.", | ||
"risk_level": "low", | ||
"enabled":true | ||
}, | ||
"imp_travel": { | ||
"name": "Impossible Travel", | ||
"description": "A user had a successful Access application log in from two locations that they could not have traveled to in that period of time.", | ||
"risk_level": "high", | ||
"enabled": false | ||
} | ||
} | ||
}, | ||
"success": true, | ||
"errors": [], | ||
"messages": [] | ||
}`) | ||
} | ||
|
||
mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/zt_risk_scoring/behaviors", handler) | ||
want := expectedBehaviors | ||
|
||
actual, err := client.Behaviors(context.Background(), "01a7362d577a6c3019a474fd6f485823") | ||
|
||
if assert.NoError(t, err) { | ||
assert.Equal(t, want, actual) | ||
} | ||
} | ||
|
||
func TestUpdateBehaviors(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
handler := func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) | ||
b, err := io.ReadAll(r.Body) | ||
defer r.Body.Close() | ||
|
||
if assert.NoError(t, err) { | ||
assert.JSONEq(t, `{ | ||
"behaviors": { | ||
"high_dlp": { | ||
"risk_level": "low", | ||
"enabled":true | ||
}, | ||
"imp_travel": { | ||
"risk_level": "high", | ||
"enabled": false | ||
} | ||
} | ||
}`, string(b), "JSON payload not equal") | ||
} | ||
|
||
w.Header().Set("content-type", "application/json") | ||
fmt.Fprintf(w, `{ | ||
"result": { | ||
"behaviors": { | ||
"high_dlp": { | ||
"name": "High Number of DLP Policies Triggered", | ||
"description": "User has triggered an active DLP profile in a Gateway policy fifteen times or more within one minute.", | ||
"risk_level": "low", | ||
"enabled":true | ||
}, | ||
"imp_travel": { | ||
"name": "Impossible Travel", | ||
"description": "A user had a successful Access application log in from two locations that they could not have traveled to in that period of time.", | ||
"risk_level": "high", | ||
"enabled": false | ||
} | ||
} | ||
}, | ||
"success": true, | ||
"errors": [], | ||
"messages": [] | ||
}`) | ||
} | ||
|
||
mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/zt_risk_scoring/behaviors", handler) | ||
|
||
want := expectedBehaviors | ||
actual, err := client.UpdateBehaviors(context.Background(), "01a7362d577a6c3019a474fd6f485823", updateBehaviors) | ||
|
||
if assert.NoError(t, err) { | ||
assert.Equal(t, want, actual) | ||
} | ||
} | ||
|
||
func TestRiskLevelFromString(t *testing.T) { | ||
got, _ := RiskLevelFromString("high") | ||
want := High | ||
|
||
if *got != want { | ||
t.Errorf("got %#v, wanted %#v", *got, want) | ||
} | ||
} | ||
|
||
func TestStringFromRiskLevel(t *testing.T) { | ||
got := fmt.Sprint(High) | ||
want := "high" | ||
|
||
if got != want { | ||
t.Errorf("got %#v, wanted %#v", got, want) | ||
} | ||
} |