Arachne 1.0
Arachne - the perpetual stitcher of Wikidata entities.
Loading...
Searching...
No Matches
arachne::pheidippides::hardened_transport::implementation Struct Referencefinal
Collaboration diagram for arachne::pheidippides::hardened_transport::implementation:

Public Member Functions

 implementation (fs::path artifact_root, const json &configuration)
acquired_artifact_v1 execute (const fetch_request_v1 &original) const
void complete_flight (const std::string &cache_key, const std::shared_ptr< flight_state > &flight, const acquired_artifact_v1 &result) const
 implementation (fs::path artifact_root, const json &configuration)
acquired_artifact_v1 execute (const fetch_request_v1 &original) const
void complete_flight (const std::string &cache_key, const std::shared_ptr< flight_state > &flight, const acquired_artifact_v1 &result) const

Public Attributes

fs::path artifact_root
fs::path cache_root
transport byte_transport
std::map< std::string, door_configuration, std::less<> > doors
std::mutex flights_mutex
std::map< std::string, std::shared_ptr< flight_state >, std::less<> > flights

Detailed Description

Definition at line 943 of file hardened_transport.cpp.

Constructor & Destructor Documentation

◆ implementation() [1/2]

arachne::pheidippides::hardened_transport::implementation::implementation ( fs::path artifact_root,
const json & configuration )
inline

Definition at line 944 of file hardened_transport.cpp.

945 : artifact_root(std::move(artifact_root))
947 constexpr std::array root_fields {
948 std::string_view { "format_version" },
949 std::string_view { "defaults" },
950 std::string_view { "doors" },
951 };
952 reject_unknown_keys(configuration, root_fields, "transport");
953 if (configuration.value("format_version", 0) != 1) {
954 throw std::invalid_argument("transport.format_version must be 1");
955 }
956 policy_settings global_policy;
958 global_policy,
959 required_object(configuration, "defaults", "transport"),
960 "transport.defaults"
961 );
962 const auto door_documents = configuration.find("doors");
963 if (door_documents == configuration.end() || !door_documents->is_array()
964 || door_documents->empty()) {
965 throw std::invalid_argument(
966 "transport.doors must be a non-empty array"
967 );
968 }
969 for (std::size_t door_index = 0; door_index < door_documents->size();
970 ++door_index) {
971 const json& document = door_documents->at(door_index);
972 const std::string location
973 = "transport.doors[" + std::to_string(door_index) + "]";
974 constexpr std::array door_fields {
975 std::string_view { "door_id" },
976 std::string_view { "defaults" },
977 std::string_view { "endpoints" },
978 };
979 reject_unknown_keys(document, door_fields, location);
980 door_configuration door;
981 door.door_id = required_string(document, "door_id", location);
982 if (!safe_token(door.door_id) || doors.contains(door.door_id)) {
983 throw std::invalid_argument(
984 location + ".door_id is invalid or duplicated"
985 );
986 }
987 door.policy = global_policy;
988 if (const auto defaults = document.find("defaults");
989 defaults != document.end()) {
990 apply_settings(door.policy, *defaults, location + ".defaults");
991 }
992 door.admission
993 = std::make_shared<admission_gate>(door.policy.admission);
994 const auto endpoint_documents = document.find("endpoints");
995 if (endpoint_documents == document.end()
996 || !endpoint_documents->is_array()
997 || endpoint_documents->empty()) {
998 throw std::invalid_argument(
999 location + ".endpoints must be a non-empty array"
1000 );
1001 }
1002 for (std::size_t endpoint_index = 0;
1003 endpoint_index < endpoint_documents->size();
1004 ++endpoint_index) {
1005 const json& endpoint_document
1006 = endpoint_documents->at(endpoint_index);
1007 const std::string endpoint_location = location + ".endpoints["
1008 + std::to_string(endpoint_index) + "]";
1009 constexpr std::array endpoint_fields {
1010 std::string_view { "endpoint_id" },
1011 std::string_view { "protocol" },
1012 std::string_view { "base_url" },
1013 std::string_view { "allowed_methods" },
1014 std::string_view { "authentication" },
1015 std::string_view { "bulk_capable" },
1016 std::string_view { "resumable_download" },
1017 std::string_view { "write_enabled" },
1018 std::string_view { "idempotency_header" },
1019 std::string_view { "allow_insecure_http" },
1020 std::string_view { "policy" },
1021 };
1023 endpoint_document, endpoint_fields, endpoint_location
1024 );
1025 endpoint_configuration endpoint;
1026 endpoint.endpoint_id = required_string(
1027 endpoint_document, "endpoint_id", endpoint_location
1028 );
1029 if (!safe_token(endpoint.endpoint_id)
1030 || door.endpoints.contains(endpoint.endpoint_id)) {
1031 throw std::invalid_argument(
1032 endpoint_location
1033 + ".endpoint_id is invalid or duplicated"
1034 );
1035 }
1036 endpoint.protocol = required_string(
1037 endpoint_document, "protocol", endpoint_location
1038 );
1039 constexpr std::array protocols {
1040 std::string_view { "http_file" },
1041 std::string_view { "rest" },
1042 std::string_view { "sparql" },
1043 std::string_view { "oai_pmh" },
1044 std::string_view { "ldes" },
1045 std::string_view { "iiif" },
1046 };
1047 if (std::ranges::find(protocols, endpoint.protocol)
1048 == protocols.end()) {
1049 throw std::invalid_argument(
1050 endpoint_location + ".protocol is unsupported"
1051 );
1052 }
1053 endpoint.base_url = parse_absolute_url(
1055 endpoint_document, "base_url", endpoint_location
1056 ),
1057 endpoint_location + ".base_url"
1058 );
1059 endpoint.allow_insecure_http = optional_boolean(
1060 endpoint_document, "allow_insecure_http", false,
1061 endpoint_location
1062 );
1063 if (endpoint.base_url.scheme != "https"
1064 && !endpoint.allow_insecure_http) {
1065 throw std::invalid_argument(
1066 endpoint_location
1067 + " uses HTTP without explicit test/development "
1068 "permission"
1069 );
1070 }
1071 const auto methods = endpoint_document.find("allowed_methods");
1072 if (methods == endpoint_document.end() || !methods->is_array()
1073 || methods->empty()) {
1074 throw std::invalid_argument(
1075 endpoint_location
1076 + ".allowed_methods must be a non-empty array"
1077 );
1078 }
1079 for (const auto& method : *methods) {
1080 if (!method.is_string()) {
1081 throw std::invalid_argument(
1082 endpoint_location
1083 + ".allowed_methods contains a non-string"
1084 );
1085 }
1086 const std::string value = method.get<std::string>();
1087 if (value == "GET") {
1088 endpoint.allowed_methods.insert(http_method::get);
1089 } else if (value == "POST") {
1090 endpoint.allowed_methods.insert(http_method::post);
1091 } else {
1092 throw std::invalid_argument(
1093 endpoint_location
1094 + ".allowed_methods contains an unsupported method"
1095 );
1096 }
1097 }
1098 endpoint.authentication = parse_authentication(
1100 endpoint_document, "authentication", endpoint_location
1101 ),
1102 endpoint_location + ".authentication"
1103 );
1104 endpoint.bulk_capable = optional_boolean(
1105 endpoint_document, "bulk_capable", false, endpoint_location
1106 );
1107 endpoint.resumable_download = optional_boolean(
1108 endpoint_document, "resumable_download", false,
1109 endpoint_location
1110 );
1111 endpoint.write_enabled = optional_boolean(
1112 endpoint_document, "write_enabled", false, endpoint_location
1113 );
1114 if (endpoint_document.contains("idempotency_header")) {
1115 endpoint.idempotency_header = required_string(
1116 endpoint_document, "idempotency_header",
1117 endpoint_location
1118 );
1119 const std::string lowered
1120 = ascii_lower(endpoint.idempotency_header);
1121 if (!endpoint.write_enabled || lowered == "host"
1122 || sensitive_header(endpoint.idempotency_header)
1124 endpoint.idempotency_header
1125 )) {
1126 throw std::invalid_argument(
1127 endpoint_location
1128 + ".idempotency_header is unsafe or declared for a "
1129 "read-only endpoint"
1130 );
1131 }
1132 }
1133 endpoint.policy = door.policy;
1134 if (const auto overrides = endpoint_document.find("policy");
1135 overrides != endpoint_document.end()) {
1137 endpoint.policy, *overrides,
1138 endpoint_location + ".policy"
1139 );
1140 }
1141 if (endpoint.policy.redirects.allowed_hosts.empty()) {
1142 endpoint.policy.redirects.allowed_hosts
1143 = { configured_authority(endpoint.base_url) };
1144 }
1145 endpoint.admission = std::make_shared<admission_gate>(
1146 endpoint.policy.admission
1147 );
1148 door.endpoints.emplace(
1149 endpoint.endpoint_id, std::move(endpoint)
1150 );
1151 }
1152 doors.emplace(door.door_id, std::move(door));
1153 }
1154
1155 std::error_code error;
1156 const auto root_state = fs::symlink_status(this->artifact_root, error);
1157 if (error == std::errc::no_such_file_or_directory) {
1158 error.clear();
1159 fs::create_directories(this->artifact_root, error);
1160 } else if (
1161 !error
1162 && (fs::is_symlink(root_state) || !fs::is_directory(root_state))
1163 ) {
1164 throw std::invalid_argument(
1165 "artifact root must be a real directory"
1166 );
1167 }
1168 if (error) {
1169 throw std::invalid_argument(
1170 "cannot create artifact root: " + error.message()
1171 );
1172 }
1173 cache_root = this->artifact_root / ".pheidippides-cache";
1174 const auto cache_state = fs::symlink_status(cache_root, error);
1175 if (error == std::errc::no_such_file_or_directory) {
1176 error.clear();
1177 fs::create_directory(cache_root, error);
1178 } else if (
1179 !error
1180 && (fs::is_symlink(cache_state) || !fs::is_directory(cache_state))
1181 ) {
1182 throw std::invalid_argument(
1183 "transport cache root must be a real directory"
1184 );
1185 }
1186 if (error) {
1187 throw std::invalid_argument(
1188 "cannot create transport cache root: " + error.message()
1189 );
1190 }
1191 }
const json & required_object(const json &parent, const std::string_view key)
Definition main.cpp:313
std::string required_string(const nlohmann::json &value, std::string_view field, std::string_view context)
void apply_settings(policy_settings &policy, const json &object, const std::string_view location)
authentication_settings parse_authentication(const json &object, const std::string_view location)
void reject_unknown_keys(const json &object, const std::span< const std::string_view > allowed, const std::string_view location)
bool optional_boolean(const json &object, const std::string_view key, const bool fallback, const std::string_view location)
parsed_url parse_absolute_url(const std::string &value, const std::string_view location)
std::map< std::string, door_configuration, std::less<> > doors

References arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::allow_insecure_http, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::ascii_lower(), arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::base_url, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::configured_authority(), arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::endpoint_id, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::idempotency_header, implementation(), and arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::parsed_url::scheme.

Referenced by implementation().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ implementation() [2/2]

arachne::pheidippides::hardened_transport::implementation::implementation ( fs::path artifact_root,
const json & configuration )
inline

Definition at line 944 of file hardened_transport.cpp.

945 : artifact_root(std::move(artifact_root))
947 constexpr std::array root_fields {
948 std::string_view { "format_version" },
949 std::string_view { "defaults" },
950 std::string_view { "doors" },
951 };
952 reject_unknown_keys(configuration, root_fields, "transport");
953 if (configuration.value("format_version", 0) != 1) {
954 throw std::invalid_argument("transport.format_version must be 1");
955 }
956 policy_settings global_policy;
958 global_policy,
959 required_object(configuration, "defaults", "transport"),
960 "transport.defaults"
961 );
962 const auto door_documents = configuration.find("doors");
963 if (door_documents == configuration.end() || !door_documents->is_array()
964 || door_documents->empty()) {
965 throw std::invalid_argument(
966 "transport.doors must be a non-empty array"
967 );
968 }
969 for (std::size_t door_index = 0; door_index < door_documents->size();
970 ++door_index) {
971 const json& document = door_documents->at(door_index);
972 const std::string location
973 = "transport.doors[" + std::to_string(door_index) + "]";
974 constexpr std::array door_fields {
975 std::string_view { "door_id" },
976 std::string_view { "defaults" },
977 std::string_view { "endpoints" },
978 };
979 reject_unknown_keys(document, door_fields, location);
980 door_configuration door;
981 door.door_id = required_string(document, "door_id", location);
982 if (!safe_token(door.door_id) || doors.contains(door.door_id)) {
983 throw std::invalid_argument(
984 location + ".door_id is invalid or duplicated"
985 );
986 }
987 door.policy = global_policy;
988 if (const auto defaults = document.find("defaults");
989 defaults != document.end()) {
990 apply_settings(door.policy, *defaults, location + ".defaults");
991 }
992 door.admission
993 = std::make_shared<admission_gate>(door.policy.admission);
994 const auto endpoint_documents = document.find("endpoints");
995 if (endpoint_documents == document.end()
996 || !endpoint_documents->is_array()
997 || endpoint_documents->empty()) {
998 throw std::invalid_argument(
999 location + ".endpoints must be a non-empty array"
1000 );
1001 }
1002 for (std::size_t endpoint_index = 0;
1003 endpoint_index < endpoint_documents->size();
1004 ++endpoint_index) {
1005 const json& endpoint_document
1006 = endpoint_documents->at(endpoint_index);
1007 const std::string endpoint_location = location + ".endpoints["
1008 + std::to_string(endpoint_index) + "]";
1009 constexpr std::array endpoint_fields {
1010 std::string_view { "endpoint_id" },
1011 std::string_view { "protocol" },
1012 std::string_view { "base_url" },
1013 std::string_view { "allowed_methods" },
1014 std::string_view { "authentication" },
1015 std::string_view { "bulk_capable" },
1016 std::string_view { "resumable_download" },
1017 std::string_view { "write_enabled" },
1018 std::string_view { "idempotency_header" },
1019 std::string_view { "allow_insecure_http" },
1020 std::string_view { "policy" },
1021 };
1023 endpoint_document, endpoint_fields, endpoint_location
1024 );
1025 endpoint_configuration endpoint;
1026 endpoint.endpoint_id = required_string(
1027 endpoint_document, "endpoint_id", endpoint_location
1028 );
1029 if (!safe_token(endpoint.endpoint_id)
1030 || door.endpoints.contains(endpoint.endpoint_id)) {
1031 throw std::invalid_argument(
1032 endpoint_location
1033 + ".endpoint_id is invalid or duplicated"
1034 );
1035 }
1036 endpoint.protocol = required_string(
1037 endpoint_document, "protocol", endpoint_location
1038 );
1039 constexpr std::array protocols {
1040 std::string_view { "http_file" },
1041 std::string_view { "rest" },
1042 std::string_view { "sparql" },
1043 std::string_view { "oai_pmh" },
1044 std::string_view { "ldes" },
1045 std::string_view { "iiif" },
1046 };
1047 if (std::ranges::find(protocols, endpoint.protocol)
1048 == protocols.end()) {
1049 throw std::invalid_argument(
1050 endpoint_location + ".protocol is unsupported"
1051 );
1052 }
1053 endpoint.base_url = parse_absolute_url(
1055 endpoint_document, "base_url", endpoint_location
1056 ),
1057 endpoint_location + ".base_url"
1058 );
1059 endpoint.allow_insecure_http = optional_boolean(
1060 endpoint_document, "allow_insecure_http", false,
1061 endpoint_location
1062 );
1063 if (endpoint.base_url.scheme != "https"
1064 && !endpoint.allow_insecure_http) {
1065 throw std::invalid_argument(
1066 endpoint_location
1067 + " uses HTTP without explicit test/development "
1068 "permission"
1069 );
1070 }
1071 const auto methods = endpoint_document.find("allowed_methods");
1072 if (methods == endpoint_document.end() || !methods->is_array()
1073 || methods->empty()) {
1074 throw std::invalid_argument(
1075 endpoint_location
1076 + ".allowed_methods must be a non-empty array"
1077 );
1078 }
1079 for (const auto& method : *methods) {
1080 if (!method.is_string()) {
1081 throw std::invalid_argument(
1082 endpoint_location
1083 + ".allowed_methods contains a non-string"
1084 );
1085 }
1086 const std::string value = method.get<std::string>();
1087 if (value == "GET") {
1088 endpoint.allowed_methods.insert(http_method::get);
1089 } else if (value == "POST") {
1090 endpoint.allowed_methods.insert(http_method::post);
1091 } else {
1092 throw std::invalid_argument(
1093 endpoint_location
1094 + ".allowed_methods contains an unsupported method"
1095 );
1096 }
1097 }
1098 endpoint.authentication = parse_authentication(
1100 endpoint_document, "authentication", endpoint_location
1101 ),
1102 endpoint_location + ".authentication"
1103 );
1104 endpoint.bulk_capable = optional_boolean(
1105 endpoint_document, "bulk_capable", false, endpoint_location
1106 );
1107 endpoint.resumable_download = optional_boolean(
1108 endpoint_document, "resumable_download", false,
1109 endpoint_location
1110 );
1111 endpoint.write_enabled = optional_boolean(
1112 endpoint_document, "write_enabled", false, endpoint_location
1113 );
1114 if (endpoint_document.contains("idempotency_header")) {
1115 endpoint.idempotency_header = required_string(
1116 endpoint_document, "idempotency_header",
1117 endpoint_location
1118 );
1119 const std::string lowered
1120 = ascii_lower(endpoint.idempotency_header);
1121 if (!endpoint.write_enabled || lowered == "host"
1122 || sensitive_header(endpoint.idempotency_header)
1124 endpoint.idempotency_header
1125 )) {
1126 throw std::invalid_argument(
1127 endpoint_location
1128 + ".idempotency_header is unsafe or declared for a "
1129 "read-only endpoint"
1130 );
1131 }
1132 }
1133 endpoint.policy = door.policy;
1134 if (const auto overrides = endpoint_document.find("policy");
1135 overrides != endpoint_document.end()) {
1137 endpoint.policy, *overrides,
1138 endpoint_location + ".policy"
1139 );
1140 }
1141 if (endpoint.policy.redirects.allowed_hosts.empty()) {
1142 endpoint.policy.redirects.allowed_hosts
1143 = { configured_authority(endpoint.base_url) };
1144 }
1145 endpoint.admission = std::make_shared<admission_gate>(
1146 endpoint.policy.admission
1147 );
1148 door.endpoints.emplace(
1149 endpoint.endpoint_id, std::move(endpoint)
1150 );
1151 }
1152 doors.emplace(door.door_id, std::move(door));
1153 }
1154
1155 std::error_code error;
1156 const auto root_state = fs::symlink_status(this->artifact_root, error);
1157 if (error == std::errc::no_such_file_or_directory) {
1158 error.clear();
1159 fs::create_directories(this->artifact_root, error);
1160 } else if (
1161 !error
1162 && (fs::is_symlink(root_state) || !fs::is_directory(root_state))
1163 ) {
1164 throw std::invalid_argument(
1165 "artifact root must be a real directory"
1166 );
1167 }
1168 if (error) {
1169 throw std::invalid_argument(
1170 "cannot create artifact root: " + error.message()
1171 );
1172 }
1173 cache_root = this->artifact_root / ".pheidippides-cache";
1174 const auto cache_state = fs::symlink_status(cache_root, error);
1175 if (error == std::errc::no_such_file_or_directory) {
1176 error.clear();
1177 fs::create_directory(cache_root, error);
1178 } else if (
1179 !error
1180 && (fs::is_symlink(cache_state) || !fs::is_directory(cache_state))
1181 ) {
1182 throw std::invalid_argument(
1183 "transport cache root must be a real directory"
1184 );
1185 }
1186 if (error) {
1187 throw std::invalid_argument(
1188 "cannot create transport cache root: " + error.message()
1189 );
1190 }
1191 }

Member Function Documentation

◆ complete_flight() [1/2]

void arachne::pheidippides::hardened_transport::implementation::complete_flight ( const std::string & cache_key,
const std::shared_ptr< flight_state > & flight,
const acquired_artifact_v1 & result ) const
inline

Definition at line 1452 of file hardened_transport.cpp.

1456 {
1457 if (!flight) {
1458 return;
1459 }
1460 {
1461 std::lock_guard lock(flights_mutex);
1462 flight->result = result;
1463 flight->complete = true;
1464 flights.erase(cache_key);
1465 }
1466 flight->condition.notify_all();
1467 }
std::map< std::string, std::shared_ptr< flight_state >, std::less<> > flights

Referenced by execute().

Here is the caller graph for this function:

◆ complete_flight() [2/2]

void arachne::pheidippides::hardened_transport::implementation::complete_flight ( const std::string & cache_key,
const std::shared_ptr< flight_state > & flight,
const acquired_artifact_v1 & result ) const
inline

Definition at line 1452 of file hardened_transport.cpp.

1456 {
1457 if (!flight) {
1458 return;
1459 }
1460 {
1461 std::lock_guard lock(flights_mutex);
1462 flight->result = result;
1463 flight->complete = true;
1464 flights.erase(cache_key);
1465 }
1466 flight->condition.notify_all();
1467 }

◆ execute() [1/2]

acquired_artifact_v1 arachne::pheidippides::hardened_transport::implementation::execute ( const fetch_request_v1 & original) const
inlinenodiscard

Definition at line 1194 of file hardened_transport.cpp.

1194 {
1195 if (original.target_artifact_ref == ".pheidippides-cache"
1196 || original.target_artifact_ref.starts_with(
1197 ".pheidippides-cache/"
1198 )) {
1199 return policy_failure(
1201 "request output_ref targets reserved transport metadata"
1202 );
1203 }
1204 const auto door_iterator = doors.find(original.door_id);
1205 if (door_iterator == doors.end()) {
1206 return policy_failure(
1208 "request names an unknown door_id"
1209 );
1210 }
1211 const door_configuration& door = door_iterator->second;
1212 const auto endpoint_iterator
1213 = door.endpoints.find(original.endpoint_id);
1214 if (endpoint_iterator == door.endpoints.end()) {
1215 return policy_failure(
1217 "request names an unknown endpoint_id for its door"
1218 );
1219 }
1220 const endpoint_configuration& endpoint = endpoint_iterator->second;
1221 if (!endpoint.allowed_methods.contains(original.method)) {
1222 return policy_failure(
1224 "request method is disabled for this endpoint"
1225 );
1226 }
1227 if (original.operation == transport_operation::bulk_snapshot
1228 && !endpoint.bulk_capable) {
1229 return policy_failure(
1231 "endpoint is not declared bulk-capable"
1232 );
1233 }
1234 if (original.operation == transport_operation::resume_download
1235 && !endpoint.resumable_download) {
1236 return policy_failure(
1238 "endpoint is not declared resumable"
1239 );
1240 }
1241 if (original.operation == transport_operation::external_write
1242 && !endpoint.write_enabled) {
1243 return policy_failure(
1245 "external writes are disabled for this endpoint"
1246 );
1247 }
1248 if (original.operation == transport_operation::external_write
1249 && !original.idempotency_key.empty()
1250 && !safe_header_value(original.idempotency_key, 512U)) {
1251 return policy_failure(
1253 "the external-write idempotency key is not a safe header value"
1254 );
1255 }
1256 parsed_url request_url;
1257 try {
1258 request_url = parse_absolute_url(original.url, "request locator");
1259 } catch (const std::exception& error) {
1260 return policy_failure(
1261 original, transport_status::door_policy_rejected, error.what()
1262 );
1263 }
1264 if (!within_base_url(request_url, endpoint.base_url)) {
1265 return policy_failure(
1267 "request locator is outside the endpoint base URL"
1268 );
1269 }
1270 for (const auto& header : original.headers) {
1271 if (sensitive_header(header.name)) {
1272 return policy_failure(
1274 "sensitive request headers must come from a runtime secret "
1275 "reference"
1276 );
1277 }
1278 if (original.operation == transport_operation::external_write
1279 && !endpoint.idempotency_header.empty()
1280 && ascii_lower(header.name)
1281 == ascii_lower(endpoint.idempotency_header)) {
1282 return policy_failure(
1284 "the configured idempotency header is managed by the "
1285 "transport"
1286 );
1287 }
1288 }
1289
1290 fetch_request_v1 request = original;
1291 request.timeout
1292 = std::min(request.timeout, endpoint.policy.timeouts.total);
1293 request.connect_timeout = std::min(
1294 request.connect_timeout, endpoint.policy.timeouts.connect
1295 );
1296 request.read_timeout
1297 = std::min(request.read_timeout, endpoint.policy.timeouts.read);
1298 request.write_timeout
1299 = std::min(request.write_timeout, endpoint.policy.timeouts.write);
1300 request.max_bytes = std::min(
1301 request.max_bytes, endpoint.policy.maximum_artifact_bytes
1302 );
1303 request.maximum_attempts = std::min(
1304 request.maximum_attempts, endpoint.policy.retry.maximum_attempts
1305 );
1306 request.initial_retry_delay = endpoint.policy.retry.initial_delay;
1307 request.maximum_retry_delay = endpoint.policy.retry.maximum_delay;
1308 request.total_retry_delay_budget
1309 = endpoint.policy.retry.total_delay_budget;
1310 request.respect_retry_after = endpoint.policy.retry.respect_retry_after;
1311 request.redirects.follow = endpoint.policy.redirects.follow;
1312 request.redirects.maximum_redirects
1313 = endpoint.policy.redirects.maximum_redirects;
1314 request.redirects.allow_https_to_http
1315 = endpoint.policy.redirects.allow_https_to_http;
1316 request.redirects.allowed_hosts
1317 = endpoint.policy.redirects.allowed_hosts;
1318 if (request.operation == transport_operation::external_write) {
1319 if (request.idempotency_key.empty()
1320 || endpoint.idempotency_header.empty()) {
1321 request.maximum_attempts = 1U;
1322 } else {
1323 request.headers.push_back(
1324 { endpoint.idempotency_header, request.idempotency_key }
1325 );
1326 }
1327 }
1328
1329 if (endpoint.authentication.mode != authentication_mode::none) {
1330 const char* raw
1331 = std::getenv(endpoint.authentication.secret_name.c_str());
1332 if (raw == nullptr || *raw == '\0') {
1333 return policy_failure(
1335 "required runtime secret is unavailable"
1336 );
1337 }
1338 if (!safe_header_value(raw, 16U * 1024U)) {
1339 return policy_failure(
1341 "required runtime secret is not a safe header value"
1342 );
1343 }
1344 const std::string value = endpoint.authentication.mode
1345 == authentication_mode::bearer_environment
1346 ? "Bearer " + std::string(raw)
1347 : std::string(raw);
1348 request.headers.push_back(
1349 { endpoint.authentication.header_name, value }
1350 );
1351 }
1352
1353 const bool cacheable
1354 = request.operation != transport_operation::external_write;
1355 const std::string cache_key
1356 = cacheable ? request_cache_key(request) : std::string {};
1357 const auto cached = cacheable
1359 : std::nullopt;
1360 if (cached) {
1361 const auto now
1362 = std::chrono::duration_cast<std::chrono::seconds>(
1363 std::chrono::system_clock::now().time_since_epoch()
1364 )
1365 .count();
1366 const bool fresh = now >= cached->stored_unix
1367 && now - cached->stored_unix
1368 <= endpoint.policy.cache.ttl.count();
1369 if (request.freshness == freshness_policy::offline_only) {
1370 return cached_receipt(request, *cached, delivery_mode::offline);
1371 }
1372 if (request.freshness == freshness_policy::cache_allowed && fresh) {
1373 return cached_receipt(
1374 request, *cached, delivery_mode::cache_validated
1375 );
1376 }
1377 if (request.freshness == freshness_policy::stale_allowed) {
1378 return cached_receipt(
1379 request, *cached,
1382 );
1383 }
1384 }
1385 if (request.freshness == freshness_policy::offline_only) {
1386 return policy_failure(
1388 "offline_only request has no valid cached artifact"
1389 );
1390 }
1391
1392 const auto admission_deadline
1393 = std::chrono::steady_clock::now() + endpoint.policy.timeouts.pool;
1394 std::shared_ptr<flight_state> flight;
1395 if (cacheable) {
1396 std::unique_lock lock(flights_mutex);
1397 if (const auto existing = flights.find(cache_key);
1398 existing != flights.end()) {
1399 flight = existing->second;
1400 if (!flight->condition.wait_until(
1401 lock, admission_deadline,
1402 [&] { return flight->complete; }
1403 )) {
1404 return policy_failure(
1406 "equivalent-read single-flight wait timed out"
1407 );
1408 }
1409 acquired_artifact_v1 result = flight->result;
1410 result.artifact_id = "artifact-" + request.request_id;
1411 result.request_id = request.request_id;
1412 result.door_id = request.door_id;
1413 result.operation = request.operation;
1414 result.source_url = request.url;
1415 if (result.delivered()) {
1416 result.delivered_via = delivery_mode::cache_validated;
1417 result.attempts = 0U;
1418 }
1419 return result;
1420 }
1421 flight = std::make_shared<flight_state>();
1422 flights.emplace(cache_key, flight);
1423 }
1424 if (!door.admission->acquire(admission_deadline)) {
1425 acquired_artifact_v1 result = policy_failure(
1427 "door concurrency or pacing admission timed out"
1428 );
1429 complete_flight(cache_key, flight, result);
1430 return result;
1431 }
1432 admission_lease door_lease(door.admission);
1433 if (!endpoint.admission->acquire(admission_deadline)) {
1434 acquired_artifact_v1 result = policy_failure(
1436 "endpoint concurrency or pacing admission timed out"
1437 );
1438 complete_flight(cache_key, flight, result);
1439 return result;
1440 }
1441 admission_lease endpoint_lease(endpoint.admission);
1442
1443 acquired_artifact_v1 result = byte_transport.execute(request);
1444 if (result.delivered() && cacheable && result.http_status >= 200
1445 && result.http_status < 300) {
1446 write_cache_entry(cache_root, cache_key, result);
1447 }
1448 complete_flight(cache_key, flight, result);
1449 return result;
1450 }
bool within_base_url(const parsed_url &request, const parsed_url &base)
void write_cache_entry(const fs::path &cache_root, const std::string &cache_key, const acquired_artifact_v1 &acquired)
bool safe_header_value(const std::string_view value, const std::size_t maximum_bytes)
acquired_artifact_v1 policy_failure(const fetch_request_v1 &request, const transport_status status, std::string message)
acquired_artifact_v1 cached_receipt(const fetch_request_v1 &request, const cached_artifact &cached, const delivery_mode mode)
std::optional< cached_artifact > read_cache_entry(const fs::path &root, const fs::path &cache_root, const std::string &cache_key)
void complete_flight(const std::string &cache_key, const std::shared_ptr< flight_state > &flight, const acquired_artifact_v1 &result) const

References arachne::pheidippides::admission_timeout, arachne::pheidippides::acquired_artifact_v1::artifact_id, arachne::pheidippides::acquired_artifact_v1::attempts, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::authentication, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::base_url, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::bearer_environment, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::bulk_capable, arachne::pheidippides::bulk_snapshot, arachne::pheidippides::cache_allowed, arachne::pheidippides::cache_miss, arachne::pheidippides::cache_validated, complete_flight(), arachne::pheidippides::acquired_artifact_v1::delivered(), arachne::pheidippides::acquired_artifact_v1::delivered_via, arachne::pheidippides::acquired_artifact_v1::door_id, arachne::pheidippides::fetch_request_v1::door_id, arachne::pheidippides::door_policy_rejected, arachne::pheidippides::fetch_request_v1::endpoint_id, arachne::pheidippides::external_write, arachne::pheidippides::fetch_request_v1::freshness, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::authentication_settings::header_name, arachne::pheidippides::acquired_artifact_v1::http_status, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::idempotency_header, arachne::pheidippides::fetch_request_v1::idempotency_key, arachne::pheidippides::fetch_request_v1::max_bytes, arachne::pheidippides::fetch_request_v1::maximum_attempts, arachne::pheidippides::fetch_request_v1::method, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::authentication_settings::mode, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::none, arachne::pheidippides::offline, arachne::pheidippides::offline_only, arachne::pheidippides::acquired_artifact_v1::operation, arachne::pheidippides::fetch_request_v1::operation, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::request_cache_key(), arachne::pheidippides::acquired_artifact_v1::request_id, arachne::pheidippides::fetch_request_v1::request_id, arachne::pheidippides::fetch_request_v1::respect_retry_after, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::resumable_download, arachne::pheidippides::resume_download, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::authentication_settings::secret_name, arachne::pheidippides::acquired_artifact_v1::source_url, arachne::pheidippides::stale, arachne::pheidippides::stale_allowed, arachne::pheidippides::fetch_request_v1::target_artifact_ref, arachne::pheidippides::fetch_request_v1::url, arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::within_base_url(), and arachne::pheidippides::anonymous_namespace{hardened_transport.cpp}::endpoint_configuration::write_enabled.

Here is the call graph for this function:

◆ execute() [2/2]

acquired_artifact_v1 arachne::pheidippides::hardened_transport::implementation::execute ( const fetch_request_v1 & original) const
inlinenodiscard

Definition at line 1194 of file hardened_transport.cpp.

1194 {
1195 if (original.target_artifact_ref == ".pheidippides-cache"
1196 || original.target_artifact_ref.starts_with(
1197 ".pheidippides-cache/"
1198 )) {
1199 return policy_failure(
1201 "request output_ref targets reserved transport metadata"
1202 );
1203 }
1204 const auto door_iterator = doors.find(original.door_id);
1205 if (door_iterator == doors.end()) {
1206 return policy_failure(
1208 "request names an unknown door_id"
1209 );
1210 }
1211 const door_configuration& door = door_iterator->second;
1212 const auto endpoint_iterator
1213 = door.endpoints.find(original.endpoint_id);
1214 if (endpoint_iterator == door.endpoints.end()) {
1215 return policy_failure(
1217 "request names an unknown endpoint_id for its door"
1218 );
1219 }
1220 const endpoint_configuration& endpoint = endpoint_iterator->second;
1221 if (!endpoint.allowed_methods.contains(original.method)) {
1222 return policy_failure(
1224 "request method is disabled for this endpoint"
1225 );
1226 }
1227 if (original.operation == transport_operation::bulk_snapshot
1228 && !endpoint.bulk_capable) {
1229 return policy_failure(
1231 "endpoint is not declared bulk-capable"
1232 );
1233 }
1234 if (original.operation == transport_operation::resume_download
1235 && !endpoint.resumable_download) {
1236 return policy_failure(
1238 "endpoint is not declared resumable"
1239 );
1240 }
1241 if (original.operation == transport_operation::external_write
1242 && !endpoint.write_enabled) {
1243 return policy_failure(
1245 "external writes are disabled for this endpoint"
1246 );
1247 }
1248 if (original.operation == transport_operation::external_write
1249 && !original.idempotency_key.empty()
1250 && !safe_header_value(original.idempotency_key, 512U)) {
1251 return policy_failure(
1253 "the external-write idempotency key is not a safe header value"
1254 );
1255 }
1256 parsed_url request_url;
1257 try {
1258 request_url = parse_absolute_url(original.url, "request locator");
1259 } catch (const std::exception& error) {
1260 return policy_failure(
1261 original, transport_status::door_policy_rejected, error.what()
1262 );
1263 }
1264 if (!within_base_url(request_url, endpoint.base_url)) {
1265 return policy_failure(
1267 "request locator is outside the endpoint base URL"
1268 );
1269 }
1270 for (const auto& header : original.headers) {
1271 if (sensitive_header(header.name)) {
1272 return policy_failure(
1274 "sensitive request headers must come from a runtime secret "
1275 "reference"
1276 );
1277 }
1278 if (original.operation == transport_operation::external_write
1279 && !endpoint.idempotency_header.empty()
1280 && ascii_lower(header.name)
1281 == ascii_lower(endpoint.idempotency_header)) {
1282 return policy_failure(
1284 "the configured idempotency header is managed by the "
1285 "transport"
1286 );
1287 }
1288 }
1289
1290 fetch_request_v1 request = original;
1291 request.timeout
1292 = std::min(request.timeout, endpoint.policy.timeouts.total);
1293 request.connect_timeout = std::min(
1294 request.connect_timeout, endpoint.policy.timeouts.connect
1295 );
1296 request.read_timeout
1297 = std::min(request.read_timeout, endpoint.policy.timeouts.read);
1298 request.write_timeout
1299 = std::min(request.write_timeout, endpoint.policy.timeouts.write);
1300 request.max_bytes = std::min(
1301 request.max_bytes, endpoint.policy.maximum_artifact_bytes
1302 );
1303 request.maximum_attempts = std::min(
1304 request.maximum_attempts, endpoint.policy.retry.maximum_attempts
1305 );
1306 request.initial_retry_delay = endpoint.policy.retry.initial_delay;
1307 request.maximum_retry_delay = endpoint.policy.retry.maximum_delay;
1308 request.total_retry_delay_budget
1309 = endpoint.policy.retry.total_delay_budget;
1310 request.respect_retry_after = endpoint.policy.retry.respect_retry_after;
1311 request.redirects.follow = endpoint.policy.redirects.follow;
1312 request.redirects.maximum_redirects
1313 = endpoint.policy.redirects.maximum_redirects;
1314 request.redirects.allow_https_to_http
1315 = endpoint.policy.redirects.allow_https_to_http;
1316 request.redirects.allowed_hosts
1317 = endpoint.policy.redirects.allowed_hosts;
1318 if (request.operation == transport_operation::external_write) {
1319 if (request.idempotency_key.empty()
1320 || endpoint.idempotency_header.empty()) {
1321 request.maximum_attempts = 1U;
1322 } else {
1323 request.headers.push_back(
1324 { endpoint.idempotency_header, request.idempotency_key }
1325 );
1326 }
1327 }
1328
1329 if (endpoint.authentication.mode != authentication_mode::none) {
1330 const char* raw
1331 = std::getenv(endpoint.authentication.secret_name.c_str());
1332 if (raw == nullptr || *raw == '\0') {
1333 return policy_failure(
1335 "required runtime secret is unavailable"
1336 );
1337 }
1338 if (!safe_header_value(raw, 16U * 1024U)) {
1339 return policy_failure(
1341 "required runtime secret is not a safe header value"
1342 );
1343 }
1344 const std::string value = endpoint.authentication.mode
1345 == authentication_mode::bearer_environment
1346 ? "Bearer " + std::string(raw)
1347 : std::string(raw);
1348 request.headers.push_back(
1349 { endpoint.authentication.header_name, value }
1350 );
1351 }
1352
1353 const bool cacheable
1354 = request.operation != transport_operation::external_write;
1355 const std::string cache_key
1356 = cacheable ? request_cache_key(request) : std::string {};
1357 const auto cached = cacheable
1359 : std::nullopt;
1360 if (cached) {
1361 const auto now
1362 = std::chrono::duration_cast<std::chrono::seconds>(
1363 std::chrono::system_clock::now().time_since_epoch()
1364 )
1365 .count();
1366 const bool fresh = now >= cached->stored_unix
1367 && now - cached->stored_unix
1368 <= endpoint.policy.cache.ttl.count();
1369 if (request.freshness == freshness_policy::offline_only) {
1370 return cached_receipt(request, *cached, delivery_mode::offline);
1371 }
1372 if (request.freshness == freshness_policy::cache_allowed && fresh) {
1373 return cached_receipt(
1374 request, *cached, delivery_mode::cache_validated
1375 );
1376 }
1377 if (request.freshness == freshness_policy::stale_allowed) {
1378 return cached_receipt(
1379 request, *cached,
1382 );
1383 }
1384 }
1385 if (request.freshness == freshness_policy::offline_only) {
1386 return policy_failure(
1388 "offline_only request has no valid cached artifact"
1389 );
1390 }
1391
1392 const auto admission_deadline
1393 = std::chrono::steady_clock::now() + endpoint.policy.timeouts.pool;
1394 std::shared_ptr<flight_state> flight;
1395 if (cacheable) {
1396 std::unique_lock lock(flights_mutex);
1397 if (const auto existing = flights.find(cache_key);
1398 existing != flights.end()) {
1399 flight = existing->second;
1400 if (!flight->condition.wait_until(
1401 lock, admission_deadline,
1402 [&] { return flight->complete; }
1403 )) {
1404 return policy_failure(
1406 "equivalent-read single-flight wait timed out"
1407 );
1408 }
1409 acquired_artifact_v1 result = flight->result;
1410 result.artifact_id = "artifact-" + request.request_id;
1411 result.request_id = request.request_id;
1412 result.door_id = request.door_id;
1413 result.operation = request.operation;
1414 result.source_url = request.url;
1415 if (result.delivered()) {
1416 result.delivered_via = delivery_mode::cache_validated;
1417 result.attempts = 0U;
1418 }
1419 return result;
1420 }
1421 flight = std::make_shared<flight_state>();
1422 flights.emplace(cache_key, flight);
1423 }
1424 if (!door.admission->acquire(admission_deadline)) {
1425 acquired_artifact_v1 result = policy_failure(
1427 "door concurrency or pacing admission timed out"
1428 );
1429 complete_flight(cache_key, flight, result);
1430 return result;
1431 }
1432 admission_lease door_lease(door.admission);
1433 if (!endpoint.admission->acquire(admission_deadline)) {
1434 acquired_artifact_v1 result = policy_failure(
1436 "endpoint concurrency or pacing admission timed out"
1437 );
1438 complete_flight(cache_key, flight, result);
1439 return result;
1440 }
1441 admission_lease endpoint_lease(endpoint.admission);
1442
1443 acquired_artifact_v1 result = byte_transport.execute(request);
1444 if (result.delivered() && cacheable && result.http_status >= 200
1445 && result.http_status < 300) {
1446 write_cache_entry(cache_root, cache_key, result);
1447 }
1448 complete_flight(cache_key, flight, result);
1449 return result;
1450 }

Member Data Documentation

◆ artifact_root

fs::path arachne::pheidippides::hardened_transport::implementation::artifact_root

Definition at line 1469 of file hardened_transport.cpp.

◆ byte_transport

transport arachne::pheidippides::hardened_transport::implementation::byte_transport

Definition at line 1471 of file hardened_transport.cpp.

◆ cache_root

fs::path arachne::pheidippides::hardened_transport::implementation::cache_root

Definition at line 1470 of file hardened_transport.cpp.

◆ doors

std::map< std::string, door_configuration, std::less<> > arachne::pheidippides::hardened_transport::implementation::doors

Definition at line 1472 of file hardened_transport.cpp.

◆ flights

std::map< std::string, std::shared_ptr< flight_state >, std::less<> > arachne::pheidippides::hardened_transport::implementation::flights
mutable

Definition at line 1475 of file hardened_transport.cpp.

◆ flights_mutex

std::mutex arachne::pheidippides::hardened_transport::implementation::flights_mutex
mutable

Definition at line 1473 of file hardened_transport.cpp.


The documentation for this struct was generated from the following file: