github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/gvisor-ligolo/pkg/fd" 23 "github.com/nicocha30/gvisor-ligolo/pkg/log" 24 "github.com/nicocha30/gvisor-ligolo/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 }