diff --git a/agent/consul/leader.go b/agent/consul/leader.go index 1baa8cbcdc..d4ae8b1d73 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -586,6 +586,55 @@ func (s *Server) initializeManagementToken(name, secretID string) error { return nil } +func (s *Server) upsertManagementToken(name, secretID string) error { + state := s.fsm.State() + if _, err := uuid.ParseUUID(secretID); err != nil { + s.logger.Warn("Configuring a non-UUID management token is deprecated") + } + + _, token, err := state.ACLTokenGetBySecret(nil, secretID, nil) + if err != nil { + return fmt.Errorf("failed to get %s: %v", name, err) + } + + if token != nil { + return nil + } + + accessor, err := lib.GenerateUUID(s.checkTokenUUID) + if err != nil { + return fmt.Errorf("failed to generate the accessor ID for %s: %v", name, err) + } + + newToken := structs.ACLToken{ + AccessorID: accessor, + SecretID: secretID, + Description: name, + Policies: []structs.ACLTokenPolicyLink{ + { + ID: structs.ACLPolicyGlobalManagementID, + }, + }, + CreateTime: time.Now(), + Local: false, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + } + + newToken.SetHash(true) + + req := structs.ACLTokenBatchSetRequest{ + Tokens: structs.ACLTokens{&newToken}, + CAS: false, + } + if _, err := s.raftApply(structs.ACLTokenSetRequestType, &req); err != nil { + return fmt.Errorf("failed to create %s: %v", name, err) + } + + s.logger.Info("Created ACL token", "description", name) + + return nil +} + func (s *Server) insertAnonymousToken() error { state := s.fsm.State() _, token, err := state.ACLTokenGetBySecret(nil, anonymousToken, nil) diff --git a/agent/consul/server.go b/agent/consul/server.go index 1415d7194c..6517b15992 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -588,6 +588,15 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, Logger: logger.Named("hcp_manager"), SCADAProvider: flat.HCP.Provider, TelemetryProvider: flat.HCP.TelemetryProvider, + ManagementTokenUpserterFn: func(name, secretId string) error { + if s.IsLeader() { + // Idea for improvement: Upsert a token with a well-known accessorId here instead + // of a randomly generated one. This would prevent any possible insertion collision between + // this and the insertion that happens during the ACL initialization process (initializeACLs function) + return s.upsertManagementToken(name, secretId) + } + return nil + }, }) var recorder *middleware.RequestRecorder diff --git a/agent/hcp/manager.go b/agent/hcp/manager.go index d53e7e7695..84b9f5f7da 100644 --- a/agent/hcp/manager.go +++ b/agent/hcp/manager.go @@ -26,9 +26,12 @@ type ManagerConfig struct { SCADAProvider scada.Provider TelemetryProvider *hcpProviderImpl - StatusFn StatusCallback - MinInterval time.Duration - MaxInterval time.Duration + StatusFn StatusCallback + // Idempotent function to upsert the HCP management token. This will be called periodically in + // the manager's main loop. + ManagementTokenUpserterFn ManagementTokenUpserter + MinInterval time.Duration + MaxInterval time.Duration Logger hclog.Logger } @@ -54,6 +57,7 @@ func (cfg *ManagerConfig) nextHeartbeat() time.Duration { } type StatusCallback func(context.Context) (hcpclient.ServerStatus, error) +type ManagementTokenUpserter func(name, secretId string) error type Manager struct { logger hclog.Logger @@ -111,6 +115,14 @@ func (m *Manager) Run(ctx context.Context) error { // main loop for { + // Check for configured management token from HCP and upsert it if found + if hcpManagement := m.cfg.CloudConfig.ManagementToken; len(hcpManagement) > 0 { + upsertTokenErr := m.cfg.ManagementTokenUpserterFn("HCP Management Token", hcpManagement) + if upsertTokenErr != nil { + m.logger.Error("failed to upsert HCP management token", "err", upsertTokenErr) + } + } + m.cfgMu.RLock() cfg := m.cfg m.cfgMu.RUnlock() diff --git a/agent/hcp/manager_test.go b/agent/hcp/manager_test.go index ad9d7461c1..e4195a6df3 100644 --- a/agent/hcp/manager_test.go +++ b/agent/hcp/manager_test.go @@ -22,12 +22,18 @@ func TestManager_Run(t *testing.T) { statusF := func(ctx context.Context) (hcpclient.ServerStatus, error) { return hcpclient.ServerStatus{ID: t.Name()}, nil } + upsertManagementTokenCalled := make(chan struct{}, 1) + upsertManagementTokenF := func(name, secretID string) error { + upsertManagementTokenCalled <- struct{}{} + return nil + } updateCh := make(chan struct{}, 1) client.EXPECT().PushServerStatus(mock.Anything, &hcpclient.ServerStatus{ID: t.Name()}).Return(nil).Once() cloudCfg := config.CloudConfig{ - ResourceID: "organization/85702e73-8a3d-47dc-291c-379b783c5804/project/8c0547c0-10e8-1ea2-dffe-384bee8da634/hashicorp.consul.global-network-manager.cluster/test", - NodeID: "node-1", + ResourceID: "organization/85702e73-8a3d-47dc-291c-379b783c5804/project/8c0547c0-10e8-1ea2-dffe-384bee8da634/hashicorp.consul.global-network-manager.cluster/test", + NodeID: "node-1", + ManagementToken: "fake-token", } scadaM := scada.NewMockProvider(t) scadaM.EXPECT().UpdateHCPConfig(cloudCfg).Return(nil) @@ -52,12 +58,13 @@ func TestManager_Run(t *testing.T) { mockTelemetryCfg, nil).Maybe() mgr := NewManager(ManagerConfig{ - Client: client, - Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), - StatusFn: statusF, - CloudConfig: cloudCfg, - SCADAProvider: scadaM, - TelemetryProvider: telemetryProvider, + Client: client, + Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard}), + StatusFn: statusF, + ManagementTokenUpserterFn: upsertManagementTokenF, + CloudConfig: cloudCfg, + SCADAProvider: scadaM, + TelemetryProvider: telemetryProvider, }) mgr.testUpdateSent = updateCh ctx, cancel := context.WithCancel(context.Background()) @@ -76,6 +83,7 @@ func TestManager_Run(t *testing.T) { require.Equal(t, client, telemetryProvider.hcpClient) require.NotNil(t, telemetryProvider.GetHeader()) require.NotNil(t, telemetryProvider.GetHTTPClient()) + require.NotEmpty(t, upsertManagementTokenCalled, "upsert management token function not called") } func TestManager_SendUpdate(t *testing.T) {