From 1826af603e75afecc873e6d913ddcb9266baeb69 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sat, 1 May 2021 14:00:12 +0200 Subject: [PATCH 01/18] initial commit --- .gitignore | 1 + README.md | 99 +++++++++++++++++++++++++++++++++ falco/values.yaml | 15 +++++ tekton/eventlistener.yaml | 16 ++++++ tekton/pipeline.yaml | 23 ++++++++ tekton/pipelinerun.yaml | 14 +++++ tekton/rbac-event-listener.yaml | 65 ++++++++++++++++++++++ tekton/rbac-falco-task.yaml | 27 +++++++++ tekton/task.yaml | 19 +++++++ tekton/trigger-bindings.yaml | 12 ++++ tekton/trigger-template.yaml | 27 +++++++++ 11 files changed, 318 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 falco/values.yaml create mode 100644 tekton/eventlistener.yaml create mode 100644 tekton/pipeline.yaml create mode 100644 tekton/pipelinerun.yaml create mode 100644 tekton/rbac-event-listener.yaml create mode 100644 tekton/rbac-falco-task.yaml create mode 100644 tekton/task.yaml create mode 100644 tekton/trigger-bindings.yaml create mode 100644 tekton/trigger-template.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fd0354 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.history/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..590cb93 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# Falcosidekick + Tekton + +In this demo I won't explain how tekton works, there is great material on how to get started using tekton, +for example the [official docs](https://tekton.dev/docs/overview/). + +I have taken lots and lots of inspiration from this awesome blog post +[Falcosidekick + OpenFaas = a Kubernetes Response Engine, Part 2](https://falco.org/blog/falcosidekick-openfaas/). +I will more or less copy Batuhan Apaydın but do it with tekton. + +You can find all the pure yaml in this repo. TODO add link to the repo. + +## Minikube + +I'm sure you can use kind as well but falcosidekick complained a bit when i tried and I was to lazy to check out what extra flags i need to start in my kind cluster. + +```shell +minikube start --cpus 3 --memory 8192 --vm-driver virtualbox +``` + +## Install Tekton + +Install tekton quickly by using pipelines and triggers. From a operations point of view I would use the tekton operator. + +```shell +kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml +kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml + +``` + +If this is your first time using tekton I would recomend you jump in to the tekton triggers [getting-started guide](https://github.com/tektoncd/triggers/tree/v0.10.1/docs/getting-started) to see how it works. + +## Install Falco + Falcosidekick + +```shell +kubectl create namespace falco +helm repo add falcosecurity https://falcosecurity.github.io/charts +helm repo update + +cat <<'EOF' >> values.yaml +falcosidekick: + config: + webhook: + address: http://el-falco-listener:8080 + customHeaders: | + Falcon:true + enabled: true + + +customRules: + # Applications which are expected to communicate with the Kubernetes API + rules_user_known_k8s_api_callers.yaml: |- + - macro: user_known_contact_k8s_api_server_activities + condition: > + (container.image.repository = "gcr.io/tekton-releases/github.com/tektoncd/triggers/cmd/eventlistenersink") +EOF + +helm upgrade --install falco falcosecurity/falco --namespace falco -f values.yaml +``` + +We need to setup a custom rule for event-listener since tekton and the event listener talks allot to the kubernetes API. + +## Configure tekton + +```shell +kubectl create ns falcoresponse +cat < + (container.image.repository = "gcr.io/tekton-releases/github.com/tektoncd/triggers/cmd/eventlistenersink") diff --git a/tekton/eventlistener.yaml b/tekton/eventlistener.yaml new file mode 100644 index 0000000..ec04001 --- /dev/null +++ b/tekton/eventlistener.yaml @@ -0,0 +1,16 @@ +apiVersion: triggers.tekton.dev/v1alpha1 +kind: EventListener +metadata: + name: falco-listener +spec: + serviceAccountName: tekton-triggers-example-sa + triggers: + - name: cel-trig-with-matches + interceptors: + - cel: + filter: >- + header.match('Falcon', 'true') + bindings: + - ref: falco-binding + template: + ref: falco-trigger-template diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml new file mode 100644 index 0000000..d1a5e26 --- /dev/null +++ b/tekton/pipeline.yaml @@ -0,0 +1,23 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: hello +spec: + params: + - name: output + description: The output from falco + - name: priority + description: The priority from falco + - name: rule + description: The triggerd falco rule + tasks: + - name: run-hello + taskRef: + name: hello + params: + - name: output + value: "$(params.output)" + - name: priority + value: "$(params.priority)" + - name: rule + value: "$(params.rule)" diff --git a/tekton/pipelinerun.yaml b/tekton/pipelinerun.yaml new file mode 100644 index 0000000..0269a4f --- /dev/null +++ b/tekton/pipelinerun.yaml @@ -0,0 +1,14 @@ +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: hello-run- +spec: + pipelineRef: + name: hello + params: + - name: output + value: "somuchoutput" + - name: priority + value: "prio90000" + - name: rule + value: "rule1234" diff --git a/tekton/rbac-event-listener.yaml b/tekton/rbac-event-listener.yaml new file mode 100644 index 0000000..a108635 --- /dev/null +++ b/tekton/rbac-event-listener.yaml @@ -0,0 +1,65 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tekton-triggers-example-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: tekton-triggers-example-minimal +rules: + # EventListeners need to be able to fetch all namespaced resources + - apiGroups: ["triggers.tekton.dev"] + resources: + ["eventlisteners", "triggerbindings", "triggertemplates", "triggers"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + # configmaps is needed for updating logging config + resources: ["configmaps"] + verbs: ["get", "list", "watch"] + # Permissions to create resources in associated TriggerTemplates + - apiGroups: ["tekton.dev"] + resources: ["pipelineruns", "pipelineresources", "taskruns"] + verbs: ["create"] + - apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["impersonate"] + - apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: ["tekton-triggers"] + verbs: ["use"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: tekton-triggers-example-binding +subjects: + - kind: ServiceAccount + name: tekton-triggers-example-sa +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: tekton-triggers-example-minimal +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: tekton-triggers-example-clusterrole +rules: + # EventListeners need to be able to fetch any clustertriggerbindings + - apiGroups: ["triggers.tekton.dev"] + resources: ["clustertriggerbindings", "clusterinterceptors"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tekton-triggers-example-clusterbinding +subjects: + - kind: ServiceAccount + name: tekton-triggers-example-sa + namespace: falcoresponse +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tekton-triggers-example-clusterrole diff --git a/tekton/rbac-falco-task.yaml b/tekton/rbac-falco-task.yaml new file mode 100644 index 0000000..9b45dcc --- /dev/null +++ b/tekton/rbac-falco-task.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: falco-pod-delete + namespace: falcoresponse +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: falco-pod-delete-cluster-role +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "delete"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: falco-pod-delete-cluster-role-binding +roleRef: + kind: ClusterRole + name: falco-pod-delete-cluster-role + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: falco-pod-delete + namespace: falcoresponse diff --git a/tekton/task.yaml b/tekton/task.yaml new file mode 100644 index 0000000..eb87cf9 --- /dev/null +++ b/tekton/task.yaml @@ -0,0 +1,19 @@ +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: hello +spec: + params: + - name: output + description: The output from falco + - name: priority + description: The priority from falco + - name: rule + description: The triggerd falco rule + steps: + - name: hello + image: ubuntu + command: + - echo + args: + - "Hello World! output: $(params.output), priority: $(params.priority), rule: $(params.rule)" diff --git a/tekton/trigger-bindings.yaml b/tekton/trigger-bindings.yaml new file mode 100644 index 0000000..40c6e5e --- /dev/null +++ b/tekton/trigger-bindings.yaml @@ -0,0 +1,12 @@ +apiVersion: triggers.tekton.dev/v1alpha1 +kind: TriggerBinding +metadata: + name: falco-binding +spec: + params: + - name: output + value: $(body.output) + - name: priority + value: $(body.priority) + - name: rule + value: $(body.rule) diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml new file mode 100644 index 0000000..e21d835 --- /dev/null +++ b/tekton/trigger-template.yaml @@ -0,0 +1,27 @@ +apiVersion: triggers.tekton.dev/v1alpha1 +kind: TriggerTemplate +metadata: + name: falco-trigger-template +spec: + params: + - name: output + description: The output from falco + - name: priority + description: The priority from falco + - name: rule + description: The triggerd falco rule + resourcetemplates: + - apiVersion: tekton.dev/v1beta1 + kind: Pipeline + metadata: + generateName: hello-task-run- + spec: + taskRef: + name: hello + params: + - name: output + value: $(tt.params.output) + - name: priority + value: $(tt.params.priority) + - name: rule + value: $(tt.params.rule) From cef7ce4825b00bea2c3ed9d0f7d7d903836c503b Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sat, 1 May 2021 16:19:18 +0200 Subject: [PATCH 02/18] Update yaml * Add minor go code, super broken! * Add NOTES.md for me --- Dockerfile | 0 NOTES.md | 14 +++++ README.md | 9 ++++ falco/values.yaml | 5 +- go.mod | 8 +++ main.go | 90 +++++++++++++++++++++++++++++++++ tekton/eventlistener.yaml | 7 +-- tekton/pipeline.yaml | 1 + tekton/pipelinerun.yaml | 1 + tekton/rbac-event-listener.yaml | 2 + tekton/task.yaml | 1 + tekton/trigger-bindings.yaml | 1 + tekton/trigger-template.yaml | 1 + 13 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 Dockerfile create mode 100644 NOTES.md create mode 100644 go.mod create mode 100644 main.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..f1ce591 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,14 @@ +# Ideas + +## Output example + +This is how I can easily test my go application. +This output is from a command that is not okay in falco. + +export BODY='{"output":"14:49:49.264147779: Notice A shell was spawned in a container with an attached terminal (user=root user_loginuid=-1 k8s.ns=default k8s.pod=alpine container=a15057582acc shell=sh parent=runc cmdline=sh -c uptime terminal=34816 container_id=a15057582acc image=alpine) k8s.ns=default k8s.pod=alpine container=a15057582acc k8s.ns=default k8s.pod=alpine container=a15057582acc","priority":"Notice","rule":"Terminal shell in container","time":"2021-05-01T14:49:49.264147779Z", "output_fields": {"container.id":"a15057582acc","container.image.repository":"alpine","evt.time":1619880589264147779,"k8s.ns.name":"default","k8s.pod.name":"alpine","proc.cmdline":"sh -c uptime","proc.name":"sh","proc.pname":"runc","proc.tty":34816,"user.loginuid":-1,"user.name":"root"}}' + +## Potential issue + +This is a log from falco. Try to find which rule generates this log. + +{"output":"15:29:29.219916520: Notice Unexpected connection to K8s API Server from container (command=eventlistenersi --el-name=falco-listener --el-namespace=falcoresponse --port=8000 --readtimeout=5 --writetimeout=40 --idletimeout=120 --timeouthandler=30 --is-multi-ns=false --tls-cert= --tls-key= k8s.ns= k8s.pod= container=9c48dd873ab9 image=: connection=172.17.0.6:49936->10.96.0.1:443) k8s.ns= k8s.pod= container=9c48dd873ab9 k8s.ns= k8s.pod= container=9c48dd873ab9","priority":"Notice","rule":"Contact K8S API Server From Container","time":"2021-05-01T15:29:29.219916520Z", "output_fields": {"container.id":"9c48dd873ab9","container.image.repository":null,"container.image.tag":null,"evt.time":1619882969219916520,"fd.name":"172.17.0.6:49936->10.96.0.1:443","k8s.ns.name":null,"k8s.pod.name":null,"proc.cmdline":"eventlistenersi --el-name=falco-listener --el-namespace=falcoresponse --port=8000 --readtimeout=5 --writetimeout=40 --idletimeout=120 --timeouthandler=30 --is-multi-ns=false --tls-cert= --tls-key="}} diff --git a/README.md b/README.md index 590cb93..d4be334 100644 --- a/README.md +++ b/README.md @@ -97,3 +97,12 @@ EOF ### EventListener Notice the Cel header match=Falcon + +## Trigger job + +```shell +kubectl run alpine --namespace default --image=alpine --restart='Never' -- sh -c "sleep 600" +# Trigger the kill +kubectl exec -i --tty alpine --namespace default -- sh -c "uptime" + +``` diff --git a/falco/values.yaml b/falco/values.yaml index 388c693..a92d80e 100644 --- a/falco/values.yaml +++ b/falco/values.yaml @@ -1,9 +1,8 @@ falcosidekick: config: webhook: - address: http://el-falco-listener:8080 - customHeaders: | - Falcon:true + address: http://el-falco-listener.falcoresponse.svc.cluster.local:8080 + customHeaders: Falcon:true\,Stuff:yes enabled: true diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..16b9016 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/NissesSenap/falcosidekick-tekton + +go 1.16 + +require ( + k8s.io/apimachinery v0.21.0 + k8s.io/client-go v0.21.0 +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..6543873 --- /dev/null +++ b/main.go @@ -0,0 +1,90 @@ +package function + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "time" + + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +var kubeClient *kubernetes.Clientset + +func init() { + + // creates the in-cluster config + config, err := rest.InClusterConfig() + if err != nil { + panic(err.Error()) + } + // creates the clientset + kubeClient, err = kubernetes.NewForConfig(config) + if err != nil { + panic(err.Error()) + } +} + +type Alert struct { + Output string `json:"output"` + Priority string `json:"priority"` + Rule string `json:"rule"` + Time time.Time `json:"time"` + OutputFields struct { + ContainerID string `json:"container.id"` + ContainerImageRepository interface{} `json:"container.image.repository"` + ContainerImageTag interface{} `json:"container.image.tag"` + EvtTime int64 `json:"evt.time"` + FdName string `json:"fd.name"` + K8SNsName string `json:"k8s.ns.name"` + K8SPodName string `json:"k8s.pod.name"` + ProcCmdline string `json:"proc.cmdline"` + } `json:"output_fields"` +} + +var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco", "openfaas", "openfaas-fn"} + +func main() { + var alert Alert + + bodyReq := os.Getenv("BODY") + if bodyReq == "" { + panic("Need to get ENV var body") + } + fmt.Println(bodyReq) + bodyReqByte := []byte(bodyReq) + json.Unmarshal(bodyReqByte, alert) + + if r.Body != nil { + defer r.Body.Close() + + body, _ := ioutil.ReadAll(r.Body) + + json.Unmarshal(body, &alert) + + podName := alert.OutputFields.K8SPodName + namespace := alert.OutputFields.K8SNsName + + var critical bool + for _, ns := range CriticalNamespaces { + if ns == namespace { + critical = true + break + } + } + + if !critical { + log.Printf("Deleting pod %s from namespace %s", podName, namespace) + kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) + } + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} diff --git a/tekton/eventlistener.yaml b/tekton/eventlistener.yaml index ec04001..3d3f565 100644 --- a/tekton/eventlistener.yaml +++ b/tekton/eventlistener.yaml @@ -2,14 +2,11 @@ apiVersion: triggers.tekton.dev/v1alpha1 kind: EventListener metadata: name: falco-listener + namespace: falcoresponse spec: serviceAccountName: tekton-triggers-example-sa triggers: - - name: cel-trig-with-matches - interceptors: - - cel: - filter: >- - header.match('Falcon', 'true') + - name: cel-trig bindings: - ref: falco-binding template: diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index d1a5e26..d34d7c2 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -2,6 +2,7 @@ apiVersion: tekton.dev/v1beta1 kind: Pipeline metadata: name: hello + namespace: falcoresponse spec: params: - name: output diff --git a/tekton/pipelinerun.yaml b/tekton/pipelinerun.yaml index 0269a4f..4db32e1 100644 --- a/tekton/pipelinerun.yaml +++ b/tekton/pipelinerun.yaml @@ -2,6 +2,7 @@ apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: generateName: hello-run- + namespace: falcoresponse spec: pipelineRef: name: hello diff --git a/tekton/rbac-event-listener.yaml b/tekton/rbac-event-listener.yaml index a108635..2d8dbfd 100644 --- a/tekton/rbac-event-listener.yaml +++ b/tekton/rbac-event-listener.yaml @@ -2,11 +2,13 @@ apiVersion: v1 kind: ServiceAccount metadata: name: tekton-triggers-example-sa + namespace: falcoresponse --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: tekton-triggers-example-minimal + namespace: falcoresponse rules: # EventListeners need to be able to fetch all namespaced resources - apiGroups: ["triggers.tekton.dev"] diff --git a/tekton/task.yaml b/tekton/task.yaml index eb87cf9..cc4a6ed 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -2,6 +2,7 @@ apiVersion: tekton.dev/v1beta1 kind: Task metadata: name: hello + namespace: falcoresponse spec: params: - name: output diff --git a/tekton/trigger-bindings.yaml b/tekton/trigger-bindings.yaml index 40c6e5e..baffd4e 100644 --- a/tekton/trigger-bindings.yaml +++ b/tekton/trigger-bindings.yaml @@ -2,6 +2,7 @@ apiVersion: triggers.tekton.dev/v1alpha1 kind: TriggerBinding metadata: name: falco-binding + namespace: falcoresponse spec: params: - name: output diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml index e21d835..b13bb6d 100644 --- a/tekton/trigger-template.yaml +++ b/tekton/trigger-template.yaml @@ -2,6 +2,7 @@ apiVersion: triggers.tekton.dev/v1alpha1 kind: TriggerTemplate metadata: name: falco-trigger-template + namespace: falcoresponse spec: params: - name: output From f5ca713ad56cf408a73cb719eb62d2d1b793a850 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sun, 2 May 2021 11:14:05 +0200 Subject: [PATCH 03/18] pipelinerun not pipline in triggerTemplate https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings --- NOTES.md | 15 +++++++++++++++ README.md | 3 +++ tekton/pipeline.yaml | 8 ++++++++ tekton/task.yaml | 4 +++- tekton/trigger-bindings.yaml | 14 ++++++++------ tekton/trigger-template.yaml | 36 +++++++++++++++++++++--------------- 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/NOTES.md b/NOTES.md index f1ce591..6266357 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,5 +1,20 @@ # Ideas +## Current + +I currently get issues to even start a pipline. +Try to make the trigger-binding as basic as possible. Aka send the entire body and no other incoming params. +Lets echo that output in the hello task/ubuntu image. +In short get a basic eventbinding to work agian. + +### TODO + +- Create a super simple go app that works with the incoming data. + - probably just a single body for now, then discuss with falco community. +- Create a Dockerfile +- Push dockerfile to quay.io +- Verify e2e workflow + ## Output example This is how I can easily test my go application. diff --git a/README.md b/README.md index d4be334..afe6378 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,9 @@ EOF Notice the Cel header match=Falcon +Notice the annotations in triggerTemplate: `triggers.tekton.dev/old-escape-quotes: "true"`. +It's needed to be able to send the json data as a [string](https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings). + ## Trigger job ```shell diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index d34d7c2..07b6302 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -5,17 +5,25 @@ metadata: namespace: falcoresponse spec: params: + - name: body + description: The entire msg from falco + default: myBody - name: output description: The output from falco + default: myOutput - name: priority description: The priority from falco + default: myPriority - name: rule description: The triggerd falco rule + default: myRule tasks: - name: run-hello taskRef: name: hello params: + - name: body + value: "$(params.body)" - name: output value: "$(params.output)" - name: priority diff --git a/tekton/task.yaml b/tekton/task.yaml index cc4a6ed..2bf39a7 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -5,6 +5,8 @@ metadata: namespace: falcoresponse spec: params: + - name: body + description: The entire msg from falco - name: output description: The output from falco - name: priority @@ -17,4 +19,4 @@ spec: command: - echo args: - - "Hello World! output: $(params.output), priority: $(params.priority), rule: $(params.rule)" + - "Hello World! output: $(params.output), priority: $(params.priority), rule: $(params.rule), body: $(params.body)" diff --git a/tekton/trigger-bindings.yaml b/tekton/trigger-bindings.yaml index baffd4e..eaaaff9 100644 --- a/tekton/trigger-bindings.yaml +++ b/tekton/trigger-bindings.yaml @@ -5,9 +5,11 @@ metadata: namespace: falcoresponse spec: params: - - name: output - value: $(body.output) - - name: priority - value: $(body.priority) - - name: rule - value: $(body.rule) + - name: body + value: $(body) + #- name: output + # value: $(body.output) + #- name: priority + # value: $(body.priority) + #- name: rule + # value: $(body.rule) diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml index b13bb6d..8adab76 100644 --- a/tekton/trigger-template.yaml +++ b/tekton/trigger-template.yaml @@ -3,26 +3,32 @@ kind: TriggerTemplate metadata: name: falco-trigger-template namespace: falcoresponse + annotations: + triggers.tekton.dev/old-escape-quotes: "true" spec: params: - - name: output - description: The output from falco - - name: priority - description: The priority from falco - - name: rule - description: The triggerd falco rule + - name: body + description: The entire msg from falco + #- name: output + # description: The output from falco + #- name: priority + # description: The priority from falco + #- name: rule + # description: The triggerd falco rule resourcetemplates: - apiVersion: tekton.dev/v1beta1 - kind: Pipeline + kind: PipelineRun metadata: - generateName: hello-task-run- + generateName: hello-pipeline-run- spec: - taskRef: + pipelineRef: name: hello params: - - name: output - value: $(tt.params.output) - - name: priority - value: $(tt.params.priority) - - name: rule - value: $(tt.params.rule) + - name: body + value: $(tt.params.body) + #- name: output + # value: $(tt.params.output) + #- name: priority + # value: $(tt.params.priority) + #- name: rule + # value: $(tt.params.rule) From 4e984ea76b06c9246bf5e6fd777c43d0ac860f4b Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sun, 2 May 2021 11:30:21 +0200 Subject: [PATCH 04/18] Removal of all extra params --- NOTES.md | 9 +-------- tekton/pipeline.yaml | 15 --------------- tekton/pipelinerun.yaml | 6 +----- tekton/task.yaml | 8 +------- tekton/trigger-bindings.yaml | 6 ------ tekton/trigger-template.yaml | 12 ------------ 6 files changed, 3 insertions(+), 53 deletions(-) diff --git a/NOTES.md b/NOTES.md index 6266357..5bfa3e2 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,13 +1,6 @@ # Ideas -## Current - -I currently get issues to even start a pipline. -Try to make the trigger-binding as basic as possible. Aka send the entire body and no other incoming params. -Lets echo that output in the hello task/ubuntu image. -In short get a basic eventbinding to work agian. - -### TODO +## TODO - Create a super simple go app that works with the incoming data. - probably just a single body for now, then discuss with falco community. diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index 07b6302..0cf3e6f 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -8,15 +8,6 @@ spec: - name: body description: The entire msg from falco default: myBody - - name: output - description: The output from falco - default: myOutput - - name: priority - description: The priority from falco - default: myPriority - - name: rule - description: The triggerd falco rule - default: myRule tasks: - name: run-hello taskRef: @@ -24,9 +15,3 @@ spec: params: - name: body value: "$(params.body)" - - name: output - value: "$(params.output)" - - name: priority - value: "$(params.priority)" - - name: rule - value: "$(params.rule)" diff --git a/tekton/pipelinerun.yaml b/tekton/pipelinerun.yaml index 4db32e1..e28ecb9 100644 --- a/tekton/pipelinerun.yaml +++ b/tekton/pipelinerun.yaml @@ -7,9 +7,5 @@ spec: pipelineRef: name: hello params: - - name: output + - name: body value: "somuchoutput" - - name: priority - value: "prio90000" - - name: rule - value: "rule1234" diff --git a/tekton/task.yaml b/tekton/task.yaml index 2bf39a7..1237302 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -7,16 +7,10 @@ spec: params: - name: body description: The entire msg from falco - - name: output - description: The output from falco - - name: priority - description: The priority from falco - - name: rule - description: The triggerd falco rule steps: - name: hello image: ubuntu command: - echo args: - - "Hello World! output: $(params.output), priority: $(params.priority), rule: $(params.rule), body: $(params.body)" + - "$(params.body)" diff --git a/tekton/trigger-bindings.yaml b/tekton/trigger-bindings.yaml index eaaaff9..1043ad1 100644 --- a/tekton/trigger-bindings.yaml +++ b/tekton/trigger-bindings.yaml @@ -7,9 +7,3 @@ spec: params: - name: body value: $(body) - #- name: output - # value: $(body.output) - #- name: priority - # value: $(body.priority) - #- name: rule - # value: $(body.rule) diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml index 8adab76..6911ddb 100644 --- a/tekton/trigger-template.yaml +++ b/tekton/trigger-template.yaml @@ -9,12 +9,6 @@ spec: params: - name: body description: The entire msg from falco - #- name: output - # description: The output from falco - #- name: priority - # description: The priority from falco - #- name: rule - # description: The triggerd falco rule resourcetemplates: - apiVersion: tekton.dev/v1beta1 kind: PipelineRun @@ -26,9 +20,3 @@ spec: params: - name: body value: $(tt.params.body) - #- name: output - # value: $(tt.params.output) - #- name: priority - # value: $(tt.params.priority) - #- name: rule - # value: $(tt.params.rule) From fc6503e880afd6450001c8942e38c095f6d47f7d Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sun, 2 May 2021 11:41:39 +0200 Subject: [PATCH 05/18] go code that works --- .gitignore | 1 + Dockerfile | 41 +++++++++++++++++++++++++++++++++++++++++ Makefile | 24 ++++++++++++++++++++++++ main.go | 41 ++++++++++++++++------------------------- 4 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 0fd0354..232f68e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .history/ +bin/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e69de29..c894b64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM golang:1.15-buster as builder + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +RUN make bin/pc + +FROM debian:buster-slim + +ARG VERSION=0.0.1 +ARG BUILD_DATE=2021-05-1 + +LABEL \ + org.opencontainers.image.created="$BUILD_DATE" \ + org.opencontainers.image.authors="edvin.norling@gmail.com" \ + org.opencontainers.image.homepage="https://github.com/NissesSenap/falcosidekick-tekton" \ + org.opencontainers.image.documentation="https://github.com/NissesSenap/falcosidekick-tekton" \ + org.opencontainers.image.source="https://github.com/NissesSenap/falcosidekick-tekton" \ + org.opencontainers.image.version="$VERSION" \ + org.opencontainers.image.vendor="GitHub" \ + org.opencontainers.image.licenses="MIT" \ + summary="Kubernetes response Engine deletes pods after commands from falcosidekick" \ + description="Kubernetes response Engine delete pods is meant to be run inside k8s and delete pods after getting instructions from falcosidekick." \ + name="podDeleter" + +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +USER 1001 + +WORKDIR /app + +COPY --from=builder /app/bin/poddeleter . + +CMD ["./poddeleter"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ab1b302 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +BUILD_FILES = $(shell go list -f '{{range .GoFiles}}{{$$.Dir}}/{{.}}\ +{{end}}' ./...) + +GH_VERSION ?= $(shell git describe --tags 2>/dev/null || git rev-parse --short HEAD) +DATE_FMT = +%Y-%m-%d +BUILD_DATE = $(shell date "$(DATE_FMT)") +IMAGE_REPO = "quay.io/nissessenap/poddeleter" + +bin/pc: $(BUILD_FILES) + @go build -trimpath -o bin/poddeleter ./main.go + +bin/container: + docker build --build-arg BUILD_DATE=$(BUILD_DATE) --build-arg VERSION=$(GH_VERSION) . -t $(IMAGE_REPO):$(GH_VERSION) + +bin/push: + docker push $(IMAGE_REPO):$(GH_VERSION) + +clean: + rm -rf ./bin ./share +.PHONY: clean + +test: + go test ./... +.PHONY: test diff --git a/main.go b/main.go index 6543873..b80846c 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,10 @@ -package function +package main import ( "context" "encoding/json" "fmt" - "io/ioutil" "log" - "net/http" "os" "time" @@ -48,7 +46,7 @@ type Alert struct { } `json:"output_fields"` } -var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco", "openfaas", "openfaas-fn"} +var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} func main() { var alert Alert @@ -61,30 +59,23 @@ func main() { bodyReqByte := []byte(bodyReq) json.Unmarshal(bodyReqByte, alert) - if r.Body != nil { - defer r.Body.Close() + podName := alert.OutputFields.K8SPodName + namespace := alert.OutputFields.K8SNsName - body, _ := ioutil.ReadAll(r.Body) - - json.Unmarshal(body, &alert) - - podName := alert.OutputFields.K8SPodName - namespace := alert.OutputFields.K8SNsName - - var critical bool - for _, ns := range CriticalNamespaces { - if ns == namespace { - critical = true - break - } + var critical bool + for _, ns := range CriticalNamespaces { + if ns == namespace { + critical = true + break } + } - if !critical { - log.Printf("Deleting pod %s from namespace %s", podName, namespace) - kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) + if !critical { + log.Printf("Deleting pod %s from namespace %s", podName, namespace) + err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) + if err != nil { + log.Fatalf("Unable to delete pod due to err %v", err) + os.Exit(1) } } - - w.WriteHeader(http.StatusOK) - w.Write([]byte("OK")) } From ebed023e08d253e39f8236467c14d31c58e7b2fb Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sun, 2 May 2021 12:04:47 +0200 Subject: [PATCH 06/18] change hello task image to poddeleter:3a9d3d5 * Update command and args and use env instead * Fix go code... * Set SA correct in triggerTemplate * Set ful path in poddeleter command, tekton changes the WORKSPACE * Ignore poddeleter in falco so we don't get a loop of delete pods --- Dockerfile | 5 ++--- Makefile | 3 +++ falco/values.yaml | 3 ++- main.go | 40 ++++++++++++++++-------------------- tekton/pipelinerun.yaml | 1 + tekton/task.yaml | 9 ++++---- tekton/trigger-template.yaml | 1 + 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index c894b64..ae3a114 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,8 +28,7 @@ LABEL \ description="Kubernetes response Engine delete pods is meant to be run inside k8s and delete pods after getting instructions from falcosidekick." \ name="podDeleter" -RUN apt-get update && apt-get install -y \ - curl \ +RUN apt-get update && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* USER 1001 @@ -38,4 +37,4 @@ WORKDIR /app COPY --from=builder /app/bin/poddeleter . -CMD ["./poddeleter"] +CMD ["/app/poddeleter"] diff --git a/Makefile b/Makefile index ab1b302..e5b4e5a 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,14 @@ DATE_FMT = +%Y-%m-%d BUILD_DATE = $(shell date "$(DATE_FMT)") IMAGE_REPO = "quay.io/nissessenap/poddeleter" +print-% : ; @echo $* = $($*) + bin/pc: $(BUILD_FILES) @go build -trimpath -o bin/poddeleter ./main.go bin/container: docker build --build-arg BUILD_DATE=$(BUILD_DATE) --build-arg VERSION=$(GH_VERSION) . -t $(IMAGE_REPO):$(GH_VERSION) +.PHONY: bin/container bin/push: docker push $(IMAGE_REPO):$(GH_VERSION) diff --git a/falco/values.yaml b/falco/values.yaml index a92d80e..1dc562e 100644 --- a/falco/values.yaml +++ b/falco/values.yaml @@ -11,4 +11,5 @@ customRules: rules_user_known_k8s_api_callers.yaml: |- - macro: user_known_contact_k8s_api_server_activities condition: > - (container.image.repository = "gcr.io/tekton-releases/github.com/tektoncd/triggers/cmd/eventlistenersink") + (container.image.repository = "gcr.io/tekton-releases/github.com/tektoncd/triggers/cmd/eventlistenersink") or + (container.image.repository = "quay.io/nissessenap/poddeleter") diff --git a/main.go b/main.go index b80846c..97acdf2 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "context" "encoding/json" - "fmt" "log" "os" "time" @@ -13,22 +12,6 @@ import ( "k8s.io/client-go/rest" ) -var kubeClient *kubernetes.Clientset - -func init() { - - // creates the in-cluster config - config, err := rest.InClusterConfig() - if err != nil { - panic(err.Error()) - } - // creates the clientset - kubeClient, err = kubernetes.NewForConfig(config) - if err != nil { - panic(err.Error()) - } -} - type Alert struct { Output string `json:"output"` Priority string `json:"priority"` @@ -46,22 +29,22 @@ type Alert struct { } `json:"output_fields"` } -var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} - func main() { + var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} var alert Alert bodyReq := os.Getenv("BODY") if bodyReq == "" { - panic("Need to get ENV var body") + panic("Need to get environment variable BODY") } - fmt.Println(bodyReq) bodyReqByte := []byte(bodyReq) - json.Unmarshal(bodyReqByte, alert) + json.Unmarshal(bodyReqByte, &alert) podName := alert.OutputFields.K8SPodName namespace := alert.OutputFields.K8SNsName + log.Printf("PodName: %v & Namespace: %v", podName, namespace) + log.Printf("Rule: %v", alert.Rule) var critical bool for _, ns := range CriticalNamespaces { if ns == namespace { @@ -70,6 +53,19 @@ func main() { } } + // setup kubeClient + var kubeClient *kubernetes.Clientset + // creates the in-cluster config + config, err := rest.InClusterConfig() + if err != nil { + panic(err.Error()) + } + // creates the clientset + kubeClient, err = kubernetes.NewForConfig(config) + if err != nil { + panic(err.Error()) + } + if !critical { log.Printf("Deleting pod %s from namespace %s", podName, namespace) err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) diff --git a/tekton/pipelinerun.yaml b/tekton/pipelinerun.yaml index e28ecb9..db15086 100644 --- a/tekton/pipelinerun.yaml +++ b/tekton/pipelinerun.yaml @@ -4,6 +4,7 @@ metadata: generateName: hello-run- namespace: falcoresponse spec: + serviceAccountName: falco-pod-delete pipelineRef: name: hello params: diff --git a/tekton/task.yaml b/tekton/task.yaml index 1237302..cc0d3cf 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -9,8 +9,7 @@ spec: description: The entire msg from falco steps: - name: hello - image: ubuntu - command: - - echo - args: - - "$(params.body)" + image: quay.io/nissessenap/poddeleter:3a9d3d5 + env: + - name: BODY + value: "$(params.body)" diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml index 6911ddb..432600d 100644 --- a/tekton/trigger-template.yaml +++ b/tekton/trigger-template.yaml @@ -15,6 +15,7 @@ spec: metadata: generateName: hello-pipeline-run- spec: + serviceAccountName: falco-pod-delete pipelineRef: name: hello params: From a5cdefd426da59012f45185fa9312ae4cab612d8 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Sun, 2 May 2021 20:24:49 +0200 Subject: [PATCH 07/18] Improve instructions * Add go.sum * Clean up NOTES.md * Add LICENSE --- LICENSE | 21 ++ NOTES.md | 8 +- README.md | 516 ++++++++++++++++++++++++++++++++++++++++--- go.sum | 428 +++++++++++++++++++++++++++++++++++ tekton/pipeline.yaml | 1 - 5 files changed, 941 insertions(+), 33 deletions(-) create mode 100644 LICENSE create mode 100644 go.sum diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..093939b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Edvin N + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTES.md b/NOTES.md index 5bfa3e2..b1d2b19 100644 --- a/NOTES.md +++ b/NOTES.md @@ -13,10 +13,4 @@ This is how I can easily test my go application. This output is from a command that is not okay in falco. -export BODY='{"output":"14:49:49.264147779: Notice A shell was spawned in a container with an attached terminal (user=root user_loginuid=-1 k8s.ns=default k8s.pod=alpine container=a15057582acc shell=sh parent=runc cmdline=sh -c uptime terminal=34816 container_id=a15057582acc image=alpine) k8s.ns=default k8s.pod=alpine container=a15057582acc k8s.ns=default k8s.pod=alpine container=a15057582acc","priority":"Notice","rule":"Terminal shell in container","time":"2021-05-01T14:49:49.264147779Z", "output_fields": {"container.id":"a15057582acc","container.image.repository":"alpine","evt.time":1619880589264147779,"k8s.ns.name":"default","k8s.pod.name":"alpine","proc.cmdline":"sh -c uptime","proc.name":"sh","proc.pname":"runc","proc.tty":34816,"user.loginuid":-1,"user.name":"root"}}' - -## Potential issue - -This is a log from falco. Try to find which rule generates this log. - -{"output":"15:29:29.219916520: Notice Unexpected connection to K8s API Server from container (command=eventlistenersi --el-name=falco-listener --el-namespace=falcoresponse --port=8000 --readtimeout=5 --writetimeout=40 --idletimeout=120 --timeouthandler=30 --is-multi-ns=false --tls-cert= --tls-key= k8s.ns= k8s.pod= container=9c48dd873ab9 image=: connection=172.17.0.6:49936->10.96.0.1:443) k8s.ns= k8s.pod= container=9c48dd873ab9 k8s.ns= k8s.pod= container=9c48dd873ab9","priority":"Notice","rule":"Contact K8S API Server From Container","time":"2021-05-01T15:29:29.219916520Z", "output_fields": {"container.id":"9c48dd873ab9","container.image.repository":null,"container.image.tag":null,"evt.time":1619882969219916520,"fd.name":"172.17.0.6:49936->10.96.0.1:443","k8s.ns.name":null,"k8s.pod.name":null,"proc.cmdline":"eventlistenersi --el-name=falco-listener --el-namespace=falcoresponse --port=8000 --readtimeout=5 --writetimeout=40 --idletimeout=120 --timeouthandler=30 --is-multi-ns=false --tls-cert= --tls-key="}} +export BODY='{"output":"17:30:51.428027354: Notice A shell was spawned in a container with an attached terminal (user=root user_loginuid=-1 k8s.ns=falcoresponse k8s.pod=alpine container=ac7c5359a04b shell=sh parent=runc cmdline=sh -c cat /etc/resolv.conf terminal=34816 container_id=ac7c5359a04b image=alpine) k8s.ns=falcoresponse k8s.pod=alpine container=ac7c5359a04b k8s.ns=falcoresponse k8s.pod=alpine container=ac7c5359a04b","priority":"Notice","rule":"Terminal shell in container","time":"2021-05-02T17:30:51.428027354Z", "output_fields": {"container.id":"ac7c5359a04b","container.image.repository":"alpine","evt.time":1619976651428027354,"k8s.ns.name":"falcoresponse","k8s.pod.name":"alpine","proc.cmdline":"sh -c cat /etc/resolv.conf","proc.name":"sh","proc.pname":"runc","proc.tty":34816,"user.loginuid":-1,"user.name":"root"}}' diff --git a/README.md b/README.md index afe6378..92e1a8a 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,41 @@ # Falcosidekick + Tekton -In this demo I won't explain how tekton works, there is great material on how to get started using tekton, -for example the [official docs](https://tekton.dev/docs/overview/). +If you follow the Falco blog you have been able to see talk about "Kubernetes Response Engine". -I have taken lots and lots of inspiration from this awesome blog post -[Falcosidekick + OpenFaas = a Kubernetes Response Engine, Part 2](https://falco.org/blog/falcosidekick-openfaas/). -I will more or less copy Batuhan Apaydın but do it with tekton. +There they used two different serverless runtimes, [Kubeless](https://kubeless.io/) and [OpenFaas](https://www.openfaas.com/). -You can find all the pure yaml in this repo. TODO add link to the repo. +In my current environment I don't want to add any extra complexity to my cluster by adding any serverless runtimes. +So instead I will use [Tekton](https://tekton.dev) which is a project that I have been using for more then a year now to run my CI pipelines. -## Minikube +I won't go through how Tekton works in depth but, you can find a good overview in the [official docs](https://tekton.dev/docs/overview/). +But hear is the crash course: -I'm sure you can use kind as well but falcosidekick complained a bit when i tried and I was to lazy to check out what extra flags i need to start in my kind cluster. +- Tekton is built to be reusable. +- The smallest part of tekton is a **step**, a step can be something like. + - Run unit tests + - Run linting +- In a **task** you can have multiple steps. +- A **pipeline** consist of one or multiple tasks. +- To trigger a pipeline to actually run you need a **pipelinerun** or a **trigger-template**. + +Tekton also supports eventlisterners that is used to listen for webhooks. +Normally these webhooks listen for incoming changes to a git repo, for example a PR. +We will use it to listen for Falco events. + +You can find all the yaml and code in my [gitrepo](https://github.com/NissesSenap/falcosidekick-tekton). + +## Prerequisites + +As always within kubernetes we need a few tools. + +- Minikube v1.19.0 +- Helm v3.4.2 +- kubectl v1.20.5 + +## Provision local Kubernetes Cluster + +I'm sure you can use a kind cluster as well to follow along. +But falco complained a bit when I tried and I was to lazy to check out what extra flags I need so I went with minikube. ```shell minikube start --cpus 3 --memory 8192 --vm-driver virtualbox @@ -19,7 +43,8 @@ minikube start --cpus 3 --memory 8192 --vm-driver virtualbox ## Install Tekton -Install tekton quickly by using pipelines and triggers. From a operations point of view I would use the tekton operator. +Install Tekton pipelines and triggers. +When doing this in production I recommend the [Tekton operator](https://github.com/tektoncd/operator) ```shell kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml @@ -27,42 +52,425 @@ kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers ``` -If this is your first time using tekton I would recomend you jump in to the tekton triggers [getting-started guide](https://github.com/tektoncd/triggers/tree/v0.10.1/docs/getting-started) to see how it works. +Within a few seconds you should be able to see a few pods in the tekton-pipelines namespace. + +```shell +kubectl get pods -n tekton-pipelines +NAME READY STATUS RESTARTS AGE +tekton-pipelines-controller-6b94f5f96-cmf8m 1/1 Running 0 1h +tekton-pipelines-webhook-5bfbbd6475-fmjp4 1/1 Running 0 1h +tekton-triggers-controller-7cbd49fbb8-p4lrz 1/1 Running 0 1h +tekton-triggers-webhook-748fb7778c-w6zxv 1/1 Running 0 1h +``` + +If you want a deeper understanding how Tekton triggers work check out the [getting-started guide](https://github.com/tektoncd/triggers/tree/v0.13.0/docs/getting-started). ## Install Falco + Falcosidekick +Create the falco namespace and add the helm repo: + ```shell kubectl create namespace falco helm repo add falcosecurity https://falcosecurity.github.io/charts helm repo update +``` + +For simplicity and long term usability lets create a custom values file and start falco. +```shell cat <<'EOF' >> values.yaml falcosidekick: config: webhook: - address: http://el-falco-listener:8080 - customHeaders: | - Falcon:true + address: http://el-falco-listener.falcoresponse.svc.cluster.local:8080 + customHeaders: Falcon:true\,Stuff:yes enabled: true - customRules: # Applications which are expected to communicate with the Kubernetes API rules_user_known_k8s_api_callers.yaml: |- - macro: user_known_contact_k8s_api_server_activities condition: > - (container.image.repository = "gcr.io/tekton-releases/github.com/tektoncd/triggers/cmd/eventlistenersink") + (container.image.repository = "gcr.io/tekton-releases/github.com/tektoncd/triggers/cmd/eventlistenersink") or + (container.image.repository = "quay.io/nissessenap/poddeleter") EOF +# Install falco helm upgrade --install falco falcosecurity/falco --namespace falco -f values.yaml ``` -We need to setup a custom rule for event-listener since tekton and the event listener talks allot to the kubernetes API. +> Note the customRules and the webhook address. + +We haven't setup this webhook address nor is there currently any reason for us to have customRules for eventlistenersink or poddeleter, but it will come. +Both the Tekton event listener and my poddeleter does a few kubernetes API calls and we don't want falco to try to delete our own infrastructure. -## Configure tekton +You should be able to see falco and falcosidekick pods in the falco namespace: + +```shell +kubectl get pods --namespace falco + +NAME READY STATUS RESTARTS AGE +falco-44p4v 1/1 Running 0 64m +falco-falcosidekick-779b87f446-8zf9m 1/1 Running 0 2h +falco-falcosidekick-779b87f446-fdk55 1/1 Running 0 2h +``` + +## Protect me Falco + +My current setup is rather harsh and will delete any pods that breaks any falco rule. +In the future I plan to make both the go code and the tekton setup better and more flexible, hopefully this is something that we can do in the community. + +During this demo I will use the [Terminal Shell in container](https://github.com/falcosecurity/falco/blob/0d7068b048772b1e2d3ca5c86c30b3040eac57df/rules/falco_rules.yaml#L2063) since it's very easy to reproduce. + +So how does all this work? + +- We start a random pod and perform a simple exec. +- Falco will notice that a pod have broken the rule +- Sends a event to Falcosidekick +- Sends a webhook to tekton event-listener +- Tekton triggers a new pipeline +- A task is started with a small go program that deletes the pod + +So lets look at some yaml. + +### The go code + +I have adapted the code that Batuhan Apaydın wrote in [Falcosidekick + OpenFaas = a Kubernetes Response Engine, Part 2](https://falco.org/blog/falcosidekick-openfaas/) to listen for json in a environment variable instead of a http request. + +Bellow you can see the code, in short it does the following: + +- Check for environment variable BODY. +- Unmarshal the data according to the Alert struct. +- Takes the pod and namespace name from the event we got from falcosidekick. +- Checks to see if they are part of CriticalNamespaces, if it is, the poddeleter won't do anything. +- Deletes the pod in the namespace specified in the falcosidekick event. + +```main.go +package main + +import ( + "context" + "encoding/json" + "log" + "os" + "time" + + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +type Alert struct { + Output string `json:"output"` + Priority string `json:"priority"` + Rule string `json:"rule"` + Time time.Time `json:"time"` + OutputFields struct { + ContainerID string `json:"container.id"` + ContainerImageRepository interface{} `json:"container.image.repository"` + ContainerImageTag interface{} `json:"container.image.tag"` + EvtTime int64 `json:"evt.time"` + FdName string `json:"fd.name"` + K8SNsName string `json:"k8s.ns.name"` + K8SPodName string `json:"k8s.pod.name"` + ProcCmdline string `json:"proc.cmdline"` + } `json:"output_fields"` +} + +func main() { + var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} + var alert Alert + + bodyReq := os.Getenv("BODY") + if bodyReq == "" { + panic("Need to get environment variable BODY") + } + bodyReqByte := []byte(bodyReq) + json.Unmarshal(bodyReqByte, &alert) + + podName := alert.OutputFields.K8SPodName + namespace := alert.OutputFields.K8SNsName + log.Printf("PodName: %v & Namespace: %v", podName, namespace) + + log.Printf("Rule: %v", alert.Rule) + var critical bool + for _, ns := range CriticalNamespaces { + if ns == namespace { + critical = true + break + } + } + + // setup kubeClient + var kubeClient *kubernetes.Clientset + // creates the in-cluster config + config, err := rest.InClusterConfig() + if err != nil { + panic(err.Error()) + } + // creates the clientset + kubeClient, err = kubernetes.NewForConfig(config) + if err != nil { + panic(err.Error()) + } + + if !critical { + log.Printf("Deleting pod %s from namespace %s", podName, namespace) + err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) + if err != nil { + log.Fatalf("Unable to delete pod due to err %v", err) + os.Exit(1) + } + } +} +``` + +If you rather see it in [github](https://raw.githubusercontent.com/NissesSenap/falcosidekick-tekton/main/main.go). + +Now that you know what I will make run in your cluster lets take a look at the Tekton yaml. + +### Tekton pipeline + +Create the falcoresponse namespace to do our tests in. ```shell kubectl create ns falcoresponse +``` + +#### Task + +So lets start with the smallest part, the task. + +```shell +cat < I cannot stress this enough DO **NOT** DO THIS ON THIS event listener. +We haven't added any protection and this task have the power to kill pods in your cluster. Don't give a potential hacker this power! + +The event listener is rather complex and can do [allot](https://tekton.dev/docs/triggers/eventlisteners/). +For example one way to improve this tekton pipeline could be to check for a specific Priority from Falco. +This could be done with a [cel interceptor](https://tekton.dev/docs/triggers/eventlisteners/#cel-interceptors) +and filter on body.Priority. + +But for now lets just trigger on everything. + +The triggerBinding lets you define what data should be gathered from the incoming webhook. +In this case I take the entire request body. + +```shell +cat < Notice the [annotations](https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings), without it the pipeline never gets triggerd due to errors. + +We define the serviceAccount to use in our pipeline/task, point to the pipeline that we should use. +And what parameter to send down to the pipeline, notice to **tt** in front of parma. This is special syntax for TriggerBindings. + +#### RBAC + +As you might have noticed we have used two different serviceAccounts. One for the event-listener and one for the poddeleter it self. + +So lets create those serviceAccounts and give them some access. + +Bellow you can find the event listener RBAC config. + +```shell +cat < Hurray our "hacked" pod have been killed +If you look in the logs of the task + +```shell +kubectl logs -f $(kubectl get pods -l tekton.dev/task=hello -o jsonpath="{.items[0].metadata.name}" -n falcoresponse) -n falcoresponse +2021/05/02 18:11:00 PodName: alpine & Namespace: falcoresponse +2021/05/02 18:11:00 Rule: Terminal shell in container +2021/05/02 18:11:00 Deleting pod alpine from namespace falcoresponse ``` + +## Conclusion + +This was a rather simple example on how we can use the power of tekton together with Falco to protect us from bad actors that is trying to take over pods in our cluster. + +As noted during this post there are allot of potential improvements before this is production ready, for example: + +- The criticalNamepsaces in our go code is currently hard-coded and needs to be input variable of some kind. +- We need to be able to delete pods depending on priority level, rule or something similar. +- To be able to debug pods we might need to shell in th them, we need a way to ignore pods temporary without the pod getting restarted. Probably a annotation to look for in the pod before deleting it. +- And probably many other needs that you can come up wtih. + +If you have any ideas/issues come and share them in the falco slack [https://kubernetes.slack.com](https://kubernetes.slack.com) #falco. diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0e1211a --- /dev/null +++ b/go.sum @@ -0,0 +1,428 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= +k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= +k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= +k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= +k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index 0cf3e6f..e8a9a1d 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -7,7 +7,6 @@ spec: params: - name: body description: The entire msg from falco - default: myBody tasks: - name: run-hello taskRef: From b118f1df6021add3a44a06eaacd185d6347b911d Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 20:20:41 +0200 Subject: [PATCH 08/18] Split up main in a few functions --- Makefile | 1 + main.go | 63 +++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index e5b4e5a..fd171d8 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ bin/container: bin/push: docker push $(IMAGE_REPO):$(GH_VERSION) +.PHONY: bin/push clean: rm -rf ./bin ./share diff --git a/main.go b/main.go index 97acdf2..bacedbb 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "fmt" "log" "os" "time" @@ -12,6 +13,7 @@ import ( "k8s.io/client-go/rest" ) +// Alert falco data structure type Alert struct { Output string `json:"output"` Priority string `json:"priority"` @@ -30,48 +32,67 @@ type Alert struct { } func main() { - var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} - var alert Alert + var criticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} + var falcoEvent Alert bodyReq := os.Getenv("BODY") if bodyReq == "" { panic("Need to get environment variable BODY") } bodyReqByte := []byte(bodyReq) - json.Unmarshal(bodyReqByte, &alert) + err := json.Unmarshal(bodyReqByte, &falcoEvent) + if err != nil { + panic(fmt.Errorf("The data doesent match the struct %w", err)) + } - podName := alert.OutputFields.K8SPodName - namespace := alert.OutputFields.K8SNsName - log.Printf("PodName: %v & Namespace: %v", podName, namespace) + kubeClient, err := setupK8sClient() + if err != nil { + panic(fmt.Errorf("Unable to create in-cluster config: %w", err)) + } - log.Printf("Rule: %v", alert.Rule) - var critical bool - for _, ns := range CriticalNamespaces { - if ns == namespace { - critical = true - break - } + err = deletePod(kubeClient, falcoEvent, criticalNamespaces) + if err != nil { + log.Fatalf("Unable to delete pod due to err %v", err) + os.Exit(1) } +} - // setup kubeClient - var kubeClient *kubernetes.Clientset - // creates the in-cluster config +// setupK8sClient +func setupK8sClient() (*kubernetes.Clientset, error) { config, err := rest.InClusterConfig() if err != nil { - panic(err.Error()) + return nil, err } + // creates the clientset - kubeClient, err = kubernetes.NewForConfig(config) + kubeClient, err := kubernetes.NewForConfig(config) if err != nil { - panic(err.Error()) + return nil, err + } + return kubeClient, nil +} + +// deletePod, if not part of the criticalNamespaces the pod will be deleted +func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNamespaces []string) error { + podName := falcoEvent.OutputFields.K8SPodName + namespace := falcoEvent.OutputFields.K8SNsName + log.Printf("PodName: %v & Namespace: %v", podName, namespace) + + log.Printf("Rule: %v", falcoEvent.Rule) + var critical bool + for _, ns := range criticalNamespaces { + if ns == namespace { + critical = true + break + } } if !critical { log.Printf("Deleting pod %s from namespace %s", podName, namespace) err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) if err != nil { - log.Fatalf("Unable to delete pod due to err %v", err) - os.Exit(1) + return err } } + return nil } From 4dc3cc38584f9a90169d980b714f4767015eb4a4 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 20:58:39 +0200 Subject: [PATCH 09/18] Change from hello task to pod-delete Update naming overall --- README.md | 22 +++++++++++----------- tekton/eventlistener.yaml | 4 ++-- tekton/pipeline.yaml | 6 +++--- tekton/pipelinerun.yaml | 4 ++-- tekton/task.yaml | 4 ++-- tekton/trigger-bindings.yaml | 2 +- tekton/trigger-template.yaml | 6 +++--- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 92e1a8a..dce61d8 100644 --- a/README.md +++ b/README.md @@ -246,14 +246,14 @@ cat < Date: Mon, 3 May 2021 21:04:33 +0200 Subject: [PATCH 10/18] Rename param body to falco-event It's a less general name --- README.md | 20 ++++++++++---------- tekton/pipeline.yaml | 6 +++--- tekton/pipelinerun.yaml | 4 ++-- tekton/task.yaml | 4 ++-- tekton/trigger-bindings.yaml | 2 +- tekton/trigger-template.yaml | 6 +++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index dce61d8..ad29853 100644 --- a/README.md +++ b/README.md @@ -250,14 +250,14 @@ metadata: namespace: falcoresponse spec: params: - - name: body + - name: falco-event description: The entire msg from falco steps: - name: pod-delete image: quay.io/nissessenap/poddeleter:3a9d3d5 env: - - name: BODY - value: "$(params.body)" + - name: falco-event + value: "$(params.falco-event)" EOF ``` @@ -281,15 +281,15 @@ metadata: namespace: falcoresponse spec: params: - - name: body + - name: falco-event description: The entire msg from falco tasks: - name: run-pod-delete taskRef: name: pod-delete params: - - name: body - value: "$(params.body)" + - name: falco-event + value: "$(params.falco-event)" EOF ``` @@ -348,7 +348,7 @@ metadata: namespace: falcoresponse spec: params: - - name: body + - name: falco-event value: $(body) EOF ``` @@ -366,7 +366,7 @@ metadata: triggers.tekton.dev/old-escape-quotes: "true" spec: params: - - name: body + - name: falco-event description: The entire msg from falco resourcetemplates: - apiVersion: tekton.dev/v1beta1 @@ -378,8 +378,8 @@ spec: pipelineRef: name: pod-delete params: - - name: body - value: $(tt.params.body) + - name: falco-event + value: $(tt.params.falco-event) EOF ``` diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index 83c9d81..d50d351 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -5,12 +5,12 @@ metadata: namespace: falcoresponse spec: params: - - name: body + - name: falco-event description: The entire msg from falco tasks: - name: run-pod-delete taskRef: name: pod-delete params: - - name: body - value: "$(params.body)" + - name: falco-event + value: "$(params.falco-event)" diff --git a/tekton/pipelinerun.yaml b/tekton/pipelinerun.yaml index f3cc196..9261d68 100644 --- a/tekton/pipelinerun.yaml +++ b/tekton/pipelinerun.yaml @@ -8,5 +8,5 @@ spec: pipelineRef: name: pod-delete-pipeline params: - - name: body - value: "somuchoutput" + - name: falco-event + value: "afalcoevent" diff --git a/tekton/task.yaml b/tekton/task.yaml index d2836cc..30a3276 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -5,11 +5,11 @@ metadata: namespace: falcoresponse spec: params: - - name: body + - name: falco-event description: The entire msg from falco steps: - name: pod-delete image: quay.io/nissessenap/poddeleter:3a9d3d5 env: - name: BODY - value: "$(params.body)" + value: "$(params.falco-event)" diff --git a/tekton/trigger-bindings.yaml b/tekton/trigger-bindings.yaml index 7859685..8a79415 100644 --- a/tekton/trigger-bindings.yaml +++ b/tekton/trigger-bindings.yaml @@ -5,5 +5,5 @@ metadata: namespace: falcoresponse spec: params: - - name: body + - name: falco-event value: $(body) diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml index 9f44bad..beaa872 100644 --- a/tekton/trigger-template.yaml +++ b/tekton/trigger-template.yaml @@ -7,7 +7,7 @@ metadata: triggers.tekton.dev/old-escape-quotes: "true" spec: params: - - name: body + - name: falco-event description: The entire msg from falco resourcetemplates: - apiVersion: tekton.dev/v1beta1 @@ -19,5 +19,5 @@ spec: pipelineRef: name: pod-delete-pipeline params: - - name: body - value: $(tt.params.body) + - name: falco-event + value: $(tt.params.falco-event) From 829dcc2f3c425f61bd0340b10dd0ce8f02bb9987 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 21:14:17 +0200 Subject: [PATCH 11/18] Update README to match the new names --- README.md | 308 +++++++++++++++++--------------- main.go | 15 +- tekton/eventlistener.yaml | 1 + tekton/pipeline.yaml | 3 +- tekton/pipelinerun.yaml | 1 + tekton/rbac-event-listener.yaml | 1 + tekton/rbac-falco-task.yaml | 1 + tekton/task.yaml | 5 +- tekton/trigger-bindings.yaml | 1 + tekton/trigger-template.yaml | 1 + 10 files changed, 181 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index ad29853..ecfd31b 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ If you follow the Falco blog you have been able to see talk about "Kubernetes Response Engine". -There they used two different serverless runtimes, [Kubeless](https://kubeless.io/) and [OpenFaas](https://www.openfaas.com/). - -In my current environment I don't want to add any extra complexity to my cluster by adding any serverless runtimes. -So instead I will use [Tekton](https://tekton.dev) which is a project that I have been using for more then a year now to run my CI pipelines. +In those blogs there is talk about two different serverless runtimes, [Kubeless](https://kubeless.io/) and [OpenFaas](https://www.openfaas.com/). +The blogs talk about how you can trigger a pod after getting input from faclosidekick to kill a compromised pod. +My plan with this blog is to showcase how we can do the same thing but with [Tekton](https://tekton.dev) and not have to add any extra complexity to your cluster by adding a serverless runtime. I won't go through how Tekton works in depth but, you can find a good overview in the [official docs](https://tekton.dev/docs/overview/). But hear is the crash course: @@ -20,7 +19,7 @@ But hear is the crash course: Tekton also supports eventlisterners that is used to listen for webhooks. Normally these webhooks listen for incoming changes to a git repo, for example a PR. -We will use it to listen for Falco events. +But we will use it to listen for Falco events. You can find all the yaml and code in my [gitrepo](https://github.com/NissesSenap/falcosidekick-tekton). @@ -44,7 +43,7 @@ minikube start --cpus 3 --memory 8192 --vm-driver virtualbox ## Install Tekton Install Tekton pipelines and triggers. -When doing this in production I recommend the [Tekton operator](https://github.com/tektoncd/operator) +When doing this in production I recommend the [Tekton operator](https://github.com/tektoncd/operator) but for now lets use some pure yaml. ```shell kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml @@ -102,7 +101,7 @@ helm upgrade --install falco falcosecurity/falco --namespace falco -f values.yam > Note the customRules and the webhook address. We haven't setup this webhook address nor is there currently any reason for us to have customRules for eventlistenersink or poddeleter, but it will come. -Both the Tekton event listener and my poddeleter does a few kubernetes API calls and we don't want falco to try to delete our own infrastructure. +Both the Tekton event listener and my poddeleter does a few kubernetes API calls and we don't want falco generate alarms for our own infrastructure. You should be able to see falco and falcosidekick pods in the falco namespace: @@ -141,9 +140,11 @@ Bellow you can see the code, in short it does the following: - Check for environment variable BODY. - Unmarshal the data according to the Alert struct. -- Takes the pod and namespace name from the event we got from falcosidekick. -- Checks to see if they are part of CriticalNamespaces, if it is, the poddeleter won't do anything. -- Deletes the pod in the namespace specified in the falcosidekick event. +- Setups a kubernetes client, by calling setupK8sClient function. +- Calls the deletePod with a kubernetes client, the falcoEvent we gotten and a list of the critical Namespaces. +- Check in the event that we got from falcosidekick and see if the pod that triggerd the event is in our critical namespaces list. +- If it is return to the main and shutdown the application. +- Else deletes the pod in the namespace specified in the falcosidekick event. ```main.go package main @@ -151,6 +152,7 @@ package main import ( "context" "encoding/json" + "fmt" "log" "os" "time" @@ -160,6 +162,7 @@ import ( "k8s.io/client-go/rest" ) +// Alert falco data structure type Alert struct { Output string `json:"output"` Priority string `json:"priority"` @@ -178,50 +181,66 @@ type Alert struct { } func main() { - var CriticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} - var alert Alert + var criticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} + var falcoEvent Alert bodyReq := os.Getenv("BODY") if bodyReq == "" { panic("Need to get environment variable BODY") } bodyReqByte := []byte(bodyReq) - json.Unmarshal(bodyReqByte, &alert) + err := json.Unmarshal(bodyReqByte, &falcoEvent) + if err != nil { + panic(fmt.Errorf("The data doesent match the struct %w", err)) + } - podName := alert.OutputFields.K8SPodName - namespace := alert.OutputFields.K8SNsName - log.Printf("PodName: %v & Namespace: %v", podName, namespace) + kubeClient, err := setupK8sClient() + if err != nil { + panic(fmt.Errorf("Unable to create in-cluster config: %w", err)) + } - log.Printf("Rule: %v", alert.Rule) - var critical bool - for _, ns := range CriticalNamespaces { - if ns == namespace { - critical = true - break - } + err = deletePod(kubeClient, falcoEvent, criticalNamespaces) + if err != nil { + log.Fatalf("Unable to delete pod due to err %v", err) + os.Exit(1) } +} - // setup kubeClient - var kubeClient *kubernetes.Clientset - // creates the in-cluster config +// setupK8sClient +func setupK8sClient() (*kubernetes.Clientset, error) { config, err := rest.InClusterConfig() if err != nil { - panic(err.Error()) + return nil, err } + // creates the clientset - kubeClient, err = kubernetes.NewForConfig(config) + kubeClient, err := kubernetes.NewForConfig(config) if err != nil { - panic(err.Error()) + return nil, err } + return kubeClient, nil +} - if !critical { - log.Printf("Deleting pod %s from namespace %s", podName, namespace) - err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) - if err != nil { - log.Fatalf("Unable to delete pod due to err %v", err) - os.Exit(1) +// deletePod, if not part of the criticalNamespaces the pod will be deleted +func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNamespaces []string) error { + podName := falcoEvent.OutputFields.K8SPodName + namespace := falcoEvent.OutputFields.K8SNsName + log.Printf("PodName: %v & Namespace: %v", podName, namespace) + + log.Printf("Rule: %v", falcoEvent.Rule) + for _, ns := range criticalNamespaces { + if ns == namespace { + log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, ns) + return nil } } + + log.Printf("Deleting pod %s from namespace %s", podName, namespace) + err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) + if err != nil { + return err + } + return nil } ``` @@ -254,30 +273,30 @@ spec: description: The entire msg from falco steps: - name: pod-delete - image: quay.io/nissessenap/poddeleter:3a9d3d5 + image: quay.io/nissessenap/poddeleter@sha256:323524ac50274a51f5dfdb70bf3ea0d24c57b0ca312b5fbcaa9c81446d5da998 env: - - name: falco-event - value: "$(params.falco-event)" + - name: BODY + value: \$(params.falco-event) EOF ``` -- The task needs a input variable called body. +- The task needs a input variable falco-event. - The step called pod-delete uses the poddeleter image. -- Step pod-delete sets the environment BODY from the input parameter called body. +- Step pod-delete sets the environment BODY from the input parameter called falco-event. #### Pipeline Bellow you can see the reusability of tekton. This pipeline can easily add more tasks and other pipelines can use the exact same task as this one. -Just like the task this pipeline expects a parameter called body which it sends in to the pod-delete task. +Just like the task this pipeline expects a parameter called falco-event which it sends in to the pod-delete task. ```shell cat < I cannot stress this enough DO **NOT** DO THIS ON THIS event listener. -We haven't added any protection and this task have the power to kill pods in your cluster. Don't give a potential hacker this power! - -The event listener is rather complex and can do [allot](https://tekton.dev/docs/triggers/eventlisteners/). -For example one way to improve this tekton pipeline could be to check for a specific Priority from Falco. -This could be done with a [cel interceptor](https://tekton.dev/docs/triggers/eventlisteners/#cel-interceptors) -and filter on body.Priority. - -But for now lets just trigger on everything. - -The triggerBinding lets you define what data should be gathered from the incoming webhook. -In this case I take the entire request body. - -```shell -cat < Notice the [annotations](https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings), without it the pipeline never gets triggerd due to errors. - -We define the serviceAccount to use in our pipeline/task, point to the pipeline that we should use. -And what parameter to send down to the pipeline, notice to **tt** in front of parma. This is special syntax for TriggerBindings. - #### RBAC -As you might have noticed we have used two different serviceAccounts. One for the event-listener and one for the poddeleter it self. +We will be using two separate serviceAccounts, one for the event-listener and one for the poddeleter it self. So lets create those serviceAccounts and give them some access. @@ -502,6 +426,101 @@ subjects: EOF ``` +#### Event listener + +Finally time to configure the tekton webhook receiver. +Just like rest of Tekton the event listener builds on multiple parts. + +```shell +cat < I cannot stress this enough DO **NOT** MAKE THE EVENT LISTENER PUBLIC TO THE INTERNET. +We haven't added any protection and this task have the power to kill pods in your cluster. Don't give a potential hacker this power! + +The event listener is rather complex and can do [allot](https://tekton.dev/docs/triggers/eventlisteners/). +For example one way to improve this tekton pipeline could be to check for a specific Priority from Falco. +This could be done with a [cel interceptor](https://tekton.dev/docs/triggers/eventlisteners/#cel-interceptors) +and filter on body.Priority. + +But for now lets just trigger on everything. + +The triggerBinding lets you define what data should be gathered from the incoming webhook. +In this case I take the entire request body. + +```shell +cat < Notice the [annotations](https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings), without it the pipeline never gets triggerd due to errors. + +We define the serviceAccount to use in our pipeline/task, point to the pipeline that we should use. +And what parameter to send down to the pipeline, notice to **tt** in front of parma. This is special syntax for TriggerBindings. + +The triggerTemplate was the final pice needed and you should see a pod spinning up in the falcoresponse namespace. + +```shell +kubectl get pdos -n falcoresponse +NAME READY STATUS RESTARTS AGE +el-falco-listener-557786f598-zdmw2 1/1 Running 0 2h +``` + ## Trigger job Finally it's time to test our setup. @@ -546,10 +565,11 @@ Sun May 2 18:00:10 2021: Starting internal webserver, listening on port 8765 In **Terminal 2** you should see a pod starting and hopefully Complete without any errors and the alpine pod getting killed. ```shell -NAME READY STATUS RESTARTS AGE -alpine 0/1 Terminating 0 2m7s -el-falco-listener-557786f598-zdmw2 1/1 Running 0 2h -hello-pipeline-run-vs89z-run-hello-6cxqn-pod-sjk2z 0/1 Completed 0 1m +NAME READY STATUS RESTARTS AGE +alpine 0/1 Terminating 0 1m7s +el-falco-listener-557786f598-znzk9 1/1 Running 0 10m +falco-pod-delete-pipeline-run-w2vf8-run-pod-delete-jlxl7--mk44k 0/1 Completed 0 59s + ``` > Hurray our "hacked" pod have been killed @@ -572,6 +592,6 @@ As noted during this post there are allot of potential improvements before this - The criticalNamepsaces in our go code is currently hard-coded and needs to be input variable of some kind. - We need to be able to delete pods depending on priority level, rule or something similar. - To be able to debug pods we might need to shell in th them, we need a way to ignore pods temporary without the pod getting restarted. Probably a annotation to look for in the pod before deleting it. -- And probably many other needs that you can come up wtih. +- And probably many other needs that you can come up with. If you have any ideas/issues come and share them in the falco slack [https://kubernetes.slack.com](https://kubernetes.slack.com) #falco. diff --git a/main.go b/main.go index bacedbb..d811d1b 100644 --- a/main.go +++ b/main.go @@ -79,20 +79,17 @@ func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNames log.Printf("PodName: %v & Namespace: %v", podName, namespace) log.Printf("Rule: %v", falcoEvent.Rule) - var critical bool for _, ns := range criticalNamespaces { if ns == namespace { - critical = true - break + log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, ns) + return nil } } - if !critical { - log.Printf("Deleting pod %s from namespace %s", podName, namespace) - err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) - if err != nil { - return err - } + log.Printf("Deleting pod %s from namespace %s", podName, namespace) + err := kubeClient.CoreV1().Pods(namespace).Delete(context.Background(), podName, metaV1.DeleteOptions{}) + if err != nil { + return err } return nil } diff --git a/tekton/eventlistener.yaml b/tekton/eventlistener.yaml index 2ba3e48..9be17a1 100644 --- a/tekton/eventlistener.yaml +++ b/tekton/eventlistener.yaml @@ -1,3 +1,4 @@ +--- apiVersion: triggers.tekton.dev/v1alpha1 kind: EventListener metadata: diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml index d50d351..41abb8b 100644 --- a/tekton/pipeline.yaml +++ b/tekton/pipeline.yaml @@ -1,3 +1,4 @@ +--- apiVersion: tekton.dev/v1beta1 kind: Pipeline metadata: @@ -13,4 +14,4 @@ spec: name: pod-delete params: - name: falco-event - value: "$(params.falco-event)" + value: $(params.falco-event) diff --git a/tekton/pipelinerun.yaml b/tekton/pipelinerun.yaml index 9261d68..de50834 100644 --- a/tekton/pipelinerun.yaml +++ b/tekton/pipelinerun.yaml @@ -1,3 +1,4 @@ +--- apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: diff --git a/tekton/rbac-event-listener.yaml b/tekton/rbac-event-listener.yaml index 2d8dbfd..39ccaee 100644 --- a/tekton/rbac-event-listener.yaml +++ b/tekton/rbac-event-listener.yaml @@ -1,3 +1,4 @@ +--- apiVersion: v1 kind: ServiceAccount metadata: diff --git a/tekton/rbac-falco-task.yaml b/tekton/rbac-falco-task.yaml index 9b45dcc..ebbed94 100644 --- a/tekton/rbac-falco-task.yaml +++ b/tekton/rbac-falco-task.yaml @@ -1,3 +1,4 @@ +--- apiVersion: v1 kind: ServiceAccount metadata: diff --git a/tekton/task.yaml b/tekton/task.yaml index 30a3276..4434986 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -1,3 +1,4 @@ +--- apiVersion: tekton.dev/v1beta1 kind: Task metadata: @@ -9,7 +10,7 @@ spec: description: The entire msg from falco steps: - name: pod-delete - image: quay.io/nissessenap/poddeleter:3a9d3d5 + image: quay.io/nissessenap/poddeleter@sha256:323524ac50274a51f5dfdb70bf3ea0d24c57b0ca312b5fbcaa9c81446d5da998 env: - name: BODY - value: "$(params.falco-event)" + value: $(params.falco-event) diff --git a/tekton/trigger-bindings.yaml b/tekton/trigger-bindings.yaml index 8a79415..8e0aec7 100644 --- a/tekton/trigger-bindings.yaml +++ b/tekton/trigger-bindings.yaml @@ -1,3 +1,4 @@ +--- apiVersion: triggers.tekton.dev/v1alpha1 kind: TriggerBinding metadata: diff --git a/tekton/trigger-template.yaml b/tekton/trigger-template.yaml index beaa872..a03551f 100644 --- a/tekton/trigger-template.yaml +++ b/tekton/trigger-template.yaml @@ -1,3 +1,4 @@ +--- apiVersion: triggers.tekton.dev/v1alpha1 kind: TriggerTemplate metadata: From 30caa0be6876ae91817c18c4fcf70286b0999aae Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 22:28:38 +0200 Subject: [PATCH 12/18] Fix spelling errors * Also remove a unused exit(1) --- Dockerfile | 2 +- README.md | 21 ++++++++++----------- main.go | 1 - 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index ae3a114..267aea7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.15-buster as builder +FROM golang:1.16-buster as builder WORKDIR /app diff --git a/README.md b/README.md index ecfd31b..39957c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Falcosidekick + Tekton -If you follow the Falco blog you have been able to see talk about "Kubernetes Response Engine". +If you follow the Falco blog you have been able to see a recent blog post about "Kubernetes Response Engine". In those blogs there is talk about two different serverless runtimes, [Kubeless](https://kubeless.io/) and [OpenFaas](https://www.openfaas.com/). The blogs talk about how you can trigger a pod after getting input from faclosidekick to kill a compromised pod. @@ -25,7 +25,7 @@ You can find all the yaml and code in my [gitrepo](https://github.com/NissesSena ## Prerequisites -As always within kubernetes we need a few tools. +As always within Kubernetes we need a few tools, I have used the following versions of Helm, Minikube and kubectl in my setup. - Minikube v1.19.0 - Helm v3.4.2 @@ -136,7 +136,7 @@ So lets look at some yaml. I have adapted the code that Batuhan Apaydın wrote in [Falcosidekick + OpenFaas = a Kubernetes Response Engine, Part 2](https://falco.org/blog/falcosidekick-openfaas/) to listen for json in a environment variable instead of a http request. -Bellow you can see the code, in short it does the following: +Below you can see the code, in short it does the following: - Check for environment variable BODY. - Unmarshal the data according to the Alert struct. @@ -202,7 +202,6 @@ func main() { err = deletePod(kubeClient, falcoEvent, criticalNamespaces) if err != nil { log.Fatalf("Unable to delete pod due to err %v", err) - os.Exit(1) } } @@ -286,7 +285,7 @@ EOF #### Pipeline -Bellow you can see the reusability of tekton. +Here you can see the reusability of tekton. This pipeline can easily add more tasks and other pipelines can use the exact same task as this one. Just like the task this pipeline expects a parameter called falco-event which it sends in to the pod-delete task. @@ -316,9 +315,9 @@ EOF We will be using two separate serviceAccounts, one for the event-listener and one for the poddeleter it self. -So lets create those serviceAccounts and give them some access. +So lets create these serviceAccounts and give them some access. -Bellow you can find the event listener RBAC config. +Below you can find the event listener RBAC config. ```shell cat < Notice the [annotations](https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings), without it the pipeline never gets triggerd due to errors. +> Notice the [annotations](https://tekton.dev/docs/triggers/triggertemplates/#escaping-quoted-strings), without it the pipeline will never get triggered. We define the serviceAccount to use in our pipeline/task, point to the pipeline that we should use. -And what parameter to send down to the pipeline, notice to **tt** in front of parma. This is special syntax for TriggerBindings. +And what parameter to send down to the pipeline, notice the **tt** in front of parma. This is special syntax for TriggerBindings. The triggerTemplate was the final pice needed and you should see a pod spinning up in the falcoresponse namespace. @@ -587,11 +586,11 @@ kubectl logs -f $(kubectl get pods -l tekton.dev/task=pod-delete -o jsonpath="{. This was a rather simple example on how we can use the power of tekton together with Falco to protect us from bad actors that is trying to take over pods in our cluster. -As noted during this post there are allot of potential improvements before this is production ready, for example: +As noted during this post there are allot of potential improvements before this is production ready: - The criticalNamepsaces in our go code is currently hard-coded and needs to be input variable of some kind. - We need to be able to delete pods depending on priority level, rule or something similar. -- To be able to debug pods we might need to shell in th them, we need a way to ignore pods temporary without the pod getting restarted. Probably a annotation to look for in the pod before deleting it. +- To be able to debug pods we might need to shell in to them, we need a way to ignore pods temporary without the pod getting restarted. Probably a annotation to look for in the pod before deleting it. - And probably many other needs that you can come up with. If you have any ideas/issues come and share them in the falco slack [https://kubernetes.slack.com](https://kubernetes.slack.com) #falco. diff --git a/main.go b/main.go index d811d1b..063bb81 100644 --- a/main.go +++ b/main.go @@ -53,7 +53,6 @@ func main() { err = deletePod(kubeClient, falcoEvent, criticalNamespaces) if err != nil { log.Fatalf("Unable to delete pod due to err %v", err) - os.Exit(1) } } From 5bb363e542fa91e3b8b26cfccf6d0a8b76182e63 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 22:58:36 +0200 Subject: [PATCH 13/18] Use hash map instead of []string --- main.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 063bb81..e910c8e 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,12 @@ type Alert struct { } func main() { - var criticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} + criticalNamespaces := map[string]bool{ + "kube-system": true, + "kube-public": true, + "kube-node-lease": true, + "falco": true, + } var falcoEvent Alert bodyReq := os.Getenv("BODY") @@ -72,14 +77,14 @@ func setupK8sClient() (*kubernetes.Clientset, error) { } // deletePod, if not part of the criticalNamespaces the pod will be deleted -func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNamespaces []string) error { +func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNamespaces map[string]bool) error { podName := falcoEvent.OutputFields.K8SPodName namespace := falcoEvent.OutputFields.K8SNsName log.Printf("PodName: %v & Namespace: %v", podName, namespace) log.Printf("Rule: %v", falcoEvent.Rule) - for _, ns := range criticalNamespaces { - if ns == namespace { + for ns := range criticalNamespaces { + if criticalNamespaces[ns] { log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, ns) return nil } From 57b8fa48ecd9e16e2b5c6bcd32a8becb5d111d1c Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 23:05:18 +0200 Subject: [PATCH 14/18] minor updates to README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 39957c9..4772e59 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ If you follow the Falco blog you have been able to see a recent blog post about "Kubernetes Response Engine". -In those blogs there is talk about two different serverless runtimes, [Kubeless](https://kubeless.io/) and [OpenFaas](https://www.openfaas.com/). -The blogs talk about how you can trigger a pod after getting input from faclosidekick to kill a compromised pod. +In those blogs two different serverless runtimes is used, [Kubeless](https://kubeless.io/) and [OpenFaas](https://www.openfaas.com/). +The blogs describes how you can trigger a pod after getting input from faclosidekick to kill a compromised pod. My plan with this blog is to showcase how we can do the same thing but with [Tekton](https://tekton.dev) and not have to add any extra complexity to your cluster by adding a serverless runtime. I won't go through how Tekton works in depth but, you can find a good overview in the [official docs](https://tekton.dev/docs/overview/). -But hear is the crash course: +But here is the crash course: - Tekton is built to be reusable. -- The smallest part of tekton is a **step**, a step can be something like. +- The smallest part of tekton is a **step**, a step can be something like this: - Run unit tests - Run linting - In a **task** you can have multiple steps. @@ -33,8 +33,8 @@ As always within Kubernetes we need a few tools, I have used the following versi ## Provision local Kubernetes Cluster -I'm sure you can use a kind cluster as well to follow along. -But falco complained a bit when I tried and I was to lazy to check out what extra flags I need so I went with minikube. +I'm sure you can use a [kind](https://github.com/kubernetes-sigs/kind) cluster as well to follow along, +but falco complained a bit when I tried and I was to lazy to check out what extra flags I need so I went with minikube. ```shell minikube start --cpus 3 --memory 8192 --vm-driver virtualbox From abcecf99f14c3c0c3cab97e2a74c36dcfa1d8b87 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 23:28:28 +0200 Subject: [PATCH 15/18] use hash map correct... --- README.md | 2 +- main.go | 8 +++----- tekton/task.yaml | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4772e59..d456e11 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ spec: description: The entire msg from falco steps: - name: pod-delete - image: quay.io/nissessenap/poddeleter@sha256:323524ac50274a51f5dfdb70bf3ea0d24c57b0ca312b5fbcaa9c81446d5da998 + image: quay.io/nissessenap/poddeleter@sha256:12ddc401785694bc2d5dd6d5b528d26ecaa3befe6099fa4331450e4b039d137c env: - name: BODY value: \$(params.falco-event) diff --git a/main.go b/main.go index e910c8e..2d0bcbf 100644 --- a/main.go +++ b/main.go @@ -83,11 +83,9 @@ func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNames log.Printf("PodName: %v & Namespace: %v", podName, namespace) log.Printf("Rule: %v", falcoEvent.Rule) - for ns := range criticalNamespaces { - if criticalNamespaces[ns] { - log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, ns) - return nil - } + if criticalNamespaces[namespace] { + log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, namespace) + return nil } log.Printf("Deleting pod %s from namespace %s", podName, namespace) diff --git a/tekton/task.yaml b/tekton/task.yaml index 4434986..38c8fe6 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -10,7 +10,7 @@ spec: description: The entire msg from falco steps: - name: pod-delete - image: quay.io/nissessenap/poddeleter@sha256:323524ac50274a51f5dfdb70bf3ea0d24c57b0ca312b5fbcaa9c81446d5da998 + image: quay.io/nissessenap/poddeleter@sha256:12ddc401785694bc2d5dd6d5b528d26ecaa3befe6099fa4331450e4b039d137c env: - name: BODY value: $(params.falco-event) From d0ff1465bd12aec681df9decffa6e5dc91824e60 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Mon, 3 May 2021 23:35:52 +0200 Subject: [PATCH 16/18] Update README to match go code --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d456e11..e86c27f 100644 --- a/README.md +++ b/README.md @@ -141,8 +141,8 @@ Below you can see the code, in short it does the following: - Check for environment variable BODY. - Unmarshal the data according to the Alert struct. - Setups a kubernetes client, by calling setupK8sClient function. -- Calls the deletePod with a kubernetes client, the falcoEvent we gotten and a list of the critical Namespaces. -- Check in the event that we got from falcosidekick and see if the pod that triggerd the event is in our critical namespaces list. +- Calls the deletePod with a kubernetes client, the falcoEvent we gotten and a hash map of critical Namespaces. +- Check in the event that we got from falcosidekick and see if the pod that triggered the event is in our critical namespaces hash map. - If it is return to the main and shutdown the application. - Else deletes the pod in the namespace specified in the falcosidekick event. @@ -181,7 +181,12 @@ type Alert struct { } func main() { - var criticalNamespaces = []string{"kube-system", "kube-public", "kube-node-lease", "falco"} + criticalNamespaces := map[string]bool{ + "kube-system": true, + "kube-public": true, + "kube-node-lease": true, + "falco": true, + } var falcoEvent Alert bodyReq := os.Getenv("BODY") @@ -221,17 +226,15 @@ func setupK8sClient() (*kubernetes.Clientset, error) { } // deletePod, if not part of the criticalNamespaces the pod will be deleted -func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNamespaces []string) error { +func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNamespaces map[string]bool) error { podName := falcoEvent.OutputFields.K8SPodName namespace := falcoEvent.OutputFields.K8SNsName log.Printf("PodName: %v & Namespace: %v", podName, namespace) log.Printf("Rule: %v", falcoEvent.Rule) - for _, ns := range criticalNamespaces { - if ns == namespace { - log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, ns) - return nil - } + if criticalNamespaces[namespace] { + log.Printf("The pod %v won't be deleted due to it's part of the critical ns list: %v ", podName, namespace) + return nil } log.Printf("Deleting pod %s from namespace %s", podName, namespace) @@ -241,6 +244,7 @@ func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNames } return nil } + ``` If you rather see it in [github](https://raw.githubusercontent.com/NissesSenap/falcosidekick-tekton/main/main.go). From a0037ab569826a4c65fbd91d3cd001c80f5c325f Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Tue, 4 May 2021 08:30:21 +0200 Subject: [PATCH 17/18] Being more consistent in main.go --- README.md | 17 ++++++++--------- main.go | 14 +++++++------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e86c27f..4e8091e 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Below you can see the code, in short it does the following: - Check for environment variable BODY. - Unmarshal the data according to the Alert struct. -- Setups a kubernetes client, by calling setupK8sClient function. +- Setups a kubernetes client, by calling setupKubeClient function. - Calls the deletePod with a kubernetes client, the falcoEvent we gotten and a hash map of critical Namespaces. - Check in the event that we got from falcosidekick and see if the pod that triggered the event is in our critical namespaces hash map. - If it is return to the main and shutdown the application. @@ -152,7 +152,6 @@ package main import ( "context" "encoding/json" - "fmt" "log" "os" "time" @@ -187,21 +186,22 @@ func main() { "kube-node-lease": true, "falco": true, } + var falcoEvent Alert bodyReq := os.Getenv("BODY") if bodyReq == "" { - panic("Need to get environment variable BODY") + log.Fatalf("Need to get environment variable BODY") } bodyReqByte := []byte(bodyReq) err := json.Unmarshal(bodyReqByte, &falcoEvent) if err != nil { - panic(fmt.Errorf("The data doesent match the struct %w", err)) + log.Fatalf("The data doesent match the struct %v", err) } - kubeClient, err := setupK8sClient() + kubeClient, err := setupKubeClient() if err != nil { - panic(fmt.Errorf("Unable to create in-cluster config: %w", err)) + log.Fatalf("Unable to create in-cluster config: %v", err) } err = deletePod(kubeClient, falcoEvent, criticalNamespaces) @@ -210,8 +210,8 @@ func main() { } } -// setupK8sClient -func setupK8sClient() (*kubernetes.Clientset, error) { +// setupKubeClient +func setupKubeClient() (*kubernetes.Clientset, error) { config, err := rest.InClusterConfig() if err != nil { return nil, err @@ -244,7 +244,6 @@ func deletePod(kubeClient *kubernetes.Clientset, falcoEvent Alert, criticalNames } return nil } - ``` If you rather see it in [github](https://raw.githubusercontent.com/NissesSenap/falcosidekick-tekton/main/main.go). diff --git a/main.go b/main.go index 2d0bcbf..06a3b7d 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "context" "encoding/json" - "fmt" "log" "os" "time" @@ -38,21 +37,22 @@ func main() { "kube-node-lease": true, "falco": true, } + var falcoEvent Alert bodyReq := os.Getenv("BODY") if bodyReq == "" { - panic("Need to get environment variable BODY") + log.Fatalf("Need to get environment variable BODY") } bodyReqByte := []byte(bodyReq) err := json.Unmarshal(bodyReqByte, &falcoEvent) if err != nil { - panic(fmt.Errorf("The data doesent match the struct %w", err)) + log.Fatalf("The data doesent match the struct %v", err) } - kubeClient, err := setupK8sClient() + kubeClient, err := setupKubeClient() if err != nil { - panic(fmt.Errorf("Unable to create in-cluster config: %w", err)) + log.Fatalf("Unable to create in-cluster config: %v", err) } err = deletePod(kubeClient, falcoEvent, criticalNamespaces) @@ -61,8 +61,8 @@ func main() { } } -// setupK8sClient -func setupK8sClient() (*kubernetes.Clientset, error) { +// setupKubeClient +func setupKubeClient() (*kubernetes.Clientset, error) { config, err := rest.InClusterConfig() if err != nil { return nil, err From a4d989746c923eb7133ce22a784ddda3cf60db83 Mon Sep 17 00:00:00 2001 From: Edvin Norling Date: Tue, 4 May 2021 08:38:21 +0200 Subject: [PATCH 18/18] Update docker tag to latest --- README.md | 2 +- tekton/task.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e8091e..8099b5e 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ spec: description: The entire msg from falco steps: - name: pod-delete - image: quay.io/nissessenap/poddeleter@sha256:12ddc401785694bc2d5dd6d5b528d26ecaa3befe6099fa4331450e4b039d137c + image: quay.io/nissessenap/poddeleter@sha256:ae94ec2c9f005573e31e4944d1055a0dd92ee7594e7e7e36a4540a1811977270 env: - name: BODY value: \$(params.falco-event) diff --git a/tekton/task.yaml b/tekton/task.yaml index 38c8fe6..57e86ed 100644 --- a/tekton/task.yaml +++ b/tekton/task.yaml @@ -10,7 +10,7 @@ spec: description: The entire msg from falco steps: - name: pod-delete - image: quay.io/nissessenap/poddeleter@sha256:12ddc401785694bc2d5dd6d5b528d26ecaa3befe6099fa4331450e4b039d137c + image: quay.io/nissessenap/poddeleter@sha256:ae94ec2c9f005573e31e4944d1055a0dd92ee7594e7e7e36a4540a1811977270 env: - name: BODY value: $(params.falco-event)