Skip to content

Commit f153bb2

Browse files
authored
Merge pull request ceph#63047 from awojno-bloomberg/sts-fix
rgw: check all JWKS for STS Reviewed-by: Pritha Srivastava <[email protected]>
2 parents d0442e4 + 759fb7f commit f153bb2

File tree

3 files changed

+78
-56
lines changed

3 files changed

+78
-56
lines changed

src/common/options/rgw.yaml.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,17 @@ options:
12481248
see_also:
12491249
- rgw_keystone_verify_ssl
12501250
with_legacy: true
1251+
- name: rgw_enable_jwks_url_verification
1252+
type: bool
1253+
level: advanced
1254+
desc: Enable JWKS url verification for AWS compliance
1255+
long_desc:
1256+
Verifies the security of the JWKS url endpoint using the client provided thumbprints
1257+
for AWS compliance. If turned on, the legacy verification option of using thumbprints
1258+
to verify JWT x5c certs is disabled.
1259+
default: false
1260+
services:
1261+
- rgw
12511262
# The following are tunables for caches of RGW NFS (and other file
12521263
# client) objects.
12531264
#

src/rgw/rgw_rest_sts.cc

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ WebTokenEngine::connect_to_host_get_cert_chain(const DoutPrefixProvider* dpp, co
552552
return chain_pem;
553553
}
554554

555-
void
555+
bool
556556
WebTokenEngine::validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const
557557
{
558558
try {
@@ -571,18 +571,48 @@ WebTokenEngine::validate_signature_using_n_e(const DoutPrefixProvider* dpp, cons
571571
}
572572
} catch (const std::exception& e) {
573573
ldpp_dout(dpp, 10) << std::string("Signature validation using n, e failed: ") + e.what() << dendl;
574-
throw std::system_error(EACCES, std::system_category(), std::string("Signature validation using n, e failed: ") + e.what());
574+
return false;
575575
}
576576
ldpp_dout(dpp, 10) << "Verified signature using n and e"<< dendl;
577-
return;
577+
return true;
578+
}
579+
580+
bool WebTokenEngine::verify_oidc_thumbprint(const DoutPrefixProvider* dpp, const std::string& cert_url,
581+
const std::vector<std::string>& thumbprints) const
582+
{
583+
if (!cct->_conf.get_val<bool>("rgw_enable_jwks_url_verification")) {
584+
ldpp_dout(dpp, 5) << "Verification of JWKS endpoint is turned off." << dendl;
585+
return true;
586+
}
587+
588+
// Fetch and verify cert according to https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
589+
const auto hostname = get_top_level_domain_from_host(dpp, cert_url);
590+
ldpp_dout(dpp, 20) << "Validating hostname: " << hostname << dendl;
591+
const auto cert_chain = connect_to_host_get_cert_chain(dpp, hostname, 443);
592+
std::string cert;
593+
try {
594+
cert = extract_last_certificate(dpp, cert_chain);
595+
ldpp_dout(dpp, 20) << "last cert: " << cert << dendl;
596+
} catch(const std::exception& e) {
597+
ldpp_dout(dpp, 20) << "Extracting last cert of jwks uri failed with: " << e.what() << dendl;
598+
return false;
599+
}
600+
601+
if (!is_cert_valid(thumbprints, cert)) {
602+
ldpp_dout(dpp, 20) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
603+
return false;
604+
}
605+
606+
return true;
578607
}
579608

580609
void
581610
WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector<string>& thumbprints, optional_yield y) const
582611
{
583612
if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") {
584-
string cert_url = get_cert_url(iss, dpp, y);
585-
if (cert_url.empty()) {
613+
const auto cert_url = get_cert_url(iss, dpp, y);
614+
if (cert_url.empty() || !verify_oidc_thumbprint(dpp, cert_url, thumbprints)) {
615+
ldpp_dout(dpp, 5) << "Not able to validate JWKS url with registered thumbprints" << dendl;
586616
throw std::system_error(EINVAL, std::system_category());
587617
}
588618

@@ -609,33 +639,30 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec
609639
for (auto &key : keys) {
610640
JSONParser k_parser;
611641
vector<string> x5c;
612-
std::string use;
613-
bool skip{false};
642+
std::string use, kid;
614643
if (k_parser.parse(key.c_str(), key.size())) {
615-
if (JSONDecoder::decode_json("use", use, &k_parser)) {
616-
if (use == "enc") { //if key is for encryption then don't use x5c or n and e for signature validation
617-
skip = true;
618-
} else if (use == "sig") {
619-
skip = false;
620-
}
644+
if (JSONDecoder::decode_json("kid", kid, &k_parser)) {
645+
ldpp_dout(dpp, 20) << "Checking key id: " << kid << dendl;
621646
}
622-
if (JSONDecoder::decode_json("x5c", x5c, &k_parser)) {
623-
if (skip == true) {
647+
if (JSONDecoder::decode_json("use", use, &k_parser) && use != "sig") {
624648
continue;
625-
}
649+
}
650+
651+
if (JSONDecoder::decode_json("x5c", x5c, &k_parser)) {
626652
string cert;
627653
bool found_valid_cert = false;
654+
bool skip_thumbprint_verification = cct->_conf.get_val<bool>("rgw_enable_jwks_url_verification");
628655
for (auto& it : x5c) {
629656
cert = "-----BEGIN CERTIFICATE-----\n" + it + "\n-----END CERTIFICATE-----";
630657
ldpp_dout(dpp, 20) << "Certificate is: " << cert.c_str() << dendl;
631-
if (is_cert_valid(thumbprints, cert)) {
658+
if (skip_thumbprint_verification || is_cert_valid(thumbprints, cert)) {
632659
found_valid_cert = true;
633660
break;
634661
}
635662
}
636-
if (! found_valid_cert) {
637-
ldpp_dout(dpp, 0) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
638-
throw std::system_error(EINVAL, std::system_category());
663+
if (!found_valid_cert) {
664+
ldpp_dout(dpp, 10) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
665+
continue;
639666
}
640667
try {
641668
//verify method takes care of expired tokens also
@@ -694,53 +721,35 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec
694721
verifier.verify(decoded);
695722
return;
696723
} else {
697-
ldpp_dout(dpp, 0) << "Unsupported algorithm: " << algorithm << dendl;
698-
throw std::system_error(EINVAL, std::system_category());
724+
ldpp_dout(dpp, 5) << "Unsupported algorithm: " << algorithm << dendl;
699725
}
700726
}
701727
catch (const std::exception& e) {
702-
ldpp_dout(dpp, 0) << "Signature validation using x5c failed" << e.what() << dendl;
703-
throw std::system_error(EACCES, std::system_category());
728+
ldpp_dout(dpp, 10) << "Signature validation using x5c failed" << e.what() << dendl;
704729
}
705730
} else {
731+
// Try bare key validation
732+
ldpp_dout(dpp, 20) << "Trying bare key validation" << dendl;
733+
std::string kty;
734+
if (JSONDecoder::decode_json("kty", kty, &k_parser) && kty != "RSA") {
735+
ldpp_dout(dpp, 10) << "Only RSA bare key validation is currently supported" << dendl;
736+
continue;
737+
}
738+
706739
if (algorithm == "RS256" || algorithm == "RS384" || algorithm == "RS512") {
707-
string n, e; //modulus and exponent
740+
std::string n, e; //modulus and exponent
708741
if (JSONDecoder::decode_json("n", n, &k_parser) && JSONDecoder::decode_json("e", e, &k_parser)) {
709-
if (skip == true) {
710-
continue;
711-
}
712-
//Fetch and verify cert according to https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
713-
//and the same must be installed as part of create oidc provider in rgw
714-
//this can be made common to all types of keys(x5c, n&e), making thumbprint validation similar to
715-
//AWS
716-
std::string hostname = get_top_level_domain_from_host(dpp, cert_url);
717-
//connect to host and get back cert chain from it
718-
std::string cert_chain = connect_to_host_get_cert_chain(dpp, hostname, 443);
719-
std::string cert;
720-
try {
721-
cert = extract_last_certificate(dpp, cert_chain);
722-
ldpp_dout(dpp, 20) << "last cert: " << cert << dendl;
723-
} catch(const std::exception& e) {
724-
ldpp_dout(dpp, 20) << "Extracting last cert of jwks uri failed with: " << e.what() << dendl;
725-
throw std::system_error(EINVAL, std::system_category());
742+
if (validate_signature_using_n_e(dpp, decoded, algorithm, n, e)) {
743+
return;
726744
}
727-
if (!is_cert_valid(thumbprints, cert)) {
728-
ldpp_dout(dpp, 20) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
729-
throw std::system_error(EINVAL, std::system_category());
730-
}
731-
validate_signature_using_n_e(dpp, decoded, algorithm, n, e);
732-
return;
733745
}
734-
ldpp_dout(dpp, 0) << "x5c not present or n, e not present" << dendl;
735-
throw std::system_error(EINVAL, std::system_category());
736-
} else {
737-
throw std::system_error(EINVAL, std::system_category(), "Invalid algorithm: " + algorithm);
746+
ldpp_dout(dpp, 10) << "Bare key parameters (n&e) are not present for key" << dendl;
738747
}
739748
}
740-
ldpp_dout(dpp, 0) << "Signature can not be validated with the input given in keys: "<< dendl;
741-
throw std::system_error(EINVAL, std::system_category());
742749
} //end k_parser.parse
743-
}//end for iterate through keys
750+
} //end for iterate through keys
751+
ldpp_dout(dpp, 0) << "Signature can not be validated with the JWKS present." << dendl;
752+
throw std::system_error(EINVAL, std::system_category());
744753
} else { //end val->is_array
745754
ldpp_dout(dpp, 0) << "keys not present in JSON" << dendl;
746755
throw std::system_error(EINVAL, std::system_category());

src/rgw/rgw_rest_sts.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class WebTokenEngine : public rgw::auth::Engine {
5353
std::tuple<boost::optional<WebTokenEngine::token_t>, boost::optional<WebTokenEngine::principal_tags_t>>
5454
get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s, optional_yield y) const;
5555

56-
void validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const;
56+
bool validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const;
5757

5858
void validate_signature (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string& algorithm, const std::string& iss, const std::vector<std::string>& thumbprints, optional_yield y) const;
5959

@@ -69,6 +69,8 @@ class WebTokenEngine : public rgw::auth::Engine {
6969
std::string connect_to_host_get_cert_chain(const DoutPrefixProvider* dpp, const std::string& hostname, int port = 443) const;
7070
std::string get_top_level_domain_from_host(const DoutPrefixProvider* dpp, const std::string& hostname) const;
7171
std::string extract_last_certificate(const DoutPrefixProvider* dpp, const std::string& pem_chain) const;
72+
bool verify_oidc_thumbprint(const DoutPrefixProvider* dpp, const std::string& cert_url,
73+
const std::vector<std::string>& thumbprints) const;
7274
void shutdown_ssl(const DoutPrefixProvider* dpp, SSL* ssl, SSL_CTX* ctx) const;
7375

7476
public:

0 commit comments

Comments
 (0)