github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/kubernetesaudit/k8s_audit.go (about)

     1  package kubernetesauditacquisition
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	log "github.com/sirupsen/logrus"
    13  	"gopkg.in/tomb.v2"
    14  	"gopkg.in/yaml.v2"
    15  	"k8s.io/apiserver/pkg/apis/audit"
    16  
    17  	"github.com/crowdsecurity/go-cs-lib/trace"
    18  
    19  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    20  	"github.com/crowdsecurity/crowdsec/pkg/types"
    21  )
    22  
    23  type KubernetesAuditConfiguration struct {
    24  	ListenAddr                        string `yaml:"listen_addr"`
    25  	ListenPort                        int    `yaml:"listen_port"`
    26  	WebhookPath                       string `yaml:"webhook_path"`
    27  	configuration.DataSourceCommonCfg `yaml:",inline"`
    28  }
    29  
    30  type KubernetesAuditSource struct {
    31  	metricsLevel int
    32  	config       KubernetesAuditConfiguration
    33  	logger       *log.Entry
    34  	mux          *http.ServeMux
    35  	server       *http.Server
    36  	outChan      chan types.Event
    37  	addr         string
    38  }
    39  
    40  var eventCount = prometheus.NewCounterVec(
    41  	prometheus.CounterOpts{
    42  		Name: "cs_k8sauditsource_hits_total",
    43  		Help: "Total number of events received by k8s-audit source",
    44  	},
    45  	[]string{"source"})
    46  
    47  var requestCount = prometheus.NewCounterVec(
    48  	prometheus.CounterOpts{
    49  		Name: "cs_k8sauditsource_requests_total",
    50  		Help: "Total number of requests received",
    51  	},
    52  	[]string{"source"})
    53  
    54  func (ka *KubernetesAuditSource) GetUuid() string {
    55  	return ka.config.UniqueId
    56  }
    57  
    58  func (ka *KubernetesAuditSource) GetMetrics() []prometheus.Collector {
    59  	return []prometheus.Collector{eventCount, requestCount}
    60  }
    61  
    62  func (ka *KubernetesAuditSource) GetAggregMetrics() []prometheus.Collector {
    63  	return []prometheus.Collector{eventCount, requestCount}
    64  }
    65  
    66  func (ka *KubernetesAuditSource) UnmarshalConfig(yamlConfig []byte) error {
    67  	k8sConfig := KubernetesAuditConfiguration{}
    68  	err := yaml.UnmarshalStrict(yamlConfig, &k8sConfig)
    69  	if err != nil {
    70  		return fmt.Errorf("cannot parse k8s-audit configuration: %w", err)
    71  	}
    72  
    73  	ka.config = k8sConfig
    74  
    75  	if ka.config.ListenAddr == "" {
    76  		return fmt.Errorf("listen_addr cannot be empty")
    77  	}
    78  
    79  	if ka.config.ListenPort == 0 {
    80  		return fmt.Errorf("listen_port cannot be empty")
    81  	}
    82  
    83  	if ka.config.WebhookPath == "" {
    84  		return fmt.Errorf("webhook_path cannot be empty")
    85  	}
    86  
    87  	if ka.config.WebhookPath[0] != '/' {
    88  		ka.config.WebhookPath = "/" + ka.config.WebhookPath
    89  	}
    90  
    91  	if ka.config.Mode == "" {
    92  		ka.config.Mode = configuration.TAIL_MODE
    93  	}
    94  	return nil
    95  }
    96  
    97  func (ka *KubernetesAuditSource) Configure(config []byte, logger *log.Entry, MetricsLevel int) error {
    98  	ka.logger = logger
    99  	ka.metricsLevel = MetricsLevel
   100  
   101  	err := ka.UnmarshalConfig(config)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	ka.logger.Tracef("K8SAudit configuration: %+v", ka.config)
   107  
   108  	ka.addr = fmt.Sprintf("%s:%d", ka.config.ListenAddr, ka.config.ListenPort)
   109  
   110  	ka.mux = http.NewServeMux()
   111  
   112  	ka.server = &http.Server{
   113  		Addr:    ka.addr,
   114  		Handler: ka.mux,
   115  	}
   116  
   117  	ka.mux.HandleFunc(ka.config.WebhookPath, ka.webhookHandler)
   118  	return nil
   119  }
   120  
   121  func (ka *KubernetesAuditSource) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error {
   122  	return fmt.Errorf("k8s-audit datasource does not support command-line acquisition")
   123  }
   124  
   125  func (ka *KubernetesAuditSource) GetMode() string {
   126  	return ka.config.Mode
   127  }
   128  
   129  func (ka *KubernetesAuditSource) GetName() string {
   130  	return "k8s-audit"
   131  }
   132  
   133  func (ka *KubernetesAuditSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error {
   134  	return fmt.Errorf("k8s-audit datasource does not support one-shot acquisition")
   135  }
   136  
   137  func (ka *KubernetesAuditSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error {
   138  	ka.outChan = out
   139  	t.Go(func() error {
   140  		defer trace.CatchPanic("crowdsec/acquis/k8s-audit/live")
   141  		ka.logger.Infof("Starting k8s-audit server on %s:%d%s", ka.config.ListenAddr, ka.config.ListenPort, ka.config.WebhookPath)
   142  		t.Go(func() error {
   143  			err := ka.server.ListenAndServe()
   144  			if err != nil && err != http.ErrServerClosed {
   145  				return fmt.Errorf("k8s-audit server failed: %w", err)
   146  			}
   147  			return nil
   148  		})
   149  		<-t.Dying()
   150  		ka.logger.Infof("Stopping k8s-audit server on %s:%d%s", ka.config.ListenAddr, ka.config.ListenPort, ka.config.WebhookPath)
   151  		ka.server.Shutdown(context.TODO())
   152  		return nil
   153  	})
   154  	return nil
   155  }
   156  
   157  func (ka *KubernetesAuditSource) CanRun() error {
   158  	return nil
   159  }
   160  
   161  func (ka *KubernetesAuditSource) Dump() interface{} {
   162  	return ka
   163  }
   164  
   165  func (ka *KubernetesAuditSource) webhookHandler(w http.ResponseWriter, r *http.Request) {
   166  
   167  	if ka.metricsLevel != configuration.METRICS_NONE {
   168  		requestCount.WithLabelValues(ka.addr).Inc()
   169  	}
   170  	if r.Method != http.MethodPost {
   171  		w.WriteHeader(http.StatusMethodNotAllowed)
   172  		return
   173  	}
   174  	ka.logger.Tracef("webhookHandler called")
   175  	var auditEvents audit.EventList
   176  
   177  	jsonBody, err := io.ReadAll(r.Body)
   178  	if err != nil {
   179  		ka.logger.Errorf("Error reading request body: %v", err)
   180  		w.WriteHeader(http.StatusInternalServerError)
   181  		return
   182  	}
   183  	ka.logger.Tracef("webhookHandler receveid: %s", string(jsonBody))
   184  	err = json.Unmarshal(jsonBody, &auditEvents)
   185  	if err != nil {
   186  		ka.logger.Errorf("Error decoding audit events: %s", err)
   187  		w.WriteHeader(http.StatusInternalServerError)
   188  		return
   189  	}
   190  
   191  	remoteIP := strings.Split(r.RemoteAddr, ":")[0]
   192  	for _, auditEvent := range auditEvents.Items {
   193  		if ka.metricsLevel != configuration.METRICS_NONE {
   194  			eventCount.WithLabelValues(ka.addr).Inc()
   195  		}
   196  		bytesEvent, err := json.Marshal(auditEvent)
   197  		if err != nil {
   198  			ka.logger.Errorf("Error marshaling audit event: %s", err)
   199  			continue
   200  		}
   201  		ka.logger.Tracef("Got audit event: %s", string(bytesEvent))
   202  		l := types.Line{
   203  			Raw:     string(bytesEvent),
   204  			Labels:  ka.config.Labels,
   205  			Time:    auditEvent.StageTimestamp.Time,
   206  			Src:     remoteIP,
   207  			Process: true,
   208  			Module:  ka.GetName(),
   209  		}
   210  		ka.outChan <- types.Event{
   211  			Line:       l,
   212  			Process:    true,
   213  			Type:       types.LOG,
   214  			ExpectMode: types.LIVE,
   215  		}
   216  	}
   217  }