| #ifndef THIRD_PARTY_MILOTIC_INTERNAL_CC_AUTHZ_BMCWEB_AUTHORIZER_SINGLETON_H_ | |
| #define THIRD_PARTY_MILOTIC_INTERNAL_CC_AUTHZ_BMCWEB_AUTHORIZER_SINGLETON_H_ | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <map> | |
| #include <string> | |
| #include <string_view> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "tlbmc/anycast_user.h" | |
| #include "absl/base/thread_annotations.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include "boost/beast/http/verb.hpp" // NOLINT | |
| #include "authorizer_enums.h" | |
| #include "redfish_v1.pb.h" | |
| #include <nlohmann/json.hpp> | |
| #include "config_parser.h" | |
| #include "redfish_authorizer.h" | |
| #include "redfish_authorizer_interface.h" | |
| #include "g3/grpc_headers.h" | |
| #include "zatar/certificate_metadata.h" | |
| namespace milotic::authz { | |
| // Performs dynamic authorization based on configuration files. | |
| // It encapsulates all states and operations that're needed for BMC | |
| // authorization. | |
| // This class is thread-safe. | |
| class BmcWebAuthorizerSingleton { | |
| public: | |
| struct RequestState { | |
| bool with_trust_bundle = true; | |
| bool peer_authenticated = false; | |
| std::unordered_set<std::string> peer_privileges; | |
| bool from_grpc = true; | |
| }; | |
| virtual ~BmcWebAuthorizerSingleton() = default; | |
| // Note: the first call will initialize the function local instance. All | |
| // future calls won't change the state of the singleton. | |
| // Typical usage: | |
| // // Initialize with arguments | |
| // BmcWebAuthorizerSingleton::Initialize(...); | |
| // // Reference without arguments | |
| // BmcWebAuthorizerSingleton::GetInstance().CheckTarget(...); | |
| static BmcWebAuthorizerSingleton& Initialize(const AuthorizerOptions& options, | |
| std::string_view oauth_key_path); | |
| static BmcWebAuthorizerSingleton& GetInstance(); | |
| // Checks if the peer's target matches with the BMC. | |
| // Shall be called in every RPC. | |
| grpc::Status CheckTarget( | |
| const grpc::AuthContext& context, | |
| const std::multimap<grpc::string_ref, grpc::string_ref>& client_metadata, | |
| const ::redfish::v1::Request& request) const; | |
| // Authorizes peer via its identity and the status of the request. | |
| // The identity of the peer can be derived from peer's X.509 SAN, or from | |
| // OAuth Token. | |
| // Returns OK status if the request is authorized. Otherwise returns | |
| // corresponding error. | |
| grpc::Status Authorize(std::string_view uri, boost::beast::http::verb verb, | |
| const RequestState& request_state) const; | |
| // Checks if the request to the given URI and HTTP verb is allowed from an | |
| // insecure transport (e.g., port 80 or insecure gRPC port 3995) when | |
| // restrict-redfish-routes-from-insecure-transport is enabled. | |
| bool IsAccessAllowedFromInsecureTransport( | |
| std::string_view uri, boost::beast::http::verb verb) const; | |
| // Processes the OAuth workflow and gets peer's privileges | |
| // - decodes the token | |
| // - verifies the signature of the token | |
| // - verifies the peer X.509 certificate matches the subject | |
| // - extract peer's privileges | |
| grpc::Status GetPrivilegesViaOAuth( | |
| const grpc::AuthContext& context, const std::string& token, | |
| std::unordered_set<std::string>& peer_privileges) const; | |
| // Looks up the peer's privileges by its MTls identity and the authorization | |
| // configuration | |
| grpc::Status GetPrivilegesViaMTls( | |
| const grpc::AuthContext& context, | |
| std::unordered_set<std::string>& peer_privileges) const; | |
| // Reloads the authorization configuration and all credentials (GMI, OAuth | |
| // key, etc). | |
| void ReloadConfiguration(); | |
| // Records a new subscription for the peer and returns if it is allowed. | |
| grpc::Status RecordNewSubscription(const PeerSpiffeIdentity& peer); | |
| // Records a new unsubscription for the peer. | |
| grpc::Status RecordNewUnsubscription(const PeerSpiffeIdentity& peer); | |
| static grpc::Status GetPeerIdentityFromAuthContext( | |
| const grpc::AuthContext& context, PeerSpiffeIdentity& peer_identity); | |
| // Returns the peer role from the auth context. | |
| // On error, peer_role will be empty. | |
| grpc::Status GetPeerRoleFromAuthContext(const grpc::AuthContext& context, | |
| std::string& peer_role) const; | |
| // Returns the sample rate limit for the peer role. | |
| uint64_t GetSampleRateLimit(const std::string& peer_role) const; | |
| // Returns feed client target address for the peer role. | |
| std::string GetAnycastAddress(const std::string& peer_role) const; | |
| // Returns the underlying Redfish authorizer through its nlohmann-free | |
| // decision-API interface. | |
| const RedfishAuthorizerInterface& GetRedfishAuthorizer() const { | |
| return redfish_authorizer_; | |
| } | |
| // Reloads the redfish authorizer with the given configuration path. | |
| void ReloadRedfishAuthorizer(const std::string& configuration_path); | |
| // Reloads the redfish authorizer with the current configuration path. | |
| void ReloadRedfishAuthorizer(); | |
| void RegisterAnycastUser( | |
| const std::string& service, | |
| platforms_syshealth::collection::feed::AnycastUser* user); | |
| void DeregisterAnycastUser(const std::string& service); | |
| static ecclesia::Operation BoostVerbToOperation( | |
| boost::beast::http::verb verb); | |
| ecclesia::ResourceEntity GetEntityTypeFromRedfishUri( | |
| std::string_view uri) const; | |
| size_t GetNodeIndexInPatternArray(std::string_view uri) const; | |
| // Get current Redfish Privilege Registry | |
| nlohmann::json GetRedfishPrivilegeRegistry() const; | |
| bool IsBasePrivilegeRegistryFound() const; | |
| void SetBasePrivilegesFolder(std::string_view base_privileges_folder); | |
| // Reset GMI path, used by unit test only. | |
| void ReloadGmiPaths(std::string_view gmi_path); | |
| BmcWebAuthorizerSingleton(const AuthorizerOptions& options, | |
| std::string_view oauth_key_path); | |
| nlohmann::json GetConfig() const; | |
| nlohmann::json GetPrivilegeRegistry() const; | |
| bool HasBmcDirectEthernet() const { | |
| absl::MutexLock lock(mutex_); | |
| return has_bmc_direct_ethernet_; | |
| } | |
| void SetHasBmcDirectEthernet(bool has_bmc_direct_ethernet) { | |
| absl::MutexLock lock(mutex_); | |
| has_bmc_direct_ethernet_ = has_bmc_direct_ethernet; | |
| } | |
| protected: | |
| std::string ReadPrimaryFqdnFromGmi() const; | |
| std::string ReadOauthKeyFromFile() const; | |
| void ReadOfflineNodeEntity(std::string_view primary_fqdn); | |
| virtual grpc::Status AuthorizeWithoutTrustBundle( | |
| std::string_view uri, boost::beast::http::verb verb) const; | |
| virtual grpc::Status RecoveryAuthorizeForBloom( | |
| std::string_view uri, boost::beast::http::verb verb) const; | |
| virtual grpc::Status AuthorizeWithTrustBundle( | |
| std::string_view uri, boost::beast::http::verb verb, | |
| const RequestState& request_state) const; | |
| std::string GetPrimaryFqdn() const { | |
| absl::MutexLock lock(mutex_); | |
| return primary_fqdn_; | |
| } | |
| std::string GetOauthKey() const { | |
| absl::MutexLock lock(mutex_); | |
| return oauth_key_; | |
| } | |
| std::vector<std::string> GetInterfaceFqdns() const { | |
| absl::MutexLock lock(mutex_); | |
| return interface_fqdns_; | |
| } | |
| void SetPrimaryFqdn(std::string_view fqdn) { | |
| absl::MutexLock lock(mutex_); | |
| primary_fqdn_ = fqdn; | |
| } | |
| void SetOauthKey(std::string_view oauth_key) { | |
| absl::MutexLock lock(mutex_); | |
| oauth_key_ = oauth_key; | |
| } | |
| void SetInterfaceFqdns(std::vector<std::string>&& fqdns) { | |
| absl::MutexLock lock(mutex_); | |
| interface_fqdns_ = std::move(fqdns); | |
| } | |
| bool tlbmc_trust_bundle_install_module_is_enabled_; | |
| private: | |
| mutable absl::Mutex mutex_; | |
| std::string gmi_path_; | |
| std::string oauth_key_path_; | |
| std::string offline_node_entity_path_; | |
| // |primary_fqdn_| is read from GMI file at |gmi_path_|; empty string means an | |
| // error happened, e.g., missing or corrupted GMI file | |
| std::string primary_fqdn_ ABSL_GUARDED_BY(mutex_); | |
| // |interface_fqdns_| is read from offline node entity file; including all | |
| // interfaces' domain name | |
| std::vector<std::string> interface_fqdns_ ABSL_GUARDED_BY(mutex_); | |
| // |has_bmc_direct_ethernet_| is read from offline node entity file; | |
| // indicating if current node has BMC_IR_DIRECT_ETHERNET interface role | |
| bool has_bmc_direct_ethernet_ ABSL_GUARDED_BY(mutex_) = false; | |
| // |oauth_key_| is read from key file at |oauth_key_path_|; empty string means | |
| // an error happened, e.g., missing or corrupted GMI file | |
| std::string oauth_key_ ABSL_GUARDED_BY(mutex_); | |
| RedfishAuthorizer redfish_authorizer_; | |
| ::milotic::redfish::CertificateMetadataParser metadata_parser_; | |
| }; | |
| } // namespace milotic::authz | |
| #endif // THIRD_PARTY_MILOTIC_INTERNAL_CC_AUTHZ_BMCWEB_AUTHORIZER_SINGLETON_H_ |