diff options
| author | flu0r1ne <flu0r1ne@flu0r1ne.net> | 2023-11-20 17:45:24 -0600 | 
|---|---|---|
| committer | flu0r1ne <flu0r1ne@flu0r1ne.net> | 2023-11-20 17:46:39 -0600 | 
| commit | 50df8f91a90d8c91676b81cc9668b58914f85b08 (patch) | |
| tree | 3a201d743adaf1d4212c3c52dcb873bc93001758 | |
| parent | 24488352a124556001f3a32bf76570754a9d389c (diff) | |
| download | wg2nd-50df8f91a90d8c91676b81cc9668b58914f85b08.tar.xz wg2nd-50df8f91a90d8c91676b81cc9668b58914f85b08.zip | |
Allow configurable activation policy, allow -h before dropping caps
| -rw-r--r-- | README.md | 32 | ||||
| -rw-r--r-- | src/main.cpp | 217 | ||||
| -rw-r--r-- | src/wg2nd.cpp | 33 | ||||
| -rw-r--r-- | src/wg2nd.hpp | 12 | 
4 files changed, 191 insertions, 103 deletions
| @@ -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 +	);  }; | 
