aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorflu0r1ne <flu0r1ne@flu0r1ne.net>2023-11-20 17:45:24 -0600
committerflu0r1ne <flu0r1ne@flu0r1ne.net>2023-11-20 17:46:39 -0600
commit50df8f91a90d8c91676b81cc9668b58914f85b08 (patch)
tree3a201d743adaf1d4212c3c52dcb873bc93001758
parent24488352a124556001f3a32bf76570754a9d389c (diff)
downloadwg2nd-50df8f91a90d8c91676b81cc9668b58914f85b08.tar.xz
wg2nd-50df8f91a90d8c91676b81cc9668b58914f85b08.zip
Allow configurable activation policy, allow -h before dropping caps
-rw-r--r--README.md32
-rw-r--r--src/main.cpp217
-rw-r--r--src/wg2nd.cpp33
-rw-r--r--src/wg2nd.hpp12
4 files changed, 191 insertions, 103 deletions
diff --git a/README.md b/README.md
index 9664eb4..36ad5e4 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,8 @@ wg2nd generate -t nft /etc/wireguard/wg0.conf >> /etc/nftables.conf
networkctl up wg0
```
-To enable automatic starting, ensure that the `ActivationPolicy` is removed from the generated `network` configuration.
+To enable automatic starting, use `wg2nd install -a up /etc/wireguard/wg0.conf`. This sets the
+default [activation policy](https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#ActivationPolicy=) to `up`.
### Batch Conversion
@@ -136,7 +137,7 @@ Usage: wg2nd version
```
```plaintext
-Usage: wg2nd install [ -h ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE
+Usage: ./wg2nd install [ -h ] [ -a ACTIVATION_POLICY ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE
`wg2nd install` translates `wg-quick(8)` configuration into corresponding
`networkd` configuration and installs the resulting files in `OUTPUT_PATH`.
@@ -152,6 +153,10 @@ Usage: wg2nd install [ -h ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE
`wg2nd generate -t nft CONFIG_FILE`.
Options:
+ -a ACTIVATION_POLICY
+ manual Require manual activation (default)
+ up Automatically set the link "up"
+
-o OUTPUT_PATH The installation path (default is /etc/systemd/network)
-f FILE_NAME The base name for the installed configuration files. The
@@ -166,18 +171,21 @@ Options:
```
```plaintext
-Usage: wg2nd generate [ -h ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE
-
-`wg2nd generate` translates `wg-quick(8)` configuration into the equivalent
-`systemd-networkd` configuration. The results are printed to `stdout`. Users
-are responsible for installing these files correctly and restricting access privileges.
+Usage: ./wg2nd generate [ -h ] [ -a ACTIVATION_POLICY ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE
Options:
+ -a ACTIVATION_POLICY
+ manual Require manual activation (default)
+ up Automatically set the link "up"
+
-t FILE_TYPE
- network Generate a Network Configuration File (see systemd.network(8))
- netdev Generate a Virtual Device File (see systemd.netdev(8))
- keyfile Print the interface's private key
- nft Print the netfilter table `nft(8)` installed by `wg-quick(8)`
+ network Generate a Network Configuration File (see systemd.network(8))
+ netdev Generate a Virtual Device File (see systemd.netdev(8))
+ keyfile Print the interface's private key
+ nft Print the netfilter table `nft(8)` installed by `wg-quick(8)`
+
+ -k KEYPATH Full path to the keyfile (a path relative to /etc/systemd/network is generated
+ if unspecified)
- -h Display this help
+ -h Print this help
```
diff --git a/src/main.cpp b/src/main.cpp
index 36226c7..7cc8d13 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -134,13 +134,16 @@ void print_help(const char *prog) {
}
void die_usage_generate(const char *prog) {
- err("Usage: %s generate [ -h ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE", prog);
+ err("Usage: %s generate [ -h ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] [ -a ACTIVATION_POLICY ] CONFIG_FILE\n", prog);
die("Use -h for help");
}
void print_help_generate(const char *prog) {
- err("Usage: %s generate [ -h ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE\n", prog);
+ err("Usage: %s generate [ -h ] [ -a ACTIVATION_POLICY ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE\n", prog);
err("Options:");
+ err(" -a ACTIVATION_POLICY");
+ err(" manual Require manual activation (default)");
+ err(" up Automatically set the link \"up\"\n");
err(" -t FILE_TYPE");
err(" network Generate a Network Configuration File (see systemd.network(8))");
err(" netdev Generate a Virtual Device File (see systemd.netdev(8))");
@@ -153,12 +156,12 @@ void print_help_generate(const char *prog) {
}
void die_usage_install(const char *prog) {
- err("Usage: %s install [ -h ] [ -f FILE_NAME ] [ -k KEYFILE ] [ -o OUTPUT_PATH ] CONFIG_FILE", prog);
+ err("Usage: %s install [ -h ] [ -a ACTIVATION_POLICY ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE\n", prog);
die("Use -h for help");
}
void print_help_install(const char *prog) {
- err("Usage: %s install [ -h ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE\n", prog);
+ err("Usage: %s install [ -h ] [ -a ACTIVATION_POLICY ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE\n", prog);
err(" `wg2nd install` translates `wg-quick(8)` configuration into corresponding");
err(" `networkd` configuration and installs the resulting files in `OUTPUT_PATH`.\n");
err(" `wg2nd install` generates a `netdev`, `network`, and `keyfile` for each");
@@ -170,6 +173,9 @@ void print_help_install(const char *prog) {
err(" with `wg2nd install`. The equivalent firewall can be generated with");
err(" `wg2nd generate -t nft CONFIG_FILE`.\n");
err("Options:");
+ err(" -a ACTIVATION_POLICY");
+ err(" manual Require manual activation (default)");
+ err(" up Automatically set the link \"up\"\n");
err(" -o OUTPUT_PATH The installation path (default is /etc/systemd/network)\n");
err(" -f FILE_NAME The base name for the installed configuration files. The");
err(" networkd-specific configuration suffix will be added");
@@ -211,7 +217,7 @@ enum class FileType {
using namespace wg2nd;
-void write_systemd_file(SystemdFilespec const & filespec, std::string output_path, bool secure) {
+static void write_systemd_file(SystemdFilespec const & filespec, std::string output_path, bool secure) {
std::string full_path = output_path + "/" + filespec.name;
std::ofstream ofs;
@@ -254,10 +260,11 @@ void write_systemd_file(SystemdFilespec const & filespec, std::string output_pat
}
}
-SystemdConfig generate_cfg_or_die(
+static SystemdConfig generate_cfg_or_die(
std::filesystem::path && config_path,
std::filesystem::path const & keyfile_or_output_path,
- std::optional<std::string> const & filename
+ std::optional<std::string> const & filename,
+ ActivationPolicy activation_policy
) {
std::fstream cfg_stream { config_path, std::ios_base::in };
@@ -270,7 +277,13 @@ SystemdConfig generate_cfg_or_die(
std::string interface_name = interface_name_from_filename(config_path);
try {
- cfg = wg2nd::wg2nd(interface_name, cfg_stream, keyfile_or_output_path, filename);
+ cfg = wg2nd::wg2nd(
+ interface_name,
+ cfg_stream,
+ keyfile_or_output_path,
+ filename,
+ activation_policy
+ );
} catch(ConfigurationException const & cex) {
const ParsingException * pex = dynamic_cast<const ParsingException *>(&cex);
@@ -286,8 +299,9 @@ SystemdConfig generate_cfg_or_die(
}
-void wg2nd_install_internal(std::optional<std::string> && filename, std::string && keyfile_name,
- std::filesystem::path && output_path, std::filesystem::path && config_path) {
+static void wg2nd_install_internal(std::optional<std::string> && filename, std::string && keyfile_name,
+ std::filesystem::path && output_path, std::filesystem::path && config_path,
+ ActivationPolicy activation_policy) {
if(!std::filesystem::path(output_path).is_absolute()) {
output_path = std::filesystem::absolute(output_path);
@@ -299,7 +313,12 @@ void wg2nd_install_internal(std::optional<std::string> && filename, std::string
keyfile_or_output_path /= keyfile_name;
}
- SystemdConfig cfg = generate_cfg_or_die(std::move(config_path), keyfile_or_output_path, std::move(filename));
+ SystemdConfig cfg = generate_cfg_or_die(
+ std::move(config_path),
+ keyfile_or_output_path,
+ std::move(filename),
+ activation_policy
+ );
for(std::string const & warning : cfg.warnings) {
err("warning: %s", warning.c_str());
@@ -314,11 +333,15 @@ void wg2nd_install_internal(std::optional<std::string> && filename, std::string
}
}
-void wg2nd_generate_internal(FileType type, std::string && config_file,
- std::optional<std::filesystem::path> && keyfile_path) {
+static void wg2nd_generate_internal(FileType type, std::string && config_file,
+ std::optional<std::filesystem::path> && keyfile_path,
+ ActivationPolicy activation_policy) {
SystemdConfig cfg = generate_cfg_or_die(
- std::move(config_file), std::move(keyfile_path.value_or(DEFAULT_OUTPUT_PATH)), {}
+ std::move(config_file),
+ std::move(keyfile_path.value_or(DEFAULT_OUTPUT_PATH)),
+ {},
+ activation_policy
);
switch(type) {
@@ -339,15 +362,79 @@ void wg2nd_generate_internal(FileType type, std::string && config_file,
}
}
+#ifdef HAVE_LIBCAP
+
+// Drop excess capabilities and ensure the process have proper capabilities upfront
+static void drop_excess_capabilities(std::vector<cap_value_t> cap_required) {
+
+ cap_t current_cap = cap_get_proc();
+ cap_t wanted_cap = cap_get_proc();
+
+ if(!current_cap || !wanted_cap) {
+ goto die_cap_failure;
+ }
+
+ if(cap_clear(wanted_cap)) {
+ goto die_cap_failure;
+ }
+
+ // Add required capabilities to the wanted set
+ for(cap_value_t cap : cap_required) {
+ cap_flag_value_t is_set;
+
+ // Print nice error message if the user does not have the required permissions
+ if(cap_get_flag(current_cap, cap, CAP_PERMITTED, &is_set)) {
+ goto die_cap_failure;
+ }
+
+ if(is_set != CAP_SET) {
+ char * name = cap_to_name(cap);
+ die("Failed to obtain capability \"%s\": do you need to elevate permissions?", name);
+ }
+
+ if(cap_set_flag(wanted_cap, CAP_PERMITTED, 1, &cap, CAP_SET)) {
+ goto die_cap_failure;
+ }
+
+ if(cap_set_flag(wanted_cap, CAP_EFFECTIVE, 1, &cap, CAP_SET)) {
+ goto die_cap_failure;
+ }
+ }
+
+ if(cap_set_proc(wanted_cap)) {
+ goto die_cap_failure;
+ }
+
+ if(cap_free(wanted_cap) || cap_free(current_cap)) {
+ goto die_cap_failure;
+ }
+
+ return;
+
+die_cap_failure:
+ die_errno("Failed to drop capabilities");
+}
+#endif /* HAVE_LIBCAP */
+
+static ActivationPolicy activation_policy_from_argument(std::string const & arg) {
+ if(arg == "manual") {
+ return ActivationPolicy::MANUAL;
+ } else if (arg == "up") {
+ return ActivationPolicy::UP;
+ } else {
+ die("Unknown activation policy: \"%s\"", arg.c_str());
+ }
+}
static int wg2nd_generate(char const * prog, int argc, char **argv) {
std::filesystem::path config_path = "";
FileType type = FileType::NONE;
std::optional<std::filesystem::path> keyfile_path = {};
+ ActivationPolicy activation_policy = ActivationPolicy::MANUAL;
int opt;
- while ((opt = getopt(argc, argv, "ht:k:")) != -1) {
+ while ((opt = getopt(argc, argv, "ht:k:a:")) != -1) {
switch (opt) {
case 't':
if (strcmp(optarg, "network") == 0) {
@@ -365,6 +452,9 @@ static int wg2nd_generate(char const * prog, int argc, char **argv) {
case 'k':
keyfile_path = optarg;
break;
+ case 'a':
+ activation_policy = activation_policy_from_argument(optarg);
+ break;
case 'h':
print_help_generate(prog);
break;
@@ -377,9 +467,18 @@ static int wg2nd_generate(char const * prog, int argc, char **argv) {
die_usage_generate(prog);
}
+#ifdef HAVE_LIBCAP
+ drop_excess_capabilities({});
+#endif /* HAVE_LIBCAP */
+
config_path = argv[optind];
- wg2nd_generate_internal(type, std::move(config_path), std::move(keyfile_path));
+ wg2nd_generate_internal(
+ type,
+ std::move(config_path),
+ std::move(keyfile_path),
+ activation_policy
+ );
return 0;
}
@@ -390,9 +489,10 @@ static int wg2nd_install(char const * prog, int argc, char **argv) {
std::optional<std::string> filename = {};
std::filesystem::path output_path = DEFAULT_OUTPUT_PATH;
std::string keyfile_name = "";
+ ActivationPolicy activation_policy = ActivationPolicy::MANUAL;
int opt;
- while ((opt = getopt(argc, argv, "o:f:k:h")) != -1) {
+ while ((opt = getopt(argc, argv, "o:f:k:a:h")) != -1) {
switch (opt) {
case 'o': {
std::string path = optarg;
@@ -411,6 +511,9 @@ static int wg2nd_install(char const * prog, int argc, char **argv) {
case 'k':
keyfile_name = optarg;
break;
+ case 'a':
+ activation_policy = activation_policy_from_argument(optarg);
+ break;
default:
die_usage_install(prog);
}
@@ -420,66 +523,25 @@ static int wg2nd_install(char const * prog, int argc, char **argv) {
die_usage_install(prog);
}
- config_path = argv[optind];
-
- wg2nd_install_internal(std::move(filename), std::move(keyfile_name), std::move(output_path), std::move(config_path));
-
- return 0;
-}
-
#ifdef HAVE_LIBCAP
+ drop_excess_capabilities({{
+ CAP_CHOWN,
+ CAP_DAC_OVERRIDE
+ }});
+#endif /* HAVE_LIBCAP */
-// Drop excess capabilities and ensure the process have proper capabilities upfront
-void drop_excess_capabilities(std::vector<cap_value_t> cap_required) {
-
- cap_t current_cap = cap_get_proc();
- cap_t wanted_cap = cap_get_proc();
-
- if(!current_cap || !wanted_cap) {
- goto die_cap_failure;
- }
-
- if(cap_clear(wanted_cap)) {
- goto die_cap_failure;
- }
-
- // Add required capabilities to the wanted set
- for(cap_value_t cap : cap_required) {
- cap_flag_value_t is_set;
-
- // Print nice error message if the user does not have the required permissions
- if(cap_get_flag(current_cap, cap, CAP_PERMITTED, &is_set)) {
- goto die_cap_failure;
- }
-
- if(is_set != CAP_SET) {
- char * name = cap_to_name(cap);
- die("Failed to obtain capability \"%s\": do you need to elevate permissions?", name);
- }
-
- if(cap_set_flag(wanted_cap, CAP_PERMITTED, 1, &cap, CAP_SET)) {
- goto die_cap_failure;
- }
-
- if(cap_set_flag(wanted_cap, CAP_EFFECTIVE, 1, &cap, CAP_SET)) {
- goto die_cap_failure;
- }
- }
-
- if(cap_set_proc(wanted_cap)) {
- goto die_cap_failure;
- }
-
- if(cap_free(wanted_cap) || cap_free(current_cap)) {
- goto die_cap_failure;
- }
+ config_path = argv[optind];
- return;
+ wg2nd_install_internal(
+ std::move(filename),
+ std::move(keyfile_name),
+ std::move(output_path),
+ std::move(config_path),
+ activation_policy
+ );
-die_cap_failure:
- die_errno("Failed to drop capabilities");
+ return 0;
}
-#endif /* HAVE_LIBCAP */
int main(int argc, char **argv) {
char const * prog = "wg2nd";
@@ -494,17 +556,6 @@ int main(int argc, char **argv) {
std::string action = argv[1];
-#ifdef HAVE_LIBCAP
- std::vector<cap_value_t> required_caps;
-
- if(action == "install") {
- required_caps.push_back(CAP_CHOWN);
- required_caps.push_back(CAP_DAC_OVERRIDE);
- }
-
- drop_excess_capabilities(required_caps);
-#endif /* HAVE_LIBCAP */
-
if (action == "generate") {
return wg2nd_generate(prog, argc - 1, argv + 1);
} else if (action == "install") {
diff --git a/src/wg2nd.cpp b/src/wg2nd.cpp
index deb3212..afe3132 100644
--- a/src/wg2nd.cpp
+++ b/src/wg2nd.cpp
@@ -446,7 +446,18 @@ namespace wg2nd {
return netdev.str();
}
- static std::string _gen_network_cfg(Config const & cfg, uint32_t fwd_table) {
+ static std::string_view activation_policy_keyword(ActivationPolicy activation_policy) {
+ switch(activation_policy) {
+ case ActivationPolicy::MANUAL:
+ return "manual";
+ case ActivationPolicy::UP:
+ return "up";
+ }
+
+ return "none";
+ }
+
+ static std::string _gen_network_cfg(Config const & cfg, uint32_t fwd_table, ActivationPolicy activation_policy) {
std::stringstream network;
network << "# Autogenerated by wg2nd\n";
@@ -455,7 +466,9 @@ namespace wg2nd {
network << "\n";
network << "[Link]" << "\n";
- network << "ActivationPolicy = manual\n";
+
+ network << "ActivationPolicy = " << activation_policy_keyword(activation_policy) << "\n";
+
if(!cfg.intf.mtu.empty()) {
network << "MTUBytes = " << cfg.intf.mtu << "\n";
}
@@ -551,7 +564,8 @@ namespace wg2nd {
SystemdConfig gen_systemd_config(
Config const & cfg,
std::filesystem::path const & keyfile_or_output_path,
- std::optional<std::string> const & filename
+ std::optional<std::string> const & filename,
+ ActivationPolicy activation_policy
) {
// If the table is explicitly specified with Table=<number>,
@@ -608,7 +622,7 @@ if(!cfg.intf.field_.empty()) { \
},
.network = {
.name = basename + ".network",
- .contents = _gen_network_cfg(cfg, fwd_table)
+ .contents = _gen_network_cfg(cfg, fwd_table, activation_policy)
},
.private_keyfile = {
.name = keyfile_path.filename(),
@@ -621,8 +635,15 @@ if(!cfg.intf.field_.empty()) { \
}
SystemdConfig wg2nd(std::string const & interface_name, std::istream & stream,
- std::filesystem::path const & keyfile_or_output_path, std::optional<std::string> const & filename) {
- return gen_systemd_config(parse_config(interface_name, stream), keyfile_or_output_path, filename);
+ std::filesystem::path const & keyfile_or_output_path,
+ std::optional<std::string> const & filename,
+ ActivationPolicy activation_policy) {
+ return gen_systemd_config(
+ parse_config(interface_name, stream),
+ keyfile_or_output_path,
+ filename,
+ activation_policy
+ );
}
}
diff --git a/src/wg2nd.hpp b/src/wg2nd.hpp
index 4e3e18a..fbded13 100644
--- a/src/wg2nd.hpp
+++ b/src/wg2nd.hpp
@@ -17,6 +17,11 @@
namespace wg2nd {
+ enum class ActivationPolicy {
+ MANUAL,
+ UP,
+ };
+
struct Interface {
// File name, or defaults to "wg"
std::string name;
@@ -143,11 +148,14 @@ namespace wg2nd {
SystemdConfig gen_systemd_config(
Config const & cfg,
std::filesystem::path const & keyfile_or_output_path,
- std::optional<std::string> const & filename
+ std::optional<std::string> const & filename,
+ ActivationPolicy activation_policy = ActivationPolicy::MANUAL
);
SystemdConfig wg2nd(std::string const & interface_name, std::istream & stream,
std::filesystem::path const & keyfile_or_output_path,
- std::optional<std::string> const & filename);
+ std::optional<std::string> const & filename,
+ ActivationPolicy activation_policy = ActivationPolicy::MANUAL
+ );
};