Files
nomad/client/consul/consul.go
Tim Gross 140747240f consul: include admin partition in JWT login requests (#22226)
When logging into a JWT auth method, we need to explicitly supply the Consul
admin partition if the local Consul agent is in a partition. We can't derive
this from agent configuration because the Consul agent's configuration is
canonical, so instead we get the partition from the fingerprint (if
available). This changeset updates the Consul client constructor so that we
close over the partition from the fingerprint.

Ref: https://hashicorp.atlassian.net/browse/NET-9451
2024-05-29 16:31:09 -04:00

144 lines
4.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"fmt"
consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/helper/useragent"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/nomad/structs/config"
)
// TokenDeriverFunc takes an allocation and a set of tasks and derives a
// service identity token for each. Requests go through nomad server.
type TokenDeriverFunc func(*structs.Allocation, []string) (map[string]string, error)
// ServiceIdentityAPI is the interface the Nomad Client uses to request Consul
// Service Identity tokens through Nomad Server.
//
// ACL requirements
// - acl:write (used by Server only)
type ServiceIdentityAPI interface {
// DeriveSITokens contacts the nomad server and requests consul service
// identity tokens be generated for tasks in the allocation.
DeriveSITokens(alloc *structs.Allocation, tasks []string) (map[string]string, error)
}
// SupportedProxiesAPI is the interface the Nomad Client uses to request from
// Consul the set of supported proxied to use for Consul Connect.
//
// No ACL requirements
type SupportedProxiesAPI interface {
Proxies() (map[string][]string, error)
}
// SupportedProxiesAPIFunc returns an interface that the Nomad client uses for
// requesting the set of supported proxies from Consul.
type SupportedProxiesAPIFunc func(string) SupportedProxiesAPI
// JWTLoginRequest is an object representing a login request with JWT
type JWTLoginRequest struct {
JWT string
AuthMethodName string
Meta map[string]string
}
// Client is the interface that the nomad client uses to interact with
// Consul.
type Client interface {
// DeriveTokenWithJWT logs into Consul using JWT and retrieves a Consul ACL
// token.
DeriveTokenWithJWT(JWTLoginRequest) (*consulapi.ACLToken, error)
RevokeTokens([]*consulapi.ACLToken) error
}
type consulClient struct {
// client is the API client to interact with consul
client *consulapi.Client
// partition is the Consul partition for the local agent
partition string
// config is the configuration to connect to consul
config *config.ConsulConfig
logger hclog.Logger
}
// ConsulClientFunc creates a new Consul client for the specific Consul config
type ConsulClientFunc func(config *config.ConsulConfig, logger hclog.Logger) (Client, error)
// NewConsulClientFactory returns a ConsulClientFunc that closes over the
// partition
func NewConsulClientFactory(node *structs.Node) ConsulClientFunc {
partition := node.Attributes["consul.partition"]
return func(config *config.ConsulConfig, logger hclog.Logger) (Client, error) {
if config == nil {
return nil, fmt.Errorf("nil consul config")
}
logger = logger.Named("consul").With("name", config.Name)
c := &consulClient{
config: config,
logger: logger,
partition: partition,
}
// Get the Consul API configuration
apiConf, err := config.ApiConfig()
if err != nil {
logger.Error("error creating default Consul API config", "error", err)
return nil, err
}
// Create the API client
client, err := consulapi.NewClient(apiConf)
if err != nil {
logger.Error("error creating Consul client", "error", err)
return nil, err
}
useragent.SetHeaders(client)
c.client = client
return c, nil
}
}
// DeriveTokenWithJWT takes a JWT from request and returns a consul token.
func (c *consulClient) DeriveTokenWithJWT(req JWTLoginRequest) (*consulapi.ACLToken, error) {
t, _, err := c.client.ACL().Login(&consulapi.ACLLoginParams{
AuthMethod: req.AuthMethodName,
BearerToken: req.JWT,
Meta: req.Meta,
}, &consulapi.WriteOptions{
Partition: c.partition,
})
return t, err
}
func (c *consulClient) RevokeTokens(tokens []*consulapi.ACLToken) error {
var mErr *multierror.Error
for _, token := range tokens {
_, err := c.client.ACL().Logout(&consulapi.WriteOptions{
Namespace: token.Namespace,
Partition: token.Partition,
Token: token.SecretID,
})
mErr = multierror.Append(mErr, err)
}
return mErr.ErrorOrNil()
}