1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-03 01:17:41 +03:00

B OpenNebula/one#6759: Add extra checks for ports in SecurityGroup validation (#3319)

Signed-off-by: Valentyn Bohdan <vbohdan@opennebula.io>
This commit is contained in:
Valentyn Bohdan 2024-12-11 12:58:32 +01:00 committed by GitHub
parent 146dfe6a09
commit 5aebf691e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 62 deletions

View File

@ -23,6 +23,7 @@
#include <set> #include <set>
#include <algorithm> #include <algorithm>
#include <random> #include <random>
#include <regex>
#include <mutex> #include <mutex>
#include <openssl/crypto.h> #include <openssl/crypto.h>
@ -141,12 +142,8 @@ namespace one_util
* *
* @param st string to split * @param st string to split
* @param delim delimiter character * @param delim delimiter character
* @param clean_empty true to clean empty split parts. * @param parts where the result will be saved
* Example for st "a::b:c"
* clean_empty true will return ["a", "b", "c"]
* clean_empty fase will return ["a", "", "b", "c"]
* *
* @return a vector containing the resulting substrings
*/ */
template <class T> template <class T>
void split(const std::string &st, char delim, std::vector<T> &parts) void split(const std::string &st, char delim, std::vector<T> &parts)
@ -176,6 +173,19 @@ namespace one_util
} }
} }
/**
* Splits a string, using the given delimiter
*
* @param st string to split
* @param delim delimiter character
* @param clean_empty true to clean empty split parts.
* Example for st "a::b:c"
* clean_empty true will return ["a", "b", "c"]
* clean_empty fase will return ["a", "", "b", "c"]
*
* @return a vector containing the resulting substrings
*/
std::vector<std::string> split(const std::string& st, char delim, std::vector<std::string> split(const std::string& st, char delim,
bool clean_empty = true); bool clean_empty = true);
@ -364,6 +374,34 @@ namespace one_util
template <> template <>
bool str_cast(const std::string& str, std::string& value); bool str_cast(const std::string& str, std::string& value);
/**
* Converts string into unsigned integer type
* @param str Input string
*
* @return Unsigned integer value on success, 0 on failure.
* @note If value in string is greater than typename T can hold,
* maximum possible value will be returned
*/
template <typename T>
T string_to_unsigned(const std::string& str)
{
T value;
if (std::regex_search(str, std::regex("[^0-9]")))
{
return 0;
}
std::istringstream iss(str);
iss >> value;
if (iss.fail() || !iss.eof())
{
value = std::numeric_limits<T>::max();
}
return value;
}
} // namespace one_util } // namespace one_util
#endif /* _NEBULA_UTIL_H_ */ #endif /* _NEBULA_UTIL_H_ */

View File

@ -63,7 +63,8 @@ int SecurityGroup::insert(SqlDB *db, string& error_str)
if (name.empty()) if (name.empty())
{ {
goto error_name; error_str = "No NAME in template for Security Group.";
return -1;
} }
get_template_attribute("RULE", rules); get_template_attribute("RULE", rules);
@ -72,7 +73,7 @@ int SecurityGroup::insert(SqlDB *db, string& error_str)
{ {
if (!is_valid(rule, error_str)) if (!is_valid(rule, error_str))
{ {
goto error_valid; return -1;
} }
} }
@ -80,19 +81,10 @@ int SecurityGroup::insert(SqlDB *db, string& error_str)
if ( insert_replace(db, false, error_str) != 0 ) if ( insert_replace(db, false, error_str) != 0 )
{ {
goto error_db; return -1;
} }
return 0; return 0;
error_name:
error_str = "No NAME in template for Security Group.";
goto error_common;
error_valid:
error_db:
error_common:
return -1;
} }
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -302,18 +294,12 @@ void SecurityGroup::get_rules(vector<VectorAttribute*>& result) const
bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
{ {
string value, ip, proto;
unsigned int ivalue;
int id;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Check PROTOCOL and extensions // Check PROTOCOL and extensions
// - RANGE for TCP and UDP // - RANGE for TCP and UDP
// - ICMP_TYPE for ICMP // - ICMP_TYPE for ICMP
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
proto = rule->vector_value("PROTOCOL"); auto proto = rule->vector_value("PROTOCOL");
one_util::toupper(proto); one_util::toupper(proto);
@ -325,7 +311,7 @@ bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
return false; return false;
} }
value = rule->vector_value("RANGE"); auto value = rule->vector_value("RANGE");
if (!value.empty() && proto != "TCP" && proto != "UDP") if (!value.empty() && proto != "TCP" && proto != "UDP")
{ {
@ -335,24 +321,64 @@ bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
if (!value.empty()) if (!value.empty())
{ {
const char *range_pattern = "^(([[:digit:]]+|[[:digit:]]+:[[:digit:]]+),)*([[:digit:]]+|[[:digit:]]+:[[:digit:]]+)$"; constexpr auto ports_list_pattern =
if (one_util::regex_match(range_pattern, value.c_str()) != 0) "^(([[:digit:]]+|[[:digit:]]+:[[:digit:]]+),)*([[:digit:]]+|[[:digit:]]+:[[:digit:]]+)$";
if (one_util::regex_match(ports_list_pattern, value.c_str()) != 0)
{ {
error = "Invalid RANGE specification."; error = "Invalid RANGE specification.";
return false; return false;
} }
// Check all port numbers are between 0-65535 std::size_t port_counter = 0;
const char *big_port_pattern = "([1-9][[:digit:]]{5,}|"
"[7-9][[:digit:]]{4,}|" const auto port_rules = one_util::split(value, ',');
"6[6-9][[:digit:]]{3,}|"
"65[6-9][[:digit:]]{2,}|" for (const auto& port_rule : port_rules)
"655[4-9][[:digit:]]|"
"6553[6-9])";
if (one_util::regex_match(big_port_pattern, value.c_str()) == 0)
{ {
error = "RANGE out of bounds 0-65536."; constexpr auto range_pattern = "([[:digit:]]+:[[:digit:]]+)";
return false;
if (one_util::regex_match(range_pattern, port_rule.c_str()) == 0)
{
const auto port_values = one_util::split(port_rule, ':');
if (port_values.size() != 2)
{
error = "Invalid RANGE specification.";
return false;
}
const auto end_value = one_util::string_to_unsigned<std::uint32_t>(port_values[1]);
if (end_value > 65535)
{
error = "RANGE out of bounds 0-65536.";
return false;
}
if (end_value < one_util::string_to_unsigned<std::uint32_t>(port_values[0]))
{
error = "RANGE BEGIN value is greater than RANGE END value";
return false;
}
port_counter += 2;
}
else
{
if (one_util::string_to_unsigned<std::uint32_t>(port_rule) > 65535)
{
error = "RANGE out of bounds 0-65536.";
return false;
}
++port_counter;
}
if (port_counter > 15)
{
error = "Not more than 15 ports can be specified in a RULE";
return false;
}
} }
} }
@ -366,6 +392,7 @@ bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
return false; return false;
} }
unsigned int ivalue = 0;
if (rule->vector_value("ICMP_TYPE", ivalue) != 0) if (rule->vector_value("ICMP_TYPE", ivalue) != 0)
{ {
error = "Wrong ICMP_TYPE, it must be integer"; error = "Wrong ICMP_TYPE, it must be integer";
@ -383,6 +410,7 @@ bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
return false; return false;
} }
unsigned int ivalue = 0;
if (rule->vector_value("ICMPV6_TYPE", ivalue) != 0) if (rule->vector_value("ICMPV6_TYPE", ivalue) != 0)
{ {
error = "Wrong ICMPV6_TYPE, it must be integer"; error = "Wrong ICMPV6_TYPE, it must be integer";
@ -408,12 +436,13 @@ bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
// Check IP, SIZE and NETWORK_ID // Check IP, SIZE and NETWORK_ID
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
ip = rule->vector_value("IP"); const auto ip = rule->vector_value("IP");
if (!ip.empty()) //Target as IP & SIZE if (!ip.empty()) //Target as IP & SIZE
{ {
struct in6_addr ip_addr; struct in6_addr ip_addr;
unsigned int ivalue = 0;
if (rule->vector_value("SIZE", ivalue) != 0) if (rule->vector_value("SIZE", ivalue) != 0)
{ {
error = "Wrong or empty SIZE."; error = "Wrong or empty SIZE.";
@ -431,6 +460,7 @@ bool SecurityGroup::is_valid(const VectorAttribute * rule, string& error) const
} }
else //Target is ANY or NETWORK_ID else //Target is ANY or NETWORK_ID
{ {
int id = 0;
if (rule->vector_value("NETWORK_ID", value) == 0 && if (rule->vector_value("NETWORK_ID", value) == 0 &&
rule->vector_value("NETWORK_ID", id) != 0) rule->vector_value("NETWORK_ID", id) != 0)
{ {

View File

@ -23,6 +23,7 @@
#include "Nebula.h" #include "Nebula.h"
#include "Image.h" #include "Image.h"
#include "DatastorePool.h" #include "DatastorePool.h"
#include "NebulaUtil.h"
#include <regex> #include <regex>
#include <exception> #include <exception>
@ -105,27 +106,6 @@ static int to_int(const string& s)
return val; return val;
} }
template <class T>
T to_unsigned(const std::string& str)
{
T value;
if (std::regex_search(str, std::regex("[^0-9]")))
{
return 0;
}
std::istringstream iss(str);
iss >> value;
if (iss.fail() || !iss.eof())
{
value = std::numeric_limits<T>::max();
}
return value;
}
template<typename T> template<typename T>
static void insert_sec(ofstream& file, const string& base, const string& s, static void insert_sec(ofstream& file, const string& base, const string& s,
const string& sm, const string& sml) const string& sm, const string& sml)
@ -134,7 +114,7 @@ static void insert_sec(ofstream& file, const string& base, const string& s,
if (!s.empty()) if (!s.empty())
{ {
s_i = to_unsigned<T>(s); s_i = one_util::string_to_unsigned<T>(s);
file << "\t\t\t\t<" << base << "_sec>" << one_util::escape_xml(std::to_string(s_i)) file << "\t\t\t\t<" << base << "_sec>" << one_util::escape_xml(std::to_string(s_i))
<< "</" << base << "_sec>\n"; << "</" << base << "_sec>\n";
@ -142,7 +122,7 @@ static void insert_sec(ofstream& file, const string& base, const string& s,
if (!sm.empty()) if (!sm.empty())
{ {
const auto sm_i = to_unsigned<T>(sm); const auto sm_i = one_util::string_to_unsigned<T>(sm);
if ( sm_i > s_i) if ( sm_i > s_i)
{ {
@ -152,7 +132,7 @@ static void insert_sec(ofstream& file, const string& base, const string& s,
if (!sml.empty()) if (!sml.empty())
{ {
const auto sml_i = to_unsigned<T>(sml); const auto sml_i = one_util::string_to_unsigned<T>(sml);
file << "\t\t\t\t<" << base << "_sec_max_length>" file << "\t\t\t\t<" << base << "_sec_max_length>"
<< one_util::escape_xml(std::to_string(sml_i)) << one_util::escape_xml(std::to_string(sml_i))