github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/internal/config/manager.go (about) 1 // Copyright 2017 Google Inc. 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 // https://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 config contains internal structures and methods relating to managing 16 // a client's configuration. 17 package config 18 19 import ( 20 "crypto/ecdsa" 21 "crypto/elliptic" 22 "crypto/rand" 23 "crypto/x509" 24 "errors" 25 "fmt" 26 "sync" 27 "time" 28 29 log "github.com/golang/glog" 30 "github.com/google/fleetspeak/fleetspeak/src/client/config" 31 "github.com/google/fleetspeak/fleetspeak/src/client/stats" 32 "github.com/google/fleetspeak/fleetspeak/src/common" 33 "google.golang.org/protobuf/proto" 34 35 clpb "github.com/google/fleetspeak/fleetspeak/src/client/proto/fleetspeak_client" 36 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 37 ) 38 39 // A Manager stores and manages the current configuration of the client. 40 // It exports a view of the current configuration and is safe for concurrent 41 // access. 42 type Manager struct { 43 cfg *config.Configuration // does not change 44 cc *clpb.CommunicatorConfig 45 stats stats.ConfigManagerCollector 46 47 lock sync.RWMutex // protects the state variables below 48 state *clpb.ClientState 49 id common.ClientID 50 revokedSerials map[string]bool // key is raw bytes of serial 51 dirty bool // indicates that state has changed and should be written to disk 52 writebackPath string 53 runningServices map[string][]byte // map from service name to signature of installed service configuration 54 55 syncTicker *time.Ticker 56 configChanges chan<- *fspb.ClientInfoData 57 done chan bool 58 } 59 60 // StartManager attempts to create a Manager from the provided client 61 // configuration. 62 // 63 // The labels parameter defines what client labels the client should 64 // report to the server. 65 // TODO(b/297019580): Consider defining and consuming a more specific `ConfigManagerCollector` 66 // interface here, containing only the methods that Manager actually cares about. 67 func StartManager(cfg *config.Configuration, configChanges chan<- *fspb.ClientInfoData, c stats.ConfigManagerCollector) (*Manager, error) { 68 if cfg == nil { 69 return nil, errors.New("configuration must be provided") 70 } 71 72 if cfg.PersistenceHandler == nil { 73 log.Warning("PersistenceHandler not provided. Using NoopPersistenceHandler.") 74 cfg.PersistenceHandler = config.NewNoopPersistenceHandler() 75 } 76 77 for _, l := range cfg.ClientLabels { 78 if l.ServiceName != "client" || l.Label == "" { 79 return nil, fmt.Errorf("invalid client label: %v", l) 80 } 81 } 82 83 r := Manager{ 84 cfg: cfg, 85 cc: cfg.CommunicatorConfig, 86 stats: c, 87 88 state: &clpb.ClientState{}, 89 revokedSerials: make(map[string]bool), 90 dirty: true, 91 runningServices: make(map[string][]byte), 92 93 configChanges: configChanges, 94 } 95 96 if r.cc == nil { 97 r.cc = &clpb.CommunicatorConfig{} 98 } 99 100 var err error 101 if r.state, err = cfg.PersistenceHandler.ReadState(); err != nil || r.state == nil { 102 if err != nil { 103 log.Errorf("initial load of writeback failed (continuing): %v", err) 104 } 105 r.state = &clpb.ClientState{} 106 } 107 r.AddRevokedSerials(r.state.RevokedCertSerials) 108 r.AddRevokedSerials(cfg.RevokedCertSerials) 109 110 if r.state.ClientKey == nil { 111 if err := r.Rekey(); err != nil { 112 return nil, fmt.Errorf("no key present, and %v", err) 113 } 114 } else { 115 k, err := x509.ParseECPrivateKey(r.state.ClientKey) 116 if err != nil { 117 return nil, fmt.Errorf("unable to parse client key: %v", err) 118 } 119 r.id, err = common.MakeClientID(k.Public()) 120 if err != nil { 121 return nil, fmt.Errorf("unable to create clientID: %v", err) 122 } 123 log.Infof("Using client id: %v", r.id) 124 } 125 126 if cc, err := cfg.PersistenceHandler.ReadCommunicatorConfig(); err == nil { 127 if cc != nil { 128 r.cc = cc 129 } 130 } else { 131 log.Errorf("Error reading communicator config, ignoring: %v", err) 132 } 133 134 if err := r.Sync(); err != nil { 135 return nil, fmt.Errorf("unable to write initial writeback: %v", err) 136 } 137 r.syncTicker = time.NewTicker(time.Minute) 138 r.done = make(chan bool) 139 go r.syncLoop() 140 141 return &r, nil 142 } 143 144 // Rekey creates a new private key and identity for the client. 145 func (m *Manager) Rekey() (err error) { 146 defer func() { 147 m.stats.AfterRekey(err) 148 }() 149 150 k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 151 if err != nil { 152 return fmt.Errorf("unable to generate new key: %v", err) 153 } 154 bytes, err := x509.MarshalECPrivateKey(k) 155 if err != nil { 156 return fmt.Errorf("unable to marshal new key: %v", err) 157 } 158 id, err := common.MakeClientID(k.Public()) 159 if err != nil { 160 return fmt.Errorf("unable to create client id: %v", err) 161 } 162 163 m.lock.Lock() 164 m.state.ClientKey = bytes 165 m.id = id 166 m.dirty = true 167 m.lock.Unlock() 168 169 log.Infof("Using new client id: %v", id) 170 171 return nil 172 } 173 174 func (m *Manager) needsSync() bool { 175 m.lock.RLock() 176 defer m.lock.RUnlock() 177 return m.dirty 178 } 179 180 func (m *Manager) doSync() (err error) { 181 defer func() { 182 m.stats.AfterConfigSync(err) 183 }() 184 185 m.lock.Lock() 186 defer m.lock.Unlock() 187 188 if err = m.cfg.PersistenceHandler.WriteState(m.state); err != nil { 189 return fmt.Errorf("Failed to sync state to writeback: %v", err) 190 } 191 m.dirty = false 192 return nil 193 } 194 195 // Sync writes the current dynamic state to the writeback location. This saves the current state, so changing data (e.g. the deduplication nonces) is persisted across restarts. 196 func (m *Manager) Sync() error { 197 if !m.needsSync() { 198 return nil 199 } 200 return m.doSync() 201 } 202 203 // AddRevokedSerials takes a list of revoked certificate serial numbers and adds 204 // them to the configuration's list. 205 func (m *Manager) AddRevokedSerials(revoked [][]byte) { 206 if len(revoked) == 0 { 207 return 208 } 209 210 m.lock.Lock() 211 defer m.lock.Unlock() 212 213 for _, serial := range revoked { 214 ss := string(serial) 215 if !m.revokedSerials[ss] { 216 m.revokedSerials[ss] = true 217 m.state.RevokedCertSerials = append(m.state.RevokedCertSerials, serial) 218 m.dirty = true 219 } 220 } 221 } 222 223 // Stop shuts down the Manager, in particular it will stop sychronizing to the 224 // writeback file. 225 func (m *Manager) Stop() { 226 if m.syncTicker != nil { 227 m.syncTicker.Stop() 228 close(m.done) 229 } 230 } 231 232 func (m *Manager) syncLoop() { 233 for { 234 select { 235 case <-m.syncTicker.C: 236 m.Sync() 237 case <-m.done: 238 return 239 } 240 } 241 } 242 243 // ClientID returns the current client identifier. 244 func (m *Manager) ClientID() common.ClientID { 245 m.lock.RLock() 246 defer m.lock.RUnlock() 247 return m.id 248 } 249 250 // RecordRunningService adds name to the list of services which this client is 251 // currently running. This list will be included when sending a ClientInfo 252 // record to the server. The optional parameter sig should be set when the 253 // configuration was signed to emake it clear to the server which instance of 254 // the service is running. 255 func (m *Manager) RecordRunningService(name string, sig []byte) { 256 m.lock.Lock() 257 defer m.lock.Unlock() 258 m.runningServices[name] = sig 259 m.dirty = true 260 } 261 262 // SequencingNonce returns the most recent sequencing nonce known to the client. 263 func (m *Manager) SequencingNonce() uint64 { 264 m.lock.RLock() 265 defer m.lock.RUnlock() 266 return m.state.SequencingNonce 267 } 268 269 // SetSequencingNonce sets the sequencing nonce received from the server. 270 func (m *Manager) SetSequencingNonce(n uint64) { 271 m.lock.Lock() 272 defer m.lock.Unlock() 273 m.state.SequencingNonce = n 274 m.dirty = true 275 } 276 277 // SendConfigUpdate causes a ClientInfo message capturing the current configuration 278 // to be sent to the server. 279 func (m *Manager) SendConfigUpdate() { 280 m.lock.RLock() 281 defer m.lock.RUnlock() 282 283 info := fspb.ClientInfoData{ 284 Labels: m.cfg.ClientLabels, 285 } 286 for s, sig := range m.runningServices { 287 info.Services = append(info.Services, 288 &fspb.ClientInfoData_ServiceID{ 289 Name: s, 290 Signature: sig, 291 }) 292 } 293 m.configChanges <- &info 294 } 295 296 // CurrentState returns the client's current dynamic state. 297 func (m *Manager) CurrentState() *clpb.ClientState { 298 m.lock.RLock() 299 defer m.lock.RUnlock() 300 301 return proto.Clone(m.state).(*clpb.ClientState) 302 } 303 304 // Configuration returns the configuration struct used to create this client. 305 func (m *Manager) Configuration() *config.Configuration { 306 return m.cfg 307 } 308 309 // CommunicatorConfig returns the communicator configuration that the client 310 // is configured to use. 311 func (m *Manager) CommunicatorConfig() *clpb.CommunicatorConfig { 312 return m.cc 313 } 314 315 // ChainRevoked returns true if any certificate in provided slice has been revoked. 316 func (m *Manager) ChainRevoked(chain []*x509.Certificate) bool { 317 m.lock.RLock() 318 defer m.lock.RUnlock() 319 320 for _, c := range chain { 321 if m.revokedSerials[string(c.SerialNumber.Bytes())] { 322 return true 323 } 324 } 325 return false 326 } 327 328 // Labels returns the labels built into this client. 329 func (m *Manager) Labels() []*fspb.Label { 330 return m.cfg.ClientLabels 331 }