You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.8 KiB
141 lines
4.8 KiB
|
2 months ago
|
package mock
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"reflect"
|
||
|
|
"strings"
|
||
|
|
"tera-clone/services/tera-api/internal/alarmdelivery"
|
||
|
|
"tera-clone/services/tera-api/internal/networkconfig"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ReconcileStartupNetwork checks the owning daemon's committed configuration
|
||
|
|
// and actual interfaces. Pending user changes are never applied at startup.
|
||
|
|
func (s *Store) ReconcileStartupNetwork(ctx context.Context) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
desired, err := networkconfig.Normalize(networkConfigForController(s.runningNetwork))
|
||
|
|
s.mu.Unlock()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
status, err := s.networkController.Status(ctx)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if status.State == "rollback_failed" || status.State == "pending_confirmation" {
|
||
|
|
return errors.New("network recovery not complete")
|
||
|
|
}
|
||
|
|
if status.Current == nil || !reflect.DeepEqual(desired, *status.Current) || verifyStartupNetworkInterfaces(desired, status) != nil {
|
||
|
|
result, err := s.networkController.Apply(ctx, desired)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
status, err = s.networkController.Status(ctx)
|
||
|
|
if err == nil {
|
||
|
|
err = verifyStartupNetworkInterfaces(desired, status)
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
_, rollbackErr := s.networkController.Rollback(ctx, result.TransactionID)
|
||
|
|
return errors.Join(err, rollbackErr)
|
||
|
|
}
|
||
|
|
status, err = s.networkController.Confirm(ctx, result.TransactionID)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return verifyStartupNetworkInterfaces(desired, status)
|
||
|
|
}
|
||
|
|
|
||
|
|
func verifyStartupNetworkInterfaces(config networkconfig.Config, status networkconfig.Status) error {
|
||
|
|
for _, wanted := range config.Interfaces {
|
||
|
|
found := false
|
||
|
|
for _, actual := range status.Interfaces {
|
||
|
|
if actual.Device != wanted.Device {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
found = actual.Link != "unknown"
|
||
|
|
if config.Advanced.Mode == networkconfig.AdvancedModeSingle && wanted.AddressMode == networkconfig.AddressModeStatic && wanted.Address != "" {
|
||
|
|
matched := false
|
||
|
|
for _, address := range actual.Addresses {
|
||
|
|
if address == wanted.Address {
|
||
|
|
matched = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !matched {
|
||
|
|
return fmt.Errorf("network address not applied: %s", wanted.Name)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !found {
|
||
|
|
return fmt.Errorf("network interface not observed: %s", wanted.Name)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Policies are consumed directly from the restored running store per request.
|
||
|
|
// Validate enabled policies and credential references without sending alarms or
|
||
|
|
// attempting an external login as a side effect of startup.
|
||
|
|
func (s *Store) VerifyStartupPolicies(ctx context.Context) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
for _, err := range []error{validateAlarmDeliveryPolicy(s.runningAlarmDelivery), validateEmailNotification(s.runningEmail), validateRemoteSyslog(s.runningSyslog), validateExternalAuth(s.runningExternalAuth), validateLogRetention(s.runningRetention)} {
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if s.runningEmail.Enabled && s.runningEmail.AuthEnabled && !s.secrets.Has(configSecretKey("running", "smtp/password")) {
|
||
|
|
return errors.New("SMTP credential unavailable")
|
||
|
|
}
|
||
|
|
for _, target := range s.runningSNMPTraps {
|
||
|
|
if !target.Enabled {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
prefix := "snmp/trap/" + strings.ToLower(target.TargetID)
|
||
|
|
if !s.secrets.Has(configSecretKey("running", prefix+"/credential")) {
|
||
|
|
return errors.New("SNMP trap credential unavailable")
|
||
|
|
}
|
||
|
|
if target.PrivacyProtocol != "" && target.PrivacyProtocol != "none" && !s.secrets.Has(configSecretKey("running", prefix+"/privacy")) {
|
||
|
|
return errors.New("SNMP trap privacy credential unavailable")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if s.runningExternalAuth.Enabled {
|
||
|
|
for kind, servers := range map[string][]externalAuthServerConfig{"radius": s.runningExternalAuth.RadiusServers, "tacacs": s.runningExternalAuth.TACACSServers} {
|
||
|
|
for _, server := range servers {
|
||
|
|
if server.Enabled && !s.secrets.Has(externalAuthSecretKey("running", kind, server.ServerID)) {
|
||
|
|
return errors.New("external authentication credential unavailable")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, rule := range s.runningAlarms {
|
||
|
|
if !rule.Enabled {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
channels, syslog, email, traps := s.availableDeliveryTargetsLocked(rule.Delivery, "startup validation")
|
||
|
|
// Disabled delivery targets are intentionally inactive; enabled targets
|
||
|
|
// must form valid requests before readiness can be reported.
|
||
|
|
if len(channels) == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
_, err := alarmdelivery.NormalizeRequest(alarmdelivery.Request{Message: "startup validation", Channels: channels, Syslog: syslog, Email: email, SNMPTraps: traps, RetryCount: s.runningAlarmDelivery.RetryCount, RetryIntervalSeconds: s.runningAlarmDelivery.RetryIntervalSeconds, QueueLimit: min(s.runningAlarmDelivery.PendingQueueLimit, 1000)})
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) ReconcileStartupLogs(ctx context.Context) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return s.applyJournalRetentionLocked(s.runningRetention)
|
||
|
|
}
|