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  }