mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
Nomad servers, if upgraded, can return node identities as part of the register and update/heartbeat response objects. The Nomad client will now handle this and store it as appropriate within its memory and statedb. The client will now use any stored identity for RPC authentication with a fallback to the secretID. This supports upgrades paths where the Nomad clients are updated before the Nomad servers.
127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package widmgr
|
|
|
|
import (
|
|
"fmt"
|
|
"sync/atomic"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
type RPCer interface {
|
|
RPC(method string, args any, reply any) error
|
|
}
|
|
|
|
// IdentitySigner is the interface needed to retrieve signed identities for
|
|
// workload identities. At runtime it is implemented by *widmgr.Signer.
|
|
type IdentitySigner interface {
|
|
SignIdentities(minIndex uint64, req []*structs.WorkloadIdentityRequest) ([]*structs.SignedWorkloadIdentity, error)
|
|
}
|
|
|
|
// SignerConfig wraps the configuration parameters the workload identity manager
|
|
// needs.
|
|
type SignerConfig struct {
|
|
// NodeSecret is the node's secret token
|
|
NodeSecret string
|
|
|
|
// Region of the node
|
|
Region string
|
|
|
|
RPC RPCer
|
|
}
|
|
|
|
// Signer fetches and validates workload identities.
|
|
type Signer struct {
|
|
nodeSecret string
|
|
nodeIdentityToken atomic.Value
|
|
region string
|
|
rpc RPCer
|
|
}
|
|
|
|
// NewSigner workload identity manager.
|
|
func NewSigner(c SignerConfig) *Signer {
|
|
return &Signer{
|
|
nodeSecret: c.NodeSecret,
|
|
region: c.Region,
|
|
rpc: c.RPC,
|
|
}
|
|
}
|
|
|
|
// SetNodeIdentityToken fulfills the NodeIdentityHandler interface, allowing
|
|
// the client to update the node identity token used for RPC calls when it is
|
|
// renewed.
|
|
func (s *Signer) SetNodeIdentityToken(token string) { s.nodeIdentityToken.Store(token) }
|
|
|
|
// SignIdentities wraps the Alloc.SignIdentities RPC and retrieves signed
|
|
// workload identities. The minIndex should be set to the lowest allocation
|
|
// CreateIndex to ensure that the server handling the request isn't so stale
|
|
// that it doesn't know the allocation exist (and therefore rejects the signing
|
|
// requests).
|
|
//
|
|
// Since a single rejection causes an error to be returned, SignIdentities
|
|
// should currently only be used when requesting signed identities for a single
|
|
// allocation.
|
|
func (s *Signer) SignIdentities(minIndex uint64, req []*structs.WorkloadIdentityRequest) ([]*structs.SignedWorkloadIdentity, error) {
|
|
if len(req) == 0 {
|
|
return nil, fmt.Errorf("no identities to sign")
|
|
}
|
|
|
|
// Default to using the node secret, but if the node identity token is set,
|
|
// this will be used instead. This handles the case where the node is
|
|
// upgraded before the Nomad servers and should be removed in Nomad 1.13.
|
|
authToken := s.nodeSecret
|
|
|
|
if id := s.nodeIdentityToken.Load(); id != nil {
|
|
authToken = id.(string)
|
|
}
|
|
|
|
args := structs.AllocIdentitiesRequest{
|
|
Identities: req,
|
|
QueryOptions: structs.QueryOptions{
|
|
Region: s.region,
|
|
|
|
// Unlike other RPCs, this one doesn't care about "subsequent
|
|
// modifications" after an index. We only want to ensure the state
|
|
// isn't too stale to know about this alloc, so we instruct the
|
|
// Server to block at least until the Allocation is created.
|
|
MinQueryIndex: minIndex - 1,
|
|
AllowStale: true,
|
|
AuthToken: authToken,
|
|
},
|
|
}
|
|
reply := structs.AllocIdentitiesResponse{}
|
|
if err := s.rpc.RPC("Alloc.SignIdentities", &args, &reply); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if n := len(reply.Rejections); n == 1 {
|
|
return nil, fmt.Errorf(
|
|
"%d/%d signing request was rejected: %v",
|
|
n, len(req), reply.Rejections[0].Reason,
|
|
)
|
|
} else if n > 1 {
|
|
var mErr *multierror.Error
|
|
for _, r := range reply.Rejections {
|
|
mErr = multierror.Append(
|
|
fmt.Errorf(
|
|
"%d/%d signing request was rejected: %v",
|
|
n, len(req), r.Reason,
|
|
))
|
|
}
|
|
return nil, mErr
|
|
}
|
|
|
|
if len(reply.SignedIdentities) == 0 {
|
|
return nil, fmt.Errorf("empty signed identity response")
|
|
}
|
|
|
|
if exp, act := len(reply.SignedIdentities), len(req); exp != act {
|
|
return nil, fmt.Errorf("expected %d signed identities but received %d", exp, act)
|
|
}
|
|
|
|
return reply.SignedIdentities, nil
|
|
}
|