blob: 0c1e2e029955e68d4e5ea598d6fd10ee302516e5 [file] [log] [blame]
#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_