in internal/pkg/agent/cmd/container.go [1027:1180]
func shouldFleetEnroll(setupCfg setupConfig) (bool, error) {
if !setupCfg.Fleet.Enroll {
// Enrollment is explicitly disabled in the setup configuration.
return false, nil
}
if setupCfg.Fleet.Force {
// Enrollment is explicitly enforced by the setup configuration.
return true, nil
}
agentCfgFilePath := paths.AgentConfigFile()
_, err := statAgentConfigFile(agentCfgFilePath)
if os.IsNotExist(err) {
// The agent configuration file does not exist, so enrollment is required.
return true, nil
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
store, err := newEncryptedDiskStore(ctx, agentCfgFilePath)
if err != nil {
return false, fmt.Errorf("failed to instantiate encrypted disk store: %w", err)
}
reader, err := store.Load()
if err != nil {
return false, fmt.Errorf("failed to load from disk store: %w", err)
}
cfg, err := config.NewConfigFrom(reader)
if err != nil {
return false, fmt.Errorf("failed to read from disk store: %w", err)
}
storedConfig, err := configuration.NewFromConfig(cfg)
if err != nil {
return false, fmt.Errorf("failed to read from disk store: %w", err)
}
// Check if enrolling with a specifically defined Elastic Agent ID.
// If the ID's don't match then it needs to enroll.
if setupCfg.Fleet.ID != "" && (storedConfig.Fleet.Info == nil || storedConfig.Fleet.Info.ID != setupCfg.Fleet.ID) {
// ID is a mismatch
return true, nil
}
storedFleetHosts := storedConfig.Fleet.Client.GetHosts()
if len(storedFleetHosts) == 0 || !slices.Contains(storedFleetHosts, setupCfg.Fleet.URL) {
// The Fleet URL in the setup does not exist in the stored configuration, so enrollment is required.
return true, nil
}
// Evaluate the stored enrollment token hash against the setup enrollment token if both are present.
// Note that when "upgrading" from an older agent version the enrollment token hash will not exist
// in the stored configuration.
if len(storedConfig.Fleet.EnrollmentTokenHash) > 0 && len(setupCfg.Fleet.EnrollmentToken) > 0 {
enrollmentHashBytes, err := base64.StdEncoding.DecodeString(storedConfig.Fleet.EnrollmentTokenHash)
if err != nil {
return false, fmt.Errorf("failed to decode enrollment token hash: %w", err)
}
err = crypto.ComparePBKDF2HashAndPassword(enrollmentHashBytes, []byte(setupCfg.Fleet.EnrollmentToken))
switch {
case errors.Is(err, crypto.ErrMismatchedHashAndPassword):
// The stored enrollment token hash does not match the new token, so enrollment is required.
return true, nil
case err != nil:
return false, fmt.Errorf("failed to compare enrollment token hash: %w", err)
}
}
// Evaluate the stored replace token hash against the setup replace token if both are present.
// Note that when "upgrading" from an older agent version the replace token hash will not exist
// in the stored configuration.
if len(storedConfig.Fleet.ReplaceTokenHash) > 0 && len(setupCfg.Fleet.ReplaceToken) > 0 {
replaceHashBytes, err := base64.StdEncoding.DecodeString(storedConfig.Fleet.ReplaceTokenHash)
if err != nil {
return false, fmt.Errorf("failed to decode replace token hash: %w", err)
}
err = crypto.ComparePBKDF2HashAndPassword(replaceHashBytes, []byte(setupCfg.Fleet.ReplaceToken))
switch {
case errors.Is(err, crypto.ErrMismatchedHashAndPassword):
// The stored enrollment token hash does not match the new token, so enrollment is required.
return true, nil
case err != nil:
return false, fmt.Errorf("failed to compare replace token hash: %w", err)
}
}
// Validate the stored API token to check if the agent is still authorized with Fleet.
log, err := logger.New("fleet_client", false)
if err != nil {
return false, fmt.Errorf("failed to create logger: %w", err)
}
fc, err := newFleetClient(log, storedConfig.Fleet.AccessAPIKey, storedConfig.Fleet.Client)
if err != nil {
return false, fmt.Errorf("failed to create fleet client: %w", err)
}
// Perform an ACK request with **empty events** to verify the validity of the API token.
// If the agent has been manually un-enrolled through the Kibana UI, the ACK request will fail due to an invalid API token.
// In such cases, the agent should automatically re-enroll and "recover" their enrollment status without manual intervention,
// maintaining seamless operation.
err = ackFleet(ctx, fc, storedConfig.Fleet.Info.ID)
switch {
case errors.Is(err, fleetclient.ErrInvalidAPIKey):
// The API key is invalid, so enrollment is required.
return true, nil
case err != nil:
return false, fmt.Errorf("failed to validate api token: %w", err)
}
saveConfig := false
// Update the stored enrollment token hash if there is no previous enrollment token hash
// (can happen when "upgrading" from an older version of the agent) and setup enrollment token is present.
if len(storedConfig.Fleet.EnrollmentTokenHash) == 0 && len(setupCfg.Fleet.EnrollmentToken) > 0 {
enrollmentHashBytes, err := crypto.GeneratePBKDF2FromPassword([]byte(setupCfg.Fleet.EnrollmentToken))
if err != nil {
return false, errors.New("failed to generate enrollment token hash")
}
enrollmentTokenHash := base64.StdEncoding.EncodeToString(enrollmentHashBytes)
storedConfig.Fleet.EnrollmentTokenHash = enrollmentTokenHash
saveConfig = true
}
// Update the stored replace token hash if there is no previous replace token hash
// (can happen when "upgrading" from an older version of the agent) and setup replace token is present.
if len(storedConfig.Fleet.ReplaceTokenHash) == 0 && len(setupCfg.Fleet.ReplaceToken) > 0 {
replaceHashBytes, err := crypto.GeneratePBKDF2FromPassword([]byte(setupCfg.Fleet.ReplaceToken))
if err != nil {
return false, errors.New("failed to generate replace token hash")
}
replaceTokenHash := base64.StdEncoding.EncodeToString(replaceHashBytes)
storedConfig.Fleet.ReplaceTokenHash = replaceTokenHash
saveConfig = true
}
if saveConfig {
data, err := yaml.Marshal(storedConfig)
if err != nil {
return false, errors.New("could not marshal config")
}
if err := safelyStoreAgentInfo(store, bytes.NewReader(data)); err != nil {
return false, fmt.Errorf("failed to store agent config: %w", err)
}
}
return false, nil
}