mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-09 13:55:51 +02:00
Move and fix GetContainerRuntime check from bpfd proc (#1996)
* Moved genuinetools/bpfd proc * Removed unused code from util/proc, added genuinetools/bpfd proc test, fixed naming * refactor: adjust bpfd container runtime detection * Moved util/proc.go to util/proc/proc.go, added boilerplate Co-authored-by: ejose19 <8742215+ejose19@users.noreply.github.com>
This commit is contained in:
@@ -32,8 +32,8 @@ import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/logging"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util/proc"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/genuinetools/bpfd/proc"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
@@ -18,7 +18,6 @@ require (
|
||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21
|
||||
github.com/containerd/cgroups v1.0.3 // indirect
|
||||
github.com/docker/docker v20.10.13+incompatible
|
||||
github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8
|
||||
github.com/go-git/go-billy/v5 v5.3.1
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/godbus/dbus/v5 v5.0.6 // indirect
|
||||
|
||||
@@ -653,9 +653,6 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt
|
||||
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8 h1:4gvqvgeXA3WeVPFfudNMwxdSLT2o7ZOQQUFPeEa86aM=
|
||||
github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8/go.mod h1:O3IeOSRt7TXBSKb3hmOpQdlusALsEbNF+niGBqgKCs0=
|
||||
github.com/genuinetools/pkg v0.0.0-20181004225747-e152a0f47ee4/go.mod h1:XTcrCYlXPxnxL2UpnwuRn7tcaTn9HAhxFoFJucootk8=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
Copyright 2022 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Ported from https://github.com/genuinetools/bpfd/blob/a4bfa5e3e9d1bfdbc56268a36a0714911ae9b6bf/proc/proc.go
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContainerRuntime is the type for the various container runtime strings.
|
||||
type ContainerRuntime string
|
||||
|
||||
const (
|
||||
// RuntimeDocker is the string for the docker runtime.
|
||||
RuntimeDocker ContainerRuntime = "docker"
|
||||
// RuntimeRkt is the string for the rkt runtime.
|
||||
RuntimeRkt ContainerRuntime = "rkt"
|
||||
// RuntimeNspawn is the string for the systemd-nspawn runtime.
|
||||
RuntimeNspawn ContainerRuntime = "systemd-nspawn"
|
||||
// RuntimeLXC is the string for the lxc runtime.
|
||||
RuntimeLXC ContainerRuntime = "lxc"
|
||||
// RuntimeLXCLibvirt is the string for the lxc-libvirt runtime.
|
||||
RuntimeLXCLibvirt ContainerRuntime = "lxc-libvirt"
|
||||
// RuntimeOpenVZ is the string for the openvz runtime.
|
||||
RuntimeOpenVZ ContainerRuntime = "openvz"
|
||||
// RuntimeKubernetes is the string for the kubernetes runtime.
|
||||
RuntimeKubernetes ContainerRuntime = "kube"
|
||||
// RuntimeGarden is the string for the garden runtime.
|
||||
RuntimeGarden ContainerRuntime = "garden"
|
||||
// RuntimePodman is the string for the podman runtime.
|
||||
RuntimePodman ContainerRuntime = "podman"
|
||||
// RuntimeGVisor is the string for the gVisor (runsc) runtime.
|
||||
RuntimeGVisor ContainerRuntime = "gvisor"
|
||||
// RuntimeFirejail is the string for the firejail runtime.
|
||||
RuntimeFirejail ContainerRuntime = "firejail"
|
||||
// RuntimeWSL is the string for the Windows Subsystem for Linux runtime.
|
||||
RuntimeWSL ContainerRuntime = "wsl"
|
||||
// RuntimeNotFound is the string for when no container runtime is found.
|
||||
RuntimeNotFound ContainerRuntime = "not-found"
|
||||
)
|
||||
|
||||
var (
|
||||
// ContainerRuntimes contains all the container runtimes.
|
||||
ContainerRuntimes = []ContainerRuntime{
|
||||
RuntimeDocker,
|
||||
RuntimeRkt,
|
||||
RuntimeNspawn,
|
||||
RuntimeLXC,
|
||||
RuntimeLXCLibvirt,
|
||||
RuntimeOpenVZ,
|
||||
RuntimeKubernetes,
|
||||
RuntimeGarden,
|
||||
RuntimePodman,
|
||||
RuntimeGVisor,
|
||||
RuntimeFirejail,
|
||||
RuntimeWSL,
|
||||
}
|
||||
)
|
||||
|
||||
// GetContainerRuntime returns the container runtime the process is running in.
|
||||
// If pid is less than one, it returns the runtime for "self".
|
||||
func GetContainerRuntime(tgid, pid int) ContainerRuntime {
|
||||
file := "/proc/self/cgroup"
|
||||
if pid > 0 {
|
||||
if tgid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/task/%d/cgroup", tgid, pid)
|
||||
} else {
|
||||
file = fmt.Sprintf("/proc/%d/cgroup", pid)
|
||||
}
|
||||
}
|
||||
|
||||
// read the cgroups file
|
||||
a := readFileString(file)
|
||||
runtime := getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// /proc/vz exists in container and outside of the container, /proc/bc only outside of the container.
|
||||
if osFileExists("/proc/vz") && !osFileExists("/proc/bc") {
|
||||
return RuntimeOpenVZ
|
||||
}
|
||||
|
||||
// /__runsc_containers__ directory is present in gVisor containers.
|
||||
if osFileExists("/__runsc_containers__") {
|
||||
return RuntimeGVisor
|
||||
}
|
||||
|
||||
// firejail runs with `firejail` as pid 1.
|
||||
// As firejail binary cannot be run with argv[0] != "firejail"
|
||||
// it's okay to rely on cmdline.
|
||||
a = readFileString("/proc/1/cmdline")
|
||||
runtime = getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// WSL has /proc/version_signature starting with "Microsoft".
|
||||
a = readFileString("/proc/version_signature")
|
||||
if strings.HasPrefix(a, "Microsoft") {
|
||||
return RuntimeWSL
|
||||
}
|
||||
|
||||
a = os.Getenv("container")
|
||||
runtime = getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// PID 1 might have dropped this information into a file in /run.
|
||||
// Read from /run/systemd/container since it is better than accessing /proc/1/environ,
|
||||
// which needs CAP_SYS_PTRACE
|
||||
a = readFileString("/run/systemd/container")
|
||||
runtime = getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// Check for container specific files
|
||||
runtime = detectContainerFiles()
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
// Related implementation: https://github.com/systemd/systemd/blob/6604fb0207ee10e8dc05d67f6fe45de0b193b5c4/src/basic/virt.c#L523-L549
|
||||
func detectContainerFiles() ContainerRuntime {
|
||||
files := []struct {
|
||||
runtime ContainerRuntime
|
||||
location string
|
||||
}{
|
||||
// https://github.com/containers/podman/issues/6192
|
||||
// https://github.com/containers/podman/issues/3586#issuecomment-661918679
|
||||
{RuntimePodman, "/run/.containerenv"},
|
||||
// https://github.com/moby/moby/issues/18355
|
||||
{RuntimeDocker, "/.dockerenv"},
|
||||
}
|
||||
|
||||
for i := range files {
|
||||
if osFileExists(files[i].location) {
|
||||
return files[i].runtime
|
||||
}
|
||||
}
|
||||
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
func getContainerRuntime(input string) ContainerRuntime {
|
||||
if len(strings.TrimSpace(input)) < 1 {
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
for _, runtime := range ContainerRuntimes {
|
||||
if strings.Contains(input, string(runtime)) {
|
||||
return runtime
|
||||
}
|
||||
}
|
||||
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
func osFileExists(file string) bool {
|
||||
if _, err := os.Stat(file); !os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readFile(file string) []byte {
|
||||
if !osFileExists(file) {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, _ := ioutil.ReadFile(file)
|
||||
return b
|
||||
}
|
||||
|
||||
func readFileString(file string) string {
|
||||
b := readFile(file)
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
Copyright 2022 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetContainerRuntime(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
expectedRuntime ContainerRuntime
|
||||
input string
|
||||
}{
|
||||
"empty": {
|
||||
expectedRuntime: RuntimeNotFound,
|
||||
},
|
||||
"typical docker": {
|
||||
expectedRuntime: RuntimeDocker,
|
||||
input: `11:pids:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
10:devices:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
9:freezer:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
8:net_cls,net_prio:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
7:perf_event:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
6:cpuset:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
5:memory:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
4:blkio:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
3:cpu,cpuacct:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
2:hugetlb:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
1:name=systemd:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
|
||||
0::/system.slice/containerd.service`,
|
||||
},
|
||||
"uncontainerized process": {
|
||||
expectedRuntime: RuntimeNotFound,
|
||||
input: `11:pids:/system.slice/ssh.service
|
||||
10:devices:/system.slice/ssh.service
|
||||
9:freezer:/
|
||||
8:net_cls,net_prio:/
|
||||
7:perf_event:/
|
||||
6:cpuset:/
|
||||
5:memory:/system.slice/ssh.service
|
||||
4:blkio:/system.slice/ssh.service
|
||||
3:cpu,cpuacct:/system.slice/ssh.service
|
||||
2:hugetlb:/
|
||||
1:name=systemd:/system.slice/ssh.service
|
||||
0::/system.slice/ssh.service`,
|
||||
},
|
||||
"kubernetes": {
|
||||
expectedRuntime: RuntimeKubernetes,
|
||||
input: `12:perf_event:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
11:freezer:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
10:pids:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
9:net_cls,net_prio:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
8:memory:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
7:cpuset:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
6:devices:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
5:blkio:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
4:rdma:/
|
||||
3:hugetlb:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
2:cpu,cpuacct:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
|
||||
1:name=systemd:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47`,
|
||||
},
|
||||
"lxc": {
|
||||
expectedRuntime: RuntimeLXC, // this is usually in $container for lxc
|
||||
input: `10:cpuset:/lxc/debian2
|
||||
9:pids:/lxc/debian2
|
||||
8:devices:/lxc/debian2
|
||||
7:net_cls,net_prio:/lxc/debian2
|
||||
6:freezer:/lxc/debian2
|
||||
5:blkio:/lxc/debian2
|
||||
4:memory:/lxc/debian2
|
||||
3:cpu,cpuacct:/lxc/debian2
|
||||
2:perf_event:/lxc/debian2
|
||||
1:name=systemd:/lxc/debian2`,
|
||||
},
|
||||
"nspawn": {
|
||||
expectedRuntime: RuntimeNotFound, // since this variable is in $container
|
||||
input: `10:cpuset:/
|
||||
9:pids:/machine.slice/machine-nspawntest.scope
|
||||
8:devices:/machine.slice/machine-nspawntest.scope
|
||||
7:net_cls,net_prio:/
|
||||
6:freezer:/user/root/0
|
||||
5:blkio:/machine.slice/machine-nspawntest.scope
|
||||
4:memory:/machine.slice/machine-nspawntest.scope
|
||||
3:cpu,cpuacct:/machine.slice/machine-nspawntest.scope
|
||||
2:perf_event:/
|
||||
1:name=systemd:/machine.slice/machine-nspawntest.scope`,
|
||||
},
|
||||
"rkt": {
|
||||
expectedRuntime: RuntimeRkt,
|
||||
input: `10:cpuset:/
|
||||
9:pids:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
|
||||
8:devices:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
|
||||
7:net_cls,net_prio:/
|
||||
6:freezer:/user/root/0
|
||||
5:blkio:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
|
||||
4:memory:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
|
||||
3:cpu,cpuacct:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
|
||||
2:perf_event:/
|
||||
1:name=systemd:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service`,
|
||||
},
|
||||
"rkt host": {
|
||||
expectedRuntime: RuntimeRkt,
|
||||
input: `10:cpuset:/
|
||||
9:pids:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
|
||||
8:devices:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
|
||||
7:net_cls,net_prio:/
|
||||
6:freezer:/user/root/0
|
||||
5:blkio:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
|
||||
4:memory:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
|
||||
3:cpu,cpuacct:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
|
||||
2:perf_event:/
|
||||
1:name=systemd:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service`,
|
||||
},
|
||||
}
|
||||
|
||||
for key, tc := range testcases {
|
||||
runtime := getContainerRuntime(tc.input)
|
||||
if runtime != tc.expectedRuntime {
|
||||
t.Errorf("[%s]: expected runtime %q, got %q", key, tc.expectedRuntime, runtime)
|
||||
}
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 The Genuinetools Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-557
@@ -1,557 +0,0 @@
|
||||
// Package proc provides tools for inspecting proc.
|
||||
package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// ContainerRuntime is the type for the various container runtime strings.
|
||||
type ContainerRuntime string
|
||||
|
||||
// SeccompMode is the type for the various seccomp mode strings.
|
||||
type SeccompMode string
|
||||
|
||||
const (
|
||||
// RuntimeDocker is the string for the docker runtime.
|
||||
RuntimeDocker ContainerRuntime = "docker"
|
||||
// RuntimeRkt is the string for the rkt runtime.
|
||||
RuntimeRkt ContainerRuntime = "rkt"
|
||||
// RuntimeNspawn is the string for the systemd-nspawn runtime.
|
||||
RuntimeNspawn ContainerRuntime = "systemd-nspawn"
|
||||
// RuntimeLXC is the string for the lxc runtime.
|
||||
RuntimeLXC ContainerRuntime = "lxc"
|
||||
// RuntimeLXCLibvirt is the string for the lxc-libvirt runtime.
|
||||
RuntimeLXCLibvirt ContainerRuntime = "lxc-libvirt"
|
||||
// RuntimeOpenVZ is the string for the openvz runtime.
|
||||
RuntimeOpenVZ ContainerRuntime = "openvz"
|
||||
// RuntimeKubernetes is the string for the kubernetes runtime.
|
||||
RuntimeKubernetes ContainerRuntime = "kube"
|
||||
// RuntimeGarden is the string for the garden runtime.
|
||||
RuntimeGarden ContainerRuntime = "garden"
|
||||
// RuntimePodman is the string for the podman runtime.
|
||||
RuntimePodman ContainerRuntime = "podman"
|
||||
// RuntimeGVisor is the string for the gVisor (runsc) runtime.
|
||||
RuntimeGVisor ContainerRuntime = "gvisor"
|
||||
// RuntimeFirejail is the string for the firejail runtime.
|
||||
RuntimeFirejail ContainerRuntime = "firejail"
|
||||
// RuntimeWSL is the string for the Windows Subsystem for Linux runtime.
|
||||
RuntimeWSL ContainerRuntime = "wsl"
|
||||
// RuntimeNotFound is the string for when no container runtime is found.
|
||||
RuntimeNotFound ContainerRuntime = "not-found"
|
||||
|
||||
// SeccompModeDisabled is equivalent to "0" in the /proc/{pid}/status file.
|
||||
SeccompModeDisabled SeccompMode = "disabled"
|
||||
// SeccompModeStrict is equivalent to "1" in the /proc/{pid}/status file.
|
||||
SeccompModeStrict SeccompMode = "strict"
|
||||
// SeccompModeFiltering is equivalent to "2" in the /proc/{pid}/status file.
|
||||
SeccompModeFiltering SeccompMode = "filtering"
|
||||
|
||||
apparmorUnconfined = "unconfined"
|
||||
|
||||
uint32Max = 4294967295
|
||||
|
||||
cgroupContainerID = ":(/docker/|/kube.*/.*/|/kube.*/.*/.*/.*/|/system.slice/docker-|/machine.slice/machine-rkt-|/machine.slice/machine-|/lxc/|/lxc-libvirt/|/garden/|/podman/)([[:alnum:]\\-]{1,64})(.scope|$)"
|
||||
statusFileValue = ":(.*)"
|
||||
)
|
||||
|
||||
var (
|
||||
// ContainerRuntimes contains all the container runtimes.
|
||||
ContainerRuntimes = []ContainerRuntime{
|
||||
RuntimeDocker,
|
||||
RuntimeRkt,
|
||||
RuntimeNspawn,
|
||||
RuntimeLXC,
|
||||
RuntimeLXCLibvirt,
|
||||
RuntimeOpenVZ,
|
||||
RuntimeKubernetes,
|
||||
RuntimeGarden,
|
||||
RuntimePodman,
|
||||
RuntimeGVisor,
|
||||
RuntimeFirejail,
|
||||
RuntimeWSL,
|
||||
}
|
||||
|
||||
seccompModes = map[string]SeccompMode{
|
||||
"0": SeccompModeDisabled,
|
||||
"1": SeccompModeStrict,
|
||||
"2": SeccompModeFiltering,
|
||||
}
|
||||
|
||||
cgroupContainerIDRegex = regexp.MustCompile(cgroupContainerID)
|
||||
statusFileValueRegex = regexp.MustCompile(statusFileValue)
|
||||
)
|
||||
|
||||
// GetContainerRuntime returns the container runtime the process is running in.
|
||||
// If pid is less than one, it returns the runtime for "self".
|
||||
func GetContainerRuntime(tgid, pid int) ContainerRuntime {
|
||||
file := "/proc/self/cgroup"
|
||||
if pid > 0 {
|
||||
if tgid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/task/%d/cgroup", tgid, pid)
|
||||
} else {
|
||||
file = fmt.Sprintf("/proc/%d/cgroup", pid)
|
||||
}
|
||||
}
|
||||
|
||||
// read the cgroups file
|
||||
a := readFileString(file)
|
||||
runtime := getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// /proc/vz exists in container and outside of the container, /proc/bc only outside of the container.
|
||||
if fileExists("/proc/vz") && !fileExists("/proc/bc") {
|
||||
return RuntimeOpenVZ
|
||||
}
|
||||
|
||||
// /__runsc_containers__ directory is present in gVisor containers.
|
||||
if fileExists("/__runsc_containers__") {
|
||||
return RuntimeGVisor
|
||||
}
|
||||
|
||||
// firejail runs with `firejail` as pid 1.
|
||||
// As firejail binary cannot be run with argv[0] != "firejail"
|
||||
// it's okay to rely on cmdline.
|
||||
a = readFileString("/proc/1/cmdline")
|
||||
runtime = getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// WSL has /proc/version_signature starting with "Microsoft".
|
||||
a = readFileString("/proc/version_signature")
|
||||
if strings.HasPrefix(a, "Microsoft") {
|
||||
return RuntimeWSL
|
||||
}
|
||||
|
||||
a = os.Getenv("container")
|
||||
runtime = getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
// PID 1 might have dropped this information into a file in /run.
|
||||
// Read from /run/systemd/container since it is better than accessing /proc/1/environ,
|
||||
// which needs CAP_SYS_PTRACE
|
||||
a = readFileString("/run/systemd/container")
|
||||
runtime = getContainerRuntime(a)
|
||||
if runtime != RuntimeNotFound {
|
||||
return runtime
|
||||
}
|
||||
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
func getContainerRuntime(input string) ContainerRuntime {
|
||||
if len(strings.TrimSpace(input)) < 1 {
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
for _, runtime := range ContainerRuntimes {
|
||||
if strings.Contains(input, string(runtime)) {
|
||||
return runtime
|
||||
}
|
||||
}
|
||||
|
||||
return RuntimeNotFound
|
||||
}
|
||||
|
||||
// GetContainerID returns the container ID for a process if it's running in a container.
|
||||
// If pid is less than one, it returns the container ID for "self".
|
||||
func GetContainerID(tgid, pid int) string {
|
||||
file := "/proc/self/cgroup"
|
||||
if pid > 0 {
|
||||
if tgid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/task/%d/cgroup", tgid, pid)
|
||||
} else {
|
||||
file = fmt.Sprintf("/proc/%d/cgroup", pid)
|
||||
}
|
||||
}
|
||||
|
||||
return getContainerID(readFileString(file))
|
||||
}
|
||||
|
||||
func getContainerID(input string) string {
|
||||
if len(strings.TrimSpace(input)) < 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// rkt encodes the dashes as ascii, replace them.
|
||||
input = strings.Replace(input, `\x2d`, "-", -1)
|
||||
|
||||
lines := strings.Split(input, "\n")
|
||||
for _, line := range lines {
|
||||
matches := cgroupContainerIDRegex.FindStringSubmatch(line)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetAppArmorProfile determines the AppArmor profile for a process.
|
||||
// If pid is less than one, it returns the AppArmor profile for "self".
|
||||
func GetAppArmorProfile(pid int) string {
|
||||
file := "/proc/self/attr/current"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/attr/current", pid)
|
||||
}
|
||||
|
||||
f := readFileString(file)
|
||||
if f == "" {
|
||||
return apparmorUnconfined
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// UserMapping holds the values for a {uid,gid}_map.
|
||||
type UserMapping struct {
|
||||
ContainerID int64
|
||||
HostID int64
|
||||
Range int64
|
||||
}
|
||||
|
||||
// GetUserNamespaceInfo determines if the process is running in a UserNamespace
|
||||
// and returns the mappings if true.
|
||||
// If pid is less than one, it returns the user namespace info for "self".
|
||||
func GetUserNamespaceInfo(pid int) (bool, []UserMapping) {
|
||||
file := "/proc/self/uid_map"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/uid_map", pid)
|
||||
}
|
||||
|
||||
f := readFileString(file)
|
||||
if len(f) < 0 {
|
||||
// user namespace is uninitialized
|
||||
return true, nil
|
||||
}
|
||||
|
||||
userNs, mappings, err := readUserMappings(f)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return userNs, mappings
|
||||
}
|
||||
|
||||
func readUserMappings(f string) (iuserNS bool, mappings []UserMapping, err error) {
|
||||
parts := strings.Split(f, " ")
|
||||
parts = deleteEmpty(parts)
|
||||
if len(parts) < 3 {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(parts); i += 3 {
|
||||
nsu, hu, r := parts[i], parts[i+1], parts[i+2]
|
||||
mapping := UserMapping{}
|
||||
|
||||
mapping.ContainerID, err = strconv.ParseInt(nsu, 10, 0)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
mapping.HostID, err = strconv.ParseInt(hu, 10, 0)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
mapping.Range, err = strconv.ParseInt(r, 10, 0)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
if mapping.ContainerID == 0 && mapping.HostID == 0 && mapping.Range == uint32Max {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
|
||||
return true, mappings, nil
|
||||
}
|
||||
|
||||
// GetCapabilities returns the allowed capabilities for the process.
|
||||
// If pid is less than one, it returns the capabilities for "self".
|
||||
func GetCapabilities(pid int) (map[string][]string, error) {
|
||||
allCaps := capability.List()
|
||||
|
||||
caps, err := capability.NewPid(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowedCaps := map[string][]string{}
|
||||
allowedCaps["EFFECTIVE | PERMITTED | INHERITABLE"] = []string{}
|
||||
allowedCaps["BOUNDING"] = []string{}
|
||||
allowedCaps["AMBIENT"] = []string{}
|
||||
|
||||
for _, cap := range allCaps {
|
||||
if caps.Get(capability.CAPS, cap) {
|
||||
allowedCaps["EFFECTIVE | PERMITTED | INHERITABLE"] = append(allowedCaps["EFFECTIVE | PERMITTED | INHERITABLE"], cap.String())
|
||||
}
|
||||
if caps.Get(capability.BOUNDING, cap) {
|
||||
allowedCaps["BOUNDING"] = append(allowedCaps["BOUNDING"], cap.String())
|
||||
}
|
||||
if caps.Get(capability.AMBIENT, cap) {
|
||||
allowedCaps["AMBIENT"] = append(allowedCaps["AMBIENT"], cap.String())
|
||||
}
|
||||
}
|
||||
|
||||
return allowedCaps, nil
|
||||
}
|
||||
|
||||
// GetUIDGID returns the uid and gid for a process.
|
||||
// If pid is less than one, it returns the seccomp enforcing mode for "self".
|
||||
func GetUIDGID(tgid, pid int) (uint32, uint32, error) {
|
||||
file := "/proc/self/status"
|
||||
if pid > 0 {
|
||||
if tgid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/task/%d/status", tgid, pid)
|
||||
} else {
|
||||
file = fmt.Sprintf("/proc/%d/status", pid)
|
||||
}
|
||||
}
|
||||
|
||||
return getUIDGID(readFileString(file))
|
||||
}
|
||||
|
||||
func getUIDGID(input string) (uint32, uint32, error) {
|
||||
// Split status file string by line
|
||||
statusMappings := strings.Split(input, "\n")
|
||||
statusMappings = deleteEmpty(statusMappings)
|
||||
|
||||
var uid, gid string
|
||||
for _, line := range statusMappings {
|
||||
if strings.Contains(line, "Uid:") {
|
||||
matches := statusFileValueRegex.FindStringSubmatch(line)
|
||||
if len(matches) > 1 {
|
||||
uid = matches[1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "Gid:") {
|
||||
matches := statusFileValueRegex.FindStringSubmatch(line)
|
||||
if len(matches) > 1 {
|
||||
gid = matches[1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(uid) > 0 && len(gid) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(uid) < 1 && len(gid) < 1 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
u, err := strconv.Atoi(strings.Split(strings.Split(strings.TrimSpace(uid), " ")[0], "\t")[0])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
g, err := strconv.Atoi(strings.Split(strings.Split(strings.TrimSpace(gid), " ")[0], "\t")[0])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return uint32(u), uint32(g), nil
|
||||
}
|
||||
|
||||
// GetSeccompEnforcingMode returns the seccomp enforcing level (disabled, filtering, strict)
|
||||
// for a process.
|
||||
// If pid is less than one, it returns the seccomp enforcing mode for "self".
|
||||
func GetSeccompEnforcingMode(pid int) SeccompMode {
|
||||
file := "/proc/self/status"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/status", pid)
|
||||
}
|
||||
|
||||
return getSeccompEnforcingMode(readFileString(file))
|
||||
}
|
||||
|
||||
func getSeccompEnforcingMode(input string) SeccompMode {
|
||||
mode := getStatusEntry(input, "Seccomp:")
|
||||
sm, ok := seccompModes[mode]
|
||||
if ok {
|
||||
return sm
|
||||
}
|
||||
|
||||
// Pre linux 3.8, check if Seccomp is supported, via CONFIG_SECCOMP.
|
||||
if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL {
|
||||
// Make sure the kernel has CONFIG_SECCOMP_FILTER.
|
||||
if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL {
|
||||
return SeccompModeStrict
|
||||
}
|
||||
}
|
||||
|
||||
return SeccompModeDisabled
|
||||
}
|
||||
|
||||
// GetNoNewPrivileges returns if no_new_privileges is set
|
||||
// for a process.
|
||||
// If pid is less than one, it returns if set for "self".
|
||||
func GetNoNewPrivileges(pid int) bool {
|
||||
file := "/proc/self/status"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/status", pid)
|
||||
}
|
||||
|
||||
return getNoNewPrivileges(readFileString(file))
|
||||
}
|
||||
|
||||
func getNoNewPrivileges(input string) bool {
|
||||
return getStatusEntry(input, "NoNewPrivs:") == "1"
|
||||
}
|
||||
|
||||
// GetCmdline returns the cmdline for a process.
|
||||
// If pid is less than one, it returns the cmdline for "self".
|
||||
func GetCmdline(pid int) []string {
|
||||
file := "/proc/self/cmdline"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/cmdline", pid)
|
||||
}
|
||||
|
||||
return parseProcFile(readFile(file))
|
||||
}
|
||||
|
||||
// GetEnviron returns the environ for a process.
|
||||
// If pid is less than one, it returns the environ for "self".
|
||||
func GetEnviron(pid int) []string {
|
||||
file := "/proc/self/environ"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/environ", pid)
|
||||
}
|
||||
|
||||
return parseProcFile(readFile(file))
|
||||
}
|
||||
|
||||
// GetCwd returns the current working directory for the process.
|
||||
// If pid is less than one, it returns the current working directory for "self".
|
||||
func GetCwd(pid int) string {
|
||||
file := "/proc/self/cwd"
|
||||
if pid > 0 {
|
||||
file = fmt.Sprintf("/proc/%d/cwd", pid)
|
||||
}
|
||||
|
||||
cwd, err := os.Readlink(file)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// Ignore the permission errors or the logs are noisy.
|
||||
return ""
|
||||
}
|
||||
// Ignore errors in general.
|
||||
return ""
|
||||
}
|
||||
|
||||
return cwd
|
||||
}
|
||||
|
||||
// TODO: make this function more efficient and read the file line by line.
|
||||
func getStatusEntry(input, find string) string {
|
||||
// Split status file string by line
|
||||
statusMappings := strings.Split(input, "\n")
|
||||
statusMappings = deleteEmpty(statusMappings)
|
||||
|
||||
for _, line := range statusMappings {
|
||||
if strings.Contains(line, find) {
|
||||
matches := statusFileValueRegex.FindStringSubmatch(line)
|
||||
if len(matches) > 1 {
|
||||
return strings.TrimSpace(matches[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func fileExists(file string) bool {
|
||||
if _, err := os.Stat(file); !os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readFile(file string) []byte {
|
||||
if !fileExists(file) {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, _ := ioutil.ReadFile(file)
|
||||
return b
|
||||
}
|
||||
|
||||
func readFileString(file string) string {
|
||||
b := readFile(file)
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
func deleteEmpty(s []string) []string {
|
||||
var r []string
|
||||
for _, str := range s {
|
||||
if strings.TrimSpace(str) != "" {
|
||||
r = append(r, strings.TrimSpace(str))
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func parseProcFile(data []byte) []string {
|
||||
if len(data) < 1 {
|
||||
return nil
|
||||
}
|
||||
if data[len(data)-1] == 0 {
|
||||
data = data[:len(data)-1]
|
||||
}
|
||||
parts := bytes.Split(data, []byte{0})
|
||||
var strParts []string
|
||||
for _, p := range parts {
|
||||
strParts = append(strParts, string(p))
|
||||
}
|
||||
|
||||
return strParts
|
||||
}
|
||||
|
||||
// IsValidContainerRuntime checks if a string is a valid container runtime.
|
||||
func IsValidContainerRuntime(s string) bool {
|
||||
for _, b := range ContainerRuntimes {
|
||||
if string(b) == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasNamespace determines if a container is using a particular namespace or the
|
||||
// host namespace.
|
||||
// The device number of an unnamespaced /proc/1/ns/{ns} is 4 and anything else is
|
||||
// higher.
|
||||
// Only works from inside a container.
|
||||
func HasNamespace(ns string) (bool, error) {
|
||||
file := fmt.Sprintf("/proc/1/ns/%s", ns)
|
||||
|
||||
// Use Lstat to not follow the symlink.
|
||||
var info syscall.Stat_t
|
||||
if err := syscall.Lstat(file, &info); err != nil {
|
||||
return false, &os.PathError{Op: "lstat", Path: file, Err: err}
|
||||
}
|
||||
|
||||
// Get the device number. If it is higher than 4 it is in a namespace.
|
||||
if info.Dev > 4 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
Vendored
-3
@@ -445,9 +445,6 @@ github.com/emirpasic/gods/utils
|
||||
# github.com/fsnotify/fsnotify v1.5.1
|
||||
## explicit; go 1.13
|
||||
github.com/fsnotify/fsnotify
|
||||
# github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8
|
||||
## explicit
|
||||
github.com/genuinetools/bpfd/proc
|
||||
# github.com/go-git/gcfg v1.5.0
|
||||
## explicit
|
||||
github.com/go-git/gcfg
|
||||
|
||||
Reference in New Issue
Block a user