Skip to content

Commit

Permalink
DAOS-15849 control: Add client uid map to agent config (#14381)
Browse files Browse the repository at this point in the history
Allow daos_agent to optionally handle unresolvable client
uids via custom mapping. In deployments where the agent
may not have access to the same user namespace as client
applications (e.g. in containerized deployments), the
client_user_map can provide a fallback mechanism for
resolving the client uids to known usernames for the
purpose of applying ACL permissions tests.

Example agent config:

credential_config:
  client_user_map:
    default:
      user: nobody
      group: nobody
    1000:
      user: joe
      group: blow

Signed-off-by: Michael MacDonald <[email protected]>
  • Loading branch information
mjmac authored Jun 11, 2024
1 parent ff1cf6f commit 3ca434b
Show file tree
Hide file tree
Showing 14 changed files with 768 additions and 422 deletions.
48 changes: 25 additions & 23 deletions src/control/cmd/daos_agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,24 @@ func (rm refreshMinutes) Duration() time.Duration {

// Config defines the agent configuration.
type Config struct {
SystemName string `yaml:"name"`
AccessPoints []string `yaml:"access_points"`
ControlPort int `yaml:"port"`
RuntimeDir string `yaml:"runtime_dir"`
LogFile string `yaml:"log_file"`
LogLevel common.ControlLogLevel `yaml:"control_log_mask,omitempty"`
TransportConfig *security.TransportConfig `yaml:"transport_config"`
DisableCache bool `yaml:"disable_caching,omitempty"`
CacheExpiration refreshMinutes `yaml:"cache_expiration,omitempty"`
DisableAutoEvict bool `yaml:"disable_auto_evict,omitempty"`
EvictOnStart bool `yaml:"enable_evict_on_start,omitempty"`
ExcludeFabricIfaces common.StringSet `yaml:"exclude_fabric_ifaces,omitempty"`
FabricInterfaces []*NUMAFabricConfig `yaml:"fabric_ifaces,omitempty"`
ProviderIdx uint // TODO SRS-31: Enable with multiprovider functionality
TelemetryPort int `yaml:"telemetry_port,omitempty"`
TelemetryEnabled bool `yaml:"telemetry_enabled,omitempty"`
TelemetryRetain time.Duration `yaml:"telemetry_retain,omitempty"`
SystemName string `yaml:"name"`
AccessPoints []string `yaml:"access_points"`
ControlPort int `yaml:"port"`
RuntimeDir string `yaml:"runtime_dir"`
LogFile string `yaml:"log_file"`
LogLevel common.ControlLogLevel `yaml:"control_log_mask,omitempty"`
CredentialConfig *security.CredentialConfig `yaml:"credential_config"`
TransportConfig *security.TransportConfig `yaml:"transport_config"`
DisableCache bool `yaml:"disable_caching,omitempty"`
CacheExpiration refreshMinutes `yaml:"cache_expiration,omitempty"`
DisableAutoEvict bool `yaml:"disable_auto_evict,omitempty"`
EvictOnStart bool `yaml:"enable_evict_on_start,omitempty"`
ExcludeFabricIfaces common.StringSet `yaml:"exclude_fabric_ifaces,omitempty"`
FabricInterfaces []*NUMAFabricConfig `yaml:"fabric_ifaces,omitempty"`
ProviderIdx uint // TODO SRS-31: Enable with multiprovider functionality
TelemetryPort int `yaml:"telemetry_port,omitempty"`
TelemetryEnabled bool `yaml:"telemetry_enabled,omitempty"`
TelemetryRetain time.Duration `yaml:"telemetry_retain,omitempty"`
}

// TelemetryExportEnabled returns true if client telemetry export is enabled.
Expand Down Expand Up @@ -113,11 +114,12 @@ func LoadConfig(cfgPath string) (*Config, error) {
func DefaultConfig() *Config {
localServer := fmt.Sprintf("localhost:%d", build.DefaultControlPort)
return &Config{
SystemName: build.DefaultSystemName,
ControlPort: build.DefaultControlPort,
AccessPoints: []string{localServer},
RuntimeDir: defaultRuntimeDir,
LogLevel: common.DefaultControlLogLevel,
TransportConfig: security.DefaultAgentTransportConfig(),
SystemName: build.DefaultSystemName,
ControlPort: build.DefaultControlPort,
AccessPoints: []string{localServer},
RuntimeDir: defaultRuntimeDir,
LogLevel: common.DefaultControlLogLevel,
TransportConfig: security.DefaultAgentTransportConfig(),
CredentialConfig: &security.CredentialConfig{},
}
}
30 changes: 23 additions & 7 deletions src/control/cmd/daos_agent/config_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// (C) Copyright 2021-2023 Intel Corporation.
// (C) Copyright 2021-2024 Intel Corporation.
//
// SPDX-License-Identifier: BSD-2-Clause-Patent
//
Expand Down Expand Up @@ -46,6 +46,12 @@ control_log_mask: debug
disable_caching: true
cache_expiration: 30
disable_auto_evict: true
credential_config:
client_user_map:
1000:
user: frodo
group: baggins
groups: ["ringbearers"]
transport_config:
allow_insecure: true
exclude_fabric_ifaces: ["ib3"]
Expand Down Expand Up @@ -104,12 +110,13 @@ transport_config:
"without optional items": {
path: withoutOptCfg,
expResult: &Config{
SystemName: "shire",
AccessPoints: []string{"one:10001", "two:10001"},
ControlPort: 4242,
RuntimeDir: "/tmp/runtime",
LogFile: "/home/frodo/logfile",
LogLevel: common.DefaultControlLogLevel,
SystemName: "shire",
AccessPoints: []string{"one:10001", "two:10001"},
ControlPort: 4242,
RuntimeDir: "/tmp/runtime",
LogFile: "/home/frodo/logfile",
LogLevel: common.DefaultControlLogLevel,
CredentialConfig: &security.CredentialConfig{},
TransportConfig: &security.TransportConfig{
AllowInsecure: true,
CertificateConfig: DefaultConfig().TransportConfig.CertificateConfig,
Expand All @@ -132,6 +139,15 @@ transport_config:
DisableCache: true,
CacheExpiration: refreshMinutes(30 * time.Minute),
DisableAutoEvict: true,
CredentialConfig: &security.CredentialConfig{
ClientUserMap: map[uint32]*security.MappedClientUser{
1000: {
User: "frodo",
Group: "baggins",
Groups: []string{"ringbearers"},
},
},
},
TransportConfig: &security.TransportConfig{
AllowInsecure: true,
CertificateConfig: DefaultConfig().TransportConfig.CertificateConfig,
Expand Down
71 changes: 54 additions & 17 deletions src/control/cmd/daos_agent/security_rpc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// (C) Copyright 2018-2022 Intel Corporation.
// (C) Copyright 2018-2024 Intel Corporation.
//
// SPDX-License-Identifier: BSD-2-Clause-Patent
//
Expand All @@ -9,6 +9,9 @@ package main
import (
"context"
"net"
"os/user"

"github.com/pkg/errors"

"github.com/daos-stack/daos/src/control/drpc"
"github.com/daos-stack/daos/src/control/lib/daos"
Expand All @@ -17,21 +20,31 @@ import (
"github.com/daos-stack/daos/src/control/security/auth"
)

// SecurityModule is the security drpc module struct
type SecurityModule struct {
log logging.Logger
ext auth.UserExt
config *security.TransportConfig
}
type (
credSignerFn func(*auth.CredentialRequest) (*auth.Credential, error)

// securityConfig defines configuration parameters for SecurityModule.
securityConfig struct {
credentials *security.CredentialConfig
transport *security.TransportConfig
}

// SecurityModule is the security drpc module struct
SecurityModule struct {
log logging.Logger
signCredential credSignerFn

config *securityConfig
}
)

// NewSecurityModule creates a new module with the given initialized TransportConfig
func NewSecurityModule(log logging.Logger, tc *security.TransportConfig) *SecurityModule {
mod := SecurityModule{
log: log,
config: tc,
func NewSecurityModule(log logging.Logger, cfg *securityConfig) *SecurityModule {
return &SecurityModule{
log: log,
signCredential: auth.GetSignedCredential,
config: cfg,
}
mod.ext = &auth.External{}
return &mod
}

// HandleCall is the handler for calls to the SecurityModule
Expand All @@ -46,6 +59,10 @@ func (m *SecurityModule) HandleCall(_ context.Context, session *drpc.Session, me
// getCredentials generates a signed user credential based on the data attached to
// the Unix Domain Socket.
func (m *SecurityModule) getCredential(session *drpc.Session) ([]byte, error) {
if session == nil {
return nil, drpc.NewFailureWithMessage("session is nil")
}

uConn, ok := session.Conn.(*net.UnixConn)
if !ok {
return nil, drpc.NewFailureWithMessage("connection is not a unix socket")
Expand All @@ -57,17 +74,37 @@ func (m *SecurityModule) getCredential(session *drpc.Session) ([]byte, error) {
return m.credRespWithStatus(daos.MiscError)
}

signingKey, err := m.config.PrivateKey()
signingKey, err := m.config.transport.PrivateKey()
if err != nil {
m.log.Errorf("%s: failed to get signing key: %s", info, err)
// something is wrong with the cert config
return m.credRespWithStatus(daos.BadCert)
}

cred, err := auth.AuthSysRequestFromCreds(m.ext, info, signingKey)
req := auth.NewCredentialRequest(info, signingKey)
cred, err := m.signCredential(req)
if err != nil {
m.log.Errorf("%s: failed to get AuthSys struct: %s", info, err)
return m.credRespWithStatus(daos.MiscError)
if err := func() error {
if !errors.Is(err, user.UnknownUserIdError(info.Uid())) {
return err
}

mu := m.config.credentials.ClientUserMap.Lookup(info.Uid())
if mu == nil {
return user.UnknownUserIdError(info.Uid())
}

req.WithUserAndGroup(mu.User, mu.Group, mu.Groups...)
cred, err = m.signCredential(req)
if err != nil {
return err
}

return nil
}(); err != nil {
m.log.Errorf("%s: failed to get user credential: %s", info, err)
return m.credRespWithStatus(daos.MiscError)
}
}

m.log.Tracef("%s: successfully signed credential", info)
Expand Down
Loading

0 comments on commit 3ca434b

Please sign in to comment.