| #include "oauth_utils.h" |
| |
| #include <exception> |
| #include <fstream> |
| #include <iterator> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| #include "absl/log/log.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_cat.h" |
| #include "redfish_v1.pb.h" |
| #include "zatar/x509_certificate.h" |
| #include "grpcpp/support/status.h" |
| #include "nlohmann/json.hpp" |
| #include "jwt-cpp/jwt.h" |
| #include "jwt-cpp/traits/nlohmann-json/defaults.h" |
| #include "config_parser.h" |
| |
| namespace milotic::authz { |
| |
| std::string GenerateOAuthSubject(const PeerSpiffeIdentity& peer_identity) { |
| std::string subject; |
| |
| absl::StrAppend(&subject, "URI:", peer_identity.spiffe_id); |
| if (peer_identity.fqdn) { |
| absl::StrAppend(&subject, ", ", "DNS:", *peer_identity.fqdn); |
| } |
| |
| return subject; |
| } |
| |
| std::string GetSubject(const std::string& certificate_path) { |
| std::ifstream file(certificate_path.c_str()); |
| if (!file.is_open()) { |
| LOG(WARNING) << "Certificate file at '" << certificate_path |
| << "' is missing."; |
| return ""; |
| } |
| std::string buffer = {std::istreambuf_iterator<char>(file), |
| std::istreambuf_iterator<char>()}; |
| |
| using ::ecclesia::GetSubjectAltName; |
| using ::ecclesia::SubjectAltName; |
| using ::milotic::authz::GenerateOAuthSubject; |
| using ::milotic::authz::PeerSpiffeIdentity; |
| auto san = GetSubjectAltName(buffer); |
| if (!san.ok() || !san->spiffe_id.has_value() || !san->fqdn.has_value()) { |
| LOG(WARNING) << "Certificate file at '" << certificate_path |
| << "' is illegal."; |
| LOG(WARNING) << "SAN is '" << san->DebugInfo(); |
| return ""; |
| } |
| PeerSpiffeIdentity identity = { |
| .spiffe_id = *san->spiffe_id, |
| .fqdn = *san->fqdn, |
| }; |
| return GenerateOAuthSubject(identity); |
| } |
| |
| std::optional<std::string> GetOAuthTokenFromRequest( |
| const ::redfish::v1::Request& request) { |
| // Both field name of the Authorization header and the token type are case |
| // insensitive |
| // References |
| // https://www.rfc-editor.org/rfc/rfc7230#section-3.2 |
| // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 |
| for (const auto& [key, value] : request.headers()) { |
| constexpr std::string_view kBearer = "Bearer "; |
| if (absl::EqualsIgnoreCase(key, "Authorization") && |
| absl::StartsWithIgnoreCase(value, kBearer)) { |
| return value.substr(kBearer.size()); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| grpc::Status VerifyAndExtractRoleFromToken(const std::string& token, |
| const std::string& public_key, |
| const std::string& expected_subject, |
| const std::string& expected_audience, |
| const std::string& expected_issuer, |
| std::string& redfish_role) { |
| try { |
| auto verifier = jwt::verify() |
| .allow_algorithm(jwt::algorithm::rs256(public_key)) |
| .with_subject(expected_subject) |
| .with_audience(expected_audience) |
| .with_issuer(expected_issuer); |
| auto decoded = jwt::decode(token); |
| verifier.verify(decoded); |
| |
| nlohmann::json scope = decoded.get_payload_claim("scope").to_json(); |
| const std::string* scope_str = scope.get_ptr<const std::string*>(); |
| if (scope_str == nullptr) { |
| return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "scope is not a string"); |
| } |
| constexpr std::string_view kRedfishRole = "Redfish.Role."; |
| if (!absl::StartsWith(*scope_str, kRedfishRole)) { |
| return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "scope doesn't start with \"Redfish.Role.\""); |
| } |
| redfish_role = scope_str->substr(kRedfishRole.size()); |
| return grpc::Status::OK; |
| } catch (const std::exception& e) { |
| // TODO(nanzhou) enumerate all the possible expceptions in jwt-cpp |
| LOG(WARNING) << "Token verification failed!"; |
| LOG(WARNING) << "Token is " << token; |
| LOG(WARNING) << "Public key is " << public_key; |
| LOG(WARNING) << "Expected subject is " << expected_subject |
| << "; Expected audience is " << expected_audience |
| << "; Expected issuer is " << expected_issuer; |
| return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); |
| } |
| } |
| |
| } // namespace milotic::authz |