github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/nsfix/nsfix.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     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 nsfix
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	"os/exec"
    26  	"reflect"
    27  	"strings"
    28  	"syscall"
    29  
    30  	"github.com/golang/glog"
    31  )
    32  
    33  // ReexecHandler is a function that can be passed to
    34  // RegisterReexec to be executed my nsfix mechanism after
    35  // self-reexec. arg can be safely casted to the type of arg
    36  // passed to RegisterReexec plus one level of pointer
    37  // inderection, i.e. if you pass somestruct{} to RegisterReexec
    38  // you may cast arg safely to *somestruct.
    39  type ReexecHandler func(arg interface{}) (interface{}, error)
    40  
    41  type handlerEntry struct {
    42  	handler ReexecHandler
    43  	argType reflect.Type
    44  }
    45  
    46  var reexecMap = map[string]handlerEntry{}
    47  
    48  type retStruct struct {
    49  	Success bool
    50  	Result  json.RawMessage
    51  	Error   string
    52  }
    53  
    54  // RegisterReexec registers the specified function as a reexec handler.
    55  // arg specifies the argument type to pass. Note that if you pass somestruct{}
    56  // as arg, the handler will receive *somestruct as its argument (i.e. a level
    57  // of pointer indirection is added).
    58  func RegisterReexec(name string, handler ReexecHandler, arg interface{}) {
    59  	reexecMap[name] = handlerEntry{handler, reflect.TypeOf(arg)}
    60  }
    61  
    62  func getGlogLevel() int {
    63  	// XXX: apparently there's no better way to get the log level
    64  	i := 0
    65  	for ; i < 100; i++ {
    66  		if !glog.V(glog.Level(i)) {
    67  			return i - 1
    68  		}
    69  	}
    70  	return i
    71  }
    72  
    73  func restoreGlogLevel() {
    74  	logLevelStr := os.Getenv("NSFIX_LOG_LEVEL")
    75  	if logLevelStr == "" {
    76  		logLevelStr = "1"
    77  	}
    78  	// configure glog (apparently no better way to do it ...)
    79  	flag.CommandLine.Parse([]string{"-v=" + logLevelStr, "-logtostderr=true"})
    80  }
    81  
    82  func marshalResult(ret interface{}, retErr error) ([]byte, error) {
    83  	var r retStruct
    84  	if retErr != nil {
    85  		r.Error = retErr.Error()
    86  	} else {
    87  		resultBytes, err := json.Marshal(ret)
    88  		if err != nil {
    89  			return nil, fmt.Errorf("error marshalling the result: %v", err)
    90  		}
    91  
    92  		r.Success = true
    93  		r.Result = json.RawMessage(resultBytes)
    94  	}
    95  	retBytes, err := json.Marshal(r)
    96  	if err != nil {
    97  		return nil, fmt.Errorf("error marshalling retStruct: %v", err)
    98  	}
    99  	return retBytes, nil
   100  }
   101  
   102  func unmarshalResult(retBytes []byte, ret interface{}) error {
   103  	var r retStruct
   104  	if err := json.Unmarshal(retBytes, &r); err != nil {
   105  		return fmt.Errorf("error unmarshalling the result: %v", err)
   106  	}
   107  	if !r.Success {
   108  		return errors.New(r.Error)
   109  	}
   110  	if ret != nil {
   111  		if err := json.Unmarshal(r.Result, ret); err != nil {
   112  			return fmt.Errorf("error unmarshalling the result: %v", err)
   113  		}
   114  	}
   115  	return nil
   116  }
   117  
   118  // HandleReexec handles executing the code in another namespace.
   119  // If reexcution is requested, the function calls os.Exit() after
   120  // handling it.
   121  func HandleReexec() {
   122  	if os.Getenv("NSFIX_NS_PID") == "" {
   123  		return
   124  	}
   125  
   126  	restoreGlogLevel()
   127  	handlerName := os.Getenv("NSFIX_HANDLER")
   128  	if handlerName == "" {
   129  		glog.Fatal("NSFIX_HANDLER not set")
   130  	}
   131  	entry, found := reexecMap[handlerName]
   132  	if !found {
   133  		glog.Fatalf("Bad NSFIX_HANDLER %q", handlerName)
   134  	}
   135  
   136  	var arg interface{}
   137  	if entry.argType != nil {
   138  		arg = reflect.New(entry.argType).Interface()
   139  		argStr := os.Getenv("NSFIX_ARG")
   140  		if argStr != "" {
   141  			if err := json.Unmarshal([]byte(argStr), arg); err != nil {
   142  				glog.Fatalf("Can't unmarshal NSFIX_ARG (NSFIX_HANDLER %q):\n%s\n", handlerName, argStr)
   143  			}
   144  		}
   145  	}
   146  
   147  	spawned := os.Getenv("NSFIX_SPAWN") != ""
   148  	switch ret, err := entry.handler(arg); {
   149  	case err != nil && !spawned:
   150  		glog.Fatalf("Error invoking NSFIX_HANDLER %q: %v", handlerName, err)
   151  	case err == nil && !spawned:
   152  		os.Exit(0)
   153  	default:
   154  		outBytes, err := marshalResult(ret, err)
   155  		if err != nil {
   156  			glog.Fatalf("Error marshalling the result from NSFIX_HANDLER %q: %v", handlerName, err)
   157  		}
   158  		os.Stdout.Write(outBytes)
   159  		os.Exit(0)
   160  	}
   161  }
   162  
   163  // Call describes a call to be executed in network, mount, UTS
   164  // and IPC namespaces of another process.
   165  type Call struct {
   166  	targetPid   int
   167  	handlerName string
   168  	arg         interface{}
   169  	remountSys  bool
   170  	dropPrivs   bool
   171  }
   172  
   173  // NewCall makes a new Call structure with specified
   174  // handlerName using PID 1.
   175  func NewCall(handlerName string) *Call {
   176  	return &Call{
   177  		targetPid:   1,
   178  		handlerName: handlerName,
   179  	}
   180  }
   181  
   182  // TargetPid sets target PID value for Call
   183  func (c *Call) TargetPid(targetPid int) *Call {
   184  	c.targetPid = targetPid
   185  	return c
   186  }
   187  
   188  // Arg sets argument for Call
   189  func (c *Call) Arg(arg interface{}) *Call {
   190  	c.arg = arg
   191  	return c
   192  }
   193  
   194  // RemountSys instructs Call to remount /sys in the new process
   195  func (c *Call) RemountSys() *Call {
   196  	c.remountSys = true
   197  	return c
   198  }
   199  
   200  // DropPrivs instructs Call to drop privileges in the new process
   201  func (c *Call) DropPrivs() *Call {
   202  	c.dropPrivs = true
   203  	return c
   204  }
   205  
   206  func (c *Call) getEnvForExec(spawn bool) ([]string, error) {
   207  	env := os.Environ()
   208  	filteredEnv := []string{}
   209  	for _, envItem := range env {
   210  		if !strings.HasPrefix(envItem, "NSFIX_") {
   211  			filteredEnv = append(filteredEnv, envItem)
   212  		}
   213  	}
   214  
   215  	if c.arg != nil {
   216  		argBytes, err := json.Marshal(c.arg)
   217  		if err != nil {
   218  			return nil, fmt.Errorf("error marshalling handler arg: %v", err)
   219  		}
   220  		filteredEnv = append(filteredEnv, fmt.Sprintf("NSFIX_ARG=%s", argBytes))
   221  	}
   222  
   223  	if c.remountSys {
   224  		filteredEnv = append(filteredEnv, "NSFIX_REMOUNT_SYS=1")
   225  	}
   226  
   227  	if c.dropPrivs {
   228  		filteredEnv = append(filteredEnv, "NSFIX_DROP_PRIVS=1")
   229  	}
   230  
   231  	if spawn {
   232  		filteredEnv = append(filteredEnv, "NSFIX_SPAWN=1")
   233  	}
   234  
   235  	return append(filteredEnv,
   236  		fmt.Sprintf("NSFIX_NS_PID=%d", c.targetPid),
   237  		fmt.Sprintf("NSFIX_HANDLER=%s", c.handlerName),
   238  		fmt.Sprintf("NSFIX_LOG_LEVEL=%d", getGlogLevel())), nil
   239  }
   240  
   241  // SwitchToNamespaces executes the specified handler using network,
   242  // mount, UTS and IPC namespaces of the specified process. It passes
   243  // the argument to the handler using JSON serialization. The current
   244  // process gets replaced by the new one. If dropPrivs is true, the new
   245  // process will execute using non-root uid/gid (using real uid/gid of
   246  // the process if they're non-zero or 65534 which is nobody/nogroup)
   247  func (c *Call) SwitchToNamespaces() error {
   248  	env, err := c.getEnvForExec(false)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	return syscall.Exec(os.Args[0], os.Args[:1], env)
   253  }
   254  
   255  // SpawnInNamespaces executes the specified handler using network,
   256  // mount, UTS and IPC namespaces of the specified process. It passes
   257  // the argument to the handler using JSON serialization. It then
   258  // returns the value returned by the handler (also via JSON
   259  // serialization + deserialization). If dropPrivs is true, the new
   260  // process will execute using non-root uid/gid (using real uid/gid of
   261  // the process if they're non-zero or 65534 which is nobody/nogroup)
   262  func (c *Call) SpawnInNamespaces(ret interface{}) error {
   263  	env, err := c.getEnvForExec(true)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	cmd := exec.Command(os.Args[0])
   269  	cmd.Env = env
   270  	cmd.Stderr = os.Stderr
   271  	out, err := cmd.Output()
   272  	if err != nil {
   273  		return fmt.Errorf("reexec caused error: %v", err)
   274  	}
   275  
   276  	return unmarshalResult(out, ret)
   277  }