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::KeyPairPtr node_sign_kp;
24 ccf::crypto::Pem node_cert;
25 std::atomic_size_t attempts;
26
27 public:
29 size_t refresh_interval_s,
30 NetworkState& network,
31 const std::shared_ptr<ccf::kv::Consensus>& consensus,
32 const std::shared_ptr<ccf::RPCSessions>& rpcsessions,
33 const std::shared_ptr<ccf::RPCMap>& rpc_map,
34 const ccf::crypto::KeyPairPtr& node_sign_kp,
35 const ccf::crypto::Pem& node_cert) :
36 refresh_interval_s(refresh_interval_s),
37 network(network),
39 rpcsessions(rpcsessions),
40 rpc_map(rpc_map),
41 node_sign_kp(node_sign_kp),
42 node_cert(node_cert),
43 attempts(0)
44 {}
45
52
53 void start()
54 {
55 auto refresh_msg = std::make_unique<::threading::Tmsg<RefreshTimeMsg>>(
56 [](std::unique_ptr<::threading::Tmsg<RefreshTimeMsg>> msg) {
57 if (!msg->data.self.consensus->can_replicate())
58 {
60 "JWT key auto-refresh: Node is not primary, skipping");
61 }
62 else
63 {
64 msg->data.self.refresh_jwt_keys();
65 }
67 "JWT key auto-refresh: Scheduling in {}s",
68 msg->data.self.refresh_interval_s);
69 auto delay = std::chrono::seconds(msg->data.self.refresh_interval_s);
71 std::move(msg), delay);
72 },
73 *this);
74
76 "JWT key auto-refresh: Scheduling in {}s", refresh_interval_s);
77 auto delay = std::chrono::seconds(refresh_interval_s);
79 std::move(refresh_msg), delay);
80 }
81
83 {
84 auto refresh_msg = std::make_unique<::threading::Tmsg<RefreshTimeMsg>>(
85 [](std::unique_ptr<::threading::Tmsg<RefreshTimeMsg>> msg) {
86 if (!msg->data.self.consensus->can_replicate())
87 {
89 "JWT key one-off refresh: Node is not primary, skipping");
90 }
91 else
92 {
93 msg->data.self.refresh_jwt_keys();
94 }
95 },
96 *this);
97
98 LOG_DEBUG_FMT("JWT key one-off refresh: Scheduling without delay");
99 auto delay = std::chrono::seconds(0);
101 std::move(refresh_msg), delay);
102 }
103
104 template <typename T>
106 {
107 ::http::Request request(fmt::format(
108 "/{}/{}",
110 "jwt_keys/refresh"));
111 request.set_header(
112 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
113
114 auto body = nlohmann::json(msg).dump();
115 request.set_body(body);
116
117 auto packed = request.build_request();
118
119 auto node_session = std::make_shared<ccf::SessionContext>(
120 ccf::InvalidSessionId, node_cert.raw());
121 auto ctx = ccf::make_rpc_context(node_session, packed);
122
123 std::shared_ptr<ccf::RpcHandler> search =
124 ::http::fetch_rpc_handler(ctx, this->rpc_map);
125
126 search->process(ctx);
127 }
128
130 {
131 // A message that the endpoint fails to parse, leading to 500.
132 // This is done purely for exposing errors as endpoint metrics.
133 auto msg = false;
135 }
136
138 const std::string& issuer,
139 const std::optional<std::string>& issuer_constraint,
140 http_status status,
141 std::vector<uint8_t>&& data)
142 {
143 if (status != HTTP_STATUS_OK)
144 {
146 "JWT key auto-refresh: Error while requesting JWKS: {} {}{}",
147 status,
148 http_status_str(status),
149 data.empty() ?
150 "" :
151 fmt::format(" '{}'", std::string(data.begin(), data.end())));
153 return;
154 }
155
157 "JWT key auto-refresh: Received JWKS for issuer '{}'", issuer);
158
159 JsonWebKeySet jwks;
160 try
161 {
162 jwks = nlohmann::json::parse(data).get<JsonWebKeySet>();
163 }
164 catch (const std::exception& e)
165 {
167 "JWT key auto-refresh: Cannot parse JWKS for issuer '{}': {}",
168 issuer,
169 e.what());
171 return;
172 }
173
174 // call internal endpoint to update keys
175 auto msg = SetJwtPublicSigningKeys{issuer, jwks};
176
177 // For each key we leave the specified issuer constraint or set a common
178 // one otherwise (if present).
179 if (issuer_constraint.has_value())
180 {
181 for (auto& key : jwks.keys)
182 {
183 if (!key.issuer.has_value())
184 {
185 key.issuer = issuer_constraint;
186 }
187 }
188 }
189
191 }
192
194 const std::string& issuer,
195 std::shared_ptr<::tls::CA> ca,
196 http_status status,
197 std::vector<uint8_t>&& data)
198 {
199 if (status != HTTP_STATUS_OK)
200 {
202 "JWT key auto-refresh: Error while requesting OpenID metadata: {} "
203 "{}{}",
204 status,
205 http_status_str(status),
206 data.empty() ?
207 "" :
208 fmt::format(" '{}'", std::string(data.begin(), data.end())));
210 return;
211 }
212
214 "JWT key auto-refresh: Received OpenID metadata for issuer '{}'",
215 issuer);
216
217 std::string jwks_url_str;
218 nlohmann::json metadata;
219 try
220 {
221 metadata = nlohmann::json::parse(data);
222 jwks_url_str = metadata.at("jwks_uri").get<std::string>();
223 }
224 catch (const std::exception& e)
225 {
227 "JWT key auto-refresh: Cannot parse OpenID metadata for issuer '{}': "
228 "{}",
229 issuer,
230 e.what());
232 return;
233 }
234 ::http::URL jwks_url;
235 try
236 {
237 jwks_url = ::http::parse_url_full(jwks_url_str);
238 }
239 catch (const std::invalid_argument& e)
240 {
242 "JWT key auto-refresh: Cannot parse jwks_uri for issuer '{}': {}",
243 issuer,
244 jwks_url_str);
246 return;
247 }
248 auto jwks_url_port = !jwks_url.port.empty() ? jwks_url.port : "443";
249
250 auto ca_cert = std::make_shared<::tls::Cert>(
251 ca, std::nullopt, std::nullopt, jwks_url.host);
252
253 std::optional<std::string> issuer_constraint{std::nullopt};
254 const auto constraint = metadata.find("issuer");
255 if (constraint != metadata.end())
256 {
257 issuer_constraint = *constraint;
258 }
259
261 "JWT key auto-refresh: Requesting JWKS at https://{}:{}{}",
262 jwks_url.host,
263 jwks_url_port,
264 jwks_url.path);
265 auto http_client = rpcsessions->create_client(ca_cert);
266 // Note: Connection errors are not signalled and hence not tracked in
267 // endpoint metrics currently.
268 http_client->connect(
269 std::string(jwks_url.host),
270 std::string(jwks_url_port),
271 [this, issuer, issuer_constraint](
272 http_status status, http::HeaderMap&&, std::vector<uint8_t>&& data) {
273 handle_jwt_jwks_response(
274 issuer, issuer_constraint, status, std::move(data));
275 return true;
276 });
277 ::http::Request r(jwks_url.path, HTTP_GET);
278 r.set_header(ccf::http::headers::HOST, std::string(jwks_url.host));
279 http_client->send_request(std::move(r));
280 }
281
283 {
284 auto tx = network.tables->create_read_only_tx();
285 auto jwt_issuers = tx.ro(network.jwt_issuers);
286 auto ca_cert_bundles = tx.ro(network.ca_cert_bundles);
287 jwt_issuers->foreach([this, &ca_cert_bundles](
288 const JwtIssuer& issuer,
289 const JwtIssuerMetadata& metadata) {
290 if (!metadata.auto_refresh)
291 {
292 LOG_DEBUG_FMT(
293 "JWT key auto-refresh: Skipping issuer '{}', auto-refresh is "
294 "disabled",
295 issuer);
296 return true;
297 }
298
299 // Increment attempts, only when auto-refresh is enabled.
300 attempts++;
301
303 "JWT key auto-refresh: Refreshing keys for issuer '{}'", issuer);
304 auto& ca_cert_bundle_name = metadata.ca_cert_bundle_name.value();
305 auto ca_cert_bundle_pem = ca_cert_bundles->get(ca_cert_bundle_name);
306 if (!ca_cert_bundle_pem.has_value())
307 {
309 "JWT key auto-refresh: CA cert bundle with name '{}' for issuer "
310 "'{}' not "
311 "found",
312 ca_cert_bundle_name,
313 issuer);
315 return true;
316 }
317
318 auto metadata_url_str = issuer + "/.well-known/openid-configuration";
319 auto metadata_url = ::http::parse_url_full(metadata_url_str);
320 auto metadata_url_port =
321 !metadata_url.port.empty() ? metadata_url.port : "443";
322
323 auto ca_pems =
324 crypto::split_x509_cert_bundle(ca_cert_bundle_pem.value());
325 auto ca = std::make_shared<::tls::CA>(ca_pems);
326 auto ca_cert = std::make_shared<::tls::Cert>(
327 ca, std::nullopt, std::nullopt, metadata_url.host);
328
330 "JWT key auto-refresh: Requesting OpenID metadata at https://{}:{}{}",
331 metadata_url.host,
332 metadata_url_port,
333 metadata_url.path);
334 auto http_client = rpcsessions->create_client(ca_cert);
335 // Note: Connection errors are not signalled and hence not tracked in
336 // endpoint metrics currently.
337 http_client->connect(
338 std::string(metadata_url.host),
339 std::string(metadata_url_port),
340 [this, issuer, ca](
341 http_status status,
343 std::vector<uint8_t>&& data) {
344 handle_jwt_metadata_response(issuer, ca, status, std::move(data));
345 return true;
346 });
347 ::http::Request r(metadata_url.path, HTTP_GET);
348 r.set_header(ccf::http::headers::HOST, std::string(metadata_url.host));
349 http_client->send_request(std::move(r));
350 return true;
351 });
352 }
353
354 // Returns a copy of the current attempts
355 size_t get_attempts() const
356 {
357 return attempts.load();
358 }
359 };
360}
Definition jwt_key_auto_refresh.h:16
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, const ccf::crypto::KeyPairPtr &node_sign_kp, const ccf::crypto::Pem &node_cert)
Definition jwt_key_auto_refresh.h:28
void refresh_jwt_keys()
Definition jwt_key_auto_refresh.h:282
void handle_jwt_metadata_response(const std::string &issuer, std::shared_ptr<::tls::CA > ca, http_status status, std::vector< uint8_t > &&data)
Definition jwt_key_auto_refresh.h:193
void send_refresh_jwt_keys(T msg)
Definition jwt_key_auto_refresh.h:105
void start()
Definition jwt_key_auto_refresh.h:53
size_t get_attempts() const
Definition jwt_key_auto_refresh.h:355
void send_refresh_jwt_keys_error()
Definition jwt_key_auto_refresh.h:129
void handle_jwt_jwks_response(const std::string &issuer, const std::optional< std::string > &issuer_constraint, http_status status, std::vector< uint8_t > &&data)
Definition jwt_key_auto_refresh.h:137
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_body(const std::vector< uint8_t > *b)
Definition http_builder.h:74
void set_header(std::string k, const std::string &v)
Definition http_builder.h:45
Definition http_builder.h:106
std::vector< uint8_t > build_request(bool header_only=false) const
Definition http_builder.h:165
static ThreadMessaging & instance()
Definition thread_messaging.h:278
TaskQueue::TimerEntry add_task_after(std::unique_ptr< Tmsg< Payload > > msg, std::chrono::milliseconds ms)
Definition thread_messaging.h:320
llhttp_status http_status
Definition http_status.h:7
#define LOG_DEBUG_FMT
Definition logger.h:380
#define LOG_FAIL_FMT
Definition logger.h:396
std::vector< ccf::crypto::Pem > split_x509_cert_bundle(const std::string_view &pem)
Definition pem.cpp:36
std::shared_ptr< KeyPair > KeyPairPtr
Definition key_pair.h:145
std::map< std::string, std::string, std::less<> > HeaderMap
Definition http_header_map.h:10
Definition app_interface.h:15
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:31
std::string JwtIssuer
Definition jwt.h:57
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:375
Definition consensus_types.h:23
URL parse_url_full(const std::string &url)
Definition http_parser.h:145
Definition jwt.h:97
std::vector< ccf::crypto::JsonWebKey > keys
Definition jwt.h:98
Definition jwt.h:41
bool auto_refresh
Whether to auto-refresh keys from the issuer.
Definition jwt.h:49
std::optional< std::string > ca_cert_bundle_name
Optional CA bundle name used for authentication when auto-refreshing.
Definition jwt.h:47
Definition jwt_key_auto_refresh.h:47
JwtKeyAutoRefresh & self
Definition jwt_key_auto_refresh.h:50
RefreshTimeMsg(JwtKeyAutoRefresh &self_)
Definition jwt_key_auto_refresh.h:48
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:157
const CACertBundlePEMs ca_cert_bundles
Definition network_tables.h:156
Definition node_frontend.h:100
Definition http_parser.h:136
std::string host
Definition http_parser.h:138
std::string port
Definition http_parser.h:139
std::string path
Definition http_parser.h:140