doos 1.1.0: limits, hardening, pasta, signatures, Debian image
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+115
-10
@@ -1,6 +1,8 @@
|
||||
#include "container.hpp"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
@@ -82,6 +84,23 @@ void Container::seed(const std::string& marker, const std::string& label,
|
||||
fsutil::writeFile(m, "ok\n");
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Names that may hold credentials are never copied, even inside allowed directories.
|
||||
bool etcDenied(const fs::path& p) {
|
||||
std::string n = p.filename().string();
|
||||
if (n.size() > 4 && n.compare(n.size() - 4, 4, ".key") == 0) return true;
|
||||
for (auto d : spec::kEtcDenyNames) {
|
||||
std::string pat(d);
|
||||
if (!pat.empty() && pat.back() == '*') {
|
||||
if (n.rfind(pat.substr(0, pat.size() - 1), 0) == 0) return true;
|
||||
} else if (n == pat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Container::prepareLayout() const {
|
||||
fsutil::mkdirs(rootfs());
|
||||
if (imageMode()) {
|
||||
@@ -154,8 +173,17 @@ void Container::prepareLayout() const {
|
||||
for (auto d : spec::kCopiedDirs) {
|
||||
std::error_code ec;
|
||||
if (!fs::is_directory(std::string(d), ec)) continue;
|
||||
seed("copy-" + std::string(slug(d)), "copy of " + std::string(d),
|
||||
[&] { return fsutil::copyTree(std::string(d), privateDir(std::string(d))); });
|
||||
std::string key = slug(d);
|
||||
std::replace(key.begin(), key.end(), '/', '_');
|
||||
if (relaxed_) {
|
||||
seed("copy-" + key, "copy of " + std::string(d) + " (relaxed)",
|
||||
[&] { return fsutil::copyTree(std::string(d), privateDir(std::string(d))); });
|
||||
} else {
|
||||
seed("copy-" + key, "allowlisted " + std::string(d), [&] {
|
||||
std::vector<std::string> names(spec::kEtcAllow.begin(), spec::kEtcAllow.end());
|
||||
return fsutil::copyEntries(std::string(d), privateDir(std::string(d)), names, etcDenied);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,15 +205,40 @@ std::uint64_t Container::usedBytes(std::size_t& skipped) const {
|
||||
std::error_code ec;
|
||||
if (fs::exists(dir_ / "layers", ec)) findUppers(dir_ / "layers", 0, dirs);
|
||||
if (fs::exists(dir_ / "private", ec)) dirs.push_back(dir_ / "private");
|
||||
std::uint64_t total = 0;
|
||||
for (const auto& d : dirs) total += fsutil::diskUsage(d, skipped);
|
||||
return total;
|
||||
auto scan = [&](std::size_t& sk) {
|
||||
std::uint64_t total = 0;
|
||||
for (const auto& d : dirs) total += fsutil::diskUsage(d, sk);
|
||||
return total;
|
||||
};
|
||||
if (ids_ == IdMode::Single) return scan(skipped);
|
||||
// Files of other container ids (host uids 100000+) and their mode 700 directories are invisible to the host
|
||||
// user; measuring as root of the id map sees them.
|
||||
int p[2];
|
||||
if (::pipe2(p, O_CLOEXEC) != 0) throw SysError("pipe");
|
||||
Fd rd(p[0]), wr(p[1]);
|
||||
int rc = UserNsJob::run(ids_, [&] {
|
||||
std::size_t sk = 0;
|
||||
std::uint64_t t = scan(sk);
|
||||
::dprintf(wr.get(), "%llu %zu\n", static_cast<unsigned long long>(t), sk);
|
||||
return 0;
|
||||
});
|
||||
wr.reset();
|
||||
if (rc != 0) throw std::runtime_error("size scan failed");
|
||||
char buf[64] = {0};
|
||||
if (::read(rd.get(), buf, sizeof buf - 1) <= 0) throw std::runtime_error("size scan returned nothing");
|
||||
unsigned long long t = 0;
|
||||
std::size_t sk = 0;
|
||||
std::sscanf(buf, "%llu %zu", &t, &sk);
|
||||
skipped += sk;
|
||||
return t;
|
||||
}
|
||||
|
||||
void Container::writeMeta() const {
|
||||
fsutil::writeFile(metaFile(), "net=" + Network::name(net_) + "\ncreated=" + std::to_string(created_) +
|
||||
"\nversion=" DOOS_VERSION "\nimage=" + image_ + "\nids=" + IdMap::name(ids_) +
|
||||
"\nsize=" + std::to_string(size_) + "\n");
|
||||
"\nsize=" + std::to_string(size_) + "\nmemory=" + std::to_string(memory_) +
|
||||
"\npids=" + std::to_string(pids_) + "\ncpus=" + std::to_string(milliCpus_) +
|
||||
"\nhardening=" + (relaxed_ ? "relaxed" : "default") + "\n");
|
||||
}
|
||||
|
||||
void Container::setSizeCap(std::uint64_t bytes) {
|
||||
@@ -197,11 +250,41 @@ void Container::setSizeCap(std::uint64_t bytes) {
|
||||
writeMeta();
|
||||
}
|
||||
|
||||
void Container::create(const NetMode& net, const std::string& image, IdMode ids, std::uint64_t size) {
|
||||
void Container::setLimits(std::optional<std::uint64_t> memory, std::optional<std::uint32_t> pids,
|
||||
std::optional<std::uint32_t> milliCpus) {
|
||||
if (memory) {
|
||||
if (*memory < spec::kMinMemoryBytes || *memory > spec::kMaxMemoryBytes) {
|
||||
throw std::runtime_error("memory must be between " + formatSize(spec::kMinMemoryBytes) + " and " +
|
||||
formatSize(spec::kMaxMemoryBytes));
|
||||
}
|
||||
memory_ = *memory;
|
||||
}
|
||||
if (pids) {
|
||||
if (*pids < spec::kMinPids || *pids > spec::kMaxPids) {
|
||||
throw std::runtime_error("pids must be between " + std::to_string(spec::kMinPids) + " and " +
|
||||
std::to_string(spec::kMaxPids));
|
||||
}
|
||||
pids_ = *pids;
|
||||
}
|
||||
if (milliCpus) {
|
||||
if (*milliCpus != 0 && (*milliCpus < spec::kMinMilliCpus || *milliCpus > spec::kMaxMilliCpus)) {
|
||||
throw std::runtime_error("cpus must be between 0.1 and 1024 (0 = all cores)");
|
||||
}
|
||||
milliCpus_ = *milliCpus;
|
||||
}
|
||||
writeMeta();
|
||||
}
|
||||
|
||||
void Container::create(const NetMode& net, const std::string& image, IdMode ids, const Options& o) {
|
||||
net_ = net;
|
||||
image_ = image;
|
||||
ids_ = ids;
|
||||
size_ = size;
|
||||
size_ = o.size.value_or(spec::kDefaultSizeBytes);
|
||||
relaxed_ = o.relaxed.value_or(false);
|
||||
memory_ = spec::kDefaultMemoryBytes;
|
||||
pids_ = spec::kDefaultPids;
|
||||
milliCpus_ = 0;
|
||||
if (o.memory || o.pids || o.milliCpus) setLimits(o.memory, o.pids, o.milliCpus);
|
||||
created_ = std::time(nullptr);
|
||||
fsutil::mkdirs(dir_);
|
||||
prepareLayout();
|
||||
@@ -220,6 +303,10 @@ void Container::readMeta() {
|
||||
image_ = kv.count("image") ? kv["image"] : "";
|
||||
ids_ = IdMap::parse(kv.count("ids") ? kv["ids"] : "single");
|
||||
size_ = kv.count("size") ? std::stoull(kv["size"]) : spec::kDefaultSizeBytes;
|
||||
memory_ = kv.count("memory") ? std::stoull(kv["memory"]) : spec::kDefaultMemoryBytes;
|
||||
pids_ = kv.count("pids") ? static_cast<std::uint32_t>(std::stoul(kv["pids"])) : spec::kDefaultPids;
|
||||
milliCpus_ = kv.count("cpus") ? static_cast<std::uint32_t>(std::stoul(kv["cpus"])) : 0;
|
||||
relaxed_ = kv.count("hardening") && kv["hardening"] == "relaxed";
|
||||
}
|
||||
|
||||
std::optional<pid_t> Container::runningPid() const {
|
||||
@@ -307,7 +394,9 @@ std::pair<Container, bool> Store::getOrCreate(const std::string& name, const Opt
|
||||
if (opts.net) fixed("net", Network::name(c.net()), Network::name(*opts.net));
|
||||
if (image) fixed("image", c.image().empty() ? "none" : c.image(), *image);
|
||||
if (opts.ids) fixed("ids", IdMap::name(c.ids()), IdMap::name(*opts.ids));
|
||||
if (opts.relaxed) fixed("hardening", c.relaxed() ? "relaxed" : "default", *opts.relaxed ? "relaxed" : "default");
|
||||
if (opts.size && *opts.size != c.sizeCap()) c.setSizeCap(*opts.size);
|
||||
if (opts.memory || opts.pids || opts.milliCpus) c.setLimits(opts.memory, opts.pids, opts.milliCpus);
|
||||
};
|
||||
|
||||
if (fs::exists(dir / "meta", ec)) {
|
||||
@@ -329,7 +418,7 @@ std::pair<Container, bool> Store::getOrCreate(const std::string& name, const Opt
|
||||
ImageStore store = images();
|
||||
if (!store.has(*image)) {
|
||||
if (!Catalog::knows(*image)) {
|
||||
throw std::runtime_error("unknown image '" + *image + "'. Add it: doos image add SOURCE [NAME] (catalog: alpine, ubuntu)");
|
||||
throw std::runtime_error("unknown image '" + *image + "'. Add it: doos image add SOURCE [NAME] (catalog: alpine, ubuntu, debian)");
|
||||
}
|
||||
std::fprintf(stderr, "doos: image '%s' not present, fetching it\n", image->c_str());
|
||||
store.add(Catalog::resolve(*image), ids);
|
||||
@@ -353,7 +442,23 @@ std::pair<Container, bool> Store::getOrCreate(const std::string& name, const Opt
|
||||
verifyExisting();
|
||||
return {c, false};
|
||||
}
|
||||
c.create(opts.net.value_or(NetMode::Slirp), image.value_or(""), ids, size);
|
||||
Options resolved = opts;
|
||||
resolved.size = size;
|
||||
try {
|
||||
c.create(opts.net.value_or(NetMode::Slirp), image.value_or(""), ids, resolved);
|
||||
} catch (...) {
|
||||
// a container that failed halfway must not stay behind as an unreadable directory
|
||||
try {
|
||||
if (ids == IdMode::Subuid) UserNsJob::run(ids, [&] { fsutil::removeTree(dir); return 0; });
|
||||
else fsutil::removeTree(dir);
|
||||
} catch (...) {
|
||||
}
|
||||
throw;
|
||||
}
|
||||
if (c.net() == NetMode::Host) {
|
||||
std::fprintf(stderr, "doos: note: --net host shares the host's abstract unix sockets and loopback; "
|
||||
"--net pasta gives the same reachability in a private namespace\n");
|
||||
}
|
||||
return {c, true};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user