You've already forked isolationRunner
c0f579ff6d
This allows a jail to have a VPN config associated and as a result we start a new net namespace, completely isolating the jails networking. We then start an openVPN client to route between the main network and the jails' network. The main limitation here is that we don't setup DNS, which basically means that the VPN will route DNS calls to the other side, but since we don't remount resolv.conf this depends on the vpn provider actually mapping the nameserver we use. For people that use a nameserver like 192.168.100.1, this most of the time works just fine. Improvement is possible.
260 lines
8.9 KiB
C++
260 lines
8.9 KiB
C++
#include <QtCore/QCoreApplication>
|
|
#include <QtDBus/QtDBus>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <iostream>
|
|
|
|
constexpr const char *SERVICE_NAME = "org.tom.IsolationRunner";
|
|
|
|
class Client: public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
void setVerbose(bool on) {
|
|
m_verbose = on;
|
|
}
|
|
void setApp(const QString &appName, const QString &appPath) {
|
|
m_appName = appName;
|
|
m_appPath = appPath;
|
|
}
|
|
|
|
void setListDetails(bool getDetails) {
|
|
m_listDetails = getDetails;
|
|
}
|
|
|
|
void setAppArguments(const QList<QString> &arguments) {
|
|
m_arguments = arguments;
|
|
}
|
|
|
|
bool autodelete() const {
|
|
return m_autodelete;
|
|
}
|
|
void setAutodelete(bool ad) {
|
|
m_autodelete = ad;
|
|
}
|
|
|
|
bool lockedDown() const {
|
|
return m_lockedDown;
|
|
}
|
|
void setLockedDown(bool newLockedDown) {
|
|
m_lockedDown = newLockedDown;
|
|
}
|
|
void setEnclosingJailId(const QString &jailId) {
|
|
m_enclosingJailId = jailId;
|
|
}
|
|
|
|
void setVpnOVPNPath(const QString &newVpnOVPNPath)
|
|
{
|
|
m_vpnOVPNPath = newVpnOVPNPath;
|
|
}
|
|
|
|
void setVpnAcPath(const QString &newVpnAcPath)
|
|
{
|
|
m_vpnAcPath = newVpnAcPath;
|
|
}
|
|
|
|
public slots:
|
|
void start() {
|
|
struct ShutDown {
|
|
~ShutDown() { QCoreApplication::exit(rc); }
|
|
int rc = 0;
|
|
};
|
|
ShutDown shutDownHandler;
|
|
// find our remote
|
|
auto iface = new QDBusInterface(SERVICE_NAME, "/apps", SERVICE_NAME,
|
|
QDBusConnection::sessionBus(), this);
|
|
if (!iface->isValid()) {
|
|
shutDownHandler.rc = 1;
|
|
std::cerr << "Could not connect" << std::endl;
|
|
if (m_verbose)
|
|
std::cerr << "[" << qPrintable(QDBusConnection::sessionBus().lastError().message()) << "]" << std::endl;
|
|
return;
|
|
}
|
|
|
|
QTextStream out(stdout);
|
|
QDBusMessage rc;
|
|
if (m_listDetails && m_appName.isEmpty() && m_appPath.isEmpty()) {
|
|
rc = iface->callWithArgumentList(QDBus::Block, "listProfiles",
|
|
QVariantList() << QVariant::fromValue<bool>(m_verbose));
|
|
out << "Available profiles:" << Qt::endl << Qt::endl;
|
|
}
|
|
else {
|
|
QVariantList list;
|
|
list << m_appName;
|
|
if (!m_listDetails) {
|
|
// then its a call to 'run2'.
|
|
list.append(m_arguments);
|
|
list << m_appPath;
|
|
list << QVariant::fromValue(m_lockedDown);
|
|
list << QVariant::fromValue(m_autodelete);
|
|
list << QVariant::fromValue(m_enclosingJailId);
|
|
list << QVariant::fromValue(m_vpnAcPath);
|
|
list << QVariant::fromValue(m_vpnOVPNPath);
|
|
}
|
|
rc = iface->callWithArgumentList(QDBus::Block,
|
|
m_listDetails ? "details" : "run2", list);
|
|
}
|
|
if (rc.type() == QDBusMessage::ErrorMessage) {
|
|
QTextStream err(stderr);
|
|
err << "Failed: " << rc.errorMessage() << Qt::endl;
|
|
shutDownHandler.rc = 1;
|
|
return;
|
|
}
|
|
if (rc.type() != QDBusMessage::ReplyMessage) {
|
|
qWarning() << "huh? Not a reply received";
|
|
return;
|
|
}
|
|
|
|
for (const auto &line : rc.arguments()) {
|
|
if (line.canConvert(QMetaType::QString)) {
|
|
QString content = line.toString();
|
|
if (m_listDetails) {
|
|
printDetailsXml(out, content);
|
|
return;
|
|
}
|
|
out << line.toString() << Qt::endl;
|
|
continue;
|
|
}
|
|
// probably a wrapped one then.
|
|
auto embedded = line.value<QDBusVariant>().variant();
|
|
if (embedded.isValid()) {
|
|
if (embedded.canConvert(QMetaType::QStringList)) {
|
|
for (const auto &s : embedded.toStringList()) {
|
|
out << s << Qt::endl;
|
|
}
|
|
} else if (embedded.canConvert(QMetaType::QString)) {
|
|
out << embedded.toString() << Qt::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
void printDetailsXml(QTextStream &out, const QString &xml) {
|
|
QXmlStreamReader parser(xml);
|
|
bool firstDeny = true;
|
|
bool firstAllow = true;
|
|
QStringView cur;
|
|
while (parser.readNextStartElement()) {
|
|
if (!cur.isEmpty() && cur != parser.name())
|
|
out << Qt::endl;
|
|
cur = QStringView();
|
|
if (parser.name() == QLatin1String("app")) {
|
|
auto att = parser.attributes();
|
|
out << "Profile: " << att.value("name") << Qt::endl;
|
|
out << " Path: " << att.value("exe") << Qt::endl;
|
|
if (m_verbose)
|
|
out << "Storage: $HOME/.local/jails/" << att.value("id") << "/" << Qt::endl;
|
|
}
|
|
else if (parser.name() == QLatin1String("denied")) {
|
|
if (firstDeny)
|
|
out << "Denied :";
|
|
firstDeny = false;
|
|
out << " ";
|
|
out << parser.readElementText();
|
|
cur = parser.name();
|
|
}
|
|
else if (parser.name() == QLatin1String("allowed")) {
|
|
if (firstAllow)
|
|
out << "Allowed:";
|
|
firstAllow = false;
|
|
out << " ";
|
|
out << parser.readElementText();
|
|
cur = parser.name();
|
|
}
|
|
else if (parser.name() == QLatin1String("lastRun")) {
|
|
auto att = parser.attributes();
|
|
auto start = QDateTime::fromString(att.value("start").toString(), Qt::ISODate);
|
|
auto convertedStart = start.toLocalTime();
|
|
out << "Started: " << convertedStart.toString() << Qt::endl;
|
|
if (!att.value("pid").isNull())
|
|
out << " [Currently Running] PID: " << att.value("pid").toString() << Qt::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool m_verbose = false;
|
|
bool m_listDetails = false;
|
|
bool m_lockedDown = false;
|
|
bool m_autodelete = false;
|
|
QString m_appName;
|
|
QString m_appPath;
|
|
QString m_enclosingJailId;
|
|
QStringList m_arguments;
|
|
|
|
// vpn
|
|
QString m_vpnOVPNPath;
|
|
QString m_vpnAcPath;
|
|
};
|
|
|
|
|
|
int main(int x, char **y) {
|
|
QCoreApplication app(x, y);
|
|
QCommandLineParser parser;
|
|
parser.setApplicationDescription("Isolation Application runner");
|
|
parser.addPositionalArgument("application", "The app you wish to run or list");
|
|
parser.addHelpOption(); // allows users to get an overview of options
|
|
QCommandLineOption details(QStringList() << "details" << "l", "list details instead");
|
|
parser.addOption(details);
|
|
QCommandLineOption verbose(QStringList() << "verbose" << "v", "More verbose output");
|
|
parser.addOption(verbose);
|
|
QCommandLineOption exe(QStringList() << "exe" << "e", "Full path of exe to run", "PATH");
|
|
parser.addOption(exe);
|
|
QCommandLineOption lockdown(QStringList() << "secure" << "s", "Run app with minimal permissions");
|
|
parser.addOption(lockdown);
|
|
QCommandLineOption autodelete(QStringList() << "rm", "Auto delete upon completion");
|
|
parser.addOption(autodelete);
|
|
QCommandLineOption inJail(QStringList() << "in", "Run exe IN argument jail", "JAIL");
|
|
parser.addOption(inJail);
|
|
QCommandLineOption vpnConf(QStringList() << "vpn", "A OVPN open vpn config file", "PATH");
|
|
parser.addOption(vpnConf);
|
|
QCommandLineOption vpnPwdFile(QStringList() << "vpnac", "VPN AccessCredentials file path", "PATH");
|
|
parser.addOption(vpnPwdFile);
|
|
parser.process(app);
|
|
|
|
|
|
/*
|
|
* Users may simply want to start an executable and they pass in that exe
|
|
* name as 'appName'. This should obviously be possible.
|
|
*
|
|
* What should also nicely work, it should be possible to start "socials" as
|
|
* an appName and on first run we can pass in the appPath (/bin/firefox) and
|
|
* any arguments.
|
|
*/
|
|
QString appName;
|
|
QString appPath;
|
|
if (parser.isSet(exe))
|
|
appPath = parser.value(exe);
|
|
const auto args = parser.positionalArguments();
|
|
if (!args.isEmpty())
|
|
appName = args.first();
|
|
|
|
if (appName.isEmpty())
|
|
appName = appPath;
|
|
|
|
if (appName.isEmpty() && !parser.isSet(details))
|
|
parser.showHelp(0);
|
|
|
|
if (!QDBusConnection::sessionBus().isConnected()) {
|
|
fprintf(stderr, "Cannot connect to the D-Bus session bus.\n");
|
|
return 1;
|
|
}
|
|
|
|
Client client;
|
|
client.setVerbose(parser.isSet(verbose));
|
|
client.setListDetails(parser.isSet(details));
|
|
client.setAutodelete(parser.isSet(autodelete));
|
|
client.setLockedDown(parser.isSet(lockdown));
|
|
client.setVpnAcPath(parser.value(vpnPwdFile));
|
|
client.setVpnOVPNPath(parser.value(vpnConf));
|
|
if (parser.isSet(inJail))
|
|
client.setEnclosingJailId(parser.value(inJail));
|
|
client.setApp(appName, appPath);
|
|
client.setAppArguments(args.mid(1));
|
|
QTimer::singleShot(0, &client, &Client::start);
|
|
return app.exec();
|
|
}
|
|
|
|
#include "client.moc"
|