Line data Source code
1 : #include "remote_config.h"
2 :
3 : #include <cstdint>
4 : #include <type_traits>
5 : #include <unordered_set>
6 :
7 : #include "base64.h"
8 : #include "datadog/string_view.h"
9 : #include "json.hpp"
10 : #include "random.h"
11 : #include "version.h"
12 :
13 : using namespace nlohmann::literals;
14 :
15 : namespace datadog {
16 : namespace tracing {
17 : namespace {
18 :
19 : // The ".client.capabilities" field of the remote config request payload
20 : // describes which parts of the library's configuration are supported for remote
21 : // configuration.
22 : //
23 : // It's a bitset, 64 bits wide, where each bit indicates whether the library
24 : // supports a particular feature for remote configuration.
25 : //
26 : // The bitset is encoded in the request as a JSON array of 8 integers, where
27 : // each integer is one byte from the 64 bits. The bytes are in big-endian order
28 : // within the array.
29 : enum CapabilitiesFlag : uint64_t {
30 : APM_TRACING_SAMPLE_RATE = 1 << 12,
31 : };
32 :
33 : constexpr std::array<uint8_t, sizeof(uint64_t)> capabilities_byte_array(
34 : uint64_t in) {
35 : std::size_t j = sizeof(in) - 1;
36 : std::array<uint8_t, sizeof(uint64_t)> res{};
37 : for (std::size_t i = 0; i < sizeof(in); ++i) {
38 : res[j--] = in >> (i * 8);
39 : }
40 :
41 : return res;
42 : }
43 :
44 : constexpr std::array<uint8_t, sizeof(uint64_t)> k_apm_capabilities =
45 : capabilities_byte_array(APM_TRACING_SAMPLE_RATE);
46 :
47 : constexpr StringView k_apm_product = "APM_TRACING";
48 : constexpr StringView k_apm_product_path_substring = "/APM_TRACING/";
49 :
50 3 : ConfigUpdate parse_dynamic_config(const nlohmann::json& j) {
51 3 : ConfigUpdate config_update;
52 :
53 3 : if (auto sampling_rate_it = j.find("tracing_sampling_rate");
54 3 : sampling_rate_it != j.cend()) {
55 2 : TraceSamplerConfig trace_sampler_cfg;
56 2 : trace_sampler_cfg.sample_rate = *sampling_rate_it;
57 :
58 2 : config_update.trace_sampler = trace_sampler_cfg;
59 2 : }
60 :
61 3 : return config_update;
62 0 : }
63 :
64 : } // namespace
65 :
66 447 : RemoteConfigurationManager::RemoteConfigurationManager(
67 447 : const TracerSignature& tracer_signature, ConfigManager& config_manager)
68 447 : : tracer_signature_(tracer_signature),
69 447 : config_manager_(config_manager),
70 447 : client_id_(uuid()) {}
71 :
72 11 : bool RemoteConfigurationManager::is_new_config(
73 : StringView config_path, const nlohmann::json& config_meta) {
74 11 : auto it = applied_config_.find(std::string{config_path});
75 11 : if (it == applied_config_.cend()) return true;
76 :
77 1 : return it->second.hash !=
78 2 : config_meta.at("/hashes/sha256"_json_pointer).get<StringView>();
79 : }
80 :
81 14 : nlohmann::json RemoteConfigurationManager::make_request_payload() {
82 : // clang-format off
83 : auto j = nlohmann::json{
84 : {"client", {
85 14 : {"id", client_id_},
86 42 : {"products", nlohmann::json::array({k_apm_product})},
87 : {"is_tracer", true},
88 : {"capabilities", k_apm_capabilities},
89 : {"client_tracer", {
90 14 : {"runtime_id", tracer_signature_.runtime_id().string()},
91 14 : {"language", tracer_signature_.library_language()},
92 14 : {"tracer_version", tracer_signature_.library_version()},
93 14 : {"service", tracer_signature_.default_service()},
94 14 : {"env", tracer_signature_.default_environment()}
95 : }},
96 : {"state", {
97 0 : {"root_version", 1},
98 14 : {"targets_version", state_.targets_version},
99 14 : {"backend_client_state", state_.opaque_backend_state}
100 : }}
101 : }}
102 994 : };
103 : // clang-format on
104 :
105 14 : if (!applied_config_.empty()) {
106 0 : auto config_states = nlohmann::json::array();
107 0 : for (const auto& [_, config] : applied_config_) {
108 0 : config_states.emplace_back(nlohmann::json{{"id", config.id},
109 0 : {"version", config.version},
110 0 : {"product", k_apm_product}});
111 : }
112 :
113 0 : j["config_states"] = config_states;
114 0 : }
115 :
116 14 : if (state_.error_message) {
117 13 : j["has_error"] = true;
118 13 : j["error"] = *state_.error_message;
119 : }
120 :
121 14 : return j;
122 0 : }
123 :
124 19 : void RemoteConfigurationManager::process_response(const nlohmann::json& json) {
125 19 : state_.error_message = nullopt;
126 :
127 : try {
128 : const auto targets = nlohmann::json::parse(
129 41 : base64_decode(json.at("targets").get<StringView>()));
130 :
131 16 : state_.targets_version = targets.at("/signed/version"_json_pointer);
132 : state_.opaque_backend_state =
133 14 : targets.at("/signed/custom/opaque_backend_state"_json_pointer);
134 :
135 14 : const auto client_configs_it = json.find("client_configs");
136 :
137 : // `client_configs` is absent => remove previously applied configuration if
138 : // any applied.
139 14 : if (client_configs_it == json.cend()) {
140 1 : if (!applied_config_.empty()) {
141 1 : std::for_each(applied_config_.cbegin(), applied_config_.cend(),
142 1 : [this](const auto it) { revert_config(it.second); });
143 1 : applied_config_.clear();
144 : }
145 1 : return;
146 : }
147 :
148 : // Keep track of config path received to know which ones to revert.
149 13 : std::unordered_set<std::string> visited_config;
150 13 : visited_config.reserve(client_configs_it->size());
151 :
152 18 : for (const auto& client_config : *client_configs_it) {
153 13 : auto config_path = client_config.get<StringView>();
154 13 : visited_config.emplace(config_path);
155 :
156 : const auto& config_metadata =
157 15 : targets.at("/signed/targets"_json_pointer).at(config_path);
158 22 : if (!contains(config_path, k_apm_product_path_substring) ||
159 11 : !is_new_config(config_path, config_metadata)) {
160 2 : continue;
161 : }
162 :
163 12 : const auto& target_files = json.at("/target_files"_json_pointer);
164 : auto target_it = std::find_if(
165 11 : target_files.cbegin(), target_files.cend(),
166 9 : [&config_path](const nlohmann::json& j) {
167 9 : return j.at("/path"_json_pointer).get<StringView>() == config_path;
168 20 : });
169 :
170 9 : if (target_it == target_files.cend()) {
171 : state_.error_message =
172 1 : "Missing configuration from Remote Configuration response";
173 1 : return;
174 : }
175 :
176 : const auto config_json = nlohmann::json::parse(
177 18 : base64_decode(target_it.value().at("raw").get<StringView>()));
178 :
179 6 : const auto& targeted_service = config_json.at("service_target");
180 5 : if (targeted_service.at("service").get<StringView>() !=
181 9 : tracer_signature_.default_service() ||
182 4 : targeted_service.at("env").get<StringView>() !=
183 : tracer_signature_.default_environment()) {
184 2 : continue;
185 : }
186 :
187 3 : Configuration new_config;
188 3 : new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer);
189 3 : new_config.id = config_json.at("id");
190 3 : new_config.version = config_json.at("revision");
191 3 : new_config.content = parse_dynamic_config(config_json.at("lib_config"));
192 :
193 3 : apply_config(new_config);
194 3 : applied_config_[std::string{config_path}] = new_config;
195 6 : }
196 :
197 : // Applied configuration not present must be reverted.
198 8 : for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) {
199 3 : if (!visited_config.count(it->first)) {
200 0 : revert_config(it->second);
201 0 : it = applied_config_.erase(it);
202 : } else {
203 3 : it++;
204 : }
205 : }
206 35 : } catch (const nlohmann::json::exception& e) {
207 12 : std::string error_message = "Ill-formatted Remote Configuration response: ";
208 12 : error_message += e.what();
209 :
210 12 : state_.error_message = std::move(error_message);
211 12 : }
212 : }
213 :
214 3 : void RemoteConfigurationManager::apply_config(Configuration config) {
215 3 : config_manager_.update(config.content);
216 3 : }
217 :
218 1 : void RemoteConfigurationManager::revert_config(Configuration) {
219 1 : config_manager_.reset();
220 1 : }
221 :
222 : } // namespace tracing
223 : } // namespace datadog
|