|
14 | 14 | along with this program; if not, write to the Free Software |
15 | 15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
16 | 16 |
|
17 | | -#include <mysqld_error.h> |
| 17 | +#include <algorithm> |
18 | 18 | #include <sstream> |
19 | 19 |
|
20 | 20 | #include "data_provider.h" |
21 | 21 | #include "logger.h" |
| 22 | +#include "telemetry_status_allowlist.h" |
| 23 | +#include "telemetry_sysvars_allowlist.h" |
22 | 24 |
|
23 | 25 | namespace { |
24 | 26 | inline const char *b2s(bool val) { return val ? "1" : "0"; } |
25 | 27 |
|
| 28 | +/* Reject values that look like paths (defense in depth: the allow list is |
| 29 | + already curated, but we double-check at collection time in case a value |
| 30 | + shape changes upstream). */ |
| 31 | +inline bool is_value_safe_for_export(const std::string &value) { |
| 32 | + return std::all_of(value.begin(), value.end(), |
| 33 | + [](unsigned char ch) { return ch != '/' && ch != '\\'; }); |
| 34 | +} |
| 35 | + |
26 | 36 | /* |
27 | 37 | percona.telemetry user is created when server starts with telemetry |
28 | 38 | enabled. The user is deleted, when the server is started with telemetry |
@@ -67,6 +77,11 @@ const char *size = "size"; |
67 | 77 | // server configuration variables |
68 | 78 | const char *server_config_info = "server_config_info"; |
69 | 79 | const char *thread_handling = "thread_handling"; |
| 80 | +const char *nondefault_allowlisted_sysvars = "nondefault_allowlisted_sysvars"; |
| 81 | +const char *variable_source = "variable_source"; |
| 82 | +const char *variable_value = "variable_value"; |
| 83 | +const char *server_status_info = "server_status_info"; |
| 84 | +const char *allowlisted_global_status = "allowlisted_global_status"; |
70 | 85 | } // namespace JSONKey |
71 | 86 | } // namespace |
72 | 87 |
|
@@ -659,12 +674,121 @@ bool DataProvider::collect_server_config(rapidjson::Document *document) { |
659 | 674 | thread_handling, allocator); |
660 | 675 | } |
661 | 676 |
|
| 677 | + /* |
| 678 | + Non-default globals: provenance from performance_schema.variables_info, |
| 679 | + restricted to allowlisted names via SQL IN (...) (see |
| 680 | + telemetry_sysvars_allowlist.h) and value shape via |
| 681 | + is_value_safe_for_export() above. |
| 682 | + */ |
| 683 | + if (!kSysvarsAllowlistCsv.empty()) { |
| 684 | + std::ostringstream oss; |
| 685 | + oss << "SELECT vi.VARIABLE_NAME, gv.VARIABLE_VALUE, vi.VARIABLE_SOURCE " |
| 686 | + "FROM performance_schema.variables_info vi " |
| 687 | + "INNER JOIN performance_schema.global_variables gv " |
| 688 | + "USING (VARIABLE_NAME) " |
| 689 | + "WHERE vi.VARIABLE_SOURCE <> 'COMPILED' " |
| 690 | + "AND vi.VARIABLE_NAME IN (" |
| 691 | + << kSysvarsAllowlistCsv << ')'; |
| 692 | + |
| 693 | + QueryResult rows; |
| 694 | + if (!do_query(oss.str(), &rows, nullptr, true)) { |
| 695 | + rapidjson::Value sysvars_array(rapidjson::Type::kArrayType); |
| 696 | + for (const Row &row : rows) { |
| 697 | + if (row.size() < 3) { |
| 698 | + continue; |
| 699 | + } |
| 700 | + const std::string &vname = row[0]; |
| 701 | + const std::string &vval = row[1]; |
| 702 | + const std::string &vsrc = row[2]; |
| 703 | + if (!is_value_safe_for_export(vval)) { |
| 704 | + continue; |
| 705 | + } |
| 706 | + rapidjson::Value one_status(rapidjson::Type::kObjectType); |
| 707 | + rapidjson::Value name_json; |
| 708 | + name_json.SetString(vname.c_str(), |
| 709 | + static_cast<rapidjson::SizeType>(vname.length()), |
| 710 | + allocator); |
| 711 | + one_status.AddMember(rapidjson::StringRef(JSONKey::name), name_json, |
| 712 | + allocator); |
| 713 | + rapidjson::Value val_json; |
| 714 | + val_json.SetString(vval.c_str(), |
| 715 | + static_cast<rapidjson::SizeType>(vval.length()), |
| 716 | + allocator); |
| 717 | + one_status.AddMember(rapidjson::StringRef(JSONKey::variable_value), |
| 718 | + val_json, allocator); |
| 719 | + rapidjson::Value src_json; |
| 720 | + src_json.SetString(vsrc.c_str(), |
| 721 | + static_cast<rapidjson::SizeType>(vsrc.length()), |
| 722 | + allocator); |
| 723 | + one_status.AddMember(rapidjson::StringRef(JSONKey::variable_source), |
| 724 | + src_json, allocator); |
| 725 | + sysvars_array.PushBack(one_status, allocator); |
| 726 | + } |
| 727 | + |
| 728 | + if (!sysvars_array.Empty()) { |
| 729 | + server_config_json.AddMember( |
| 730 | + rapidjson::StringRef(JSONKey::nondefault_allowlisted_sysvars), |
| 731 | + sysvars_array, allocator); |
| 732 | + } |
| 733 | + } |
| 734 | + } |
| 735 | + |
662 | 736 | document->AddMember(rapidjson::StringRef(JSONKey::server_config_info), |
663 | 737 | server_config_json, allocator); |
664 | 738 |
|
665 | 739 | return false; |
666 | 740 | } |
667 | 741 |
|
| 742 | +bool DataProvider::collect_server_status(rapidjson::Document *document) { |
| 743 | + if (!kStatusAllowlistCsv.empty()) { |
| 744 | + std::ostringstream oss; |
| 745 | + oss << "SELECT VARIABLE_NAME, VARIABLE_VALUE FROM " |
| 746 | + "performance_schema.global_status WHERE VARIABLE_NAME IN (" |
| 747 | + << kStatusAllowlistCsv << ')'; |
| 748 | + |
| 749 | + QueryResult rows; |
| 750 | + if (!do_query(oss.str(), &rows, nullptr, true)) { |
| 751 | + rapidjson::Document::AllocatorType &allocator = document->GetAllocator(); |
| 752 | + rapidjson::Value status_array(rapidjson::Type::kArrayType); |
| 753 | + for (const Row &row : rows) { |
| 754 | + if (row.size() < 2) { |
| 755 | + continue; |
| 756 | + } |
| 757 | + const std::string &vname = row[0]; |
| 758 | + const std::string &vval = row[1]; |
| 759 | + if (!is_value_safe_for_export(vval)) { |
| 760 | + continue; |
| 761 | + } |
| 762 | + rapidjson::Value one_status(rapidjson::Type::kObjectType); |
| 763 | + rapidjson::Value name_json; |
| 764 | + name_json.SetString(vname.c_str(), |
| 765 | + static_cast<rapidjson::SizeType>(vname.length()), |
| 766 | + allocator); |
| 767 | + one_status.AddMember(rapidjson::StringRef(JSONKey::name), name_json, |
| 768 | + allocator); |
| 769 | + rapidjson::Value val_json; |
| 770 | + val_json.SetString(vval.c_str(), |
| 771 | + static_cast<rapidjson::SizeType>(vval.length()), |
| 772 | + allocator); |
| 773 | + one_status.AddMember(rapidjson::StringRef(JSONKey::variable_value), |
| 774 | + val_json, allocator); |
| 775 | + status_array.PushBack(one_status, allocator); |
| 776 | + } |
| 777 | + |
| 778 | + if (!status_array.Empty()) { |
| 779 | + rapidjson::Value server_status_json(rapidjson::Type::kObjectType); |
| 780 | + server_status_json.AddMember( |
| 781 | + rapidjson::StringRef(JSONKey::allowlisted_global_status), |
| 782 | + status_array, allocator); |
| 783 | + document->AddMember(rapidjson::StringRef(JSONKey::server_status_info), |
| 784 | + server_status_json, allocator); |
| 785 | + } |
| 786 | + } |
| 787 | + } |
| 788 | + |
| 789 | + return false; |
| 790 | +} |
| 791 | + |
668 | 792 | bool DataProvider::collect_metrics(rapidjson::Document *document) { |
669 | 793 | /* The configuration of this instance might have changed, so we need to colect |
670 | 794 | it every time. */ |
@@ -692,6 +816,7 @@ bool DataProvider::collect_metrics(rapidjson::Document *document) { |
692 | 816 | res |= collect_group_replication_info(document); |
693 | 817 | res |= collect_async_replication_info(document); |
694 | 818 | res |= collect_server_config(document); |
| 819 | + res |= collect_server_status(document); |
695 | 820 |
|
696 | 821 | /* The requirement is to have db_replication_id key at the top of JSON |
697 | 822 | structure. But it may originate from the different places. The above |
|
0 commit comments