nix2container icon indicating copy to clipboard operation
nix2container copied to clipboard

Add support for adding capabilities

Open ulrikstrid opened this issue 1 year ago • 4 comments

This is very hacky, just wanted to push before I close for the day. It needs a lot of cleanup when it finally works.

I'm not a Go programmer at all so excuse the quality. I tried basing the changes on the perms feature which seems like it's quite similar in use.

This doesn't seem to work as expected as when it's untared it doesn't have the correct capabilities. But I do have a small Go program to test that the bytes seems to be generated correctly, so I assume it's a question of trying to figure out what format the string should be in.

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"
	"syscall"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Printf("Usage: %s <filepath>\n", os.Args[0])
        os.Exit(1)
    }
    filepath := os.Args[1]

    // Version 3 capability format
    // struct vfs_cap_data {
    //     __le32 magic_etc;            /* magic, version and flags */
    //     struct {
    //         __le32 permitted;        /* permitted capabilities */
    //         __le32 inheritable;      /* inheritable capabilities */
    //     } data[2];                   /* realistically, one is enough */
    //     __le32 effective;            /* effective capabilities */
    // };
		type vfsNsCapData struct {
			MagicEtc uint32
			Data     [2]struct {
				Permitted   uint32
				Inheritable uint32
			}
			Effective uint32
		}

		const vfsCapRevision3 = 0x03000000

		data := vfsNsCapData{MagicEtc: vfsCapRevision3 | uint32(0)}

		data.Data[0].Permitted = uint32(10)
		data.Data[0].Inheritable = uint32(10)
		data.Data[1].Permitted = uint32(10 >> 32)
		data.Data[1].Inheritable = uint32(10 >> 32)
		data.Effective = uint32(10)

		fmt.Printf("Failed to write buffer: %v\n", data)


    buf := &bytes.Buffer{}
		if err := binary.Write(buf, binary.LittleEndian, data); err != nil {
			fmt.Printf("Failed to write buffer: %v\n", err)
			os.Exit(1)
		}

    capBytes := buf.Bytes()

		fmt.Printf("capBytes: %v\n", capBytes)

    // Set the extended attribute
    err := syscall.Setxattr(filepath, "security.capability", capBytes, 0)
    if err != nil {
        fmt.Printf("Failed to set capability: %v\n", err)
        os.Exit(1)
    }

    fmt.Printf("Successfully set CAP_NET_BIND_SERVICE capability on %s\n", filepath)
}

ulrikstrid avatar Nov 13 '24 16:11 ulrikstrid

This is now technically working for my use case. It would have to become generic and probably a lot of cleanup before we can merge it. But I'm now unblocked from shipping my container.

ulrikstrid avatar Nov 14 '24 13:11 ulrikstrid

I ended up not needing this because I could solve it at another level. If you want to close this that's fine, otherwise I'll try to get back to it when I have some more time and do the clean-up and generalisation.

ulrikstrid avatar Nov 15 '24 15:11 ulrikstrid

@ulrikstrid oh, that's unfortunate you no longer need this because this is pretty interesting! But if someone else is interested in, this dratf will be pretty useful since 80% of the job is implemented.

Excepting supporting user specified capabilities (not only CAP_NET_BIND_SERVICE), only tests are missing I think.

nlewo avatar Nov 15 '24 18:11 nlewo

If you're open to keeping the PR around I'm willing to fix it, just at a slower pace 😊

ulrikstrid avatar Nov 15 '24 18:11 ulrikstrid