├── .github └── workflows │ └── seccompagent.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── seccompagent │ └── seccompagent.go ├── deploy └── seccompagent.yaml ├── docs ├── CONTRIBUTING.md ├── _index.md ├── architecture.md ├── example-syscall-mount.png ├── examples │ └── pod.yaml ├── install.md ├── kubernetes-to-seccomp-agent.png ├── profiles │ ├── notify-dangerous.json │ └── notify-dangerous.yaml ├── seccomp-notify-kernel-synchronisation.png └── terraform │ ├── democluster.tf │ └── providers.tf ├── falco-plugin ├── .gitignore ├── Dockerfile ├── Dockerfile.dockerignore ├── Makefile ├── README.md ├── api │ ├── Makefile │ ├── seccomp-agent.pb.go │ ├── seccomp-agent.proto │ └── seccomp-agent_grpc.pb.go ├── falco-config-plugin-snippet.yaml ├── main.go ├── seccomp-profile-demo.json ├── seccomp_agent_rules.yaml └── server.go ├── go.mod ├── go.sum ├── pkg ├── agent │ └── agent.go ├── handlers │ ├── default.go │ ├── error.go │ ├── exec.go │ ├── falco │ │ └── falco.go │ ├── mkdir.go │ ├── mount.go │ └── prometheus │ │ └── promtheus.go ├── kuberesolver │ ├── k8s │ │ └── k8s.go │ └── kuberesolver.go ├── nsenter │ ├── README.md │ ├── nsenter.go │ ├── nsenter_gccgo.go │ ├── nsenter_unsupported.go │ ├── nsexec.c │ └── utils.go ├── readarg │ └── readarg.go ├── registry │ └── registry.go └── userns │ └── check.go └── tools └── image-tag /.github/workflows/seccompagent.yml: -------------------------------------------------------------------------------- 1 | name: Compile Kinvolk Seccomp Agent 2 | on: 3 | push: 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Check out code 13 | uses: actions/checkout@v1 14 | 15 | - name: Build container and publish to Registry 16 | id: publish-registry 17 | uses: elgohr/Publish-Docker-Github-Action@v5 18 | with: 19 | # name: quay.io/kinvolk/seccompagent 20 | name: ${{ secrets.CONTAINER_REPO }} 21 | username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} 22 | password: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} 23 | registry: quay.io 24 | workdir: . 25 | dockerfile: Dockerfile 26 | snapshot: true 27 | cache: ${{ github.event_name != 'schedule' }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /seccompagent 2 | *.swp 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1-alpine as builder 2 | RUN apk add alpine-sdk libseccomp libseccomp-dev 3 | 4 | RUN mkdir /build 5 | ADD . /build/ 6 | WORKDIR /build 7 | RUN go build -o seccompagent ./cmd/seccompagent 8 | 9 | FROM alpine:latest 10 | RUN apk add libseccomp 11 | COPY --from=builder /build/seccompagent /bin/seccompagent 12 | 13 | CMD ["/bin/seccompagent", "-resolver=kubernetes"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO := go 2 | GO_BUILD := go build 3 | 4 | IMAGE_TAG=$(shell ./tools/image-tag) 5 | IMAGE_BRANCH_TAG=$(shell ./tools/image-tag branch) 6 | CONTAINER_REPO ?= quay.io/kinvolk/seccompagent 7 | 8 | .PHONY: seccompagent 9 | seccompagent: 10 | $(GO_BUILD) -o seccompagent ./cmd/seccompagent 11 | 12 | .PHONY: container-build 13 | container-build: 14 | docker build -t $(CONTAINER_REPO):$(IMAGE_TAG) -f Dockerfile . 15 | docker tag $(CONTAINER_REPO):$(IMAGE_TAG) $(CONTAINER_REPO):$(IMAGE_BRANCH_TAG) 16 | 17 | .PHONY: container-push 18 | container-push: 19 | docker push $(CONTAINER_REPO):$(IMAGE_TAG) 20 | docker push $(CONTAINER_REPO):$(IMAGE_BRANCH_TAG) 21 | 22 | .PHONY: vendor 23 | vendor: 24 | $(GO) mod tidy 25 | $(GO) mod vendor 26 | $(GO) mod verify 27 | 28 | .PHONY: test 29 | test: 30 | go test -test.v ./... 31 | 32 | .PHONY: falco-plugin 33 | falco-plugin: 34 | DOCKER_BUILDKIT=1 docker build -f falco-plugin/Dockerfile --output=falco-plugin/ . 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kinvolk Seccomp Agent 2 | 3 | The Kinvolk Seccomp Agent is receiving seccomp file descriptors from container runtimes and handling system calls on behalf of the containers. 4 | Its goal is to support different use cases: 5 | - unprivileged container builds (procfs mounts with masked entries) 6 | - support of safe mknod (e.g. /dev/null) 7 | 8 | It is possible to write your own seccomp agent with a different behaviour by reusing the packages in the `pkg/` directory. 9 | The Kinvolk Seccomp Agent is only about 100 lines of code. It relies on different packages: 10 | - `pkg/agent`: listens on a unix socket to receive new seccomp file descriptors from the container runtime and associates a registry to them 11 | - `pkg/handlers`: basic implementations of system call handlers, such as mkdir, mount... 12 | - `pkg/kuberesolver`: allows users to assign a custom registry to the seccomp fd depending on the Kubernetes pod. 13 | - `pkg/nsenter`: allows handlers implementations to execute code in different namespaces 14 | - `pkg/readarg`: allows handlers implementations to dereference system call arguments. 15 | - `pkg/registry`: a set of system call handlers associated to a seccomp file descriptor. 16 | 17 | ## Basic demo 18 | 19 | * Run the Seccomp Agent with the "demo-basic" container resolver. 20 | ``` 21 | sudo ./seccompagent -resolver=demo-basic 22 | ``` 23 | 24 | Demo of mount in a container without `CAP_SYS_ADMIN`: 25 | ``` 26 | / # mount -t proc proc root 27 | / # ls /root/self/cmdline 28 | /root/self/cmdline 29 | ``` 30 | 31 | * Demo of overriding a `mkdir` path: 32 | ``` 33 | / # mkdir /abc 34 | / # ls -1d /ab* 35 | /abc-pid-4072889 36 | ``` 37 | 38 | * Demo of overriding a `chmod` error: 39 | ``` 40 | / # chmod 777 / 41 | chmod: /: Bad message 42 | ``` 43 | 44 | ## Demo on Kubernetes 45 | Before you install the demo on k8s, please ensure all [the requirements](./docs/install.md) are satisfied. 46 | 47 | This demo shows that the Seccomp Agent can have different behaviour depending on the Kubernetes pod (in this case, the pod's namespace and name). 48 | 49 | * Install a seccomp policy: `/var/lib/kubelet/seccomp/notify.json` 50 | ``` 51 | { 52 | "architectures" : [ 53 | "SCMP_ARCH_X86", 54 | "SCMP_ARCH_X32" 55 | ], 56 | "defaultAction" : "SCMP_ACT_ALLOW", 57 | "listenerPath": "/run/seccomp-agent.socket", 58 | "listenerMetadata": "MKDIR_TMPL=-{{.Namespace}}-{{.Pod}}-{{.Container}}\nEXEC_PATTERN=/bin/true\nEXEC_DURATION=2s\nMOUNT_PROC=true", 59 | "syscalls" : [ 60 | { 61 | "action" : "SCMP_ACT_NOTIFY", 62 | "names" : [ 63 | "openat", 64 | "open", 65 | "mkdir", 66 | "mount", 67 | "chmod" 68 | ] 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | * Deploy the seccomp agent: 75 | ``` 76 | kubectl apply -f deploy/seccompagent.yaml 77 | ``` 78 | 79 | * Deploy a pod with the seccomp policy: 80 | ``` 81 | apiVersion: v1 82 | kind: Pod 83 | metadata: 84 | name: mynotifypod 85 | # For older versions of Kubernetes (this annotation was deprecated in 86 | # Kubernetes v1.19 and completely removed in v1.27): 87 | annotations: 88 | seccomp.security.alpha.kubernetes.io/pod: localhost/notify.json 89 | spec: 90 | restartPolicy: Never 91 | securityContext: 92 | # /var/lib/kubelet/seccomp/notify.json 93 | seccompProfile: 94 | type: Localhost 95 | localhostProfile: notify.json 96 | containers: 97 | - name: container1 98 | image: busybox 99 | command: ["sh"] 100 | args: ["-c", "sleep infinity"] 101 | ``` 102 | 103 | * Run commands in the pod: 104 | ``` 105 | $ kubectl exec -it mynotifypod -- /bin/sh 106 | / # mkdir /abc 107 | / # ls -1d /abc* 108 | /abc-default-mynotifypod-TODO 109 | / # mount -t proc proc root 110 | / # mount|grep /root 111 | proc on /root type proc (rw,relatime) 112 | / # time -f %E /bin/echo -n "" 113 | 0m 0.00s 114 | / # time -f %E /bin/true 115 | 0m 2.00s 116 | ``` 117 | 118 | ## Combining with user namespaces 119 | 120 | By combining this with Kubernetes's user namespace support it is possible to 121 | allow a user within a user namespace to perform some operations which would 122 | otherwise be limited to host root. 123 | 124 | One example is mounting other filesystem types. This is most useful combined 125 | with user namespaces to allow mounting network file systems while a pod is 126 | running. This is far safer than giving the container `privileged` access but 127 | does expose more of the kernel to the pod, so you should consider your security 128 | carefully. 129 | 130 | There is a possibility a process could change its user namespace after making 131 | the mount system call, which could result in a confusing state. To fix this the 132 | seccomp notify policy should use the SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV 133 | flag, however this is [not yet available in 134 | runc](https://github.com/opencontainers/runc/issues/3860) and requires Linux >= 135 | 5.19. 136 | 137 | Configure a policy, similar to above, but with the following metadata: 138 | ```json 139 | { 140 | "architectures" : [ 141 | "SCMP_ARCH_X86", 142 | "SCMP_ARCH_X32" 143 | ], 144 | "defaultAction" : "SCMP_ACT_ALLOW", 145 | "listenerPath": "/run/seccomp-agent.socket", 146 | "listenerMetadata": "MOUNT_OTHER_FS_LIST=cifs\nMOUNT_NEED_CAP_ADMIN=true", 147 | "syscalls" : [ 148 | { 149 | "action" : "SCMP_ACT_NOTIFY", 150 | "names" : [ 151 | "mount" 152 | ] 153 | }, 154 | { 155 | "action" : "SCMP_ACT_ALLOW", 156 | "names" : [ 157 | "umount" 158 | ] 159 | } 160 | ] 161 | } 162 | ``` 163 | 164 | (Policy cut down for sake of example, recommended to use a full policy that 165 | additionally configures notify for mount and allows umount.) 166 | 167 | This has currently been successfully tested with cifs. Other filesystem types 168 | should work; NFS will need NFS client utilities installing within the container 169 | *and* on the host (e.g. to make upcalls work). 170 | 171 | * Deploy a pod with the seccomp policy and user namespaces: 172 | ```yaml 173 | apiVersion: v1 174 | kind: Pod 175 | metadata: 176 | name: mynotifypod-userns 177 | spec: 178 | restartPolicy: Never 179 | # Needs "UserNamespacesSupport" feature gate currently 180 | hostUsers: false 181 | securityContext: 182 | # /var/lib/kubelet/seccomp/notify.json 183 | seccompProfile: 184 | type: Localhost 185 | localhostProfile: notify.json 186 | containers: 187 | - name: container1 188 | image: alpine 189 | command: ["sh"] 190 | args: ["-c", "sleep infinity"] 191 | securityContext: 192 | capabilities: 193 | # This is safe combined with hostUsers: false 194 | add: [SYS_ADMIN] 195 | ``` 196 | 197 | * Run commands in the pod: 198 | ```shell 199 | $ kubectl exec -it mynotifypod-userns -- /bin/sh 200 | / # mkdir /mnt 201 | / # mount -t cifs -o username=user,password=pass '//10.0.0.1/C' /mnt 202 | / # df -h /mnt 203 | /mnt # df -h /mnt 204 | Filesystem Size Used Available Use% Mounted on 205 | //10.0.0.1/C 95.4G 85.3G 10.1G 89% /mnt 206 | / # ls /mnt 207 | $Recycle.Bin Documents and Settings Program files 208 | [...] 209 | / # sed -i 's!^\(nobody.*/\)false!\1sh!' /etc/passwd 210 | / # su nobody 211 | / $ mount -t cifs -o username=user,password=pass '//10.0.0.1/C' /mnt 212 | mount: permission denied (are you root?) 213 | ``` 214 | -------------------------------------------------------------------------------- /cmd/seccompagent/seccompagent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build linux && cgo 16 | // +build linux,cgo 17 | 18 | package main 19 | 20 | import ( 21 | "errors" 22 | "flag" 23 | "fmt" 24 | "net/http" 25 | "os" 26 | "strings" 27 | "text/template" 28 | "time" 29 | 30 | "github.com/kinvolk/seccompagent/pkg/agent" 31 | "github.com/kinvolk/seccompagent/pkg/handlers" 32 | "github.com/kinvolk/seccompagent/pkg/handlers/falco" 33 | prometheus_handler "github.com/kinvolk/seccompagent/pkg/handlers/prometheus" 34 | "github.com/kinvolk/seccompagent/pkg/kuberesolver" 35 | "github.com/kinvolk/seccompagent/pkg/nsenter" 36 | "github.com/kinvolk/seccompagent/pkg/registry" 37 | 38 | "github.com/opencontainers/runtime-spec/specs-go" 39 | "github.com/prometheus/client_golang/prometheus" 40 | "github.com/prometheus/client_golang/prometheus/promhttp" 41 | log "github.com/sirupsen/logrus" 42 | "golang.org/x/sys/unix" 43 | ) 44 | 45 | var ( 46 | socketFile string 47 | resolverParam string 48 | logflags string 49 | metricsBindAddress string 50 | ) 51 | 52 | func init() { 53 | flag.StringVar(&socketFile, "socketfile", "/run/seccomp-agent.socket", "Socket file") 54 | flag.StringVar(&resolverParam, "resolver", "", "Container resolver to use [none, demo-basic, kubernetes]") 55 | flag.StringVar(&logflags, "log", "info", "log level [trace,debug,info,warn,error,fatal,color,nocolor,json]") 56 | flag.StringVar(&metricsBindAddress, "metrics-bind-address", "", "[host]:port to listen on for monitoring, empty means no monitoring port") 57 | } 58 | 59 | func main() { 60 | nsenter.Init() 61 | 62 | flag.Parse() 63 | for _, v := range strings.Split(logflags, ",") { 64 | if v == "json" { 65 | log.SetFormatter(&log.JSONFormatter{}) 66 | } else if v == "color" { 67 | log.SetFormatter(&log.TextFormatter{ForceColors: true}) 68 | } else if v == "nocolor" { 69 | log.SetFormatter(&log.TextFormatter{DisableColors: true}) 70 | } else if lvl, err := log.ParseLevel(v); err == nil { 71 | log.SetLevel(lvl) 72 | } else { 73 | fmt.Fprintf(os.Stderr, "Invalid log level: %s\n", err.Error()) 74 | flag.Usage() 75 | os.Exit(1) 76 | } 77 | } 78 | if flag.NArg() > 0 { 79 | flag.PrintDefaults() 80 | panic(errors.New("invalid command")) 81 | } 82 | 83 | if metricsBindAddress != "" { 84 | reg := prometheus.DefaultRegisterer 85 | reg.MustRegister(prometheus.NewBuildInfoCollector()) 86 | http.Handle("/metrics", promhttp.Handler()) 87 | 88 | go func() { 89 | err := http.ListenAndServe(metricsBindAddress, nil) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | }() 94 | } 95 | 96 | var resolver registry.ResolverFunc 97 | 98 | switch resolverParam { 99 | case "none", "": 100 | resolver = nil 101 | case "falco": 102 | resolver = func(state *specs.ContainerProcessState) *registry.Registry { 103 | r := registry.New() 104 | podCtx := &kuberesolver.PodContext{ 105 | Pid: state.State.Pid, 106 | Pid1: state.Pid, 107 | } 108 | r.MiddlewareHandlers = append(r.MiddlewareHandlers, falco.NotifyFalco(podCtx)) 109 | return r 110 | } 111 | case "demo-basic": 112 | // Using the resolver allows to implement different behaviour 113 | // depending on the container. For example, you could connect to the 114 | // Kubernetes API, find the pod, and allow or deny a syscall depending 115 | // on the pod specifications (e.g. namespace, annotations, 116 | // serviceAccount). 117 | resolver = func(state *specs.ContainerProcessState) *registry.Registry { 118 | r := registry.New() 119 | 120 | // Example: 121 | // / # mount -t proc proc root 122 | // / # ls /root/self/cmdline 123 | // /root/self/cmdline 124 | allowedFilesystems := map[string]struct{}{"proc": struct{}{}} 125 | r.SyscallHandler["mount"] = handlers.Mount(allowedFilesystems, false /* do not check capabilities */) 126 | 127 | // Example: 128 | // # chmod 777 / 129 | // chmod: /: Bad message 130 | r.SyscallHandler["chmod"] = handlers.Error(unix.EBADMSG) 131 | 132 | // Example: 133 | // # mkdir /abc 134 | // # ls -d /abc* 135 | // /abc-pid-3528098 136 | if state != nil { 137 | r.SyscallHandler["mkdir"] = handlers.MkdirWithSuffix(fmt.Sprintf("-pid-%d", state.State.Pid)) 138 | } 139 | 140 | return r 141 | } 142 | case "kubernetes": 143 | kubeResolverFunc := func(podCtx *kuberesolver.PodContext, metadata map[string]string) *registry.Registry { 144 | log.WithFields(log.Fields{ 145 | "pod": podCtx, 146 | "metadata": metadata, 147 | }).Debug("New container") 148 | 149 | r := registry.New() 150 | 151 | if v, ok := metadata["MIDDLEWARE"]; ok { 152 | for _, middleware := range strings.Split(v, ",") { 153 | switch middleware { 154 | case "falco": 155 | r.MiddlewareHandlers = append(r.MiddlewareHandlers, falco.NotifyFalco(podCtx)) 156 | case "prometheus": 157 | r.MiddlewareHandlers = append(r.MiddlewareHandlers, prometheus_handler.UpdateMetrics(podCtx)) 158 | default: 159 | log.WithFields(log.Fields{ 160 | "pod": podCtx, 161 | "middleware": middleware, 162 | }).Error("Invalid middleware") 163 | } 164 | } 165 | } 166 | 167 | if v, ok := metadata["DEFAULT_ACTION"]; ok { 168 | switch v { 169 | // DEFAULT_ACTION=kill-container differs from SCMP_ACT_KILL_PROCESS that 170 | // it kills the pid1 of the container, terminating all processes of the 171 | // container instead of just the process making the syscall. 172 | case "kill-container": 173 | r.DefaultHandler = handlers.KillContainer(podCtx.Pid1) 174 | case "freeze-container": 175 | r.DefaultHandler = handlers.FreezeContainer(podCtx.Pid1) 176 | default: 177 | log.WithFields(log.Fields{ 178 | "pod": podCtx, 179 | "default-action": v, 180 | }).Error("Invalid default action") 181 | } 182 | } 183 | 184 | if v, ok := metadata["MKDIR_TMPL"]; ok { 185 | tmpl, err := template.New("mkdirTmpl").Parse(v) 186 | if err == nil { 187 | var suffix strings.Builder 188 | err = tmpl.Execute(&suffix, podCtx) 189 | if err == nil { 190 | r.SyscallHandler["mkdir"] = handlers.MkdirWithSuffix(suffix.String()) 191 | } 192 | } 193 | } 194 | 195 | if fileName, ok := metadata["EXEC_PATTERN"]; ok { 196 | d, ok := metadata["EXEC_DURATION"] 197 | if ok { 198 | duration, _ := time.ParseDuration(d) 199 | r.SyscallHandler["execve"] = handlers.ExecCondition(fileName, duration) 200 | } 201 | } 202 | 203 | if sidecars, ok := metadata["SIDECARS"]; ok { 204 | d, ok := metadata["SIDECARS_DELAY"] 205 | if ok { 206 | duration, _ := time.ParseDuration(d) 207 | r.SyscallHandler["execve"] = handlers.ExecSidecars(podCtx, sidecars, duration) 208 | } 209 | } 210 | 211 | allowedFilesystems := map[string]struct{}{} 212 | if v, ok := metadata["MOUNT_PROC"]; ok && v == "true" { 213 | allowedFilesystems["proc"] = struct{}{} 214 | } 215 | if v, ok := metadata["MOUNT_SYSFS"]; ok && v == "true" { 216 | allowedFilesystems["sysfs"] = struct{}{} 217 | } 218 | if v, ok := metadata["MOUNT_OTHER_FS_LIST"]; ok { 219 | for _, fs := range strings.Split(v, ",") { 220 | allowedFilesystems[fs] = struct{}{} 221 | } 222 | } 223 | 224 | requireCapsForMount := false 225 | if v, ok := metadata["MOUNT_NEED_CAP_ADMIN"]; ok && v == "true" { 226 | requireCapsForMount = true 227 | } 228 | 229 | if len(allowedFilesystems) > 0 { 230 | r.SyscallHandler["mount"] = handlers.Mount(allowedFilesystems, requireCapsForMount) 231 | } 232 | return r 233 | } 234 | var err error 235 | resolver, err = kuberesolver.KubeResolver(kubeResolverFunc) 236 | if err != nil { 237 | panic(err) 238 | } 239 | default: 240 | panic(errors.New("invalid container resolver")) 241 | } 242 | 243 | err := agent.StartAgent(socketFile, resolver) 244 | if err != nil { 245 | panic(err) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /deploy/seccompagent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: seccomp-agent 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: seccomp-agent 10 | namespace: seccomp-agent 11 | --- 12 | kind: ClusterRoleBinding 13 | apiVersion: rbac.authorization.k8s.io/v1 14 | metadata: 15 | name: seccomp-agent 16 | subjects: 17 | - kind: ServiceAccount 18 | name: seccomp-agent 19 | namespace: seccomp-agent 20 | roleRef: 21 | kind: ClusterRole 22 | name: view 23 | apiGroup: rbac.authorization.k8s.io 24 | --- 25 | apiVersion: apps/v1 26 | kind: DaemonSet 27 | metadata: 28 | name: seccomp-agent 29 | namespace: seccomp-agent 30 | labels: 31 | k8s-app: seccomp-agent 32 | spec: 33 | selector: 34 | matchLabels: 35 | k8s-app: seccomp-agent 36 | template: 37 | metadata: 38 | labels: 39 | k8s-app: seccomp-agent 40 | spec: 41 | serviceAccount: seccomp-agent 42 | hostPID: true 43 | containers: 44 | - name: seccomp-agent 45 | image: quay.io/kinvolk/seccompagent:latest 46 | command: [ "/bin/seccompagent", "-resolver=kubernetes", "-log=trace" ] 47 | imagePullPolicy: Always 48 | env: 49 | - name: NODE_NAME 50 | valueFrom: 51 | fieldRef: 52 | fieldPath: spec.nodeName 53 | securityContext: 54 | # We need to keep 'privileged' to opt out of cgroupns, so we can 55 | # inspect containers' cgroups via their /proc/$pid/cgroup files. 56 | privileged: true 57 | capabilities: 58 | add: 59 | # CAP_SYS_PTRACE is useful to read arguments of a processes with 60 | # the prctl PR_SET_DUMPABLE bit set to zero. 61 | - "SYS_PTRACE" 62 | # CAP_SYS_ADMIN is useful to setns in containers and mount 63 | - "SYS_ADMIN" 64 | - "NET_ADMIN" 65 | # privileged: true 66 | volumeMounts: 67 | - name: seccomp-policies 68 | mountPath: /host/seccomp 69 | - name: run 70 | mountPath: /run 71 | # the freezer handler needs write access to the cgroup filesystems to 72 | # write "FROZEN" on "freezer.state" interface file. 73 | - name: cgroup 74 | mountPath: /sys/fs/cgroup 75 | tolerations: 76 | - effect: NoSchedule 77 | operator: Exists 78 | - effect: NoExecute 79 | operator: Exists 80 | volumes: 81 | - name: seccomp-policies 82 | hostPath: 83 | path: /var/lib/kubelet/seccomp 84 | - name: run 85 | hostPath: 86 | path: /run 87 | - name: cgroup 88 | hostPath: 89 | path: /sys/fs/cgroup 90 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contribution Guidelines 3 | weight: 40 4 | --- 5 | 6 | ## Code of Conduct 7 | 8 | Please refer to the Kinvolk [Code of Conduct](https://github.com/kinvolk/contribution/blob/master/CODE_OF_CONDUCT.md). 9 | 10 | ## Setup developer environment 11 | 12 | ```bash 13 | git clone git@github.com:kinvolk/seccompagent.git 14 | cd seccompagent 15 | ``` 16 | 17 | ## Build the code 18 | 19 | ```bash 20 | make 21 | ``` 22 | 23 | ## Build with container image 24 | 25 | ```bash 26 | make container-image 27 | ``` 28 | 29 | ## Authoring PRs 30 | 31 | For the general guidelines on making PRs/commits easier to review, please check out 32 | Kinvolk's 33 | [contribution guidelines on git](https://github.com/kinvolk/contribution/tree/master/topics/git.md). 34 | 35 | ## Updating dependencies 36 | 37 | In order to update dependencies managed with Go modules, run `make vendor`, 38 | which will ensure that all steps needed for an update are taken (tidy and vendoring). 39 | 40 | ## Testing and linting requirements 41 | 42 | ```bash 43 | make test 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | content_type: seccomp-agent 3 | title: Seccomp Agent 4 | linktitle: Seccomp Agent 5 | main_menu: true 6 | weight: 45 7 | --- 8 | 9 | The Kinvolk Seccomp Agent extends the Kubernetes seccomp policies with the Seccomp Notify features in Linux and runc. 10 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture 3 | weight: 20 4 | description: > 5 | Architecture of Seccomp Agent. 6 | --- 7 | 8 | 9 | ## From Kubernetes to Seccomp Agent 10 | 11 | In this example, we use containerd but this can be with other container runtimes. 12 | 13 | ![From Kubernetes to Seccomp Agent](kubernetes-to-seccomp-agent.png "From Kubernetes to Seccomp Agent") 14 | 15 | Steps: 16 | - A user creates a pod with `kubectl apply -f pod.yaml`. 17 | The file `pod.yaml` specifies the seccomp profile `foo.json` as follows: 18 | ``` 19 | apiVersion: v1 20 | kind: Pod 21 | metadata: 22 | name: mypod 23 | spec: 24 | securityContext: 25 | seccompProfile: 26 | type: Localhost 27 | localhostProfile: foo.json 28 | ``` 29 | - Once the pod is scheduled on a node, a Kubelet on a node receives the pod 30 | specification. 31 | - The Kubelet calls the method 32 | [StartContainer](https://github.com/kubernetes/cri-api/blob/v0.22.0-alpha.2/pkg/apis/runtime/v1/api.proto#L65) 33 | on containerd using the 34 | [Container Runtime Interface (CRI)](https://github.com/kubernetes/cri-api). 35 | The method includes the field `SeccompProfilePath` with the value 36 | `/var/lib/kubelet/seccomp/foo.json`. 37 | - Containerd reads the file `/var/lib/kubelet/seccomp/foo.json`: 38 | ``` 39 | { 40 | "listenerPath": "/run/seccomp-agent.socket", 41 | "defaultAction": "SCMP_ACT_ALLOW", 42 | "syscalls": [ 43 | { 44 | "names": ["mount"], 45 | "action": "SCMP_ACT_NOTIFY" 46 | } 47 | } 48 | ``` 49 | - Containerd prepares config.json that includes the file foo.json from above: 50 | ``` 51 | { 52 | "linux": { 53 | "seccomp": { 54 | "listenerPath": "/run/seccomp-agent.socket", 55 | "defaultAction": "SCMP_ACT_ALLOW", 56 | "syscalls": [ 57 | { 58 | "names": ["mount"], 59 | "action": "SCMP_ACT_NOTIFY" 60 | } 61 | ``` 62 | - Containerd starts runc with `config.json`. 63 | - Runc gets the seccomp file descriptor by calling the seccomp() system call 64 | via libseccomp-golang. 65 | - Runc sends the seccomp file descriptor to the seccomp agent, along with 66 | following 67 | [Container Process State](https://github.com/opencontainers/runtime-spec/blob/1c3f411f041711bbeecf35ff7e93461ea6789220/config-linux.md#containerprocessstate): 68 | ``` 69 | { 70 | "ociVersion": "0.2.0", 71 | "fds": ["seccompFd"], 72 | "pid": 4422, 73 | "metadata": "", 74 | "state": { 75 | "ociVersion": "0.2.0", 76 | "id": "8eea9f6d...", 77 | "status": "creating", 78 | "pid": 4422, 79 | "bundle": "rootfs", 80 | "annotations": {} 81 | } 82 | } 83 | ``` 84 | - At this point, the Seccomp Agent has the seccomp file descriptor and the 85 | Container Process State contains the following 86 | [annotations](https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/annotations/annotations.go) 87 | to identify the pod. Additional information could be queried to the 88 | Kubernetes API. 89 | - Pod namespace: `io.kubernetes.cri.sandbox-namespace` 90 | - Pod name: `io.kubernetes.cri.sandbox-name` 91 | - Container name: `io.kubernetes.cri.container-name` 92 | 93 | ## Example of system call performed on behalf of the process 94 | 95 | In this example, we use the mount system call because that was useful for the 96 | unprivileged builds use case. But the mechanism is the same for other use 97 | cases. 98 | 99 | ![Mount syscall with Seccomp Notify](example-syscall-mount.png "Mount syscall with Seccomp Notify") 100 | 101 | Steps: 102 | - A process executes the mount system call: 103 | ``` 104 | mount("proc", "/proc", "proc", 0, NULL) 105 | ``` 106 | - The seccomp subssytem in the kernel executes the BPF program for the 107 | seccomp filter. 108 | The BPF program receives a `struct seccomp_data` as argument, containing: 109 | ``` 110 | struct seccomp_data { 111 | int nr; // syscall mount = 165 112 | u32 arch; // x86_64 113 | u64 instruction_pointer; 114 | u64 args[6]; // syscall args 115 | } 116 | ``` 117 | - The BPF program returns `SCMP_ACT_NOTIFY`, meaning the decision is deferred 118 | to a seccomp agent. 119 | - The seccomp agent receives a request from seccomp by running the 120 | `SECCOMP_IOCTL_NOTIF_RECV` ioctl on the seccomp file descriptor. The request 121 | is a `struct seccomp_notif` with the following content: 122 | ``` 123 | struct seccomp_notif { 124 | __u64 id; 125 | __u32 pid; 126 | __u32 flags; 127 | struct seccomp_data data; 128 | }; 129 | ``` 130 | - The seccomp agent reads the process' memory to get the arguments of the 131 | system call. 132 | - The seccomp agent decides to perform the request mount on behalf of the 133 | process. It enters the mount namespace of the process and calls `mount`. 134 | - The seccomp agent gives a response to the seccomp subsystem by running the 135 | `SECCOMP_IOCTL_NOTIF_SEND` ioctl on the seccomp file descriptor. The response 136 | is a `struct seccomp_notif_resp` with the following content: 137 | ``` 138 | struct seccomp_notif_resp { 139 | __u64 id; 140 | __s64 val; // returns 0 141 | __s32 error; // errno 0 142 | __u32 flags; 143 | }; 144 | ``` 145 | - The seccomp subsystem completes the mount system call from the process by 146 | returning 0 (success) as instructed by the seccomp agent. 147 | 148 | ## Details on kernel synchronisation 149 | 150 | ![Kernel synchronisation](seccomp-notify-kernel-synchronisation.png "Kernel synchronisation") 151 | -------------------------------------------------------------------------------- /docs/example-syscall-mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinvolk/seccompagent/f15b9f96a5060fb9be27ff2e07bc65f00be0909a/docs/example-syscall-mount.png -------------------------------------------------------------------------------- /docs/examples/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: mypod 5 | spec: 6 | securityContext: 7 | seccompProfile: 8 | type: Localhost 9 | # When using SPO 10 | localhostProfile: operator/default/notify-dangerous.json 11 | # When installing the policy manually in /var/lib/kubelet/seccomp/notify-dangerous.json 12 | #localhostProfile: notify-dangerous.json 13 | restartPolicy: Never 14 | containers: 15 | - name: container1 16 | image: busybox 17 | command: ["sh"] 18 | args: ["-c", "sleep infinity"] 19 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | weight: 10 4 | description: > 5 | How to install. 6 | --- 7 | 8 | Seccomp Agent is a DaemonSet deployed in the cluster and relies on new features in runc. 9 | 10 | ## Installing Seccomp Agent 11 | 12 | System Requirements: 13 | - Linux kernel >= 5.9 14 | - Libseccomp >= 2.5.2 (>=2.5.2 recommended) 15 | - Runc >= 1.1.0 16 | - Docker from git(needs to include [this PR](https://github.com/moby/moby/pull/42604)) 17 | - Or if you are using containerd instead of docker, containerd >=1.5.5(>=1.6.0-rc.1 recommended) 18 | 19 | Recommended: 20 | - Flatcar Container Linux >= 3127.0.0 21 | - containerd >= 1.6.0-rc1 22 | - Security Profiles Operator (SPO) >= v0.4.1 (unreleased) or from git main 23 | 24 | To ensure you have installed correct version of container runtime that support seccomp notify, 25 | use the command below: 26 | ``` 27 | strings $(which dockerd) | grep listenerPath 28 | ``` 29 | or if you are using containerd as your runtime 30 | ``` 31 | strings $(which containerd) | grep listenerPath 32 | ``` 33 | If the output is empty, it means your container runtime haven't enabled the feature of seccomp notify. 34 | Please check the requirements again in case you missed one. 35 | ### With Typhoon on Azure 36 | 37 | In the `docs/terraform` directory, you can find terraform files to start a 38 | Kubernetes cluster with the required dependencies. 39 | 40 | Please see the [Azure tutorial](https://typhoon.psdn.io/flatcar-linux/azure/) 41 | from the [Typhoon](https://github.com/poseidon/typhoon) documentation. 42 | 43 | ### Deploy the Seccomp Agent DaemonSet 44 | 45 | ``` 46 | kubectl apply -f deploy/seccompagent.yaml 47 | ``` 48 | 49 | ### Deploy a pod with a Seccomp Profile 50 | 51 | If you use the [Security Profiles Operator 52 | (SPO)](https://github.com/kubernetes-sigs/security-profiles-operator), you can 53 | deploy a Seccomp Profile with kubectl: 54 | 55 | ``` 56 | kubectl apply -f docs/profiles/notify-dangerous.yaml 57 | ``` 58 | 59 | Otherwise, you can install `docs/profiles/notify-dangerous.json` on the worker 60 | nodes manually, in the `/var/lib/kubelet/seccomp/` directory. 61 | 62 | 63 | Start a new pod: 64 | 65 | ``` 66 | kubectl apply -f docs/examples/pod.yaml 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/kubernetes-to-seccomp-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinvolk/seccompagent/f15b9f96a5060fb9be27ff2e07bc65f00be0909a/docs/kubernetes-to-seccomp-agent.png -------------------------------------------------------------------------------- /docs/profiles/notify-dangerous.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAction": "SCMP_ACT_ALLOW", 3 | "architectures": [ 4 | "SCMP_ARCH_X86_64" 5 | ], 6 | "listenerPath": "/run/seccomp-agent.socket", 7 | "listenerMetadata": "DEFAULT_ACTION=kill-container\nMIDDLEWARE=falco", 8 | "syscalls": [ 9 | { 10 | "action": "SCMP_ACT_NOTIFY", 11 | "names": [ 12 | "acct", 13 | "add_key", 14 | "bpf", 15 | "clock_adjtime", 16 | "clock_settime", 17 | "create_module", 18 | "delete_module", 19 | "finit_module", 20 | "get_kernel_syms", 21 | "get_mempolicy", 22 | "init_module", 23 | "ioperm", 24 | "iopl", 25 | "kcmp", 26 | "kexec_file_load", 27 | "kexec_load", 28 | "keyctl", 29 | "lookup_dcookie", 30 | "mbind", 31 | "mount", 32 | "move_pages", 33 | "name_to_handle_at", 34 | "nfsservctl", 35 | "open_by_handle_at", 36 | "perf_event_open", 37 | "personality", 38 | "pivot_root", 39 | "process_vm_readv", 40 | "process_vm_writev", 41 | "ptrace", 42 | "query_module", 43 | "quotactl", 44 | "reboot", 45 | "request_key", 46 | "set_mempolicy", 47 | "setns", 48 | "settimeofday", 49 | "stime", 50 | "swapoff", 51 | "swapon", 52 | "_sysctl", 53 | "sysfs", 54 | "umount2", 55 | "umount", 56 | "unshare", 57 | "uselib", 58 | "userfaultfd", 59 | "ustat", 60 | "vm86old", 61 | "vm86" 62 | ] 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /docs/profiles/notify-dangerous.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: security-profiles-operator.x-k8s.io/v1beta1 2 | kind: SeccompProfile 3 | metadata: 4 | name: notify-dangerous 5 | annotations: 6 | description: "Allow most syscalls except dangerous ones where it uses notify." 7 | spec: 8 | defaultAction: SCMP_ACT_ALLOW 9 | architectures: 10 | - SCMP_ARCH_X86_64 11 | listenerPath: "/run/seccomp-agent.socket" 12 | #listenerMetadata: "DEFAULT_ACTION=kill-container" 13 | #listenerMetadata: "DEFAULT_ACTION=freeze-container\nMIDDLEWARE=falco" 14 | listenerMetadata: "DEFAULT_ACTION=kill-container\nMIDDLEWARE=falco" 15 | 16 | syscalls: 17 | 18 | - action: SCMP_ACT_NOTIFY 19 | names: 20 | - acct 21 | - add_key 22 | - bpf 23 | - clock_adjtime 24 | - clock_settime 25 | - create_module 26 | - delete_module 27 | - finit_module 28 | - get_kernel_syms 29 | - get_mempolicy 30 | - init_module 31 | - ioperm 32 | - iopl 33 | - kcmp 34 | - kexec_file_load 35 | - kexec_load 36 | - keyctl 37 | - lookup_dcookie 38 | - mbind 39 | - mount 40 | - move_pages 41 | - name_to_handle_at 42 | - nfsservctl 43 | - open_by_handle_at 44 | - perf_event_open 45 | - personality 46 | - pivot_root 47 | - process_vm_readv 48 | - process_vm_writev 49 | - ptrace 50 | - query_module 51 | - quotactl 52 | - reboot 53 | - request_key 54 | - set_mempolicy 55 | - setns 56 | - settimeofday 57 | - stime 58 | - swapoff 59 | - swapon 60 | - _sysctl 61 | - sysfs 62 | - umount2 63 | - umount 64 | - unshare 65 | - uselib 66 | - userfaultfd 67 | - ustat 68 | - vm86old 69 | - vm86 70 | 71 | -------------------------------------------------------------------------------- /docs/seccomp-notify-kernel-synchronisation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinvolk/seccompagent/f15b9f96a5060fb9be27ff2e07bc65f00be0909a/docs/seccomp-notify-kernel-synchronisation.png -------------------------------------------------------------------------------- /docs/terraform/democluster.tf: -------------------------------------------------------------------------------- 1 | module "democluster" { 2 | source = "git::https://github.com/poseidon/typhoon//azure/flatcar-linux/kubernetes?ref=v1.23.3" 3 | 4 | # Azure 5 | cluster_name = "democluster" 6 | region = "westeurope" 7 | dns_zone = "example.com" 8 | dns_zone_group = "my-resource-group" 9 | 10 | # configuration 11 | ssh_authorized_key = "ssh-rsa ..." 12 | 13 | os_image = "flatcar-alpha" 14 | # calico currently does not work: https://github.com/projectcalico/calico/issues/5011 15 | networking = "cilium" 16 | # worker_type's default Standard_DS1_v2 does not have enough CPU for SPO 17 | worker_type = "Standard_DS2_v2" 18 | worker_count = 2 19 | host_cidr = "10.0.0.0/20" 20 | 21 | } 22 | 23 | resource "local_file" "kubeconfig-democluster" { 24 | content = module.democluster.kubeconfig-admin 25 | filename = "/home/user/.kube/democluster-config" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /docs/terraform/providers.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } 4 | 5 | provider "ct" {} 6 | 7 | terraform { 8 | required_providers { 9 | ct = { 10 | source = "poseidon/ct" 11 | version = "0.9.1" 12 | } 13 | azurerm = { 14 | source = "hashicorp/azurerm" 15 | version = "2.92.0" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /falco-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | -------------------------------------------------------------------------------- /falco-plugin/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the same base image as falco to ensure compatibility with glibc version 2 | FROM golang:buster as builder 3 | 4 | # Cache go modules so they won't be downloaded at each build 5 | COPY go.mod go.sum /src/ 6 | RUN cd /src && go mod download 7 | 8 | COPY ./ /src 9 | RUN cd /src && make -C falco-plugin 10 | 11 | FROM falcosecurity/falco-no-driver:0.35.1 12 | 13 | SHELL ["/bin/bash", "-c"] 14 | 15 | COPY falco-plugin/falco-config-plugin-snippet.yaml /etc/falco/ 16 | COPY falco-plugin/seccomp_agent_rules.yaml /etc/falco/rules.d/ 17 | 18 | RUN \ 19 | SNIPPET="$(jq -Rs . < /etc/falco/falco-config-plugin-snippet.yaml)" && \ 20 | SNIPPET="${SNIPPET:1}" && \ 21 | SNIPPET="${SNIPPET/%?/}" && \ 22 | sed -i \ 23 | -e '/^plugins:$/a \'"$SNIPPET" \ 24 | -e 's/^load_plugins:.*$/load_plugins: [seccompagent]/' \ 25 | /etc/falco/falco.yaml && \ 26 | rm -f /etc/falco/falco-config-plugin-snippet.yaml 27 | COPY --from=builder /src/falco-plugin/*.so /usr/share/falco/plugins/ 28 | 29 | -------------------------------------------------------------------------------- /falco-plugin/Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /falco-plugin/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2022 The Falco Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | # the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | # specific language governing permissions and limitations under the License. 12 | # 13 | 14 | SHELL=/bin/bash -o pipefail 15 | GO ?= go 16 | 17 | NAME := seccompagent 18 | OUTPUT := lib$(NAME).so 19 | 20 | ifeq ($(DEBUG), 1) 21 | GODEBUGFLAGS= GODEBUG=cgocheck=2 22 | else 23 | GODEBUGFLAGS= GODEBUG=cgocheck=0 24 | endif 25 | 26 | all: $(OUTPUT) 27 | 28 | clean: 29 | @rm -f *.so 30 | 31 | $(OUTPUT): *.go clean 32 | @$(GODEBUGFLAGS) $(GO) build -buildmode=c-shared -o $(OUTPUT) 33 | -------------------------------------------------------------------------------- /falco-plugin/README.md: -------------------------------------------------------------------------------- 1 | # seccompagent Falco plugin 2 | 3 | ## Build the plugin standalone 4 | 5 | ``` 6 | make -C falco-plugin 7 | ls -l falco-plugin/libseccompagent.so 8 | ``` 9 | 10 | ## Build a Falco container image with the plugin 11 | 12 | ``` 13 | export CONTAINER_REPO=${USER}test.azurecr.io/falco-with-seccompagent 14 | export TAG=$CONTAINER_REPO:dev 15 | docker build -f falco-plugin/Dockerfile -t $TAG . 16 | docker push $TAG 17 | ``` 18 | 19 | ## Run Falco with the plugin 20 | 21 | Start Falco in a container as previously compiled: 22 | ``` 23 | docker run --rm -i -t \ 24 | --privileged \ 25 | -v /var/run/docker.sock:/host/var/run/docker.sock \ 26 | -v /run/seccomp-agent-falco-plugin:/run/seccomp-agent-falco-plugin \ 27 | -v /proc:/host/proc:ro \ 28 | $TAG falco --modern-bpf 29 | ``` 30 | 31 | Start the Seccomp Agent: 32 | ``` 33 | sudo ./seccompagent -resolver=falco -log trace 34 | ``` 35 | 36 | Launch a container and run a command: 37 | ``` 38 | $ docker run --rm -it \ 39 | --security-opt \ 40 | seccomp=falco-plugin/seccomp-profile-demo.json \ 41 | busybox 42 | / # mkdir /a 43 | ``` 44 | 45 | Falco logs the following: 46 | ``` 47 | Notice The seccomp agent detected a mkdir... 48 | ``` 49 | -------------------------------------------------------------------------------- /falco-plugin/api/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: generated-files 2 | generated-files: seccomp-agent.pb.go 3 | 4 | seccomp-agent.pb.go: seccomp-agent.proto 5 | protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative seccomp-agent.proto 6 | 7 | clean: 8 | rm -f seccomp-agent.pb.go seccomp-agent_grpc.pb.go 9 | -------------------------------------------------------------------------------- /falco-plugin/api/seccomp-agent.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Seccomp Agent authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.27.1 18 | // protoc v3.17.3 19 | // source: seccomp-agent.proto 20 | 21 | package seccompagent 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | reflect "reflect" 27 | sync "sync" 28 | ) 29 | 30 | const ( 31 | // Verify that this generated code is sufficiently up-to-date. 32 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 33 | // Verify that runtime/protoimpl is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 35 | ) 36 | 37 | type PublishEventRequest struct { 38 | state protoimpl.MessageState 39 | sizeCache protoimpl.SizeCache 40 | unknownFields protoimpl.UnknownFields 41 | 42 | // id is the cookie passed by the kernel in struct seccomp_notif 43 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 44 | // pid refers to the process that made the syscall 45 | Pid uint64 `protobuf:"varint,2,opt,name=pid,proto3" json:"pid,omitempty"` 46 | // syscall is the name of the syscall 47 | Syscall string `protobuf:"bytes,3,opt,name=syscall,proto3" json:"syscall,omitempty"` 48 | // KubernetesWorkload 49 | K8S *KubernetesWorkload `protobuf:"bytes,4,opt,name=k8s,proto3" json:"k8s,omitempty"` 50 | } 51 | 52 | func (x *PublishEventRequest) Reset() { 53 | *x = PublishEventRequest{} 54 | if protoimpl.UnsafeEnabled { 55 | mi := &file_seccomp_agent_proto_msgTypes[0] 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | ms.StoreMessageInfo(mi) 58 | } 59 | } 60 | 61 | func (x *PublishEventRequest) String() string { 62 | return protoimpl.X.MessageStringOf(x) 63 | } 64 | 65 | func (*PublishEventRequest) ProtoMessage() {} 66 | 67 | func (x *PublishEventRequest) ProtoReflect() protoreflect.Message { 68 | mi := &file_seccomp_agent_proto_msgTypes[0] 69 | if protoimpl.UnsafeEnabled && x != nil { 70 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 71 | if ms.LoadMessageInfo() == nil { 72 | ms.StoreMessageInfo(mi) 73 | } 74 | return ms 75 | } 76 | return mi.MessageOf(x) 77 | } 78 | 79 | // Deprecated: Use PublishEventRequest.ProtoReflect.Descriptor instead. 80 | func (*PublishEventRequest) Descriptor() ([]byte, []int) { 81 | return file_seccomp_agent_proto_rawDescGZIP(), []int{0} 82 | } 83 | 84 | func (x *PublishEventRequest) GetId() uint64 { 85 | if x != nil { 86 | return x.Id 87 | } 88 | return 0 89 | } 90 | 91 | func (x *PublishEventRequest) GetPid() uint64 { 92 | if x != nil { 93 | return x.Pid 94 | } 95 | return 0 96 | } 97 | 98 | func (x *PublishEventRequest) GetSyscall() string { 99 | if x != nil { 100 | return x.Syscall 101 | } 102 | return "" 103 | } 104 | 105 | func (x *PublishEventRequest) GetK8S() *KubernetesWorkload { 106 | if x != nil { 107 | return x.K8S 108 | } 109 | return nil 110 | } 111 | 112 | type KubernetesWorkload struct { 113 | state protoimpl.MessageState 114 | sizeCache protoimpl.SizeCache 115 | unknownFields protoimpl.UnknownFields 116 | 117 | // Kubernetes namespace 118 | Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` 119 | // Kubernetes pod 120 | Pod string `protobuf:"bytes,2,opt,name=pod,proto3" json:"pod,omitempty"` 121 | // Kubernetes container, useful if there are several containers in the pod 122 | Container string `protobuf:"bytes,3,opt,name=container,proto3" json:"container,omitempty"` 123 | // pid is the pid 1 of the container 124 | Pid uint64 `protobuf:"varint,4,opt,name=pid,proto3" json:"pid,omitempty"` 125 | // pid_filter refers to the process that attached the seccomp filter. Usually 126 | // the pid 1 of the container, except with "docker-exec", "kubectl-exec" or 127 | // equivalent. 128 | PidFilter uint64 `protobuf:"varint,5,opt,name=pid_filter,json=pidFilter,proto3" json:"pid_filter,omitempty"` 129 | } 130 | 131 | func (x *KubernetesWorkload) Reset() { 132 | *x = KubernetesWorkload{} 133 | if protoimpl.UnsafeEnabled { 134 | mi := &file_seccomp_agent_proto_msgTypes[1] 135 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 136 | ms.StoreMessageInfo(mi) 137 | } 138 | } 139 | 140 | func (x *KubernetesWorkload) String() string { 141 | return protoimpl.X.MessageStringOf(x) 142 | } 143 | 144 | func (*KubernetesWorkload) ProtoMessage() {} 145 | 146 | func (x *KubernetesWorkload) ProtoReflect() protoreflect.Message { 147 | mi := &file_seccomp_agent_proto_msgTypes[1] 148 | if protoimpl.UnsafeEnabled && x != nil { 149 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 150 | if ms.LoadMessageInfo() == nil { 151 | ms.StoreMessageInfo(mi) 152 | } 153 | return ms 154 | } 155 | return mi.MessageOf(x) 156 | } 157 | 158 | // Deprecated: Use KubernetesWorkload.ProtoReflect.Descriptor instead. 159 | func (*KubernetesWorkload) Descriptor() ([]byte, []int) { 160 | return file_seccomp_agent_proto_rawDescGZIP(), []int{1} 161 | } 162 | 163 | func (x *KubernetesWorkload) GetNamespace() string { 164 | if x != nil { 165 | return x.Namespace 166 | } 167 | return "" 168 | } 169 | 170 | func (x *KubernetesWorkload) GetPod() string { 171 | if x != nil { 172 | return x.Pod 173 | } 174 | return "" 175 | } 176 | 177 | func (x *KubernetesWorkload) GetContainer() string { 178 | if x != nil { 179 | return x.Container 180 | } 181 | return "" 182 | } 183 | 184 | func (x *KubernetesWorkload) GetPid() uint64 { 185 | if x != nil { 186 | return x.Pid 187 | } 188 | return 0 189 | } 190 | 191 | func (x *KubernetesWorkload) GetPidFilter() uint64 { 192 | if x != nil { 193 | return x.PidFilter 194 | } 195 | return 0 196 | } 197 | 198 | type PublishEventResponse struct { 199 | state protoimpl.MessageState 200 | sizeCache protoimpl.SizeCache 201 | unknownFields protoimpl.UnknownFields 202 | } 203 | 204 | func (x *PublishEventResponse) Reset() { 205 | *x = PublishEventResponse{} 206 | if protoimpl.UnsafeEnabled { 207 | mi := &file_seccomp_agent_proto_msgTypes[2] 208 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 209 | ms.StoreMessageInfo(mi) 210 | } 211 | } 212 | 213 | func (x *PublishEventResponse) String() string { 214 | return protoimpl.X.MessageStringOf(x) 215 | } 216 | 217 | func (*PublishEventResponse) ProtoMessage() {} 218 | 219 | func (x *PublishEventResponse) ProtoReflect() protoreflect.Message { 220 | mi := &file_seccomp_agent_proto_msgTypes[2] 221 | if protoimpl.UnsafeEnabled && x != nil { 222 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 223 | if ms.LoadMessageInfo() == nil { 224 | ms.StoreMessageInfo(mi) 225 | } 226 | return ms 227 | } 228 | return mi.MessageOf(x) 229 | } 230 | 231 | // Deprecated: Use PublishEventResponse.ProtoReflect.Descriptor instead. 232 | func (*PublishEventResponse) Descriptor() ([]byte, []int) { 233 | return file_seccomp_agent_proto_rawDescGZIP(), []int{2} 234 | } 235 | 236 | var File_seccomp_agent_proto protoreflect.FileDescriptor 237 | 238 | var file_seccomp_agent_proto_rawDesc = []byte{ 239 | 0x0a, 0x13, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6d, 0x70, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 240 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x67, 241 | 0x65, 0x6e, 0x74, 0x66, 0x61, 0x6c, 0x63, 0x6f, 0x22, 0x8a, 0x01, 0x0a, 0x13, 0x50, 0x75, 0x62, 242 | 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 243 | 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 244 | 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x70, 245 | 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 246 | 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x12, 0x37, 0x0a, 0x03, 247 | 0x6b, 0x38, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x65, 0x63, 0x63, 248 | 0x6f, 0x6d, 0x70, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x66, 0x61, 0x6c, 0x63, 0x6f, 0x2e, 0x4b, 0x75, 249 | 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 250 | 0x52, 0x03, 0x6b, 0x38, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x12, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 251 | 0x65, 0x74, 0x65, 0x73, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, 252 | 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 253 | 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x6f, 254 | 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x6f, 0x64, 0x12, 0x1c, 0x0a, 0x09, 255 | 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 256 | 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 257 | 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 258 | 0x70, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 259 | 0x52, 0x09, 0x70, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x16, 0x0a, 0x14, 0x50, 260 | 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 261 | 0x6e, 0x73, 0x65, 0x32, 0x76, 0x0a, 0x11, 0x53, 0x65, 0x63, 0x63, 0x6f, 0x6d, 0x70, 0x41, 0x67, 262 | 0x65, 0x6e, 0x74, 0x46, 0x61, 0x6c, 0x63, 0x6f, 0x12, 0x61, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 263 | 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x2e, 0x73, 0x65, 0x63, 0x63, 0x6f, 264 | 0x6d, 0x70, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x66, 0x61, 0x6c, 0x63, 0x6f, 0x2e, 0x50, 0x75, 0x62, 265 | 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 266 | 0x1a, 0x27, 0x2e, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x66, 267 | 0x61, 0x6c, 0x63, 0x6f, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 268 | 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x21, 0x5a, 0x1f, 0x67, 269 | 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 270 | 0x6b, 0x2f, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 271 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 272 | } 273 | 274 | var ( 275 | file_seccomp_agent_proto_rawDescOnce sync.Once 276 | file_seccomp_agent_proto_rawDescData = file_seccomp_agent_proto_rawDesc 277 | ) 278 | 279 | func file_seccomp_agent_proto_rawDescGZIP() []byte { 280 | file_seccomp_agent_proto_rawDescOnce.Do(func() { 281 | file_seccomp_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_seccomp_agent_proto_rawDescData) 282 | }) 283 | return file_seccomp_agent_proto_rawDescData 284 | } 285 | 286 | var file_seccomp_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 287 | var file_seccomp_agent_proto_goTypes = []interface{}{ 288 | (*PublishEventRequest)(nil), // 0: seccompagentfalco.PublishEventRequest 289 | (*KubernetesWorkload)(nil), // 1: seccompagentfalco.KubernetesWorkload 290 | (*PublishEventResponse)(nil), // 2: seccompagentfalco.PublishEventResponse 291 | } 292 | var file_seccomp_agent_proto_depIdxs = []int32{ 293 | 1, // 0: seccompagentfalco.PublishEventRequest.k8s:type_name -> seccompagentfalco.KubernetesWorkload 294 | 0, // 1: seccompagentfalco.SeccompAgentFalco.PublishEvent:input_type -> seccompagentfalco.PublishEventRequest 295 | 2, // 2: seccompagentfalco.SeccompAgentFalco.PublishEvent:output_type -> seccompagentfalco.PublishEventResponse 296 | 2, // [2:3] is the sub-list for method output_type 297 | 1, // [1:2] is the sub-list for method input_type 298 | 1, // [1:1] is the sub-list for extension type_name 299 | 1, // [1:1] is the sub-list for extension extendee 300 | 0, // [0:1] is the sub-list for field type_name 301 | } 302 | 303 | func init() { file_seccomp_agent_proto_init() } 304 | func file_seccomp_agent_proto_init() { 305 | if File_seccomp_agent_proto != nil { 306 | return 307 | } 308 | if !protoimpl.UnsafeEnabled { 309 | file_seccomp_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 310 | switch v := v.(*PublishEventRequest); i { 311 | case 0: 312 | return &v.state 313 | case 1: 314 | return &v.sizeCache 315 | case 2: 316 | return &v.unknownFields 317 | default: 318 | return nil 319 | } 320 | } 321 | file_seccomp_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 322 | switch v := v.(*KubernetesWorkload); i { 323 | case 0: 324 | return &v.state 325 | case 1: 326 | return &v.sizeCache 327 | case 2: 328 | return &v.unknownFields 329 | default: 330 | return nil 331 | } 332 | } 333 | file_seccomp_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 334 | switch v := v.(*PublishEventResponse); i { 335 | case 0: 336 | return &v.state 337 | case 1: 338 | return &v.sizeCache 339 | case 2: 340 | return &v.unknownFields 341 | default: 342 | return nil 343 | } 344 | } 345 | } 346 | type x struct{} 347 | out := protoimpl.TypeBuilder{ 348 | File: protoimpl.DescBuilder{ 349 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 350 | RawDescriptor: file_seccomp_agent_proto_rawDesc, 351 | NumEnums: 0, 352 | NumMessages: 3, 353 | NumExtensions: 0, 354 | NumServices: 1, 355 | }, 356 | GoTypes: file_seccomp_agent_proto_goTypes, 357 | DependencyIndexes: file_seccomp_agent_proto_depIdxs, 358 | MessageInfos: file_seccomp_agent_proto_msgTypes, 359 | }.Build() 360 | File_seccomp_agent_proto = out.File 361 | file_seccomp_agent_proto_rawDesc = nil 362 | file_seccomp_agent_proto_goTypes = nil 363 | file_seccomp_agent_proto_depIdxs = nil 364 | } 365 | -------------------------------------------------------------------------------- /falco-plugin/api/seccomp-agent.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Seccomp Agent authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option go_package = "github.com/kinvolk/seccompagent"; 18 | 19 | package seccompagentfalco; 20 | 21 | service SeccompAgentFalco { 22 | rpc PublishEvent(PublishEventRequest) returns (PublishEventResponse) {} 23 | } 24 | 25 | message PublishEventRequest { 26 | // id is the cookie passed by the kernel in struct seccomp_notif 27 | uint64 id = 1; 28 | 29 | // pid refers to the process that made the syscall 30 | uint64 pid = 2; 31 | 32 | // syscall is the name of the syscall 33 | string syscall = 3; 34 | 35 | // KubernetesWorkload 36 | KubernetesWorkload k8s = 4; 37 | } 38 | 39 | message KubernetesWorkload { 40 | // Kubernetes namespace 41 | string namespace = 1; 42 | 43 | // Kubernetes pod 44 | string pod = 2; 45 | 46 | // Kubernetes container, useful if there are several containers in the pod 47 | string container = 3; 48 | 49 | // pid is the pid 1 of the container 50 | uint64 pid = 4; 51 | 52 | // pid_filter refers to the process that attached the seccomp filter. Usually 53 | // the pid 1 of the container, except with "docker-exec", "kubectl-exec" or 54 | // equivalent. 55 | uint64 pid_filter = 5; 56 | 57 | } 58 | 59 | message PublishEventResponse { 60 | } 61 | -------------------------------------------------------------------------------- /falco-plugin/api/seccomp-agent_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package seccompagent 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // SeccompAgentFalcoClient is the client API for SeccompAgentFalco service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type SeccompAgentFalcoClient interface { 21 | PublishEvent(ctx context.Context, in *PublishEventRequest, opts ...grpc.CallOption) (*PublishEventResponse, error) 22 | } 23 | 24 | type seccompAgentFalcoClient struct { 25 | cc grpc.ClientConnInterface 26 | } 27 | 28 | func NewSeccompAgentFalcoClient(cc grpc.ClientConnInterface) SeccompAgentFalcoClient { 29 | return &seccompAgentFalcoClient{cc} 30 | } 31 | 32 | func (c *seccompAgentFalcoClient) PublishEvent(ctx context.Context, in *PublishEventRequest, opts ...grpc.CallOption) (*PublishEventResponse, error) { 33 | out := new(PublishEventResponse) 34 | err := c.cc.Invoke(ctx, "/seccompagentfalco.SeccompAgentFalco/PublishEvent", in, out, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return out, nil 39 | } 40 | 41 | // SeccompAgentFalcoServer is the server API for SeccompAgentFalco service. 42 | // All implementations must embed UnimplementedSeccompAgentFalcoServer 43 | // for forward compatibility 44 | type SeccompAgentFalcoServer interface { 45 | PublishEvent(context.Context, *PublishEventRequest) (*PublishEventResponse, error) 46 | mustEmbedUnimplementedSeccompAgentFalcoServer() 47 | } 48 | 49 | // UnimplementedSeccompAgentFalcoServer must be embedded to have forward compatible implementations. 50 | type UnimplementedSeccompAgentFalcoServer struct { 51 | } 52 | 53 | func (UnimplementedSeccompAgentFalcoServer) PublishEvent(context.Context, *PublishEventRequest) (*PublishEventResponse, error) { 54 | return nil, status.Errorf(codes.Unimplemented, "method PublishEvent not implemented") 55 | } 56 | func (UnimplementedSeccompAgentFalcoServer) mustEmbedUnimplementedSeccompAgentFalcoServer() {} 57 | 58 | // UnsafeSeccompAgentFalcoServer may be embedded to opt out of forward compatibility for this service. 59 | // Use of this interface is not recommended, as added methods to SeccompAgentFalcoServer will 60 | // result in compilation errors. 61 | type UnsafeSeccompAgentFalcoServer interface { 62 | mustEmbedUnimplementedSeccompAgentFalcoServer() 63 | } 64 | 65 | func RegisterSeccompAgentFalcoServer(s grpc.ServiceRegistrar, srv SeccompAgentFalcoServer) { 66 | s.RegisterService(&SeccompAgentFalco_ServiceDesc, srv) 67 | } 68 | 69 | func _SeccompAgentFalco_PublishEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 70 | in := new(PublishEventRequest) 71 | if err := dec(in); err != nil { 72 | return nil, err 73 | } 74 | if interceptor == nil { 75 | return srv.(SeccompAgentFalcoServer).PublishEvent(ctx, in) 76 | } 77 | info := &grpc.UnaryServerInfo{ 78 | Server: srv, 79 | FullMethod: "/seccompagentfalco.SeccompAgentFalco/PublishEvent", 80 | } 81 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 82 | return srv.(SeccompAgentFalcoServer).PublishEvent(ctx, req.(*PublishEventRequest)) 83 | } 84 | return interceptor(ctx, in, info, handler) 85 | } 86 | 87 | // SeccompAgentFalco_ServiceDesc is the grpc.ServiceDesc for SeccompAgentFalco service. 88 | // It's only intended for direct use with grpc.RegisterService, 89 | // and not to be introspected or modified (even as a copy) 90 | var SeccompAgentFalco_ServiceDesc = grpc.ServiceDesc{ 91 | ServiceName: "seccompagentfalco.SeccompAgentFalco", 92 | HandlerType: (*SeccompAgentFalcoServer)(nil), 93 | Methods: []grpc.MethodDesc{ 94 | { 95 | MethodName: "PublishEvent", 96 | Handler: _SeccompAgentFalco_PublishEvent_Handler, 97 | }, 98 | }, 99 | Streams: []grpc.StreamDesc{}, 100 | Metadata: "seccomp-agent.proto", 101 | } 102 | -------------------------------------------------------------------------------- /falco-plugin/falco-config-plugin-snippet.yaml: -------------------------------------------------------------------------------- 1 | - name: seccompagent 2 | library_path: libseccompagent.so 3 | init_config: 4 | socketFile: /run/seccomp-agent-falco-plugin/seccomp-agent-falco-plugin.sock 5 | flushInterval: 30 6 | -------------------------------------------------------------------------------- /falco-plugin/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2022 The Falco Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io/ioutil" 24 | "net" 25 | "os" 26 | "time" 27 | 28 | "github.com/falcosecurity/plugin-sdk-go/pkg/sdk" 29 | "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins" 30 | "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/extractor" 31 | "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/source" 32 | "google.golang.org/grpc" 33 | 34 | pb "github.com/kinvolk/seccompagent/falco-plugin/api" 35 | ) 36 | 37 | // SeccompAgentPlugin represents our plugin 38 | type SeccompAgentPlugin struct { 39 | plugins.BasePlugin 40 | 41 | SocketFile string `json:"socketFile" jsonschema:"description=Socket File for receiving events from Seccomp Agent via gRPC (Default: /run/seccomp-agent-falco-plugin.sock)"` 42 | FlushInterval uint64 `json:"flushInterval" jsonschema:"description=Flush Interval in ms (Default: 30)"` 43 | } 44 | 45 | // SeccompAgentInstance represents a opened stream based on our Plugin 46 | type SeccompAgentInstance struct { 47 | source.BaseInstance 48 | msgC <-chan SeccompAgentMessage 49 | errC <-chan error 50 | ctx context.Context 51 | } 52 | 53 | // init function is used for referencing our plugin to the Falco plugin framework 54 | func init() { 55 | plugins.SetFactory(func() plugins.Plugin { 56 | p := &SeccompAgentPlugin{} 57 | extractor.Register(p) 58 | source.Register(p) 59 | return p 60 | }) 61 | } 62 | 63 | // Info displays information of the plugin to Falco plugin framework 64 | func (seccompAgentPlugin *SeccompAgentPlugin) Info() *plugins.Info { 65 | return &plugins.Info{ 66 | ID: 6, 67 | Name: "seccompagent", 68 | Description: "Seccomp Agent Events", 69 | Contact: "github.com/kinvolk/seccompagent/", 70 | Version: "0.2.0", 71 | EventSource: "seccompagent", 72 | } 73 | } 74 | 75 | // Init is called by the Falco plugin framework as first entry, 76 | // we use it for setting default configuration values and mapping 77 | // values from `init_config` (json format for this plugin) 78 | func (seccompAgentPlugin *SeccompAgentPlugin) Init(config string) error { 79 | seccompAgentPlugin.FlushInterval = 30 80 | seccompAgentPlugin.SocketFile = "/run/seccomp-agent-falco-plugin.sock" 81 | return json.Unmarshal([]byte(config), &seccompAgentPlugin) 82 | } 83 | 84 | // Fields exposes to Falco plugin framework all availables fields for this plugin 85 | func (seccompAgentPlugin *SeccompAgentPlugin) Fields() []sdk.FieldEntry { 86 | return []sdk.FieldEntry{ 87 | {Type: "uint64", Name: "seccompagent.id", Desc: "Cookie passed by the kernel in struct seccomp_notif"}, 88 | {Type: "uint64", Name: "seccompagent.pid", Desc: "Process that made the syscall"}, 89 | {Type: "string", Name: "seccompagent.syscall", Desc: "Name of the syscall"}, 90 | {Type: "string", Name: "seccompagent.k8s.namespace", Desc: "Kubernetes namespace"}, 91 | {Type: "string", Name: "seccompagent.k8s.pod", Desc: "Kubernetes pod"}, 92 | {Type: "string", Name: "seccompagent.k8s.container", Desc: "Kubernetes container"}, 93 | {Type: "uint64", Name: "seccompagent.k8s.pid", Desc: "Pid 1 of the container"}, 94 | {Type: "uint64", Name: "seccompagent.k8s.pidfilter", Desc: "Process that attached the seccomp filter"}, 95 | } 96 | } 97 | 98 | // Extract allows Falco plugin framework to get values for all available fields 99 | func (seccompAgentPlugin *SeccompAgentPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error { 100 | var data SeccompAgentMessage 101 | 102 | rawData, err := ioutil.ReadAll(evt.Reader()) 103 | if err != nil { 104 | fmt.Println(err.Error()) 105 | return err 106 | } 107 | 108 | err = json.Unmarshal(rawData, &data) 109 | if err != nil { 110 | fmt.Println(err.Error()) 111 | return err 112 | } 113 | 114 | switch req.Field() { 115 | case "seccompagent.id": 116 | req.SetValue(data.ID) 117 | case "seccompagent.pid": 118 | req.SetValue(data.Pid) 119 | case "seccompagent.syscall": 120 | req.SetValue(data.Syscall) 121 | case "seccompagent.k8s.namespace": 122 | req.SetValue(data.K8SNamespace) 123 | case "seccompagent.k8s.pod": 124 | req.SetValue(data.K8SPod) 125 | case "seccompagent.k8s.container": 126 | req.SetValue(data.K8SContainer) 127 | case "seccompagent.k8s.pid": 128 | req.SetValue(data.K8SPid) 129 | case "seccompagent.k8s.pidfilter": 130 | req.SetValue(data.K8SPidFilter) 131 | default: 132 | return fmt.Errorf("no known field: %s", req.Field()) 133 | } 134 | 135 | return nil 136 | } 137 | 138 | // Open is called by Falco plugin framework for opening a stream of events, we call that an instance 139 | func (seccompAgentPlugin *SeccompAgentPlugin) Open(params string) (source.Instance, error) { 140 | ctx := context.Background() 141 | msgC := make(chan SeccompAgentMessage) 142 | errC := make(chan error) 143 | 144 | os.Remove(seccompAgentPlugin.SocketFile) 145 | lis, err := net.Listen("unix", seccompAgentPlugin.SocketFile) 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | var opts []grpc.ServerOption 151 | grpcServer := grpc.NewServer(opts...) 152 | 153 | server, err := NewServer(msgC, errC) 154 | if err != nil { 155 | return nil, err 156 | } 157 | pb.RegisterSeccompAgentFalcoServer(grpcServer, server) 158 | go grpcServer.Serve(lis) 159 | 160 | return &SeccompAgentInstance{ 161 | msgC: msgC, 162 | errC: errC, 163 | ctx: ctx, 164 | }, nil 165 | } 166 | 167 | // String represents the raw value of on event 168 | // (not currently used by Falco plugin framework, only there for future usage) 169 | func (seccompAgentPlugin *SeccompAgentPlugin) String(evt sdk.EventReader) (string, error) { 170 | evtBytes, err := ioutil.ReadAll(evt.Reader()) 171 | if err != nil { 172 | return "", err 173 | } 174 | evtStr := string(evtBytes) 175 | 176 | return fmt.Sprintf("%v", evtStr), nil 177 | } 178 | 179 | // NextBatch is called by Falco plugin framework to get a batch of events from the instance 180 | func (seccompAgentInstance *SeccompAgentInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (int, error) { 181 | 182 | seccompAgentPlugin := pState.(*SeccompAgentPlugin) 183 | 184 | i := 0 185 | expire := time.After(time.Duration(seccompAgentPlugin.FlushInterval) * time.Millisecond) 186 | for i < evts.Len() { 187 | select { 188 | case m := <-seccompAgentInstance.msgC: 189 | s, _ := json.Marshal(m) 190 | evt := evts.Get(i) 191 | if _, err := evt.Writer().Write(s); err != nil { 192 | return i, err 193 | } 194 | i++ 195 | case <-expire: 196 | // Timeout occurred, flush a partial batch 197 | return i, sdk.ErrTimeout 198 | case err := <-seccompAgentInstance.errC: 199 | // todo: this will cause the program to exit. May we want to ignore some kind of error? 200 | return i, err 201 | } 202 | } 203 | 204 | // The batch is full 205 | return i, nil 206 | } 207 | 208 | func (seccompAgentInstance *SeccompAgentInstance) Close() { 209 | // TODO: Check if we need to close the channels on the sender side (not here)? 210 | seccompAgentInstance.ctx.Done() 211 | } 212 | 213 | // main is mandatory but empty, because the plugin will be used as C library by Falco plugin framework 214 | func main() {} 215 | -------------------------------------------------------------------------------- /falco-plugin/seccomp-profile-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAction": "SCMP_ACT_ALLOW", 3 | "architectures": [ 4 | "SCMP_ARCH_X86_64" 5 | ], 6 | "listenerPath": "/run/seccomp-agent.socket", 7 | "listenerMetadata": "MIDDLEWARE=falco", 8 | "syscalls": [ 9 | { 10 | "action": "SCMP_ACT_NOTIFY", 11 | "names": [ 12 | "mkdir" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /falco-plugin/seccomp_agent_rules.yaml: -------------------------------------------------------------------------------- 1 | - required_engine_version: 11 2 | 3 | - rule: Seccomp Agent 4 | desc: Seccomp Agent 5 | condition: seccompagent.syscall == "mkdir" 6 | output: > 7 | The seccomp agent detected a mkdir: 8 | id=%seccompagent.id 9 | pid=%seccompagent.pid 10 | syscall=%seccompagent.syscall 11 | k8s=( 12 | namespace=%seccompagent.k8s.namespace 13 | pod=%seccompagent.k8s.pod 14 | container=%seccompagent.k8s.container 15 | pid=%seccompagent.k8s.pid 16 | pidfilter=%seccompagent.k8s.pidfilter 17 | ) 18 | priority: NOTICE 19 | source: seccompagent 20 | tags: [seccompagent] 21 | -------------------------------------------------------------------------------- /falco-plugin/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2022 The Seccomp Agent Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | 22 | pb "github.com/kinvolk/seccompagent/falco-plugin/api" 23 | ) 24 | 25 | type Server struct { 26 | pb.UnimplementedSeccompAgentFalcoServer 27 | 28 | msgC chan SeccompAgentMessage 29 | errC chan error 30 | } 31 | 32 | // SeccompAgentMessage 33 | type SeccompAgentMessage struct { 34 | ID uint64 35 | Pid uint64 36 | Syscall string 37 | K8SNamespace string 38 | K8SPod string 39 | K8SContainer string 40 | K8SPid uint64 41 | K8SPidFilter uint64 42 | } 43 | 44 | func NewServer(msgC chan SeccompAgentMessage, errC chan error) (*Server, error) { 45 | return &Server{ 46 | msgC: msgC, 47 | errC: errC, 48 | }, nil 49 | } 50 | 51 | func (s *Server) PublishEvent(_ context.Context, req *pb.PublishEventRequest) (*pb.PublishEventResponse, error) { 52 | s.msgC <- SeccompAgentMessage{ 53 | ID: req.Id, 54 | Pid: req.Pid, 55 | Syscall: req.Syscall, 56 | K8SNamespace: req.K8S.Namespace, 57 | K8SPod: req.K8S.Pod, 58 | K8SContainer: req.K8S.Container, 59 | K8SPid: req.K8S.Pid, 60 | K8SPidFilter: req.K8S.PidFilter, 61 | } 62 | return &pb.PublishEventResponse{}, nil 63 | } 64 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kinvolk/seccompagent 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/falcosecurity/plugin-sdk-go v0.7.1 7 | github.com/inspektor-gadget/inspektor-gadget v0.12.1 8 | github.com/opencontainers/runc v1.1.12 9 | github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 10 | github.com/prometheus/client_golang v1.15.0 11 | github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 12 | github.com/sirupsen/logrus v1.8.1 13 | golang.org/x/sys v0.6.0 14 | google.golang.org/grpc v1.47.0 15 | google.golang.org/protobuf v1.30.0 16 | k8s.io/api v0.25.4 17 | k8s.io/apimachinery v0.25.4 18 | k8s.io/client-go v0.25.4 19 | kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 20 | ) 21 | 22 | require ( 23 | github.com/PuerkitoBio/purell v1.1.1 // indirect 24 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 25 | github.com/beorn7/perks v1.0.1 // indirect 26 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 27 | github.com/cilium/ebpf v0.9.1 // indirect 28 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 29 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 32 | github.com/go-logr/logr v1.2.3 // indirect 33 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 34 | github.com/go-openapi/jsonreference v0.19.5 // indirect 35 | github.com/go-openapi/swag v0.19.14 // indirect 36 | github.com/godbus/dbus/v5 v5.0.6 // indirect 37 | github.com/gogo/protobuf v1.3.2 // indirect 38 | github.com/golang/protobuf v1.5.3 // indirect 39 | github.com/google/gnostic v0.5.7-v3refs // indirect 40 | github.com/google/gofuzz v1.2.0 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/mailru/easyjson v0.7.6 // indirect 44 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 45 | github.com/moby/sys/mountinfo v0.5.0 // indirect 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 47 | github.com/modern-go/reflect2 v1.0.2 // indirect 48 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 49 | github.com/prometheus/client_model v0.3.0 // indirect 50 | github.com/prometheus/common v0.42.0 // indirect 51 | github.com/prometheus/procfs v0.9.0 // indirect 52 | golang.org/x/net v0.8.0 // indirect 53 | golang.org/x/oauth2 v0.5.0 // indirect 54 | golang.org/x/term v0.6.0 // indirect 55 | golang.org/x/text v0.8.0 // indirect 56 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 57 | google.golang.org/appengine v1.6.7 // indirect 58 | google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect 59 | gopkg.in/inf.v0 v0.9.1 // indirect 60 | gopkg.in/yaml.v2 v2.4.0 // indirect 61 | gopkg.in/yaml.v3 v3.0.1 // indirect 62 | k8s.io/klog/v2 v2.70.1 // indirect 63 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 64 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 65 | kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect 66 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 67 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 68 | sigs.k8s.io/yaml v1.3.0 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= 5 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 6 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 7 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 8 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 9 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 10 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 11 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 12 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 13 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 14 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 15 | github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4= 16 | github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= 17 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 18 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 19 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 20 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 21 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 22 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 23 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 24 | github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= 25 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 26 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 27 | github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= 28 | github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 33 | github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= 34 | github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 35 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 36 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 37 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 38 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 39 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 40 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 41 | github.com/falcosecurity/plugin-sdk-go v0.7.1 h1:tVi5MdQ9dq6i5f5R29ufhsgKs0gYOXLZQ4d83gEbanE= 42 | github.com/falcosecurity/plugin-sdk-go v0.7.1/go.mod h1:NP+y22DYOS+G3GDXIXNmzf0CBL3nfPPMoQuHvAzfitQ= 43 | github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= 44 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 45 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 46 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 47 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 48 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 49 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 50 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 51 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 52 | github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= 53 | github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= 54 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 55 | github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= 56 | github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 57 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 58 | github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= 59 | github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 60 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 61 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 62 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 63 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 64 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 65 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 68 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 69 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 70 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 71 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 72 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 73 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 74 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 75 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 76 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 77 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 78 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 79 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 80 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 81 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= 82 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 83 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 84 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 85 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 86 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 87 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 88 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 89 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 90 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 91 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 92 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 93 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 94 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 95 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 96 | github.com/inspektor-gadget/inspektor-gadget v0.12.1 h1:rJDc86ls4J3N4e9eWmiOSsIGt35ZBxzQoa9WBEQxkr8= 97 | github.com/inspektor-gadget/inspektor-gadget v0.12.1/go.mod h1:nHHmmKxDgVoKm8ePfG2Z2OP2AZBCllwgwDrhfibcbDE= 98 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 99 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 100 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 101 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 102 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 103 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 104 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 105 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 106 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 107 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 108 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 109 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 110 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 111 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 112 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 113 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 114 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 115 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 116 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 117 | github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= 118 | github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= 119 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 120 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 121 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 122 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 123 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 124 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 125 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 126 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 127 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 128 | github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= 129 | github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= 130 | github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= 131 | github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= 132 | github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= 133 | github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 134 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 135 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 136 | github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= 137 | github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= 138 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 139 | github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= 140 | github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 141 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 142 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 143 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= 144 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 145 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 146 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 147 | github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds= 148 | github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= 149 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 150 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 151 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 152 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 153 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 154 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 155 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 156 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 157 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 158 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 159 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 160 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 161 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 162 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 163 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 164 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 165 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 166 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 167 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 168 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 169 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 170 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 171 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 172 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 173 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 174 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 175 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 176 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 177 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 178 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 179 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 180 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 181 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 182 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 183 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 184 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 185 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 186 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 187 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 188 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 189 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 190 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 191 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 192 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 193 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 194 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 195 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 196 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 197 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 198 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 199 | golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= 200 | golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= 201 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 202 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 203 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 204 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 205 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 206 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 207 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 208 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 209 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 211 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 212 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 213 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 217 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 218 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 219 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 220 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 221 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= 222 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 223 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 224 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 225 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 226 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 227 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 228 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 229 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= 230 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 231 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 232 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 233 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 234 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 235 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 236 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 237 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 238 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 239 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 240 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 241 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 242 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 243 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 244 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 245 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 246 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 247 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 248 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 249 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 250 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 251 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 252 | google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= 253 | google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= 254 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 255 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 256 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 257 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 258 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 259 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 260 | google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 261 | google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= 262 | google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 263 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 264 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 265 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 266 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 267 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 268 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 269 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 270 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 271 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 272 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 273 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 274 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 275 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 276 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 277 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 278 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 279 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 280 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 281 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 282 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 283 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 284 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 285 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 286 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 287 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 288 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 289 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 290 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 291 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 292 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 293 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 294 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 295 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 296 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 297 | k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= 298 | k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= 299 | k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= 300 | k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= 301 | k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8= 302 | k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= 303 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 304 | k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= 305 | k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 306 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= 307 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= 308 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= 309 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 310 | kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag= 311 | kernel.org/pub/linux/libs/security/libcap/cap v1.2.69/go.mod h1:Tk5Ip2TuxaWGpccL7//rAsLRH6RQ/jfqTGxuN/+i/FQ= 312 | kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs= 313 | kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= 314 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= 315 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 316 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 317 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 318 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 319 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 320 | -------------------------------------------------------------------------------- /pkg/agent/agent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build linux 16 | // +build linux 17 | 18 | package agent 19 | 20 | import ( 21 | "encoding/json" 22 | "errors" 23 | "fmt" 24 | "net" 25 | "os" 26 | 27 | "github.com/opencontainers/runtime-spec/specs-go" 28 | libseccomp "github.com/seccomp/libseccomp-golang" 29 | log "github.com/sirupsen/logrus" 30 | "golang.org/x/sys/unix" 31 | 32 | "github.com/kinvolk/seccompagent/pkg/registry" 33 | ) 34 | 35 | func closeStateFds(recvFds []int) { 36 | // If performance becomes an issue, we can fallback to the new syscall closerange(). 37 | for i := range recvFds { 38 | // Ignore the return code. There isn't anything better to do. 39 | unix.Close(i) 40 | } 41 | } 42 | 43 | // parseStateFds returns the seccomp-fd and closes the rest of the fds in 44 | // recvFds. In case of error, no fd is closed. 45 | // StateFds is assumed to be formated as specs.ContainerProcessState.Fds and 46 | // recvFds the corresponding list of received fds in the same SCM_RIGHT message. 47 | func parseStateFds(stateFds []string, recvFds []int) (uintptr, error) { 48 | // Lets find the index in stateFds of the seccomp-fd. 49 | idx := -1 50 | idxCount := 0 51 | 52 | for i, name := range stateFds { 53 | if name == specs.SeccompFdName { 54 | idx = i 55 | idxCount++ 56 | } 57 | } 58 | 59 | if idxCount != 1 || idx == -1 { 60 | return 0, errors.New("seccomp fd not found or malformed containerProcessState.Fds") 61 | } 62 | 63 | if idx >= len(recvFds) { 64 | return 0, fmt.Errorf("seccomp fd index out of range") 65 | } 66 | 67 | fd := uintptr(recvFds[idx]) 68 | 69 | for i := range recvFds { 70 | if i == idx { 71 | continue 72 | } 73 | 74 | unix.Close(recvFds[i]) 75 | } 76 | 77 | return fd, nil 78 | } 79 | 80 | func receiveNewSeccompFile(resolver registry.ResolverFunc, sockfd int) (*registry.Registry, *os.File, error) { 81 | MaxStateLen := 32768 82 | 83 | // File descriptors over SCM_RIGHTS are 'int' according to "man cmsg". 84 | // The unix golang package assumes that a file descriptor is a int32, see: 85 | // https://github.com/golang/sys/blob/68d13333faf2/unix/sockcmsg_unix.go#L66-L73 86 | // On Linux and Windows, `int` is always 32 bits, so it's fine: 87 | // https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models 88 | oobSpace := unix.CmsgSpace(4) 89 | stateBuf := make([]byte, MaxStateLen) 90 | oob := make([]byte, oobSpace) 91 | 92 | // TODO: use conn.ReadMsgUnix() instead of unix.Recvmsg(). 93 | 94 | n, oobn, _, _, err := unix.Recvmsg(sockfd, stateBuf, oob, 0) 95 | if err != nil { 96 | return nil, nil, err 97 | } 98 | if n >= MaxStateLen || oobn != oobSpace { 99 | return nil, nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) 100 | } 101 | 102 | // Truncate. 103 | stateBuf = stateBuf[:n] 104 | oob = oob[:oobn] 105 | 106 | scms, err := unix.ParseSocketControlMessage(oob) 107 | if err != nil { 108 | return nil, nil, err 109 | } 110 | if len(scms) != 1 { 111 | return nil, nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) 112 | } 113 | scm := scms[0] 114 | 115 | // The fds are added just after executing recvmsg(). So, since then 116 | // until here, if we return, we are leaking fds. 117 | // However, it is tricky to close the fds before we have a reference to 118 | // the fds slice, that we create just here. 119 | // TODO: Close fds if we return before this too. 120 | fds, err := unix.ParseUnixRights(&scm) 121 | if err != nil { 122 | return nil, nil, err 123 | } 124 | 125 | containerProcessState := &specs.ContainerProcessState{} 126 | err = json.Unmarshal(stateBuf, containerProcessState) 127 | if err != nil { 128 | closeStateFds(fds) 129 | return nil, nil, fmt.Errorf("cannot parse OCI state: %v\n", err) 130 | } 131 | 132 | fd, err := parseStateFds(containerProcessState.Fds, fds) 133 | if err != nil { 134 | closeStateFds(fds) 135 | return nil, nil, err 136 | } 137 | 138 | log.WithFields(log.Fields{ 139 | "fd": fd, 140 | "id": containerProcessState.State.ID, 141 | "pid": containerProcessState.Pid, 142 | "pid1": containerProcessState.State.Pid, 143 | "annotations": containerProcessState.State.Annotations, 144 | }).Debug("New seccomp fd received on socket") 145 | 146 | var reg *registry.Registry 147 | if resolver != nil { 148 | reg = resolver(containerProcessState) 149 | } 150 | 151 | return reg, os.NewFile(fd, "seccomp-fd"), nil 152 | } 153 | 154 | // notifHandler handles seccomp notifications and responses 155 | func notifHandler(reg *registry.Registry, seccompFile *os.File) { 156 | fd := libseccomp.ScmpFd(seccompFile.Fd()) 157 | defer func() { 158 | log.WithFields(log.Fields{ 159 | "fd": fd, 160 | }).Debug("Closing seccomp fd") 161 | seccompFile.Close() 162 | }() 163 | 164 | for { 165 | req, err := libseccomp.NotifReceive(fd) 166 | if err != nil { 167 | if err == unix.ENOENT { 168 | log.WithFields(log.Fields{ 169 | "fd": fd, 170 | }).Trace("Handling of new notification could not start") 171 | continue 172 | } 173 | log.WithFields(log.Fields{ 174 | "fd": fd, 175 | "err": err, 176 | }).Error("Error on receiving seccomp notification") 177 | return 178 | } 179 | syscallName, err := req.Data.Syscall.GetName() 180 | if err != nil { 181 | log.WithFields(log.Fields{ 182 | "fd": fd, 183 | "req": req, 184 | "err": err, 185 | }).Error("Error in decoding syscall") 186 | return 187 | } 188 | 189 | log.WithFields(log.Fields{ 190 | "fd": fd, 191 | "syscall": syscallName, 192 | }).Trace("Received syscall") 193 | 194 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 195 | log.WithFields(log.Fields{ 196 | "fd": fd, 197 | "syscall": syscallName, 198 | "req": req, 199 | }).Debug("Notification no longer valid") 200 | continue 201 | } 202 | 203 | resp := &libseccomp.ScmpNotifResp{ 204 | ID: req.ID, 205 | Error: 0, 206 | Val: 0, 207 | Flags: libseccomp.NotifRespFlagContinue, 208 | } 209 | 210 | if reg != nil { 211 | handler := reg.Lookup(syscallName) 212 | if handler != nil { 213 | result := handler(fd, req) 214 | if result.Intr { 215 | log.WithFields(log.Fields{ 216 | "fd": fd, 217 | "syscall": syscallName, 218 | "req": req, 219 | }).Debug("Handling of syscall interrupted") 220 | continue 221 | } 222 | resp.Error = result.ErrVal 223 | resp.Val = result.Val 224 | resp.Flags = result.Flags 225 | } 226 | } 227 | 228 | if err = libseccomp.NotifRespond(fd, resp); err != nil { 229 | if err == unix.ENOENT { 230 | log.WithFields(log.Fields{ 231 | "fd": fd, 232 | "syscall": syscallName, 233 | "req": req, 234 | "resp": resp, 235 | }).Debug("Could not reply to seccomp notification") 236 | continue 237 | } 238 | log.WithFields(log.Fields{ 239 | "fd": fd, 240 | "syscall": syscallName, 241 | "req": req, 242 | "resp": resp, 243 | "err": err, 244 | }).Error("Error on responding seccomp notification") 245 | return 246 | } 247 | } 248 | } 249 | 250 | func StartAgent(socketFile string, resolver registry.ResolverFunc) error { 251 | if err := os.RemoveAll(socketFile); err != nil { 252 | return err 253 | } 254 | 255 | l, err := net.Listen("unix", socketFile) 256 | if err != nil { 257 | return fmt.Errorf("cannot listen on %s: %s", socketFile, err) 258 | } 259 | defer l.Close() 260 | 261 | for { 262 | conn, err := l.Accept() 263 | if err != nil { 264 | return fmt.Errorf("cannot accept connection: %s", err) 265 | } 266 | socket, err := conn.(*net.UnixConn).File() 267 | conn.Close() 268 | if err != nil { 269 | return fmt.Errorf("cannot get socket: %v\n", err) 270 | } 271 | 272 | reg, newSeccompFile, err := receiveNewSeccompFile(resolver, int(socket.Fd())) 273 | if err != nil { 274 | log.WithFields(log.Fields{ 275 | "socket": socketFile, 276 | "err": err, 277 | }).Error("Error on receiving seccomp fd") 278 | } 279 | socket.Close() 280 | 281 | go notifHandler(reg, newSeccompFile) 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /pkg/handlers/default.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handlers 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | "strings" 23 | 24 | libctManager "github.com/opencontainers/runc/libcontainer/cgroups/manager" 25 | libctConfig "github.com/opencontainers/runc/libcontainer/configs" 26 | libseccomp "github.com/seccomp/libseccomp-golang" 27 | log "github.com/sirupsen/logrus" 28 | "golang.org/x/sys/unix" 29 | 30 | "github.com/kinvolk/seccompagent/pkg/registry" 31 | ) 32 | 33 | func KillContainer(pid int) registry.HandlerFunc { 34 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 35 | p, err := os.FindProcess(pid) 36 | if err != nil { 37 | log.WithFields(log.Fields{ 38 | "pid": pid, 39 | }).Error("cannot find process") 40 | return registry.HandlerResultErrno(unix.EPERM) 41 | } 42 | 43 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 44 | return registry.HandlerResultIntr() 45 | } 46 | 47 | err = p.Signal(os.Kill) 48 | if err != nil { 49 | log.WithFields(log.Fields{ 50 | "pid": pid, 51 | }).Error("cannot kill process") 52 | return registry.HandlerResultErrno(unix.EPERM) 53 | } 54 | 55 | return registry.HandlerResultErrno(unix.EPERM) 56 | } 57 | } 58 | 59 | // freezerCgroupPath parses /proc/$pid/cgroup and find the cgroup path from 60 | // - either the freezer cgroup (starting with '%d:freezer:'), or 61 | // - the unified hierarchy (starting with '0::') 62 | func freezerCgroupPath(pid int) string { 63 | var err error 64 | var cgroupFile *os.File 65 | if cgroupFile, err = os.Open(filepath.Join("/proc", fmt.Sprintf("%d", pid), "cgroup")); err != nil { 66 | log.WithFields(log.Fields{ 67 | "pid": pid, 68 | "err": err, 69 | }).Error("cannot parse cgroup") 70 | return "" 71 | } 72 | defer cgroupFile.Close() 73 | 74 | reader := bufio.NewReader(cgroupFile) 75 | for { 76 | line, err := reader.ReadString('\n') 77 | if err != nil { 78 | break 79 | } 80 | line = strings.TrimSuffix(line, "\n") 81 | fields := strings.SplitN(line, ":", 3) 82 | if len(fields) != 3 { 83 | continue 84 | } 85 | cgroupHierarchyID := fields[0] 86 | cgroupControllerList := fields[1] 87 | cgroupPath := fields[2] 88 | 89 | for _, cgroupController := range strings.Split(cgroupControllerList, ",") { 90 | if cgroupController == "freezer" { 91 | return cgroupPath 92 | } 93 | } 94 | if cgroupHierarchyID == "0" && cgroupControllerList == "" { 95 | return cgroupPath 96 | } 97 | } 98 | return "" 99 | } 100 | 101 | func FreezeContainer(pid int) registry.HandlerFunc { 102 | cgroupPath := freezerCgroupPath(pid) 103 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 104 | if cgroupPath == "" { 105 | log.WithFields(log.Fields{ 106 | "pid": pid, 107 | }).Error("cgroup path not found") 108 | return registry.HandlerResultErrno(unix.EPERM) 109 | } 110 | 111 | if cgroupPath == "/" { 112 | log.WithFields(log.Fields{ 113 | "pid": pid, 114 | }).Error("refuse to use root cgroup") 115 | return registry.HandlerResultErrno(unix.EPERM) 116 | } 117 | 118 | cgroup := &libctConfig.Cgroup{ 119 | Path: cgroupPath, 120 | Resources: &libctConfig.Resources{}, 121 | } 122 | 123 | m, err := libctManager.New(cgroup) 124 | if err != nil { 125 | log.WithFields(log.Fields{ 126 | "pid": pid, 127 | "cgroupPath": cgroupPath, 128 | "err": err, 129 | }).Error("cannot create new cgroup manager") 130 | return registry.HandlerResultErrno(unix.EPERM) 131 | } 132 | 133 | if err := m.Freeze(libctConfig.Frozen); err != nil { 134 | log.WithFields(log.Fields{ 135 | "pid": pid, 136 | "cgroupPath": cgroupPath, 137 | "err": err, 138 | }).Error("cannot freeze cgroup") 139 | return registry.HandlerResultErrno(unix.EPERM) 140 | } 141 | 142 | return registry.HandlerResultErrno(unix.EPERM) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /pkg/handlers/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handlers 16 | 17 | import ( 18 | "github.com/kinvolk/seccompagent/pkg/registry" 19 | 20 | libseccomp "github.com/seccomp/libseccomp-golang" 21 | ) 22 | 23 | func Error(err error) registry.HandlerFunc { 24 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 25 | return registry.HandlerResultErrno(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/handlers/exec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handlers 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | "time" 21 | 22 | "github.com/kinvolk/seccompagent/pkg/kuberesolver" 23 | "github.com/kinvolk/seccompagent/pkg/readarg" 24 | "github.com/kinvolk/seccompagent/pkg/registry" 25 | 26 | libseccomp "github.com/seccomp/libseccomp-golang" 27 | log "github.com/sirupsen/logrus" 28 | "golang.org/x/sys/unix" 29 | ) 30 | 31 | func ExecCondition(filePattern string, duration time.Duration) registry.HandlerFunc { 32 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 33 | // This handlers does not change the behaviour but just delay the return 34 | result = registry.HandlerResult{Flags: libseccomp.NotifRespFlagContinue} 35 | 36 | memFile, err := readarg.OpenMem(req.Pid) 37 | if err != nil { 38 | return 39 | } 40 | defer memFile.Close() 41 | 42 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 43 | return registry.HandlerResult{Intr: true} 44 | } 45 | 46 | fileName, err := readarg.ReadString(memFile, int64(req.Data.Args[0])) 47 | if err != nil { 48 | log.WithFields(log.Fields{ 49 | "fd": fd, 50 | "pid": req.Pid, 51 | "err": err, 52 | }).Error("Cannot read argument") 53 | return 54 | } 55 | 56 | if fileName == filePattern { 57 | log.WithFields(log.Fields{ 58 | "fd": fd, 59 | "pid": req.Pid, 60 | "filename": fileName, 61 | "file-pattern": filePattern, 62 | "duration": duration, 63 | }).Debug("Execve: introduce delay") 64 | time.Sleep(duration) 65 | } else { 66 | log.WithFields(log.Fields{ 67 | "fd": fd, 68 | "pid": req.Pid, 69 | "filename": fileName, 70 | "file-pattern": filePattern, 71 | }).Debug("Execve: no match; continue") 72 | } 73 | return 74 | } 75 | } 76 | 77 | func ExecSidecars(podCtx *kuberesolver.PodContext, sidecarsList string, duration time.Duration) registry.HandlerFunc { 78 | sidecars := map[string]struct{}{} 79 | for _, sidecar := range strings.Split(sidecarsList, ",") { 80 | sidecars[sidecar] = struct{}{} 81 | } 82 | 83 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 84 | // This handlers does not change the behaviour but just delay the return 85 | result = registry.HandlerResult{Flags: libseccomp.NotifRespFlagContinue} 86 | 87 | // Only care about processes from runc-init, not from runc-exec 88 | if podCtx.Pid != podCtx.Pid1 { 89 | return 90 | } 91 | // Only care about syscalls from pid1 92 | if int(req.Pid) != podCtx.Pid1 { 93 | return 94 | } 95 | 96 | // Sidecars can go on 97 | if _, ok := sidecars[podCtx.Container]; ok { 98 | log.WithFields(log.Fields{ 99 | "fd": fd, 100 | "pid": req.Pid, 101 | "container": podCtx.Container, 102 | }).Debug("Execve: found sidecar") 103 | 104 | return 105 | } 106 | 107 | // Non-sidecars have to wait 108 | var stat unix.Stat_t 109 | err := unix.Stat(fmt.Sprintf("/proc/%d", req.Pid), &stat) 110 | if err != nil { 111 | log.WithFields(log.Fields{ 112 | "fd": fd, 113 | "pid": req.Pid, 114 | "container": podCtx.Container, 115 | }).Error("Execve: cannot read procfs") 116 | return 117 | } 118 | ctime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) 119 | diff := ctime.Add(duration).Sub(time.Now()) 120 | 121 | log.WithFields(log.Fields{ 122 | "fd": fd, 123 | "pid": req.Pid, 124 | "container": podCtx.Container, 125 | "ctime": ctime.String(), 126 | "diff": diff.String(), 127 | }).Debug("Execve: found non-sidecar container") 128 | if diff > 0 { 129 | time.Sleep(diff) 130 | } 131 | return 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /pkg/handlers/falco/falco.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package falco 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | libseccomp "github.com/seccomp/libseccomp-golang" 22 | log "github.com/sirupsen/logrus" 23 | "google.golang.org/grpc" 24 | 25 | pb "github.com/kinvolk/seccompagent/falco-plugin/api" 26 | 27 | "github.com/kinvolk/seccompagent/pkg/kuberesolver" 28 | "github.com/kinvolk/seccompagent/pkg/registry" 29 | ) 30 | 31 | const socketfile = "/run/seccomp-agent-falco-plugin/seccomp-agent-falco-plugin.sock" 32 | 33 | func NotifyFalco(podCtx *kuberesolver.PodContext) func(h registry.HandlerFunc) registry.HandlerFunc { 34 | return func(h registry.HandlerFunc) registry.HandlerFunc { 35 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) registry.HandlerResult { 36 | log.WithFields(log.Fields{ 37 | "pod": podCtx, 38 | }).Debug("Falco middleware") 39 | 40 | var client pb.SeccompAgentFalcoClient 41 | var ctx context.Context 42 | var cancel context.CancelFunc 43 | conn, err := grpc.Dial("unix://"+socketfile, grpc.WithInsecure()) 44 | if err != nil { 45 | panic(err) 46 | } 47 | defer conn.Close() 48 | client = pb.NewSeccompAgentFalcoClient(conn) 49 | ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) 50 | defer cancel() 51 | 52 | syscallName, err := req.Data.Syscall.GetName() 53 | if err != nil { 54 | log.WithFields(log.Fields{ 55 | "fd": fd, 56 | "req": req, 57 | "err": err, 58 | }).Error("Error in decoding syscall") 59 | } 60 | 61 | _, err = client.PublishEvent(ctx, &pb.PublishEventRequest{ 62 | Id: req.ID, 63 | Pid: uint64(req.Pid), 64 | Syscall: syscallName, 65 | K8S: &pb.KubernetesWorkload{ 66 | Namespace: podCtx.Namespace, 67 | Pod: podCtx.Pod, 68 | Container: podCtx.Container, 69 | PidFilter: uint64(podCtx.Pid), 70 | Pid: uint64(podCtx.Pid1), 71 | }, 72 | }) 73 | if err != nil { 74 | log.WithFields(log.Fields{ 75 | "fd": fd, 76 | "req": req, 77 | "err": err, 78 | }).Error("Error in sending event to Falco") 79 | } 80 | 81 | var r registry.HandlerResult 82 | if h != nil { 83 | r = h(fd, req) 84 | } else { 85 | r = registry.HandlerResultContinue() 86 | } 87 | 88 | log.WithFields(log.Fields{ 89 | "pod": podCtx, 90 | }).Debug("Falco middleware completed") 91 | return r 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/handlers/mkdir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handlers 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "strconv" 21 | 22 | "github.com/kinvolk/seccompagent/pkg/nsenter" 23 | "github.com/kinvolk/seccompagent/pkg/readarg" 24 | "github.com/kinvolk/seccompagent/pkg/registry" 25 | 26 | libseccomp "github.com/seccomp/libseccomp-golang" 27 | log "github.com/sirupsen/logrus" 28 | "golang.org/x/sys/unix" 29 | ) 30 | 31 | var _ = nsenter.RegisterModule("mkdir", runMkdirInNamespaces) 32 | 33 | type mkdirModuleParams struct { 34 | Module string `json:"module,omitempty"` 35 | Path string `json:"path,omitempty"` 36 | Mode uint32 `json:"mode,omitempty"` 37 | } 38 | 39 | func runMkdirInNamespaces(param []byte) string { 40 | var params mkdirModuleParams 41 | err := json.Unmarshal(param, ¶ms) 42 | if err != nil { 43 | return fmt.Sprintf("%d", int(unix.ENOSYS)) 44 | } 45 | 46 | err = unix.Mkdir(params.Path, params.Mode) 47 | if err != nil { 48 | return fmt.Sprintf("%d", int(err.(unix.Errno))) 49 | } 50 | return "0" 51 | } 52 | 53 | func MkdirWithSuffix(suffix string) registry.HandlerFunc { 54 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 55 | memFile, err := readarg.OpenMem(req.Pid) 56 | if err != nil { 57 | return registry.HandlerResult{Flags: libseccomp.NotifRespFlagContinue} 58 | } 59 | defer memFile.Close() 60 | 61 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 62 | return registry.HandlerResultIntr() 63 | } 64 | 65 | fileName, err := readarg.ReadString(memFile, int64(req.Data.Args[0])) 66 | if err != nil { 67 | log.WithFields(log.Fields{ 68 | "fd": fd, 69 | "pid": req.Pid, 70 | "err": err, 71 | }).Error("Cannot read argument") 72 | return registry.HandlerResultErrno(unix.EFAULT) 73 | } 74 | 75 | params := mkdirModuleParams{ 76 | Module: "mkdir", 77 | Path: fileName + suffix, 78 | Mode: uint32(req.Data.Args[1]), 79 | } 80 | 81 | mntns, err := nsenter.OpenNamespace(req.Pid, "mnt") 82 | if err != nil { 83 | log.WithFields(log.Fields{ 84 | "fd": fd, 85 | "pid": req.Pid, 86 | "err": err, 87 | }).Error("Cannot open namespace") 88 | return registry.HandlerResultErrno(unix.EPERM) 89 | } 90 | defer mntns.Close() 91 | 92 | root, err := nsenter.OpenRoot(req.Pid) 93 | if err != nil { 94 | log.WithFields(log.Fields{ 95 | "fd": fd, 96 | "pid": req.Pid, 97 | "err": err, 98 | }).Error("Cannot open root") 99 | return registry.HandlerResultErrno(unix.EPERM) 100 | } 101 | defer root.Close() 102 | 103 | cwd, err := nsenter.OpenCwd(req.Pid) 104 | if err != nil { 105 | log.WithFields(log.Fields{ 106 | "fd": fd, 107 | "pid": req.Pid, 108 | "err": err, 109 | }).Error("Cannot open cwd") 110 | return registry.HandlerResultErrno(unix.EPERM) 111 | } 112 | defer cwd.Close() 113 | 114 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 115 | log.WithFields(log.Fields{ 116 | "fd": fd, 117 | "req": req, 118 | "err": err, 119 | }).Debug("Notification no longer valid") 120 | return registry.HandlerResultIntr() 121 | } 122 | 123 | output, err := nsenter.Run(root, cwd, mntns, nil, nil, params) 124 | if err != nil { 125 | log.WithFields(log.Fields{ 126 | "fd": fd, 127 | "pid": req.Pid, 128 | "output": output, 129 | "err": err, 130 | }).Error("Run in target namespaces failed") 131 | return registry.HandlerResultErrno(unix.ENOSYS) 132 | } 133 | errno, err := strconv.Atoi(string(output)) 134 | if err != nil { 135 | log.WithFields(log.Fields{ 136 | "fd": fd, 137 | "pid": req.Pid, 138 | "output": output, 139 | "err": err, 140 | }).Error("Cannot parse return value") 141 | return registry.HandlerResultErrno(unix.ENOSYS) 142 | } 143 | if errno != 0 { 144 | return registry.HandlerResultErrno(unix.Errno(errno)) 145 | } 146 | 147 | return registry.HandlerResultSuccess() 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /pkg/handlers/mount.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build linux && cgo 16 | // +build linux,cgo 17 | 18 | package handlers 19 | 20 | import ( 21 | "encoding/json" 22 | "fmt" 23 | "strconv" 24 | 25 | libseccomp "github.com/seccomp/libseccomp-golang" 26 | log "github.com/sirupsen/logrus" 27 | "golang.org/x/sys/unix" 28 | 29 | "github.com/kinvolk/seccompagent/pkg/nsenter" 30 | "github.com/kinvolk/seccompagent/pkg/readarg" 31 | "github.com/kinvolk/seccompagent/pkg/registry" 32 | "github.com/kinvolk/seccompagent/pkg/userns" 33 | ) 34 | 35 | var _ = nsenter.RegisterModule("mount", runMountInNamespaces) 36 | 37 | type mountModuleParams struct { 38 | Module string `json:"module,omitempty"` 39 | Source string `json:"source,omitempty"` 40 | Dest string `json:"dest,omitempty"` 41 | Filesystem string `json:"filesystem,omitempty"` 42 | Flags int64 `json:"flags,omitempty"` 43 | Options string `json:"options,omitempty"` 44 | } 45 | 46 | func runMountInNamespaces(param []byte) string { 47 | var params mountModuleParams 48 | err := json.Unmarshal(param, ¶ms) 49 | if err != nil { 50 | return fmt.Sprintf("%d", int(unix.ENOSYS)) 51 | } 52 | 53 | err = unix.Mount(params.Source, params.Dest, params.Filesystem, 0, params.Options) 54 | if err != nil { 55 | return fmt.Sprintf("%d", int(err.(unix.Errno))) 56 | } 57 | return "0" 58 | } 59 | 60 | func Mount(allowedFilesystems map[string]struct{}, requireUserNamespaceAdmin bool) registry.HandlerFunc { 61 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) (result registry.HandlerResult) { 62 | memFile, err := readarg.OpenMem(req.Pid) 63 | if err != nil { 64 | return registry.HandlerResultErrno(unix.EPERM) 65 | } 66 | defer memFile.Close() 67 | 68 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 69 | return registry.HandlerResultIntr() 70 | } 71 | 72 | source, err := readarg.ReadString(memFile, int64(req.Data.Args[0])) 73 | if err != nil { 74 | log.WithFields(log.Fields{ 75 | "fd": fd, 76 | "pid": req.Pid, 77 | "arg": 0, 78 | "err": err, 79 | }).Error("Cannot read argument") 80 | return registry.HandlerResultErrno(unix.EFAULT) 81 | } 82 | dest, err := readarg.ReadString(memFile, int64(req.Data.Args[1])) 83 | if err != nil { 84 | log.WithFields(log.Fields{ 85 | "fd": fd, 86 | "pid": req.Pid, 87 | "arg": 1, 88 | "err": err, 89 | }).Error("Cannot read argument") 90 | return registry.HandlerResultErrno(unix.EFAULT) 91 | } 92 | filesystem, err := readarg.ReadString(memFile, int64(req.Data.Args[2])) 93 | if err != nil { 94 | log.WithFields(log.Fields{ 95 | "fd": fd, 96 | "pid": req.Pid, 97 | "arg": 2, 98 | "err": err, 99 | }).Error("Cannot read argument") 100 | return registry.HandlerResultErrno(unix.EFAULT) 101 | } 102 | 103 | // We don't handle flags, we may want to consider allowing a few. 104 | // This is here so the debug logging makes it possible to see flags used. 105 | flags := int64(req.Data.Args[3]) 106 | 107 | log.WithFields(log.Fields{ 108 | "fd": fd, 109 | "pid": req.Pid, 110 | "source": source, 111 | "dest": dest, 112 | "filesystem": filesystem, 113 | "flags": flags, 114 | }).Debug("Mount") 115 | 116 | if _, ok := allowedFilesystems[filesystem]; !ok { 117 | // The seccomp agent is not allowed to perform this operation. 118 | // Let the kernel decide if it's allowed 119 | return registry.HandlerResultContinue() 120 | } 121 | 122 | var options string 123 | if req.Data.Args[4] != 0/* NULL */ && filesystem != "sysfs" { 124 | // Get options, we assume because this is specified in 125 | // allowedFilesystems that the data argument to mount(2) 126 | // is a string so this is safe now. We ignore options for sysfs, as it 127 | // doesn't define options. 128 | options, err = readarg.ReadString(memFile, int64(req.Data.Args[4])) 129 | if err != nil { 130 | log.WithFields(log.Fields{ 131 | "fd": fd, 132 | "pid": req.Pid, 133 | "arg": 4, 134 | "err": err, 135 | }).Error("Cannot read argument") 136 | return registry.HandlerResultErrno(unix.EFAULT) 137 | } 138 | 139 | // Log this at trace level only as it could have user credentials. 140 | log.WithFields(log.Fields{ 141 | "fd": fd, 142 | "pid": req.Pid, 143 | "source": source, 144 | "dest": dest, 145 | "filesystem": filesystem, 146 | "flags": flags, 147 | "options": options, 148 | }).Trace("Handle mount") 149 | } 150 | 151 | if requireUserNamespaceAdmin { 152 | ok, err := userns.IsPIDAdminCapable(req.Pid) 153 | if err != nil { 154 | log.WithFields(log.Fields{ 155 | "fd": fd, 156 | "pid": req.Pid, 157 | "err": err, 158 | }).Error("Cannot check user namespace capabilities") 159 | return registry.HandlerResultErrno(unix.EFAULT) 160 | } 161 | if !ok { 162 | log.WithFields(log.Fields{ 163 | "fd": fd, 164 | "pid": req.Pid, 165 | }).Info("Mount attempted without CAP_SYS_ADMIN") 166 | return registry.HandlerResultErrno(unix.EPERM) 167 | } 168 | 169 | // Ensure the notification is still valid after checking user namespace capabilities. 170 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 171 | log.WithFields(log.Fields{ 172 | "fd": fd, 173 | "req": req, 174 | "err": err, 175 | }).Debug("Notification no longer valid") 176 | return registry.HandlerResultIntr() 177 | } 178 | } 179 | 180 | params := mountModuleParams{ 181 | Module: "mount", 182 | Source: source, 183 | Dest: dest, 184 | Filesystem: filesystem, 185 | Options: options, 186 | } 187 | 188 | mntns, err := nsenter.OpenNamespace(req.Pid, "mnt") 189 | if err != nil { 190 | log.WithFields(log.Fields{ 191 | "fd": fd, 192 | "pid": req.Pid, 193 | "kind": "mnt", 194 | "err": err, 195 | }).Error("Cannot open namespace") 196 | return registry.HandlerResultErrno(unix.EPERM) 197 | } 198 | defer mntns.Close() 199 | 200 | netns, err := nsenter.OpenNamespace(req.Pid, "net") 201 | if err != nil { 202 | log.WithFields(log.Fields{ 203 | "fd": fd, 204 | "pid": req.Pid, 205 | "kind": "net", 206 | "err": err, 207 | }).Error("Cannot open namespace") 208 | return registry.HandlerResultErrno(unix.EPERM) 209 | } 210 | defer netns.Close() 211 | 212 | pidns, err := nsenter.OpenNamespace(req.Pid, "pid") 213 | if err != nil { 214 | log.WithFields(log.Fields{ 215 | "fd": fd, 216 | "pid": req.Pid, 217 | "kind": "pid", 218 | "err": err, 219 | }).Error("Cannot open namespace") 220 | return registry.HandlerResultErrno(unix.EPERM) 221 | } 222 | defer pidns.Close() 223 | 224 | root, err := nsenter.OpenRoot(req.Pid) 225 | if err != nil { 226 | log.WithFields(log.Fields{ 227 | "fd": fd, 228 | "pid": req.Pid, 229 | "err": err, 230 | }).Error("Cannot open root") 231 | return registry.HandlerResultErrno(unix.EPERM) 232 | } 233 | defer root.Close() 234 | 235 | cwd, err := nsenter.OpenCwd(req.Pid) 236 | if err != nil { 237 | log.WithFields(log.Fields{ 238 | "fd": fd, 239 | "pid": req.Pid, 240 | "err": err, 241 | }).Error("Cannot open cwd") 242 | return registry.HandlerResultErrno(unix.EPERM) 243 | } 244 | defer cwd.Close() 245 | 246 | if err := libseccomp.NotifIDValid(fd, req.ID); err != nil { 247 | log.WithFields(log.Fields{ 248 | "fd": fd, 249 | "req": req, 250 | "err": err, 251 | }).Debug("Notification no longer valid") 252 | return registry.HandlerResultIntr() 253 | } 254 | 255 | output, err := nsenter.Run(root, cwd, mntns, netns, pidns, params) 256 | if err != nil { 257 | log.WithFields(log.Fields{ 258 | "fd": fd, 259 | "pid": req.Pid, 260 | "output": output, 261 | "err": err, 262 | }).Error("Run in target namespaces failed") 263 | return registry.HandlerResultErrno(unix.ENOSYS) 264 | } 265 | errno, err := strconv.Atoi(string(output)) 266 | if err != nil { 267 | log.WithFields(log.Fields{ 268 | "fd": fd, 269 | "pid": req.Pid, 270 | "output": output, 271 | "err": err, 272 | }).Error("Cannot parse return value") 273 | return registry.HandlerResultErrno(unix.ENOSYS) 274 | } 275 | if errno != 0 { 276 | return registry.HandlerResultErrno(unix.Errno(errno)) 277 | } 278 | 279 | return registry.HandlerResultSuccess() 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /pkg/handlers/prometheus/promtheus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 G-Research 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package prometheus 16 | 17 | import ( 18 | "strconv" 19 | "time" 20 | 21 | libseccomp "github.com/seccomp/libseccomp-golang" 22 | log "github.com/sirupsen/logrus" 23 | 24 | "github.com/kinvolk/seccompagent/pkg/kuberesolver" 25 | "github.com/kinvolk/seccompagent/pkg/registry" 26 | 27 | "github.com/prometheus/client_golang/prometheus" 28 | ) 29 | 30 | var requestsHistogram = prometheus.NewHistogramVec( 31 | prometheus.HistogramOpts{ 32 | Namespace: "seccompagent", 33 | Name: "request_duration_seconds", 34 | Help: "Histogram of latencies for agent requests.", 35 | Buckets: []float64{.05, .2, 1}, 36 | }, 37 | // We don't include pod to avoid high cardinality in the labels. 38 | []string{"namespace", "syscall", "status"}) 39 | 40 | func init() { 41 | prometheus.Register(requestsHistogram) 42 | } 43 | 44 | func UpdateMetrics(podCtx *kuberesolver.PodContext) func(h registry.HandlerFunc) registry.HandlerFunc { 45 | return func(h registry.HandlerFunc) registry.HandlerFunc { 46 | return func(fd libseccomp.ScmpFd, req *libseccomp.ScmpNotifReq) registry.HandlerResult { 47 | 48 | start := time.Now() 49 | 50 | syscallName, err := req.Data.Syscall.GetName() 51 | if err != nil { 52 | log.WithFields(log.Fields{ 53 | "fd": fd, 54 | "req": req, 55 | "err": err, 56 | }).Error("Error in decoding syscall") 57 | } 58 | 59 | r := h(fd, req) 60 | 61 | elapsed := time.Now().Sub(start) 62 | status := strconv.Itoa(int(r.ErrVal)) 63 | requestsHistogram.With( 64 | prometheus.Labels{ 65 | "namespace": podCtx.Namespace, 66 | "syscall": syscallName, 67 | "status": status}).Observe(float64(elapsed)) 68 | 69 | return r 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/kuberesolver/k8s/k8s.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package k8s 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "errors" 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | 26 | log "github.com/sirupsen/logrus" 27 | "k8s.io/api/core/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/apimachinery/pkg/fields" 30 | "k8s.io/client-go/kubernetes" 31 | "k8s.io/client-go/rest" 32 | ) 33 | 34 | type K8sClient struct { 35 | clientset *kubernetes.Clientset 36 | nodeName string 37 | fieldSelector string 38 | } 39 | 40 | func NewK8sClient(nodeName string) (*K8sClient, error) { 41 | config, err := rest.InClusterConfig() 42 | if err != nil { 43 | return nil, err 44 | } 45 | clientset, err := kubernetes.NewForConfig(config) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | fieldSelector := fields.OneTermEqualSelector("spec.nodeName", nodeName).String() 51 | 52 | return &K8sClient{ 53 | clientset: clientset, 54 | nodeName: nodeName, 55 | fieldSelector: fieldSelector, 56 | }, nil 57 | } 58 | 59 | func (k *K8sClient) ContainerLookup(pid int) (*v1.Pod, error) { 60 | cgroupPathV1, cgroupPathV2, err := getCgroupPaths(pid) 61 | 62 | pods, err := k.clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{ 63 | FieldSelector: k.fieldSelector, 64 | }) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | // Unfortunately, we cannot check pod.Status.ContainerStatuses because 70 | // it might not be set yet at this stage. We try to match the pod uid 71 | // with the cgroup instead. 72 | 73 | for _, pod := range pods.Items { 74 | uid := string(pod.ObjectMeta.UID) 75 | // check if this container is associated to this pod 76 | uidWithUnderscores := strings.ReplaceAll(uid, "-", "_") 77 | 78 | if !strings.Contains(cgroupPathV2, uidWithUnderscores) && 79 | !strings.Contains(cgroupPathV2, uid) && 80 | !strings.Contains(cgroupPathV1, uidWithUnderscores) && 81 | !strings.Contains(cgroupPathV1, uid) { 82 | continue 83 | } 84 | 85 | log.WithFields(log.Fields{ 86 | "pid": pid, 87 | "cgroupPathV1": cgroupPathV1, 88 | "cgroupPathV2": cgroupPathV2, 89 | "namespace": pod.ObjectMeta.Namespace, 90 | "pod": pod.ObjectMeta.Name, 91 | }).Trace("Found pod from pid") 92 | 93 | return &pod, nil 94 | } 95 | return nil, errors.New("Container ID not found") 96 | } 97 | 98 | // getCgroupPaths returns the cgroup1 and cgroup2 paths of a process. 99 | // It does not include the "/sys/fs/cgroup/{unified,systemd,}" prefix. 100 | func getCgroupPaths(pid int) (string, string, error) { 101 | cgroupPathV1 := "" 102 | cgroupPathV2 := "" 103 | if cgroupFile, err := os.Open(filepath.Join("/proc", fmt.Sprintf("%d", pid), "cgroup")); err == nil { 104 | defer cgroupFile.Close() 105 | reader := bufio.NewReader(cgroupFile) 106 | for { 107 | line, err := reader.ReadString('\n') 108 | if err != nil { 109 | break 110 | } 111 | if strings.HasPrefix(line, "1:name=systemd:") { 112 | cgroupPathV1 = strings.TrimPrefix(line, "1:name=systemd:") 113 | cgroupPathV1 = strings.TrimSuffix(cgroupPathV1, "\n") 114 | continue 115 | } 116 | if strings.HasPrefix(line, "0::") { 117 | cgroupPathV2 = strings.TrimPrefix(line, "0::") 118 | cgroupPathV2 = strings.TrimSuffix(cgroupPathV2, "\n") 119 | continue 120 | } 121 | } 122 | } else { 123 | return "", "", fmt.Errorf("cannot parse cgroup: %v", err) 124 | } 125 | 126 | if cgroupPathV1 == "/" { 127 | cgroupPathV1 = "" 128 | } 129 | 130 | if cgroupPathV2 == "/" { 131 | cgroupPathV2 = "" 132 | } 133 | 134 | if cgroupPathV2 == "" && cgroupPathV1 == "" { 135 | return "", "", fmt.Errorf("cannot find cgroup path in /proc/PID/cgroup") 136 | } 137 | 138 | return cgroupPathV1, cgroupPathV2, nil 139 | } 140 | -------------------------------------------------------------------------------- /pkg/kuberesolver/kuberesolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kuberesolver 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | "strings" 21 | 22 | "github.com/kinvolk/seccompagent/pkg/kuberesolver/k8s" 23 | "github.com/kinvolk/seccompagent/pkg/registry" 24 | 25 | "github.com/opencontainers/runtime-spec/specs-go" 26 | log "github.com/sirupsen/logrus" 27 | 28 | ociannotations "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/oci-annotations" 29 | ) 30 | 31 | type PodContext struct { 32 | // Namespace is the Kubernetes namespace of the pod 33 | Namespace string 34 | 35 | // Pod is the name of the Kubernetes pod 36 | Pod string 37 | 38 | // Container is the name of the container in the Kubernetes pod 39 | Container string 40 | 41 | // Pid is the process is that is traced by the seccomp filter 42 | Pid int 43 | 44 | // Pid1 is the first process in the container 45 | Pid1 int 46 | } 47 | 48 | type KubeResolverFunc func(pod *PodContext, metadata map[string]string) *registry.Registry 49 | 50 | func parseKV(metadata string) map[string]string { 51 | vars := map[string]string{} 52 | varsArr := strings.Split(metadata, "\n") 53 | for _, line := range varsArr { 54 | idx := strings.Index(line, "=") 55 | switch idx { 56 | case -1: 57 | vars[line] = "" 58 | case 0: 59 | // skip 60 | default: 61 | k := line[0:idx] 62 | v := line[idx+1:] 63 | vars[k] = v 64 | } 65 | } 66 | return vars 67 | } 68 | 69 | func readAnnotations(ann map[string]string) (podCtx *PodContext) { 70 | podCtx = &PodContext{} 71 | if ann == nil { 72 | return 73 | } 74 | 75 | annResolver, err := ociannotations.NewResolverFromAnnotations(ann) 76 | if err != nil { 77 | return 78 | } 79 | if val := annResolver.PodNamespace(ann); val != "" { 80 | podCtx.Namespace = val 81 | } 82 | if val := annResolver.PodName(ann); val != "" { 83 | podCtx.Pod = val 84 | } 85 | if val := annResolver.ContainerName(ann); val != "" { 86 | podCtx.Container = val 87 | } 88 | return 89 | } 90 | 91 | func KubeResolver(f KubeResolverFunc) (registry.ResolverFunc, error) { 92 | nodeName := os.Getenv("NODE_NAME") 93 | k8sClient, err := k8s.NewK8sClient(nodeName) 94 | if err != nil { 95 | return nil, errors.New("cannot create kubernetes client") 96 | } 97 | 98 | return func(state *specs.ContainerProcessState) *registry.Registry { 99 | vars := parseKV(state.Metadata) 100 | 101 | podCtx := readAnnotations(state.State.Annotations) 102 | 103 | podCtx.Pid = state.Pid 104 | podCtx.Pid1 = state.State.Pid 105 | 106 | if podCtx.Pod != "" && podCtx.Namespace != "" { 107 | log.WithFields(log.Fields{ 108 | "namespace": podCtx.Namespace, 109 | "pod": podCtx.Pod, 110 | "container": podCtx.Container, 111 | "err": err, 112 | }).Trace("Pod details found from annotations") 113 | return f(podCtx, vars) 114 | } 115 | 116 | pod, err := k8sClient.ContainerLookup(state.State.Pid) 117 | if err != nil { 118 | log.WithFields(log.Fields{ 119 | "pid": state.State.Pid, 120 | "err": err, 121 | }).Error("Cannot find container in Kubernetes") 122 | return f(podCtx, vars) 123 | } 124 | podCtx.Namespace = pod.ObjectMeta.Namespace 125 | podCtx.Pod = pod.ObjectMeta.Name 126 | 127 | return f(podCtx, vars) 128 | }, nil 129 | } 130 | -------------------------------------------------------------------------------- /pkg/nsenter/README.md: -------------------------------------------------------------------------------- 1 | ## nsenter 2 | 3 | ### Credits 4 | 5 | This package was taken from runc and adapted for the needs of the seccomp agent. 6 | 7 | Source: https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/README.md 8 | 9 | ### How does it work? 10 | 11 | The `nsenter` package registers a special init constructor that is called before 12 | the Go runtime has a chance to boot. This provides us the ability to `setns` on 13 | existing namespaces and avoid the issues that the Go runtime has with multiple 14 | threads. This constructor will be called if this package is registered, 15 | imported, in your go application. 16 | 17 | The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/) 18 | package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, 19 | called the preamble, is used as a header when compiling the C parts of the package. 20 | So every time we import package `nsenter`, the C code function `nsexec()` would be 21 | called. 22 | 23 | Because `nsexec()` must be run before the Go runtime in order to use the 24 | Linux kernel namespace, you must `import` this library into a package if 25 | you plan to use `libcontainer` directly. Otherwise Go will not execute 26 | the `nsexec()` constructor, which means that the re-exec will not cause 27 | the namespaces to be joined. 28 | -------------------------------------------------------------------------------- /pkg/nsenter/nsenter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 The runc authors 2 | // Copyright 2020-2021 Kinvolk 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // +build linux,!gccgo 17 | 18 | package nsenter 19 | 20 | /* 21 | #cgo CFLAGS: -Wall 22 | extern void nsexec(); 23 | void __attribute__((constructor)) init(void) { 24 | nsexec(); 25 | } 26 | */ 27 | import "C" 28 | -------------------------------------------------------------------------------- /pkg/nsenter/nsenter_gccgo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 The runc authors 2 | // Copyright 2020-2021 Kinvolk 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // +build linux,gccgo 17 | 18 | package nsenter 19 | 20 | /* 21 | #cgo CFLAGS: -Wall 22 | extern void nsexec(); 23 | void __attribute__((constructor)) init(void) { 24 | nsexec(); 25 | } 26 | */ 27 | import "C" 28 | 29 | // AlwaysFalse is here to stay false 30 | // (and be exported so the compiler doesn't optimize out its reference) 31 | var AlwaysFalse bool 32 | 33 | func init() { 34 | if AlwaysFalse { 35 | // by referencing this C init() in a noop test, it will ensure the compiler 36 | // links in the C function. 37 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65134 38 | C.init() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/nsenter/nsenter_unsupported.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 The runc authors 2 | // Copyright 2020-2021 Kinvolk 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // +build !linux !cgo 17 | 18 | package nsenter 19 | -------------------------------------------------------------------------------- /pkg/nsenter/nsexec.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define PANIC "panic" 29 | #define FATAL "fatal" 30 | #define ERROR "error" 31 | #define WARNING "warning" 32 | #define INFO "info" 33 | #define DEBUG "debug" 34 | 35 | /* 36 | * Use the raw syscall for versions of glibc which don't include a function for 37 | * it, namely (glibc 2.12). 38 | */ 39 | #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14 40 | # define _GNU_SOURCE 41 | # include "syscall.h" 42 | # if !defined(SYS_setns) && defined(__NR_setns) 43 | # define SYS_setns __NR_setns 44 | # endif 45 | 46 | #ifndef SYS_setns 47 | # error "setns(2) syscall not supported by glibc version" 48 | #endif 49 | 50 | int setns(int fd, int nstype) 51 | { 52 | return syscall(SYS_setns, fd, nstype); 53 | } 54 | #endif 55 | 56 | static void write_log_with_info(const char *level, const char *function, int line, const char *format, ...) 57 | { 58 | char message[1024] = {}; 59 | 60 | va_list args; 61 | 62 | if (level == NULL) 63 | return; 64 | 65 | va_start(args, format); 66 | if (vsnprintf(message, sizeof(message), format, args) < 0) 67 | goto done; 68 | 69 | printf("{\"level\":\"%s\", \"msg\": \"%s:%d %s\"}\n", level, function, line, message); 70 | fflush(stdout); 71 | done: 72 | va_end(args); 73 | } 74 | 75 | #define write_log(level, fmt, ...) \ 76 | write_log_with_info((level), __FUNCTION__, __LINE__, (fmt), ##__VA_ARGS__) 77 | 78 | #define bail(fmt, ...) \ 79 | do { \ 80 | write_log(FATAL, "nsenter: " fmt ": %m", ##__VA_ARGS__); \ 81 | exit(1); \ 82 | } while(0) 83 | 84 | void join_ns(char *fdstr, int nstype) 85 | { 86 | int fd = atoi(fdstr); 87 | 88 | if (setns(fd, nstype) < 0) 89 | bail("failed to setns to fd %s", fdstr); 90 | 91 | close(fd); 92 | } 93 | 94 | void nsexec(void) 95 | { 96 | char *in_init = getenv("_LIBNSENTER_INIT"); 97 | if (in_init == NULL || *in_init == '\0') 98 | return; 99 | 100 | write_log(DEBUG, "nsexec started"); 101 | 102 | /* 103 | * Make the process non-dumpable, to avoid various race conditions that 104 | * could cause processes in namespaces we're joining to access host 105 | * resources (or potentially execute code). 106 | */ 107 | if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0) 108 | bail("failed to set process as non-dumpable"); 109 | 110 | /* For debugging. */ 111 | prctl(PR_SET_NAME, (unsigned long)"seccompagent:[CHILD]", 0, 0, 0); 112 | 113 | char *mntnsfd = getenv("_LIBNSENTER_MNTNSFD"); 114 | if (mntnsfd != NULL) { 115 | write_log(DEBUG, "join mnt namespace: %s", mntnsfd); 116 | join_ns(mntnsfd, CLONE_NEWNS); 117 | } 118 | 119 | char *rootfd = getenv("_LIBNSENTER_ROOTFD"); 120 | if (rootfd != NULL) { 121 | write_log(DEBUG, "chroot: %s", rootfd); 122 | fchdir(atoi(rootfd)); 123 | chroot("."); 124 | } 125 | char *cwdfd = getenv("_LIBNSENTER_CWDFD"); 126 | if (cwdfd != NULL) { 127 | write_log(DEBUG, "chcwd: %s", cwdfd); 128 | fchdir(atoi(cwdfd)); 129 | } 130 | 131 | char *netnsfd = getenv("_LIBNSENTER_NETNSFD"); 132 | if (netnsfd != NULL) { 133 | write_log(DEBUG, "join net namespace: %s", netnsfd); 134 | join_ns(netnsfd, CLONE_NEWNET); 135 | } 136 | 137 | char *pidnsfd = getenv("_LIBNSENTER_PIDNSFD"); 138 | if (pidnsfd != NULL) { 139 | write_log(DEBUG, "join pid namespace: %s", pidnsfd); 140 | join_ns(pidnsfd, CLONE_NEWPID); 141 | } 142 | 143 | pid_t pid = fork(); 144 | if (pid == -1) { 145 | bail("failed to fork"); 146 | } 147 | if (pid == 0) { 148 | /* child process*/ 149 | /* Finish executing, let the Go runtime take over. */ 150 | write(1, "", 1); // write NULL byte 151 | return; 152 | } 153 | 154 | int wstatus; 155 | if (wait(&wstatus) < 0) 156 | bail("failed to wait for child process"); 157 | 158 | if (WIFEXITED(wstatus)) { 159 | exit(WEXITSTATUS(wstatus)); 160 | } else { 161 | exit(1); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /pkg/nsenter/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2021 The runc authors 2 | // Copyright 2020-2021 Kinvolk 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // +build linux 17 | 18 | package nsenter 19 | 20 | import ( 21 | "bytes" 22 | "encoding/base64" 23 | "encoding/json" 24 | "fmt" 25 | "os" 26 | "os/exec" 27 | "strconv" 28 | 29 | "golang.org/x/sys/unix" 30 | ) 31 | 32 | type ModuleXXX interface { 33 | Run([]byte) 34 | } 35 | 36 | type RunFunc func([]byte) string 37 | 38 | var modules map[string]RunFunc 39 | 40 | func RegisterModule(name string, f RunFunc) bool { 41 | if modules == nil { 42 | modules = map[string]RunFunc{} 43 | } 44 | modules[name] = f 45 | return true 46 | } 47 | 48 | // Init checks if the process has re-executed itself and must run a registered 49 | // module. Init() needs to be called explicitely from main() to ensure it is 50 | // called after all other init() functions. 51 | func Init() { 52 | if len(os.Args) < 2 || os.Args[1] != "-init" { 53 | return 54 | } 55 | 56 | defer os.Exit(0) 57 | 58 | str := os.Getenv("_LIBNSENTER_COMMAND") 59 | if str == "" { 60 | fmt.Printf("Invalid call to init\n") 61 | os.Exit(1) 62 | } 63 | jsonBlob, err := base64.StdEncoding.DecodeString(str) 64 | if err != nil { 65 | fmt.Println("error:", err) 66 | os.Exit(1) 67 | } 68 | 69 | type module struct { 70 | Module string `json:"module,omitempty"` 71 | } 72 | 73 | var m module 74 | err = json.Unmarshal(jsonBlob, &m) 75 | if err != nil { 76 | fmt.Println("error:", err) 77 | os.Exit(1) 78 | } 79 | 80 | f, ok := modules[m.Module] 81 | if !ok { 82 | fmt.Printf("error: module %q not registered\n", m.Module) 83 | os.Exit(1) 84 | } 85 | output := f(jsonBlob) 86 | fmt.Printf("%s", output) 87 | } 88 | 89 | // OpenNamespaces opens a namespace file. It is done separately to Run() so 90 | // that the caller can call libseccomp.NotifIDValid() in between. 91 | func OpenNamespace(pid uint32, nstype string) (*os.File, error) { 92 | nspath := fmt.Sprintf("/proc/%d/ns/%s", pid, nstype) 93 | return os.OpenFile(nspath, os.O_RDONLY, 0) 94 | } 95 | 96 | // OpenRoot opens a /proc/pid/root file. It is done separately to Run() so 97 | // that the caller can call libseccomp.NotifIDValid() in between. 98 | func OpenRoot(pid uint32) (*os.File, error) { 99 | path := fmt.Sprintf("/proc/%d/root", pid) 100 | return os.OpenFile(path, unix.O_PATH, 0) 101 | } 102 | 103 | // OpenCwd opens a /proc/pid/cwd file. It is done separately to Run() so 104 | // that the caller can call libseccomp.NotifIDValid() in between. 105 | func OpenCwd(pid uint32) (*os.File, error) { 106 | path := fmt.Sprintf("/proc/%d/cwd", pid) 107 | return os.OpenFile(path, unix.O_PATH, 0) 108 | } 109 | 110 | // Run executes a module in other namespaces 111 | func Run(root, cwd, mntns, netns, pidns *os.File, i interface{}) ([]byte, error) { 112 | b, err := json.Marshal(i) 113 | if err != nil { 114 | return nil, fmt.Errorf("Unable to encode interface: %s", err) 115 | } 116 | 117 | stdioFdCount := 3 118 | cmd := exec.Command("/proc/self/exe", "-init") 119 | cmd.Env = append(cmd.Env, "_LIBNSENTER_INIT=1") 120 | 121 | if root != nil { 122 | cmd.ExtraFiles = append(cmd.ExtraFiles, root) 123 | cmd.Env = append(cmd.Env, "_LIBNSENTER_ROOTFD="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1)) 124 | } 125 | if cwd != nil { 126 | cmd.ExtraFiles = append(cmd.ExtraFiles, cwd) 127 | cmd.Env = append(cmd.Env, "_LIBNSENTER_CWDFD="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1)) 128 | } 129 | if mntns != nil { 130 | cmd.ExtraFiles = append(cmd.ExtraFiles, mntns) 131 | cmd.Env = append(cmd.Env, "_LIBNSENTER_MNTNSFD="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1)) 132 | } 133 | if netns != nil { 134 | cmd.ExtraFiles = append(cmd.ExtraFiles, netns) 135 | cmd.Env = append(cmd.Env, "_LIBNSENTER_NETNSFD="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1)) 136 | } 137 | if pidns != nil { 138 | cmd.ExtraFiles = append(cmd.ExtraFiles, pidns) 139 | cmd.Env = append(cmd.Env, "_LIBNSENTER_PIDNSFD="+strconv.Itoa(stdioFdCount+len(cmd.ExtraFiles)-1)) 140 | } 141 | 142 | cmd.Env = append(cmd.Env, "_LIBNSENTER_COMMAND="+base64.StdEncoding.EncodeToString(b)) 143 | 144 | stdoutStderr, err := cmd.CombinedOutput() 145 | if err != nil { 146 | return nil, fmt.Errorf("Unable to start the init command: %s\n%s\n", err, stdoutStderr) 147 | } 148 | idx := bytes.Index(stdoutStderr, []byte{0}) 149 | if idx == -1 { 150 | return stdoutStderr, nil 151 | } else { 152 | return stdoutStderr[idx+1:], nil 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pkg/readarg/readarg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package readarg 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "os" 22 | 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | // OpenMem opens the memory file for the target process. It is done separately, 27 | // so that the caller can call libseccomp.NotifIDValid() in between. 28 | func OpenMem(pid uint32) (*os.File, error) { 29 | if pid == 0 { 30 | // This can happen if the seccomp agent is in a pid namespace 31 | // where the target pid is not mapped. 32 | return nil, errors.New("unknown pid") 33 | } 34 | return os.OpenFile(fmt.Sprintf("/proc/%d/mem", pid), os.O_RDONLY, 0) 35 | } 36 | 37 | func ReadString(memFile *os.File, offset int64) (string, error) { 38 | var buffer = make([]byte, 4096) // PATH_MAX 39 | 40 | _, err := unix.Pread(int(memFile.Fd()), buffer, offset) 41 | if err != nil { 42 | return "", err 43 | } 44 | 45 | // pread() will always return the size of the buffer, as usually 46 | // /proc/pid/mem is bigger than the buffer. 47 | // Then, to know when the string we are looking for finishes, we look 48 | // for the first \0 (with :bytes.IndexByte(buffer, 0)). As the string 49 | // should be nul-terminated, this is a simple way to find it. 50 | // Also, as a safety check, we add a ending 0 in the buffer, to avoid 51 | // doing buffer[-1] and panic, if the buffer doesn't contain any 0. 52 | buffer[len(buffer)-1] = 0 53 | s := buffer[:bytes.IndexByte(buffer, 0)] 54 | return string(s), nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/registry/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Kinvolk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package registry 16 | 17 | import ( 18 | specs "github.com/opencontainers/runtime-spec/specs-go" 19 | libseccomp "github.com/seccomp/libseccomp-golang" 20 | "golang.org/x/sys/unix" 21 | ) 22 | 23 | type HandlerResult struct { 24 | Intr bool 25 | ErrVal int32 26 | Val uint64 27 | Flags uint32 28 | } 29 | 30 | type HandlerFunc func(libseccomp.ScmpFd, *libseccomp.ScmpNotifReq) HandlerResult 31 | 32 | // Helper functions for handlers 33 | func HandlerResultIntr() HandlerResult { 34 | return HandlerResult{Intr: true} 35 | } 36 | func HandlerResultContinue() HandlerResult { 37 | return HandlerResult{Flags: libseccomp.NotifRespFlagContinue} 38 | } 39 | 40 | func HandlerResultErrno(err error) HandlerResult { 41 | if err == nil { 42 | return HandlerResult{} 43 | } 44 | errno, ok := err.(unix.Errno) 45 | if !ok { 46 | return HandlerResult{ErrVal: int32(unix.ENOSYS), Val: ^uint64(0)} 47 | } 48 | if errno == 0 { 49 | return HandlerResult{} 50 | } 51 | return HandlerResult{ErrVal: int32(errno), Val: ^uint64(0)} 52 | } 53 | func HandlerResultSuccess() HandlerResult { 54 | return HandlerResult{} 55 | } 56 | 57 | // Registry 58 | 59 | type Registry struct { 60 | SyscallHandler map[string]HandlerFunc 61 | DefaultHandler HandlerFunc 62 | MiddlewareHandlers []func(HandlerFunc) HandlerFunc 63 | } 64 | 65 | type ResolverFunc func(state *specs.ContainerProcessState) *Registry 66 | 67 | func New() *Registry { 68 | return &Registry{ 69 | SyscallHandler: map[string]HandlerFunc{}, 70 | } 71 | } 72 | 73 | func (r *Registry) Lookup(name string) HandlerFunc { 74 | f, ok := r.SyscallHandler[name] 75 | if !ok { 76 | f = r.DefaultHandler 77 | } 78 | for _, m := range r.MiddlewareHandlers { 79 | f = m(f) 80 | } 81 | return f 82 | } 83 | -------------------------------------------------------------------------------- /pkg/userns/check.go: -------------------------------------------------------------------------------- 1 | package userns 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/sys/unix" 7 | "kernel.org/pub/linux/libs/security/libcap/cap" 8 | ) 9 | 10 | // IsPIDAdminCapable returns true if the PID is considered an admin of a user 11 | // namespace, that is, it's in either in the init user namespace or one created 12 | // by the host root and has CAP_SYS_ADMIN. The protects against a less 13 | // privileged user either mounting a directory over a tree that gives them more 14 | // access (e.g. /etc/sudoers.d) or hiding files. 15 | func IsPIDAdminCapable(pid uint32) (bool, error) { 16 | // We unfortunately need to reimplement some of the kernel's user namespace logic. 17 | // Our goal is to allow a user with CAP_SYS_ADMIN inside the first user 18 | // namespace to call mount(). If the user nests a user namespace below that, 19 | // we don't want to allow that process to call mount. 20 | 21 | // This is security sensitive code, however TOCTOU isn't a concern in this case 22 | // as this is designed to be used while blocked on a syscall and the kernel 23 | // does not let multi-threaded processes change their user namespace (see 24 | // setns() and unshare() docs). 25 | fd, err := unix.Open(fmt.Sprintf("/proc/%d/ns/user", pid), unix.O_RDONLY, 0) 26 | if err != nil { 27 | return false, err 28 | } 29 | defer unix.Close(fd) 30 | 31 | uid, err := unix.IoctlGetInt(fd, unix.NS_GET_OWNER_UID) 32 | if err != nil { 33 | return false, err 34 | } 35 | if uid != 0 { 36 | return false, err 37 | } 38 | set, err := cap.GetPID(int(pid)) 39 | if err != nil { 40 | return false, err 41 | } 42 | 43 | return set.GetFlag(cap.Effective, cap.SYS_ADMIN) 44 | } 45 | -------------------------------------------------------------------------------- /tools/image-tag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Generate container tag names 4 | # From https://github.com/weaveworks/scope/blob/master/tools/image-tag 5 | 6 | set -o errexit 7 | set -o pipefail 8 | 9 | # If there is an explicit image tag set, just return it 10 | if [ -n "$IMAGE_TAG" ]; then 11 | echo $IMAGE_TAG 12 | exit 0 13 | fi 14 | 15 | WORKING_SUFFIX=$(if git status --porcelain | grep -qE '^(?:[^?][^ ]|[^ ][^?])\s'; then echo "-WIP"; else echo ""; fi) 16 | BRANCH_PREFIX=$(git rev-parse --abbrev-ref HEAD) 17 | if [ "$1" = "branch" ] ; then 18 | if [ "$BRANCH_PREFIX" = "master" ] || [ "$BRANCH_PREFIX" = "main" ] ; then 19 | BRANCH_PREFIX="latest" 20 | fi 21 | echo "${BRANCH_PREFIX//\//-}$WORKING_SUFFIX" 22 | else 23 | echo "${BRANCH_PREFIX//\//-}-$(git rev-parse --short HEAD)$WORKING_SUFFIX" 24 | fi 25 | --------------------------------------------------------------------------------