Compare commits

..

No commits in common. "master" and "v0.0.1" have entirely different histories.

25 changed files with 113 additions and 215 deletions

View File

@ -73,20 +73,20 @@ jobs:
path: result/* path: result/*
retention-days: 1 retention-days: 1
planterette: fpkg:
name: Planterette name: Fpkg
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Run NixOS test - name: Run NixOS test
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.planterette run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.fpkg
- name: Upload test output - name: Upload test output
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: "planterette-vm-output" name: "fpkg-vm-output"
path: result/* path: result/*
retention-days: 1 retention-days: 1
@ -97,7 +97,7 @@ jobs:
- race - race
- sandbox - sandbox
- sandbox-race - sandbox-race
- planterette - fpkg
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout

View File

@ -1 +0,0 @@
This port is solely for releasing to the github mirror and serves no purpose during development.

View File

@ -1,46 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: Create release
runs-on: ubuntu-latest
permissions:
packages: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for release
run: nix build --print-out-paths --print-build-logs .#dist
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |-
result/hakurei-**

View File

@ -1,48 +0,0 @@
name: Test
on:
- push
jobs:
dist:
name: Create distribution
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for test
id: build-test
run: >-
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
nix build --print-out-paths --print-build-logs .#dist &&
mv package.nix.old package.nix &&
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
- name: Upload test build
uses: actions/upload-artifact@v4
with:
name: "hakurei-${{ steps.build-test.outputs.rev }}"
path: result/*
retention-days: 1

View File

@ -1,29 +1,38 @@
<p align="center"> Hakurei
<a href="https://git.gensokyo.uk/security/hakurei"> =======
<picture>
<img src="https://basement.gensokyo.uk/images/yukari1.png" width="200px" alt="Yukari">
</picture>
</a>
</p>
<p align="center"> [![Go Reference](https://pkg.go.dev/badge/git.gensokyo.uk/security/hakurei.svg)](https://pkg.go.dev/git.gensokyo.uk/security/hakurei)
<a href="https://pkg.go.dev/git.gensokyo.uk/security/hakurei"><img src="https://pkg.go.dev/badge/git.gensokyo.uk/security/hakurei.svg" alt="Go Reference" /></a> [![Go Report Card](https://goreportcard.com/badge/git.gensokyo.uk/security/hakurei)](https://goreportcard.com/report/git.gensokyo.uk/security/hakurei)
<a href="https://goreportcard.com/report/git.gensokyo.uk/security/hakurei"><img src="https://goreportcard.com/badge/git.gensokyo.uk/security/hakurei" alt="Go Report Card" /></a>
</p>
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel. Lets you run graphical applications as dedicated subordinate users in a container environment with a nice NixOS
It also implements [planterette (WIP)](cmd/planterette), a self-contained Android-like package manager with modern security features. module to configure target users and provide launch scripts and desktop files.
## NixOS Module usage Why would you want this?
The NixOS module currently requires home-manager to configure subordinate users. Full module documentation can be found [here](options.md). - It protects the desktop environment from applications.
- It protects applications from each other.
- It provides UID isolation on top of the standard application sandbox.
If you have a flakes-enabled nix environment, you can try out the tool by running:
```shell
nix run git+https://git.gensokyo.uk/security/hakurei -- help
```
## Module usage
The NixOS module currently requires home-manager to configure subordinate users.
Full module documentation can be found [here](options.md).
To use the module, import it into your configuration with To use the module, import it into your configuration with
```nix ```nix
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
hakurei = { hakurei = {
url = "git+https://git.gensokyo.uk/security/hakurei"; url = "git+https://git.gensokyo.uk/security/hakurei";

View File

@ -13,23 +13,36 @@ import (
"git.gensokyo.uk/security/hakurei/command" "git.gensokyo.uk/security/hakurei/command"
"git.gensokyo.uk/security/hakurei/hst" "git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal" "git.gensokyo.uk/security/hakurei/internal"
"git.gensokyo.uk/security/hakurei/internal/app/instance"
"git.gensokyo.uk/security/hakurei/internal/hlog" "git.gensokyo.uk/security/hakurei/internal/hlog"
"git.gensokyo.uk/security/hakurei/internal/sys"
"git.gensokyo.uk/security/hakurei/sandbox"
) )
const shellPath = "/run/current-system/sw/bin/bash" const shellPath = "/run/current-system/sw/bin/bash"
var ( var (
errSuccess = errors.New("success") errSuccess = errors.New("success")
std sys.State = new(sys.Std)
) )
func init() { func init() {
hlog.Prepare("planterette") hlog.Prepare("fpkg")
if err := os.Setenv("SHELL", shellPath); err != nil { if err := os.Setenv("SHELL", shellPath); err != nil {
log.Fatalf("cannot set $SHELL: %v", err) log.Fatalf("cannot set $SHELL: %v", err)
} }
} }
func main() { func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE
sandbox.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallFmsg)
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
// not fatal: this program runs as the privileged user
}
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
log.Fatal("this program must not run as root") log.Fatal("this program must not run as root")
} }
@ -42,10 +55,15 @@ func main() {
flagVerbose bool flagVerbose bool
flagDropShell bool flagDropShell bool
) )
c := command.New(os.Stderr, log.Printf, "planterette", func([]string) error { internal.InstallFmsg(flagVerbose); return nil }). c := command.New(os.Stderr, log.Printf, "fpkg", func([]string) error {
internal.InstallFmsg(flagVerbose)
return nil
}).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action") Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess })
{ {
var ( var (
flagDropShellActivate bool flagDropShellActivate bool
@ -66,7 +84,7 @@ func main() {
} }
/* /*
Look up paths to programs started by planterette. Look up paths to programs started by fpkg.
This is done here to ease error handling as cleanup is not yet required. This is done here to ease error handling as cleanup is not yet required.
*/ */
@ -82,7 +100,7 @@ func main() {
*/ */
var workDir string var workDir string
if p, err := os.MkdirTemp("", "planterette.*"); err != nil { if p, err := os.MkdirTemp("", "fpkg.*"); err != nil {
log.Printf("cannot create temporary directory: %v", err) log.Printf("cannot create temporary directory: %v", err)
return err return err
} else { } else {

29
cmd/fpkg/proc.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"context"
"os"
"git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal/app"
"git.gensokyo.uk/security/hakurei/internal/app/instance"
"git.gensokyo.uk/security/hakurei/internal/hlog"
)
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
rs := new(app.RunState)
a := instance.MustNew(instance.ISetuid, ctx, std)
var code int
if sa, err := a.Seal(config); err != nil {
hlog.PrintBaseError(err, "cannot seal app:")
code = 1
} else {
code = instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs))
}
if code != 0 {
beforeFail()
os.Exit(code)
}
}

View File

@ -9,7 +9,7 @@ let
buildPackage = self.buildPackage.${system}; buildPackage = self.buildPackage.${system};
in in
nixosTest { nixosTest {
name = "planterette"; name = "fpkg";
nodes.machine = { nodes.machine = {
environment.etc = { environment.etc = {
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; }; "foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };

View File

@ -79,15 +79,15 @@ print(machine.succeed("sudo -u alice -i hakurei version"))
machine.wait_for_file("/run/user/1000/wayland-1") machine.wait_for_file("/run/user/1000/wayland-1")
machine.wait_for_file("/tmp/sway-ipc.sock") machine.wait_for_file("/tmp/sway-ipc.sock")
# Prepare planterette directory: # Prepare fpkg directory:
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000") machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
# Install planterette app: # Install fpkg app:
swaymsg("exec planterette -v install /etc/foot.pkg && touch /tmp/planterette-install-ok") swaymsg("exec fpkg -v install /etc/foot.pkg && touch /tmp/fpkg-install-done")
machine.wait_for_file("/tmp/planterette-install-ok") machine.wait_for_file("/tmp/fpkg-install-done")
# Start app (foot) with Wayland enablement: # Start app (foot) with Wayland enablement:
swaymsg("exec planterette -v start org.codeberg.dnkl.foot") swaymsg("exec fpkg -v start org.codeberg.dnkl.foot")
wait_for_window("hakurei@machine-foot") wait_for_window("hakurei@machine-foot")
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n") machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
machine.wait_for_file("/tmp/hakurei.1000/tmpdir/2/success-client") machine.wait_for_file("/tmp/hakurei.1000/tmpdir/2/success-client")

View File

@ -41,7 +41,7 @@ func main() {
log.Fatalf("cannot read parent executable path: %v", err) log.Fatalf("cannot read parent executable path: %v", err)
} else if strings.HasSuffix(p, " (deleted)") { } else if strings.HasSuffix(p, " (deleted)") {
log.Fatal("hakurei executable has been deleted") log.Fatal("hakurei executable has been deleted")
} else if p != mustCheckPath(hmain) { } else if p != mustCheckPath(hmain) && p != mustCheckPath(fpkg) {
log.Fatal("this program must be started by hakurei") log.Fatal("this program must be started by hakurei")
} else { } else {
toolPath = p toolPath = p

View File

@ -16,8 +16,15 @@ buildGoModule {
go mod init hsu >& /dev/null go mod init hsu >& /dev/null
''; '';
ldflags = lib.attrsets.foldlAttrs ( ldflags =
ldflags: name: value: lib.attrsets.foldlAttrs
ldflags ++ [ "-X main.${name}=${value}" ] (
) [ "-s -w" ] { hmain = "${hakurei}/libexec/hakurei"; }; ldflags: name: value:
ldflags ++ [ "-X main.${name}=${value}" ]
)
[ "-s -w" ]
{
hmain = "${hakurei}/libexec/hakurei";
fpkg = "${hakurei}/libexec/fpkg";
};
} }

View File

@ -9,6 +9,7 @@ const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
var ( var (
hmain = compPoison hmain = compPoison
fpkg = compPoison
) )
func mustCheckPath(p string) string { func mustCheckPath(p string) string {

View File

@ -1,60 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
"git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal"
"git.gensokyo.uk/security/hakurei/internal/hlog"
)
var hakureiPath = internal.MustHakureiPath()
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
var (
cmd *exec.Cmd
st io.WriteCloser
)
if r, w, err := os.Pipe(); err != nil {
beforeFail()
log.Fatalf("cannot pipe: %v", err)
} else {
if hlog.Load() {
cmd = exec.CommandContext(ctx, hakureiPath, "-v", "app", "3")
} else {
cmd = exec.CommandContext(ctx, hakureiPath, "app", "3")
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = []*os.File{r}
st = w
}
go func() {
if err := json.NewEncoder(st).Encode(config); err != nil {
beforeFail()
log.Fatalf("cannot send configuration: %v", err)
}
}()
if err := cmd.Start(); err != nil {
beforeFail()
log.Fatalf("cannot start hakurei: %v", err)
}
if err := cmd.Wait(); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
beforeFail()
internal.Exit(exitError.ExitCode())
} else {
beforeFail()
log.Fatalf("cannot wait: %v", err)
}
}
}

2
dist/install.sh vendored
View File

@ -2,7 +2,7 @@
cd "$(dirname -- "$0")" || exit 1 cd "$(dirname -- "$0")" || exit 1
install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei" install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei"
install -vDm0755 "bin/planterette" "${HAKUREI_INSTALL_PREFIX}/usr/bin/planterette" install -vDm0755 "bin/fpkg" "${HAKUREI_INSTALL_PREFIX}/usr/bin/fpkg"
install -vDm6511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu" install -vDm6511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu"
if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then

4
dist/release.sh vendored
View File

@ -11,9 +11,9 @@ cp -rv "dist/comp" "${out}"
go generate ./... go generate ./...
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static' go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
-X git.gensokyo.uk/security/hakurei/internal.version=${VERSION} -X git.gensokyo.uk/security/hakurei/internal.version=${VERSION}
-X git.gensokyo.uk/security/hakurei/internal.hakurei=/usr/bin/hakurei
-X git.gensokyo.uk/security/hakurei/internal.hsu=/usr/bin/hsu -X git.gensokyo.uk/security/hakurei/internal.hsu=/usr/bin/hsu
-X main.hmain=/usr/bin/hakurei" ./... -X main.hmain=/usr/bin/hakurei
-X main.fpkg=/usr/bin/fpkg" ./...
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}" rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
rm -rf "./${out}" rm -rf "./${out}"

View File

@ -32,7 +32,7 @@
buildPackage = forAllSystems ( buildPackage = forAllSystems (
system: system:
nixpkgsFor.${system}.callPackage ( nixpkgsFor.${system}.callPackage (
import ./cmd/planterette/build.nix { import ./cmd/fpkg/build.nix {
inherit inherit
nixpkgsFor nixpkgsFor
system system
@ -69,7 +69,7 @@
withRace = true; withRace = true;
}; };
planterette = callPackage ./cmd/planterette/test { inherit system self; }; fpkg = callPackage ./cmd/fpkg/test { inherit system self; };
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } '' formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
cd ${./.} cd ${./.}
@ -125,7 +125,7 @@
glibc glibc
xdg-dbus-proxy xdg-dbus-proxy
# planterette # fpkg
zstd zstd
gnutar gnutar
coreutils coreutils

View File

@ -8,26 +8,16 @@ import (
) )
var ( var (
hakurei = compPoison hsu = compPoison
hsu = compPoison
) )
func MustHakureiPath() string {
if name, ok := checkPath(hakurei); ok {
return name
}
hlog.BeforeExit()
log.Fatal("invalid hakurei path, this program is compiled incorrectly")
return compPoison // unreachable
}
func MustHsuPath() string { func MustHsuPath() string {
if name, ok := checkPath(hsu); ok { if name, ok := checkPath(hsu); ok {
return name return name
} }
hlog.BeforeExit() hlog.BeforeExit()
log.Fatal("invalid hsu path, this program is compiled incorrectly") log.Fatal("invalid hsu path, this program is compiled incorrectly")
return compPoison // unreachable return compPoison
} }
func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) } func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) }

View File

@ -35,7 +35,7 @@ package
*Default:* *Default:*
` <derivation hakurei-static-x86_64-unknown-linux-musl-0.0.2> ` ` <derivation hakurei-static-x86_64-unknown-linux-musl-0.4.1> `
@ -916,7 +916,7 @@ package
*Default:* *Default:*
` <derivation hakurei-hsu-0.0.2> ` ` <derivation hakurei-hsu-0.4.1> `

View File

@ -13,7 +13,7 @@
wayland-scanner, wayland-scanner,
xorg, xorg,
# for planterette # for fpkg
zstd, zstd,
gnutar, gnutar,
coreutils, coreutils,
@ -31,7 +31,7 @@
buildGoModule rec { buildGoModule rec {
pname = "hakurei"; pname = "hakurei";
version = "0.0.2"; version = "0.0.1";
srcFiltered = builtins.path { srcFiltered = builtins.path {
name = "${pname}-src"; name = "${pname}-src";
@ -76,7 +76,6 @@ buildGoModule rec {
) )
{ {
version = "v${version}"; version = "v${version}";
hakurei = "${placeholder "out"}/libexec/hakurei";
hsu = "/run/wrappers/bin/hsu"; hsu = "/run/wrappers/bin/hsu";
}; };
@ -117,7 +116,7 @@ buildGoModule rec {
makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \ makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages} --inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
makeBinaryWrapper "$out/libexec/planterette" "$out/bin/planterette" \ makeBinaryWrapper "$out/libexec/fpkg" "$out/bin/fpkg" \
--inherit-argv0 --prefix PATH : ${ --inherit-argv0 --prefix PATH : ${
lib.makeBinPath ( lib.makeBinPath (
appPackages appPackages