Compare commits
7 Commits
65e04dfdb3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6c539bc29 | ||
|
|
5d7a257b1f | ||
|
|
94723719fe | ||
|
|
bee2165ddd | ||
|
|
d050d58e92 | ||
|
|
fad0eb755a | ||
|
|
587e906d6d |
56
main.go
56
main.go
@@ -3,6 +3,7 @@ package Podman
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@@ -24,7 +25,11 @@ const (
|
||||
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 {
|
||||
// Needed so that calls to to podman which in turn call new{u,g}idmap will succeed
|
||||
os.Unsetenv("GOKRAZY_FIRST_START")
|
||||
err := os.Mkdir("/var/run/user", 0o755)
|
||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||
@@ -45,6 +50,10 @@ func Setup(uid int) error {
|
||||
return fmt.Errorf("unable to set owner for %s: %w", rundir, err)
|
||||
}
|
||||
}
|
||||
// catatonit needs /var/tmp for the pause container
|
||||
if err := os.MkdirAll("/var/tmp", 0o777); err != nil {
|
||||
fmt.Errorf("unable to create '/var/tmp': %w", err)
|
||||
}
|
||||
err = os.Chmod("/var/tmp", 0o1777)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set perms for /var/tmp: %w", err)
|
||||
@@ -65,11 +74,34 @@ func Setup(uid int) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set perms for /dev/net/tun: %w", err)
|
||||
}
|
||||
if err := MakeWritable("/etc/containers/networks/"); err != nil {
|
||||
return fmt.Errorf("unable to make writeable for /etc/containers/networks/: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll("/var/lib/containers/storage/volumes", 0o777); err != nil {
|
||||
return fmt.Errorf("unable to create /var/lib/containers/storage/volumes: %w", err)
|
||||
}
|
||||
// podman default socket location is /run/podman/podman.sock
|
||||
if err := os.MkdirAll("/run/podman/", 0o770); err != nil {
|
||||
return fmt.Errorf("unable to create /run/podman/: %w", err)
|
||||
}
|
||||
if err := os.Symlink("/proc/self/fd/0", "/dev/stdin"); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||
return fmt.Errorf("unable to make /dev/stdin symlink: %w", err)
|
||||
}
|
||||
if err := os.Symlink("/proc/self/fd/1", "/dev/stdout"); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||
return fmt.Errorf("unable to make /dev/stdout symlink: %w", err)
|
||||
}
|
||||
if err := os.Symlink("/proc/self/fd/2", "/dev/stderr"); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||
return fmt.Errorf("unable to make /dev/stderr symlink: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Run(args ...string) error {
|
||||
podman := exec.Command("/user/podman", args...)
|
||||
func Run(command string, args ...string) error {
|
||||
if command == "" {
|
||||
command = "/user/podman"
|
||||
}
|
||||
podman := exec.Command(command, args...)
|
||||
podman.Env = append(os.Environ(), "TMPDIR=/tmp")
|
||||
podman.Stdin = os.Stdin
|
||||
podman.Stdout = os.Stdout
|
||||
@@ -91,8 +123,11 @@ func Run(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Start(args ...string) (chan os.Signal, *exec.Cmd, error) {
|
||||
podman := exec.Command("/user/podman", args...)
|
||||
func Start(command string, args ...string) (chan os.Signal, *exec.Cmd, error) {
|
||||
if command == "" {
|
||||
command = "/user/podman"
|
||||
}
|
||||
podman := exec.Command(command, args...)
|
||||
podman.Env = append(os.Environ(), "TMPDIR=/tmp")
|
||||
podman.Stdin = os.Stdin
|
||||
podman.Stdout = os.Stdout
|
||||
@@ -113,6 +148,17 @@ func Start(args ...string) (chan os.Signal, *exec.Cmd, error) {
|
||||
return exit, podman, nil
|
||||
}
|
||||
|
||||
func Devices(devices ...string) []string {
|
||||
devs := []string{}
|
||||
for _, dev := range devices {
|
||||
_, err := os.Stat("/dev/" + dev)
|
||||
if err == nil {
|
||||
devs = append(devs, "--device=/dev/"+dev)
|
||||
}
|
||||
}
|
||||
return devs
|
||||
}
|
||||
|
||||
func BindRO(src, dst string) string {
|
||||
return fmt.Sprintf(`--mount=type=bind,source=%s,destination=%s,ro=true`, src, dst)
|
||||
}
|
||||
@@ -174,7 +220,7 @@ func IsMounted(mountpoint string) (bool, error) {
|
||||
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)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.narnian.us/lordwelch/Podman"
|
||||
@@ -8,7 +9,14 @@ import (
|
||||
|
||||
func main() {
|
||||
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
||||
Podman.WriteSubids("/etc/subgid")
|
||||
subidContent, err := Podman.GetSubids("/etc/passwd")
|
||||
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)
|
||||
}
|
||||
Podman.NewIDMap("gid_map")
|
||||
|
||||
73
newidmap.go
73
newidmap.go
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -56,34 +57,39 @@ func NewIDMap(idMapName string) {
|
||||
log.Fatal("You aren't allowed to do that :-P")
|
||||
}
|
||||
var (
|
||||
user Passwd
|
||||
subid moby_user.SubID
|
||||
passwds []Passwd = LoadAllPasswd()
|
||||
// This will get overwritten by /etc/passwd if we read subids from files
|
||||
user = Passwd{
|
||||
User: os.Getenv("USER"),
|
||||
UID: int(fstat.Uid),
|
||||
GID: int(fstat.Gid),
|
||||
}
|
||||
subid = generateSubid(int64(fstat.Uid))
|
||||
)
|
||||
subidstart := subidStart
|
||||
for _, u := range passwds {
|
||||
if u.UID == int(fstat.Uid) {
|
||||
user = u
|
||||
subid = moby_user.SubID{
|
||||
Name: user.User,
|
||||
SubID: subidstart,
|
||||
Count: subidCount,
|
||||
subid.Name = user.User
|
||||
|
||||
if subidUseFile {
|
||||
passwds, err := LoadAllPasswd()
|
||||
if err != nil {
|
||||
// Continue because we don't actually want to fail if there is no /etc/passwd
|
||||
// This command assumes a static mapping of subids based on the current uid. Which is already verified above
|
||||
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 subidUseFile {
|
||||
if user.User == "" {
|
||||
log.Fatalf("Unable to find user")
|
||||
}
|
||||
if idMapName == "uid_map" {
|
||||
subid, err = getSubIDs("/etc/subuid", user.User, strconv.Itoa(user.UID))
|
||||
} else {
|
||||
subid, err = getSubIDs("/etc/subgid", user.User, strconv.Itoa(user.GID))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get subid map for the user: %v", err)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get subid map for the user: %v", err)
|
||||
}
|
||||
}
|
||||
idmaps, err := getIDMaps(int64(user.UID), subid, args)
|
||||
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{}
|
||||
passwds := LoadAllPasswd()
|
||||
subidstart := subidStart
|
||||
passwds, err := LoadAllPasswd(passwd)
|
||||
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 {
|
||||
fmt.Fprintf(buf, "%s:%d:%d\n", passwd.User, subidstart, subidCount)
|
||||
subidstart += subidCount
|
||||
subid := generateSubid(int64(passwd.UID))
|
||||
_, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.narnian.us/lordwelch/Podman"
|
||||
@@ -8,7 +9,14 @@ import (
|
||||
|
||||
func main() {
|
||||
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
||||
Podman.WriteSubids("/etc/subuid")
|
||||
subidContent, err := Podman.GetSubids("/etc/passwd")
|
||||
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)
|
||||
}
|
||||
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/docker.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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -70,8 +71,9 @@ type Passwd struct {
|
||||
func MustDropPrivileges(passwd Passwd, caps ...string) {
|
||||
err := os.Chown(passwd.Home, passwd.UID, passwd.GID)
|
||||
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)
|
||||
c := cap.GetProc()
|
||||
if os.Getenv("PRIVILEGES_DROPPED") == "1" {
|
||||
@@ -141,11 +143,18 @@ func LoadPasswd() 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
|
||||
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 {
|
||||
return passwds
|
||||
return passwds, fmt.Errorf("Unable to read /etc/passwd: %w", err)
|
||||
}
|
||||
for line := range strings.SplitSeq(string(b), "\n") {
|
||||
fields := strings.SplitN(line, ":", 7)
|
||||
@@ -168,5 +177,5 @@ func LoadAllPasswd() []Passwd {
|
||||
passwd.Shell = fields[6]
|
||||
passwds = append(passwds, passwd)
|
||||
}
|
||||
return passwds
|
||||
return passwds, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user