func shouldFleetEnroll()

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
}