blob: d3dd1d50f8e1bca6f2d4a124c0d933f2c8cb365c [file] [log] [blame]
#include "config_generator.h"
#include <cstddef>
#include <fstream>
#include <iterator>
#include <optional>
#include <string>
#include <string_view>
#include "gmi/machine_identity.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/strings/str_cat.h"
#include "nlohmann/json.hpp"
#include "json_utils.h"
#include "zatar/certificate_metadata.h"
namespace milotic::authz {
namespace {
using ::milotic::redfish::CertificateMetadataParser;
using ::production_msv::node_entities::ReadOfflineNodeEntityInformation;
using ::production_msv::node_entities_proto::OfflineNodeEntityInformation;
using ::production_msv::node_entities_proto::ResolvedNodeEntity;
using ::security_prodid::GoogleMachineIdentityProto;
constexpr std::string_view kMachineManagerRedfishRole = "MManager";
constexpr std::string_view kComputeNodeRedfishRole = "ComputeNode";
constexpr std::string_view kEmptyConfiguration = R"(
{
"base_privilege_registry": "Redfish_1.3.0_PrivilegeRegistry.json",
"exchange_mappings": [],
"name": "Empty BMC Authorization Configurations",
"oem_privilege_mappings": [],
"resource_owners": [],
"role_privileges_mappings": []
}
)";
constexpr std::string_view kHwopsRole = "insecure-hwops-state";
} // namespace
nlohmann::json::json_pointer ConfigGenerator::FindLocalNodeInOwners(
const nlohmann::json& root, std::string_view hostname) {
nlohmann::json::json_pointer resource_owners_ptr =
"/resource_owners"_json_pointer;
const nlohmann::json::array_t* resource_owners =
GetValueAsArray(root, "resource_owners");
if (resource_owners == nullptr) {
return resource_owners_ptr / 0;
}
for (std::size_t i = 0; i < resource_owners->size(); ++i) {
const std::string* fqdn = GetValueAsString(resource_owners->at(i), "fqdn");
if (fqdn != nullptr &&
*fqdn == absl::StrCat(hostname, ".prod.google.com")) {
return resource_owners_ptr / i;
}
}
return resource_owners_ptr / resource_owners->size();
}
nlohmann::json::json_pointer ConfigGenerator::FindLocalNodeInExchangeMappings(
const nlohmann::json& root, std::string_view hostname,
std::string_view node_redfish_role) {
nlohmann::json::json_pointer exchange_mappings_ptr =
"/exchange_mappings"_json_pointer;
const nlohmann::json::array_t* exchange_mappings =
GetValueAsArray(root, "exchange_mappings");
if (exchange_mappings == nullptr) {
return exchange_mappings_ptr / 0;
}
for (std::size_t i = 0; i < exchange_mappings->size(); ++i) {
const std::string* redfish_role =
GetValueAsString(exchange_mappings->at(i), "redfish_role");
if (redfish_role != nullptr && *redfish_role == node_redfish_role) {
return exchange_mappings_ptr / i;
}
const nlohmann::json* peer =
GetValueAsJson(exchange_mappings->at(i), "peer");
if (peer != nullptr) {
const std::string* fqdn = GetValueAsString(*peer, "fqdn");
if (fqdn != nullptr &&
*fqdn == absl::StrCat(hostname, ".prod.google.com")) {
return exchange_mappings_ptr / i;
}
}
}
return exchange_mappings_ptr / exchange_mappings->size();
}
std::optional<GoogleMachineIdentityProto> ConfigGenerator::GetGmiDataFromGmi(
const std::string& gmi_path) {
std::ifstream gmi_file(gmi_path);
if (!gmi_file.is_open()) {
LOG(ERROR) << "GMI file at '" << gmi_path << "' is missing.";
return std::nullopt;
}
GoogleMachineIdentityProto gmi;
if (!gmi.ParseFromIstream(&gmi_file)) {
LOG(ERROR) << "GMI parsing failed at '" << gmi_path << "'";
return std::nullopt;
}
return gmi;
}
// Return the role expected in the certificate.
//
// 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
std::string_view GetCertNodeOwner(bool in_hwops_state,
std::string_view node_owner) {
if (in_hwops_state) {
return kHwopsRole;
}
return node_owner;
}
void ConfigGenerator::UpdateMachineManagerMappingsInRoot(
nlohmann::json& root, bool in_hwops_state,
const OfflineNodeEntityInformation& one) {
std::string mmanager_entity_tag =
one.resolved_config().ecclesia_machine_manager_entity();
ResolvedNodeEntity mmanager_entity =
one.resolved_config().entities().at(mmanager_entity_tag);
nlohmann::json::json_pointer mmanager_exchange_mappings_ptr =
FindLocalNodeInExchangeMappings(root, mmanager_entity.hostname(),
kMachineManagerRedfishRole);
// If a mapping to mmanager already exists, then no-op
if (root.contains(mmanager_exchange_mappings_ptr)) {
return;
}
// Define MManager Peer object
nlohmann::json::object_t mmanager = nlohmann::json::object();
mmanager["spiffe_id_matcher"]["path"] = absl::StrCat(
"/role/", GetCertNodeOwner(in_hwops_state, mmanager_entity.owner()));
mmanager["spiffe_id_matcher"]["ca_context"] =
CertificateMetadataParser::kCaContextNode;
mmanager["spiffe_id_matcher"]["trust_domain_suffix"] = ".prod.google.com";
mmanager["fqdn"] =
absl::StrCat(mmanager_entity.hostname(), ".prod.google.com");
root[FindLocalNodeInOwners(root, mmanager_entity.hostname())] = mmanager;
// Define MManager Exchange mapping
nlohmann::json::object_t mmanager_exchange = nlohmann::json::object();
mmanager_exchange["redfish_role"] = kMachineManagerRedfishRole;
mmanager_exchange["peer"] = mmanager;
root[mmanager_exchange_mappings_ptr] = mmanager_exchange;
}
void ConfigGenerator::UpdateComputeNodeMappingsInRoot(
nlohmann::json& root, bool in_hwops_state,
const OfflineNodeEntityInformation& one) {
nlohmann::json::json_pointer compute_node_exchange_mapping_ptr =
FindLocalNodeInExchangeMappings(
root, one.resolved_config().machine_name(), kComputeNodeRedfishRole);
// If a mapping to compute node already exists, then no-op
if (root.contains(compute_node_exchange_mapping_ptr)) {
return;
}
// Define Compute Node Peer object
//
// TODO: this only works on machines on which the compute node hostname ==
// machine name
nlohmann::json::object_t compute_node = nlohmann::json::object();
compute_node["spiffe_id_matcher"]["path"] = absl::StrCat(
"/role/",
GetCertNodeOwner(in_hwops_state, one.resolved_config().machine_owner()));
compute_node["spiffe_id_matcher"]["ca_context"] =
CertificateMetadataParser::kCaContextNode;
compute_node["spiffe_id_matcher"]["trust_domain_suffix"] = ".prod.google.com";
compute_node["fqdn"] =
absl::StrCat(one.resolved_config().machine_name(), ".prod.google.com");
root[FindLocalNodeInOwners(root, one.resolved_config().machine_name())] =
compute_node;
// Define Compute Node Exchange mapping
nlohmann::json::object_t compute_node_exchange = nlohmann::json::object();
compute_node_exchange["redfish_role"] = kComputeNodeRedfishRole;
compute_node_exchange["peer"] = compute_node;
root[compute_node_exchange_mapping_ptr] = compute_node_exchange;
}
nlohmann::json ConfigGenerator::ReadConfigAndAddRuntimeSubstitutions(
const std::string& base_config_path, bool in_hwops_state,
const OfflineNodeEntityInformation& one) {
std::ifstream template_file(base_config_path);
if (!template_file.is_open()) {
LOG(ERROR) << "Authorization configuration file at '" << base_config_path
<< "' is missing.";
return nlohmann::json::object();
}
std::string template_content((std::istreambuf_iterator<char>(template_file)),
std::istreambuf_iterator<char>());
nlohmann::json root = nlohmann::json::parse(template_content, nullptr, false);
if (root.is_discarded()) {
LOG(ERROR) << "Authorization configuration is not a JSON! |content|="
<< template_content;
return nlohmann::json::object();
}
UpdateMachineManagerMappingsInRoot(root, in_hwops_state, one);
UpdateComputeNodeMappingsInRoot(root, in_hwops_state, one);
return root;
}
nlohmann::json ConfigGenerator::EmptyConfiguration() {
return nlohmann::json::parse(kEmptyConfiguration, nullptr, false);
}
nlohmann::json ConfigGenerator::GenerateConfiguration(
const GeneratorOptions& options) {
std::optional<GoogleMachineIdentityProto> gmi =
GetGmiDataFromGmi(options.gmi_path);
if (!gmi) {
return nlohmann::json::object();
}
absl::StatusOr<OfflineNodeEntityInformation> one =
ReadOfflineNodeEntityInformation(options.offline_node_entities_path);
if (!one.ok()) {
LOG(ERROR) << "Could not read offline node entities information: "
<< one.status().message();
return nlohmann::json::object();
}
return ReadConfigAndAddRuntimeSubstitutions(options.authz_configuration_path,
gmi->in_hwops_state(), *one);
}
} // namespace milotic::authz