mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 17:56:52 +01:00
Add a local control socket and nebula ctl (#1975)
Add support for accessing the previously ssh-only debug commands via `nebula ctl <command>.` - unix socket (named pipe on windows) for communication - sockets scoped to the user + an optional group - in cmd/nebula and cmd/nebula-service, but not nebula.Main Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a5bdb508e
commit
0f767ce529
54 files changed
+7581
-925
No files matched your search
@@ -0,0 +1,79 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Access is who may reach a socket beyond the account nebula runs as: the node's mode,
|
||||
// and the group that shares the socket when the config names one.
|
||||
//
|
||||
// It is the dockerd model (-G): the operator names a group, nebula hands the socket to
|
||||
// it, and who is in the group is managed outside nebula. On unix the mode is the
|
||||
// socket node's and the group becomes its owning group; on windows the group is an
|
||||
// entry in the pipe's security descriptor and the mode means nothing to a pipe.
|
||||
type Access struct {
|
||||
Mode os.FileMode
|
||||
Group Group
|
||||
}
|
||||
|
||||
// Group is a group named in the config, resolved to what the platform checks. The zero
|
||||
// Group is none.
|
||||
type Group struct {
|
||||
// Name is what the config said.
|
||||
Name string
|
||||
|
||||
// ID is the platform's identifier for it: a gid on unix, a SID on windows.
|
||||
ID string
|
||||
}
|
||||
|
||||
const (
|
||||
// ownerOnlyMode is a socket's mode when no group shares it: the account nebula runs
|
||||
// as, and nobody else.
|
||||
ownerOnlyMode os.FileMode = 0600
|
||||
|
||||
// groupSocketMode is the mode a socket takes when a group shares it and the config
|
||||
// names no other: the account nebula runs as and the group, and nobody else.
|
||||
groupSocketMode os.FileMode = 0660
|
||||
)
|
||||
|
||||
// errReopenForAccess is returned by applyAccess on a platform whose access control is
|
||||
// fixed when the endpoint is created, so a change means creating it again.
|
||||
var errReopenForAccess = errors.New("access is fixed at listen time")
|
||||
|
||||
// ownerOnly is the control socket's access when no group is configured. The mode
|
||||
// itself is not configurable there; what an operator can choose is a group,
|
||||
// CtlGroupKey, which makes it groupSocketMode.
|
||||
func ownerOnly() Access { return Access{Mode: ownerOnlyMode} }
|
||||
|
||||
// shared reports whether a group shares the socket.
|
||||
func (a Access) shared() bool { return a.Group.ID != "" }
|
||||
|
||||
// String renders the access for a log line.
|
||||
func (a Access) String() string {
|
||||
if !a.shared() {
|
||||
return modeString(a.Mode)
|
||||
}
|
||||
return fmt.Sprintf("%s group %s (%s)", modeString(a.Mode), a.Group.Name, a.Group.ID)
|
||||
}
|
||||
|
||||
// groupFromConfig reads the group a key names, the zero Group when it names none. The
|
||||
// name is resolved here, when the config is read, so a group that does not exist is a
|
||||
// config error reported with the key rather than a bind that fails later.
|
||||
func groupFromConfig(c interface{ GetString(string, string) string }, key string) (Group, error) {
|
||||
name := c.GetString(key, "")
|
||||
if name == "" {
|
||||
return Group{}, nil
|
||||
}
|
||||
|
||||
id, err := lookupGroupID(name)
|
||||
if err != nil {
|
||||
return Group{}, fmt.Errorf("%s: %w", key, err)
|
||||
}
|
||||
return Group{Name: name, ID: id}, nil
|
||||
}
|
||||
|
||||
func modeString(m os.FileMode) string {
|
||||
return fmt.Sprintf("%04o", m)
|
||||
}
|
||||
Reference in new issue
Block a user