github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/pkg/rootless/rootless.go (about)

     1  // Copyright (c) 2019 Intel Corporation
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  // Copyright 2015-2019 CNI authors
     7  //
     8  // Licensed under the Apache License, Version 2.0 (the "License");
     9  // you may not use this file except in compliance with the License.
    10  // You may obtain a copy of the License at
    11  //
    12  //     http://www.apache.org/licenses/LICENSE-2.0
    13  //
    14  // Unless required by applicable law or agreed to in writing, software
    15  // distributed under the License is distributed on an "AS IS" BASIS,
    16  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    17  // See the License for the specific language governing permissions and
    18  // limitations under the License.
    19  
    20  package rootless
    21  
    22  import (
    23  	"context"
    24  	"crypto/rand"
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  	"runtime"
    29  	"strings"
    30  	"sync"
    31  
    32  	"github.com/containernetworking/plugins/pkg/ns"
    33  	"github.com/opencontainers/runc/libcontainer/system"
    34  	"github.com/sirupsen/logrus"
    35  	"golang.org/x/sys/unix"
    36  )
    37  
    38  var (
    39  	// isRootless states whether execution is rootless or not
    40  	// If nil, rootless is auto-detected
    41  	isRootless *bool
    42  
    43  	// lock for the initRootless and isRootless variables
    44  	rLock sync.Mutex
    45  
    46  	// XDG_RUNTIME_DIR defines the base directory relative to
    47  	// which user-specific non-essential runtime files are stored.
    48  	rootlessDir = os.Getenv("XDG_RUNTIME_DIR")
    49  
    50  	rootlessLog = logrus.WithFields(logrus.Fields{
    51  		"source": "rootless",
    52  	})
    53  
    54  	// IsRootless is declared this way for mocking in unit tests
    55  	IsRootless = isRootlessFunc
    56  )
    57  
    58  func SetRootless(rootless bool) {
    59  	isRootless = &rootless
    60  }
    61  
    62  // SetLogger sets up a logger for the rootless pkg
    63  func SetLogger(ctx context.Context, logger *logrus.Entry) {
    64  	fields := rootlessLog.Data
    65  	rootlessLog = logger.WithFields(fields)
    66  }
    67  
    68  // isRootlessFunc states whether kata is being ran with root or not
    69  func isRootlessFunc() bool {
    70  	rLock.Lock()
    71  	defer rLock.Unlock()
    72  	// auto-detect if nil
    73  	if isRootless == nil {
    74  		SetRootless(true)
    75  		// --rootless and --systemd-cgroup options must honoured
    76  		// but with the current implementation this is not possible
    77  		// https://github.com/kata-containers/runtime/issues/2412
    78  		if os.Geteuid() != 0 {
    79  			return true
    80  		}
    81  		if system.RunningInUserNS() {
    82  			return true
    83  		}
    84  		SetRootless(false)
    85  	}
    86  	return *isRootless
    87  }
    88  
    89  // GetRootlessDir returns the path to the location for rootless
    90  // container and sandbox storage
    91  func GetRootlessDir() string {
    92  	return rootlessDir
    93  }
    94  
    95  // Creates a new persistent network namespace and returns an object
    96  // representing that namespace, without switching to it
    97  func NewNS() (ns.NetNS, error) {
    98  	nsRunDir := filepath.Join(GetRootlessDir(), "netns")
    99  
   100  	b := make([]byte, 16)
   101  	_, err := rand.Reader.Read(b)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("failed to generate random netns name: %v", err)
   104  	}
   105  
   106  	// Create the directory for mounting network namespaces
   107  	// This needs to be a shared mountpoint in case it is mounted in to
   108  	// other namespaces (containers)
   109  	err = os.MkdirAll(nsRunDir, 0755)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	nsName := fmt.Sprintf("net-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
   115  
   116  	// create an empty file at the mount point
   117  	nsPath := filepath.Join(nsRunDir, nsName)
   118  	mountPointFd, err := os.Create(nsPath)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	if err := mountPointFd.Close(); err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	// Ensure the mount point is cleaned up on errors; if the namespace
   127  	// was successfully mounted this will have no effect because the file
   128  	// is in-use
   129  	defer func() {
   130  		_ = os.RemoveAll(nsPath)
   131  	}()
   132  
   133  	var wg sync.WaitGroup
   134  	wg.Add(1)
   135  
   136  	// do namespace work in a dedicated goroutine, so that we can safely
   137  	// Lock/Unlock OSThread without upsetting the lock/unlock state of
   138  	// the caller of this function
   139  	go (func() {
   140  		defer wg.Done()
   141  		runtime.LockOSThread()
   142  		// Don't unlock. By not unlocking, golang will kill the OS thread when the
   143  		// goroutine is done (for go1.10+)
   144  
   145  		threadNsPath := getCurrentThreadNetNSPath()
   146  
   147  		var origNS ns.NetNS
   148  		origNS, err = ns.GetNS(threadNsPath)
   149  		if err != nil {
   150  			rootlessLog.Warnf("cannot open current network namespace %s: %q", threadNsPath, err)
   151  			return
   152  		}
   153  		defer func() {
   154  			if err := origNS.Close(); err != nil {
   155  				rootlessLog.Errorf("unable to close namespace: %q", err)
   156  			}
   157  		}()
   158  
   159  		// create a new netns on the current thread
   160  		err = unix.Unshare(unix.CLONE_NEWNET)
   161  		if err != nil {
   162  			rootlessLog.Warnf("cannot create a new network namespace: %q", err)
   163  			return
   164  		}
   165  
   166  		// Put this thread back to the orig ns, since it might get reused (pre go1.10)
   167  		defer func() {
   168  			if err := origNS.Set(); err != nil {
   169  				if IsRootless() && strings.Contains(err.Error(), "operation not permitted") {
   170  					// When running in rootless mode it will fail to re-join
   171  					// the network namespace owned by root on the host.
   172  					return
   173  				}
   174  				rootlessLog.Warnf("unable to reset namespace: %q", err)
   175  			}
   176  		}()
   177  
   178  		// bind mount the netns from the current thread (from /proc) onto the
   179  		// mount point. This causes the namespace to persist, even when there
   180  		// are no threads in the ns.
   181  		err = unix.Mount(threadNsPath, nsPath, "none", unix.MS_BIND, "")
   182  		if err != nil {
   183  			err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err)
   184  		}
   185  	})()
   186  	wg.Wait()
   187  
   188  	if err != nil {
   189  		unix.Unmount(nsPath, unix.MNT_DETACH)
   190  		return nil, fmt.Errorf("failed to create namespace: %v", err)
   191  	}
   192  
   193  	return ns.GetNS(nsPath)
   194  }
   195  
   196  // getCurrentThreadNetNSPath copied from pkg/ns
   197  func getCurrentThreadNetNSPath() string {
   198  	// /proc/self/ns/net returns the namespace of the main thread, not
   199  	// of whatever thread this goroutine is running on.  Make sure we
   200  	// use the thread's net namespace since the thread is switching around
   201  	return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
   202  }