Documentation
¶
Index ¶
- Constants
- func AbsentGroupLookup(string) (*user.Group, error)
- func AbsentUserLookup(string) (*user.User, error)
- func ClassifyGroupForCreate(lookup GroupLookupFunc, groupname string, wantGID uint64) (needCreate bool, code int, out string, err error)
- func CommandsToStrings(commands []CommandType) []string
- func DefaultGroupLookup(name string) (*user.Group, error)
- func DefaultUserLookup(name string) (*user.User, error)
- func GetBoolArg(args map[string]interface{}, key string, defaultValue bool) bool
- func GetIntArg(args map[string]interface{}, key string, defaultValue int) int
- func GetStringArg(args map[string]interface{}, key string, defaultValue string) string
- func GetStringSliceArg(args map[string]interface{}, key string) []string
- func GroupExists(lookup GroupLookupFunc, name string) (g *user.Group, found bool, err error)
- func GroupGIDConflictMessage(groupname string, existingGID, requestedGID uint64) string
- func IsTimeout(ctx context.Context) bool
- func ReconcileGroupCreate(lookup GroupLookupFunc, groupname string, wantGID uint64, code int, out string, ...) (int, string, error)
- func ReconcileUserCreate(lookup UserLookupFunc, username string, wantUID uint64, uidRequested bool, ...) (int, string, error)
- func TimeoutError(timeout time.Duration) (int, string, error)
- func UserExists(lookup UserLookupFunc, name string) (u *user.User, found bool, err error)
- func UserUIDConflictMessage(username string, existingUID, requestedUID uint64) string
- func WithHandlerTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)
- type APISession
- type BaseHandler
- type CommandArgs
- type CommandExecutor
- type CommandResult
- type CommandType
- type ExecutedCommand
- type File
- type FirewallRule
- type GroupLookupFunc
- type Handler
- type HandlerType
- type MockCommandExecutor
- func (m *MockCommandExecutor) Exec(ctx context.Context, args []string, username, groupname string, ...) (int, string, error)
- func (m *MockCommandExecutor) ExecWithHook(ctx context.Context, args []string, username, groupname string, ...) (int, string, error)
- func (m *MockCommandExecutor) ExecWithStreamingHook(ctx context.Context, args []string, username, groupname string, ...) (int, string, error)
- func (m *MockCommandExecutor) GetExecutedCommands() []ExecutedCommand
- func (m *MockCommandExecutor) Invoked(program string) bool
- func (m *MockCommandExecutor) Run(ctx context.Context, name string, args ...string) (int, string, error)
- func (m *MockCommandExecutor) RunAsUser(ctx context.Context, username string, name string, args ...string) (int, string, error)
- func (m *MockCommandExecutor) RunWithInput(ctx context.Context, input string, name string, args ...string) (int, string, error)
- func (m *MockCommandExecutor) RunWithTimeout(ctx context.Context, timeout time.Duration, name string, args ...string) (int, string, error)
- func (m *MockCommandExecutor) SetResult(command string, exitCode int, output string, err error)
- type SystemInfoManager
- type UserLookupFunc
- type VersionResolver
- type WSClient
Constants ¶
const ( ShellTimeout = 30 * time.Minute UpgradeTimeout = 30 * time.Minute SystemCmdTimeout = 60 * time.Second FileTimeout = 10 * time.Minute FirewallTimeout = 2 * time.Minute UserTimeout = 2 * time.Minute UserDeleteTimeout = 5 * time.Minute GroupTimeout = 30 * time.Second InfoTimeout = 30 * time.Second )
Default timeouts for each handler domain. Each handler applies its own timeout via context.WithTimeout, rather than relying on a global pool timeout.
const ( // System commands Upgrade CommandType = "upgrade" Restart CommandType = "restart" Quit CommandType = "quit" Reboot CommandType = "reboot" Shutdown CommandType = "shutdown" Update CommandType = "update" ByeBye CommandType = "byebye" // Group commands AddGroup CommandType = "addgroup" DelGroup CommandType = "delgroup" // Info commands Ping CommandType = "ping" Help CommandType = "help" Commit CommandType = "commit" Sync CommandType = "sync" // Shell commands ShellCmd CommandType = "shell" Exec CommandType = "exec" // User commands AddUser CommandType = "adduser" DelUser CommandType = "deluser" ModUser CommandType = "moduser" // Firewall commands FirewallCmd CommandType = "firewall" FirewallRollback CommandType = "firewall-rollback" FirewallReorderChains CommandType = "firewall-reorder-chains" FirewallReorderRules CommandType = "firewall-reorder-rules" // Firewall sub-operations (used within firewall command) FirewallOpBatch string = "batch" FirewallOpFlush string = "flush" FirewallOpDelete string = "delete" FirewallOpAdd string = "add" FirewallOpUpdate string = "update" // File commands Upload CommandType = "upload" Download CommandType = "download" // Terminal commands OpenPty CommandType = "openpty" OpenFtp CommandType = "openftp" ResizePty CommandType = "resizepty" RefreshPty CommandType = "refreshpty" // Tunnel commands OpenTunnel CommandType = "opentunnel" CloseTunnel CommandType = "closetunnel" )
Command type constants
const ( ShellAndOperator = "&&" // Execute next command only if previous succeeds ShellOrOperator = "||" // Execute next command only if previous fails ShellSemicolon = ";" // Execute next command regardless of previous result )
Shell operators for command parsing
const TimeoutExitCode = 124
TimeoutExitCode is returned when a handler-level timeout is exceeded.
Variables ¶
This section is empty.
Functions ¶
func AbsentGroupLookup ¶ added in v2.3.2
AbsentGroupLookup reports the group absent (UnknownGroupError).
func AbsentUserLookup ¶ added in v2.3.2
AbsentUserLookup reports the user absent (UnknownUserError).
func ClassifyGroupForCreate ¶ added in v2.3.2
func ClassifyGroupForCreate(lookup GroupLookupFunc, groupname string, wantGID uint64) (needCreate bool, code int, out string, err error)
ClassifyGroupForCreate is the shared idempotency gate for group creation, used by both standalone addgroup (group handler) and the RHEL primary-group ensure inside handleAddUser, so the two paths stay consistent (A-3). It decides, purely from a lookup, whether the caller should create the group:
- needCreate=false, code==0 : group present with the expected gid; skip create.
- needCreate=true, code==0 : group absent; caller should create it (then pass the result through ReconcileGroupCreate as the secondary net).
- needCreate=false, code!=0 : gid conflict, unparseable gid, or a lookup that itself failed; caller returns (code, out, err) as-is.
func CommandsToStrings ¶
func CommandsToStrings(commands []CommandType) []string
CommandsToStrings converts a slice of CommandType to a slice of strings
func DefaultGroupLookup ¶ added in v2.3.2
DefaultGroupLookup is the production group lookup backed by os/user. The same CGO_ENABLED=0 caveat as DefaultUserLookup applies (reads /etc/group only).
func DefaultUserLookup ¶ added in v2.3.2
DefaultUserLookup is the production user lookup backed by os/user.
NOTE: the release binary is built with CGO_ENABLED=0 (see .goreleaser.yaml), so this uses the pure-Go resolver that reads only /etc/passwd—it does NOT consult NSS (nsswitch.conf). Local users (the provisioning target) resolve correctly, but names that exist only in LDAP/SSSD/AD are INVISIBLE here. Callers must not assume this lookup is authoritative for directory-backed names; ReconcileUserCreate adds a best-effort create-time net (a re-lookup for local TOCTOU races, plus an "already exists" output fallback for the NSS-backed case the resolver cannot see).
func GetBoolArg ¶
GetBoolArg retrieves a boolean argument from the args map
func GetStringArg ¶
GetStringArg retrieves a string argument from the args map
func GetStringSliceArg ¶
GetStringSliceArg retrieves a string slice argument from the args map
func GroupExists ¶ added in v2.3.2
GroupExists mirrors UserExists for groups (UnknownGroupError means absent).
func GroupGIDConflictMessage ¶ added in v2.3.2
GroupGIDConflictMessage mirrors UserUIDConflictMessage for groups.
func ReconcileGroupCreate ¶ added in v2.3.2
func ReconcileGroupCreate( lookup GroupLookupFunc, groupname string, wantGID uint64, code int, out string, cmdErr error, ) (int, string, error)
ReconcileGroupCreate is the create-time secondary net for addgroup/groupadd, mirroring ReconcileUserCreate. A group has no "omitted gid" case in the provisioning payloads (GID is validated required,min=1), so the gid is always compared when the group is visible. The "already exists" tertiary net tolerates only a same-name collision (NSS-backed group); a gid-in-use collision by a differently-named group is surfaced, since the requested name stays uncreated.
func ReconcileUserCreate ¶ added in v2.3.2
func ReconcileUserCreate( lookup UserLookupFunc, username string, wantUID uint64, uidRequested bool, code int, out string, cmdErr error, ) (int, string, error)
ReconcileUserCreate is the create-time secondary net for adduser/useradd.
The primary idempotency decision is made by an up-front UserExists gate. This net covers the two cases the gate cannot: a concurrent provisioner that created the user between the gate and the create call (TOCTOU), and—because the CGO_ENABLED=0 resolver cannot see NSS/LDAP/SSSD names—a directory-backed account the gate reported absent. On a non-zero create result:
- re-lookup finds the user with a matching (or omitted, uidRequested=false) uid -> idempotent success (0, "", nil).
- re-lookup finds the user with a DIFFERENT uid -> surface a conflict.
- re-lookup still cannot see the user BUT the create tool (which is NSS-aware) reported "already exists" -> tolerate as idempotent success. Identity cannot be verified here, so this is a best-effort last resort.
- otherwise -> return the original create failure unchanged.
When code == 0 it is a no-op that returns the inputs untouched, so callers can wrap every create unconditionally without affecting the success path.
func TimeoutError ¶
TimeoutError returns a standard timeout response (exit 124 + message).
func UserExists ¶ added in v2.3.2
UserExists classifies a user lookup result:
- found=true : the user is present (u is non-nil).
- found=false, err==nil : the user is definitively absent (UnknownUserError).
- found=false, err!=nil : the lookup itself failed (e.g. unreadable /etc/passwd); the caller must fail loud rather than blind-create.
func UserUIDConflictMessage ¶ added in v2.3.2
UserUIDConflictMessage is the single wording used whenever an existing user's uid differs from the requested one, so the up-front gate and the create-time secondary net surface identical, unambiguous drift messages.
func WithHandlerTimeout ¶
func WithHandlerTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)
WithHandlerTimeout wraps ctx with the given timeout and returns a context and cancel func. Use IsTimeout to check if the context deadline was exceeded, and TimeoutError to produce a standard timeout response (exit 124).
Types ¶
type APISession ¶
type APISession interface {
MultipartRequest(url string, body io.Reader, contentType string, contentLength int64, timeout time.Duration) ([]byte, int, error)
Delete(url string, rawBody interface{}, timeout time.Duration) ([]byte, int, error)
}
APISession interface for API operations (file upload, server unregister)
type BaseHandler ¶
type BaseHandler struct {
Executor CommandExecutor // Made public
// contains filtered or unexported fields
}
BaseHandler provides common functionality for all handlers
func NewBaseHandler ¶
func NewBaseHandler(name HandlerType, commands []CommandType, executor CommandExecutor) *BaseHandler
NewBaseHandler creates a new base handler
func (*BaseHandler) Commands ¶
func (h *BaseHandler) Commands() []string
Commands returns the list of supported commands
func (*BaseHandler) ValidateStruct ¶
func (h *BaseHandler) ValidateStruct(s interface{}) error
ValidateStruct validates a struct using struct tags
type CommandArgs ¶
type CommandArgs struct {
// Common fields
SessionID string
// CommandID is the UUID of the deploy shell Command that originated
// this execution (propagated from protocol.Command.ID). Empty for
// non-Command-driven work such as internal collectors.
CommandID string
URL string
Command string
Timeout time.Duration
AllowSh bool
// User management
Username string
Groupname string
Groupnames []string
HomeDirectory string
HomeDirectoryPermission string
PurgeHomeDirectory bool
UID uint64
GID uint64
Comment string
Shell string
Groups []uint64
IsServiceAccount bool
// File operations
Type string
Content string
Path string
Paths []string
Files []File
AllowOverwrite bool
AllowUnzip bool
UseBlob bool
// Terminal operations
Rows uint16
Cols uint16
Input string
// Tunnel operations
TargetPort int
ClientType string // cli, web, editor
// Environment
Env map[string]string
// ChunkCallback, if non-nil, receives streamed stdout/stderr chunks.
// Sequencing is the caller's responsibility.
ChunkCallback func(content string)
// Firewall operations
Keys []string
ChainName string
Method string
Chain string
Protocol string
PortStart int
PortEnd int
DPorts []int
ICMPType string
Source string
Destination string
Target string
Description string
Priority int
RuleType string
Rules []FirewallRule
Operation string
RuleID string
OldRuleID string
AssignmentID string
ServerID string
ChainNames []string
// Backend information
Backend string // Backend type: iptables, nftables, firewalld, ufw
Table string // iptables/nftables table: filter, nat, mangle, raw, security
Family string // IP family: ip (IPv4), ip6 (IPv6), inet, arp, bridge, netdev
// Firewalld specific
Zone string // Firewalld zone (default, public, etc.)
Service string // Firewalld service name
FirewalldRuleType string // Firewalld rule type: service, port, rich
// UFW specific
Direction string // UFW direction: in, out
Interface string // UFW interface name
}
CommandArgs is a strongly-typed struct for all command arguments
type CommandExecutor ¶
type CommandExecutor interface {
// Run executes a command with the given arguments
Run(ctx context.Context, name string, args ...string) (int, string, error)
// RunAsUser executes a command as a specific user
RunAsUser(ctx context.Context, username string, name string, args ...string) (int, string, error)
// RunWithInput executes a command with stdin input
RunWithInput(ctx context.Context, input string, name string, args ...string) (int, string, error)
// RunWithTimeout executes a command with a timeout
RunWithTimeout(ctx context.Context, timeout time.Duration, name string, args ...string) (int, string, error)
// Exec executes a command with all options (user, group, env, timeout)
Exec(ctx context.Context, args []string, username, groupname string, env map[string]string, timeout time.Duration) (int, string, error)
// ExecWithHook is like Exec but invokes pidHook with the child's pid
// immediately after the process is started (before it is waited on).
// Callers use it to register the root pid with the PAM tracker so
// sudo calls issued by the child can be attributed to the originating
// deploy shell Command. pidHook may be nil, in which case this
// behaves identically to Exec.
ExecWithHook(ctx context.Context, args []string, username, groupname string, env map[string]string, timeout time.Duration, pidHook func(pid int)) (int, string, error)
// ExecWithStreamingHook is like ExecWithHook with a chunkCallback that
// receives streamed stdout/stderr chunks. Sequencing is the caller's
// responsibility.
ExecWithStreamingHook(ctx context.Context, args []string, username, groupname string, env map[string]string, timeout time.Duration, pidHook func(pid int), chunkCallback func(content string)) (int, string, error)
}
CommandExecutor defines the interface for executing system commands. This interface is defined within the handlers package to avoid circular dependencies. The concrete implementation is in the executor package.
type CommandResult ¶
CommandResult represents the result of a mocked command execution.
type CommandType ¶
type CommandType string
CommandType represents the type of a command
func (CommandType) String ¶
func (c CommandType) String() string
String returns the string representation of CommandType
type ExecutedCommand ¶
ExecutedCommand represents a command that was executed by the mock.
type File ¶
type File struct {
Username string `json:"username"`
Groupname string `json:"groupname"`
Type string `json:"type"`
Content string `json:"content"`
Path string `json:"path"`
AllowOverwrite bool `json:"allow_overwrite"`
AllowUnzip bool `json:"allow_unzip"`
URL string `json:"url"`
}
File represents a file transfer operation
type FirewallRule ¶
type FirewallRule struct {
ChainName string `json:"chain_name"`
Method string `json:"method"`
Chain string `json:"chain"`
Protocol string `json:"protocol"`
PortStart int `json:"port_start"`
PortEnd int `json:"port_end"`
DPorts []int `json:"dports"`
ICMPType string `json:"icmp_type"`
Source string `json:"source"`
Destination string `json:"destination"`
Target string `json:"target"`
Description string `json:"description"`
Priority int `json:"priority"`
RuleType string `json:"rule_type"`
RuleID string `json:"rule_id"`
OldRuleID string `json:"old_rule_id"`
Operation string `json:"operation"`
}
FirewallRule represents a single firewall rule
type GroupLookupFunc ¶ added in v2.3.2
GroupLookupFunc resolves a group by name, mirroring os/user.LookupGroup.
func ExistingGroupLookup ¶ added in v2.3.2
func ExistingGroupLookup(gid string) GroupLookupFunc
ExistingGroupLookup returns a fake reporting a group present with the given gid.
type Handler ¶
type Handler interface {
// Name returns the handler name (e.g., "system", "user", "firewall")
Name() string
// Commands returns the list of commands this handler supports
Commands() []string
// Execute runs the specified command with the given arguments.
// Returns exit code, output string, and error if command fails.
Execute(ctx context.Context, cmd string, args *CommandArgs) (exitCode int, output string, err error)
// Validate checks if the provided arguments are valid for the command.
// This allows pre-execution validation without running the command.
Validate(cmd string, args *CommandArgs) error
}
Handler defines the interface for command handlers. Each handler is responsible for executing a specific set of commands.
type HandlerType ¶
type HandlerType string
HandlerType represents the type of a handler
const ( System HandlerType = "system" Group HandlerType = "group" Info HandlerType = "info" Shell HandlerType = "shell" User HandlerType = "user" Firewall HandlerType = "firewall" FileTransfer HandlerType = "file" Terminal HandlerType = "terminal" Tunnel HandlerType = "tunnel" )
Handler type constants
func (HandlerType) String ¶
func (h HandlerType) String() string
String returns the string representation of HandlerType
type MockCommandExecutor ¶
type MockCommandExecutor struct {
// contains filtered or unexported fields
}
MockCommandExecutor is a mock implementation of CommandExecutor for testing. It is the single source of truth for mocking in this package.
func NewMockCommandExecutor ¶
func NewMockCommandExecutor(t *testing.T) *MockCommandExecutor
func (*MockCommandExecutor) ExecWithHook ¶
func (m *MockCommandExecutor) ExecWithHook(ctx context.Context, args []string, username, groupname string, env map[string]string, timeout time.Duration, pidHook func(pid int)) (int, string, error)
ExecWithHook mirrors Exec for mock purposes and invokes pidHook (when non-nil) with a distinct, positive synthetic pid so handlers that rely on the hook contract can be exercised without spawning real processes. Using a positive pid ensures the registration code paths in the PID tracker (which ignores pid<=0) are actually exercised.
func (*MockCommandExecutor) ExecWithStreamingHook ¶ added in v2.3.1
func (m *MockCommandExecutor) ExecWithStreamingHook(ctx context.Context, args []string, username, groupname string, env map[string]string, timeout time.Duration, pidHook func(pid int), chunkCallback func(content string)) (int, string, error)
ExecWithStreamingHook mirrors ExecWithHook and emits the full output as one chunk.
func (*MockCommandExecutor) GetExecutedCommands ¶
func (m *MockCommandExecutor) GetExecutedCommands() []ExecutedCommand
func (*MockCommandExecutor) Invoked ¶ added in v2.3.2
func (m *MockCommandExecutor) Invoked(program string) bool
Invoked reports whether a command whose program name equals program was run.
func (*MockCommandExecutor) RunWithInput ¶
func (*MockCommandExecutor) RunWithTimeout ¶
type SystemInfoManager ¶
type SystemInfoManager interface {
CommitSystemInfo()
SyncSystemInfo(keys []string)
}
SystemInfoManager interface for system info operations
type UserLookupFunc ¶ added in v2.3.2
UserLookupFunc resolves a user by name. It mirrors os/user.Lookup so the production default can be assigned directly, while tests inject a fake.
func ExistingUserLookup ¶ added in v2.3.2
func ExistingUserLookup(uid string) UserLookupFunc
ExistingUserLookup returns a fake reporting a user present with the given uid.
type VersionResolver ¶
type VersionResolver interface {
GetLatestVersion() string
GetPamVersion() string
InvalidatePamCache()
}
VersionResolver provides version information for upgrade decisions.