diff --git a/DEVELOP.md b/DEVELOP.md index d104d0bd..2819b26d 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -20,16 +20,13 @@ make test ```bash # start kind cluster -kind create cluster - -# deploy manifests -k apply -f deploy - -# start the controller -bin/firewall-controller --hosts-file ./hosts +make start # watch results k describe -n firewall firewall -cat nftables.v4 -cat hosts -``` \ No newline at end of file + +# see evebox +k port-forward -n firewall svc/evebox-server-service 8080:80 + +point your browser to http://localhost:8080 +``` diff --git a/Makefile b/Makefile index 5130a6dc..54bf9e5b 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,7 @@ vet: # Generate code generate: controller-gen statik manifests $(STATIK) -src=pkg/nftables -include='*.tpl' -dest=pkg/nftables -ns tpl + $(STATIK) -src=pkg/evebox -include='*.tpl' -dest=pkg/evebox -ns tpl $(STATIK) -src=config/crd/bases -ns crd $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." @@ -88,6 +89,30 @@ docker-build: docker-push: docker push ${DOCKER_IMG} +KUBECONFIG := $(shell pwd)/.kubeconfig + +.PHONY: start +start: kind-cluster-create + kind --name firewall-controller load docker-image metalstack/firewall-controller:latest + kubectl --kubeconfig $(KUBECONFIG) delete -f "deploy/firewall-controller.yaml" || true # for idempotence + kubectl --kubeconfig $(KUBECONFIG) apply -f "deploy/firewall-controller.yaml" + kubectl --kubeconfig $(KUBECONFIG) apply -f "deploy/firewall.yaml" + # tailing + stern --kubeconfig $(KUBECONFIG) '.*' + +.PHONY: kind-cluster-create +kind-cluster-create: docker-build + @if ! which kind > /dev/null; then echo "kind needs to be installed"; exit 1; fi + @if ! kind get clusters | grep firewall-controller > /dev/null; then \ + kind create cluster \ + --name firewall-controller \ + --kubeconfig $(KUBECONFIG); fi + +.PHONY: cleanup +cleanup: + kind delete cluster --name firewall-controller + rm -f $(KUBECONFIG) + # find or download controller-gen # download controller-gen if necessary .PHONY: controller-gen diff --git a/README.md b/README.md index 9614e8f1..5adb07be 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ metadata: namespace: firewall name: firewall spec: + # clusterid should be set from the gardener-extension-provider-metal + clusterid: "" + # projectid this cluster belongs to, should be set from the gardener-extension-provider-metal + projectid: " + password: ``` Example ClusterwideNetworkPolicy: @@ -59,6 +85,15 @@ spec: port: 53 - protocol: TCP port: 53 + ingress: + - from: + - cidr: 1.1.0.0/24 + except: + - 1.1.1.0/16 + - cidr: 8.8.8.8/32 + ports: + - protocol: TCP + port: 8443 ``` ## Status diff --git a/api/v1/firewall_types.go b/api/v1/firewall_types.go index 988ba4f2..6952e156 100644 --- a/api/v1/firewall_types.go +++ b/api/v1/firewall_types.go @@ -28,6 +28,7 @@ import ( // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Interval",type=string,JSONPath=`.spec.interval` // +kubebuilder:printcolumn:name="InternalPrefixes",type=string,JSONPath=`.spec.internalprefixes` +// +kubebuilder:printcolumn:name="IDS",type=string,JSONPath=`.spec.ids.serverurl` type Firewall struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -57,6 +58,13 @@ type FirewallSpec struct { // InternalPrefixes specify prefixes which are considered local to the partition or all regions. // Traffic to/from these prefixes is accounted as internal traffic InternalPrefixes []string `json:"internalprefixes,omitempty"` + // IDS configuration + // +optional + IDS *IDS `json:"ids,omitempty"` + // ClusterID the uuid of the cluster + ClusterID string `json:"clusterid,omitempty"` + // ProjectID the uuid of the project this cluster belongs to + ProjectID string `json:"projectid,omitempty"` } // FirewallStatus defines the observed state of Firewall @@ -117,6 +125,16 @@ type InterfaceStat struct { Packets int `json:"packets"` } +// IDS configures the intrusion detection +type IDS struct { + // ServerURL the url where the IDS + ServerURL string `json:"serverurl,omitempty"` + // BasicAuthEnabled must be set to true if event sink requires username and password + // if set to true a secret in the firewall namespace with the name "ids" in the firewall namespace must be present + // it must also contain a username and password data object + BasicAuthEnabled bool `json:"basicauthenabled"` +} + func init() { SchemeBuilder.Register(&Firewall{}, &FirewallList{}) } diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index ece2316b..727f345b 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -235,6 +235,11 @@ func (in *FirewallSpec) DeepCopyInto(out *FirewallSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IDS != nil { + in, out := &in.IDS, &out.IDS + *out = new(IDS) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallSpec. @@ -310,6 +315,21 @@ func (in *FirewallStatus) DeepCopy() *FirewallStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IDS) DeepCopyInto(out *IDS) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IDS. +func (in *IDS) DeepCopy() *IDS { + if in == nil { + return nil + } + out := new(IDS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in IDSStatsByDevice) DeepCopyInto(out *IDSStatsByDevice) { { diff --git a/architecture.drawio b/architecture.drawio index 424f5125..ac5d3a9c 100644 --- a/architecture.drawio +++ b/architecture.drawio @@ -1,20 +1,21 @@ - + - + - + - - + + - + - + + @@ -36,16 +37,16 @@ - + - + - + @@ -81,33 +82,60 @@ - - + + - - + + - - + + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/architecture.svg b/architecture.svg index 898a8b9d..0b99b4fa 100644 --- a/architecture.svg +++ b/architecture.svg @@ -1 +1 @@ -
Worker Node
Worker Node
Firewall
Firewall
Worker VRF
Worker VRF
Firewall-Controller
Firewall-Controller
Internet VRF
Internet VRF
Legacy VRF
Legacy VRF
1.2.3.0/24
1.2.3.0/24
172.17.0.0/16
172.17.0.0/16
10.0.0.0/0
10.0.0.0/0
0.0.0.0/0
0.0.0.0/0
<service>
nftables-exporter
<service>...
<service>
node-exporter
<service>...
droptailer
droptailer
Worker Node
Worker Node
Worker Node
Worker Node
droptailer
droptailer
send nftables drops
<grpc>
send nftables drops...
Viewer does not support full SVG 1.1
\ No newline at end of file +
Worker Node
Worker Node
Firewall
Firewall
Worker VRF
Worker VRF
Firewall-Controller
Firewall-Controller
Internet VRF
Internet VRF
Legacy VRF
Legacy VRF
1.2.3.0/24
1.2.3.0/24
172.17.0.0/16
172.17.0.0/16
10.0.0.0/0
10.0.0.0/0
0.0.0.0/0
0.0.0.0/0
<service>
nftables-exporter
<service>...
<service>
node-exporter
<service>...
droptailer
droptailer
Worker Node
Worker Node
Worker Node
Worker Node
droptailer
droptailer
send nftables drops
<grpc>
send nftables drops...
consume eve.json
consume eve.json
send events
basic auth
send events...
evebox-agent
evebox-agent
suricata
suricata
evebox-server
evebox-server
Viewer does not support full SVG 1.1
diff --git a/config/crd/bases/metal-stack.io_firewalls.yaml b/config/crd/bases/metal-stack.io_firewalls.yaml index 506326c3..fc352cd5 100644 --- a/config/crd/bases/metal-stack.io_firewalls.yaml +++ b/config/crd/bases/metal-stack.io_firewalls.yaml @@ -15,6 +15,9 @@ spec: - JSONPath: .spec.internalprefixes name: InternalPrefixes type: string + - JSONPath: .spec.ids.serverurl + name: IDS + type: string group: metal-stack.io names: kind: Firewall @@ -43,9 +46,28 @@ spec: spec: description: FirewallSpec defines the desired state of Firewall properties: + clusterid: + description: ClusterID the uuid of the cluster + type: string dryrun: description: DryRun if set to true, firewall rules are not applied type: boolean + ids: + description: IDS configuration + properties: + basicauthenabled: + description: BasicAuthEnabled must be set to true if event sink + requires username and password if set to true a secret in the + firewall namespace with the name "ids" in the firewall namespace + must be present it must also contain a username and password data + object + type: boolean + serverurl: + description: ServerURL the url where the IDS + type: string + required: + - basicauthenabled + type: object internalprefixes: description: InternalPrefixes specify prefixes which are considered local to the partition or all regions. Traffic to/from these prefixes @@ -60,6 +82,10 @@ spec: description: TrafficControl defines where to store the generated ipv4 firewall rules on disk type: string + projectid: + description: ProjectID the uuid of the project this cluster belongs + to + type: string ratelimits: description: RateLimits allows configuration of rate limit rules for interfaces. diff --git a/controllers/firewall_controller.go b/controllers/firewall_controller.go index 01333393..fe4f0689 100644 --- a/controllers/firewall_controller.go +++ b/controllers/firewall_controller.go @@ -43,6 +43,7 @@ import ( firewallv1 "github.com/metal-stack/firewall-controller/api/v1" "github.com/metal-stack/firewall-controller/pkg/collector" + "github.com/metal-stack/firewall-controller/pkg/evebox" "github.com/metal-stack/firewall-controller/pkg/nftables" "github.com/metal-stack/firewall-controller/pkg/suricata" networking "k8s.io/api/networking/v1" @@ -69,6 +70,8 @@ const ( nodeExporterService = "nftables-exporter" nodeExporterNamedPort = "nftexporter" nodeExporterPort = 9630 + + idsSecretName = "ids" ) var done = ctrl.Result{} @@ -128,6 +131,11 @@ func (r *FirewallReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { errors = multierror.Append(errors, err) } + log.Info("reconciling evebox agent") + if err = r.reconcileEveboxAgent(ctx, f, log); err != nil { + errors = multierror.Append(errors, err) + } + log.Info("updating status field") if err = r.updateStatus(ctx, f, log); err != nil { errors = multierror.Append(errors, err) @@ -283,6 +291,52 @@ func (r *FirewallReconciler) reconcileRules(ctx context.Context, f firewallv1.Fi return nil } +func (r *FirewallReconciler) reconcileEveboxAgent(ctx context.Context, f firewallv1.Firewall, log logr.Logger) error { + if f.Spec.IDS == nil { + return nil + } + + var ( + username string + password string + ) + if f.Spec.IDS.BasicAuthEnabled { + var secrets corev1.SecretList + if err := r.List(ctx, &secrets, &client.ListOptions{Namespace: namespace}); err != nil { + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return client.IgnoreNotFound(err) + } + + var idsSecret corev1.Secret + secretFound := false + for _, s := range secrets.Items { + if s.Name == idsSecretName { + idsSecret = s + secretFound = true + break + } + } + if !secretFound { + return fmt.Errorf("ids basic auth enabled but no secret with username and password given in secret:%s", idsSecretName) + } + u, ok := idsSecret.Data["username"] + if !ok { + return fmt.Errorf("ids basic auth enabled but no username given in secret:%s", idsSecretName) + } + p, ok := idsSecret.Data["password"] + if !ok { + return fmt.Errorf("ids basic auth enabled but no password given in secret:%s", idsSecretName) + } + username = string(u) + password = string(p) + } + + agent := evebox.NewEvebox(f.Spec, f.Spec.IDS.BasicAuthEnabled, username, password) + return agent.Reconcile() +} + type firewallService struct { name string port int32 @@ -460,5 +514,6 @@ func (r *FirewallReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches(&source.Kind{Type: &firewallv1.ClusterwideNetworkPolicy{}}, triggerFirewallReconcilation). Watches(&source.Kind{Type: &networking.NetworkPolicy{}}, triggerFirewallReconcilation). Watches(&source.Kind{Type: &corev1.Service{}}, triggerFirewallReconcilation). + Watches(&source.Kind{Type: &corev1.Secret{}}, triggerFirewallReconcilation). Complete(r) } diff --git a/deploy/firewall-controller.yaml b/deploy/firewall-controller.yaml index 40ea55b6..0e0a928a 100644 --- a/deploy/firewall-controller.yaml +++ b/deploy/firewall-controller.yaml @@ -2,7 +2,8 @@ apiVersion: v1 kind: Namespace metadata: - name: firewall-controller + name: firewall + --- apiVersion: apps/v1 kind: Deployment @@ -10,7 +11,7 @@ metadata: labels: app: firewall-controller name: firewall-controller - namespace: firewall-controller + namespace: firewall spec: replicas: 1 selector: @@ -22,7 +23,7 @@ spec: app: firewall-controller spec: containers: - - image: metal-stack.io/firewall-controller + - image: metalstack/firewall-controller imagePullPolicy: IfNotPresent name: firewall-controller resources: @@ -36,6 +37,135 @@ spec: capabilities: add: - "NET_ADMIN" + volumeMounts: + - name: run + mountPath: /run + - image: jasonish/suricata + imagePullPolicy: IfNotPresent + name: suricata + args: + - -i + - eth0 + - -c + - /etc/suricata/suricata.yaml + resources: + requests: + cpu: 100m + memory: 50m + limits: + cpu: 2000m + memory: 1G + securityContext: + capabilities: + add: + - "NET_ADMIN" + volumeMounts: + - name: suricata-config-volume + mountPath: /etc/suricata + - name: suricata-log + mountPath: /var/log/suricata + - name: run + mountPath: /run + - image: jasonish/evebox:master + imagePullPolicy: IfNotPresent + name: evebox-agent + args: + - agent + - -v + - -c + - /etc/evebox/agent.yaml + resources: + requests: + cpu: 100m + memory: 50m + limits: + cpu: 200m + memory: 1G + volumeMounts: + - name: evebox-agent-config-volume + mountPath: /etc/evebox + - name: evebox-lib + mountPath: /var/lib/evebox + - name: suricata-log + mountPath: /var/log/suricata + volumes: + - name: evebox-agent-config-volume + configMap: + name: evebox-agent-config + - name: suricata-config-volume + configMap: + name: suricata-config + - name: suricata-log + emptyDir: {} + - name: evebox-lib + emptyDir: {} + - name: run + emptyDir: {} + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: evebox-server + name: evebox-server + namespace: firewall +spec: + replicas: 1 + selector: + matchLabels: + app: evebox-server + template: + metadata: + labels: + app: evebox-server + spec: + containers: + - image: jasonish/evebox:master + imagePullPolicy: IfNotPresent + name: evebox-server + args: + - server + - -v + - -c + - /etc/evebox/evebox.yaml + - --host + - "0.0.0.0" + ports: + - containerPort: 5636 + protocol: TCP + name: evebox + resources: + requests: + cpu: 100m + memory: 50m + limits: + cpu: 200m + memory: 1G + volumeMounts: + - name: evebox-server-config-volume + mountPath: /etc/evebox + - name: evebox-lib + mountPath: /var/lib/evebox + volumes: + - name: evebox-server-config-volume + configMap: + name: evebox-server-config + - name: evebox-lib + emptyDir: {} +--- +kind: Service +apiVersion: v1 +metadata: + name: evebox-server-service + namespace: firewall +spec: + selector: + app: evebox-server + ports: + - port: 80 + protocol: TCP + targetPort: evebox --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -103,4 +233,948 @@ roleRef: name: firewall-controller subjects: - kind: User - name: system:serviceaccount:firewall-controller:default + name: system:serviceaccount:firewall:default + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: evebox-agent-config + namespace: firewall +data: + agent.yaml: | + server: + url: http://evebox-server-service:80 + bookmark-directory: "/var/lib/evebox" + input: + filename: "/var/log/suricata/eve.json" + custom-fields: + host: "evebox-agent" + clusterid: "firewall-controller-test" + projectid: "development" + rules: + - /etc/suricata/rules/*.rules + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: evebox-server-config + namespace: firewall +data: + evebox.yaml: | + data-directory: /var/lib/evebox + http: + tls: + enabled: false + # Database configuration. + database: + # Database type: elasticsearch, sqlite. + type: sqlite + authentication: + required: no + # Type of login required: + # - username -- just a username... + # - usernamepassword -- username and password + # env: EVEBOX_AUTHENTICATION_TYPE + type: usernamepassword + + # A little message that is displayed in the login dialog. + #login-message: Some message here... + input: + enabled: false + rules: + - /etc/suricata/rules/*.rules + geoip: + disabled: true + # Event services: links that will be provided on events to link to additonal + # services. + event-services: + + # Custom service to link the rule in Scirius. + - type: custom + enabled: false + name: Scirius + + # Only make available for alert types. + event-types: + - alert + + # URL template. All eve values can be used. + url: https://10.16.1.179/rules/rule/{{alert.signature_id}} + + # Custom service to link to Dumpy for full packet capture. + # + # This one has no event-types meaning its available for all event types. + - type: custom + enabled: false + name: Dumpy + + # The URL template, {{raw}} expands to the raw eve event as a JSON + # string which is then url encoded. + url: http://10.16.1.1:7000/?event={{raw}} + + # Open in new window. The default is the same window. + target: new +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: suricata-config + namespace: firewall +data: + suricata.yaml: | + %YAML 1.1 + --- + + vars: + # more specific is better for alert accuracy and performance + address-groups: + HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]" + #HOME_NET: "[192.168.0.0/16]" + #HOME_NET: "[10.0.0.0/8]" + #HOME_NET: "[172.16.0.0/12]" + #HOME_NET: "any" + + EXTERNAL_NET: "!$HOME_NET" + #EXTERNAL_NET: "any" + + HTTP_SERVERS: "$HOME_NET" + SMTP_SERVERS: "$HOME_NET" + SQL_SERVERS: "$HOME_NET" + DNS_SERVERS: "$HOME_NET" + TELNET_SERVERS: "$HOME_NET" + AIM_SERVERS: "$EXTERNAL_NET" + DC_SERVERS: "$HOME_NET" + DNP3_SERVER: "$HOME_NET" + DNP3_CLIENT: "$HOME_NET" + MODBUS_CLIENT: "$HOME_NET" + MODBUS_SERVER: "$HOME_NET" + ENIP_CLIENT: "$HOME_NET" + ENIP_SERVER: "$HOME_NET" + + port-groups: + HTTP_PORTS: "80" + SHELLCODE_PORTS: "!80" + ORACLE_PORTS: 1521 + SSH_PORTS: 22 + DNP3_PORTS: 20000 + MODBUS_PORTS: 502 + FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]" + FTP_PORTS: 21 + VXLAN_PORTS: 4789 + + ## + ## Step 2: select outputs to enable + ## + + # The default logging directory. Any log or output file will be + # placed here if its not specified with a full path name. This can be + # overridden with the -l command line parameter. + default-log-dir: /var/log/suricata/ + + # global stats configuration + stats: + enabled: yes + # The interval field (in seconds) controls at what interval + # the loggers are invoked. + interval: 8 + # Add decode events as stats. + #decoder-events: true + # Decoder event prefix in stats. Has been 'decoder' before, but that leads + # to missing events in the eve.stats records. See issue #2225. + #decoder-events-prefix: "decoder.event" + # Add stream events as stats. + #stream-events: false + + # Configure the type of alert (and other) logging you would like. + outputs: + # a line based alerts log similar to Snort's fast.log + - fast: + enabled: yes + filename: fast.log + append: yes + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + + # Extensible Event Format (nicknamed EVE) event log in JSON format + - eve-log: + enabled: yes + filetype: regular + filename: eve.json + + + # include the name of the input pcap file in pcap file processing mode + pcap-file: false + community-id: false + # Seed value for the ID output. Valid values are 0-65535. + community-id-seed: 0 + + xff: + enabled: no + # Two operation modes are available, "extra-data" and "overwrite". + mode: extra-data + # Two proxy deployments are supported, "reverse" and "forward". In + # a "reverse" deployment the IP address used is the last one, in a + # "forward" deployment the first IP address is used. + deployment: reverse + # Header name where the actual IP address will be reported, if more + # than one IP address is present, the last IP address will be the + # one taken into consideration. + header: X-Forwarded-For + + types: + - alert: + tagged-packets: yes + - anomaly: + enabled: yes + # + # Choose one or more types of anomaly logging and whether to enable + # logging of the packet header for packet anomalies. + types: + # decode: no + # stream: no + # applayer: yes + #packethdr: no + - http: + extended: yes # enable this for extended logging information + - dns: + - tls: + extended: yes # enable this for extended logging information + - files: + force-magic: no # force logging magic on all logged files + - smtp: + #- dnp3 + - ftp + #- rdp + - nfs + - smb + - tftp + - ikev2 + - krb5 + - snmp + #- sip + - dhcp: + enabled: yes + # When extended mode is on, all DHCP messages are logged + # with full detail. When extended mode is off (the + # default), just enough information to map a MAC address + # to an IP address is logged. + extended: no + - ssh + - stats: + totals: yes # stats for all threads merged together + threads: no # per thread stats + deltas: no # include delta values + # bi-directional flows + - flow + # uni-directional flows + #- netflow + + # Metadata event type. Triggered whenever a pktvar is saved + # and will include the pktvars, flowvars, flowbits and + # flowints. + #- metadata + + # deprecated - unified2 alert format for use with Barnyard2 + - unified2-alert: + enabled: no + # for further options see: + # https://suricata.readthedocs.io/en/suricata-5.0.0/configuration/suricata-yaml.html#alert-output-for-use-with-barnyard2-unified2-alert + + # a line based log of HTTP requests (no alerts) + - http-log: + enabled: no + filename: http.log + append: yes + #extended: yes # enable this for extended logging information + #custom: yes # enabled the custom logging format (defined by customformat) + #customformat: "%{%D-%H:%M:%S}t.%z %{X-Forwarded-For}i %H %m %h %u %s %B %a:%p -> %A:%P" + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + + # a line based log of TLS handshake parameters (no alerts) + - tls-log: + enabled: no # Log TLS connections. + filename: tls.log # File to store TLS logs. + append: yes + #extended: yes # Log extended information like fingerprint + #custom: yes # enabled the custom logging format (defined by customformat) + #customformat: "%{%D-%H:%M:%S}t.%z %a:%p -> %A:%P %v %n %d %D" + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + # output TLS transaction where the session is resumed using a + # session id + #session-resumption: no + + # output module to store certificates chain to disk + - tls-store: + enabled: no + #certs-log-dir: certs # directory to store the certificates files + - pcap-log: + enabled: no + filename: log.pcap + + # File size limit. Can be specified in kb, mb, gb. Just a number + # is parsed as bytes. + limit: 1000mb + + # If set to a value will enable ring buffer mode. Will keep Maximum of "max-files" of size "limit" + max-files: 2000 + + # Compression algorithm for pcap files. Possible values: none, lz4. + # Enabling compression is incompatible with the sguil mode. Note also + # that on Windows, enabling compression will *increase* disk I/O. + compression: none + + # Further options for lz4 compression. The compression level can be set + # to a value between 0 and 16, where higher values result in higher + # compression. + #lz4-checksum: no + #lz4-level: 0 + + mode: normal # normal, multi or sguil. + + # Directory to place pcap files. If not provided the default log + # directory will be used. Required for "sguil" mode. + #dir: /nsm_data/ + + #ts-format: usec # sec or usec second format (default) is filename.sec usec is filename.sec.usec + use-stream-depth: no #If set to "yes" packets seen after reaching stream inspection depth are ignored. "no" logs all packets + honor-pass-rules: no # If set to "yes", flows in which a pass rule matched will stopped being logged. + + # a full alerts log containing much information for signature writers + # or for investigating suspected false positives. + - alert-debug: + enabled: no + filename: alert-debug.log + append: yes + #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram' + + # alert output to prelude (https://www.prelude-siem.org/) only + # available if Suricata has been compiled with --enable-prelude + - alert-prelude: + enabled: no + profile: suricata + log-packet-content: no + log-packet-header: yes + + # Stats.log contains data from various counters of the Suricata engine. + - stats: + enabled: yes + filename: stats.log + append: no # append to file (yes) or overwrite it (no) + totals: yes # stats for all threads merged together + threads: no # per thread stats + null-values: yes # print counters that have value 0 + + # a line based alerts log similar to fast.log into syslog + - syslog: + enabled: no + # reported identity to syslog. If ommited the program name (usually + # suricata) will be used. + #identity: "suricata" + facility: local5 + #level: Info ## possible levels: Emergency, Alert, Critical, + ## Error, Warning, Notice, Info, Debug + + # deprecated a line based information for dropped packets in IPS mode + - drop: + enabled: no + - file-store: + version: 2 + enabled: no + xff: + enabled: no + # Two operation modes are available, "extra-data" and "overwrite". + mode: extra-data + # Two proxy deployments are supported, "reverse" and "forward". In + # a "reverse" deployment the IP address used is the last one, in a + # "forward" deployment the first IP address is used. + deployment: reverse + # Header name where the actual IP address will be reported, if more + # than one IP address is present, the last IP address will be the + # one taken into consideration. + header: X-Forwarded-For + + # deprecated - file-store v1 + - file-store: + enabled: no + # further options documented at: + # https://suricata.readthedocs.io/en/suricata-5.0.0/file-extraction/file-extraction.html#file-store-version-1 + + - tcp-data: + enabled: no + type: file + filename: tcp-data.log + + - http-body-data: + enabled: no + type: file + filename: http-data.log + + - lua: + enabled: no + #scripts-dir: /etc/suricata/lua-output/ + scripts: + # - script1.lua + + # Logging configuration. This is not about logging IDS alerts/events, but + # output about what Suricata is doing, like startup messages, errors, etc. + logging: + # The default log level, can be overridden in an output section. + # Note that debug level logging will only be emitted if Suricata was + # compiled with the --enable-debug configure option. + # + # This value is overridden by the SC_LOG_LEVEL env var. + default-log-level: notice + + # The default output format. Optional parameter, should default to + # something reasonable if not provided. Can be overridden in an + # output section. You can leave this out to get the default. + # + # This value is overridden by the SC_LOG_FORMAT env var. + #default-log-format: "[%i] %t - (%f:%l) <%d> (%n) -- " + + # A regex to filter output. Can be overridden in an output section. + # Defaults to empty (no filter). + # + # This value is overridden by the SC_LOG_OP_FILTER env var. + default-output-filter: + + # Define your logging outputs. If none are defined, or they are all + # disabled you will get the default - console output. + outputs: + - console: + enabled: yes + # type: json + - file: + enabled: yes + level: info + filename: suricata.log + # type: json + - syslog: + enabled: no + facility: local5 + format: "[%i] <%d> -- " + # type: json + + + ## + ## Step 4: configure common capture settings + ## + ## See "Advanced Capture Options" below for more options, including NETMAP + ## and PF_RING. + ## + + # Linux high speed capture support + af-packet: + - interface: eth0 + # Number of receive threads. "auto" uses the number of cores + #threads: auto + # Default clusterid. AF_PACKET will load balance packets based on flow. + cluster-id: 99 + cluster-type: cluster_flow + # In some fragmentation case, the hash can not be computed. If "defrag" is set + # to yes, the kernel will do the needed defragmentation before sending the packets. + defrag: yes + - interface: default + #threads: auto + #use-mmap: no + #tpacket-v3: yes + + # Cross platform libpcap capture support + pcap: + - interface: eth0 + - interface: default + #checksum-checks: auto + + # Settings for reading pcap files + pcap-file: + checksum-checks: auto + + app-layer: + protocols: + krb5: + enabled: yes + snmp: + enabled: yes + ikev2: + enabled: yes + tls: + enabled: yes + detection-ports: + dp: 443 + dcerpc: + enabled: yes + ftp: + enabled: yes + # memcap: 64mb + # RDP, disabled by default. + rdp: + #enabled: no + ssh: + enabled: yes + smtp: + enabled: yes + raw-extraction: no + # Configure SMTP-MIME Decoder + mime: + # Decode MIME messages from SMTP transactions + # (may be resource intensive) + # This field supercedes all others because it turns the entire + # process on or off + decode-mime: yes + + # Decode MIME entity bodies (ie. base64, quoted-printable, etc.) + decode-base64: yes + decode-quoted-printable: yes + + # Maximum bytes per header data value stored in the data structure + # (default is 2000) + header-value-depth: 2000 + + # Extract URLs and save in state data structure + extract-urls: yes + # Set to yes to compute the md5 of the mail body. You will then + # be able to journalize it. + body-md5: no + # Configure inspected-tracker for file_data keyword + inspected-tracker: + content-limit: 100000 + content-inspect-min-size: 32768 + content-inspect-window: 4096 + imap: + enabled: detection-only + smb: + enabled: yes + detection-ports: + dp: 139, 445 + + # Stream reassembly size for SMB streams. By default track it completely. + #stream-depth: 0 + + nfs: + enabled: yes + tftp: + enabled: yes + dns: + # memcaps. Globally and per flow/state. + #global-memcap: 16mb + #state-memcap: 512kb + + # How many unreplied DNS requests are considered a flood. + # If the limit is reached, app-layer-event:dns.flooded; will match. + #request-flood: 500 + + tcp: + enabled: yes + detection-ports: + dp: 53 + udp: + enabled: yes + detection-ports: + dp: 53 + http: + enabled: yes + libhtp: + default-config: + personality: IDS + + # Can be specified in kb, mb, gb. Just a number indicates + # it's in bytes. + request-body-limit: 100kb + response-body-limit: 100kb + + # inspection limits + request-body-minimal-inspect-size: 32kb + request-body-inspect-window: 4kb + response-body-minimal-inspect-size: 40kb + response-body-inspect-window: 16kb + + # response body decompression (0 disables) + response-body-decompress-layer-limit: 2 + + # auto will use http-body-inline mode in IPS mode, yes or no set it statically + http-body-inline: auto + + swf-decompression: + enabled: yes + type: both + compress-depth: 0 + decompress-depth: 0 + + double-decode-path: no + double-decode-query: no + + server-config: + + modbus: + enabled: no + detection-ports: + dp: 502 + stream-depth: 0 + + # DNP3 + dnp3: + enabled: no + detection-ports: + dp: 20000 + + # SCADA EtherNet/IP and CIP protocol support + enip: + enabled: no + detection-ports: + dp: 44818 + sp: 44818 + + ntp: + enabled: yes + + dhcp: + enabled: yes + + # SIP, disabled by default. + sip: + #enabled: no + + # Limit for the maximum number of asn1 frames to decode (default 256) + asn1-max-frames: 256 + coredump: + max-dump: unlimited + + host-mode: auto + unix-command: + enabled: true + filename: /run/suricata-command.socket + + legacy: + uricontent: enabled + + engine-analysis: + # enables printing reports for fast-pattern for every rule. + rules-fast-pattern: yes + # enables printing reports for each rule + rules: yes + + #recursion and match limits for PCRE where supported + pcre: + match-limit: 3500 + match-limit-recursion: 1500 + + host-os-policy: + # Make the default policy windows. + windows: [0.0.0.0/0] + bsd: [] + bsd-right: [] + old-linux: [] + linux: [] + old-solaris: [] + solaris: [] + hpux10: [] + hpux11: [] + irix: [] + macos: [] + vista: [] + windows2k3: [] + + # Defrag settings: + + defrag: + memcap: 32mb + hash-size: 65536 + trackers: 65535 # number of defragmented flows to follow + max-frags: 65535 # number of fragments to keep (higher than trackers) + prealloc: yes + timeout: 60 + + flow: + memcap: 128mb + hash-size: 65536 + prealloc: 10000 + emergency-recovery: 30 + #managers: 1 # default to one flow manager + #recyclers: 1 # default to one flow recycler thread + + vlan: + use-for-tracking: true + + flow-timeouts: + + default: + new: 30 + established: 300 + closed: 0 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-closed: 0 + emergency-bypassed: 50 + tcp: + new: 60 + established: 600 + closed: 60 + bypassed: 100 + emergency-new: 5 + emergency-established: 100 + emergency-closed: 10 + emergency-bypassed: 50 + udp: + new: 30 + established: 300 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-bypassed: 50 + icmp: + new: 30 + established: 300 + bypassed: 100 + emergency-new: 10 + emergency-established: 100 + emergency-bypassed: 50 + + stream: + memcap: 64mb + checksum-validation: yes # reject wrong csums + inline: auto # auto will use inline mode in IPS mode, yes or no set it statically + reassembly: + memcap: 256mb + depth: 1mb # reassemble 1mb into a stream + toserver-chunk-size: 2560 + toclient-chunk-size: 2560 + randomize-chunk-size: yes + #randomize-chunk-range: 10 + #raw: yes + #segment-prealloc: 2048 + #check-overlap-different-data: true + + # Host table: + # + # Host table is used by tagging and per host thresholding subsystems. + # + host: + hash-size: 4096 + prealloc: 1000 + memcap: 32mb + + # IP Pair table: + # + # Used by xbits 'ippair' tracking. + # + #ippair: + # hash-size: 4096 + # prealloc: 1000 + # memcap: 32mb + + # Decoder settings + + decoder: + # Teredo decoder is known to not be completely accurate + # as it will sometimes detect non-teredo as teredo. + teredo: + enabled: true + # VXLAN decoder is assigned to up to 4 UDP ports. By default only the + # IANA assigned port 4789 is enabled. + vxlan: + enabled: true + ports: $VXLAN_PORTS # syntax: '8472, 4789' + + detect: + profile: medium + custom-values: + toclient-groups: 3 + toserver-groups: 25 + sgh-mpm-context: auto + inspection-recursion-limit: 3000 + # If set to yes, the loading of signatures will be made after the capture + # is started. This will limit the downtime in IPS mode. + #delayed-detect: yes + + prefilter: + # default prefiltering setting. "mpm" only creates MPM/fast_pattern + # engines. "auto" also sets up prefilter engines for other keywords. + # Use --list-keywords=all to see which keywords support prefiltering. + default: mpm + + # the grouping values above control how many groups are created per + # direction. Port whitelisting forces that port to get it's own group. + # Very common ports will benefit, as well as ports with many expensive + # rules. + grouping: + #tcp-whitelist: 53, 80, 139, 443, 445, 1433, 3306, 3389, 6666, 6667, 8080 + #udp-whitelist: 53, 135, 5060 + + profiling: + # Log the rules that made it past the prefilter stage, per packet + # default is off. The threshold setting determines how many rules + # must have made it past pre-filter for that rule to trigger the + # logging. + #inspect-logging-threshold: 200 + grouping: + dump-to-disk: false + include-rules: false # very verbose + include-mpm-stats: false + + mpm-algo: auto + + + spm-algo: auto + + # Suricata is multi-threaded. Here the threading can be influenced. + threading: + set-cpu-affinity: no + cpu-affinity: + - management-cpu-set: + cpu: [ 0 ] # include only these CPUs in affinity settings + - receive-cpu-set: + cpu: [ 0 ] # include only these CPUs in affinity settings + - worker-cpu-set: + cpu: [ "all" ] + mode: "exclusive" + # Use explicitely 3 threads and don't compute number by using + # detect-thread-ratio variable: + # threads: 3 + prio: + low: [ 0 ] + medium: [ "1-2" ] + high: [ 3 ] + default: "medium" + detect-thread-ratio: 0.2 + + luajit: + states: 128 + + # Profiling settings. Only effective if Suricata has been built with the + # the --enable-profiling configure flag. + # + profiling: + # Run profiling for every xth packet. The default is 1, which means we + # profile every packet. If set to 1000, one packet is profiled for every + # 1000 received. + #sample-rate: 1000 + + # rule profiling + rules: + + # Profiling can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: yes + filename: rule_perf.log + append: yes + + # Sort options: ticks, avgticks, checks, matches, maxticks + # If commented out all the sort options will be used. + #sort: avgticks + + # Limit the number of sids for which stats are shown at exit (per sort). + limit: 10 + + # output to json + json: yes + + # per keyword profiling + keywords: + enabled: yes + filename: keyword_perf.log + append: yes + + prefilter: + enabled: yes + filename: prefilter_perf.log + append: yes + + # per rulegroup profiling + rulegroups: + enabled: yes + filename: rule_group_perf.log + append: yes + + # packet profiling + packets: + + # Profiling can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: yes + filename: packet_stats.log + append: yes + + # per packet csv output + csv: + + # Output can be disabled here, but it will still have a + # performance impact if compiled in. + enabled: no + filename: packet_stats.csv + + # profiling of locking. Only available when Suricata was built with + # --enable-profiling-locks. + locks: + enabled: no + filename: lock_stats.log + append: yes + + pcap-log: + enabled: no + filename: pcaplog_stats.log + append: yes + nfq: + nflog: + # netlink multicast group + # (the same as the iptables --nflog-group param) + # Group 0 is used by the kernel, so you can't use it + - group: 2 + # netlink buffer size + buffer-size: 18432 + # put default value here + - group: default + # set number of packet to queue inside kernel + qthreshold: 1 + # set the delay before flushing packet in the queue inside kernel + qtimeout: 100 + # netlink max buffer size + max-size: 20000 + + ## + ## Advanced Capture Options + ## + + # general settings affecting packet capture + capture: + netmap: + # To specify OS endpoint add plus sign at the end (e.g. "eth0+") + - interface: eth2 + - interface: default + + # PF_RING configuration. for use with native PF_RING support + # for more info see http://www.ntop.org/products/pf_ring/ + pfring: + - interface: eth0 + # Number of receive threads. If set to 'auto' Suricata will first try + # to use CPU (core) count and otherwise RSS queue count. + threads: auto + + # Default clusterid. PF_RING will load balance packets based on flow. + # All threads/processes that will participate need to have the same + # clusterid. + cluster-id: 99 + + # Default PF_RING cluster type. PF_RING can load balance per flow. + # Possible values are cluster_flow or cluster_round_robin. + cluster-type: cluster_flow + + - interface: default + #threads: 2 + ipfw: + + + napatech: + streams: ["0-3"] + auto-config: yes + + ports: [all] + hashmode: hash5tuplesorted + + default-rule-path: /var/lib/suricata/rules + rule-files: + - suricata.rules + classification-file: /etc/suricata/classification.config + reference-config-file: /etc/suricata/reference.config + diff --git a/deploy/firewall.yaml b/deploy/firewall.yaml index 3c51f0cd..f63eb6e4 100644 --- a/deploy/firewall.yaml +++ b/deploy/firewall.yaml @@ -13,3 +13,6 @@ spec: internalprefixes: - "127.0.0.1" - "100.64.0.0/8" + ids: + serverurl: http://evebox-server-service:80 + basicauthenabled: false diff --git a/deploy/ids-secret.yaml b/deploy/ids-secret.yaml new file mode 100644 index 00000000..de358fb3 --- /dev/null +++ b/deploy/ids-secret.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: ids + namespace: firewall +type: Opaque +data: + username: + password: diff --git a/go.mod b/go.mod index f4ab9a85..77d103f1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/metal-stack/firewall-controller go 1.14 require ( - github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v0.1.0 github.com/google/go-cmp v0.4.1 github.com/google/nftables v0.0.0-20200316075819-7127d9d22474 @@ -13,10 +12,10 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/rakyll/statik v0.1.7 github.com/txn2/txeh v1.3.0 - k8s.io/api v0.18.4 - k8s.io/apiextensions-apiserver v0.18.4 - k8s.io/apimachinery v0.18.4 - k8s.io/client-go v0.18.4 - sigs.k8s.io/controller-runtime v0.6.0 + k8s.io/api v0.18.6 + k8s.io/apiextensions-apiserver v0.18.6 + k8s.io/apimachinery v0.18.6 + k8s.io/client-go v0.18.6 + sigs.k8s.io/controller-runtime v0.6.1 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index d5f90164..e3a2b3a7 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -235,12 +237,16 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -252,6 +258,8 @@ github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46O github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -358,6 +366,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/prometheus v2.5.0+incompatible h1:7QPitgO2kOFG8ecuRn9O/4L9+10He72rVRJvMXrE9Hg= github.com/prometheus/prometheus v2.5.0+incompatible/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= @@ -500,6 +510,7 @@ golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6a golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -510,6 +521,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= @@ -599,52 +612,69 @@ k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0= k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= +k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= +k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8= k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= k8s.io/apiextensions-apiserver v0.18.3 h1:h6oZO+iAgg0HjxmuNnguNdKNB9+wv3O1EBDdDWJViQ0= k8s.io/apiextensions-apiserver v0.18.3/go.mod h1:TMsNGs7DYpMXd+8MOCX8KzPOCx8fnZMoIGB24m03+JE= k8s.io/apiextensions-apiserver v0.18.4 h1:Y3HGERmS8t9u12YNUFoOISqefaoGRuTc43AYCLzWmWE= k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= +k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= +k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk= k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA= k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= k8s.io/apiserver v0.18.3 h1:BVjccwKP/kEqY+ResOyWs0EKs7f4XL0d0E5GkU3uiqI= k8s.io/apiserver v0.18.3/go.mod h1:tHQRmthRPLUtwqsOnJJMoI8SW3lnoReZeE861lH8vUw= k8s.io/apiserver v0.18.4 h1:pn1jSQkfboPSirZopkVpEdLW4FcQLnYMaIY8LFxxj30= k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= +k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE= k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.18.3 h1:QaJzz92tsN67oorwzmoB0a9r9ZVHuD5ryjbCKP0U22k= k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc= k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= +k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw= +k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= k8s.io/component-base v0.18.3 h1:QXq+P4lgi4LCIREya1RDr5gTcBaVFhxEcALir3QCSDA= k8s.io/component-base v0.18.3/go.mod h1:bp5GzGR0aGkYEfTj+eTY0AN/vXTgkJdQXjNTTVUaa3k= k8s.io/component-base v0.18.4 h1:Kr53Fp1iCGNsl9Uv4VcRvLy7YyIqi9oaJOQ7SXtKI98= k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= +k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7 h1:uuHDyjllyzRyCIvvn0OBjiRB0SgBZGqHNYAmjR7fO50= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM= sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= +sigs.k8s.io/controller-runtime v0.6.1 h1:LcK2+nk0kmaOnKGN+vBcWHqY5WDJNJNB/c5pW+sU8fc= +sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= diff --git a/pkg/evebox/agent.yaml.tpl b/pkg/evebox/agent.yaml.tpl new file mode 100644 index 00000000..4699f688 --- /dev/null +++ b/pkg/evebox/agent.yaml.tpl @@ -0,0 +1,42 @@ +# EveBox Agent configuration file - subject to change. + +# Server information. +server: + url: {{ .ServerURL }} + + # Username and password. Note that at this time even with + # authentication enabled on the EveBox server, agents can still + # submit events without authenticating. You will need to supply and + # username and password if running behind a reverse proxy + # implementing authentication. + {{ if .BasicAuthEnabled }} + username: {{ .Username}} + password: {{ .Password}} + {{ end }} + +# Directory to store bookmark information. This is optional and not +# required if the agent has write access to the directory of the log +# file being reader. +bookmark-directory: "/var/lib/evebox" + +# If the EveBox server is running behind TLS and the certificate is +# self signed, certificate validation can be disabled. +#disable-certificate-check: true + +# Path to log file. Only a single path is allowed at this time. +input: + filename: "/var/log/suricata/eve.json" + + # Custom fields to add to the event. Only top level fields can be set, + # and only simple values (string, integer) can be set. + custom-fields: + # Set a host field. This will override the "host" field set by + # Suricata if the Suricata "sensor-name" option is set. + #host: "evebox-agent" + cluster: {{ .ClusterID }} + project: {{ .ProjectID }} + + # The EveBox agent can add the rule to an event. To do so, list the + # rule files here. + rules: + - /etc/suricata/rules/*.rules diff --git a/pkg/evebox/evebox.go b/pkg/evebox/evebox.go new file mode 100644 index 00000000..179251bd --- /dev/null +++ b/pkg/evebox/evebox.go @@ -0,0 +1,128 @@ +package evebox + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "os" + "os/exec" + "text/template" + + firewallv1 "github.com/metal-stack/firewall-controller/api/v1" + "github.com/metal-stack/firewall-controller/pkg/file" + _ "github.com/metal-stack/firewall-controller/pkg/nftables/statik" + "github.com/rakyll/statik/fs" +) + +const ( + eveboxAgentService = "evebox-agent.service" + systemctlBin = "/bin/systemctl" +) + +// Evebox configures the Evebox agent +type Evebox struct { + ServerURL string + BasicAuthEnabled bool + Username string + Password string + ClusterID string + ProjectID string + agentFile string + statikFS http.FileSystem +} + +// NewEvebox creates a evebox object which manages the evebox-agent +func NewEvebox(spec firewallv1.FirewallSpec, basicAuthEnabled bool, username, password string) *Evebox { + statikFS, err := fs.NewWithNamespace("tpl") + if err != nil { + panic(err) + } + + agentFile := "/etc/evebox/agent.yaml" + return &Evebox{ + ServerURL: spec.IDS.ServerURL, + BasicAuthEnabled: basicAuthEnabled, + Username: username, + Password: password, + ClusterID: spec.ClusterID, + ProjectID: spec.ProjectID, + statikFS: statikFS, + agentFile: agentFile, + } +} + +// Reconcile drives the nftables firewall against the desired state by comparison with the current rule file. +func (e *Evebox) Reconcile() error { + tmpFile, err := ioutil.TempFile("/var/tmp", "agent.yaml") + if err != nil { + return err + } + defer os.Remove(tmpFile.Name()) + + desired := tmpFile.Name() + err = e.write(desired) + if err != nil { + return err + } + if file.Equal(e.agentFile, desired) { + return nil + } + err = os.Rename(desired, e.agentFile) + if err != nil { + return err + } + return e.reload(e.agentFile) +} + +func (e *Evebox) write(file string) error { + c, err := e.renderString() + if err != nil { + return err + } + err = ioutil.WriteFile(file, []byte(c), 0644) + if err != nil { + return fmt.Errorf("error writing to eve agent file '%s': %w", file, err) + } + return nil +} + +func (e *Evebox) renderString() (string, error) { + var b bytes.Buffer + + tplString, err := e.readTpl() + if err != nil { + return "", err + } + + tpl := template.Must(template.New("yaml").Parse(tplString)) + + err = tpl.Execute(&b, e) + if err != nil { + return "", err + } + + return b.String(), nil +} + +func (e *Evebox) readTpl() (string, error) { + r, err := e.statikFS.Open("/agent.yaml.tpl") + if err != nil { + return "", err + } + defer r.Close() + bytes, err := ioutil.ReadAll(r) + if err != nil { + return "", err + } + return string(bytes), nil +} + +func (e *Evebox) reload(file string) error { + c := exec.Command(systemctlBin, "reload", eveboxAgentService) + err := c.Run() + if err != nil { + return fmt.Errorf("%s could not be applied, err: %w", file, err) + } + return nil +} diff --git a/pkg/file/file.go b/pkg/file/file.go new file mode 100644 index 00000000..9ebc12e9 --- /dev/null +++ b/pkg/file/file.go @@ -0,0 +1,42 @@ +package file + +import ( + "bytes" + "crypto/sha256" + "io" + "os" +) + +// Equal compares two files for content equality +// returns true of both have the same content hash, otherwise false +func Equal(source, target string) bool { + sourceChecksum, err := checksum(source) + if err != nil { + return false + } + + targetChecksum, err := checksum(target) + if err != nil { + return false + } + + return bytes.Equal(sourceChecksum, targetChecksum) +} + +func checksum(file string) ([]byte, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + + defer func() { + _ = f.Close() + }() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return nil, err + } + + return h.Sum(nil), nil +} diff --git a/pkg/nftables/firewall.go b/pkg/nftables/firewall.go index 9865180f..f89a2aa8 100644 --- a/pkg/nftables/firewall.go +++ b/pkg/nftables/firewall.go @@ -11,6 +11,7 @@ import ( "text/template" firewallv1 "github.com/metal-stack/firewall-controller/api/v1" + "github.com/metal-stack/firewall-controller/pkg/file" _ "github.com/metal-stack/firewall-controller/pkg/nftables/statik" "github.com/rakyll/statik/fs" corev1 "k8s.io/api/core/v1" @@ -107,7 +108,7 @@ func (f *Firewall) Reconcile() error { if err != nil { return err } - if equal(f.Ipv4RuleFile, desired) { + if file.Equal(f.Ipv4RuleFile, desired) { return nil } err = os.Rename(desired, f.Ipv4RuleFile) diff --git a/pkg/nftables/util.go b/pkg/nftables/util.go index 8c68e200..85a4890c 100644 --- a/pkg/nftables/util.go +++ b/pkg/nftables/util.go @@ -1,11 +1,7 @@ package nftables import ( - "bytes" - "crypto/sha256" "fmt" - "io" - "os" "sort" "strings" @@ -25,38 +21,6 @@ func uniqueSorted(elements []string) []string { return r } -func equal(source, target string) bool { - sourceChecksum, err := checksum(source) - if err != nil { - return false - } - - targetChecksum, err := checksum(target) - if err != nil { - return false - } - - return bytes.Equal(sourceChecksum, targetChecksum) -} - -func checksum(file string) ([]byte, error) { - f, err := os.Open(file) - if err != nil { - return nil, err - } - - defer func() { - _ = f.Close() - }() - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return nil, err - } - - return h.Sum(nil), nil -} - func assembleDestinationPortRule(common []string, protocol string, ports []string, comment string) string { parts := common parts = append(parts, fmt.Sprintf("%s dport { %s }", protocol, strings.Join(ports, ", ")))