github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/sentry/seccheck/config.go (about)

     1  // Copyright 2022 The gVisor 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  package seccheck
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"sync"
    21  
    22  	"github.com/MerlinKodo/gvisor/pkg/fd"
    23  	"github.com/MerlinKodo/gvisor/pkg/log"
    24  	"github.com/MerlinKodo/gvisor/pkg/metric"
    25  )
    26  
    27  // DefaultSessionName is the name of the only session that can exist in the
    28  // system for now. When multiple sessions are supported, this can be removed.
    29  const DefaultSessionName = "Default"
    30  
    31  var (
    32  	sessionsMu = sync.Mutex{}
    33  	sessions   = make(map[string]*State)
    34  )
    35  
    36  var sessionCounter = metric.MustCreateNewUint64Metric("/trace/sessions_created", false /* sync */, "Counts the number of trace sessions created.")
    37  
    38  // SessionConfig describes a new session configuration. A session consists of a
    39  // set of points to be enabled and sinks where the points are sent to.
    40  type SessionConfig struct {
    41  	// Name is the unique session name.
    42  	Name string `json:"name,omitempty"`
    43  	// Points is the set of points to enable in this session.
    44  	Points []PointConfig `json:"points,omitempty"`
    45  	// IgnoreMissing skips point and optional/context fields not found. This can
    46  	// be used to apply a single configuration file with newer points/fields with
    47  	// older versions which do not have them yet. Note that it may hide typos in
    48  	// the configuration.
    49  	//
    50  	// This field does NOT apply to sinks.
    51  	IgnoreMissing bool `json:"ignore_missing,omitempty"`
    52  	// Sinks are the sinks that will process the points enabled above.
    53  	Sinks []SinkConfig `json:"sinks,omitempty"`
    54  }
    55  
    56  // PointConfig describes a point to be enabled in a given session.
    57  type PointConfig struct {
    58  	// Name is the point to be enabled. The point must exist in the system.
    59  	Name string `json:"name,omitempty"`
    60  	// OptionalFields is the list of optional fields to collect from the point.
    61  	OptionalFields []string `json:"optional_fields,omitempty"`
    62  	// ContextFields is the list of context fields to collect.
    63  	ContextFields []string `json:"context_fields,omitempty"`
    64  }
    65  
    66  // SinkConfig describes the sink that will process the points in a given
    67  // session.
    68  type SinkConfig struct {
    69  	// Name is the sink to be created. The sink must exist in the system.
    70  	Name string `json:"name,omitempty"`
    71  	// Config is a opaque json object that is passed to the sink.
    72  	Config map[string]any `json:"config,omitempty"`
    73  	// IgnoreSetupError makes errors during sink setup to be ignored. Otherwise,
    74  	// failures will prevent the container from starting.
    75  	IgnoreSetupError bool `json:"ignore_setup_error,omitempty"`
    76  	// Status is the runtime status for the sink.
    77  	Status SinkStatus `json:"status,omitempty"`
    78  	// FD is the endpoint returned from Setup. It may be nil.
    79  	FD *fd.FD `json:"-"`
    80  }
    81  
    82  // Create reads the session configuration and applies it to the system.
    83  func Create(conf *SessionConfig, force bool) error {
    84  	log.Debugf("Creating seccheck: %+v", conf)
    85  	sessionsMu.Lock()
    86  	defer sessionsMu.Unlock()
    87  
    88  	if _, ok := sessions[conf.Name]; ok {
    89  		if !force {
    90  			return fmt.Errorf("session %q already exists", conf.Name)
    91  		}
    92  		if err := deleteLocked(conf.Name); err != nil {
    93  			return err
    94  		}
    95  		log.Infof("Trace session %q was deleted to be replaced", conf.Name)
    96  	}
    97  	if conf.Name != DefaultSessionName {
    98  		return fmt.Errorf(`only a single "Default" session is supported`)
    99  	}
   100  	state := &Global
   101  
   102  	var reqs []PointReq
   103  	for _, ptConfig := range conf.Points {
   104  		desc, err := findPointDesc(ptConfig.Name)
   105  		if err != nil {
   106  			if conf.IgnoreMissing {
   107  				log.Warningf("Skipping point %q: %v", ptConfig.Name, err)
   108  				continue
   109  			}
   110  			return err
   111  		}
   112  		req := PointReq{Pt: desc.ID}
   113  
   114  		mask, err := setFields(ptConfig.OptionalFields, desc.OptionalFields, conf.IgnoreMissing)
   115  		if err != nil {
   116  			return fmt.Errorf("configuring point %q: %w", ptConfig.Name, err)
   117  		}
   118  		req.Fields.Local = mask
   119  
   120  		mask, err = setFields(ptConfig.ContextFields, desc.ContextFields, conf.IgnoreMissing)
   121  		if err != nil {
   122  			return fmt.Errorf("configuring point %q: %w", ptConfig.Name, err)
   123  		}
   124  		req.Fields.Context = mask
   125  
   126  		reqs = append(reqs, req)
   127  	}
   128  
   129  	for _, sinkConfig := range conf.Sinks {
   130  		desc, err := findSinkDesc(sinkConfig.Name)
   131  		if err != nil {
   132  			return err
   133  		}
   134  		sink, err := desc.New(sinkConfig.Config, sinkConfig.FD)
   135  		if err != nil {
   136  			return fmt.Errorf("creating event sink: %w", err)
   137  		}
   138  		state.AppendSink(sink, reqs)
   139  	}
   140  
   141  	sessions[conf.Name] = state
   142  	sessionCounter.Increment()
   143  	return nil
   144  }
   145  
   146  // SetupSinks runs the setup step of all sinks in the configuration.
   147  func SetupSinks(sinks []SinkConfig) ([]*os.File, error) {
   148  	var files []*os.File
   149  	for _, sink := range sinks {
   150  		sinkFile, err := setupSink(sink)
   151  		if err != nil {
   152  			if !sink.IgnoreSetupError {
   153  				return nil, err
   154  			}
   155  			log.Warningf("Ignoring sink setup failure: %v", err)
   156  			// Set sinkFile is nil and append it to the list to ensure the file
   157  			// order is preserved.
   158  			sinkFile = nil
   159  		}
   160  		files = append(files, sinkFile)
   161  	}
   162  	return files, nil
   163  }
   164  
   165  // setupSink runs the setup step for a given sink.
   166  func setupSink(config SinkConfig) (*os.File, error) {
   167  	sink, err := findSinkDesc(config.Name)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	if sink.Setup == nil {
   172  		return nil, nil
   173  	}
   174  	return sink.Setup(config.Config)
   175  }
   176  
   177  // Delete deletes an existing session.
   178  func Delete(name string) error {
   179  	sessionsMu.Lock()
   180  	defer sessionsMu.Unlock()
   181  	return deleteLocked(name)
   182  }
   183  
   184  // +checklocks:sessionsMu
   185  func deleteLocked(name string) error {
   186  	session := sessions[name]
   187  	if session == nil {
   188  		return fmt.Errorf("session %q not found", name)
   189  	}
   190  
   191  	session.clearSink()
   192  	delete(sessions, name)
   193  	return nil
   194  }
   195  
   196  // List lists all existing sessions.
   197  func List(out *[]SessionConfig) {
   198  	sessionsMu.Lock()
   199  	defer sessionsMu.Unlock()
   200  
   201  	for name, state := range sessions {
   202  		// Only report session name. Consider adding rest of the fields as needed.
   203  		session := SessionConfig{Name: name}
   204  		for _, sink := range state.getSinks() {
   205  			session.Sinks = append(session.Sinks, SinkConfig{
   206  				Name:   sink.Name(),
   207  				Status: sink.Status(),
   208  			})
   209  		}
   210  		*out = append(*out, session)
   211  	}
   212  }
   213  
   214  func findPointDesc(name string) (PointDesc, error) {
   215  	if desc, ok := Points[name]; ok {
   216  		return desc, nil
   217  	}
   218  	return PointDesc{}, fmt.Errorf("point %q not found", name)
   219  }
   220  
   221  func findField(name string, fields []FieldDesc) (FieldDesc, error) {
   222  	for _, f := range fields {
   223  		if f.Name == name {
   224  			return f, nil
   225  		}
   226  	}
   227  	return FieldDesc{}, fmt.Errorf("field %q not found", name)
   228  }
   229  
   230  func setFields(names []string, fields []FieldDesc, ignoreMissing bool) (FieldMask, error) {
   231  	fm := FieldMask{}
   232  	for _, name := range names {
   233  		desc, err := findField(name, fields)
   234  		if err != nil {
   235  			if ignoreMissing {
   236  				log.Warningf("Skipping field %q: %v", name, err)
   237  				continue
   238  			}
   239  			return FieldMask{}, err
   240  		}
   241  		fm.Add(desc.ID)
   242  	}
   243  	return fm, nil
   244  }
   245  
   246  func findSinkDesc(name string) (SinkDesc, error) {
   247  	if desc, ok := Sinks[name]; ok {
   248  		return desc, nil
   249  	}
   250  	return SinkDesc{}, fmt.Errorf("sink %q not found", name)
   251  }