| #include "config_parser.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <fstream> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmi/machine_identity.pb.h" | |
| #include "one/node_entities_api.pb.h" | |
| #include "one/offline_node_entities.pb.h" | |
| #include "one/resolved_entities.pb.h" | |
| #include "one/public_offline_node_entities.h" | |
| #include "absl/log/log.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/ascii.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include <nlohmann/json.hpp> | |
| #include <nlohmann/json_fwd.hpp> | |
| #include "json_utils.h" | |
| #include "authorized_entity.pb.h" | |
| #include "zatar/certificate_metadata.h" | |
| namespace milotic::authz { | |
| using ::milotic::redfish::CertificateMetadataParser; | |
| using ::production_msv::node_entities::ReadOfflineNodeEntityInformation; | |
| using ::production_msv::node_entities_proto::OfflineNodeEntityInformation; | |
| using ::security_prodid::GoogleMachineIdentityProto; | |
| using EntityTagIdentity = AuthzConfiguration::EntityTagIdentity; | |
| AuthzConfiguration::AuthzConfiguration( | |
| const nlohmann::json& config, absl::string_view offline_node_entities_path, | |
| absl::string_view gmi_path) | |
| : offline_node_entities_path_(offline_node_entities_path), | |
| gmi_path_(gmi_path) { | |
| ReloadConfig(config, nlohmann::json()); | |
| }; | |
| std::optional<AuthzConfiguration::SpiffeIdentityMatcher> | |
| AuthzConfiguration::CreateIdentityMatcherFromPeerSpiffeIdentity( | |
| const PeerSpiffeIdentity& peer) { | |
| AuthzConfiguration::SpiffeIdentityMatcher matcher; | |
| matcher.fqdn = peer.fqdn; | |
| const std::string spiffe_id = absl::AsciiStrToLower(peer.spiffe_id); | |
| // As per SPIFFE specification, SPIFFE schema and trust domain are case | |
| // insensitive. | |
| // https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#24-spiffe-id-parsing | |
| constexpr std::string_view kSpiffeSchema = "spiffe://"; | |
| if (!absl::StartsWithIgnoreCase(spiffe_id, kSpiffeSchema)) { | |
| return std::nullopt; | |
| } | |
| std::size_t path_start = spiffe_id.find_first_of('/', kSpiffeSchema.size()); | |
| if (path_start == std::string_view::npos) { | |
| return std::nullopt; | |
| } | |
| matcher.spiffe_id_matcher.path = spiffe_id.substr(path_start); | |
| const std::string trust_domain = absl::AsciiStrToLower(spiffe_id.substr( | |
| kSpiffeSchema.size(), path_start - kSpiffeSchema.size())); | |
| // Trust domain consists of (issuing-role).(context).(realm).(suffix) | |
| // See go/zatar-prod-certificates for details. | |
| std::vector<std::string_view> trust_domain_pieces = | |
| absl::StrSplit(trust_domain, '.'); | |
| // There should be at least 4 elements in this array for a property trust | |
| // domain | |
| if (trust_domain_pieces.size() < 4) { | |
| return std::nullopt; | |
| } | |
| matcher.spiffe_id_matcher.issuer = trust_domain_pieces[0]; | |
| matcher.spiffe_id_matcher.ca_context = trust_domain_pieces[1]; | |
| matcher.spiffe_id_matcher.realm = trust_domain_pieces[2]; | |
| matcher.spiffe_id_matcher.trust_domain_suffix = | |
| absl::StrCat(".", absl::StrJoin(trust_domain_pieces.begin() + 3, | |
| trust_domain_pieces.end(), ".")); | |
| return matcher; | |
| } | |
| std::optional<AuthzConfiguration::SpiffeIdentityMatcher> | |
| AuthzConfiguration::ParseSpiffeIdentityMatcher(const nlohmann::json& config) { | |
| const nlohmann::json* spiffe_id_matcher = | |
| GetValueAsJson(config, "spiffe_id_matcher"); | |
| if (spiffe_id_matcher == nullptr) { | |
| return std::nullopt; | |
| } | |
| const std::string* trust_domain_suffix = | |
| GetValueAsString(*spiffe_id_matcher, "trust_domain_suffix"); | |
| if (trust_domain_suffix == nullptr) { | |
| LOG(WARNING) << "Warning: trust_domain_suffix isn't a string; " | |
| << "|spiffe_id_matcher|=" << spiffe_id_matcher->dump(2); | |
| return std::nullopt; | |
| } | |
| AuthzConfiguration::SpiffeIdentityMatcher matcher; | |
| matcher.spiffe_id_matcher.trust_domain_suffix = | |
| absl::AsciiStrToLower(*trust_domain_suffix); | |
| const std::string* path = GetValueAsString(*spiffe_id_matcher, "path"); | |
| if (path == nullptr) { | |
| return std::nullopt; | |
| } | |
| matcher.spiffe_id_matcher.path = absl::AsciiStrToLower(*path); | |
| const std::string* ca_context = | |
| GetValueAsString(*spiffe_id_matcher, "ca_context"); | |
| if (ca_context == nullptr) { | |
| return std::nullopt; | |
| } | |
| matcher.spiffe_id_matcher.ca_context = absl::AsciiStrToLower(*ca_context); | |
| const std::string* issuer = GetValueAsString(*spiffe_id_matcher, "issuer"); | |
| if (issuer != nullptr) { | |
| matcher.spiffe_id_matcher.issuer = absl::AsciiStrToLower(*issuer); | |
| } | |
| // If realm_check is enabled, get current realm from GMI | |
| const bool* realm_check = GetValueAsBool(*spiffe_id_matcher, "realm_check"); | |
| if (realm_check != nullptr && *realm_check) { | |
| matcher.spiffe_id_matcher.realm = GetNodeRealm(); | |
| } | |
| const std::string* fqdn = GetValueAsString(config, "fqdn"); | |
| // Non-string value | |
| if (fqdn == nullptr && config.find("fqdn") != config.end()) { | |
| return std::nullopt; | |
| } | |
| if (fqdn != nullptr) { | |
| matcher.fqdn = *fqdn; | |
| } | |
| return matcher; | |
| } | |
| bool AuthzConfiguration::IsResourceOwnerExisting( | |
| const ResourceOwner& resource_owner) const { | |
| absl::MutexLock lock(mutex_); | |
| return std::binary_search(resource_owners_.begin(), resource_owners_.end(), | |
| resource_owner); | |
| } | |
| bool AuthzConfiguration::IsPeerResourceOwner( | |
| const PeerSpiffeIdentity& peer) const { | |
| std::optional<SpiffeIdentityMatcher> peer_matcher = | |
| CreateIdentityMatcherFromPeerSpiffeIdentity(peer); | |
| if (peer_matcher == std::nullopt) { | |
| LOG(WARNING) << "Peer spiffe id" << peer.spiffe_id << "is malformed"; | |
| return false; | |
| } | |
| ResourceOwner peer_to_resource_owner{.identity_matcher = *peer_matcher}; | |
| return IsResourceOwnerExisting(peer_to_resource_owner); | |
| } | |
| absl::StatusOr<std::string> AuthzConfiguration::GetRedfishRoleOfSpiffeMatcher( | |
| const SpiffeUser& spiffe_user) const { | |
| absl::MutexLock lock(mutex_); | |
| auto it = | |
| std::lower_bound(spiffe_users_.begin(), spiffe_users_.end(), spiffe_user); | |
| // Comparing equality | |
| if (it == spiffe_users_.end() || *it != spiffe_user) { | |
| return absl::NotFoundError( | |
| absl::StrCat("No user with matching spiffe; spiffe_user is ", | |
| spiffe_user.identity_matcher.DebugString())); | |
| } | |
| return it->redfish_role; | |
| } | |
| std::string AuthzConfiguration::GetPeerRedfishRole( | |
| const PeerSpiffeIdentity& peer) const { | |
| std::optional<SpiffeIdentityMatcher> peer_matcher = | |
| CreateIdentityMatcherFromPeerSpiffeIdentity(peer); | |
| if (peer_matcher == std::nullopt) { | |
| LOG(WARNING) << "Peer spiffe id" << peer.spiffe_id << "is malformed"; | |
| return ""; | |
| } | |
| SpiffeUser peer_to_spiffe_user{.identity_matcher = *peer_matcher, | |
| .redfish_role = ""}; | |
| absl::StatusOr<std::string> role = | |
| GetRedfishRoleOfSpiffeMatcher(peer_to_spiffe_user); | |
| if (!role.ok()) { | |
| LOG(WARNING) << role.status(); | |
| return ""; | |
| } | |
| return *role; | |
| } | |
| uint64_t AuthzConfiguration::GetSampleRateLimit( | |
| const std::string& peer_role) const { | |
| absl::MutexLock lock(mutex_); | |
| auto it = role_to_sample_rate_limit_.find(peer_role); | |
| if (it != role_to_sample_rate_limit_.end()) { | |
| return it->second; | |
| } | |
| return 0; | |
| } | |
| std::string AuthzConfiguration::GetAnycastAddress( | |
| const std::string& peer_role) const { | |
| absl::MutexLock lock(mutex_); | |
| auto it = role_to_anycast_address_.find(peer_role); | |
| if (it != role_to_anycast_address_.end()) { | |
| return it->second; | |
| } | |
| return ""; | |
| } | |
| std::unordered_set<std::string> AuthzConfiguration::GetPeerRedfishPrivileges( | |
| const PeerSpiffeIdentity& peer) const { | |
| std::optional<SpiffeIdentityMatcher> peer_matcher = | |
| CreateIdentityMatcherFromPeerSpiffeIdentity(peer); | |
| if (peer_matcher == std::nullopt) { | |
| LOG(WARNING) << "Peer spiffe id" << peer.spiffe_id << "is malformed"; | |
| return {"NoAuth"}; | |
| } | |
| SpiffeUser peer_to_spiffe_user{.identity_matcher = *peer_matcher, | |
| .redfish_role = ""}; | |
| absl::StatusOr<std::string> role = | |
| GetRedfishRoleOfSpiffeMatcher(peer_to_spiffe_user); | |
| if (!role.ok()) { | |
| LOG(WARNING) << role.status(); | |
| return {"NoAuth"}; | |
| } | |
| return GetRedfishPrivileges(*role); | |
| } | |
| std::unordered_set<std::string> AuthzConfiguration::GetPeerRedfishPrivileges( | |
| const PeerLoasIdentity& peer) const { | |
| std::optional<std::string> role = GetRoleByUsername(peer.username); | |
| if (role) { | |
| return GetRedfishPrivileges(*role); | |
| } | |
| role = GetRoleByGroup(peer.username); | |
| if (role) { | |
| return GetRedfishPrivileges(*role); | |
| } | |
| return {"NoAuth"}; | |
| } | |
| absl::string_view AuthzConfiguration::GetCertNodeOwner( | |
| absl::string_view node_owner) const { | |
| if (IsHwopsState()) { | |
| return kHwopsRole; | |
| } | |
| return node_owner; | |
| } | |
| std::vector<AuthzConfiguration::SpiffeIdentityMatcher> | |
| AuthzConfiguration::ParseSpiffeIdentityMatchersUsingOne( | |
| const nlohmann::json& config) const { | |
| const std::string* entity = GetValueAsString(config, "entity"); | |
| if (entity == nullptr) { | |
| return {}; | |
| } | |
| AuthorizedEntity entity_enum; | |
| if (!AuthorizedEntity_Parse(*entity, &entity_enum)) { | |
| LOG(WARNING) << "Warning: entity " << *entity | |
| << " is not a valid AuthorizedEntity"; | |
| return {}; | |
| }; | |
| std::vector<AuthzConfiguration::SpiffeIdentityMatcher> matchers; | |
| for (const std::string& entity_tag : | |
| GetAllEntityTagsFromAuthorizedEntity(entity_enum)) { | |
| AuthzConfiguration::SpiffeIdentityMatcher matcher; | |
| absl::StatusOr<AuthzConfiguration::EntityTagIdentity> entity_tag_identity = | |
| GetEntityTagIdentity(entity_tag); | |
| if (!entity_tag_identity.ok()) { | |
| continue; | |
| } | |
| matcher.fqdn = | |
| absl::StrCat(entity_tag_identity->hostname, ".prod.google.com"); | |
| matcher.spiffe_id_matcher.trust_domain_suffix = ".prod.google.com"; | |
| matcher.spiffe_id_matcher.ca_context = | |
| CertificateMetadataParser::kCaContextNode; | |
| matcher.spiffe_id_matcher.path = | |
| absl::StrCat("/role/", GetCertNodeOwner(entity_tag_identity->owner)); | |
| matchers.push_back(std::move(matcher)); | |
| } | |
| return matchers; | |
| } | |
| void AuthzConfiguration::ReloadSampleRateLimit(const nlohmann::json& config) { | |
| if (config.is_discarded()) { | |
| return; | |
| } | |
| const nlohmann::json::array_t* role_sample_rate_limits = | |
| GetValueAsArray(config, "role_sample_rate_limits"); | |
| if (role_sample_rate_limits == nullptr) { | |
| return; | |
| } | |
| std::unordered_map<std::string, std::string> new_role_to_anycast_address; | |
| std::unordered_map<std::string, uint64_t> new_role_to_sample_rate_limit; | |
| for (const nlohmann::json& role_sample_rate_limit : | |
| *role_sample_rate_limits) { | |
| const std::string* role = | |
| GetValueAsString(role_sample_rate_limit, "redfish_role"); | |
| if (role == nullptr) { | |
| continue; | |
| } | |
| const uint64_t* sample_rate_limit = | |
| GetValueAsUint(role_sample_rate_limit, "sample_rate_limit"); | |
| if (sample_rate_limit == nullptr) { | |
| continue; | |
| } | |
| const std::string* anycast_address = | |
| GetValueAsString(role_sample_rate_limit, "anycast_address"); | |
| if (anycast_address != nullptr) { | |
| new_role_to_anycast_address[*role] = *anycast_address; | |
| } | |
| new_role_to_sample_rate_limit[*role] = *sample_rate_limit; | |
| } | |
| absl::MutexLock lock(mutex_); | |
| role_to_sample_rate_limit_ = std::move(new_role_to_sample_rate_limit); | |
| role_to_anycast_address_ = std::move(new_role_to_anycast_address); | |
| } | |
| void AuthzConfiguration::ReloadResourceOwners(const nlohmann::json& config) { | |
| if (config.is_discarded()) { | |
| return; | |
| } | |
| const nlohmann::json::array_t* resource_owners = | |
| GetValueAsArray(config, "resource_owners"); | |
| if (resource_owners == nullptr) { | |
| return; | |
| } | |
| std::vector<ResourceOwner> new_resource_owners; | |
| for (const nlohmann::json& resource_owner : *resource_owners) { | |
| // If the spiffe matcher is explicitly defined, parse it. | |
| if (resource_owner.contains("spiffe_id_matcher")) { | |
| ResourceOwner owner; | |
| std::optional<SpiffeIdentityMatcher> matcher = | |
| ParseSpiffeIdentityMatcher(resource_owner); | |
| if (matcher) { | |
| owner.identity_matcher = *matcher; | |
| new_resource_owners.push_back(std::move(owner)); | |
| } | |
| continue; | |
| } | |
| // Otherwise continue with entity based parsing | |
| std::vector<SpiffeIdentityMatcher> matchers = | |
| ParseSpiffeIdentityMatchersUsingOne(resource_owner); | |
| for (const SpiffeIdentityMatcher& matcher : matchers) { | |
| ResourceOwner owner; | |
| owner.identity_matcher = matcher; | |
| new_resource_owners.push_back(std::move(owner)); | |
| } | |
| } | |
| std::sort(new_resource_owners.begin(), new_resource_owners.end()); | |
| new_resource_owners.erase( | |
| std::unique(new_resource_owners.begin(), new_resource_owners.end()), | |
| new_resource_owners.end()); | |
| absl::MutexLock lock(mutex_); | |
| resource_owners_ = std::move(new_resource_owners); | |
| } | |
| void AuthzConfiguration::AddRedfishUsersInfoFromConfig( | |
| const nlohmann::json& config, std::vector<SpiffeUser>& spiffe_users, | |
| std::unordered_map<std::string, std::string>& username_to_role, | |
| std::unordered_map<std::string, std::string>& group_to_role) { | |
| if (config.is_discarded()) { | |
| return; | |
| } | |
| const nlohmann::json::array_t* exchange_mappings = | |
| GetValueAsArray(config, "exchange_mappings"); | |
| if (exchange_mappings == nullptr) { | |
| return; | |
| } | |
| for (const nlohmann::json& exchange_mapping : *exchange_mappings) { | |
| const std::string* redfish_role = | |
| GetValueAsString(exchange_mapping, "redfish_role"); | |
| if (redfish_role == nullptr) { | |
| continue; | |
| } | |
| // If peer is explicitly defined, then create exchange mapping with peer. | |
| if (exchange_mapping.contains("peer")) { | |
| const nlohmann::json* peer = GetValueAsJson(exchange_mapping, "peer"); | |
| if (peer == nullptr) { | |
| continue; | |
| } | |
| // If it is username based, parse the username and continue | |
| const std::string* username = GetValueAsString(*peer, "username"); | |
| if (username != nullptr) { | |
| username_to_role[*username] = *redfish_role; | |
| continue; | |
| } | |
| // If it is group based, parse the group and continue | |
| const std::string* group = GetValueAsString(*peer, "group"); | |
| if (group != nullptr) { | |
| group_to_role[*group] = *redfish_role; | |
| continue; | |
| } | |
| // Otherwise, it is spiffe based | |
| SpiffeUser user; | |
| std::optional<SpiffeIdentityMatcher> matcher = | |
| ParseSpiffeIdentityMatcher(*peer); | |
| if (!matcher) { | |
| continue; | |
| } | |
| user.identity_matcher = *matcher; | |
| user.redfish_role = *redfish_role; | |
| spiffe_users.push_back(std::move(user)); | |
| continue; | |
| } | |
| // Otherwise, continue with entity based parsing | |
| std::vector<SpiffeIdentityMatcher> matchers = | |
| ParseSpiffeIdentityMatchersUsingOne(exchange_mapping); | |
| for (const SpiffeIdentityMatcher& matcher : matchers) { | |
| SpiffeUser user; | |
| user.identity_matcher = matcher; | |
| user.redfish_role = *redfish_role; | |
| // TODO(edwarddl): This is n^2. We can make it linear with refactoring of | |
| // vector into hashmap. Do profiling and benchmarking to see if this would | |
| // lead to better performance. | |
| // Update the redfish_role of the matcher if | |
| // the user already exists. | |
| auto it = std::find(spiffe_users.begin(), spiffe_users.end(), user); | |
| if (it != spiffe_users.end()) { | |
| it->redfish_role = *redfish_role; | |
| continue; | |
| } | |
| // Add the user to the list if they aren't there | |
| spiffe_users.push_back(std::move(user)); | |
| } | |
| } | |
| } | |
| void AuthzConfiguration::ReloadRedfishUsers( | |
| const nlohmann::json& config, const nlohmann::json& platform_config) { | |
| if (config.is_discarded()) { | |
| return; | |
| } | |
| std::vector<SpiffeUser> new_spiffe_users; | |
| std::unordered_map<std::string, std::string> new_username_to_role; | |
| std::unordered_map<std::string, std::string> new_group_to_role; | |
| AddRedfishUsersInfoFromConfig(config, new_spiffe_users, new_username_to_role, | |
| new_group_to_role); | |
| AddRedfishUsersInfoFromConfig(platform_config, new_spiffe_users, | |
| new_username_to_role, new_group_to_role); | |
| std::sort(new_spiffe_users.begin(), new_spiffe_users.end()); | |
| absl::MutexLock lock(mutex_); | |
| spiffe_users_ = std::move(new_spiffe_users); | |
| username_to_role_ = std::move(new_username_to_role); | |
| group_to_role_ = std::move(new_group_to_role); | |
| } | |
| static void AddRoleToPrivilegesToMap( | |
| const nlohmann::json& role_privileges, | |
| std::unordered_map<std::string, std::unordered_set<std::string>>& | |
| role_to_privileges) { | |
| if (role_privileges.is_discarded()) { | |
| return; | |
| } | |
| for (const nlohmann::json& role_privilege : role_privileges) { | |
| const std::string* role = GetValueAsString(role_privilege, "redfish_role"); | |
| if (role == nullptr) { | |
| continue; | |
| } | |
| const nlohmann::json* privileges = | |
| GetValueAsJson(role_privilege, "privileges"); | |
| if (privileges == nullptr) { | |
| continue; | |
| } | |
| std::unordered_set<std::string> privileges_set; | |
| const nlohmann::json::array_t* base_privileges = | |
| GetValueAsArray(*privileges, "base_privileges"); | |
| if (base_privileges != nullptr) { | |
| for (const nlohmann::json& privilege : *base_privileges) { | |
| const std::string* privilege_str = | |
| privilege.get_ptr<const std::string*>(); | |
| if (privilege_str == nullptr) { | |
| continue; | |
| } | |
| privileges_set.insert(*privilege_str); | |
| } | |
| } | |
| const nlohmann::json::array_t* oem_privileges = | |
| GetValueAsArray(*privileges, "oem_privileges"); | |
| if (oem_privileges != nullptr) { | |
| for (const nlohmann::json& privilege : *oem_privileges) { | |
| const std::string* privilege_str = | |
| privilege.get_ptr<const std::string*>(); | |
| if (privilege_str == nullptr) { | |
| continue; | |
| } | |
| privileges_set.insert(*privilege_str); | |
| } | |
| } | |
| privileges_set.insert("NoAuth"); | |
| role_to_privileges[*role] = std::move(privileges_set); | |
| } | |
| } | |
| void AuthzConfiguration::ReloadRoleToPrivileges( | |
| const nlohmann::json& config, const nlohmann::json& platform_config) { | |
| if (config.is_discarded()) { | |
| return; | |
| } | |
| const nlohmann::json::array_t* role_privileges = | |
| GetValueAsArray(config, "role_privileges_mappings"); | |
| std::unordered_map<std::string, std::unordered_set<std::string>> | |
| new_role_to_privileges; | |
| if (role_privileges != nullptr) { | |
| AddRoleToPrivilegesToMap(*role_privileges, new_role_to_privileges); | |
| } | |
| const nlohmann::json::array_t* platform_role_privileges = | |
| GetValueAsArray(platform_config, "role_privileges_mappings"); | |
| if (platform_role_privileges != nullptr) { | |
| AddRoleToPrivilegesToMap(*platform_role_privileges, new_role_to_privileges); | |
| } | |
| absl::MutexLock lock(mutex_); | |
| role_to_privileges_ = std::move(new_role_to_privileges); | |
| } | |
| void AuthzConfiguration::ReloadOfflineNodeEntities() { | |
| if (offline_node_entities_path_.empty()) { | |
| return; | |
| } | |
| absl::StatusOr<OfflineNodeEntityInformation> parsed_offline_node_entities = | |
| ReadOfflineNodeEntityInformation(offline_node_entities_path_); | |
| if (!parsed_offline_node_entities.ok()) { | |
| LOG(WARNING) << "Could not read offline node entities information from " | |
| << offline_node_entities_path_ << ": " | |
| << parsed_offline_node_entities.status().message(); | |
| return; | |
| } | |
| absl::MutexLock lock(mutex_); | |
| offline_node_entities_ = std::move(*parsed_offline_node_entities); | |
| } | |
| void AuthzConfiguration::ReloadGmi() { | |
| if (gmi_path_.empty()) { | |
| return; | |
| } | |
| std::ifstream gmi_file(gmi_path_); | |
| if (!gmi_file.is_open()) { | |
| LOG(ERROR) << "GMI file at '" << gmi_path_ << "' is missing."; | |
| return; | |
| } | |
| GoogleMachineIdentityProto gmi; | |
| if (!gmi.ParseFromIstream(&gmi_file)) { | |
| LOG(ERROR) << "GMI parsing failed at '" << gmi_path_ << "'"; | |
| return; | |
| } | |
| absl::MutexLock lock(mutex_); | |
| gmi_ = std::move(gmi); | |
| } | |
| void AuthzConfiguration::ReloadConfig(const nlohmann::json& config, | |
| const nlohmann::json& platform_config) { | |
| if (config.is_discarded()) { | |
| LOG(WARNING) << "Warning: config isn't a valid JSON"; | |
| return; | |
| } | |
| ReloadGmi(); | |
| ReloadOfflineNodeEntities(); | |
| ReloadResourceOwners(config); | |
| ReloadRedfishUsers(config, platform_config); | |
| ReloadRoleToPrivileges(config, platform_config); | |
| ReloadSampleRateLimit(config); | |
| absl::MutexLock lock(mutex_); | |
| config_ = config; | |
| } | |
| std::unordered_set<std::string> AuthzConfiguration::GetOemPrivileges() const { | |
| std::unordered_set<std::string> privileges; | |
| absl::MutexLock lock(mutex_); | |
| for (const auto& [role, privilege_set] : role_to_privileges_) { | |
| for (const std::string& privilege : privilege_set) { | |
| // If the privilege is not a default redfish privilege, it is OEM. | |
| if (!std::binary_search(kDefaultRedfishPrivileges.begin(), | |
| kDefaultRedfishPrivileges.end(), privilege)) { | |
| privileges.insert(privilege); | |
| } | |
| } | |
| } | |
| return privileges; | |
| } | |
| absl::StatusOr<EntityTagIdentity> AuthzConfiguration::GetEntityTagIdentity( | |
| absl::string_view entity_tag) const { | |
| absl::MutexLock lock(mutex_); | |
| auto it = | |
| offline_node_entities_.resolved_config().entities().find(entity_tag); | |
| if (it == offline_node_entities_.resolved_config().entities().end()) { | |
| return absl::NotFoundError("Entity Tag Not Found"); | |
| } | |
| return EntityTagIdentity{.hostname = it->second.hostname(), | |
| .owner = it->second.owner()}; | |
| } | |
| std::string AuthzConfiguration::GetMachineManagerEntityTag() const { | |
| absl::MutexLock lock(mutex_); | |
| return offline_node_entities_.resolved_config() | |
| .ecclesia_machine_manager_entity(); | |
| } | |
| std::vector<std::string> AuthzConfiguration::GetAllComputeNodeEntityTags() | |
| const { | |
| std::vector<std::string> entity_tags; | |
| absl::MutexLock lock(mutex_); | |
| for (const auto& [entity_tag, entity] : | |
| offline_node_entities_.resolved_config().entities()) { | |
| if (entity.node_type_info().is_compute()) { | |
| entity_tags.push_back(entity_tag); | |
| } | |
| } | |
| return entity_tags; | |
| } | |
| std::vector<std::string> AuthzConfiguration::GetAllOffloadNodeEntityTags() | |
| const { | |
| std::vector<std::string> entity_tags; | |
| absl::MutexLock lock(mutex_); | |
| for (const auto& [entity_tag, entity] : | |
| offline_node_entities_.resolved_config().entities()) { | |
| if (entity.node_type_info().is_offload()) { | |
| entity_tags.push_back(entity_tag); | |
| } | |
| } | |
| return entity_tags; | |
| } | |
| std::vector<std::string> AuthzConfiguration::GetEntityTagsFromRedfishSystemId( | |
| absl::string_view system_id) const { | |
| std::vector<std::string> entity_tags; | |
| absl::MutexLock lock(mutex_); | |
| for (const auto& [entity_tag, entity] : | |
| offline_node_entities_.resolved_config().entities()) { | |
| if (entity.redfish_system_id() == system_id) { | |
| entity_tags.push_back(entity_tag); | |
| } | |
| } | |
| return entity_tags; | |
| } | |
| std::vector<std::string> | |
| AuthzConfiguration::GetAllEntityTagsFromAuthorizedEntity( | |
| AuthorizedEntity entity) const { | |
| switch (entity) { | |
| case AuthorizedEntity::ComputeNode: | |
| return GetAllComputeNodeEntityTags(); | |
| case AuthorizedEntity::ComputeNode1: | |
| return GetEntityTagsFromRedfishSystemId("system1"); | |
| case AuthorizedEntity::ComputeNode2: | |
| return GetEntityTagsFromRedfishSystemId("system2"); | |
| case AuthorizedEntity::OffloadNodes: | |
| return GetAllOffloadNodeEntityTags(); | |
| case AuthorizedEntity::EcclesiaMachineManagerEntity: | |
| return {GetMachineManagerEntityTag()}; | |
| case AuthorizedEntity::Undefined: | |
| return {}; | |
| case AuthorizedEntity_INT_MIN_SENTINEL_DO_NOT_USE_: | |
| case AuthorizedEntity_INT_MAX_SENTINEL_DO_NOT_USE_: | |
| return {}; | |
| } | |
| return {}; | |
| } | |
| bool AuthzConfiguration::IsHwopsState() const { | |
| absl::MutexLock lock(mutex_); | |
| if (gmi_.has_in_hwops_state()) { | |
| return gmi_.in_hwops_state(); | |
| } | |
| return false; | |
| } | |
| std::string AuthzConfiguration::GetNodeRealm() const { | |
| absl::MutexLock lock(mutex_); | |
| // GMI only supports 1 realm | |
| if (gmi_.security_realms_size() != 1) { | |
| LOG(ERROR) << "Machine GMI's realm is broken. All realm specific Borg jobs " | |
| "will be denied."; | |
| return ""; | |
| } | |
| return gmi_.security_realms(0); | |
| } | |
| } // namespace milotic::authz |