snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
149
oryx/landlockx/landlockx.go
Normal file
149
oryx/landlockx/landlockx.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package landlockx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
ll "github.com/landlock-lsm/go-landlock/landlock"
|
||||
llsyscall "github.com/landlock-lsm/go-landlock/landlock/syscall"
|
||||
|
||||
"github.com/ory/x/logrusx"
|
||||
)
|
||||
|
||||
// IsSupported returns true if the running kernel supports Landlock LSM.
|
||||
// On non-Linux platforms or kernels older than 5.13, this returns false.
|
||||
func IsSupported() bool {
|
||||
v, err := llsyscall.LandlockGetABIVersion()
|
||||
return err == nil && v > 0
|
||||
}
|
||||
|
||||
// runningExe returns the kernel-canonical path of the running binary.
|
||||
// jsonnetsecure re-execs the running binary as a sandboxed worker, so
|
||||
// the rule we attach must use the inode the kernel resolves at execve
|
||||
// time. os.Executable on Linux returns the readlink of /proc/self/exe;
|
||||
// EvalSymlinks resolves any further symlinks so the rule attaches to
|
||||
// the actual file inode.
|
||||
func runningExe() string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if real, err := filepath.EvalSymlinks(exe); err == nil {
|
||||
return real
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// Apply activates the Landlock LSM filesystem sandbox for the calling process.
|
||||
//
|
||||
// roPaths is a list of paths that the process may read. Each path is stat-ed
|
||||
// to determine whether to apply directory or file access rights; non-existent
|
||||
// paths are silently skipped.
|
||||
//
|
||||
// rwDirs is a list of directories that the process may read and write
|
||||
// recursively. Missing paths are silently ignored.
|
||||
//
|
||||
// Apply is a no-op on non-Linux platforms and on Linux kernels older than
|
||||
// 5.13 (BestEffort degrades gracefully).
|
||||
func Apply(l *logrusx.Logger, roPaths, rwDirs []string) error {
|
||||
rules := defaultRules()
|
||||
|
||||
for _, p := range roPaths {
|
||||
info, err := os.Stat(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if info.IsDir() {
|
||||
rules = append(rules, ll.RODirs(p))
|
||||
} else {
|
||||
rules = append(rules, ll.ROFiles(p))
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range rwDirs {
|
||||
rules = append(rules, ll.RWDirs(d).IgnoreIfMissing())
|
||||
}
|
||||
|
||||
if !IsSupported() {
|
||||
l.Warn("Landlock LSM is not available on this kernel; filesystem sandbox not applied.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ll.V3.BestEffort().RestrictPaths(rules...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Info("Landlock filesystem sandbox is active.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyEmpty activates a Landlock filesystem sandbox with no allowed
|
||||
// paths, denying every filesystem access the V3 ABI handles. Already-open
|
||||
// file descriptors (stdin, stdout, stderr inherited from the parent)
|
||||
// keep working, but no path-based open/read/write/exec is permitted.
|
||||
//
|
||||
// Use it inside helper subprocesses that only need stdio — for example,
|
||||
// the hidden `jsonnet` worker that jsonnetsecure re-execs to evaluate
|
||||
// mappers in isolation.
|
||||
//
|
||||
// Landlock layers stack: this call adds a strictly-more-restrictive
|
||||
// layer on top of whatever the parent already enforced.
|
||||
//
|
||||
// ApplyEmpty is a no-op on non-Linux platforms and on Linux kernels
|
||||
// older than 5.13.
|
||||
func ApplyEmpty(l *logrusx.Logger) error {
|
||||
if !IsSupported() {
|
||||
if l != nil {
|
||||
l.Warn("Landlock LSM is not available on this kernel; empty sandbox not applied.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := ll.V3.BestEffort().RestrictPaths(); err != nil {
|
||||
return err
|
||||
}
|
||||
if l != nil {
|
||||
l.Info("Landlock empty filesystem sandbox is active.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultRules returns the rules every kratos serve process needs regardless
|
||||
// of operator config:
|
||||
// - /dev/null is a kernel-managed sink used by subprocess plumbing
|
||||
// (exec.Command stdin redirection, log discarding, etc.); it cannot
|
||||
// leak data.
|
||||
// - /etc/resolv.conf carries the DNS nameserver list and /etc/hosts
|
||||
// the static host table; Go's pure-Go resolver reads both for every
|
||||
// hostname lookup. /etc/nsswitch.conf is intentionally not granted:
|
||||
// the binary is built with CGO_ENABLED=0, so when nsswitch.conf is
|
||||
// unreadable the resolver falls back to "files then DNS", which is
|
||||
// what we want anyway.
|
||||
// - the running binary is re-exec'd by jsonnetsecure for sandboxed mapper
|
||||
// evaluation, so it needs read+execute. The binary must be statically
|
||||
// linked (CGO_ENABLED=0) — execve of a dynamically-linked binary also
|
||||
// requires read+execute on the system dynamic linker (PT_INTERP) and
|
||||
// its NEEDED libraries, which we deliberately do not grant.
|
||||
//
|
||||
// /etc/ssl (system trust roots for outbound HTTPS) is intentionally not
|
||||
// included. Cloud binaries blank-import
|
||||
// golang.org/x/crypto/x509roots/fallback (see cloudlib/x509_roots.go)
|
||||
// and set `godebug x509usefallbackroots=1` in their go.mod, so
|
||||
// crypto/x509 ships its own embedded Mozilla bundle and never reads
|
||||
// /etc/ssl at runtime. Operators who need to trust an additional CA
|
||||
// continue to set SSL_CERT_FILE / SSL_CERT_DIR and list the file under
|
||||
// security.landlock.allowed_paths.
|
||||
func defaultRules() []ll.Rule {
|
||||
rules := []ll.Rule{
|
||||
ll.RWFiles("/dev/null"),
|
||||
ll.ROFiles("/etc/resolv.conf").IgnoreIfMissing(),
|
||||
ll.ROFiles("/etc/hosts").IgnoreIfMissing(),
|
||||
}
|
||||
if exe := runningExe(); exe != "" {
|
||||
access := ll.AccessFSSet(llsyscall.AccessFSReadFile | llsyscall.AccessFSExecute)
|
||||
rules = append(rules, ll.PathAccess(access, exe).IgnoreIfMissing())
|
||||
}
|
||||
return rules
|
||||
}
|
||||
32
oryx/landlockx/paths.go
Normal file
32
oryx/landlockx/paths.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package landlockx
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ory/x/sqlxx"
|
||||
)
|
||||
|
||||
// SQLiteDirFromDSN returns the directory containing the SQLite database
|
||||
// referenced by the DSN. Granting the directory once is simpler and more
|
||||
// robust than enumerating every sibling: SQLite manages -journal in
|
||||
// rollback mode and -wal/-shm in WAL mode, plus transient -mj-XXXXX
|
||||
// rollback masters during multi-database checkpoints — the latter use
|
||||
// random suffixes that a per-file rule cannot cover.
|
||||
//
|
||||
// Returns an empty string for non-SQLite DSNs and for the in-memory
|
||||
// SQLite DSN.
|
||||
func SQLiteDirFromDSN(dsn string) string {
|
||||
scheme, path, err := sqlxx.ExtractSchemeFromDSN(dsn)
|
||||
if err != nil || (scheme != "sqlite" && scheme != "sqlite3") {
|
||||
return ""
|
||||
}
|
||||
path, _, _ = strings.Cut(path, "?")
|
||||
if path == "" || strings.Contains(path, ":memory:") {
|
||||
return ""
|
||||
}
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
Reference in New Issue
Block a user