Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package qubesome for openSUSE:Factory checked in at 2024-12-09 21:13:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/qubesome (Old) and /work/SRC/openSUSE:Factory/.qubesome.new.29675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "qubesome" Mon Dec 9 21:13:03 2024 rev:3 rq:1229429 version:0.0.8 Changes: -------- --- /work/SRC/openSUSE:Factory/qubesome/qubesome.changes 2024-11-26 20:56:10.479121091 +0100 +++ /work/SRC/openSUSE:Factory/.qubesome.new.29675/qubesome.changes 2024-12-09 21:14:06.531335200 +0100 @@ -1,0 +2,26 @@ +Sun Dec 08 09:47:22 UTC 2024 - [email protected] + +- Update to version 0.0.8: + * obs: Remove rebuild process + * build: Group dependabot updates + * profiles: Check whether profile is running Instead of erroring, confirm whether the profile is running, if it isn't it is safe to delete to file and start a new profile. + * build(deps): bump github.com/urfave/cli/v3 + * Add GPU support for podman + * Move drive and env pkgs to internal/util + * obs: Add rebuild_package step on push + * Move ephemeral dir to ~/.qubesome/run Previously the ephemeral dir for qubesome was kept at /run/user/1000/qubesome, this has now moved to ~/.qubesome/run instead. Profile dirs are created inside the new dir and removed once 'qubesome start' finishes. + * build: Add fuzz testing checks + * profile: Enforce mTLS with inception server + * obs: Move source project to :unstable A new long standing project will be the home of qubesome packages when changes are merged into main. The new project name is: home:pjbgf:devel:languages:go:unstable + * build(deps): bump github.com/urfave/cli/v3 + * obs: Trigger services on PR merge + * WSL: Eval symlink to .X11-unix dir + * xauth: Decrease min magic cookie length to 40 This is causing issues on Tumbleweed distrobox in WSL, where the cookie being generated is 49 chars long. + * Remove redundant files + * xauth: Add Fuzz tests + * clip: Show active profiles for autocomplete + * build: Pin GH Actions. Set bump interval to monthly + * obs: Change target project to align with branch convention + * build: Add OBS integration + +------------------------------------------------------------------- Old: ---- qubesome-0.0.7.tar.gz New: ---- qubesome-0.0.8.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ qubesome.spec ++++++ --- /var/tmp/diff_new_pack.yG4Wjd/_old 2024-12-09 21:14:07.019355618 +0100 +++ /var/tmp/diff_new_pack.yG4Wjd/_new 2024-12-09 21:14:07.023355785 +0100 @@ -17,7 +17,7 @@ Name: qubesome -Version: 0.0.7 +Version: 0.0.8 Release: 0 Summary: Containerize Window Managers, apps and config from a declarative state in Git License: Apache-2.0 @@ -41,7 +41,7 @@ %check # execute the binary as a basic check -./%{name} deps show +./%{name} deps %install install -D -m 0755 %{name} "%{buildroot}/%{_bindir}/%{name}" ++++++ _service ++++++ --- /var/tmp/diff_new_pack.yG4Wjd/_old 2024-12-09 21:14:07.051356957 +0100 +++ /var/tmp/diff_new_pack.yG4Wjd/_new 2024-12-09 21:14:07.055357124 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/qubesome/cli.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.0.7</param> + <param name="revision">v0.0.8</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="match-tag">v*</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.yG4Wjd/_old 2024-12-09 21:14:07.075357961 +0100 +++ /var/tmp/diff_new_pack.yG4Wjd/_new 2024-12-09 21:14:07.075357961 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/qubesome/cli.git</param> - <param name="changesrevision">698eb74437070f2fe7c9623ad1702369f2b6e5b6</param></service></servicedata> + <param name="changesrevision">f21dcef5cda98bca020f8463171184c991efd59a</param></service></servicedata> (No newline at EOF) ++++++ qubesome-0.0.7.tar.gz -> qubesome-0.0.8.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/.github/dependabot.yaml new/qubesome-0.0.8/.github/dependabot.yaml --- old/qubesome-0.0.7/.github/dependabot.yaml 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/.github/dependabot.yaml 2024-12-08 10:40:21.000000000 +0100 @@ -4,8 +4,16 @@ directory: "/" schedule: interval: "weekly" + groups: + golang.org: + patterns: + - "golang.org/*" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" + interval: "monthly" + groups: + github-actions-updates: + patterns: + - "*" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/.github/workflows/fuzz.yml new/qubesome-0.0.8/.github/workflows/fuzz.yml --- old/qubesome-0.0.7/.github/workflows/fuzz.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/qubesome-0.0.8/.github/workflows/fuzz.yml 2024-12-08 10:40:21.000000000 +0100 @@ -0,0 +1,42 @@ +name: fuzz tests + +on: + push: + workflow_dispatch: + + schedule: + - cron: "0 7 * * 6" + +permissions: {} + +jobs: + fuzz: + runs-on: ubuntu-latest + + permissions: + contents: read + actions: read # for cache access + env: + FUZZ_TIME: 10m + + steps: + - name: Checkout + uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb # v4.2.2 + + - name: Set up Go + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + with: + go-version: stable + + - name: Set environment variable based on trigger + run: | + if [[ "${{ github.event_name }}" != "push" ]]; then + echo "FUZZ_TIME=30m" >> $GITHUB_ENV + fi + + - name: Fuzzing + uses: form3tech-oss/go-ci-fuzz/ci/github-actions/fuzz@4663eaaadb263d2621592c62681dac7f7002d582 + with: + fuzz-time: ${{ env.FUZZ_TIME }} + fail-fast: true + version: 0.1.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/.github/workflows/release.yml new/qubesome-0.0.8/.github/workflows/release.yml --- old/qubesome-0.0.7/.github/workflows/release.yml 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/.github/workflows/release.yml 2024-12-08 10:40:21.000000000 +0100 @@ -18,20 +18,20 @@ steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb # v4.2.2 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version: stable - - uses: anchore/sbom-action/[email protected] - - uses: sigstore/[email protected] + - uses: anchore/sbom-action/download-syft@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8 + - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 with: distribution: goreleaser version: '~> v2' @@ -40,6 +40,6 @@ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Attest release artefacts - uses: actions/attest-build-provenance@v1 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: "dist/qubesome*" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/.github/workflows/test.yml new/qubesome-0.0.8/.github/workflows/test.yml --- old/qubesome-0.0.7/.github/workflows/test.yml 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/.github/workflows/test.yml 2024-12-08 10:40:21.000000000 +0100 @@ -14,10 +14,10 @@ steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@cbb722410c2e876e24abbe8de2cc27693e501dcb # v4.2.2 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version: stable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/.obs/workflows.yml new/qubesome-0.0.8/.obs/workflows.yml --- old/qubesome-0.0.7/.obs/workflows.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/qubesome-0.0.8/.obs/workflows.yml 2024-12-08 10:40:21.000000000 +0100 @@ -0,0 +1,20 @@ +version: '1.1' +testbuild: + steps: + - branch_package: + source_project: home:pjbgf:devel:languages:go:unstable + source_package: qubesome + target_project: home:pjbgf:ci + add_repositories: disable + - configure_repositories: + project: home:pjbgf:ci + repositories: + - name: openSUSE_Factory + paths: + - target_project: home:pjbgf:devel:languages:go:unstable + target_repository: openSUSE_Factory + architectures: + - x86_64 + - aarch64 + filters: + event: pull_request diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/cmd/cli/clipboard.go new/qubesome-0.0.8/cmd/cli/clipboard.go --- old/qubesome-0.0.7/cmd/cli/clipboard.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/cmd/cli/clipboard.go 2024-12-08 10:40:21.000000000 +0100 @@ -48,6 +48,14 @@ Flags: []cli.Flag{ clipType, }, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + if cmd.NArg() > 0 { + return + } + for _, p := range activeProfiles() { + fmt.Println(p) + } + }, Action: func(ctx context.Context, c *cli.Command) error { target, err := profileOrActive(targetProfile) if err != nil { @@ -88,6 +96,14 @@ Flags: []cli.Flag{ clipType, }, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + if cmd.NArg() > 1 { + return + } + for _, p := range activeProfiles() { + fmt.Println(p) + } + }, Action: func(ctx context.Context, c *cli.Command) error { cfg := profileConfigOrDefault(targetProfile) @@ -136,6 +152,14 @@ Flags: []cli.Flag{ clipType, }, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + if cmd.NArg() > 0 { + return + } + for _, p := range activeProfiles() { + fmt.Println(p) + } + }, Action: func(ctx context.Context, c *cli.Command) error { target, err := profileOrActive(sourceProfile) if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/cmd/cli/root.go new/qubesome-0.0.8/cmd/cli/root.go --- old/qubesome-0.0.7/cmd/cli/root.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/cmd/cli/root.go 2024-12-08 10:40:21.000000000 +0100 @@ -157,3 +157,14 @@ return active } + +func activeProfiles() []string { + cfgs := activeConfigs() + profiles := make([]string, 0, len(cfgs)) + + for _, file := range cfgs { + profiles = append(profiles, strings.TrimSuffix(filepath.Base(file), ".config")) + } + + return profiles +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/go.mod new/qubesome-0.0.8/go.mod --- old/qubesome-0.0.7/go.mod 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/go.mod 2024-12-08 10:40:21.000000000 +0100 @@ -7,7 +7,7 @@ github.com/go-git/go-git/v5 v5.12.1-0.20241115094014-70dd9f8347eb github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.10.0 - github.com/urfave/cli/v3 v3.0.0-alpha9.5 + github.com/urfave/cli/v3 v3.0.0-beta1 golang.org/x/sys v0.27.0 google.golang.org/grpc v1.68.0 google.golang.org/protobuf v1.35.2 @@ -30,7 +30,6 @@ github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/net v0.31.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/go.sum new/qubesome-0.0.8/go.sum --- old/qubesome-0.0.7/go.sum 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/go.sum 2024-12-08 10:40:21.000000000 +0100 @@ -65,14 +65,12 @@ github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli/v3 v3.0.0-alpha9.5 h1:Y693/B4H1nO6s5Xxns2AVLWNZ650HCF7AjThgvkP0bw= -github.com/urfave/cli/v3 v3.0.0-alpha9.5/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/command/mocks.go new/qubesome-0.0.8/internal/command/mocks.go --- old/qubesome-0.0.7/internal/command/mocks.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/command/mocks.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,95 +0,0 @@ -package command - -import ( - "github.com/qubesome/cli/internal/types" - "github.com/stretchr/testify/mock" -) - -func NewHandlerMock[T any](f func(a App) (Action[T], []Option[T], error)) *HandlerMock[T] { - return &HandlerMock[T]{ - handle: f, - } -} - -type HandlerMock[T any] struct { - handle func(a App) (Action[T], []Option[T], error) - mock.Mock -} - -func (m *HandlerMock[T]) Handle(a App) (Action[T], []Option[T], error) { - return m.handle(a) -} - -func (m *HandlerMock[T]) Run(opts ...Option[T]) error { - args := m.Called(opts) - return args.Error(0) -} - -type ConsoleMock[T any] struct { - mock.Mock -} - -func (m *ConsoleMock[T]) ExecName() string { - args := m.Called() - return args.String(0) -} - -func (m *ConsoleMock[T]) Args() []string { - args := m.Called() - return args.Get(0).([]string) //nolint -} - -func (m *ConsoleMock[T]) UserConfig() *types.Config { - args := m.Called() - cfg := args.Get(0) - - if cfg == nil { - return nil - } - - return cfg.(*types.Config) //nolint -} - -func (m *ConsoleMock[T]) ProfileConfig(a string) *types.Config { - args := m.Called(a) - cfg := args.Get(0) - - if cfg == nil { - return nil - } - - return cfg.(*types.Config) //nolint -} - -func (m *ConsoleMock[T]) Command(name string) bool { - args := m.Called(name) - return args.Bool(0) -} - -func (m *ConsoleMock[T]) RunSubCommand() error { - args := m.Called() - return args.Error(0) -} - -func (m *ConsoleMock[T]) Usage(format string) { - m.Called(format) -} - -func (m *ConsoleMock[T]) Exit(code int) { - m.Called(code) -} - -func (m *ConsoleMock[T]) Printf(format string, a ...any) (int, error) { - args := m.Called(format, a) - return args.Int(0), args.Error(1) -} - -func (m *ConsoleMock[T]) Handle(a App) (Action[T], []Option[T], error) { - args := m.Called(a) - return args.Get(0).(Action[T]), args.Get(1).([]Option[T]), args.Error(2) //nolint -} - -func (m *ConsoleMock[T]) Run(opts ...Option[T]) error { - args := m.Called(opts) - return args.Error(0) -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/drive/drive.go new/qubesome-0.0.8/internal/drive/drive.go --- old/qubesome-0.0.7/internal/drive/drive.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/drive/drive.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,47 +0,0 @@ -package drive - -import ( - "bufio" - "bytes" - "fmt" - "os" - "strings" -) - -const mountsFile = "/proc/mounts" - -var readFile = os.ReadFile - -func Mounts(drive, mount string) (bool, error) { - if drive == "" { - return false, fmt.Errorf("drive is empty") - } - if mount == "" { - return false, fmt.Errorf("mount is empty") - } - - data, err := readFile(mountsFile) - if err != nil { - return false, fmt.Errorf("failed to open mounts file: %w", err) - } - - r := bytes.NewReader(data) - scanner := bufio.NewScanner(r) - - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) - if fields[0] != drive { - continue - } - - if len(fields) <= 1 { - continue - } - - if fields[1] == mount { - return true, nil - } - } - - return false, nil -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/drive/drive_test.go new/qubesome-0.0.8/internal/drive/drive_test.go --- old/qubesome-0.0.7/internal/drive/drive_test.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/drive/drive_test.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,89 +0,0 @@ -package drive - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMounts(t *testing.T) { - tests := []struct { - name string - drive string - mount string - want bool - readFileFunc func(name string) ([]byte, error) - err string - }{ - { - name: "empty drive", - mount: "/media", - err: "drive is empty", - }, - { - name: "empty mount", - drive: "/media", - err: "mount is empty", - }, - { - name: "drive not found", - drive: "foo-bar", - mount: "/media", - readFileFunc: func(name string) ([]byte, error) { - return []byte("foo\nbar\n"), nil - }, - }, - { - name: "mounts file not found", - drive: "foo-bar", - mount: "/media", - readFileFunc: func(name string) ([]byte, error) { - return nil, os.ErrNotExist - }, - err: "failed to open mounts file: file does not exist", - }, - { - name: "single mount point", - drive: "foo-bar", - mount: "/media/foo/bar", - readFileFunc: func(name string) ([]byte, error) { - return []byte("foo\nbar\nfoo-bar /media/foo/bar\n"), nil - }, - want: true, - }, - { - name: "multiple mount points", - drive: "foo-bar", - mount: "/media/foo/bar", - readFileFunc: func(name string) ([]byte, error) { - return []byte("foo-bar /foo/for/bar\nfoo\nbar\nfoo-bar /media/foo/bar\n"), nil - }, - want: true, - }, - { - name: "not right mount point", - drive: "foo-bar", - mount: "/media/bar/foo", - readFileFunc: func(name string) ([]byte, error) { - return []byte("foo\nbar\nfoo-bar /media/foo/bar\n"), nil - }, - want: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - readFile = tc.readFileFunc - got, err := Mounts(tc.drive, tc.mount) - - if tc.err == "" { - require.NoError(t, err) - assert.Equal(t, tc.want, got) - } else { - require.ErrorContains(t, err, tc.err) - } - }) - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/env/env.go new/qubesome-0.0.8/internal/env/env.go --- old/qubesome-0.0.7/internal/env/env.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/env/env.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,41 +0,0 @@ -package env - -import ( - "fmt" - "log/slog" - "os" -) - -func init() { //nolint - h, _ := os.UserHomeDir() - _ = Update("HOME", h) -} - -var mapping = map[string]string{ - "HOME": "", - "GITDIR": "", -} - -func Update(k, v string) error { - slog.Debug("setting env", k, v) - if _, ok := mapping[k]; ok { - mapping[k] = v - return nil - } - return fmt.Errorf("%q is not an expandable env var", k) -} - -func Add(k, v string) { - mapping[k] = v -} - -func Expand(in string) string { - return os.Expand(in, expand) -} - -func expand(s string) string { - if out, ok := mapping[s]; ok { - return out - } - return "" -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/files/files.go new/qubesome-0.0.8/internal/files/files.go --- old/qubesome-0.0.7/internal/files/files.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/files/files.go 2024-12-08 10:40:21.000000000 +0100 @@ -3,8 +3,8 @@ // Key locations: // - ~/.qubesome: default location for persistent files. // - ~/.qubesome/images-last-checked: file that stores when images were last checked. -// - /run/user/%d/qubesome: root of ephemeral files. -// - /run/user/%d/qubesome/git/<git-url>/<path>: where git repositories +// - ~/.qubesome/run: root of ephemeral files. +// - ~/.qubesome/git/<git-url>/<path>: where git repositories // are cloned to. package files @@ -53,7 +53,7 @@ // RunUserQubesome returns the path to the user-specific qubesome directory. func RunUserQubesome() string { - return fmt.Sprintf("/run/user/%d/qubesome", os.Getuid()) + return filepath.Join(QubesomeDir(), "run") } // ClientCookiePath returns the path to the client cookie file for the given profile. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/inception/client.go new/qubesome-0.0.8/internal/inception/client.go --- old/qubesome-0.0.7/internal/inception/client.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/inception/client.go 2024-12-08 10:40:21.000000000 +0100 @@ -2,14 +2,18 @@ import ( "context" + "crypto/tls" + "crypto/x509" "fmt" "log/slog" + "os" "strings" "time" + "github.com/qubesome/cli/internal/util/mtls" pb "github.com/qubesome/cli/pkg/inception/proto" "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials" ) func NewClient(socket string) *Client { @@ -22,8 +26,42 @@ socket string } +func getCreds() (credentials.TransportCredentials, error) { + caPEM := []byte(os.Getenv("Q_MTLS_CA")) + certPEM := []byte(os.Getenv("Q_MTLS_CERT")) + keyPEM := []byte(os.Getenv("Q_MTLS_KEY")) + + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return nil, err + } + + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(caPEM) { + return nil, err + } + + creds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: certPool, + MinVersion: tls.VersionTLS13, + // The connection is made via unix socket, so generally the + // expected server name will be localhost - unless overridden + // by ServerName. + ServerName: mtls.HostServerName, + }) + + return creds, nil +} + func (c *Client) XdgOpen(ctx context.Context, url string) error { - conn, err := grpc.NewClient(c.socket, grpc.WithTransportCredentials(insecure.NewCredentials())) + creds, err := getCreds() + if err != nil { + return err + } + + conn, err := grpc.NewClient(c.socket, + grpc.WithTransportCredentials(creds)) if err != nil { return fmt.Errorf("failed to connect to qubesome host: %w", err) } @@ -44,7 +82,12 @@ } func (c *Client) Run(ctx context.Context, workload string, args []string) error { - conn, err := grpc.NewClient(c.socket, grpc.WithTransportCredentials(insecure.NewCredentials())) + creds, err := getCreds() + if err != nil { + return err + } + + conn, err := grpc.NewClient(c.socket, grpc.WithTransportCredentials(creds)) if err != nil { return fmt.Errorf("failed to connect to qubesome host: %w", err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/profiles/profiles.go new/qubesome-0.0.8/internal/profiles/profiles.go --- old/qubesome-0.0.7/internal/profiles/profiles.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/profiles/profiles.go 2024-12-08 10:40:21.000000000 +0100 @@ -16,14 +16,15 @@ "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/google/uuid" "github.com/qubesome/cli/internal/command" - "github.com/qubesome/cli/internal/drive" - "github.com/qubesome/cli/internal/env" "github.com/qubesome/cli/internal/files" "github.com/qubesome/cli/internal/images" "github.com/qubesome/cli/internal/runners/util/container" "github.com/qubesome/cli/internal/types" "github.com/qubesome/cli/internal/util/dbus" + "github.com/qubesome/cli/internal/util/drive" + "github.com/qubesome/cli/internal/util/env" "github.com/qubesome/cli/internal/util/gpu" + "github.com/qubesome/cli/internal/util/mtls" "github.com/qubesome/cli/internal/util/resolution" "github.com/qubesome/cli/internal/util/xauth" "github.com/qubesome/cli/pkg/inception" @@ -85,10 +86,10 @@ ln := files.ProfileConfig(name) if _, err := os.Lstat(ln); err == nil { - // Wayland is not cleaning up profile state after closure. - if !strings.EqualFold(os.Getenv("XDG_SESSION_TYPE"), "wayland") { + if container.Running(runner, fmt.Sprintf(ContainerNameFormat, name)) { return fmt.Errorf("profile %q is already started", name) } + if err = os.Remove(ln); err != nil { return fmt.Errorf("failed to remove leftover profile symlink: %w", err) } @@ -167,10 +168,6 @@ return fmt.Errorf("cannot file profile %q in config %q", name, cfgPath) } - if p.Runner != "" { - runner = p.Runner - } - // When sourcing from git, ensure profile path is relative to the git repository. pp, err := securejoin.SecureJoin(filepath.Dir(cfgPath), p.Path) if err != nil { @@ -197,6 +194,12 @@ return err } + // If runner is not being overwritten (via -runner), use the runner + // set at profile level in the config. + if runner == "" && profile.Runner != "" { + runner = profile.Runner + } + binary := files.ContainerRunnerBinary(runner) fi, err := os.Lstat(binary) if err != nil || !fi.Mode().IsRegular() { @@ -250,11 +253,15 @@ return err } + creds, err := mtls.NewCredentials() + if err != nil { + return err + } go func() { defer wg.Done() server := inception.NewServer(profile, cfg) - err1 := server.Listen(sockPath) + err1 := server.Listen(creds.ServerCert, creds.CA, sockPath) if err1 != nil { slog.Debug("error listening to socket", "error", err1) if err == nil { @@ -264,7 +271,12 @@ }() defer func() { - _ = os.Remove(sockPath) + // Clean up the profile dir once profile finishes. + pd := files.ProfileDir(profile.Name) + err = os.RemoveAll(pd) + if err != nil { + slog.Warn("failed to remove profile dir", "path", pd, "error", err) + } }() err = createMagicCookie(profile) @@ -272,7 +284,9 @@ return err } - err = createNewDisplay(binary, profile, strconv.Itoa(int(profile.Display))) + err = createNewDisplay(binary, + creds.CA, creds.ClientPEM, creds.ClientKeyPEM, + profile, strconv.Itoa(int(profile.Display))) if err != nil { return err } @@ -356,7 +370,7 @@ return nil } -func createNewDisplay(bin string, profile *types.Profile, display string) error { +func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile, display string) error { command := "Xephyr" res, err := resolution.Primary() if err != nil { @@ -433,10 +447,20 @@ break } + x11Dir := "/tmp/.X11-unix" + if os.Getenv("WSL_DISTRO_NAME") != "" { + fmt.Println("\033[33mWARN: Running qubesome in WSL is experimental. Some features may not work as expected.\033[0m") + fp, err := filepath.EvalSymlinks(x11Dir) + if err != nil { + return fmt.Errorf("failed to eval symlink: %w", err) + } + x11Dir = fp + } + //nolint var paths []string paths = append(paths, "-v=/etc/localtime:/etc/localtime:ro") - paths = append(paths, "-v=/tmp/.X11-unix:/tmp/.X11-unix:rw") + paths = append(paths, fmt.Sprintf("-v=%s:/tmp/.X11-unix:rw", x11Dir)) paths = append(paths, fmt.Sprintf("-v=%s:/tmp/qube.sock:ro", socket)) paths = append(paths, fmt.Sprintf("-v=%s:/home/xorg-user/.Xserver", server)) paths = append(paths, fmt.Sprintf("-v=%s:/home/xorg-user/.Xauthority", workload)) @@ -453,6 +477,9 @@ // rely on currently set DISPLAY. "-e", "DISPLAY", "-e", "XDG_SESSION_TYPE=X11", + "-e", "Q_MTLS_CA", + "-e", "Q_MTLS_CERT", + "-e", "Q_MTLS_KEY", "--device", "/dev/dri", "--security-opt=no-new-privileges:true", "--cap-drop=ALL", @@ -477,9 +504,10 @@ } if profile.HostAccess.Gpus != "" { if strings.HasSuffix(bin, "podman") { - dockerArgs = append(dockerArgs, "--runtime=nvidia.com/gpu=all") + dockerArgs = append(dockerArgs, "--device=nvidia.com/gpu=all") + } else { + dockerArgs = append(dockerArgs, "--gpus", profile.HostAccess.Gpus) } - dockerArgs = append(dockerArgs, "--gpus", profile.HostAccess.Gpus) } if profile.DNS != "" { @@ -536,6 +564,9 @@ slog.Debug("exec: "+bin, "args", dockerArgs) cmd := execabs.Command(bin, dockerArgs...) + cmd.Env = append(os.Environ(), "Q_MTLS_CA="+string(ca)) + cmd.Env = append(cmd.Env, "Q_MTLS_CERT="+string(cert)) + cmd.Env = append(cmd.Env, "Q_MTLS_KEY="+string(key)) output, err := cmd.CombinedOutput() if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/qubesome/run.go new/qubesome-0.0.8/internal/qubesome/run.go --- old/qubesome-0.0.7/internal/qubesome/run.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/qubesome/run.go 2024-12-08 10:40:21.000000000 +0100 @@ -12,8 +12,6 @@ securejoin "github.com/cyphar/filepath-securejoin" "github.com/qubesome/cli/internal/command" - "github.com/qubesome/cli/internal/drive" - "github.com/qubesome/cli/internal/env" "github.com/qubesome/cli/internal/files" "github.com/qubesome/cli/internal/images" "github.com/qubesome/cli/internal/inception" @@ -22,6 +20,8 @@ "github.com/qubesome/cli/internal/runners/podman" "github.com/qubesome/cli/internal/types" "github.com/qubesome/cli/internal/util/dbus" + "github.com/qubesome/cli/internal/util/drive" + "github.com/qubesome/cli/internal/util/env" "gopkg.in/yaml.v3" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/runners/docker/run.go new/qubesome-0.0.8/internal/runners/docker/run.go --- old/qubesome-0.0.7/internal/runners/docker/run.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/runners/docker/run.go 2024-12-08 10:40:21.000000000 +0100 @@ -9,13 +9,13 @@ "strconv" "strings" - "github.com/qubesome/cli/internal/env" "github.com/qubesome/cli/internal/files" "github.com/qubesome/cli/internal/runners/util/container" "github.com/qubesome/cli/internal/runners/util/mime" "github.com/qubesome/cli/internal/runners/util/usb" "github.com/qubesome/cli/internal/types" "github.com/qubesome/cli/internal/util/dbus" + "github.com/qubesome/cli/internal/util/env" "github.com/qubesome/cli/internal/util/gpu" "golang.org/x/sys/execabs" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/runners/podman/run.go new/qubesome-0.0.8/internal/runners/podman/run.go --- old/qubesome-0.0.7/internal/runners/podman/run.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/runners/podman/run.go 2024-12-08 10:40:21.000000000 +0100 @@ -9,13 +9,13 @@ "strconv" "strings" - "github.com/qubesome/cli/internal/env" "github.com/qubesome/cli/internal/files" "github.com/qubesome/cli/internal/runners/util/container" "github.com/qubesome/cli/internal/runners/util/mime" "github.com/qubesome/cli/internal/runners/util/usb" "github.com/qubesome/cli/internal/types" "github.com/qubesome/cli/internal/util/dbus" + "github.com/qubesome/cli/internal/util/env" "github.com/qubesome/cli/internal/util/gpu" "golang.org/x/sys/execabs" ) @@ -82,7 +82,7 @@ } if wl.HostAccess.Gpus != "" { - args = append(args, "--gpus", wl.HostAccess.Gpus) + args = append(args, "--device=nvidia.com/gpu=all") } for _, cap := range wl.HostAccess.CapsAdd { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/socket/socket.go new/qubesome-0.0.8/internal/socket/socket.go --- old/qubesome-0.0.7/internal/socket/socket.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/socket/socket.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,80 +0,0 @@ -package socket - -import ( - "fmt" - "log/slog" - "net" - "os" - "os/signal" - "path/filepath" - "syscall" - - "github.com/qubesome/cli/internal/files" - "github.com/qubesome/cli/internal/types" -) - -type ConnectionHandler func(cfg *types.Config, p *types.Profile, conn net.Conn) - -func Listen(p *types.Profile, cfg *types.Config, handler ConnectionHandler) error { - fn, err := files.SocketPath(p.Name) - if err != nil { - return err - } - - // Removes previous versions of the socket that were not cleaned up. - if _, err := os.Stat(fn); err == nil { - _ = os.Remove(fn) - } - - err = os.MkdirAll(filepath.Dir(fn), files.DirMode) - if err != nil { - return err - } - - socket, err := net.Listen("unix", fn) - if err != nil { - return fmt.Errorf("failed to listen to socket: %w", err) - } - defer func() { - _ = os.Remove(fn) - }() - - uid := os.Getuid() - - err = os.Chmod(fn, files.FileMode) - if err != nil { - return err - } - - pdir := fmt.Sprintf("/run/user/%d/qubesome/%s", uid, p.Name) - err = os.MkdirAll(pdir, files.DirMode) - if err != nil { - return fmt.Errorf("failed to create profile dir: %w", err) - } - - err = os.Chmod(pdir, files.DirMode) - if err != nil { - return err - } - - // Remove the sock file if the process is terminated. - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - os.Exit(1) - }() - - slog.Debug("listening profile socket", "path", fn) - for { - // Accept an incoming connection. - conn, err := socket.Accept() - if err != nil { - slog.Error("cannot accept connection", "error", err) - continue - } - - // Handle the connection in a separate goroutine. - go handler(cfg, p, conn) - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/types/workload.go new/qubesome-0.0.8/internal/types/workload.go --- old/qubesome-0.0.7/internal/types/workload.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/types/workload.go 2024-12-08 10:40:21.000000000 +0100 @@ -6,7 +6,7 @@ "slices" "strings" - "github.com/qubesome/cli/internal/env" + "github.com/qubesome/cli/internal/util/env" ) type Workload struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/util/drive/drive.go new/qubesome-0.0.8/internal/util/drive/drive.go --- old/qubesome-0.0.7/internal/util/drive/drive.go 1970-01-01 01:00:00.000000000 +0100 +++ new/qubesome-0.0.8/internal/util/drive/drive.go 2024-12-08 10:40:21.000000000 +0100 @@ -0,0 +1,47 @@ +package drive + +import ( + "bufio" + "bytes" + "fmt" + "os" + "strings" +) + +const mountsFile = "/proc/mounts" + +var readFile = os.ReadFile + +func Mounts(drive, mount string) (bool, error) { + if drive == "" { + return false, fmt.Errorf("drive is empty") + } + if mount == "" { + return false, fmt.Errorf("mount is empty") + } + + data, err := readFile(mountsFile) + if err != nil { + return false, fmt.Errorf("failed to open mounts file: %w", err) + } + + r := bytes.NewReader(data) + scanner := bufio.NewScanner(r) + + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if fields[0] != drive { + continue + } + + if len(fields) <= 1 { + continue + } + + if fields[1] == mount { + return true, nil + } + } + + return false, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/util/drive/drive_test.go new/qubesome-0.0.8/internal/util/drive/drive_test.go --- old/qubesome-0.0.7/internal/util/drive/drive_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/qubesome-0.0.8/internal/util/drive/drive_test.go 2024-12-08 10:40:21.000000000 +0100 @@ -0,0 +1,89 @@ +package drive + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMounts(t *testing.T) { + tests := []struct { + name string + drive string + mount string + want bool + readFileFunc func(name string) ([]byte, error) + err string + }{ + { + name: "empty drive", + mount: "/media", + err: "drive is empty", + }, + { + name: "empty mount", + drive: "/media", + err: "mount is empty", + }, + { + name: "drive not found", + drive: "foo-bar", + mount: "/media", + readFileFunc: func(name string) ([]byte, error) { + return []byte("foo\nbar\n"), nil + }, + }, + { + name: "mounts file not found", + drive: "foo-bar", + mount: "/media", + readFileFunc: func(name string) ([]byte, error) { + return nil, os.ErrNotExist + }, + err: "failed to open mounts file: file does not exist", + }, + { + name: "single mount point", + drive: "foo-bar", + mount: "/media/foo/bar", + readFileFunc: func(name string) ([]byte, error) { + return []byte("foo\nbar\nfoo-bar /media/foo/bar\n"), nil + }, + want: true, + }, + { + name: "multiple mount points", + drive: "foo-bar", + mount: "/media/foo/bar", + readFileFunc: func(name string) ([]byte, error) { + return []byte("foo-bar /foo/for/bar\nfoo\nbar\nfoo-bar /media/foo/bar\n"), nil + }, + want: true, + }, + { + name: "not right mount point", + drive: "foo-bar", + mount: "/media/bar/foo", + readFileFunc: func(name string) ([]byte, error) { + return []byte("foo\nbar\nfoo-bar /media/foo/bar\n"), nil + }, + want: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + readFile = tc.readFileFunc + got, err := Mounts(tc.drive, tc.mount) + + if tc.err == "" { + require.NoError(t, err) + assert.Equal(t, tc.want, got) + } else { + require.ErrorContains(t, err, tc.err) + } + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/util/env/env.go new/qubesome-0.0.8/internal/util/env/env.go --- old/qubesome-0.0.7/internal/util/env/env.go 1970-01-01 01:00:00.000000000 +0100 +++ new/qubesome-0.0.8/internal/util/env/env.go 2024-12-08 10:40:21.000000000 +0100 @@ -0,0 +1,41 @@ +package env + +import ( + "fmt" + "log/slog" + "os" +) + +func init() { //nolint + h, _ := os.UserHomeDir() + _ = Update("HOME", h) +} + +var mapping = map[string]string{ + "HOME": "", + "GITDIR": "", +} + +func Update(k, v string) error { + slog.Debug("setting env", k, v) + if _, ok := mapping[k]; ok { + mapping[k] = v + return nil + } + return fmt.Errorf("%q is not an expandable env var", k) +} + +func Add(k, v string) { + mapping[k] = v +} + +func Expand(in string) string { + return os.Expand(in, expand) +} + +func expand(s string) string { + if out, ok := mapping[s]; ok { + return out + } + return "" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/util/mtls/mtls.go new/qubesome-0.0.8/internal/util/mtls/mtls.go --- old/qubesome-0.0.7/internal/util/mtls/mtls.go 1970-01-01 01:00:00.000000000 +0100 +++ new/qubesome-0.0.8/internal/util/mtls/mtls.go 2024-12-08 10:40:21.000000000 +0100 @@ -0,0 +1,159 @@ +package mtls + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" +) + +const ( + validFor = 7 * 24 * time.Hour // 7 days + // ProfileServerName sets the server name for the qubesome profile. + ProfileServerName = "qubesome-profile" + // HostServerName sets the server name for the qubesome host. + HostServerName = "qubesome-host" +) + +type Credentials struct { + ServerCert tls.Certificate + CA []byte + ClientPEM []byte + ClientKeyPEM []byte +} + +func NewCredentials() (*Credentials, error) { + caCert, caKey, caBytes, err := generateCA() + if err != nil { + return nil, err + } + + serverCertBytes, serverKey, err := generateCert(caCert, caKey, true) + if err != nil { + return nil, err + } + serverCertPEM, serverKeyPEM, err := pemEncode(serverCertBytes, serverKey) + if err != nil { + return nil, err + } + + serverCert, err := tls.X509KeyPair(serverCertPEM, serverKeyPEM) + if err != nil { + return nil, err + } + + clientCertBytes, clientKey, err := generateCert(caCert, caKey, false) + if err != nil { + return nil, err + } + clientCertPEM, clientKeyPEM, err := pemEncode(clientCertBytes, clientKey) + if err != nil { + return nil, err + } + + ca := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caBytes}) + return &Credentials{ + ServerCert: serverCert, + CA: ca, + ClientPEM: clientCertPEM, + ClientKeyPEM: clientKeyPEM, + }, nil +} + +// generateCA generates an in-memory CA certificate and private key. +func generateCA() (*x509.Certificate, *ecdsa.PrivateKey, []byte, error) { + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to generate CA private key: %w", err) + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().UnixNano()), + Subject: pkix.Name{ + CommonName: "qubesome inception CA", + Organization: []string{"qubesome"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(validFor), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + IsCA: true, + BasicConstraintsValid: true, + SignatureAlgorithm: x509.ECDSAWithSHA256, + } + + certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to create CA certificate: %w", err) + } + + certPEM := new(bytes.Buffer) + err = pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to PEM encode certificate: %w", err) + } + + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to parse CA certificate: %w", err) + } + + return cert, priv, certBytes, nil +} + +// generateCert generates a certificate signed by caCert. +func generateCert(caCert *x509.Certificate, caKey *ecdsa.PrivateKey, isServer bool) ([]byte, *ecdsa.PrivateKey, error) { + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate private key: %w", err) + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().UnixNano()), + Subject: pkix.Name{ + Organization: []string{"qubesome"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(validFor), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + SignatureAlgorithm: x509.ECDSAWithSHA256, + } + + if isServer { + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + template.DNSNames = []string{HostServerName} + } else { + template.DNSNames = []string{ProfileServerName} + } + + certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, &priv.PublicKey, caKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to create certificate: %w", err) + } + + return certBytes, priv, nil +} + +// pemEncode encodes the certificate and private key to PEM format. +func pemEncode(certBytes []byte, priv *ecdsa.PrivateKey) ([]byte, []byte, error) { + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + + privBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal private key: %w", err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}) + + return certPEM, keyPEM, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/util/xauth/xauth.go new/qubesome-0.0.8/internal/util/xauth/xauth.go --- old/qubesome-0.0.7/internal/util/xauth/xauth.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/util/xauth/xauth.go 2024-12-08 10:40:21.000000000 +0100 @@ -16,13 +16,13 @@ var cookieFunc = newCookie func AuthPair(display uint8, parent io.Reader, server, client io.Writer) error { - data := make([]byte, 50) + data := make([]byte, 40) n, err := parent.Read(data) if err != nil { return fmt.Errorf("failed to read from parent auth file: %w", err) } - if n < 50 { - return fmt.Errorf("auth file must be at least 50 chars long: was %d instead", n) + if n < 40 { + return fmt.Errorf("auth file must be at least 40 chars long: was %d instead", n) } _, _ = server.Write(data[0:2]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/internal/util/xauth/xauth_test.go new/qubesome-0.0.8/internal/util/xauth/xauth_test.go --- old/qubesome-0.0.7/internal/util/xauth/xauth_test.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/internal/util/xauth/xauth_test.go 2024-12-08 10:40:21.000000000 +0100 @@ -105,3 +105,16 @@ }) } } + +func FuzzToNumberic(f *testing.F) { + f.Fuzz(func(t *testing.T, input []byte) { + ToNumeric(input) + }) +} + +func FuzzAuthPair(f *testing.F) { + f.Fuzz(func(t *testing.T, display uint8, parent []byte) { + _ = AuthPair(display, bytes.NewReader(parent), + &bytes.Buffer{}, &bytes.Buffer{}) + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qubesome-0.0.7/pkg/inception/server.go new/qubesome-0.0.8/pkg/inception/server.go --- old/qubesome-0.0.7/pkg/inception/server.go 2024-11-25 11:55:53.000000000 +0100 +++ new/qubesome-0.0.8/pkg/inception/server.go 2024-12-08 10:40:21.000000000 +0100 @@ -2,6 +2,8 @@ import ( "context" + "crypto/tls" + "crypto/x509" "fmt" "log/slog" "net" @@ -10,8 +12,10 @@ "github.com/qubesome/cli/internal/command" "github.com/qubesome/cli/internal/qubesome" "github.com/qubesome/cli/internal/types" + "github.com/qubesome/cli/internal/util/mtls" pb "github.com/qubesome/cli/pkg/inception/proto" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) // NewServer returns a new inception server. @@ -32,13 +36,26 @@ server *grpcServer } -func (s *Server) Listen(socket string) error { +func (s *Server) Listen(serverCert tls.Certificate, ca []byte, socket string) error { lis, err := net.Listen("unix", socket) if err != nil { return fmt.Errorf("failed to listen: %w", err) } - gs := grpc.NewServer() + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(ca) { + return fmt.Errorf("failed to append CA from PEM") + } + + creds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{serverCert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: certPool, + MinVersion: tls.VersionTLS13, + ServerName: mtls.ProfileServerName, + }) + + gs := grpc.NewServer(grpc.Creds(creds)) pb.RegisterQubesomeHostServer(gs, s.server) slog.Debug("[server] listening", "addr", lis.Addr()) ++++++ vendor.tar.gz ++++++ ++++ 5233 lines of diff (skipped)
