diff --git a/changes/21866-startup-expired-abm-cert b/changes/21866-startup-expired-abm-cert new file mode 100644 index 0000000000..f9e74bb641 --- /dev/null +++ b/changes/21866-startup-expired-abm-cert @@ -0,0 +1,2 @@ +- Fixed issue where Fleet server could start when expired ABM cerfificate was provided as server + config options. diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index c8773ba765..19dfd798aa 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -499,7 +499,18 @@ the way that the Fleet server works. mdmPushService = nanomdm_pushsvc.New(mdmStorage, mdmStorage, pushProviderFactory, nanoMDMLogger) } - // validate Apple APNs/SCEP config + checkMDMAssets := func(names []fleet.MDMAssetName) (bool, error) { + _, err = ds.GetAllMDMConfigAssetsByName(context.Background(), names) + if err != nil { + if fleet.IsNotFound(err) || errors.Is(err, mysql.ErrPartialResult) { + return false, nil + } + return false, err + } + return true, nil + } + + // reconcile Apple Business Manager configuration environment variables with the database if config.MDM.IsAppleAPNsSet() || config.MDM.IsAppleSCEPSet() { if !config.MDM.IsAppleAPNsSet() { initFatal(errors.New("Apple APNs MDM configuration must be provided when Apple SCEP is provided"), "validate Apple MDM") @@ -508,40 +519,63 @@ the way that the Fleet server works. } if len(config.Server.PrivateKey) == 0 { - initFatal(errors.New("inserting APNs and SCEP assets"), "missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + initFatal(errors.New("inserting MDM APNs and SCEP assets"), "missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") } + // parse the APNs and SCEP assets from the config _, apnsCertPEM, apnsKeyPEM, err := config.MDM.AppleAPNs() if err != nil { - initFatal(err, "validate Apple APNs certificate and key") + initFatal(err, "parse Apple APNs certificate and key from config") } - _, appleSCEPCertPEM, appleSCEPKeyPEM, err := config.MDM.AppleSCEP() if err != nil { - initFatal(err, "validate Apple SCEP certificate and key") + initFatal(err, "load Apple SCEP certificate and key from config") } - err = ds.InsertMDMConfigAssets(context.Background(), []fleet.MDMConfigAsset{ - {Name: fleet.MDMAssetAPNSCert, Value: apnsCertPEM}, - {Name: fleet.MDMAssetAPNSKey, Value: apnsKeyPEM}, - {Name: fleet.MDMAssetCACert, Value: appleSCEPCertPEM}, - {Name: fleet.MDMAssetCAKey, Value: appleSCEPKeyPEM}, - }) - if err != nil { - // duplicate key errors mean that we already - // have a value for those keys in the - // database, fail to initalize on other - // cases. - if !mysql.IsDuplicate(err) { - initFatal(err, "inserting MDM APNs and SCEP assets") - } + // first we'll check if the APNs and SCEP assets are already in the database and + // only insert config values if they're not already present in the database + toInsert := make([]fleet.MDMConfigAsset, 0, 4) - level.Warn(logger).Log("msg", "Your server already has stored SCEP and APNs certificates. Fleet will ignore any certificates provided via environment variables when this happens.") + // check DB for APNs assets + found, err := checkMDMAssets([]fleet.MDMAssetName{fleet.MDMAssetAPNSCert, fleet.MDMAssetAPNSKey}) + switch { + case err != nil: + initFatal(err, "reading APNs assets from database") + case !found: + toInsert = append(toInsert, fleet.MDMConfigAsset{Name: fleet.MDMAssetAPNSCert, Value: apnsCertPEM}, fleet.MDMConfigAsset{Name: fleet.MDMAssetAPNSKey, Value: apnsKeyPEM}) + default: + level.Warn(logger).Log("msg", "Your server already has stored APNs certificates. Fleet will ignore any certificates provided via environment variables when this happens.") + } + + // check DB for SCEP assets + found, err = checkMDMAssets([]fleet.MDMAssetName{fleet.MDMAssetCACert, fleet.MDMAssetCAKey}) + switch { + case err != nil: + initFatal(err, "reading SCEP assets from database") + case !found: + toInsert = append(toInsert, fleet.MDMConfigAsset{Name: fleet.MDMAssetCACert, Value: appleSCEPCertPEM}, fleet.MDMConfigAsset{Name: fleet.MDMAssetCAKey, Value: appleSCEPKeyPEM}) + default: + level.Warn(logger).Log("msg", "Your server already has stored SCEP certificates. Fleet will ignore any certificates provided via environment variables when this happens.") + } + + if len(toInsert) > 0 { + if len(config.Server.PrivateKey) == 0 { + initFatal(errors.New("inserting APNs and SCEP assets"), "missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key") + } + if err := ds.InsertMDMConfigAssets(context.Background(), toInsert); err != nil { + if mysql.IsDuplicate(err) { + // we already checked for existing assets so we should never have a duplicate key error here; we'll add a debug log just in case + level.Debug(logger).Log("msg", "unexpected duplicate key error inserting MDM APNs and SCEP assets") + } else { + initFatal(err, "inserting MDM APNs and SCEP assets") + } + } } } - // validate Apple BM config + // reconcile Apple Business Manager configuration environment variables with the database if config.MDM.IsAppleBMSet() { + // TODO: Confirm whether we should have any fatal license errors if !license.IsPremium() { initFatal(errors.New("Apple Business Manager configuration is only available in Fleet Premium"), "validate Apple BM") } @@ -552,36 +586,38 @@ the way that the Fleet server works. appleBM, err := config.MDM.AppleBM() if err != nil { - initFatal(err, "validate Apple BM token, certificate and key") + initFatal(err, "parse Apple BM token, certificate and key from config") } - err = ds.InsertMDMConfigAssets(context.Background(), []fleet.MDMConfigAsset{ - {Name: fleet.MDMAssetABMKey, Value: appleBM.KeyPEM}, - {Name: fleet.MDMAssetABMCert, Value: appleBM.CertPEM}, - }) - if err != nil { - // duplicate key errors mean that we already - // have a value for those keys in the - // database, fail to initalize on other - // cases. - if !mysql.IsDuplicate(err) { - initFatal(err, "inserting MDM ABM assets") - } + toInsert := make([]fleet.MDMConfigAsset, 0, 4) + found, err := checkMDMAssets([]fleet.MDMAssetName{fleet.MDMAssetABMKey, fleet.MDMAssetABMCert}) + switch { + case err != nil: + initFatal(err, "reading ABM assets from database") + case !found: + toInsert = append(toInsert, fleet.MDMConfigAsset{Name: fleet.MDMAssetABMKey, Value: appleBM.KeyPEM}, fleet.MDMConfigAsset{Name: fleet.MDMAssetABMCert, Value: appleBM.CertPEM}) + default: level.Warn(logger).Log("msg", "Your server already has stored ABM certificates and token. Fleet will ignore any certificates provided via environment variables when this happens.") - } else { - // insert the ABM token without any metdata, - // it'll be picked by the - // apple_mdm_dep_profile_assigner cron and - // backfilled - tok := &fleet.ABMToken{ - EncryptedToken: appleBM.EncryptedToken, - // 2000-01-01 is our "zero value" for time - RenewAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), - } - _, err = ds.InsertABMToken(context.Background(), tok) - if err != nil { - initFatal(err, "save ABM token") + } + + if len(toInsert) > 0 { + err := ds.InsertMDMConfigAssets(context.Background(), toInsert) + switch { + case err != nil && mysql.IsDuplicate(err): + // we already checked for existing assets so we should never have a duplicate key error here; we'll add a debug log just in case + level.Debug(logger).Log("msg", "unexpected duplicate key error inserting ABM assets") + case err != nil: + initFatal(err, "inserting ABM assets") + default: + // insert the ABM token without any metdata; it'll be picked by the + // apple_mdm_dep_profile_assigner cron and backfilled + if _, err := ds.InsertABMToken(context.Background(), &fleet.ABMToken{ + EncryptedToken: appleBM.EncryptedToken, + RenewAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), // 2000-01-01 is our "zero value" for time + }); err != nil { + initFatal(err, "save ABM token") + } } } } @@ -591,17 +627,6 @@ the way that the Fleet server works. initFatal(err, "loading app config") } - checkMDMAssets := func(names []fleet.MDMAssetName) (bool, error) { - _, err = ds.GetAllMDMConfigAssetsByName(context.Background(), names) - if err != nil { - if fleet.IsNotFound(err) || errors.Is(err, mysql.ErrPartialResult) { - return false, nil - } - return false, err - } - return true, nil - } - appCfg.MDM.EnabledAndConfigured = false appCfg.MDM.AppleBMEnabledAndConfigured = false if len(config.Server.PrivateKey) > 0 { @@ -612,7 +637,7 @@ the way that the Fleet server works. fleet.MDMAssetAPNSCert, }) if err != nil { - initFatal(err, "validating MDM assets from database") + initFatal(err, "loading MDM assets from database") } var appleBMCerts bool @@ -621,14 +646,14 @@ the way that the Fleet server works. fleet.MDMAssetABMKey, }) if err != nil { - initFatal(err, "validating MDM ABM assets from database") + initFatal(err, "loading MDM ABM assets from database") } if appleBMCerts { // the ABM certs are there, check if a token exists and if so, apple // BM is enabled and configured. count, err := ds.GetABMTokenCount(context.Background()) if err != nil { - initFatal(err, "validating MDM ABM token from database") + initFatal(err, "loading MDM ABM token from database") } appCfg.MDM.AppleBMEnabledAndConfigured = count > 0 } diff --git a/server/config/config.go b/server/config/config.go index 9e4cb6003a..3bc6a351fa 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -622,6 +622,7 @@ type CalendarConfig struct { func (c *CalendarConfig) AlwaysReloadEvent() bool { return c.alwaysReloadEvent } + func (c *CalendarConfig) SetAlwaysReloadEvent(value bool) { c.alwaysReloadEvent = value } @@ -714,8 +715,7 @@ func (m *MDMConfig) IsAppleBMSet() bool { return pair.IsSet() || m.AppleBMServerToken != "" || m.AppleBMServerTokenBytes != "" } -// AppleAPNs returns the parsed and validated TLS certificate for Apple APNs. -// It parses and validates it if it hasn't been done yet. +// AppleAPNs returns the parsed TLS certificate for Apple APNs. func (m *MDMConfig) AppleAPNs() (cert *tls.Certificate, pemCert, pemKey []byte, err error) { if m.appleAPNs == nil { pair := x509KeyPairConfig{ @@ -735,8 +735,7 @@ func (m *MDMConfig) AppleAPNs() (cert *tls.Certificate, pemCert, pemKey []byte, return m.appleAPNs, m.appleAPNsPEMCert, m.appleAPNsPEMKey, nil } -// AppleSCEP returns the parsed and validated TLS certificate for Apple SCEP. -// It parses and validates it if it hasn't been done yet. +// AppleSCEP returns the parsed TLS certificate for Apple SCEP. func (m *MDMConfig) AppleSCEP() (cert *tls.Certificate, pemCert, pemKey []byte, err error) { if m.appleSCEP == nil { pair := x509KeyPairConfig{ @@ -763,7 +762,7 @@ type ParsedAppleBM struct { Token *nanodep_client.OAuth1Tokens } -func decryptAndValidateABMToken(tokenBytes []byte, cert *x509.Certificate, keyPEM []byte) (*nanodep_client.OAuth1Tokens, error) { +func decryptABMToken(tokenBytes []byte, cert *x509.Certificate, keyPEM []byte) (*nanodep_client.OAuth1Tokens, error) { bmKey, err := tokenpki.RSAKeyFromPEM(keyPEM) if err != nil { return nil, fmt.Errorf("Apple BM configuration: parse private key: %w", err) @@ -776,14 +775,11 @@ func decryptAndValidateABMToken(tokenBytes []byte, cert *x509.Certificate, keyPE if err := json.Unmarshal(token, &jsonTok); err != nil { return nil, fmt.Errorf("Apple BM configuration: unmarshal JSON token: %w", err) } - if jsonTok.AccessTokenExpiry.Before(time.Now()) { - return nil, errors.New("Apple BM configuration: token is expired") - } return &jsonTok, nil } -// AppleBM returns the parsed, validated and decrypted server token for Apple -// Business Manager. It also parses and validates the Apple BM certificate and +// AppleBM returns the parsed and decrypted server token for Apple +// Business Manager. It also parses the Apple BM certificate and // private key in the process, in order to decrypt the token. func (m *MDMConfig) AppleBM() (*ParsedAppleBM, error) { if m.appleBMToken == nil { @@ -801,7 +797,7 @@ func (m *MDMConfig) AppleBM() (*ParsedAppleBM, error) { if err != nil { return nil, fmt.Errorf("Apple BM configuration: %w", err) } - jsonTok, err := decryptAndValidateABMToken(encToken, cert.Leaf, pair.keyBytes) + jsonTok, err := decryptABMToken(encToken, cert.Leaf, pair.keyBytes) if err != nil { return nil, err }