| #ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_AUTHZ_CONFIG_PARSER_H_ | |
| #define THIRD_PARTY_MILOTIC_EXTERNAL_CC_AUTHZ_CONFIG_PARSER_H_ | |
| #include <array> | |
| #include <cstdint> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <vector> | |
| #include "gmi/machine_identity.pb.h" | |
| #include "one/offline_node_entities.pb.h" | |
| #include "absl/base/thread_annotations.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/substitute.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include <nlohmann/json.hpp> | |
| #include "peer_identity.h" | |
| #include "authorized_entity.pb.h" | |
| namespace milotic::authz { | |
| using ::production_msv::node_entities_proto::OfflineNodeEntityInformation; | |
| using ::security_prodid::GoogleMachineIdentityProto; | |
| constexpr std::array<absl::string_view, 6> kDefaultRedfishPrivileges{ | |
| // go/keep-sorted start | |
| "ConfigureComponents", | |
| "ConfigureManager", | |
| "ConfigureSelf", | |
| "ConfigureUsers", | |
| "Login", | |
| "NoAuth" | |
| // go/keep-sorted end | |
| }; | |
| constexpr absl::string_view kHwopsRole = "insecure-hwops-state"; | |
| struct PeerLoasIdentity { | |
| std::string username; | |
| std::string DebugString() const { | |
| std::string res = "|username|='"; | |
| res += username; | |
| res += '\''; | |
| return res; | |
| } | |
| }; | |
| // Stores the configurations for the authorization server or the Redfish | |
| // authorization processor. | |
| // The class is thread-safe | |
| class AuthzConfiguration { | |
| public: | |
| struct SpiffeIdMatcher { | |
| std::string trust_domain_suffix; | |
| std::string path; | |
| std::string ca_context; | |
| std::optional<std::string> realm; // std::nullopt realm means any realm. | |
| std::string issuer; // TODO (b/274144469) issuer is not used for now. | |
| // Since SPIFFE is unique with the fields (path, ca_context, trust), not | |
| // using issuer or realm in operator<. | |
| // issuer is not used and realm may be null when creating matchers. | |
| bool operator<(const SpiffeIdMatcher& other) const { | |
| return std::tie(path, ca_context, trust_domain_suffix) < | |
| std::tie(other.path, other.ca_context, other.trust_domain_suffix); | |
| } | |
| bool operator==(const SpiffeIdMatcher& other) const { | |
| // Realm must exist for both matchers for realm checking | |
| if (realm.has_value() && other.realm.has_value()) { | |
| return std::tie(path, ca_context, trust_domain_suffix, realm) == | |
| std::tie(other.path, other.ca_context, other.trust_domain_suffix, | |
| other.realm); | |
| } | |
| return std::tie(path, ca_context, trust_domain_suffix) == | |
| std::tie(other.path, other.ca_context, other.trust_domain_suffix); | |
| } | |
| std::string DebugString() const { | |
| return absl::Substitute( | |
| "SpiffeIdMatcher is |trust_domain_suffix|=$0; |path|=$1; " | |
| "|ca_context|=$2; |realm|=$3; |issuer|=$4\n", | |
| trust_domain_suffix, path, ca_context, realm.value_or("ALL"), issuer); | |
| } | |
| }; | |
| struct SpiffeIdentityMatcher { | |
| SpiffeIdMatcher spiffe_id_matcher; | |
| std::optional<std::string> fqdn; | |
| bool operator<(const SpiffeIdentityMatcher& other) const { | |
| return std::tie(spiffe_id_matcher, fqdn) < | |
| std::tie(other.spiffe_id_matcher, other.fqdn); | |
| } | |
| bool operator==(const SpiffeIdentityMatcher& other) const { | |
| return std::tie(spiffe_id_matcher, fqdn) == | |
| std::tie(other.spiffe_id_matcher, other.fqdn); | |
| } | |
| std::string DebugString() const { | |
| return absl::Substitute( | |
| "SpiffeIdentityMatcher is |spiffe_id_matcher|=$0; |fqdn|=$1\n", | |
| spiffe_id_matcher.DebugString(), fqdn.value_or("nullopt")); | |
| } | |
| }; | |
| struct ResourceOwner { | |
| SpiffeIdentityMatcher identity_matcher; | |
| bool operator<(const ResourceOwner& other) const { | |
| return identity_matcher < other.identity_matcher; | |
| } | |
| bool operator==(const ResourceOwner& other) const { | |
| return identity_matcher == other.identity_matcher; | |
| } | |
| std::string DebugString() const { | |
| return absl::Substitute("ResourceOwner is |SpiffeIdentityMatcher|=$0\n", | |
| identity_matcher.DebugString()); | |
| } | |
| }; | |
| struct SpiffeUser { | |
| SpiffeIdentityMatcher identity_matcher; | |
| std::string redfish_role; | |
| bool operator<(const SpiffeUser& other) const { | |
| return identity_matcher < other.identity_matcher; | |
| } | |
| bool operator==(const SpiffeUser& other) const { | |
| return identity_matcher == other.identity_matcher; | |
| } | |
| bool operator!=(const SpiffeUser& other) const { return !(*this == other); } | |
| }; | |
| // This class is used to store information when looking up an entity tag in | |
| // Offline Node Entities. | |
| struct EntityTagIdentity { | |
| std::string hostname; | |
| std::string owner; | |
| bool operator==(const EntityTagIdentity& other) const = default; | |
| }; | |
| AuthzConfiguration() = default; | |
| explicit AuthzConfiguration(const nlohmann::json& config, | |
| absl::string_view offline_node_entities_path = "", | |
| absl::string_view gmi_path = ""); | |
| std::string GetPeerRedfishRole(const PeerSpiffeIdentity& peer) const; | |
| uint64_t GetSampleRateLimit(const std::string& peer_role) const; | |
| std::string GetAnycastAddress(const std::string& peer_role) const; | |
| bool IsPeerResourceOwner(const PeerSpiffeIdentity& peer) const; | |
| // Returns the Redfish privileges of the given user identified by SPIFFE. | |
| std::unordered_set<std::string> GetPeerRedfishPrivileges( | |
| const PeerSpiffeIdentity& peer) const; | |
| // Returns the Redfish privileges of the given user identified by LOAS name. | |
| std::unordered_set<std::string> GetPeerRedfishPrivileges( | |
| const PeerLoasIdentity& peer) const; | |
| std::unordered_set<std::string> GetRedfishPrivileges( | |
| const std::string& role) const { | |
| absl::MutexLock lock(mutex_); | |
| auto it = role_to_privileges_.find(role); | |
| if (it == role_to_privileges_.end()) { | |
| return {"NoAuth"}; | |
| } | |
| return it->second; | |
| } | |
| std::unordered_set<std::string> GetOemPrivileges() const; | |
| std::vector<std::string> GetDefaultRedfishPrivileges() const { | |
| return {kDefaultRedfishPrivileges.begin(), kDefaultRedfishPrivileges.end()}; | |
| } | |
| void ReloadConfig(const nlohmann::json& config, | |
| const nlohmann::json& platform_config); | |
| OfflineNodeEntityInformation GetOfflineNodeEntities() const { | |
| absl::MutexLock lock(mutex_); | |
| return offline_node_entities_; | |
| } | |
| absl::StatusOr<EntityTagIdentity> GetEntityTagIdentity( | |
| absl::string_view entity_tag) const; | |
| std::string GetMachineManagerEntityTag() const; | |
| // Returns all entity tags in which the NodeTypeInfo indicates that the | |
| // current ResolvedEntity in OfflineNodeEntities is a ComputeNode | |
| std::vector<std::string> GetAllComputeNodeEntityTags() const; | |
| // Returns all entity tags in which the NodeTypeInfo indicates that the | |
| // current ResolvedEntity in OfflineNodeEntities is an OffloadNode | |
| std::vector<std::string> GetAllOffloadNodeEntityTags() const; | |
| // Given a redfish system id, return a list of the entity tags with that | |
| // system id. If there are none, return empty list | |
| std::vector<std::string> GetEntityTagsFromRedfishSystemId( | |
| absl::string_view system_id) const; | |
| std::vector<std::string> GetAllEntityTagsFromAuthorizedEntity( | |
| AuthorizedEntity entity) const; | |
| bool IsHwopsState() const; | |
| static std::optional<AuthzConfiguration::SpiffeIdentityMatcher> | |
| CreateIdentityMatcherFromPeerSpiffeIdentity(const PeerSpiffeIdentity& peer); | |
| nlohmann::json GetConfig() const { | |
| absl::MutexLock lock(mutex_); | |
| return config_; | |
| } | |
| protected: | |
| bool IsResourceOwnerExisting(const ResourceOwner& resource_owner) const; | |
| absl::StatusOr<std::string> GetRedfishRoleOfSpiffeMatcher( | |
| const SpiffeUser& spiffe_user) const; | |
| // USED ONLY IN UNIT TESTS | |
| std::vector<ResourceOwner> GetResourceOwners() const { | |
| absl::MutexLock lock(mutex_); | |
| return resource_owners_; | |
| } | |
| // USED ONLY IN UNIT TESTS | |
| std::vector<SpiffeUser> GetSpiffeUsers() const { | |
| absl::MutexLock lock(mutex_); | |
| return spiffe_users_; | |
| } | |
| std::unordered_map<std::string, std::string> GetGroupToRoles() const { | |
| absl::MutexLock lock(mutex_); | |
| return group_to_role_; | |
| } | |
| // Returns the Redfish role of the given user identitfied by username. | |
| // Returns nullopt if the username is unknown. | |
| std::optional<std::string> GetRoleByUsername( | |
| const std::string& username) const { | |
| absl::MutexLock lock(mutex_); | |
| auto it = username_to_role_.find(username); | |
| if (it != username_to_role_.end()) { | |
| return it->second; | |
| } else { | |
| return std::nullopt; | |
| } | |
| } | |
| // Returns the Redfish role of the given user identitfied by group. | |
| // The implementation searches the first group that the user is in; if no | |
| // group is found, returns nullopt. | |
| std::optional<std::string> GetRoleByGroup( | |
| [[maybe_unused]] const std::string& username) const { | |
| return std::nullopt; | |
| } | |
| void ReloadResourceOwners(const nlohmann::json& config); | |
| void ReloadRedfishUsers(const nlohmann::json& config, | |
| const nlohmann::json& platform_config); | |
| void ReloadRoleToPrivileges(const nlohmann::json& config, | |
| const nlohmann::json& platform_config); | |
| void ReloadOfflineNodeEntities(); | |
| void ReloadGmi(); | |
| void ReloadSampleRateLimit(const nlohmann::json& config); | |
| void AddRedfishUsersInfoFromConfig( | |
| const nlohmann::json& config, | |
| std::vector<AuthzConfiguration::SpiffeUser>& spiffe_users, | |
| std::unordered_map<std::string, std::string>& username_to_role, | |
| std::unordered_map<std::string, std::string>& group_to_role); | |
| std::optional<AuthzConfiguration::SpiffeIdentityMatcher> | |
| ParseSpiffeIdentityMatcher(const nlohmann::json& config); | |
| std::vector<SpiffeIdentityMatcher> ParseSpiffeIdentityMatchersUsingOne( | |
| const nlohmann::json& config) const; | |
| // When the machine is in HWOPS, GMI reports the actual owner and | |
| // "in_hwops_state", while certificates use insecure-hwops-state as the role | |
| // rather than the actual owner. go/aristotle-ownership-change#owner | |
| absl::string_view GetCertNodeOwner(absl::string_view node_owner) const; | |
| std::string GetNodeRealm() const; | |
| private: | |
| mutable absl::Mutex mutex_; | |
| const std::string offline_node_entities_path_; | |
| const std::string gmi_path_; | |
| // We need to expose this exact config in Redfish to modify the Authz Config. | |
| // We could generate it from the other data structures, but that increases | |
| // complexity. It is easier to just use a little extra memory and expose it. | |
| nlohmann::json config_ ABSL_GUARDED_BY(mutex_); | |
| OfflineNodeEntityInformation offline_node_entities_ ABSL_GUARDED_BY(mutex_); | |
| GoogleMachineIdentityProto gmi_ ABSL_GUARDED_BY(mutex_); | |
| std::vector<ResourceOwner> resource_owners_ ABSL_GUARDED_BY(mutex_); | |
| std::vector<SpiffeUser> spiffe_users_ ABSL_GUARDED_BY(mutex_); | |
| std::unordered_map<std::string, std::string> username_to_role_ | |
| ABSL_GUARDED_BY(mutex_); | |
| std::unordered_map<std::string, std::string> group_to_role_ | |
| ABSL_GUARDED_BY(mutex_); | |
| std::unordered_map<std::string, std::unordered_set<std::string>> | |
| role_to_privileges_ ABSL_GUARDED_BY(mutex_); | |
| std::unordered_map<std::string, uint64_t> role_to_sample_rate_limit_ | |
| ABSL_GUARDED_BY(mutex_); | |
| std::unordered_map<std::string, std::string> role_to_anycast_address_ | |
| ABSL_GUARDED_BY(mutex_); | |
| }; | |
| } // namespace milotic::authz | |
| #endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_AUTHZ_CONFIG_PARSER_H_ |