Compare commits
2 Commits
65e04dfdb3
...
fad0eb755a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fad0eb755a | ||
|
|
587e906d6d |
6
main.go
6
main.go
@@ -24,7 +24,11 @@ const (
|
|||||||
G = 1024 * 1024 * 1024
|
G = 1024 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Setup ensures that everything needed for running podman under the given uid is setup
|
||||||
|
// new{u,g}idmap is handled separately
|
||||||
|
// Note: This should be called while still root (before MustDropPrivileges) and must be called after any use of the GOKRAZY_FIRST_START environment variable
|
||||||
func Setup(uid int) error {
|
func Setup(uid int) error {
|
||||||
|
// Needed so that calls to to podman which in turn call new{u,g}idmap will succeed
|
||||||
os.Unsetenv("GOKRAZY_FIRST_START")
|
os.Unsetenv("GOKRAZY_FIRST_START")
|
||||||
err := os.Mkdir("/var/run/user", 0o755)
|
err := os.Mkdir("/var/run/user", 0o755)
|
||||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
@@ -174,7 +178,7 @@ func IsMounted(mountpoint string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, line := range strings.Split(strings.TrimSpace(string(b)), "\n") {
|
for line := range strings.SplitSeq(strings.TrimSpace(string(b)), "\n") {
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
if len(parts) < 5 {
|
if len(parts) < 5 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gitea.narnian.us/lordwelch/Podman"
|
"gitea.narnian.us/lordwelch/Podman"
|
||||||
@@ -8,7 +9,14 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
||||||
Podman.WriteSubids("/etc/subgid")
|
subidContent, err := Podman.GetSubids("/etc/subgid")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to generate /etc/subgid successfully, podman will probably not work: %s", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile("/etc/subgid", subidContent, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to write to /etc/subgid, podman will probably not work: %s", err)
|
||||||
|
}
|
||||||
os.Exit(125)
|
os.Exit(125)
|
||||||
}
|
}
|
||||||
Podman.NewIDMap("gid_map")
|
Podman.NewIDMap("gid_map")
|
||||||
|
|||||||
73
newidmap.go
73
newidmap.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -56,34 +57,39 @@ func NewIDMap(idMapName string) {
|
|||||||
log.Fatal("You aren't allowed to do that :-P")
|
log.Fatal("You aren't allowed to do that :-P")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
user Passwd
|
// This will get overwritten by /etc/passwd if we read subids from files
|
||||||
subid moby_user.SubID
|
user = Passwd{
|
||||||
passwds []Passwd = LoadAllPasswd()
|
User: os.Getenv("USER"),
|
||||||
|
UID: int(fstat.Uid),
|
||||||
|
GID: int(fstat.Gid),
|
||||||
|
}
|
||||||
|
subid = generateSubid(int64(fstat.Uid))
|
||||||
)
|
)
|
||||||
subidstart := subidStart
|
subid.Name = user.User
|
||||||
for _, u := range passwds {
|
|
||||||
if u.UID == int(fstat.Uid) {
|
if subidUseFile {
|
||||||
user = u
|
passwds, err := LoadAllPasswd()
|
||||||
subid = moby_user.SubID{
|
if err != nil {
|
||||||
Name: user.User,
|
// Continue because we don't actually want to fail if there is no /etc/passwd
|
||||||
SubID: subidstart,
|
// This command assumes a static mapping of subids based on the current uid. Which is already verified above
|
||||||
Count: subidCount,
|
log.Println(err)
|
||||||
|
}
|
||||||
|
for _, u := range passwds {
|
||||||
|
if u.UID == int(fstat.Uid) {
|
||||||
|
user = u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subidstart += subidCount
|
if user.User == "" {
|
||||||
}
|
log.Fatalf("Unable to find user")
|
||||||
if user.User == "" {
|
}
|
||||||
log.Fatalf("Unable to find user")
|
|
||||||
}
|
|
||||||
if subidUseFile {
|
|
||||||
if idMapName == "uid_map" {
|
if idMapName == "uid_map" {
|
||||||
subid, err = getSubIDs("/etc/subuid", user.User, strconv.Itoa(user.UID))
|
subid, err = getSubIDs("/etc/subuid", user.User, strconv.Itoa(user.UID))
|
||||||
} else {
|
} else {
|
||||||
subid, err = getSubIDs("/etc/subgid", user.User, strconv.Itoa(user.GID))
|
subid, err = getSubIDs("/etc/subgid", user.User, strconv.Itoa(user.GID))
|
||||||
}
|
}
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Unable to get subid map for the user: %v", err)
|
||||||
log.Fatalf("Unable to get subid map for the user: %v", err)
|
}
|
||||||
}
|
}
|
||||||
idmaps, err := getIDMaps(int64(user.UID), subid, args)
|
idmaps, err := getIDMaps(int64(user.UID), subid, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,19 +124,28 @@ func NewIDMap(idMapName string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteSubids(file string) error {
|
func GetSubids(passwd string) ([]byte, error) {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
passwds := LoadAllPasswd()
|
passwds, err := LoadAllPasswd(passwd)
|
||||||
subidstart := subidStart
|
if err != nil { // Only errors are io errors reading /etc/passwd
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Slice(passwds, func(i, j int) bool { return passwds[i].UID < passwds[j].UID })
|
||||||
for _, passwd := range passwds {
|
for _, passwd := range passwds {
|
||||||
fmt.Fprintf(buf, "%s:%d:%d\n", passwd.User, subidstart, subidCount)
|
subid := generateSubid(int64(passwd.UID))
|
||||||
subidstart += subidCount
|
_, err := fmt.Fprintf(buf, "%s:%d:%d\n", passwd.User, subid.SubID, subid.Count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err := os.WriteFile(file, buf.Bytes(), 0o644)
|
return buf.Bytes(), nil
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
|
func generateSubid(id int64) moby_user.SubID {
|
||||||
|
return moby_user.SubID{
|
||||||
|
SubID: subidStart + (subidCount * id),
|
||||||
|
Count: subidCount,
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIDMaps(id int64, subid moby_user.SubID, args []string) ([]moby_user.IDMap, error) {
|
func getIDMaps(id int64, subid moby_user.SubID, args []string) ([]moby_user.IDMap, error) {
|
||||||
|
|||||||
38
newidmap_test.go
Normal file
38
newidmap_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package Podman_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.narnian.us/lordwelch/Podman"
|
||||||
|
)
|
||||||
|
|
||||||
|
// passwdContent is specifically not in order
|
||||||
|
const passwdContent = `root:x:0:0:root:/perm/home:/perm/bin/bash
|
||||||
|
vw:x:2:2:vw:/perm/home/vw:/bin/nologin
|
||||||
|
jellyfin:x:1:1:jellyfin:/perm/home/jellyfin:/bin/nologin
|
||||||
|
nzbget:x:3:3:nzbget:/perm/home/nzbget:/bin/nologin
|
||||||
|
`
|
||||||
|
|
||||||
|
// expected is always in uid order
|
||||||
|
const expected = `root:200:65536
|
||||||
|
jellyfin:65736:65536
|
||||||
|
vw:131272:65536
|
||||||
|
nzbget:196808:65536
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestGetSubids(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
passwd := tmpDir + "/passwd"
|
||||||
|
err := os.WriteFile(passwd, []byte(passwdContent), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to write %s: %s", passwd, err)
|
||||||
|
}
|
||||||
|
ids, err := Podman.GetSubids(passwd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to generate subids", err)
|
||||||
|
}
|
||||||
|
if got, want := string(ids), expected; got != want {
|
||||||
|
t.Errorf("GetSubids: unexpected result: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gitea.narnian.us/lordwelch/Podman"
|
"gitea.narnian.us/lordwelch/Podman"
|
||||||
@@ -8,7 +9,14 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
||||||
Podman.WriteSubids("/etc/subuid")
|
subidContent, err := Podman.GetSubids("/etc/subuid")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to generate /etc/subuid successfully, podman will probably not work: %s", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile("/etc/subuid", subidContent, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to write to /etc/subuid, podman will probably not work: %s", err)
|
||||||
|
}
|
||||||
os.Exit(125)
|
os.Exit(125)
|
||||||
}
|
}
|
||||||
Podman.NewIDMap("uid_map")
|
Podman.NewIDMap("uid_map")
|
||||||
|
|||||||
28
podmanSocket/main.go
Normal file
28
podmanSocket/main.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"gitea.narnian.us/lordwelch/Podman"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
Podman.Setup(199) // subuids start at 200
|
||||||
|
Podman.MustDropPrivileges(Podman.Passwd{
|
||||||
|
User: "podmanSocket",
|
||||||
|
UID: 199,
|
||||||
|
GID: 199,
|
||||||
|
Home: "/perm/home/podmanSocket",
|
||||||
|
Shell: "/bin/sh",
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"/user/podman", "system", "--log-level=debug", "service", "--time=0", "unix:///run/podman.sock",
|
||||||
|
}
|
||||||
|
err := syscall.Exec("/user/podman", args, os.Environ())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to start podman: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
privDrop.go
19
privDrop.go
@@ -1,6 +1,7 @@
|
|||||||
package Podman
|
package Podman
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -70,8 +71,9 @@ type Passwd struct {
|
|||||||
func MustDropPrivileges(passwd Passwd, caps ...string) {
|
func MustDropPrivileges(passwd Passwd, caps ...string) {
|
||||||
err := os.Chown(passwd.Home, passwd.UID, passwd.GID)
|
err := os.Chown(passwd.Home, passwd.UID, passwd.GID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("Failed to chown home directory", os.Getenv("HOME"))
|
log.Printf("Failed to chown home directory %s", passwd.Home)
|
||||||
}
|
}
|
||||||
|
os.Setenv("HOME", passwd.Home)
|
||||||
os.Setenv("USER", passwd.User)
|
os.Setenv("USER", passwd.User)
|
||||||
c := cap.GetProc()
|
c := cap.GetProc()
|
||||||
if os.Getenv("PRIVILEGES_DROPPED") == "1" {
|
if os.Getenv("PRIVILEGES_DROPPED") == "1" {
|
||||||
@@ -141,11 +143,18 @@ func LoadPasswd() Passwd {
|
|||||||
}
|
}
|
||||||
return passwd
|
return passwd
|
||||||
}
|
}
|
||||||
func LoadAllPasswd() []Passwd {
|
|
||||||
|
// LoadAllPasswd reads /etc/passwd by default.
|
||||||
|
// If a string is passed the first argument is used as the path instead of /etc/passwd
|
||||||
|
func LoadAllPasswd(passwd ...string) ([]Passwd, error) {
|
||||||
var passwds []Passwd
|
var passwds []Passwd
|
||||||
b, err := os.ReadFile("/etc/passwd")
|
var passwdFile = "/etc/passwd"
|
||||||
|
if len(passwd) > 0 {
|
||||||
|
passwdFile = passwd[0]
|
||||||
|
}
|
||||||
|
b, err := os.ReadFile(passwdFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return passwds
|
return passwds, fmt.Errorf("Unable to read /etc/passwd: %w", err)
|
||||||
}
|
}
|
||||||
for line := range strings.SplitSeq(string(b), "\n") {
|
for line := range strings.SplitSeq(string(b), "\n") {
|
||||||
fields := strings.SplitN(line, ":", 7)
|
fields := strings.SplitN(line, ":", 7)
|
||||||
@@ -168,5 +177,5 @@ func LoadAllPasswd() []Passwd {
|
|||||||
passwd.Shell = fields[6]
|
passwd.Shell = fields[6]
|
||||||
passwds = append(passwds, passwd)
|
passwds = append(passwds, passwd)
|
||||||
}
|
}
|
||||||
return passwds
|
return passwds, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user