| #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 "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"; |
| |
| // Peer's identity. |
| // Defined in third_party/milotic/internal/auth_config/schema. |
| struct PeerSpiffeIdentity { |
| std::string spiffe_id; |
| std::optional<std::string> fqdn; |
| |
| std::string DebugString() const { |
| std::string res = "|spiffe_id|='"; |
| res += spiffe_id; |
| res += "'; |fqdn|='"; |
| res += fqdn.value_or(""); |
| res += '\''; |
| return res; |
| } |
| }; |
| |
| 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; |
| |
| 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; |
| |
| // 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); |
| |
| 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); |
| void ReloadRoleToPrivileges(const nlohmann::json& config, |
| const nlohmann::json& platform_config); |
| void ReloadOfflineNodeEntities(); |
| void ReloadGmi(); |
| void ReloadSampleRateLimit(const nlohmann::json& config); |
| |
| 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_; |
| 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_); |
| }; |
| |
| } // namespace milotic::authz |
| |
| #endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_AUTHZ_CONFIG_PARSER_H_ |