github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiserver/papi_cmd.go (about)

     1  package apiserver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"time"
     7  
     8  	log "github.com/sirupsen/logrus"
     9  
    10  	"github.com/crowdsecurity/go-cs-lib/ptr"
    11  
    12  	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
    13  	"github.com/crowdsecurity/crowdsec/pkg/models"
    14  	"github.com/crowdsecurity/crowdsec/pkg/modelscapi"
    15  	"github.com/crowdsecurity/crowdsec/pkg/types"
    16  )
    17  
    18  type deleteDecisions struct {
    19  	UUID      string   `json:"uuid"`
    20  	Decisions []string `json:"decisions"`
    21  }
    22  
    23  type blocklistLink struct {
    24  	// blocklist name
    25  	Name string `json:"name"`
    26  	// blocklist url
    27  	Url string `json:"url"`
    28  	// blocklist remediation
    29  	Remediation string `json:"remediation"`
    30  	// blocklist scope
    31  	Scope string `json:"scope,omitempty"`
    32  	// blocklist duration
    33  	Duration string `json:"duration,omitempty"`
    34  }
    35  
    36  type forcePull struct {
    37  	Blocklist *blocklistLink `json:"blocklist,omitempty"`
    38  }
    39  
    40  type listUnsubscribe struct {
    41  	Name string `json:"name"`
    42  }
    43  
    44  func DecisionCmd(message *Message, p *Papi, sync bool) error {
    45  	switch message.Header.OperationCmd {
    46  	case "delete":
    47  		data, err := json.Marshal(message.Data)
    48  		if err != nil {
    49  			return err
    50  		}
    51  
    52  		UUIDs := make([]string, 0)
    53  		deleteDecisionMsg := deleteDecisions{
    54  			Decisions: make([]string, 0),
    55  		}
    56  
    57  		if err := json.Unmarshal(data, &deleteDecisionMsg); err != nil {
    58  			return fmt.Errorf("message for '%s' contains bad data format: %w", message.Header.OperationType, err)
    59  		}
    60  
    61  		UUIDs = append(UUIDs, deleteDecisionMsg.Decisions...)
    62  		log.Infof("Decisions UUIDs to remove: %+v", UUIDs)
    63  
    64  		filter := make(map[string][]string)
    65  		filter["uuid"] = UUIDs
    66  		_, deletedDecisions, err := p.DBClient.SoftDeleteDecisionsWithFilter(filter)
    67  
    68  		if err != nil {
    69  			return fmt.Errorf("unable to delete decisions %+v: %w", UUIDs, err)
    70  		}
    71  
    72  		decisions := make([]*models.Decision, 0)
    73  
    74  		for _, deletedDecision := range deletedDecisions {
    75  			log.Infof("Decision from '%s' for '%s' (%s) has been deleted", deletedDecision.Origin, deletedDecision.Value, deletedDecision.Type)
    76  			dec := &models.Decision{
    77  				UUID:     deletedDecision.UUID,
    78  				Origin:   &deletedDecision.Origin,
    79  				Scenario: &deletedDecision.Scenario,
    80  				Scope:    &deletedDecision.Scope,
    81  				Value:    &deletedDecision.Value,
    82  				ID:       int64(deletedDecision.ID),
    83  				Until:    deletedDecision.Until.String(),
    84  				Type:     &deletedDecision.Type,
    85  			}
    86  			decisions = append(decisions, dec)
    87  		}
    88  		p.Channels.DeleteDecisionChannel <- decisions
    89  	default:
    90  		return fmt.Errorf("unknown command '%s' for operation type '%s'", message.Header.OperationCmd, message.Header.OperationType)
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  func AlertCmd(message *Message, p *Papi, sync bool) error {
    97  	switch message.Header.OperationCmd {
    98  	case "add":
    99  		data, err := json.Marshal(message.Data)
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		alert := &models.Alert{}
   105  
   106  		if err := json.Unmarshal(data, alert); err != nil {
   107  			return fmt.Errorf("message for '%s' contains bad alert format: %w", message.Header.OperationType, err)
   108  		}
   109  
   110  		log.Infof("Received order %s from PAPI (%d decisions)", alert.UUID, len(alert.Decisions))
   111  
   112  		/*Fix the alert with missing mandatory items*/
   113  		if alert.StartAt == nil || *alert.StartAt == "" {
   114  			log.Warnf("Alert %d has no StartAt, setting it to now", alert.ID)
   115  			alert.StartAt = ptr.Of(time.Now().UTC().Format(time.RFC3339))
   116  		}
   117  
   118  		if alert.StopAt == nil || *alert.StopAt == "" {
   119  			log.Warnf("Alert %d has no StopAt, setting it to now", alert.ID)
   120  			alert.StopAt = ptr.Of(time.Now().UTC().Format(time.RFC3339))
   121  		}
   122  
   123  		alert.EventsCount = ptr.Of(int32(0))
   124  		alert.Capacity = ptr.Of(int32(0))
   125  		alert.Leakspeed = ptr.Of("")
   126  		alert.Simulated = ptr.Of(false)
   127  		alert.ScenarioHash = ptr.Of("")
   128  		alert.ScenarioVersion = ptr.Of("")
   129  		alert.Message = ptr.Of("")
   130  		alert.Scenario = ptr.Of("")
   131  		alert.Source = &models.Source{}
   132  
   133  		//if we're setting Source.Scope to types.ConsoleOrigin, it messes up the alert's value
   134  		if len(alert.Decisions) >= 1 {
   135  			alert.Source.Scope = alert.Decisions[0].Scope
   136  			alert.Source.Value = alert.Decisions[0].Value
   137  		} else {
   138  			log.Warningf("No decision found in alert for Polling API (%s : %s)", message.Header.Source.User, message.Header.Message)
   139  			alert.Source.Scope = ptr.Of(types.ConsoleOrigin)
   140  			alert.Source.Value = &message.Header.Source.User
   141  		}
   142  
   143  		alert.Scenario = &message.Header.Message
   144  
   145  		for _, decision := range alert.Decisions {
   146  			if *decision.Scenario == "" {
   147  				decision.Scenario = &message.Header.Message
   148  			}
   149  
   150  			log.Infof("Adding decision for '%s' with UUID: %s", *decision.Value, decision.UUID)
   151  		}
   152  
   153  		//use a different method : alert and/or decision might already be partially present in the database
   154  		_, err = p.DBClient.CreateOrUpdateAlert("", alert)
   155  		if err != nil {
   156  			log.Errorf("Failed to create alerts in DB: %s", err)
   157  		} else {
   158  			p.Channels.AddAlertChannel <- []*models.Alert{alert}
   159  		}
   160  
   161  	default:
   162  		return fmt.Errorf("unknown command '%s' for operation type '%s'", message.Header.OperationCmd, message.Header.OperationType)
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  func ManagementCmd(message *Message, p *Papi, sync bool) error {
   169  	if sync {
   170  		p.Logger.Infof("Ignoring management command from PAPI in sync mode")
   171  		return nil
   172  	}
   173  
   174  	switch message.Header.OperationCmd {
   175  
   176  	case "blocklist_unsubscribe":
   177  		data, err := json.Marshal(message.Data)
   178  		if err != nil {
   179  			return err
   180  		}
   181  		unsubscribeMsg := listUnsubscribe{}
   182  		if err := json.Unmarshal(data, &unsubscribeMsg); err != nil {
   183  			return fmt.Errorf("message for '%s' contains bad data format: %s", message.Header.OperationType, err)
   184  		}
   185  		if unsubscribeMsg.Name == "" {
   186  			return fmt.Errorf("message for '%s' contains bad data format: missing blocklist name", message.Header.OperationType)
   187  		}
   188  		p.Logger.Infof("Received blocklist_unsubscribe command from PAPI, unsubscribing from blocklist %s", unsubscribeMsg.Name)
   189  
   190  		filter := make(map[string][]string)
   191  		filter["origin"] = []string{types.ListOrigin}
   192  		filter["scenario"] = []string{unsubscribeMsg.Name}
   193  
   194  		_, deletedDecisions, err := p.DBClient.SoftDeleteDecisionsWithFilter(filter)
   195  		if err != nil {
   196  			return fmt.Errorf("unable to delete decisions for list %s : %w", unsubscribeMsg.Name, err)
   197  		}
   198  		p.Logger.Infof("deleted %d decisions for list %s", len(deletedDecisions), unsubscribeMsg.Name)
   199  
   200  	case "reauth":
   201  		p.Logger.Infof("Received reauth command from PAPI, resetting token")
   202  		p.apiClient.GetClient().Transport.(*apiclient.JWTTransport).ResetToken()
   203  	case "force_pull":
   204  		data, err := json.Marshal(message.Data)
   205  		if err != nil {
   206  			return err
   207  		}
   208  		forcePullMsg := forcePull{}
   209  		if err := json.Unmarshal(data, &forcePullMsg); err != nil {
   210  			return fmt.Errorf("message for '%s' contains bad data format: %s", message.Header.OperationType, err)
   211  		}
   212  
   213  		if forcePullMsg.Blocklist == nil {
   214  			p.Logger.Infof("Received force_pull command from PAPI, pulling community and 3rd-party blocklists")
   215  			err = p.apic.PullTop(true)
   216  			if err != nil {
   217  				return fmt.Errorf("failed to force pull operation: %s", err)
   218  			}
   219  		} else {
   220  			p.Logger.Infof("Received force_pull command from PAPI, pulling blocklist %s", forcePullMsg.Blocklist.Name)
   221  			err = p.apic.PullBlocklist(&modelscapi.BlocklistLink{
   222  				Name:        &forcePullMsg.Blocklist.Name,
   223  				URL:         &forcePullMsg.Blocklist.Url,
   224  				Remediation: &forcePullMsg.Blocklist.Remediation,
   225  				Scope:       &forcePullMsg.Blocklist.Scope,
   226  				Duration:    &forcePullMsg.Blocklist.Duration,
   227  			}, true)
   228  			if err != nil {
   229  				return fmt.Errorf("failed to force pull operation: %w", err)
   230  			}
   231  		}
   232  	default:
   233  		return fmt.Errorf("unknown command '%s' for operation type '%s'", message.Header.OperationCmd, message.Header.OperationType)
   234  	}
   235  
   236  	return nil
   237  }