mirror of
https://github.com/crowdsecurity/crowdsec.git
synced 2025-05-11 12:25:53 +02:00
allowlists: check during bulk decision import (#3588)
This commit is contained in:
parent
8689783ade
commit
5bc2b49387
13 changed files with 664 additions and 50 deletions
|
@ -8,7 +8,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,7 +19,6 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
"github.com/crowdsecurity/go-cs-lib/slicetools"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
|
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
@ -38,7 +39,7 @@ func parseDecisionList(content []byte, format string) ([]decisionRaw, error) {
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case "values":
|
case "values":
|
||||||
log.Infof("Parsing values")
|
fmt.Fprintln(os.Stdout, "Parsing values")
|
||||||
|
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(content))
|
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
@ -50,13 +51,13 @@ func parseDecisionList(content []byte, format string) ([]decisionRaw, error) {
|
||||||
return nil, fmt.Errorf("unable to parse values: '%w'", err)
|
return nil, fmt.Errorf("unable to parse values: '%w'", err)
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
log.Infof("Parsing json")
|
fmt.Fprintln(os.Stdout, "Parsing json")
|
||||||
|
|
||||||
if err := json.Unmarshal(content, &ret); err != nil {
|
if err := json.Unmarshal(content, &ret); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "csv":
|
case "csv":
|
||||||
log.Infof("Parsing csv")
|
fmt.Fprintln(os.Stdout, "Parsing csv")
|
||||||
|
|
||||||
if err := csvutil.Unmarshal(content, &ret); err != nil {
|
if err := csvutil.Unmarshal(content, &ret); err != nil {
|
||||||
return nil, fmt.Errorf("unable to parse csv: '%w'", err)
|
return nil, fmt.Errorf("unable to parse csv: '%w'", err)
|
||||||
|
@ -68,6 +69,20 @@ func parseDecisionList(content []byte, format string) ([]decisionRaw, error) {
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func excludeAllowlistedDecisions(decisions []*models.Decision, allowlistedValues []string) iter.Seq[*models.Decision] {
|
||||||
|
return func(yield func(*models.Decision) bool) {
|
||||||
|
for _, d := range decisions {
|
||||||
|
if slices.Contains(allowlistedValues, *d.Value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(d) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *cliDecisions) import_(ctx context.Context, input string, duration string, scope string, reason string, type_ string, batch int, format string) error {
|
func (cli *cliDecisions) import_(ctx context.Context, input string, duration string, scope string, reason string, type_ string, batch int, format string) error {
|
||||||
var (
|
var (
|
||||||
content []byte
|
content []byte
|
||||||
|
@ -124,6 +139,10 @@ func (cli *cliDecisions) import_(ctx context.Context, input string, duration str
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(decisionsListRaw) == 0 {
|
||||||
|
return errors.New("no decisions found")
|
||||||
|
}
|
||||||
|
|
||||||
decisions := make([]*models.Decision, len(decisionsListRaw))
|
decisions := make([]*models.Decision, len(decisionsListRaw))
|
||||||
|
|
||||||
for i, d := range decisionsListRaw {
|
for i, d := range decisionsListRaw {
|
||||||
|
@ -163,11 +182,50 @@ func (cli *cliDecisions) import_(ctx context.Context, input string, duration str
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(decisions) > 1000 {
|
if len(decisions) > 1000 {
|
||||||
log.Infof("You are about to add %d decisions, this may take a while", len(decisions))
|
fmt.Fprintf(os.Stdout, "You are about to add %d decisions, this may take a while\n", len(decisions))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chunk := range slicetools.Chunks(decisions, batch) {
|
if batch == 0 {
|
||||||
|
batch = len(decisions)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stdout, "batch size: %d\n", batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
allowlistedValues := make([]string, 0)
|
||||||
|
|
||||||
|
for chunk := range slices.Chunk(decisions, batch) {
|
||||||
log.Debugf("Processing chunk of %d decisions", len(chunk))
|
log.Debugf("Processing chunk of %d decisions", len(chunk))
|
||||||
|
|
||||||
|
decisionsStr := make([]string, 0, len(chunk))
|
||||||
|
|
||||||
|
for _, d := range chunk {
|
||||||
|
if *d.Scope != types.Ip && *d.Scope != types.Range {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
decisionsStr = append(decisionsStr, *d.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if no IPs or ranges
|
||||||
|
if len(decisionsStr) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
allowlistResp, _, err := cli.client.Allowlists.CheckIfAllowlistedBulk(ctx, decisionsStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range allowlistResp.Results {
|
||||||
|
fmt.Fprintf(os.Stdout, "Value %s is allowlisted by %s\n", *r.Target, r.Allowlists)
|
||||||
|
allowlistedValues = append(allowlistedValues, *r.Target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actualDecisions := slices.Collect(excludeAllowlistedDecisions(decisions, allowlistedValues))
|
||||||
|
|
||||||
|
for chunk := range slices.Chunk(actualDecisions, batch) {
|
||||||
importAlert := models.Alert{
|
importAlert := models.Alert{
|
||||||
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
||||||
Scenario: ptr.Of(fmt.Sprintf("import %s: %d IPs", input, len(chunk))),
|
Scenario: ptr.Of(fmt.Sprintf("import %s: %d IPs", input, len(chunk))),
|
||||||
|
@ -195,7 +253,7 @@ func (cli *cliDecisions) import_(ctx context.Context, input string, duration str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Imported %d decisions", len(decisions))
|
fmt.Fprintf(os.Stdout, "Imported %d decisions", len(actualDecisions))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ func (s *AllowlistsService) CheckIfAllowlisted(ctx context.Context, value string
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var discardBody interface{}
|
var discardBody any
|
||||||
|
|
||||||
resp, err := s.client.Do(ctx, req, discardBody)
|
resp, err := s.client.Do(ctx, req, discardBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -111,3 +111,25 @@ func (s *AllowlistsService) CheckIfAllowlistedWithReason(ctx context.Context, va
|
||||||
|
|
||||||
return body, resp, nil
|
return body, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AllowlistsService) CheckIfAllowlistedBulk(ctx context.Context, values []string) (*models.BulkCheckAllowlistResponse, *Response, error) {
|
||||||
|
u := s.client.URLPrefix + "/allowlists/check"
|
||||||
|
|
||||||
|
body := &models.BulkCheckAllowlistRequest{
|
||||||
|
Targets: values,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.client.PrepareRequest(ctx, http.MethodPost, u, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody := &models.BulkCheckAllowlistResponse{}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(ctx, req, responseBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody, resp, nil
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -147,3 +148,57 @@ func TestCheckInAllowlist(t *testing.T) {
|
||||||
|
|
||||||
require.Equal(t, http.StatusNoContent, w.Code)
|
require.Equal(t, http.StatusNoContent, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBulkCheckAllowlist(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
lapi := SetupLAPITest(t, ctx)
|
||||||
|
|
||||||
|
// create an allowlist and add one live entry
|
||||||
|
l, err := lapi.DBClient.CreateAllowList(ctx, "test", "test", "", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
added, err := lapi.DBClient.AddToAllowlist(ctx, l, []*models.AllowlistItem{
|
||||||
|
{Value: "1.2.3.4"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, added)
|
||||||
|
|
||||||
|
// craft a bulk check payload with one matching and one non-matching target
|
||||||
|
reqBody := `{"targets":["1.2.3.4","2.3.4.5"]}`
|
||||||
|
w := lapi.RecordResponse(t, ctx, http.MethodPost, "/v1/allowlists/check", strings.NewReader(reqBody), passwordAuthType)
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
// unmarshal and verify
|
||||||
|
resp := models.BulkCheckAllowlistResponse{}
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
require.Len(t, resp.Results, 1)
|
||||||
|
|
||||||
|
// expect only "1.2.3.4" in the "test" allowlist, while "2.3.4.5" should not be in the response
|
||||||
|
var match bool
|
||||||
|
|
||||||
|
for _, r := range resp.Results {
|
||||||
|
switch *r.Target {
|
||||||
|
case "1.2.3.4":
|
||||||
|
match = true
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"1.2.3.4 from test"}, r.Allowlists)
|
||||||
|
default:
|
||||||
|
t.Errorf("unexpected target %v", r.Target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.True(t, match, "did not see result for 1.2.3.4")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBulkCheckAllowlist_BadRequest(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
lapi := SetupLAPITest(t, ctx)
|
||||||
|
|
||||||
|
// missing or empty body should yield 400
|
||||||
|
w := lapi.RecordResponse(t, ctx, http.MethodPost, "/v1/allowlists/check", emptyBody, passwordAuthType)
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
|
||||||
|
// malformed JSON should also yield 400
|
||||||
|
w = lapi.RecordResponse(t, ctx, http.MethodPost, "/v1/allowlists/check", strings.NewReader("{invalid-json"), passwordAuthType)
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
}
|
||||||
|
|
|
@ -129,6 +129,7 @@ func (c *Controller) NewV1() error {
|
||||||
jwtAuth.GET("/allowlists/:allowlist_name", c.HandlerV1.GetAllowlist)
|
jwtAuth.GET("/allowlists/:allowlist_name", c.HandlerV1.GetAllowlist)
|
||||||
jwtAuth.GET("/allowlists/check/:ip_or_range", c.HandlerV1.CheckInAllowlist)
|
jwtAuth.GET("/allowlists/check/:ip_or_range", c.HandlerV1.CheckInAllowlist)
|
||||||
jwtAuth.HEAD("/allowlists/check/:ip_or_range", c.HandlerV1.CheckInAllowlist)
|
jwtAuth.HEAD("/allowlists/check/:ip_or_range", c.HandlerV1.CheckInAllowlist)
|
||||||
|
jwtAuth.POST("/allowlists/check", c.HandlerV1.CheckInAllowlistBulk)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKeyAuth := groupV1.Group("")
|
apiKeyAuth := groupV1.Group("")
|
||||||
|
|
|
@ -10,6 +10,43 @@ import (
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Controller) CheckInAllowlistBulk(gctx *gin.Context) {
|
||||||
|
var req models.BulkCheckAllowlistRequest
|
||||||
|
|
||||||
|
if err := gctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
gctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Targets) == 0 {
|
||||||
|
gctx.JSON(http.StatusBadRequest, gin.H{"message": "targets list cannot be empty"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := models.BulkCheckAllowlistResponse{
|
||||||
|
Results: make([]*models.BulkCheckAllowlistResult, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range req.Targets {
|
||||||
|
lists, err := c.DBClient.IsAllowlistedBy(gctx.Request.Context(), target)
|
||||||
|
if err != nil {
|
||||||
|
c.HandleDBErrors(gctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lists) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Results = append(resp.Results, &models.BulkCheckAllowlistResult{
|
||||||
|
Target: &target,
|
||||||
|
Allowlists: lists,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) CheckInAllowlist(gctx *gin.Context) {
|
func (c *Controller) CheckInAllowlist(gctx *gin.Context) {
|
||||||
value := gctx.Param("ip_or_range")
|
value := gctx.Param("ip_or_range")
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,7 @@ func (c *Client) AddToAllowlist(ctx context.Context, list *ent.AllowList, items
|
||||||
SetComment(item.Description)
|
SetComment(item.Description)
|
||||||
|
|
||||||
if !time.Time(item.Expiration).IsZero() {
|
if !time.Time(item.Expiration).IsZero() {
|
||||||
query = query.SetExpiresAt(time.Time(item.Expiration))
|
query = query.SetExpiresAt(time.Time(item.Expiration).UTC())
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := query.Save(ctx)
|
content, err := query.Save(ctx)
|
||||||
|
@ -236,7 +236,7 @@ func (c *Client) ReplaceAllowlist(ctx context.Context, list *ent.AllowList, item
|
||||||
return added, nil
|
return added, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) IsAllowlisted(ctx context.Context, value string) (bool, string, error) {
|
func (c *Client) IsAllowlistedBy(ctx context.Context, value string) ([]string, error) {
|
||||||
/*
|
/*
|
||||||
Few cases:
|
Few cases:
|
||||||
- value is an IP/range directly is in allowlist
|
- value is an IP/range directly is in allowlist
|
||||||
|
@ -245,7 +245,7 @@ func (c *Client) IsAllowlisted(ctx context.Context, value string) (bool, string,
|
||||||
*/
|
*/
|
||||||
sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(value)
|
sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Log.Debugf("checking if %s is allowlisted", value)
|
c.Log.Debugf("checking if %s is allowlisted", value)
|
||||||
|
@ -314,22 +314,41 @@ func (c *Client) IsAllowlisted(ctx context.Context, value string) (bool, string,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
allowed, err := query.WithAllowlist().First(ctx)
|
items, err := query.WithAllowlist().All(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ent.IsNotFound(err) {
|
return nil, fmt.Errorf("unable to check if value is allowlisted: %w", err)
|
||||||
return false, "", nil
|
}
|
||||||
|
|
||||||
|
reasons := make([]string, 0)
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
if len(item.Edges.Allowlist) == 0 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, "", fmt.Errorf("unable to check if value is allowlisted: %w", err)
|
reason := item.Value + " from " + item.Edges.Allowlist[0].Name
|
||||||
|
if item.Comment != "" {
|
||||||
|
reason += " (" + item.Comment + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
reasons = append(reasons, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
allowlistName := allowed.Edges.Allowlist[0].Name
|
return reasons, nil
|
||||||
reason := allowed.Value + " from " + allowlistName
|
}
|
||||||
|
|
||||||
if allowed.Comment != "" {
|
func (c *Client) IsAllowlisted(ctx context.Context, value string) (bool, string, error) {
|
||||||
reason += " (" + allowed.Comment + ")"
|
reasons, err := c.IsAllowlistedBy(ctx, value)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(reasons) == 0 {
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reason := strings.Join(reasons, ", ")
|
||||||
|
|
||||||
return true, reason, nil
|
return true, reason, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,3 +104,63 @@ func TestCheckAllowlist(t *testing.T) {
|
||||||
require.True(t, allowlisted)
|
require.True(t, allowlisted)
|
||||||
require.Equal(t, "8a95:c186:9f96:4c75:0dad:49c6:ff62:94b8 from test", reason)
|
require.Equal(t, "8a95:c186:9f96:4c75:0dad:49c6:ff62:94b8 from test", reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsAllowListedBy_SingleAndMultiple(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
dbClient := getDBClient(t, ctx)
|
||||||
|
|
||||||
|
list1, err := dbClient.CreateAllowList(ctx, "list1", "first list", "", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
list2, err := dbClient.CreateAllowList(ctx, "list2", "second list", "", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Add overlapping and distinct entries
|
||||||
|
_, err = dbClient.AddToAllowlist(ctx, list1, []*models.AllowlistItem{
|
||||||
|
{Value: "1.1.1.1"},
|
||||||
|
{Value: "10.0.0.0/8"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = dbClient.AddToAllowlist(ctx, list2, []*models.AllowlistItem{
|
||||||
|
{Value: "1.1.1.1"}, // overlaps with list1
|
||||||
|
{Value: "192.168.0.0/16"}, // only in list2
|
||||||
|
{Value: "2.2.2.2", Expiration: strfmt.DateTime(time.Now().Add(-time.Hour))}, // expired
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Exact IP that lives in both
|
||||||
|
names, err := dbClient.IsAllowlistedBy(ctx, "1.1.1.1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.ElementsMatch(t, []string{"1.1.1.1 from list1", "1.1.1.1 from list2"}, names)
|
||||||
|
|
||||||
|
// IP matching only list1's CIDR
|
||||||
|
names, err = dbClient.IsAllowlistedBy(ctx, "10.5.6.7")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"10.0.0.0/8 from list1"}, names)
|
||||||
|
|
||||||
|
// IP matching only list2's CIDR
|
||||||
|
names, err = dbClient.IsAllowlistedBy(ctx, "192.168.1.42")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"192.168.0.0/16 from list2"}, names)
|
||||||
|
|
||||||
|
// Expired entry in list2 should not appear
|
||||||
|
names, err = dbClient.IsAllowlistedBy(ctx, "2.2.2.2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, names)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsAllowListedBy_NoMatch(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
dbClient := getDBClient(t, ctx)
|
||||||
|
|
||||||
|
list, err := dbClient.CreateAllowList(ctx, "solo", "single", "", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = dbClient.AddToAllowlist(ctx, list, []*models.AllowlistItem{
|
||||||
|
{Value: "5.5.5.5"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// completely unrelated IP
|
||||||
|
names, err := dbClient.IsAllowlistedBy(ctx, "8.8.4.4")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, names)
|
||||||
|
}
|
||||||
|
|
71
pkg/models/bulk_check_allowlist_request.go
Normal file
71
pkg/models/bulk_check_allowlist_request.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
"github.com/go-openapi/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BulkCheckAllowlistRequest bulk check allowlist request
|
||||||
|
//
|
||||||
|
// swagger:model BulkCheckAllowlistRequest
|
||||||
|
type BulkCheckAllowlistRequest struct {
|
||||||
|
|
||||||
|
// Array of IP addresses or CIDR ranges to check
|
||||||
|
// Required: true
|
||||||
|
Targets []string `json:"targets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this bulk check allowlist request
|
||||||
|
func (m *BulkCheckAllowlistRequest) Validate(formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.validateTargets(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BulkCheckAllowlistRequest) validateTargets(formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if err := validate.Required("targets", "body", m.Targets); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this bulk check allowlist request based on context it is used
|
||||||
|
func (m *BulkCheckAllowlistRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *BulkCheckAllowlistRequest) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *BulkCheckAllowlistRequest) UnmarshalBinary(b []byte) error {
|
||||||
|
var res BulkCheckAllowlistRequest
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
124
pkg/models/bulk_check_allowlist_response.go
Normal file
124
pkg/models/bulk_check_allowlist_response.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
"github.com/go-openapi/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BulkCheckAllowlistResponse bulk check allowlist response
|
||||||
|
//
|
||||||
|
// swagger:model BulkCheckAllowlistResponse
|
||||||
|
type BulkCheckAllowlistResponse struct {
|
||||||
|
|
||||||
|
// Per-target allowlist membership results
|
||||||
|
// Required: true
|
||||||
|
Results []*BulkCheckAllowlistResult `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this bulk check allowlist response
|
||||||
|
func (m *BulkCheckAllowlistResponse) Validate(formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.validateResults(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BulkCheckAllowlistResponse) validateResults(formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if err := validate.Required("results", "body", m.Results); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(m.Results); i++ {
|
||||||
|
if swag.IsZero(m.Results[i]) { // not required
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Results[i] != nil {
|
||||||
|
if err := m.Results[i].Validate(formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("results" + "." + strconv.Itoa(i))
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("results" + "." + strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validate this bulk check allowlist response based on the context it is used
|
||||||
|
func (m *BulkCheckAllowlistResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.contextValidateResults(ctx, formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BulkCheckAllowlistResponse) contextValidateResults(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
for i := 0; i < len(m.Results); i++ {
|
||||||
|
|
||||||
|
if m.Results[i] != nil {
|
||||||
|
|
||||||
|
if swag.IsZero(m.Results[i]) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.Results[i].ContextValidate(ctx, formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("results" + "." + strconv.Itoa(i))
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("results" + "." + strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *BulkCheckAllowlistResponse) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *BulkCheckAllowlistResponse) UnmarshalBinary(b []byte) error {
|
||||||
|
var res BulkCheckAllowlistResponse
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
88
pkg/models/bulk_check_allowlist_result.go
Normal file
88
pkg/models/bulk_check_allowlist_result.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
"github.com/go-openapi/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BulkCheckAllowlistResult bulk check allowlist result
|
||||||
|
//
|
||||||
|
// swagger:model BulkCheckAllowlistResult
|
||||||
|
type BulkCheckAllowlistResult struct {
|
||||||
|
|
||||||
|
// Matching ip or range, name of the allowlist and comment related to the target
|
||||||
|
// Required: true
|
||||||
|
Allowlists []string `json:"allowlists"`
|
||||||
|
|
||||||
|
// The IP or range that is allowlisted
|
||||||
|
// Required: true
|
||||||
|
Target *string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this bulk check allowlist result
|
||||||
|
func (m *BulkCheckAllowlistResult) Validate(formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.validateAllowlists(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.validateTarget(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BulkCheckAllowlistResult) validateAllowlists(formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if err := validate.Required("allowlists", "body", m.Allowlists); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BulkCheckAllowlistResult) validateTarget(formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if err := validate.Required("target", "body", m.Target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this bulk check allowlist result based on context it is used
|
||||||
|
func (m *BulkCheckAllowlistResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *BulkCheckAllowlistResult) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *BulkCheckAllowlistResult) UnmarshalBinary(b []byte) error {
|
||||||
|
var res BulkCheckAllowlistResult
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -833,6 +833,33 @@ paths:
|
||||||
description: "missing ip_or_range"
|
description: "missing ip_or_range"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/ErrorResponse"
|
$ref: "#/definitions/ErrorResponse"
|
||||||
|
/allowlists/check:
|
||||||
|
post:
|
||||||
|
description: Check multiple IPs or ranges against allowlists
|
||||||
|
summary: postCheckAllowlist
|
||||||
|
tags:
|
||||||
|
- watchers
|
||||||
|
operationId: postCheckAllowlist
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: body
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
description: IP addresses or CIDR ranges to check
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/BulkCheckAllowlistRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Allowlists check results for each target
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/BulkCheckAllowlistResponse'
|
||||||
|
'400':
|
||||||
|
description: "400 response"
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ErrorResponse"
|
||||||
definitions:
|
definitions:
|
||||||
WatcherRegistrationRequest:
|
WatcherRegistrationRequest:
|
||||||
title: WatcherRegistrationRequest
|
title: WatcherRegistrationRequest
|
||||||
|
@ -1396,6 +1423,40 @@ definitions:
|
||||||
reason:
|
reason:
|
||||||
type: string
|
type: string
|
||||||
description: 'item that matched the provided value'
|
description: 'item that matched the provided value'
|
||||||
|
BulkCheckAllowlistRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
targets:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Array of IP addresses or CIDR ranges to check
|
||||||
|
required:
|
||||||
|
- targets
|
||||||
|
BulkCheckAllowlistResult:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
target:
|
||||||
|
type: string
|
||||||
|
description: The IP or range that is allowlisted
|
||||||
|
allowlists:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Matching ip or range, name of the allowlist and comment related to the target
|
||||||
|
required:
|
||||||
|
- target
|
||||||
|
- allowlists
|
||||||
|
BulkCheckAllowlistResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/BulkCheckAllowlistResult'
|
||||||
|
description: Per-target allowlist membership results
|
||||||
|
required:
|
||||||
|
- results
|
||||||
ErrorResponse:
|
ErrorResponse:
|
||||||
type: "object"
|
type: "object"
|
||||||
required:
|
required:
|
||||||
|
|
|
@ -87,24 +87,24 @@ teardown() {
|
||||||
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
||||||
|
|
||||||
rune -0 cscli decisions import -i "${TESTDATA}/decisions.json"
|
rune -0 cscli decisions import -i "${TESTDATA}/decisions.json"
|
||||||
assert_stderr --partial "Parsing json"
|
assert_output --partial "Parsing json"
|
||||||
assert_stderr --partial "Imported 5 decisions"
|
assert_output --partial "Imported 5 decisions"
|
||||||
|
|
||||||
# import from stdin
|
# import from stdin
|
||||||
rune -1 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.json")
|
rune -1 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.json")
|
||||||
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
||||||
rune -0 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.json") --format json
|
rune -0 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.json") --format json
|
||||||
assert_stderr --partial "Parsing json"
|
assert_output --partial "Parsing json"
|
||||||
assert_stderr --partial "Imported 5 decisions"
|
assert_output --partial "Imported 5 decisions"
|
||||||
|
|
||||||
# invalid json
|
# invalid json
|
||||||
rune -1 cscli decisions import -i - <<<'{"blah":"blah"}' --format json
|
rune -1 cscli decisions import -i - <<<'{"blah":"blah"}' --format json
|
||||||
assert_stderr --partial 'Parsing json'
|
assert_output --partial 'Parsing json'
|
||||||
assert_stderr --partial 'json: cannot unmarshal object into Go value of type []clidecision.decisionRaw'
|
assert_stderr --partial 'json: cannot unmarshal object into Go value of type []clidecision.decisionRaw'
|
||||||
|
|
||||||
# json with extra data
|
# json with extra data
|
||||||
rune -1 cscli decisions import -i - <<<'{"values":"1.2.3.4","blah":"blah"}' --format json
|
rune -1 cscli decisions import -i - <<<'{"values":"1.2.3.4","blah":"blah"}' --format json
|
||||||
assert_stderr --partial 'Parsing json'
|
assert_output --partial 'Parsing json'
|
||||||
assert_stderr --partial 'json: cannot unmarshal object into Go value of type []clidecision.decisionRaw'
|
assert_stderr --partial 'json: cannot unmarshal object into Go value of type []clidecision.decisionRaw'
|
||||||
|
|
||||||
#----------
|
#----------
|
||||||
|
@ -116,21 +116,21 @@ teardown() {
|
||||||
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
||||||
|
|
||||||
rune -0 cscli decisions import -i "${TESTDATA}/decisions.csv"
|
rune -0 cscli decisions import -i "${TESTDATA}/decisions.csv"
|
||||||
assert_stderr --partial 'Parsing csv'
|
assert_output --partial 'Parsing csv'
|
||||||
assert_stderr --partial 'Imported 5 decisions'
|
assert_output --partial 'Imported 5 decisions'
|
||||||
|
|
||||||
# import from stdin
|
# import from stdin
|
||||||
rune -1 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.csv")
|
rune -1 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.csv")
|
||||||
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
assert_stderr --partial "unable to guess format from file extension, please provide a format with --format flag"
|
||||||
rune -0 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.csv") --format csv
|
rune -0 cscli decisions import -i /dev/stdin < <(cat "${TESTDATA}/decisions.csv") --format csv
|
||||||
assert_stderr --partial "Parsing csv"
|
assert_output --partial "Parsing csv"
|
||||||
assert_stderr --partial "Imported 5 decisions"
|
assert_output --partial "Imported 5 decisions"
|
||||||
|
|
||||||
# invalid csv
|
# invalid csv
|
||||||
# XXX: improve validation
|
# XXX: improve validation
|
||||||
rune -0 cscli decisions import -i - <<<'value\n1.2.3.4,5.6.7.8' --format csv
|
rune -1 cscli decisions import -i - <<<'value\n1.2.3.4,5.6.7.8' --format csv
|
||||||
assert_stderr --partial 'Parsing csv'
|
assert_output "Parsing csv"
|
||||||
assert_stderr --partial "Imported 0 decisions"
|
assert_stderr "Error: no decisions found"
|
||||||
|
|
||||||
#----------
|
#----------
|
||||||
# VALUES
|
# VALUES
|
||||||
|
@ -142,8 +142,8 @@ teardown() {
|
||||||
1.2.3.5
|
1.2.3.5
|
||||||
1.2.3.6
|
1.2.3.6
|
||||||
EOT
|
EOT
|
||||||
assert_stderr --partial 'Parsing values'
|
assert_output --partial 'Parsing values'
|
||||||
assert_stderr --partial 'Imported 3 decisions'
|
assert_output --partial 'Imported 3 decisions'
|
||||||
|
|
||||||
# leading or trailing spaces are ignored
|
# leading or trailing spaces are ignored
|
||||||
rune -0 cscli decisions import -i - --format values <<-EOT
|
rune -0 cscli decisions import -i - --format values <<-EOT
|
||||||
|
@ -151,20 +151,18 @@ teardown() {
|
||||||
10.2.3.5
|
10.2.3.5
|
||||||
10.2.3.6
|
10.2.3.6
|
||||||
EOT
|
EOT
|
||||||
assert_stderr --partial 'Parsing values'
|
assert_output --partial 'Parsing values'
|
||||||
assert_stderr --partial 'Imported 3 decisions'
|
assert_output --partial 'Imported 3 decisions'
|
||||||
|
|
||||||
# silently discarding (but logging) invalid decisions
|
# silently discarding (but logging) invalid decisions
|
||||||
|
|
||||||
rune -0 cscli alerts delete --all
|
rune -0 cscli alerts delete --all
|
||||||
truncate -s 0 "$LOGFILE"
|
truncate -s 0 "$LOGFILE"
|
||||||
|
|
||||||
rune -0 cscli decisions import -i - --format values <<-EOT
|
rune -1 cscli decisions import -i - --format values <<-EOT
|
||||||
whatever
|
whatever
|
||||||
EOT
|
EOT
|
||||||
assert_stderr --partial 'Parsing values'
|
assert_stderr --partial "invalid ip address 'whatever'"
|
||||||
assert_stderr --partial 'Imported 1 decisions'
|
|
||||||
assert_file_contains "$LOGFILE" "invalid addr/range 'whatever': invalid ip address 'whatever'"
|
|
||||||
|
|
||||||
rune -0 cscli decisions list -a -o json
|
rune -0 cscli decisions list -a -o json
|
||||||
assert_json '[]'
|
assert_json '[]'
|
||||||
|
@ -174,18 +172,17 @@ teardown() {
|
||||||
rune -0 cscli alerts delete --all
|
rune -0 cscli alerts delete --all
|
||||||
truncate -s 0 "$LOGFILE"
|
truncate -s 0 "$LOGFILE"
|
||||||
|
|
||||||
rune -0 cscli decisions import -i - --format values <<-EOT
|
rune -1 cscli decisions import -i - --format values <<-EOT
|
||||||
1.2.3.4
|
1.2.3.4
|
||||||
bad-apple
|
bad-apple
|
||||||
1.2.3.5
|
1.2.3.5
|
||||||
EOT
|
EOT
|
||||||
assert_stderr --partial 'Parsing values'
|
assert_output "Parsing values"
|
||||||
assert_stderr --partial 'Imported 3 decisions'
|
assert_stderr "Error: API error: invalid ip address 'bad-apple'"
|
||||||
assert_file_contains "$LOGFILE" "invalid addr/range 'bad-apple': invalid ip address 'bad-apple'"
|
|
||||||
|
|
||||||
rune -0 cscli decisions list -a -o json
|
rune -0 cscli decisions list -a -o json
|
||||||
rune -0 jq -r '.[0].decisions | length' <(output)
|
rune -0 jq -r '.[0].decisions | length' <(output)
|
||||||
assert_output 2
|
assert_output 0
|
||||||
|
|
||||||
#----------
|
#----------
|
||||||
# Batch
|
# Batch
|
||||||
|
@ -198,5 +195,5 @@ teardown() {
|
||||||
EOT
|
EOT
|
||||||
assert_stderr --partial 'Processing chunk of 2 decisions'
|
assert_stderr --partial 'Processing chunk of 2 decisions'
|
||||||
assert_stderr --partial 'Processing chunk of 1 decisions'
|
assert_stderr --partial 'Processing chunk of 1 decisions'
|
||||||
assert_stderr --partial 'Imported 3 decisions'
|
assert_output --partial 'Imported 3 decisions'
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,15 +135,36 @@ teardown() {
|
||||||
refute_stderr
|
refute_stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "cscli allolists: range check" {
|
@test "cscli allowlists: check during decisions add" {
|
||||||
|
rune -0 cscli allowlist create foo -d 'a foo'
|
||||||
|
rune -0 cscli allowlist add foo 192.168.0.0/16
|
||||||
|
rune -1 cscli decisions add -i 192.168.1.1
|
||||||
|
assert_stderr 'Error: 192.168.1.1 is allowlisted by item 192.168.0.0/16 from foo, use --bypass-allowlist to add the decision anyway'
|
||||||
|
refute_output
|
||||||
|
rune -0 cscli decisions add -i 192.168.1.1 --bypass-allowlist
|
||||||
|
assert_stderr --partial 'Decision successfully added'
|
||||||
|
refute_output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "cscli allowlists: check during decisions import" {
|
||||||
|
rune -0 cscli allowlist create foo -d 'a foo'
|
||||||
|
rune -0 cscli allowlist add foo 192.168.0.0/16
|
||||||
|
rune -0 cscli decisions import -i - <<<'192.168.1.1' --format values
|
||||||
|
assert_output - <<-EOT
|
||||||
|
Parsing values
|
||||||
|
Value 192.168.1.1 is allowlisted by [192.168.0.0/16 from foo]
|
||||||
|
Imported 0 decisions
|
||||||
|
EOT
|
||||||
|
refute_stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "cscli allowlists: range check" {
|
||||||
rune -0 cscli allowlist create foo -d 'a foo'
|
rune -0 cscli allowlist create foo -d 'a foo'
|
||||||
rune -0 cscli allowlist add foo 192.168.0.0/16
|
rune -0 cscli allowlist add foo 192.168.0.0/16
|
||||||
rune -1 cscli decisions add -r 192.168.10.20/24
|
rune -1 cscli decisions add -r 192.168.10.20/24
|
||||||
assert_stderr 'Error: 192.168.10.20/24 is allowlisted by item 192.168.0.0/16 from foo, use --bypass-allowlist to add the decision anyway'
|
assert_stderr --partial '192.168.10.20/24 is allowlisted by item 192.168.0.0/16 from foo, use --bypass-allowlist to add the decision anyway'
|
||||||
refute_output
|
|
||||||
rune -0 cscli decisions add -r 192.168.10.20/24 --bypass-allowlist
|
rune -0 cscli decisions add -r 192.168.10.20/24 --bypass-allowlist
|
||||||
assert_stderr --partial 'Decision successfully added'
|
assert_stderr --partial 'Decision successfully added'
|
||||||
refute_output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "cscli allowlists delete" {
|
@test "cscli allowlists delete" {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue