Unify trunk on main: initialization → main #3
+47
-13
@@ -7,6 +7,7 @@ import (
|
|||||||
osuser "os/user"
|
osuser "os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/novox/mesh-host/internal/declaration"
|
"github.com/novox/mesh-host/internal/declaration"
|
||||||
"github.com/novox/mesh-host/internal/system"
|
"github.com/novox/mesh-host/internal/system"
|
||||||
@@ -97,18 +98,9 @@ func own(path, owner string) error {
|
|||||||
if owner == "" {
|
if owner == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
found, err := osuser.Lookup(owner)
|
uid, gid, err := idsOf(owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s should belong to %q and this machine has no such user: %w",
|
return fmt.Errorf("%s should belong to %q: %w", path, owner, err)
|
||||||
path, owner, err)
|
|
||||||
}
|
|
||||||
uid, err := strconv.Atoi(found.Uid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gid, err := strconv.Atoi(found.Gid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if err := os.Chown(path, uid, gid); err != nil {
|
if err := os.Chown(path, uid, gid); err != nil {
|
||||||
return fmt.Errorf("cannot give %s to %q: %w", path, owner, err)
|
return fmt.Errorf("cannot give %s to %q: %w", path, owner, err)
|
||||||
@@ -116,12 +108,54 @@ func own(path, owner string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// idsOf resolves an owner to a uid and gid: a name this machine knows, or numbers it does not.
|
||||||
|
//
|
||||||
|
// **Numbers, because a container's user is a number the machine has never heard of.** A directory
|
||||||
|
// a module mounts into its container belongs to whoever runs inside — grafana's 472, redis's 999,
|
||||||
|
// www-data's 33 — and none of those has a row in this machine's passwd, so there is no name to
|
||||||
|
// look up and none to create. Refusing them looked principled and meant every module whose
|
||||||
|
// container drops privileges could not own its own data: the store's config was unreadable to
|
||||||
|
// the store, and the forge could not traverse into the directory that held its files.
|
||||||
|
//
|
||||||
|
// "uid:gid" and bare "uid" are numeric; anything else is a name, resolved as before.
|
||||||
|
func idsOf(owner string) (int, int, error) {
|
||||||
|
user, group, both := strings.Cut(owner, ":")
|
||||||
|
if uid, err := strconv.Atoi(user); err == nil {
|
||||||
|
gid := uid
|
||||||
|
if both {
|
||||||
|
g, err := strconv.Atoi(group)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf(
|
||||||
|
"%q reads as a uid with a group that is not a gid", owner)
|
||||||
|
}
|
||||||
|
gid = g
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
if both {
|
||||||
|
return 0, 0, fmt.Errorf("%q mixes a name with a colon; a name stands alone", owner)
|
||||||
|
}
|
||||||
|
found, err := osuser.Lookup(owner)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("this machine has no such user: %w", err)
|
||||||
|
}
|
||||||
|
uid, err := strconv.Atoi(found.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(found.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ownedBy reports whether a path already belongs to a user, so applying twice changes nothing.
|
// ownedBy reports whether a path already belongs to a user, so applying twice changes nothing.
|
||||||
func ownedBy(path, owner string) (bool, error) {
|
func ownedBy(path, owner string) (bool, error) {
|
||||||
if owner == "" {
|
if owner == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
found, err := osuser.Lookup(owner)
|
wantUID, wantGID, err := idsOf(owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -133,7 +167,7 @@ func ownedBy(path, owner string) (bool, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return strconv.Itoa(uid) == found.Uid && strconv.Itoa(gid) == found.Gid, nil
|
return uid == wantUID && gid == wantGID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ownAll gives a whole tree to a user, for an archive that was unpacked into it.
|
// ownAll gives a whole tree to a user, for an archive that was unpacked into it.
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package apply
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// A container's user is a number the machine has never heard of — grafana's 472, redis's 999 —
|
||||||
|
// so an owner must be expressible without a passwd row. Refusing numerics looked principled and
|
||||||
|
// meant every module whose container drops privileges could not own its own data.
|
||||||
|
func TestAnOwnerMayBeANumberTheMachineDoesNotKnow(t *testing.T) {
|
||||||
|
for owner, want := range map[string][2]int{
|
||||||
|
"472:472": {472, 472},
|
||||||
|
"1000:1000": {1000, 1000},
|
||||||
|
"999": {999, 999},
|
||||||
|
"10001:10001": {10001, 10001},
|
||||||
|
"33:0": {33, 0},
|
||||||
|
} {
|
||||||
|
uid, gid, err := idsOf(owner)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q refused: %v", owner, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if uid != want[0] || gid != want[1] {
|
||||||
|
t.Errorf("%q resolved to %d:%d, wanted %d:%d", owner, uid, gid, want[0], want[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, _, err := idsOf("no-such-user-exists-here"); err == nil {
|
||||||
|
t.Error("a name this machine does not know was accepted")
|
||||||
|
}
|
||||||
|
if _, _, err := idsOf("root:something"); err == nil {
|
||||||
|
t.Error("a name with a colon was accepted; a name stands alone")
|
||||||
|
}
|
||||||
|
if uid, gid, err := idsOf("root"); err != nil || uid != 0 || gid != 0 {
|
||||||
|
t.Errorf("root resolved to %d:%d (%v); names must still work", uid, gid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user