Skip to content

Commit

Permalink
auto-config: move autoConfigBackend impl off of Server
Browse files Browse the repository at this point in the history
Most of these methods are used exclusively for the AutoConfig RPC
endpoint. This PR uses a pattern that we've used in other places as an
incremental step to reducing the scope of Server.
  • Loading branch information
dnephin authored and dhiaayachi committed Jul 12, 2021
1 parent 2c5eac0 commit 3b90b99
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 186 deletions.
96 changes: 96 additions & 0 deletions agent/consul/auto_config_backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package consul

import (
"crypto/x509"
"fmt"
"net"
"time"

"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/metadata"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/lib"
)

type autoConfigBackend struct {
Server *Server
}

func (b autoConfigBackend) ForwardRPC(method string, info structs.RPCInfo, reply interface{}) (bool, error) {
return b.Server.ForwardRPC(method, info, reply)
}

func (b autoConfigBackend) SignCertificate(csr *x509.CertificateRequest, id connect.CertURI) (*structs.IssuedCert, error) {
return b.Server.caManager.SignCertificate(csr, id)
}

// GetCARoots returns the CA roots.
func (b autoConfigBackend) GetCARoots() (*structs.IndexedCARoots, error) {
return b.Server.getCARoots(nil, b.Server.fsm.State())
}

// DatacenterJoinAddresses will return all the strings suitable for usage in
// retry join operations to connect to the the LAN or LAN segment gossip pool.
func (b autoConfigBackend) DatacenterJoinAddresses(segment string) ([]string, error) {
members, err := b.Server.LANSegmentMembers(segment)
if err != nil {
return nil, fmt.Errorf("Failed to retrieve members for segment %s - %w", segment, err)
}

var joinAddrs []string
for _, m := range members {
if ok, _ := metadata.IsConsulServer(m); ok {
serfAddr := net.TCPAddr{IP: m.Addr, Port: int(m.Port)}
joinAddrs = append(joinAddrs, serfAddr.String())
}
}

return joinAddrs, nil
}

// CreateACLToken will create an ACL token from the given template
func (b autoConfigBackend) CreateACLToken(template *structs.ACLToken) (*structs.ACLToken, error) {
// we have to require local tokens or else it would require having these servers use a token with acl:write to make a
// token create RPC to the servers in the primary DC.
if !b.Server.LocalTokensEnabled() {
return nil, fmt.Errorf("Agent Auto Configuration requires local token usage to be enabled in this datacenter: %s", b.Server.config.Datacenter)
}

newToken := *template

// generate the accessor id
if newToken.AccessorID == "" {
accessor, err := lib.GenerateUUID(b.Server.checkTokenUUID)
if err != nil {
return nil, err
}

newToken.AccessorID = accessor
}

// generate the secret id
if newToken.SecretID == "" {
secret, err := lib.GenerateUUID(b.Server.checkTokenUUID)
if err != nil {
return nil, err
}

newToken.SecretID = secret
}

newToken.CreateTime = time.Now()

req := structs.ACLTokenBatchSetRequest{
Tokens: structs.ACLTokens{&newToken},
CAS: false,
}

// perform the request to mint the new token
if _, err := b.Server.raftApplyMsgpack(structs.ACLTokenSetRequestType, &req); err != nil {
return nil, err
}

// return the full token definition from the FSM
_, token, err := b.Server.fsm.State().ACLTokenGetByAccessor(nil, newToken.AccessorID, &newToken.EnterpriseMeta)
return token, err
}
115 changes: 115 additions & 0 deletions agent/consul/auto_config_backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package consul

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"

"github.com/hashicorp/consul/agent/structs"
)

func TestAutoConfigBackend_DatacenterJoinAddresses(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}

conf := testClusterConfig{
Datacenter: "primary",
Servers: 3,
}

nodes := newTestCluster(t, &conf)

var expected []string
for _, srv := range nodes.Servers {
expected = append(expected, fmt.Sprintf("127.0.0.1:%d", srv.config.SerfLANConfig.MemberlistConfig.BindPort))
}

backend := autoConfigBackend{Server: nodes.Servers[0]}
actual, err := backend.DatacenterJoinAddresses("")
require.NoError(t, err)
require.ElementsMatch(t, expected, actual)
}

func TestAutoConfigBackend_CreateACLToken(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}

_, srv, codec := testACLServerWithConfig(t, nil, false)

waitForLeaderEstablishment(t, srv)

r1, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1")
require.NoError(t, err)

t.Run("predefined-ids", func(t *testing.T) {
accessor := "554cd3ab-5d4e-4d6e-952e-4e8b6c77bfb3"
secret := "ef453f31-ad58-4ec8-8bf8-342e99763026"
in := &structs.ACLToken{
AccessorID: accessor,
SecretID: secret,
Description: "test",
Policies: []structs.ACLTokenPolicyLink{
{
ID: structs.ACLPolicyGlobalManagementID,
},
},
NodeIdentities: []*structs.ACLNodeIdentity{
{
NodeName: "foo",
Datacenter: "bar",
},
},
ServiceIdentities: []*structs.ACLServiceIdentity{
{
ServiceName: "web",
},
},
Roles: []structs.ACLTokenRoleLink{
{
ID: r1.ID,
},
},
}

b := autoConfigBackend{Server: srv}
out, err := b.CreateACLToken(in)
require.NoError(t, err)
require.Equal(t, accessor, out.AccessorID)
require.Equal(t, secret, out.SecretID)
require.Equal(t, "test", out.Description)
require.NotZero(t, out.CreateTime)
require.Len(t, out.Policies, 1)
require.Len(t, out.Roles, 1)
require.Len(t, out.NodeIdentities, 1)
require.Len(t, out.ServiceIdentities, 1)
require.Equal(t, structs.ACLPolicyGlobalManagementID, out.Policies[0].ID)
require.Equal(t, "foo", out.NodeIdentities[0].NodeName)
require.Equal(t, "web", out.ServiceIdentities[0].ServiceName)
require.Equal(t, r1.ID, out.Roles[0].ID)
})

t.Run("autogen-ids", func(t *testing.T) {
in := &structs.ACLToken{
Description: "test",
NodeIdentities: []*structs.ACLNodeIdentity{
{
NodeName: "foo",
Datacenter: "bar",
},
},
}

b := autoConfigBackend{Server: srv}
out, err := b.CreateACLToken(in)
require.NoError(t, err)
require.NotEmpty(t, out.AccessorID)
require.NotEmpty(t, out.SecretID)
require.Equal(t, "test", out.Description)
require.NotZero(t, out.CreateTime)
require.Len(t, out.NodeIdentities, 1)
require.Equal(t, "foo", out.NodeIdentities[0].NodeName)
})
}
8 changes: 0 additions & 8 deletions agent/consul/auto_config_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,6 @@ type AutoConfigBackend interface {
SignCertificate(csr *x509.CertificateRequest, id connect.CertURI) (*structs.IssuedCert, error)
}

type autoConfigBackend struct {
*Server
}

func (b autoConfigBackend) SignCertificate(csr *x509.CertificateRequest, id connect.CertURI) (*structs.IssuedCert, error) {
return b.Server.caManager.SignCertificate(csr, id)
}

// AutoConfig endpoint is used for cluster auto configuration operations
type AutoConfig struct {
// currently AutoConfig does not support pushing down any configuration that would be reloadable on the servers
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/auto_config_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func TestAutoConfigInitialConfiguration(t *testing.T) {

waitForLeaderEstablishment(t, s)

roots, err := s.GetCARoots()
roots, err := s.getCARoots(nil, s.fsm.State())
require.NoError(t, err)

pbroots, err := translateCARootsToProtobuf(roots)
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/leader_connect_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)

// getIntermediateCAPrimary regenerates the intermediate cert in the primary datacenter.
// This is only run for CAs that require an intermediary in the primary DC, such as Vault.
// It should only be called while the state lock is held by setting the state to non-ready.
// It should only be called while the state lock is held by setting the state to non-ready.
func (c *CAManager) getIntermediateCAPrimary(provider ca.Provider, newActiveRoot *structs.CARoot) error {
// Generate and sign an intermediate cert using the root CA.
intermediatePEM, err := provider.GenerateIntermediate()
Expand Down
68 changes: 0 additions & 68 deletions agent/consul/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1455,74 +1455,6 @@ func (s *Server) isReadyForConsistentReads() bool {
return atomic.LoadInt32(&s.readyForConsistentReads) == 1
}

// CreateACLToken will create an ACL token from the given template
// TODO: move to autoConfigBackend
func (s *Server) CreateACLToken(template *structs.ACLToken) (*structs.ACLToken, error) {
// we have to require local tokens or else it would require having these servers use a token with acl:write to make a
// token create RPC to the servers in the primary DC.
if !s.LocalTokensEnabled() {
return nil, fmt.Errorf("Agent Auto Configuration requires local token usage to be enabled in this datacenter: %s", s.config.Datacenter)
}

newToken := *template

// generate the accessor id
if newToken.AccessorID == "" {
accessor, err := lib.GenerateUUID(s.checkTokenUUID)
if err != nil {
return nil, err
}

newToken.AccessorID = accessor
}

// generate the secret id
if newToken.SecretID == "" {
secret, err := lib.GenerateUUID(s.checkTokenUUID)
if err != nil {
return nil, err
}

newToken.SecretID = secret
}

newToken.CreateTime = time.Now()

req := structs.ACLTokenBatchSetRequest{
Tokens: structs.ACLTokens{&newToken},
CAS: false,
}

// perform the request to mint the new token
if _, err := s.raftApplyMsgpack(structs.ACLTokenSetRequestType, &req); err != nil {
return nil, err
}

// return the full token definition from the FSM
_, token, err := s.fsm.State().ACLTokenGetByAccessor(nil, newToken.AccessorID, &newToken.EnterpriseMeta)
return token, err
}

// DatacenterJoinAddresses will return all the strings suitable for usage in
// retry join operations to connect to the the LAN or LAN segment gossip pool.
// TODO: move to autoConfigBackend
func (s *Server) DatacenterJoinAddresses(segment string) ([]string, error) {
members, err := s.LANSegmentMembers(segment)
if err != nil {
return nil, fmt.Errorf("Failed to retrieve members for segment %s - %w", segment, err)
}

var joinAddrs []string
for _, m := range members {
if ok, _ := metadata.IsConsulServer(m); ok {
serfAddr := net.TCPAddr{IP: m.Addr, Port: int(m.Port)}
joinAddrs = append(joinAddrs, serfAddr.String())
}
}

return joinAddrs, nil
}

// peersInfoContent is used to help operators understand what happened to the
// peers.json file. This is written to a file called peers.info in the same
// location.
Expand Down
6 changes: 0 additions & 6 deletions agent/consul/server_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ import (
"github.com/hashicorp/consul/agent/structs"
)

// GetCARoots will retrieve CARoots
// TODO: move to autoConfigBackend
func (s *Server) GetCARoots() (*structs.IndexedCARoots, error) {
return s.getCARoots(nil, s.fsm.State())
}

func (s *Server) getCARoots(ws memdb.WatchSet, state *state.Store) (*structs.IndexedCARoots, error) {
index, roots, config, err := state.CARootsAndConfig(ws)
if err != nil {
Expand Down
Loading

0 comments on commit 3b90b99

Please sign in to comment.