Repositories / jai.git
jai.git
Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git
@@ -2,7 +2,8 @@ bin_PROGRAMS = jai AM_CXXFLAGS = $(MOUNT_CFLAGS) $(LIBACL_CFLAGS) -jai_SOURCES = cred.cc fs.cc jai.cc cred.h argtype.h defer.h err.h fs.h jai.h +jai_SOURCES = cred.cc default_conf.cc fs.cc jai.cc cred.h argtype.h \ +defer.h err.h fs.h jai.h jai_LDADD = $(MOUNT_LIBS) $(LIBACL_LIBS) man1_MANS = jai.1
@@ -0,0 +1,92 @@ + +#include "jai.h" + +const std::string default_conf = + R"(# Instead of copying this file to create a new configuration, you can +# it the file by referernce using a conf command. Don't uncomment in +# this example line default.conf or you will create a loop. Example: +# +# conf default.conf + +# The default mode is strict for all named sandboxes and casual for +# the default sandbox. A strict sandbox runs under the dedicated jai +# UID and starts with an empty home directory. A casual sandbox runs +# with your own UID and makes your home directory copy-on-write via an +# overlay mount. To change the default, you can uncomment one of the +# following: + +# casual +# strict + +# jai launches programs in a sandbox by running bash with the command +# name in "$0" and the arguments in "@". bash will have a PID 1, +# which can confuse some programs. Adding "; exit $?" after the +# command and arguments will cause bash to fork and stay around, +# giving "$0" PID 2. For non-default configurations, you can also use +# a command directive to insert extra arguments or set environment +# variables. + +command "$0" "$@"; exit $? + +# Masked files are deleted when an overlayfs is first created, but +# have no effect on existing overlays. To delete files from an +# existing overlay, delete them under /run/jai/$USER/default.home. +# Otherwise, to apply new mask directives you can run "jai -u" to +# unmount any existing overlays. + +mask .jai +mask .ssh +mask .gnupg +mask .local/share/keyrings +mask .netrc +mask .git-credentials +mask .aws +mask .azure +mask .config/gcloud +mask .config/gh +mask .config/Keybase +mask .config/kube +mask .docker +mask .password-store +mask .mozilla +mask .config/chromium +mask .config/google-chrome +mask .config/BraveSoftware +mask .bash_history +mask .zsh_history + +# The following environment variables will be removed from sandboxed +# environments. You can use * as a wildcard to match any variables +# matching the pattern. + +unsetenv AZURE_CLIENT_ID +unsetenv AZURE_TENANT_ID +unsetenv DATABASE_URL +unsetenv MONGO_URI +unsetenv MONGODB_URI +unsetenv REDIS_URL +unsetenv GOOGLE_APPLICATION_CREDENTIALS +unsetenv KUBECONFIG +unsetenv BB_AUTH_STRING +unsetenv SENTRY_DSN +unsetenv SLACK_WEBHOOK_URL +unsetenv *_ACCESS_KEY +unsetenv *_API_KEY +unsetenv *_APIKEY +unsetenv *_AUTH +unsetenv *_AUTH_TOKEN +unsetenv *_CONNECTION_STRING +unsetenv *_CREDENTIAL +unsetenv *_CREDENTIALS +unsetenv *_PASSWD +unsetenv *_PASSWORD +unsetenv *_PID +unsetenv *_PRIVATE_KEY +unsetenv *_PWD +unsetenv *_SECRET +unsetenv *_SECRET_KEY +unsetenv *_SOCK +unsetenv *_SOCKET +unsetenv *_SOCKET_PATH +unsetenv *_TOKEN +)";
@@ -236,21 +236,27 @@ make_whiteout(int dfd, const path &inp) err<std::logic_error>(R"(make_whiteout: "{}" is not a relative path)", inp.string()); - Fd dirholder; - if (p.has_parent_path()) { - dirholder = ensure_dir(dfd, p.parent_path(), 0700, kNoFollow, false); - dfd = *dirholder; - p = p.filename(); - } + try { + Fd dirholder; + if (p.has_parent_path()) { + dirholder = ensure_dir(dfd, p.parent_path(), 0700, kNoFollow, false); + dfd = *dirholder; + p = p.filename(); + } - auto olduid = geteuid(); - seteuid(0); - int err = 0; - if (mknodat(dfd, p.filename().c_str(), S_IFCHR, 0)) - err = errno; - seteuid(olduid); - if ((errno = err)) - syserr("mknod {}/.jai c 0 0", fdpath(dfd, p)); + auto olduid = geteuid(); + seteuid(0); + int err = 0; + if (mknodat(dfd, p.filename().c_str(), S_IFCHR, 0)) + err = errno; + seteuid(olduid); + if ((errno = err)) + syserr("mknod {}/.jai c 0 0", fdpath(dfd, p)); + } catch (const std::system_error &e) { + if (e.code() != std::errc::not_a_directory && + e.code() != std::errc::file_exists) + throw; + } } bool
@@ -95,8 +95,10 @@ struct Config { void mask_warn() { if (mask_warn_) { - warn("--mask did nothing because ~/.jai/{}.changes already existed", - sandbox_name_.string()); + warn(R"(--mask ignored because {5}/{0}/{1}.home already mounted. +{2:>{3}} Run "{4} -u" to unmount overlays.)", + user_, sandbox_name_.string(), "", prog.filename().string().size(), + prog.filename().string(), kRunRoot); mask_warn_ = false; } } @@ -293,10 +295,6 @@ Config::make_blacklist(int dfd, path name) { Fd blacklistfd = ensure_dir(dfd, name.c_str(), 0700, kFollow); check_user(*blacklistfd); - if (!is_dir_empty(*blacklistfd)) { - mask_warn(); - return blacklistfd; - } for (path p : mask_files_) { try { @@ -576,104 +574,13 @@ Config::unmountall() unlinkat(run_jai(), user_.c_str(), AT_REMOVEDIR); } -auto env_blacklist = std::to_array<const char *>({ - // Azure - "AZURE_CLIENT_ID", - "AZURE_TENANT_ID", - // Databases (connection URIs contain embedded credentials) - "DATABASE_URL", - "MONGO_URI", - "MONGODB_URI", - "REDIS_URL", - // GCP - "GOOGLE_APPLICATION_CREDENTIALS", - // Docker / K8s - "KUBECONFIG", - // Bitbucket - "BB_AUTH_STRING", - // Sentry - "SENTRY_DSN", - // Slack - "SLACK_WEBHOOK_URL", - // Suffixes - "*_ACCESS_KEY", - "*_API_KEY", - "*_APIKEY", - "*_AUTH", - "*_AUTH_TOKEN", - "*_CONNECTION_STRING", - "*_CREDENTIAL", - "*_CREDENTIALS", - "*_PASSWD", - "*_PASSWORD", - "*_PID", - "*_PRIVATE_KEY", - "*_PWD", - "*_SECRET", - "*_SECRET_KEY", - "*_SOCK", - "*_SOCKET", - "*_SOCKET_PATH", - "*_TOKEN", -}); - -const auto default_masklist = std::to_array<const char *>({ - ".jai", - ".ssh", - ".gnupg", - ".local/share/keyrings", - ".netrc", - ".git-credentials", - ".aws", - ".azure", - ".config/gcloud", - ".config/gh", - ".config/Keybase", - ".config/kube", - ".docker", - ".password-store", - ".mozilla", - ".config/chromium", - ".config/google-chrome", - ".config/BraveSoftware", - ".bash_history", - ".zsh_history", -}); - void Config::make_default_conf() { auto fd = xopenat(home_jai(), ".", O_RDWR | O_TMPFILE | O_CLOEXEC, 0600); - std::string text = - R"(# The executed process will have PID 1, which can confuse some -# programs. Adding "; exit $?" after the command and arguments will -# cause bash to fork and stay around, so command "$0" has PID 2. - -command "$0" "$@"; exit $? - -# Masked file wills be deleted when a new overlayfs is first created, -# but have no effect on existing overlays. To delete files from an -# existing overlay, delete them under /run/jai/$USER/default.home. If -# you want to start over with a fresh overlay, you can run "jai -u" to -# unmount any existing overlays, then remove the directory -# $HOME/.jai/$USER/default.changes. The next time you run jai, it -# will create a new overlay masking all of the files below. - -)"; - for (auto p : default_masklist) - text += std::format("mask {}\n", p); - - text += R"( -# The following environment variables will be removed from sandboxed -# environments. You can use * as a wildcard to match any variables -# matching the pattern. - -)"; - for (auto e : env_blacklist) - text += std::format("unsetenv {}\n", e); - errno = EAGAIN; - if (write(*fd, text.data(), text.size()) != text.size()) + if (write(*fd, default_conf.data(), default_conf.size()) != + default_conf.size()) syserr("write(O_TMPFILE for default.conf)"); if (linkat(*fd, "", home_jai(), "default.conf", AT_EMPTY_PATH) && errno != EEXIST)
@@ -26,3 +26,5 @@ xfork(std::uint64_t flags = 0) exit(1); \ } \ } while (0) + +extern const std::string default_conf;