Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/scripts/split.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fi
declare -a job_set
current_runner=1
for FILE in test/integration/suites/*; do
job_set[$current_runner]+="${FILE##test/integration/} "
job_set[current_runner]+="${FILE##test/integration/} "

((current_runner++))
if [ "$current_runner" -gt "$NUM_RUNNERS" ]; then
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scripts/split_k8s.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fi
declare -a job_set
current_runner=1
for FILE in test/integration/suites/k8s*; do
job_set[$current_runner]+="${FILE##test/integration/} "
job_set[current_runner]+="${FILE##test/integration/} "

((current_runner++))
if [ "$current_runner" -gt "$NUM_RUNNERS" ]; then
Expand Down
157 changes: 157 additions & 0 deletions cmd/spire-agent/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/signal"
"path/filepath"
"slices"
"sort"
"strconv"
"strings"
Expand All @@ -24,7 +25,9 @@ import (
"github.com/hashicorp/hcl/hcl/token"
"github.com/mitchellh/cli"
"github.com/sirupsen/logrus"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"github.com/spiffe/spire/pkg/agent"
"github.com/spiffe/spire/pkg/agent/broker"
"github.com/spiffe/spire/pkg/agent/client"
"github.com/spiffe/spire/pkg/agent/trustbundlesources"
"github.com/spiffe/spire/pkg/agent/workloadkey"
Expand Down Expand Up @@ -108,6 +111,89 @@ type agentConfig struct {
UnusedKeyPositions map[string][]token.Pos `hcl:",unusedKeyPositions"`
}

// brokerHCLConfig is the HCL block for the SPIFFE Broker API endpoint:
//
// broker {
// # At least one of these MUST be set; both MAY be set to expose the
// # broker endpoint over both transports simultaneously (e.g., UDS
// # for a local sidecar broker AND TCP for a remote broker).
// socket_path = "/run/spire/agent/broker.sock" # POSIX-only UDS
// bind_address = "0.0.0.0:8443" # TCP, both platforms
//
// brokers = [
// { id = "spiffe://example.org/some/broker" },
// ]
// }
type brokerHCLConfig struct {
// SocketPath binds the broker endpoint to a Unix domain socket at the
// given path. POSIX-only.
SocketPath string `hcl:"socket_path"`

// BindAddress binds the broker endpoint to a TCP address ("host:port").
// Works on both POSIX and Windows.
BindAddress string `hcl:"bind_address"`

// Brokers enumerates the brokers authorized to talk to this agent's
// broker endpoint. Each entry's `id` is any valid SPIFFE ID; cross-
// trust-domain broker identities are allowed.
Brokers []brokerHCLEntry `hcl:"brokers"`

// AllowedReferenceTypesOverTCP restricts which WorkloadReference type
// URLs may be used by brokers connecting over the TCP listener. Empty
// (the default) denies every TCP request — fail-closed against remote
// use of local-only reference types such as PIDs. Each entry is a
// verbatim type URL; the wildcard `"*"` is intentionally NOT honoured
// here so operators must explicitly enumerate what is safe over the
// network. The list is global to the endpoint and applies on top of
// each broker's `allowed_reference_types`. Has no effect on UDS
// connections.
AllowedReferenceTypesOverTCP []string `hcl:"allowed_reference_types_over_tcp"`

UnusedKeyPositions map[string][]token.Pos `hcl:",unusedKeyPositions"`
}

type brokerHCLEntry struct {
ID string `hcl:"id"`

// AllowedReferenceTypes restricts which WorkloadReference types this
// broker may use. Each entry is the verbatim protobuf type URL that
// the workload attestor plugin inspects, e.g.
// `type.googleapis.com/spiffe.broker.KubernetesObjectReference`.
// Use `"*"` to allow any reference type. Must list at least one entry.
AllowedReferenceTypes []string `hcl:"allowed_reference_types"`

UnusedKeyPositions map[string][]token.Pos `hcl:",unusedKeyPositions"`
}

// brokerBindAddrs resolves the broker endpoint's listener addresses from
// the HCL block. Returns one address per configured transport (TCP via
// `bind_address`, UDS via `socket_path`). Both MAY be set; at least one
// MUST be set. The TCP branch is platform-agnostic and resolved here; the
// UDS branch delegates to `brokerSocketAddr` which is defined per platform
// (POSIX has UDS support; Windows rejects with an error).
func (c *agentConfig) brokerBindAddrs() ([]net.Addr, error) {
sp, ba := c.Experimental.Broker.SocketPath, c.Experimental.Broker.BindAddress
if sp == "" && ba == "" {
return nil, errors.New("experimental.broker requires socket_path, bind_address, or both")
}
var addrs []net.Addr
if sp != "" {
uds, err := c.brokerSocketAddr()
if err != nil {
return nil, err
}
addrs = append(addrs, uds)
}
if ba != "" {
tcp, err := net.ResolveTCPAddr("tcp", ba)
if err != nil {
return nil, fmt.Errorf("invalid experimental.broker.bind_address %q: %w", ba, err)
}
addrs = append(addrs, tcp)
}
return addrs, nil
}

type sdsConfig struct {
DefaultSVIDName string `hcl:"default_svid_name"`
DefaultBundleName string `hcl:"default_bundle_name"`
Expand Down Expand Up @@ -136,6 +222,12 @@ type experimentalConfig struct {

RateLimit workloadAPIRateLimitConfig `hcl:"ratelimit"`

// Broker holds the configuration for the SPIFFE Broker API endpoint
// (distinct from the Delegated Identity API's authorized_delegates).
// Kept under `experimental` while the spec stabilizes — breaking
// changes may land before this moves to the top-level config.
Broker *brokerHCLConfig `hcl:"broker"`

Flags fflag.RawConfig `hcl:"feature_flags"`
}

Expand Down Expand Up @@ -526,6 +618,7 @@ func NewAgentConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool)
}
ac.AdminBindAddress = adminAddr
}

// Handle join token - read from file if specified
if c.Agent.JoinTokenFile != "" {
tokenBytes, err := os.ReadFile(c.Agent.JoinTokenFile)
Expand Down Expand Up @@ -602,6 +695,59 @@ func NewAgentConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool)

ac.AuthorizedDelegates = c.Agent.AuthorizedDelegates

if c.Agent.Experimental.Broker != nil {
bindAddrs, err := c.Agent.brokerBindAddrs()
if err != nil {
return nil, err
}
// Pass 1: validate every entry has a non-empty, unique ID. Doing
// this first means subsequent errors can reference brokers by ID
// instead of slice index — which is more helpful to operators
// since they wrote the IDs themselves.
seen := make(map[string]struct{}, len(c.Agent.Experimental.Broker.Brokers))
for i, b := range c.Agent.Experimental.Broker.Brokers {
if b.ID == "" {
return nil, fmt.Errorf("experimental.broker.brokers[%d].id: must be specified", i)
}
if _, dup := seen[b.ID]; dup {
return nil, fmt.Errorf("experimental.broker.brokers[%s].id: duplicate broker id", b.ID)
}
seen[b.ID] = struct{}{}
}

// Pass 2: per-broker field validation. ID is referenced in error
// messages now that we know it's set and unique.
brokers := make([]broker.Broker, 0, len(c.Agent.Experimental.Broker.Brokers))
for _, b := range c.Agent.Experimental.Broker.Brokers {
if _, err := spiffeid.FromString(b.ID); err != nil {
return nil, fmt.Errorf("experimental.broker.brokers[%s].id: %w", b.ID, err)
}
if len(b.AllowedReferenceTypes) == 0 {
return nil, fmt.Errorf("experimental.broker.brokers[%s].allowed_reference_types: must list at least one reference type URL", b.ID)
}
brokers = append(brokers, broker.Broker{
ID: b.ID,
AllowedReferenceTypes: b.AllowedReferenceTypes,
})
}
// allowed_reference_types_over_tcp is only meaningful when a TCP
// listener is configured; surface a clear error rather than
// silently ignoring the field. Wildcards are intentionally not
// honoured for the TCP gate — operators must list specific
// reference type URLs to opt them in.
if len(c.Agent.Experimental.Broker.AllowedReferenceTypesOverTCP) > 0 && c.Agent.Experimental.Broker.BindAddress == "" {
return nil, errors.New("experimental.broker.allowed_reference_types_over_tcp set but bind_address is not configured")
}
if slices.Contains(c.Agent.Experimental.Broker.AllowedReferenceTypesOverTCP, "*") {
return nil, errors.New("experimental.broker.allowed_reference_types_over_tcp: wildcard \"*\" is not supported; list specific reference type URLs")
}
ac.Broker = agent.BrokerConfig{
BindAddresses: bindAddrs,
Brokers: brokers,
AllowedReferenceTypesOverTCP: c.Agent.Experimental.Broker.AllowedReferenceTypesOverTCP,
}
}

if c.Agent.AvailabilityTarget != "" {
t, err := time.ParseDuration(c.Agent.AvailabilityTarget)
if err != nil {
Expand Down Expand Up @@ -694,6 +840,17 @@ func checkForUnknownConfig(c *Config, l logrus.FieldLogger) (err error) {
detectedUnknown("agent", a.UnusedKeyPositions)
}

if a := c.Agent; a != nil && a.Experimental.Broker != nil {
if len(a.Experimental.Broker.UnusedKeyPositions) != 0 {
detectedUnknown("experimental.broker", a.Experimental.Broker.UnusedKeyPositions)
}
for i, b := range a.Experimental.Broker.Brokers {
if len(b.UnusedKeyPositions) != 0 {
detectedUnknown(fmt.Sprintf("experimental.broker.brokers[%d]", i), b.UnusedKeyPositions)
}
}
}

// TODO: Re-enable unused key detection for telemetry. See
// https://github.com/spiffe/spire/issues/1101 for more information
//
Expand Down
36 changes: 36 additions & 0 deletions cmd/spire-agent/cli/run/run_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ func (c *agentConfig) hasAdminAddr() bool {
return c.AdminSocketPath != ""
}

// brokerSocketAddr resolves the UDS branch of broker bind-address selection.
// Returns the platform-agnostic TCP branch is handled in brokerBindAddr.
func (c *agentConfig) brokerSocketAddr() (net.Addr, error) {
socketPathAbs, err := filepath.Abs(c.SocketPath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for socket_path: %w", err)
}
brokerSocketPathAbs, err := filepath.Abs(c.Experimental.Broker.SocketPath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for experimental.broker.socket_path: %w", err)
}

if strings.HasPrefix(brokerSocketPathAbs, filepath.Dir(socketPathAbs)+"/") {
return nil, errors.New("broker socket cannot be in the same directory or a subdirectory as that containing the Workload API socket")
}

return &net.UnixAddr{
Name: brokerSocketPathAbs,
Net: "unix",
}, nil
}

// validateOS performs posix specific validations of the agent config
func (c *agentConfig) validateOS() error {
if c.Experimental.NamedPipeName != "" {
Expand Down Expand Up @@ -88,5 +110,19 @@ func prepareEndpoints(c *agent.Config) error {
}
}

for _, addr := range c.Broker.BindAddresses {
if addr.Network() != "unix" {
continue
}
// Create uds dir and parents if not exists
brokerDir := filepath.Dir(addr.String())
if _, statErr := os.Stat(brokerDir); os.IsNotExist(statErr) {
c.Log.WithField("dir", brokerDir).Infof("Creating broker UDS directory")
if err := os.MkdirAll(brokerDir, 0755); err != nil {
return err
}
}
}

return nil
}
7 changes: 7 additions & 0 deletions cmd/spire-agent/cli/run/run_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ func (c *agentConfig) hasAdminAddr() bool {
return c.Experimental.AdminNamedPipeName != ""
}

// brokerSocketAddr resolves the UDS branch of broker bind-address selection.
// Windows does not support UDS for the broker endpoint; configure
// `experimental.broker.bind_address` (TCP) instead.
func (c *agentConfig) brokerSocketAddr() (net.Addr, error) {
return nil, errors.New("experimental.broker.socket_path is not supported on this platform; use experimental.broker.bind_address (TCP) instead")
}

// validateOS performs windows specific validations of the agent config
func (c *agentConfig) validateOS() error {
if c.SocketPath != "" {
Expand Down
95 changes: 95 additions & 0 deletions conf/agent/agent_full.conf
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,55 @@ agent {
# # # fetch_secrets: Max calls/sec per selector set for SDS FetchSecrets (unary). Default: 0 (disabled).
# # # fetch_secrets = 0
# # }

# # broker: SPIFFE Broker API endpoint configuration. The broker endpoint
# # is opt-in; omitting the block disables it. At least one of socket_path
# # or bind_address MUST be set; both MAY be set to expose the endpoint
# # over both transports simultaneously. Kept under `experimental` while
# # the spec stabilizes — breaking changes may land before this moves to
# # the top-level config.
# # broker {
# # # socket_path: Bind the broker endpoint to a Unix domain socket
# # # at the given path. POSIX-only. Mutually compatible with
# # # bind_address (both can be set).
# # # socket_path = "/tmp/spire-agent/broker/api.sock"
# #
# # # bind_address: Bind the broker endpoint to a TCP address
# # # ("host:port"). Works on POSIX and Windows. The broker
# # # endpoint authenticates callers via mutual TLS using X.509-SVIDs
# # # so it is safe to expose over the network.
# # # bind_address = "0.0.0.0:8443"
# #
# # # allowed_reference_types_over_tcp: global allowlist of
# # # WorkloadReference type URLs permitted on the TCP listener.
# # # Empty (the default) denies every TCP request — fail-closed
# # # against remote use of local-only reference types such as
# # # PIDs. Entries are verbatim type URLs; no wildcard is
# # # honoured, so operators must enumerate what is safe over
# # # the network. Only meaningful when bind_address is set.
# # # UDS connections are unaffected.
# # # allowed_reference_types_over_tcp = [
# # # "type.googleapis.com/spiffe.broker.KubernetesObjectReference",
# # # ]
# #
# # # brokers: brokers authorized to talk to this endpoint. At
# # # least one broker MUST be configured for the endpoint to
# # # start. Each entry's id is any valid SPIFFE ID;
# # # cross-trust-domain broker identities are allowed.
# # # allowed_reference_types restricts which WorkloadReference
# # # types the broker may use; each entry is the verbatim
# # # protobuf type URL the workload attestor plugin matches
# # # against. At least one entry is required; use ["*"] to allow
# # # any reference type the agent's attestor stack understands.
# # # brokers = [
# # # {
# # # id = "spiffe://example.org/broker"
# # # allowed_reference_types = [
# # # "type.googleapis.com/spiffe.broker.KubernetesObjectReference",
# # # ]
# # # },
# # # ]
# # }
# }
}

Expand Down Expand Up @@ -466,6 +515,52 @@ plugins {
# Defaults to false. (Linux only)
# verbose_container_locator_logs = false

# broker: Broker API-specific configuration used by AttestReference.
# Broker API uses AttestReference, so this block is required for
# all Broker API references handled by this plugin, including
# WorkloadPIDReference and KubernetesObjectReference. Each broker
# entry identifies a broker SPIFFE ID. IDs must be valid, unique,
# and non-empty. The broker SPIFFE ID is used as the
# SubjectAccessReview username; no groups are set.
# pod_reference_scope controls pod KubernetesObjectReference
# resolution for the broker. Valid values are "agent_node"
# (default) and "cluster". agent_node only accepts pods whose
# spec.nodeName matches this agent's node name. Non-pod object
# references always use the Kubernetes API server.
#
# Additional Kubernetes authorization requirements for Broker API
# object references:
# * The SPIRE agent ServiceAccount must be allowed to create
# subjectaccessreviews.authorization.k8s.io resources.
# * The broker SPIFFE ID must be allowed to use SPIRE's custom
# Kubernetes authorization verb "impersonate-via-spire" on
# every Kubernetes resource brokers may reference. Do not
# grant Kubernetes' built-in "impersonate" verb for this
# purpose: that verb can grant Kubernetes-native impersonation
# of users, groups, and ServiceAccounts. The custom verb is
# only a SPIRE broker authorization gate.
#
# Example broker-side Kubernetes RBAC for broker ID
# spiffe://example.org/broker:
# kind: ClusterRole
# rules:
# - apiGroups: [""]
# resources: ["pods"]
# verbs: ["impersonate-via-spire"]
# kind: ClusterRoleBinding
# subjects:
# - kind: User
# name: spiffe://example.org/broker
#
# broker {
# brokers = [
# {
# id = "spiffe://example.org/broker"
# pod_reference_scope = "cluster"
# }
# ]
# }

# sigstore: sigstore options. Enables image cosign signatures checking.
# sigstore {
# allowed_identities: Maps OIDC issuer URIs to acceptable SANs in Fulcio certificates for validating signatures.
Expand Down
Loading
Loading