CCF
Loading...
Searching...
No Matches
jwt_key_auto_refresh.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
6#include "http/http_builder.h"
9
10#define FMT_HEADER_ONLY
11#include <fmt/format.h>
12
13namespace ccf
14{
16 {
17 private:
18 size_t refresh_interval_s;
19 NetworkState& network;
20 std::shared_ptr<ccf::kv::Consensus> consensus;
21 std::shared_ptr<ccf::RPCSessions> rpcsessions;
22 std::shared_ptr<ccf::RPCMap> rpc_map;
23 ccf::crypto::ECKeyPairPtr node_sign_kp;
24 ccf::crypto::Pem node_cert;
25 std::atomic_size_t attempts;
26
27 ccf::tasks::Task periodic_refresh_task;
28
29 public:
31 size_t refresh_interval_s,
32 NetworkState& network,
33 const std::shared_ptr<ccf::kv::Consensus>& consensus,
34 const std::shared_ptr<ccf::RPCSessions>& rpcsessions,
35 const std::shared_ptr<ccf::RPCMap>& rpc_map,
36 ccf::crypto::ECKeyPairPtr node_sign_kp,
37 ccf::crypto::Pem node_cert) :
38 refresh_interval_s(refresh_interval_s),
39 network(network),
41 rpcsessions(rpcsessions),
42 rpc_map(rpc_map),
43 node_sign_kp(std::move(node_sign_kp)),
44 node_cert(std::move(node_cert)),
45 attempts(0)
46 {}
47
49 {
50 stop();
51 }
52
53 void start()
54 {
55 LOG_DEBUG_FMT("JWT key initial auto-refresh");
56 periodic_refresh_task = ccf::tasks::make_basic_task([this]() {
57 if (!this->consensus->can_replicate())
58 {
59 LOG_DEBUG_FMT("JWT key auto-refresh: Node is not primary, skipping");
60 }
61 else
62 {
63 this->refresh_jwt_keys();
64 }
65
67 "JWT key auto-refresh: Scheduling in {}s", this->refresh_interval_s);
68 });
69
70 const std::chrono::seconds period(refresh_interval_s);
71 ccf::tasks::add_periodic_task(periodic_refresh_task, period, period);
72 }
73
74 void stop()
75 {
76 if (periodic_refresh_task != nullptr)
77 {
78 periodic_refresh_task->cancel_task();
79 }
80 }
81
83 {
84 LOG_DEBUG_FMT("JWT key one-off refresh: Scheduling without delay");
86 if (!this->consensus->can_replicate())
87 {
88 LOG_DEBUG_FMT(
89 "JWT key one-off refresh: Node is not primary, skipping");
90 }
91 else
92 {
93 this->refresh_jwt_keys();
94 }
95 }));
96 }
97
98 template <typename T>
100 {
101 ::http::Request request(fmt::format(
102 "/{}/{}",
104 "jwt_keys/refresh"));
105 request.set_header(
106 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
107
108 auto body = nlohmann::json(msg).dump();
109 request.set_body(body);
110
111 auto packed = request.build_request();
112
113 auto node_session = std::make_shared<ccf::SessionContext>(
114 ccf::InvalidSessionId, node_cert.raw());
115 auto ctx = ccf::make_rpc_context(node_session, packed);
116
117 std::shared_ptr<ccf::RpcHandler> search =
118 ::http::fetch_rpc_handler(ctx, this->rpc_map);
119
120 search->process(ctx);
121 }
122
124 {
125 // A message that the endpoint fails to parse, leading to 500.
126 // This is done purely for exposing errors as endpoint metrics.
127 auto msg = false;
129 }
130
132 const std::string& issuer,
133 const std::optional<std::string>& issuer_constraint,
134 ccf::http_status status,
135 std::vector<uint8_t>&& data)
136 {
137 if (status != HTTP_STATUS_OK)
138 {
140 "JWT key auto-refresh: Error while requesting JWKS: {} {}{}",
141 status,
142 ccf::http_status_str(status),
143 data.empty() ?
144 "" :
145 fmt::format(" '{}'", std::string(data.begin(), data.end())));
147 return;
148 }
149
151 "JWT key auto-refresh: Received JWKS for issuer '{}'", issuer);
152
153 JsonWebKeySet jwks;
154 try
155 {
156 jwks = nlohmann::json::parse(data).get<JsonWebKeySet>();
157 }
158 catch (const std::exception& e)
159 {
161 "JWT key auto-refresh: Cannot parse JWKS for issuer '{}': {}",
162 issuer,
163 e.what());
165 return;
166 }
167
168 // call internal endpoint to update keys
169 auto msg = SetJwtPublicSigningKeys{issuer, jwks};
170
171 // For each key we leave the specified issuer constraint or set a common
172 // one otherwise (if present).
173 if (issuer_constraint.has_value())
174 {
175 for (auto& key : jwks.keys)
176 {
177 if (!key.issuer.has_value())
178 {
179 key.issuer = issuer_constraint;
180 }
181 }
182 }
183
185 }
186
188 const std::string& issuer,
189 std::shared_ptr<::tls::CA> ca,
190 ccf::http_status status,
191 std::vector<uint8_t>&& data)
192 {
193 if (status != HTTP_STATUS_OK)
194 {
196 "JWT key auto-refresh: Error while requesting OpenID metadata: {} "
197 "{}{}",
198 status,
199 ccf::http_status_str(status),
200 data.empty() ?
201 "" :
202 fmt::format(" '{}'", std::string(data.begin(), data.end())));
204 return;
205 }
206
208 "JWT key auto-refresh: Received OpenID metadata for issuer '{}'",
209 issuer);
210
211 std::string jwks_url_str;
212 nlohmann::json metadata;
213 try
214 {
215 metadata = nlohmann::json::parse(data);
216 jwks_url_str = metadata.at("jwks_uri").get<std::string>();
217 }
218 catch (const std::exception& e)
219 {
221 "JWT key auto-refresh: Cannot parse OpenID metadata for issuer '{}': "
222 "{}",
223 issuer,
224 e.what());
226 return;
227 }
228 ::http::URL jwks_url;
229 try
230 {
231 jwks_url = ::http::parse_url_full(jwks_url_str);
232 }
233 catch (const std::invalid_argument& e)
234 {
236 "JWT key auto-refresh: Cannot parse jwks_uri for issuer '{}': {}",
237 issuer,
238 jwks_url_str);
240 return;
241 }
242 auto jwks_url_port = !jwks_url.port.empty() ? jwks_url.port : "443";
243
244 auto ca_cert = std::make_shared<::tls::Cert>(
245 ca, std::nullopt, std::nullopt, jwks_url.host);
246
247 std::optional<std::string> issuer_constraint{std::nullopt};
248 const auto constraint = metadata.find("issuer");
249 if (constraint != metadata.end())
250 {
251 issuer_constraint = *constraint;
252 }
253
255 "JWT key auto-refresh: Requesting JWKS at https://{}:{}{}",
256 jwks_url.host,
257 jwks_url_port,
258 jwks_url.path);
259 auto http_client = rpcsessions->create_client(ca_cert);
260 // Note: Connection errors are not signalled and hence not tracked in
261 // endpoint metrics currently.
262 http_client->connect(
263 std::string(jwks_url.host),
264 std::string(jwks_url_port),
265 [this, issuer, issuer_constraint](
266 ccf::http_status status,
268 std::vector<uint8_t>&& data) {
269 handle_jwt_jwks_response(
270 issuer, issuer_constraint, status, std::move(data));
271 return true;
272 });
273 ::http::Request r(jwks_url.path, HTTP_GET);
274 r.set_header(ccf::http::headers::HOST, std::string(jwks_url.host));
275 http_client->send_request(std::move(r));
276 }
277
279 {
280 auto tx = network.tables->create_read_only_tx();
281 auto* jwt_issuers = tx.ro(network.jwt_issuers);
282 auto* ca_cert_bundles = tx.ro(network.ca_cert_bundles);
283 jwt_issuers->foreach([this, &ca_cert_bundles](
284 const JwtIssuer& issuer,
285 const JwtIssuerMetadata& metadata) {
286 if (!metadata.auto_refresh)
287 {
288 LOG_DEBUG_FMT(
289 "JWT key auto-refresh: Skipping issuer '{}', auto-refresh is "
290 "disabled",
291 issuer);
292 return true;
293 }
294
295 // Increment attempts, only when auto-refresh is enabled.
296 attempts++;
297
299 "JWT key auto-refresh: Refreshing keys for issuer '{}'", issuer);
300 const auto& ca_cert_bundle_name = metadata.ca_cert_bundle_name.value();
301 auto ca_cert_bundle_pem = ca_cert_bundles->get(ca_cert_bundle_name);
302 if (!ca_cert_bundle_pem.has_value())
303 {
305 "JWT key auto-refresh: CA cert bundle with name '{}' for issuer "
306 "'{}' not "
307 "found",
308 ca_cert_bundle_name,
309 issuer);
311 return true;
312 }
313
314 auto metadata_url_str = issuer + "/.well-known/openid-configuration";
315 auto metadata_url = ::http::parse_url_full(metadata_url_str);
316 auto metadata_url_port =
317 !metadata_url.port.empty() ? metadata_url.port : "443";
318
319 auto ca_pems =
320 crypto::split_x509_cert_bundle(ca_cert_bundle_pem.value());
321 auto ca = std::make_shared<::tls::CA>(ca_pems);
322 auto ca_cert = std::make_shared<::tls::Cert>(
323 ca, std::nullopt, std::nullopt, metadata_url.host);
324
326 "JWT key auto-refresh: Requesting OpenID metadata at https://{}:{}{}",
327 metadata_url.host,
328 metadata_url_port,
329 metadata_url.path);
330 auto http_client = rpcsessions->create_client(ca_cert);
331 // Note: Connection errors are not signalled and hence not tracked in
332 // endpoint metrics currently.
333 http_client->connect(
334 std::string(metadata_url.host),
335 std::string(metadata_url_port),
336 [this, issuer, ca](
337 ccf::http_status status,
339 std::vector<uint8_t>&& data) {
340 handle_jwt_metadata_response(issuer, ca, status, std::move(data));
341 return true;
342 });
343 ::http::Request r(metadata_url.path, HTTP_GET);
344 r.set_header(ccf::http::headers::HOST, std::string(metadata_url.host));
345 http_client->send_request(std::move(r));
346 return true;
347 });
348 }
349
350 // Returns a copy of the current attempts
351 [[nodiscard]] size_t get_attempts() const
352 {
353 return attempts.load();
354 }
355 };
356}
Definition jwt_key_auto_refresh.h:16
void refresh_jwt_keys()
Definition jwt_key_auto_refresh.h:278
~JwtKeyAutoRefresh()
Definition jwt_key_auto_refresh.h:48
void send_refresh_jwt_keys(T msg)
Definition jwt_key_auto_refresh.h:99
void start()
Definition jwt_key_auto_refresh.h:53
JwtKeyAutoRefresh(size_t refresh_interval_s, NetworkState &network, const std::shared_ptr< ccf::kv::Consensus > &consensus, const std::shared_ptr< ccf::RPCSessions > &rpcsessions, const std::shared_ptr< ccf::RPCMap > &rpc_map, ccf::crypto::ECKeyPairPtr node_sign_kp, ccf::crypto::Pem node_cert)
Definition jwt_key_auto_refresh.h:30
size_t get_attempts() const
Definition jwt_key_auto_refresh.h:351
void stop()
Definition jwt_key_auto_refresh.h:74
void handle_jwt_jwks_response(const std::string &issuer, const std::optional< std::string > &issuer_constraint, ccf::http_status status, std::vector< uint8_t > &&data)
Definition jwt_key_auto_refresh.h:131
void send_refresh_jwt_keys_error()
Definition jwt_key_auto_refresh.h:123
void handle_jwt_metadata_response(const std::string &issuer, std::shared_ptr<::tls::CA > ca, ccf::http_status status, std::vector< uint8_t > &&data)
Definition jwt_key_auto_refresh.h:187
void schedule_once()
Definition jwt_key_auto_refresh.h:82
Definition pem.h:18
std::vector< uint8_t > raw() const
Definition pem.h:71
void set_header(std::string k, const std::string &v)
Definition http_builder.h:45
void set_body(const std::vector< uint8_t > *b, bool overwrite_content_length=true)
Definition http_builder.h:72
Definition http_builder.h:117
std::vector< uint8_t > build_request(bool header_only=false) const
Definition http_builder.h:175
#define LOG_DEBUG_FMT
Definition internal_logger.h:14
#define LOG_FAIL_FMT
Definition internal_logger.h:16
std::vector< ccf::crypto::Pem > split_x509_cert_bundle(const std::string_view &pem)
Definition pem.cpp:37
std::shared_ptr< ECKeyPair > ECKeyPairPtr
Definition ec_key_pair.h:144
std::map< std::string, std::string, std::less<> > HeaderMap
Definition http_header_map.h:10
Task make_basic_task(Ts &&... ts)
Definition basic_task.h:33
void add_periodic_task(Task task, std::chrono::milliseconds initial_delay, std::chrono::milliseconds repeat_period)
Definition task_system.cpp:75
void add_task(Task task)
Definition task_system.cpp:65
std::shared_ptr< BaseTask > Task
Definition task.h:36
Definition app_interface.h:14
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:24
std::string JwtIssuer
Definition jwt.h:35
llhttp_status http_status
Definition http_status.h:9
std::shared_ptr<::http::HttpRpcContext > make_rpc_context(std::shared_ptr< ccf::SessionContext > s, const std::vector< uint8_t > &packed)
Definition http_rpc_context.h:374
Definition consensus_types.h:23
URL parse_url_full(const std::string &url)
Definition http_parser.h:151
STL namespace.
Definition jwt.h:77
std::vector< ccf::crypto::JsonWebKeyData > keys
Definition jwt.h:78
Definition jwt.h:23
bool auto_refresh
Whether to auto-refresh keys from the issuer.
Definition jwt.h:27
std::optional< std::string > ca_cert_bundle_name
Optional CA bundle name used for authentication when auto-refreshing.
Definition jwt.h:25
Definition network_state.h:12
std::shared_ptr< ccf::kv::Store > tables
Definition network_tables.h:46
const JwtIssuers jwt_issuers
Definition network_tables.h:164
const CACertBundlePEMs ca_cert_bundles
Definition network_tables.h:163
Definition node_frontend.h:120
Definition http_parser.h:142
std::string host
Definition http_parser.h:144
std::string port
Definition http_parser.h:145
std::string path
Definition http_parser.h:146