mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-22 07:52:49 +02:00
Check that kaniko is being run from inside a container. (#81)
Signed-off-by: dlorenc <dlorenc@google.com>
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
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.
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
// Package container provides tools for introspecting containers.
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// RuntimeDocker is the string for the docker runtime.
|
||||
RuntimeDocker = "docker"
|
||||
// RuntimeRkt is the string for the rkt runtime.
|
||||
RuntimeRkt = "rkt"
|
||||
// RuntimeNspawn is the string for the systemd-nspawn runtime.
|
||||
RuntimeNspawn = "systemd-nspawn"
|
||||
// RuntimeLXC is the string for the lxc runtime.
|
||||
RuntimeLXC = "lxc"
|
||||
// RuntimeLXCLibvirt is the string for the lxc-libvirt runtime.
|
||||
RuntimeLXCLibvirt = "lxc-libvirt"
|
||||
// RuntimeOpenVZ is the string for the openvz runtime.
|
||||
RuntimeOpenVZ = "openvz"
|
||||
// RuntimeKubernetes is the string for the kubernetes runtime.
|
||||
RuntimeKubernetes = "kube"
|
||||
// RuntimeGarden is the string for the garden runtime.
|
||||
RuntimeGarden = "garden"
|
||||
|
||||
uint32Max = 4294967295
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrContainerRuntimeNotFound describes when a container runtime could not be found.
|
||||
ErrContainerRuntimeNotFound = errors.New("container runtime could not be found")
|
||||
|
||||
runtimes = []string{RuntimeDocker, RuntimeRkt, RuntimeNspawn, RuntimeLXC, RuntimeLXCLibvirt, RuntimeOpenVZ, RuntimeKubernetes, RuntimeGarden}
|
||||
)
|
||||
|
||||
// DetectRuntime returns the container runtime the process is running in.
|
||||
func DetectRuntime() (string, error) {
|
||||
// read the cgroups file
|
||||
cgroups := readFile("/proc/self/cgroup")
|
||||
if len(cgroups) > 0 {
|
||||
for _, runtime := range runtimes {
|
||||
if strings.Contains(cgroups, runtime) {
|
||||
return runtime, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /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, nil
|
||||
}
|
||||
|
||||
ctrenv := os.Getenv("container")
|
||||
if ctrenv != "" {
|
||||
for _, runtime := range runtimes {
|
||||
if ctrenv == runtime {
|
||||
return runtime, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
f := readFile("/run/systemd/container")
|
||||
if len(f) > 0 {
|
||||
for _, runtime := range runtimes {
|
||||
if f == runtime {
|
||||
return runtime, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "not-found", ErrContainerRuntimeNotFound
|
||||
}
|
||||
|
||||
// HasNamespace determines if the 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.
|
||||
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
|
||||
}
|
||||
|
||||
// AppArmorProfile determines the apparmor profile for a container.
|
||||
func AppArmorProfile() string {
|
||||
f := readFile("/proc/self/attr/current")
|
||||
if f == "" {
|
||||
return "none"
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// UserMapping holds the values for a {uid,gid}_map.
|
||||
type UserMapping struct {
|
||||
ContainerID int64
|
||||
HostID int64
|
||||
Range int64
|
||||
}
|
||||
|
||||
// UserNamespace determines if the container is running in a UserNamespace and returns the mappings if so.
|
||||
func UserNamespace() (bool, []UserMapping) {
|
||||
f := readFile("/proc/self/uid_map")
|
||||
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
|
||||
}
|
||||
|
||||
// Capabilities returns the allowed capabilities in the container.
|
||||
func Capabilities() (map[string][]string, error) {
|
||||
allCaps := capability.List()
|
||||
|
||||
caps, err := capability.NewPid(0)
|
||||
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
|
||||
}
|
||||
|
||||
// Chroot detects if we are running in a chroot or a pivot_root.
|
||||
// Currently, we can not distinguish between the two.
|
||||
func Chroot() (bool, error) {
|
||||
var a, b syscall.Stat_t
|
||||
|
||||
if err := syscall.Lstat("/proc/1/root", &a); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := syscall.Lstat("/", &b); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return a.Ino == b.Ino && a.Dev == b.Dev, nil
|
||||
}
|
||||
|
||||
// SeccompEnforcingMode returns the seccomp enforcing level (disabled, filtering, strict)
|
||||
func SeccompEnforcingMode() (string, error) {
|
||||
// Read from /proc/self/status Linux 3.8+
|
||||
s := readFile("/proc/self/status")
|
||||
|
||||
// Pre linux 3.8
|
||||
if !strings.Contains(s, "Seccomp") {
|
||||
// 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 "strict", nil
|
||||
}
|
||||
}
|
||||
return "disabled", nil
|
||||
}
|
||||
|
||||
// Split status file string by line
|
||||
statusMappings := strings.Split(s, "\n")
|
||||
statusMappings = deleteEmpty(statusMappings)
|
||||
|
||||
mode := "-1"
|
||||
for _, line := range statusMappings {
|
||||
if strings.Contains(line, "Seccomp:") {
|
||||
mode = string(line[len(line)-1])
|
||||
}
|
||||
}
|
||||
|
||||
seccompModes := map[string]string{
|
||||
"0": "disabled",
|
||||
"1": "strict",
|
||||
"2": "filtering",
|
||||
}
|
||||
|
||||
seccompMode, ok := seccompModes[mode]
|
||||
if !ok {
|
||||
return "", errors.New("could not retrieve seccomp filtering status")
|
||||
}
|
||||
|
||||
return seccompMode, nil
|
||||
}
|
||||
|
||||
func fileExists(file string) bool {
|
||||
if _, err := os.Stat(file); !os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readFile(file string) string {
|
||||
if !fileExists(file) {
|
||||
return ""
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(file)
|
||||
if err != 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
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/genuinetools/amicontained/container"
|
||||
"github.com/genuinetools/amicontained/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// BANNER is what is printed for help/info output
|
||||
BANNER = ` _ _ _ _
|
||||
__ _ _ __ ___ (_) ___ ___ _ __ | |_ __ _(_)_ __ ___ __| |
|
||||
/ _` + "`" + ` | '_ ` + "`" + ` _ \| |/ __/ _ \| '_ \| __/ _` + "`" + ` | | '_ \ / _ \/ _` + "`" + ` |
|
||||
| (_| | | | | | | | (_| (_) | | | | || (_| | | | | | __/ (_| |
|
||||
\__,_|_| |_| |_|_|\___\___/|_| |_|\__\__,_|_|_| |_|\___|\__,_|
|
||||
Container introspection tool.
|
||||
Version: %s
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
debug bool
|
||||
vrsn bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
// parse flags
|
||||
flag.BoolVar(&vrsn, "version", false, "print version and exit")
|
||||
flag.BoolVar(&vrsn, "v", false, "print version and exit (shorthand)")
|
||||
flag.BoolVar(&debug, "d", false, "run in debug mode")
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprint(os.Stderr, fmt.Sprintf(BANNER, version.VERSION))
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// set log level
|
||||
if debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
if vrsn {
|
||||
fmt.Printf("amicontained version %s, build %s\n", version.VERSION, version.GITCOMMIT)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// parse the arg
|
||||
arg := flag.Args()[0]
|
||||
|
||||
if arg == "help" {
|
||||
usageAndExit("", 0)
|
||||
}
|
||||
|
||||
if arg == "version" {
|
||||
fmt.Printf("amicontained version %s, build %s\n", version.VERSION, version.GITCOMMIT)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Container Runtime
|
||||
runtime, err := container.DetectRuntime()
|
||||
if err != nil && err != container.ErrContainerRuntimeNotFound {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Container Runtime: %s\n", runtime)
|
||||
|
||||
// Namespaces
|
||||
namespaces := []string{"pid"}
|
||||
fmt.Println("Has Namespaces:")
|
||||
for _, namespace := range namespaces {
|
||||
ns, err := container.HasNamespace(namespace)
|
||||
if err != nil {
|
||||
fmt.Printf("\t%s: error -> %v\n", namespace, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("\t%s: %t\n", namespace, ns)
|
||||
}
|
||||
|
||||
// User Namespaces
|
||||
userNS, userMappings := container.UserNamespace()
|
||||
fmt.Printf("\tuser: %t\n", userNS)
|
||||
if len(userMappings) > 0 {
|
||||
fmt.Println("User Namespace Mappings:")
|
||||
for _, userMapping := range userMappings {
|
||||
fmt.Printf("\tContainer -> %d\tHost -> %d\tRange -> %d\n", userMapping.ContainerID, userMapping.HostID, userMapping.Range)
|
||||
}
|
||||
}
|
||||
|
||||
// AppArmor Profile
|
||||
aaprof := container.AppArmorProfile()
|
||||
fmt.Printf("AppArmor Profile: %s\n", aaprof)
|
||||
|
||||
// Capabilities
|
||||
caps, err := container.Capabilities()
|
||||
if err != nil {
|
||||
logrus.Warnf("getting capabilities failed: %v", err)
|
||||
}
|
||||
if len(caps) > 0 {
|
||||
fmt.Println("Capabilities:")
|
||||
for k, v := range caps {
|
||||
if len(v) > 0 {
|
||||
fmt.Printf("\t%s -> %s\n", k, strings.Join(v, " "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chroot
|
||||
chroot, err := container.Chroot()
|
||||
if err != nil {
|
||||
logrus.Debugf("chroot check error: %v", err)
|
||||
}
|
||||
fmt.Printf("Chroot (not pivot_root): %t\n", chroot)
|
||||
|
||||
// Seccomp
|
||||
seccompMode, err := container.SeccompEnforcingMode()
|
||||
if err != nil {
|
||||
logrus.Debugf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("Seccomp: %s\n", seccompMode)
|
||||
}
|
||||
|
||||
func usageAndExit(message string, exitCode int) {
|
||||
if message != "" {
|
||||
fmt.Fprintf(os.Stderr, message)
|
||||
fmt.Fprintf(os.Stderr, "\n\n")
|
||||
}
|
||||
flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package version
|
||||
|
||||
// VERSION indicates which version of the binary is running.
|
||||
var VERSION string
|
||||
|
||||
// GITCOMMIT indicates which git hash the binary was built off of
|
||||
var GITCOMMIT string
|
||||
Reference in New Issue
Block a user