From d9be058f99c8dee0efe55d82a5b39c056704c799 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:26:43 +0100 Subject: [PATCH] [8.12] invalidate remote api keys on unenroll (#3154) (#3162) * invalidate remote api keys on unenroll (#3154) * invalidate remote api keys on unenroll * fix test * using latest es snapshot in tests * try with latest 8.12 snapshot * use new 8.13 snapshot * try without build id * invalidate remote api key after force unenroll * fix lint * added integration test to force unenroll * added integration test on unenroll * fix lint * use latest snapshot * try with 8.12-snapshot * change back version in version.go --- dev-tools/integration/.env | 2 +- internal/pkg/api/auth.go | 2 +- internal/pkg/api/handleAck.go | 100 +++---- internal/pkg/api/handleCheckin.go | 17 ++ internal/pkg/model/ext.go | 24 +- internal/pkg/model/ext_test.go | 26 +- .../remote_es_output_integration_test.go | 271 ++++++++++++++++-- version/version.go | 2 +- 8 files changed, 356 insertions(+), 88 deletions(-) diff --git a/dev-tools/integration/.env b/dev-tools/integration/.env index f515930b2..215fce5d6 100644 --- a/dev-tools/integration/.env +++ b/dev-tools/integration/.env @@ -1,4 +1,4 @@ -ELASTICSEARCH_VERSION=8.12.0-9bbde39d-SNAPSHOT +ELASTICSEARCH_VERSION=8.12.0-SNAPSHOT ELASTICSEARCH_USERNAME=elastic ELASTICSEARCH_PASSWORD=changeme TEST_ELASTICSEARCH_HOSTS=localhost:9200 diff --git a/internal/pkg/api/auth.go b/internal/pkg/api/auth.go index cc869207f..308bac264 100644 --- a/internal/pkg/api/auth.go +++ b/internal/pkg/api/auth.go @@ -170,7 +170,7 @@ func authAgent(r *http.Request, id *string, bulker bulk.Bulk, c cache.Cache) (*m // Update the cache to mark the api key id associated with this agent as not enabled c.SetAPIKey(*key, false) - return nil, ErrAgentInactive + return agent, ErrAgentInactive } return agent, nil diff --git a/internal/pkg/api/handleAck.go b/internal/pkg/api/handleAck.go index 489aefba5..a63b1a81b 100644 --- a/internal/pkg/api/handleAck.go +++ b/internal/pkg/api/handleAck.go @@ -567,62 +567,16 @@ func cleanRoles(roles json.RawMessage) (json.RawMessage, int, error) { } func (ack *AckT) invalidateAPIKeys(ctx context.Context, zlog zerolog.Logger, toRetireAPIKeyIDs []model.ToRetireAPIKeyIdsItems, skip string) { - ids := make([]string, 0, len(toRetireAPIKeyIDs)) - remoteIds := make(map[string][]string) - for _, k := range toRetireAPIKeyIDs { - if k.ID == skip || k.ID == "" { - continue - } - if k.Output != "" { - if remoteIds[k.Output] == nil { - remoteIds[k.Output] = make([]string, 0) - } - remoteIds[k.Output] = append(remoteIds[k.Output], k.ID) - } else { - ids = append(ids, k.ID) - } - } - if len(ids) > 0 { - zlog.Info().Strs("fleet.policy.apiKeyIDsToRetire", ids).Msg("Invalidate old API keys") - if err := ack.bulk.APIKeyInvalidate(ctx, ids...); err != nil { - zlog.Info().Err(err).Strs("ids", ids).Msg("Failed to invalidate API keys") - } - } - // using remote es bulker to invalidate api key - for outputName, outputIds := range remoteIds { - outputBulk := ack.bulk.GetBulker(outputName) - - if outputBulk == nil { - // read output config from .fleet-policies, not filtering by policy id as agent could be reassigned - policy, err := dl.QueryOutputFromPolicy(ctx, ack.bulk, outputName) - if err != nil || policy == nil { - zlog.Warn().Str("outputName", outputName).Any("ids", outputIds).Msg("Output policy not found, API keys will be orphaned") - } else { - outputBulk, _, err = ack.bulk.CreateAndGetBulker(ctx, zlog, outputName, policy.Data.Outputs) - if err != nil { - zlog.Warn().Str("outputName", outputName).Any("ids", outputIds).Msg("Failed to recreate output bulker, API keys will be orphaned") - } - } - } - if outputBulk != nil { - if err := outputBulk.APIKeyInvalidate(ctx, outputIds...); err != nil { - zlog.Info().Err(err).Strs("ids", outputIds).Str("outputName", outputName).Msg("Failed to invalidate API keys") - } - } - } + invalidateAPIKeys(ctx, zlog, ack.bulk, toRetireAPIKeyIDs, skip) } func (ack *AckT) handleUnenroll(ctx context.Context, zlog zerolog.Logger, agent *model.Agent) error { span, ctx := apm.StartSpan(ctx, "ackUnenroll", "process") defer span.End() - apiKeys := agent.APIKeyIDs() - if len(apiKeys) > 0 { - zlog = zlog.With().Strs(LogAPIKeyID, apiKeys).Logger() - if err := ack.bulk.APIKeyInvalidate(ctx, apiKeys...); err != nil { - return fmt.Errorf("handleUnenroll invalidate apikey: %w", err) - } - } + apiKeys := agent.APIKeyIDs() + zlog.Info().Any("fleet.policy.apiKeyIDsToRetire", apiKeys).Msg("handleUnenroll invalidate API keys") + ack.invalidateAPIKeys(ctx, zlog, apiKeys, "") now := time.Now().UTC().Format(time.RFC3339) doc := bulk.UpdateFields{ @@ -740,3 +694,49 @@ func makeUpdatePolicyBody(policyID string, newRev, coordIdx int64) []byte { return buf.Bytes() } + +func invalidateAPIKeys(ctx context.Context, zlog zerolog.Logger, bulk bulk.Bulk, toRetireAPIKeyIDs []model.ToRetireAPIKeyIdsItems, skip string) { + ids := make([]string, 0, len(toRetireAPIKeyIDs)) + remoteIds := make(map[string][]string) + for _, k := range toRetireAPIKeyIDs { + if k.ID == skip || k.ID == "" { + continue + } + if k.Output != "" { + if remoteIds[k.Output] == nil { + remoteIds[k.Output] = make([]string, 0) + } + remoteIds[k.Output] = append(remoteIds[k.Output], k.ID) + } else { + ids = append(ids, k.ID) + } + } + if len(ids) > 0 { + zlog.Info().Strs("fleet.policy.apiKeyIDsToRetire", ids).Msg("Invalidate old API keys") + if err := bulk.APIKeyInvalidate(ctx, ids...); err != nil { + zlog.Info().Err(err).Strs("ids", ids).Msg("Failed to invalidate API keys") + } + } + // using remote es bulker to invalidate api key + for outputName, outputIds := range remoteIds { + outputBulk := bulk.GetBulker(outputName) + + if outputBulk == nil { + // read output config from .fleet-policies, not filtering by policy id as agent could be reassigned + policy, err := dl.QueryOutputFromPolicy(ctx, bulk, outputName) + if err != nil || policy == nil { + zlog.Warn().Str("outputName", outputName).Any("ids", outputIds).Msg("Output policy not found, API keys will be orphaned") + } else { + outputBulk, _, err = bulk.CreateAndGetBulker(ctx, zlog, outputName, policy.Data.Outputs) + if err != nil { + zlog.Warn().Str("outputName", outputName).Any("ids", outputIds).Msg("Failed to recreate output bulker, API keys will be orphaned") + } + } + } + if outputBulk != nil { + if err := outputBulk.APIKeyInvalidate(ctx, outputIds...); err != nil { + zlog.Info().Err(err).Strs("ids", outputIds).Str("outputName", outputName).Msg("Failed to invalidate API keys") + } + } + } +} diff --git a/internal/pkg/api/handleCheckin.go b/internal/pkg/api/handleCheckin.go index 8512a637c..651f88581 100644 --- a/internal/pkg/api/handleCheckin.go +++ b/internal/pkg/api/handleCheckin.go @@ -119,6 +119,11 @@ func (ct *CheckinT) handleCheckin(zlog zerolog.Logger, w http.ResponseWriter, r agent, err := authAgent(r, &id, ct.bulker, ct.cache) if err != nil { + // invalidate remote API keys of force unenrolled agents + if errors.Is(err, ErrAgentInactive) && agent != nil { + ctx := zlog.WithContext(r.Context()) + invalidateAPIKeysOfInactiveAgent(ctx, zlog, ct.bulker, agent) + } return err } @@ -136,6 +141,18 @@ func (ct *CheckinT) handleCheckin(zlog zerolog.Logger, w http.ResponseWriter, r return ct.ProcessRequest(zlog, w, r, start, agent, newVer) } +func invalidateAPIKeysOfInactiveAgent(ctx context.Context, zlog zerolog.Logger, bulker bulk.Bulk, agent *model.Agent) { + remoteAPIKeys := make([]model.ToRetireAPIKeyIdsItems, 0) + apiKeys := agent.APIKeyIDs() + for _, key := range apiKeys { + if key.Output != "" { + remoteAPIKeys = append(remoteAPIKeys, key) + } + } + zlog.Info().Any("fleet.policy.apiKeyIDsToRetire", remoteAPIKeys).Msg("handleCheckin invalidate remote API keys") + invalidateAPIKeys(ctx, zlog, bulker, remoteAPIKeys, "") +} + // validatedCheckin is a struct to wrap all the things that validateRequest returns. type validatedCheckin struct { req *CheckinRequest diff --git a/internal/pkg/model/ext.go b/internal/pkg/model/ext.go index 3c587165c..132cdea13 100644 --- a/internal/pkg/model/ext.go +++ b/internal/pkg/model/ext.go @@ -44,22 +44,34 @@ func (a *Agent) CheckDifferentVersion(ver string) string { // APIKeyIDs returns all the API keys, the valid, in-use as well as the one // marked to be retired. -func (a *Agent) APIKeyIDs() []string { +func (a *Agent) APIKeyIDs() []ToRetireAPIKeyIdsItems { if a == nil { return nil } - keys := make([]string, 0, len(a.Outputs)+1) + keys := make([]ToRetireAPIKeyIdsItems, 0, len(a.Outputs)+1) if a.AccessAPIKeyID != "" { - keys = append(keys, a.AccessAPIKeyID) + keys = append(keys, ToRetireAPIKeyIdsItems{ + ID: a.AccessAPIKeyID, + Output: "", + RetiredAt: "", + }) } - for _, output := range a.Outputs { + for outputName, output := range a.Outputs { if output.APIKeyID != "" { - keys = append(keys, output.APIKeyID) + name := "" + if outputName != "default" { + name = outputName + } + keys = append(keys, ToRetireAPIKeyIdsItems{ + ID: output.APIKeyID, + Output: name, + RetiredAt: "", + }) } for _, key := range output.ToRetireAPIKeyIds { if key.ID != "" { - keys = append(keys, key.ID) + keys = append(keys, key) } } } diff --git a/internal/pkg/model/ext_test.go b/internal/pkg/model/ext_test.go index 74a472ae0..9adc14de5 100644 --- a/internal/pkg/model/ext_test.go +++ b/internal/pkg/model/ext_test.go @@ -88,7 +88,7 @@ func TestAgentAPIKeyIDs(t *testing.T) { tcs := []struct { name string agent Agent - want []string + want []ToRetireAPIKeyIdsItems }{ { name: "no API key marked to be retired", @@ -99,7 +99,9 @@ func TestAgentAPIKeyIDs(t *testing.T) { "p2": {APIKeyID: "p2_api_key_id"}, }, }, - want: []string{"access_api_key_id", "p1_api_key_id", "p2_api_key_id"}, + want: []ToRetireAPIKeyIdsItems{{ID: "access_api_key_id", Output: "", RetiredAt: ""}, + {ID: "p1_api_key_id", Output: "p1", RetiredAt: ""}, + {ID: "p2_api_key_id", Output: "p2", RetiredAt: ""}}, }, { name: "with API key marked to be retired", @@ -109,18 +111,22 @@ func TestAgentAPIKeyIDs(t *testing.T) { "p1": { APIKeyID: "p1_api_key_id", ToRetireAPIKeyIds: []ToRetireAPIKeyIdsItems{{ - ID: "p1_to_retire_key", + ID: "p1_to_retire_key", + Output: "remote", }}}, "p2": { APIKeyID: "p2_api_key_id", ToRetireAPIKeyIds: []ToRetireAPIKeyIdsItems{{ - ID: "p2_to_retire_key", + ID: "p2_to_retire_key", + Output: "remote", }}}, }, }, - want: []string{ - "access_api_key_id", "p1_api_key_id", "p2_api_key_id", - "p1_to_retire_key", "p2_to_retire_key"}, + want: []ToRetireAPIKeyIdsItems{{ID: "access_api_key_id", Output: "", RetiredAt: ""}, + {ID: "p1_api_key_id", Output: "p1", RetiredAt: ""}, + {ID: "p2_api_key_id", Output: "p2", RetiredAt: ""}, + {ID: "p1_to_retire_key", Output: "remote", RetiredAt: ""}, + {ID: "p2_to_retire_key", Output: "remote", RetiredAt: ""}}, }, { name: "API key empty", @@ -130,7 +136,7 @@ func TestAgentAPIKeyIDs(t *testing.T) { "p1": {APIKeyID: ""}, }, }, - want: []string{"access_api_key_id"}, + want: []ToRetireAPIKeyIdsItems{{ID: "access_api_key_id", Output: "", RetiredAt: ""}}, }, { name: "retired API key empty", @@ -144,8 +150,8 @@ func TestAgentAPIKeyIDs(t *testing.T) { }}}, }, }, - want: []string{ - "access_api_key_id", "p1_api_key_id"}, + want: []ToRetireAPIKeyIdsItems{{ID: "access_api_key_id", Output: "", RetiredAt: ""}, + {ID: "p1_api_key_id", Output: "p1", RetiredAt: ""}}, }, } diff --git a/internal/pkg/server/remote_es_output_integration_test.go b/internal/pkg/server/remote_es_output_integration_test.go index 9b78eb293..c4478f052 100644 --- a/internal/pkg/server/remote_es_output_integration_test.go +++ b/internal/pkg/server/remote_es_output_integration_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/elastic/fleet-server/v7/internal/pkg/apikey" + "github.com/elastic/fleet-server/v7/internal/pkg/bulk" "github.com/elastic/fleet-server/v7/internal/pkg/dl" "github.com/elastic/fleet-server/v7/internal/pkg/model" "github.com/gofrs/uuid" @@ -25,7 +26,11 @@ import ( "github.com/stretchr/testify/require" ) -func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key string, shouldHaveRemoveES bool) (string, string) { +const ( + remoteESHost = "localhost:9201" +) + +func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key string, shouldHaveRemoteES bool, actionType string) (string, string) { cli := cleanhttp.DefaultClient() var obj map[string]interface{} @@ -59,7 +64,10 @@ func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key strin require.True(t, ok, "expected action id to be string") typeRaw := action["type"] - require.Equal(t, "POLICY_CHANGE", typeRaw) + require.Equal(t, actionType, typeRaw) + if actionType != "POLICY_CHANGE" { + return "", actionID + } dataRaw := action["data"] data, ok := dataRaw.(map[string]interface{}) require.True(t, ok, "expected data to be map") @@ -68,7 +76,7 @@ func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key strin outputs, ok := policy["outputs"].(map[string]interface{}) require.True(t, ok, "expected outputs to be map") var remoteAPIKey string - if shouldHaveRemoveES { + if shouldHaveRemoteES { remoteES, ok := outputs["remoteES"].(map[string]interface{}) require.True(t, ok, "expected remoteES to be map") oType, ok := remoteES["type"].(string) @@ -84,6 +92,7 @@ func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key strin defaultAPIKey, ok := defaultOutput["api_key"].(string) require.True(t, ok, "expected defaultAPIKey to be string") require.NotEqual(t, remoteAPIKey, defaultAPIKey, "expected remote api key to be different than default") + return remoteAPIKey, actionID } @@ -121,16 +130,6 @@ func Ack(t *testing.T, ctx context.Context, srv *tserver, actionID, agentID, key } func Test_Agent_Remote_ES_Output(t *testing.T) { - enrollBody := `{ - "type": "PERMANENT", - "shared_id": "", - "enrollment_id": "", - "metadata": { - "user_provided": {}, - "local": {}, - "tags": [] - } - }` ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -141,7 +140,6 @@ func Test_Agent_Remote_ES_Output(t *testing.T) { t.Log("Create policy with remote ES output") var policyRemoteID = uuid.Must(uuid.NewV4()).String() - remoteESHost := "localhost:9201" var policyDataRemoteES = model.PolicyData{ Outputs: map[string]map[string]interface{}{ "default": { @@ -214,10 +212,10 @@ func Test_Agent_Remote_ES_Output(t *testing.T) { } }() - remoteAPIKey, actionID := Checkin(t, ctx, srvCopy, agentID, key, true) + remoteAPIKey, actionID := Checkin(t, ctx, srvCopy, agentID, key, true, "POLICY_CHANGE") apiKeyID := strings.Split(remoteAPIKey, ":")[0] - verifyRemoteAPIKey(t, ctx, remoteESHost, apiKeyID, false) + verifyRemoteAPIKey(t, ctx, apiKeyID, false) Ack(t, ctx, srvCopy, actionID, agentID, key) @@ -244,16 +242,16 @@ func Test_Agent_Remote_ES_Output(t *testing.T) { } t.Log("Checkin so that agent gets new policy revision") - _, actionID = Checkin(t, ctx, srvCopy, agentID, key, false) + _, actionID = Checkin(t, ctx, srvCopy, agentID, key, false, "POLICY_CHANGE") t.Log("Ack so that fleet triggers remote api key invalidate") Ack(t, ctx, srvCopy, actionID, agentID, key) - verifyRemoteAPIKey(t, ctx, remoteESHost, apiKeyID, true) + verifyRemoteAPIKey(t, ctx, apiKeyID, true) } -func verifyRemoteAPIKey(t *testing.T, ctx context.Context, remoteESHost, apiKeyID string, invalidated bool) { +func verifyRemoteAPIKey(t *testing.T, ctx context.Context, apiKeyID string, invalidated bool) { // need to wait a bit before querying the api key time.Sleep(time.Second) @@ -276,3 +274,238 @@ func verifyRemoteAPIKey(t *testing.T, ctx context.Context, remoteESHost, apiKeyI require.Contains(t, string(respString), fmt.Sprintf("\"invalidated\":%t", invalidated)) } + +func Test_Agent_Remote_ES_Output_ForceUnenroll(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Start test server + srv, err := startTestServer(t, ctx, policyData) + require.NoError(t, err) + + t.Log("Create policy with remote ES output") + + var policyRemoteID = uuid.Must(uuid.NewV4()).String() + var policyDataRemoteES = model.PolicyData{ + Outputs: map[string]map[string]interface{}{ + "default": { + "type": "elasticsearch", + }, + "remoteES": { + "type": "remote_elasticsearch", + "hosts": []string{remoteESHost}, + "service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"), + }, + }, + OutputPermissions: json.RawMessage(`{"default": {}, "remoteES": {}}`), + Inputs: []map[string]interface{}{}, + Agent: json.RawMessage(`{"monitoring": {"use_output":"remoteES"}}`), + } + + _, err = dl.CreatePolicy(ctx, srv.bulker, model.Policy{ + PolicyID: policyRemoteID, + RevisionIdx: 1, + DefaultFleetServer: false, + Data: &policyDataRemoteES, + }) + if err != nil { + t.Fatal(err) + } + + t.Log("Create API key and enrollment key for new policy") + + newKey, err := apikey.Create(ctx, srv.bulker.Client(), "default", "", "true", []byte(`{ + "fleet-apikey-enroll": { + "cluster": [], + "index": [], + "applications": [{ + "application": "fleet", + "privileges": ["no-privileges"], + "resources": ["*"] + }] + } + }`), map[string]interface{}{ + "managed_by": "fleet", + "managed": true, + "type": "enroll", + "policy_id": policyRemoteID, + }) + if err != nil { + t.Fatal(err) + } + + _, err = dl.CreateEnrollmentAPIKey(ctx, srv.bulker, model.EnrollmentAPIKey{ + Name: "RemoteES", + APIKey: newKey.Key, + APIKeyID: newKey.ID, + PolicyID: policyRemoteID, + Active: true, + }) + if err != nil { + t.Fatal(err) + } + + t.Log("Enroll agent") + srvCopy := srv + srvCopy.enrollKey = newKey.Token() + agentID, key := EnrollAgent(enrollBody, t, ctx, srvCopy) + + // cleanup + defer func() { + err = srv.bulker.Delete(ctx, dl.FleetAgents, agentID) + if err != nil { + t.Log("could not clean up agent") + } + }() + + remoteAPIKey, actionID := Checkin(t, ctx, srvCopy, agentID, key, true, "POLICY_CHANGE") + apiKeyID := strings.Split(remoteAPIKey, ":")[0] + + verifyRemoteAPIKey(t, ctx, apiKeyID, false) + + Ack(t, ctx, srvCopy, actionID, agentID, key) + + t.Log("Force Unenroll agent - set inactive") + + doc := bulk.UpdateFields{ + "active": false, + } + body, err := doc.Marshal() + require.NoError(t, err) + err = srv.bulker.Update(ctx, dl.FleetAgents, agentID, body, bulk.WithRefresh(), bulk.WithRetryOnConflict(3)) + require.NoError(t, err) + + t.Log("Checkin so that invalidate logic runs") + + cli := cleanhttp.DefaultClient() + + t.Logf("Fake a checkin for agent %s", agentID) + req, err := http.NewRequestWithContext(ctx, "POST", srv.baseURL()+"/api/fleet/agents/"+agentID+"/checkin", strings.NewReader(checkinBody)) + require.NoError(t, err) + req.Header.Set("Authorization", "ApiKey "+key) + req.Header.Set("User-Agent", "elastic agent "+serverVersion) + req.Header.Set("Content-Type", "application/json") + res, err := cli.Do(req) + require.NoError(t, err) + defer res.Body.Close() + + t.Log("Verify that remote API key is invalidated") + verifyRemoteAPIKey(t, ctx, apiKeyID, true) + +} + +func Test_Agent_Remote_ES_Output_Unenroll(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Start test server + srv, err := startTestServer(t, ctx, policyData) + require.NoError(t, err) + + t.Log("Create policy with remote ES output") + + var policyRemoteID = uuid.Must(uuid.NewV4()).String() + var policyDataRemoteES = model.PolicyData{ + Outputs: map[string]map[string]interface{}{ + "default": { + "type": "elasticsearch", + }, + "remoteES": { + "type": "remote_elasticsearch", + "hosts": []string{remoteESHost}, + "service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"), + }, + }, + OutputPermissions: json.RawMessage(`{"default": {}, "remoteES": {}}`), + Inputs: []map[string]interface{}{}, + Agent: json.RawMessage(`{"monitoring": {"use_output":"remoteES"}}`), + } + + _, err = dl.CreatePolicy(ctx, srv.bulker, model.Policy{ + PolicyID: policyRemoteID, + RevisionIdx: 1, + DefaultFleetServer: false, + Data: &policyDataRemoteES, + }) + if err != nil { + t.Fatal(err) + } + + t.Log("Create API key and enrollment key for new policy") + + newKey, err := apikey.Create(ctx, srv.bulker.Client(), "default", "", "true", []byte(`{ + "fleet-apikey-enroll": { + "cluster": [], + "index": [], + "applications": [{ + "application": "fleet", + "privileges": ["no-privileges"], + "resources": ["*"] + }] + } + }`), map[string]interface{}{ + "managed_by": "fleet", + "managed": true, + "type": "enroll", + "policy_id": policyRemoteID, + }) + if err != nil { + t.Fatal(err) + } + + _, err = dl.CreateEnrollmentAPIKey(ctx, srv.bulker, model.EnrollmentAPIKey{ + Name: "RemoteES", + APIKey: newKey.Key, + APIKeyID: newKey.ID, + PolicyID: policyRemoteID, + Active: true, + }) + if err != nil { + t.Fatal(err) + } + + t.Log("Enroll agent") + srvCopy := srv + srvCopy.enrollKey = newKey.Token() + agentID, key := EnrollAgent(enrollBody, t, ctx, srvCopy) + + // cleanup + defer func() { + err = srv.bulker.Delete(ctx, dl.FleetAgents, agentID) + if err != nil { + t.Log("could not clean up agent") + } + }() + + remoteAPIKey, actionID := Checkin(t, ctx, srvCopy, agentID, key, true, "POLICY_CHANGE") + apiKeyID := strings.Split(remoteAPIKey, ":")[0] + + verifyRemoteAPIKey(t, ctx, apiKeyID, false) + + Ack(t, ctx, srvCopy, actionID, agentID, key) + + t.Log("Unenroll agent") + + doc := fmt.Sprintf(`{ + "action_id": "unenroll_action1", + "agents": ["%s"], + "@timestamp": "2023-12-11T13:00:00.000Z", + "expiration": "2099-01-10T13:14:36.565Z", + "type": "UNENROLL" + }`, agentID) + client := srv.bulker.Client() + res, err := client.Index(".fleet-actions", strings.NewReader(doc)) + require.NoError(t, err) + require.Equal(t, 201, res.StatusCode) + + t.Log("Checkin so that agent gets unenroll action") + _, actionID = Checkin(t, ctx, srvCopy, agentID, key, false, "UNENROLL") + t.Log(actionID) + + t.Log("Ack so that fleet triggers remote api key invalidate") + Ack(t, ctx, srvCopy, actionID, agentID, key) + + t.Log("Verify that remote API key is invalidated") + verifyRemoteAPIKey(t, ctx, apiKeyID, true) + +} diff --git a/version/version.go b/version/version.go index d2191e636..9c1c789a0 100644 --- a/version/version.go +++ b/version/version.go @@ -6,4 +6,4 @@ package version // DefaultVersion is the current release version of Fleet-server, this version must match the // Elastic Agent version. -const DefaultVersion = "8.13.0" +const DefaultVersion = "8.12.0"