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 <mjmac@google.com>
  • Loading branch information
mjmac authored Jun 11, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent ff1cf6f commit 3ca434b
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
@@ -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.
@@ -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
//
@@ -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"]
@@ -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,
@@ -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,
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
//
@@ -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"
@@ -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
@@ -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")
@@ -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)
Loading
Oops, something went wrong.

0 comments on commit 3ca434b

Please sign in to comment.