9 #include "XrdVersion.hh"
18 #include <unordered_map>
23 #include "INIReader.h"
26 #include "scitokens/scitokens.h"
51 bool has_entry =
false;
58 ss << (has_entry ?
", " :
"") <<
"info";
62 ss << (has_entry ?
", " :
"") <<
"warning";
66 ss << (has_entry ?
", " :
"") <<
"error";
72 inline uint64_t monotonic_time() {
74 #ifdef CLOCK_MONOTONIC_COARSE
75 clock_gettime(CLOCK_MONOTONIC_COARSE, &tp);
77 clock_gettime(CLOCK_MONOTONIC, &tp);
79 return tp.tv_sec + (tp.tv_nsec >= 500000000);
84 int new_privs = privs;
152 std::unordered_map<std::string, std::unique_ptr<std::stringstream>> rule_map;
153 for (
const auto &rule : rules) {
154 auto iter = rule_map.find(rule.second);
155 if (iter == rule_map.end()) {
156 auto result = rule_map.insert(std::make_pair(rule.second, std::make_unique<std::stringstream>()));
158 *(iter->second) << OpToName(rule.first);
160 *(iter->second) <<
"," << OpToName(rule.first);
163 std::stringstream ss;
165 for (
const auto &val : rule_map) {
166 ss << (first ?
"" :
";") << val.first <<
":" << val.second->str();
172 bool MakeCanonical(
const std::string &path, std::string &result)
174 if (path.empty() || path[0] !=
'/') {
return false;}
177 std::vector<std::string> components;
179 while (path.size() > pos && path[pos] ==
'/') {pos++;}
180 auto next_pos = path.find_first_of(
"/", pos);
181 auto next_component = path.substr(pos, next_pos - pos);
183 if (next_component.empty() || next_component ==
".") {
continue;}
184 else if (next_component ==
"..") {
185 if (!components.empty()) {
186 components.pop_back();
189 components.emplace_back(next_component);
191 }
while (pos != std::string::npos);
192 if (components.empty()) {
196 std::stringstream ss;
197 for (
const auto &comp : components) {
204 void ParseCanonicalPaths(
const std::string &path, std::vector<std::string> &results)
208 while (path.size() > pos && (path[pos] ==
',' || path[pos] ==
' ')) {pos++;}
209 auto next_pos = path.find_first_of(
", ", pos);
210 auto next_path = path.substr(pos, next_pos - pos);
212 if (!next_path.empty()) {
213 std::string canonical_path;
214 if (MakeCanonical(next_path, canonical_path)) {
215 results.emplace_back(std::move(canonical_path));
218 }
while (pos != std::string::npos);
223 IssuerConfig(
const std::string &issuer_name,
224 const std::string &issuer_url,
225 const std::vector<std::string> &base_paths,
226 const std::vector<std::string> &restricted_paths,
228 uint32_t authz_strategy,
229 const std::string &default_user,
230 const std::string &username_claim,
231 const std::string &groups_claim,
232 const std::vector<MapRule> rules,
235 : m_map_subject(map_subject || !username_claim.empty()),
236 m_acceptable_authz(acceptable_authz),
237 m_required_authz(required_authz),
238 m_authz_strategy(authz_strategy),
241 m_default_user(default_user),
242 m_username_claim(username_claim),
243 m_groups_claim(groups_claim),
244 m_base_paths(base_paths),
245 m_restricted_paths(restricted_paths),
249 const bool m_map_subject;
252 const uint32_t m_authz_strategy;
253 const std::string m_name;
254 const std::string m_url;
255 const std::string m_default_user;
256 const std::string m_username_claim;
257 const std::string m_groups_claim;
258 const std::vector<std::string> m_base_paths;
259 const std::vector<std::string> m_restricted_paths;
260 const std::vector<MapRule> m_map_rules;
263 class OverrideINIReader:
public INIReader {
265 OverrideINIReader() {};
266 inline OverrideINIReader(std::string filename) {
267 _error = ini_parse(filename.c_str(), ValueHandler,
this);
269 inline OverrideINIReader(FILE *file) {
270 _error = ini_parse_file(file, ValueHandler,
this);
286 inline static int ValueHandler(
void* user,
const char* section,
const char* name,
288 OverrideINIReader* reader = (OverrideINIReader*)user;
289 std::string key = MakeKey(section, name);
292 reader->_values[key] = value;
293 reader->_sections.insert(section);
301 ParseTokenString(
const std::string ¶m,
XrdOucEnv *env, std::vector<std::string_view> &authz_list)
304 const char *authz = env->
Get(param.c_str());
305 if (!authz) {
return;}
306 std::string_view authz_view(authz);
313 if (authz_view.substr(0, 9) ==
"Bearer%20") {
314 authz_view = authz_view.substr(9);
316 pos = authz_view.find(
",");
317 authz_list.push_back(authz_view.substr(0, pos));
318 authz_view = authz_view.substr(pos + 1);
319 }
while (pos != std::string_view::npos);
326 return AccessRuleStr(m_rules);
333 std::stringstream ss;
334 ss <<
"mapped_username=" << m_username <<
", subject=" << m_token_subject
335 <<
", issuer=" << m_issuer;
336 if (!m_groups.empty()) {
339 for (
const auto &group : m_groups) {
340 ss << (first ?
"" :
",") << group;
344 if (!m_matcher.
empty()) {
345 ss <<
", authorizations=" << m_matcher.
str();
352 return monotonic_time() > m_expiry_time;
370 const std::vector<std::pair<std::unique_ptr<SubpathMatch>, std::string>> &required_issuers,
371 const std::vector<std::shared_ptr<XrdAccRules>> &access_rules_list)
376 switch (client_oper) {
413 for (
const auto &info : required_issuers) {
415 if (info.first->apply(oper, path)) {
416 bool has_authz =
false;
418 for (
const auto &rules : access_rules_list) {
419 if (rules->get_issuer() == info.second && rules->apply(oper, path)) {
440 enum class AuthzBehavior {
449 m_parms(parms ? parms :
""),
450 m_next_clean(monotonic_time() + m_expiry_secs),
451 m_log(lp,
"scitokens_")
453 pthread_rwlock_init(&m_config_lock,
nullptr);
454 m_config_lock_initialized =
true;
455 m_log.
Say(
"++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
457 throw std::runtime_error(
"Failed to configure SciTokens authorization.");
462 if (m_config_lock_initialized) {
463 pthread_rwlock_destroy(&m_config_lock);
472 std::vector<std::string_view> authz_list;
473 authz_list.reserve(1);
479 ParseTokenString(
"authz", env, authz_list);
480 ParseTokenString(
"access_token", env, authz_list);
482 if (Entity && !strcmp(
"ztn", Entity->
prot) && Entity->
creds &&
485 authz_list.push_back(Entity->
creds);
488 if (authz_list.empty()) {
489 return OnMissing(Entity, path, oper, env);
494 if (authz_list.size() > 10) {
495 m_log.
Log(
LogMask::Warning,
"Access",
"Request had more than 10 tokens attached; ignoring");
496 return OnMissing(Entity, path, oper, env);
500 std::vector<std::shared_ptr<XrdAccRules>> access_rules_list;
501 uint64_t now = monotonic_time();
503 for (
const auto &authz : authz_list) {
504 std::shared_ptr<XrdAccRules> access_rules;
506 std::lock_guard<std::mutex> guard(m_mutex);
507 const auto iter = m_map.find(authz);
508 if (iter != m_map.end() && !iter->second->expired()) {
509 access_rules = iter->second;
513 m_log.
Log(
LogMask::Debug,
"Access",
"Token not found in recent cache; parsing.");
515 uint64_t cache_expiry;
517 std::string username;
518 std::string token_subject;
520 std::vector<MapRule> map_rules;
521 std::vector<std::string> groups;
522 uint32_t authz_strategy;
524 if (GenerateAcls(authz, cache_expiry, rules, username, token_subject, issuer, map_rules, groups, authz_strategy, acceptable_authz)) {
525 access_rules.reset(
new XrdAccRules(now + cache_expiry, username, token_subject, issuer, map_rules, groups, authz_strategy, acceptable_authz));
526 access_rules->parse(rules);
532 m_log.
Log(
LogMask::Debug,
"Access",
"New valid token", access_rules->str().c_str());
534 }
catch (std::exception &exc) {
535 m_log.
Log(
LogMask::Warning,
"Access",
"Error generating ACLs for authorization", exc.what());
538 std::lock_guard<std::mutex> guard(m_mutex);
539 m_map[std::string(authz)] = access_rules;
541 m_log.
Log(
LogMask::Debug,
"Access",
"Cached token", access_rules->str().c_str());
543 access_rules_list.push_back(access_rules);
545 if (access_rules_list.empty()) {
546 return OnMissing(Entity, path, oper, env);
548 std::string_view path_view(path, strlen(path));
552 return OnMissing(Entity, path, oper, env);
566 for (
const auto &access_rules : access_rules_list) {
568 if (!access_rules->acceptable_authz(oper)) {
569 m_log.
Log(
LogMask::Debug,
"Access",
"Issuer is not acceptable for given operation:", access_rules->get_issuer().c_str());
574 new_secentity.
vorg =
nullptr;
575 new_secentity.
grps =
nullptr;
576 new_secentity.
role =
nullptr;
579 const auto &issuer = access_rules->get_issuer();
580 if (!issuer.empty()) {
581 new_secentity.
vorg = strdup(issuer.c_str());
583 bool group_success =
false;
584 if ((access_rules->get_authz_strategy() &
IssuerAuthz::Group) && access_rules->groups().size()) {
585 std::stringstream ss;
586 for (
const auto &grp : access_rules->groups()) {
589 const auto &groups_str = ss.str();
590 new_secentity.
grps =
static_cast<char*
>(malloc(groups_str.size() + 1));
591 if (new_secentity.
grps) {
592 memcpy(new_secentity.
grps, groups_str.c_str(), groups_str.size());
593 new_secentity.
grps[groups_str.size()] =
'\0';
595 group_success =
true;
598 std::string username;
599 bool mapping_success =
false;
600 bool scope_success =
false;
601 username = access_rules->get_username(path_view);
603 mapping_success = (access_rules->get_authz_strategy() &
IssuerAuthz::Mapping) && !username.empty();
604 scope_success = (access_rules->get_authz_strategy() &
IssuerAuthz::Capability) && access_rules->apply(oper, path_view);
606 std::stringstream ss;
607 ss <<
"Grant authorization based on scopes for operation=" << OpToName(oper) <<
", path=" << path;
611 if (!scope_success && !mapping_success && !group_success) {
612 auto returned_accs = OnMissing(&new_secentity, path, oper, env);
614 if (new_secentity.
vorg !=
nullptr) free(new_secentity.
vorg);
615 if (new_secentity.
grps !=
nullptr) free(new_secentity.
grps);
616 if (new_secentity.
role !=
nullptr) free(new_secentity.
role);
618 return returned_accs;
622 if (scope_success && username.empty()) {
623 username = access_rules->get_default_username();
628 if (scope_success || mapping_success) {
630 Entity->
eaAPI->
Add(
"request.name", username,
true);
631 new_secentity.
eaAPI->
Add(
"request.name", username,
true);
640 const auto &token_subject = access_rules->get_token_subject();
641 if (!token_subject.empty()) {
642 Entity->
eaAPI->
Add(
"token.subject", token_subject,
true);
651 if (Entity->
secMon && scope_success && returned_op &&
Mon_isIO(oper))
652 Mon_Report(new_secentity, token_subject, username);
655 if (new_secentity.
vorg !=
nullptr) free(new_secentity.
vorg);
656 if (new_secentity.
grps !=
nullptr) free(new_secentity.
grps);
657 if (new_secentity.
role !=
nullptr) free(new_secentity.
role);
662 return OnMissing(Entity, path, oper, env);
676 for (
auto it: m_issuers) {
680 issuers.push_back(issuer_info);
686 virtual bool Validate(
const char *token, std::string &
emsg,
long long *expT,
694 if (!strncmp(token,
"Bearer%20", 9)) token += 9;
695 pthread_rwlock_rdlock(&m_config_lock);
696 auto retval = scitoken_deserialize(token, &scitoken, &m_valid_issuers_array[0], &err_msg);
697 pthread_rwlock_unlock(&m_config_lock);
711 {
char *value =
nullptr;
712 if (!scitoken_get_claim_string(scitoken,
"sub", &value, &err_msg))
713 Entity->
name = strdup(value);
718 if (expT && scitoken_get_expiration(scitoken, expT, &err_msg)) {
726 scitoken_destroy(scitoken);
744 return (m_chain ? m_chain->
Test(priv, oper) : 0);
755 switch (m_authz_behavior) {
756 case AuthzBehavior::PASSTHROUGH:
758 case AuthzBehavior::ALLOW:
760 case AuthzBehavior::DENY:
767 bool GenerateAcls(
const std::string_view &authz, uint64_t &cache_expiry,
AccessRulesRaw &rules, std::string &username, std::string &token_subject, std::string &issuer, std::vector<MapRule> &map_rules, std::vector<std::string> &groups, uint32_t &authz_strategy,
AuthzSetting &acceptable_authz) {
770 bool looks_good =
true;
771 int separator_count = 0;
772 for (
auto cur_char = authz.data(); *cur_char; cur_char++) {
773 if (*cur_char ==
'.') {
775 if (separator_count > 2) {
779 if (!(*cur_char >= 65 && *cur_char <= 90) &&
780 !(*cur_char >= 97 && *cur_char <= 122) &&
781 !(*cur_char >= 48 && *cur_char <= 57) &&
782 (*cur_char != 43) && (*cur_char != 47) &&
783 (*cur_char != 45) && (*cur_char != 95))
789 if ((separator_count != 2) || (!looks_good)) {
790 m_log.
Log(
LogMask::Debug,
"Parse",
"Token does not appear to be a valid JWT; skipping.");
795 SciToken token =
nullptr;
796 pthread_rwlock_rdlock(&m_config_lock);
797 auto retval = scitoken_deserialize(authz.data(), &token, &m_valid_issuers_array[0], &err_msg);
798 pthread_rwlock_unlock(&m_config_lock);
807 if (scitoken_get_expiration(token, &expiry, &err_msg)) {
808 m_log.
Log(
LogMask::Warning,
"GenerateAcls",
"Unable to determine token expiration:", err_msg);
810 scitoken_destroy(token);
814 expiry = std::max(
static_cast<int64_t
>(monotonic_time() - expiry),
815 static_cast<int64_t
>(60));
820 char *value =
nullptr;
821 if (scitoken_get_claim_string(token,
"iss", &value, &err_msg)) {
823 scitoken_destroy(token);
827 std::string token_issuer(value);
830 pthread_rwlock_rdlock(&m_config_lock);
831 auto enf = enforcer_create(token_issuer.c_str(), &m_audiences_array[0], &err_msg);
832 pthread_rwlock_unlock(&m_config_lock);
835 scitoken_destroy(token);
841 if (enforcer_generate_acls(enf, token, &acls, &err_msg)) {
842 scitoken_destroy(token);
843 enforcer_destroy(enf);
844 m_log.
Log(
LogMask::Warning,
"GenerateAcls",
"ACL generation from SciToken failed:", err_msg);
848 enforcer_destroy(enf);
850 pthread_rwlock_rdlock(&m_config_lock);
851 auto iter = m_issuers.find(token_issuer);
852 if (iter == m_issuers.end()) {
853 pthread_rwlock_unlock(&m_config_lock);
855 scitoken_destroy(token);
858 const auto config = iter->second;
859 pthread_rwlock_unlock(&m_config_lock);
863 std::vector<std::string> groups_parsed;
864 if (scitoken_get_claim_string_list(token, config.m_groups_claim.c_str(), &group_list, &err_msg) == 0) {
865 for (
int idx=0; group_list[idx]; idx++) {
866 groups_parsed.emplace_back(group_list[idx]);
868 scitoken_free_string_list(group_list);
875 if (scitoken_get_claim_string(token,
"sub", &value, &err_msg)) {
878 scitoken_destroy(token);
881 token_subject = std::string(value);
884 auto tmp_username = token_subject;
885 if (!config.m_username_claim.empty()) {
886 if (scitoken_get_claim_string(token, config.m_username_claim.c_str(), &value, &err_msg)) {
889 scitoken_destroy(token);
892 tmp_username = std::string(value);
894 }
else if (!config.m_map_subject) {
895 tmp_username = config.m_default_user;
898 for (
auto rule : config.m_map_rules) {
899 for (
auto path : config.m_base_paths) {
900 auto path_rule = rule;
901 path_rule.m_path_prefix = path + rule.m_path_prefix;
902 auto pos = path_rule.m_path_prefix.find(
"//");
903 if (pos != std::string::npos) {
904 path_rule.m_path_prefix.erase(pos + 1, 1);
906 map_rules.emplace_back(path_rule);
912 std::set<std::string> paths_write_seen;
913 std::set<std::string> paths_create_or_modify_seen;
914 std::vector<std::string> acl_paths;
915 acl_paths.reserve(config.m_restricted_paths.size() + 1);
916 while (acls[idx].resource && acls[idx++].authz) {
918 const auto &acl_path = acls[idx-1].resource;
919 const auto &acl_authz = acls[idx-1].authz;
920 if (config.m_restricted_paths.empty()) {
921 acl_paths.push_back(acl_path);
923 auto acl_path_size = strlen(acl_path);
924 for (
const auto &restricted_path : config.m_restricted_paths) {
927 if (!strncmp(acl_path, restricted_path.c_str(), restricted_path.size())) {
930 if (acl_path_size > restricted_path.size() && acl_path[restricted_path.size()] !=
'/') {
933 acl_paths.push_back(acl_path);
939 if (!strncmp(acl_path, restricted_path.c_str(), acl_path_size)) {
946 if ((restricted_path.size() > acl_path_size && restricted_path[acl_path_size] !=
'/') && (acl_path_size != 1)) {
949 acl_paths.push_back(restricted_path);
953 for (
const auto &acl_path : acl_paths) {
954 for (
const auto &base_path : config.m_base_paths) {
955 if (!acl_path[0] || acl_path[0] !=
'/') {
continue;}
957 MakeCanonical(base_path + acl_path, path);
958 if (!strcmp(acl_authz,
"read")) {
959 xrd_rules.emplace_back(
AOP_Read, path);
961 xrd_rules.emplace_back(
AOP_Stat, path);
962 }
else if (!strcmp(acl_authz,
"create")) {
963 paths_create_or_modify_seen.insert(path);
968 xrd_rules.emplace_back(
AOP_Stat, path);
969 }
else if (!strcmp(acl_authz,
"modify")) {
970 paths_create_or_modify_seen.insert(path);
977 xrd_rules.emplace_back(
AOP_Stat, path);
979 }
else if (!strcmp(acl_authz,
"write")) {
980 paths_write_seen.insert(path);
985 for (
const auto &write_path : paths_write_seen) {
986 if (paths_create_or_modify_seen.find(write_path) == paths_create_or_modify_seen.end()) {
988 xrd_rules.emplace_back(
AOP_Create, write_path);
989 xrd_rules.emplace_back(
AOP_Mkdir, write_path);
990 xrd_rules.emplace_back(
AOP_Rename, write_path);
991 xrd_rules.emplace_back(
AOP_Insert, write_path);
992 xrd_rules.emplace_back(
AOP_Update, write_path);
993 xrd_rules.emplace_back(
AOP_Stat, write_path);
994 xrd_rules.emplace_back(
AOP_Chmod, write_path);
995 xrd_rules.emplace_back(
AOP_Delete, write_path);
998 authz_strategy = config.m_authz_strategy;
1000 cache_expiry = expiry;
1001 rules = std::move(xrd_rules);
1002 username = std::move(tmp_username);
1003 issuer = std::move(token_issuer);
1004 groups = std::move(groups_parsed);
1005 acceptable_authz = config.m_acceptable_authz;
1015 char *config_filename =
nullptr;
1022 m_log.
Emsg(
"Config", -result,
"parsing config file", config_filename);
1027 std::string map_filename;
1028 while (scitokens_conf.GetLine()) {
1030 scitokens_conf.GetToken();
1031 if (!(val = scitokens_conf.GetToken())) {
1032 m_log.
Emsg(
"Config",
"scitokens.trace requires an argument. Usage: scitokens.trace [all|error|warning|info|debug|none]");
1041 else if (!strcmp(val,
"none")) {m_log.
setMsgMask(0);}
1042 else {m_log.
Emsg(
"Config",
"scitokens.trace encountered an unknown directive:", val);
return false;}
1043 }
while ((val = scitokens_conf.GetToken()));
1048 auto tlsCtx =
static_cast<XrdTlsContext*
>(xrdEnv ? xrdEnv->GetPtr(
"XrdTlsContext*") :
nullptr);
1051 if (params && !params->cafile.empty()) {
1052 #ifdef HAVE_SCITOKEN_CONFIG_SET_STR
1053 scitoken_config_set_str(
"tls.ca_file", params->cafile.c_str(),
nullptr);
1055 m_log.
Log(
LogMask::Warning,
"Config",
"tls.ca_file is set but the platform's libscitokens.so does not support setting config parameters");
1063 bool ParseMapfile(
const std::string &filename, std::vector<MapRule> &rules)
1065 std::stringstream ss;
1066 std::ifstream mapfile(filename);
1067 if (!mapfile.is_open())
1069 ss <<
"Error opening mapfile (" << filename <<
"): " << strerror(errno);
1073 picojson::value val;
1074 auto err = picojson::parse(val, mapfile);
1076 ss <<
"Unable to parse mapfile (" << filename <<
") as json: " << err;
1080 if (!val.is<picojson::array>()) {
1081 ss <<
"Top-level element of the mapfile " << filename <<
" must be a list";
1085 const auto& rule_list = val.get<picojson::array>();
1086 for (
const auto &rule : rule_list)
1088 if (!rule.is<picojson::object>()) {
1089 ss <<
"Mapfile " << filename <<
" must be a list of JSON objects; found non-object";
1096 std::string username;
1098 bool ignore =
false;
1099 for (
const auto &entry : rule.get<picojson::object>()) {
1100 if (!entry.second.is<std::string>()) {
1101 if (entry.first !=
"result" && entry.first !=
"group" && entry.first !=
"sub" && entry.first !=
"path") {
continue;}
1102 ss <<
"In mapfile " << filename <<
", rule entry for " << entry.first <<
" has non-string value";
1106 if (entry.first ==
"result") {
1107 result = entry.second.get<std::string>();
1109 else if (entry.first ==
"group") {
1110 group = entry.second.get<std::string>();
1112 else if (entry.first ==
"sub") {
1113 sub = entry.second.get<std::string>();
1114 }
else if (entry.first ==
"username") {
1115 username = entry.second.get<std::string>();
1116 }
else if (entry.first ==
"path") {
1117 std::string norm_path;
1118 if (!MakeCanonical(entry.second.get<std::string>(), norm_path)) {
1119 ss <<
"In mapfile " << filename <<
" encountered a path " << entry.second.get<std::string>()
1120 <<
" that cannot be normalized";
1125 }
else if (entry.first ==
"ignore") {
1130 if (ignore)
continue;
1133 ss <<
"In mapfile " << filename <<
" encountered a rule without a 'result' attribute";
1137 rules.emplace_back(sub, username, path, group, result);
1147 bool ParseAuthzSetting(OverrideINIReader &reader,
const std::string §ion,
const std::string &variable,
AuthzSetting &result) {
1148 auto authz_setting_str = reader.Get(section, variable,
"");
1150 if (authz_setting_str ==
"") {
1152 }
else if (authz_setting_str ==
"none") {
1154 }
else if (authz_setting_str ==
"all") {
1156 }
else if (authz_setting_str ==
"read") {
1158 }
else if (authz_setting_str ==
"write") {
1161 std::stringstream ss;
1162 ss <<
"Failed to parse " << variable <<
" in section " << section <<
": unknown authorization setting " << authz_setting_str;
1166 result = authz_setting;
1173 m_cfg_file =
"/etc/xrootd/scitokens.cfg";
1174 if (!m_parms.empty()) {
1176 std::vector<std::string> arg_list;
1178 while ((m_parms.size() > pos) && (m_parms[pos] ==
' ')) {pos++;}
1179 auto next_pos = m_parms.find_first_of(
", ", pos);
1180 auto next_arg = m_parms.substr(pos, next_pos - pos);
1182 if (!next_arg.empty()) {
1183 arg_list.emplace_back(std::move(next_arg));
1185 }
while (pos != std::string::npos);
1187 for (
const auto &arg : arg_list) {
1188 if (strncmp(arg.c_str(),
"config=", 7)) {
1189 m_log.
Log(
LogMask::Error,
"Reconfig",
"Ignoring unknown configuration argument:", arg.c_str());
1192 m_cfg_file = std::string(arg.c_str() + 7);
1195 m_log.
Log(
LogMask::Info,
"Reconfig",
"Parsing configuration file:", m_cfg_file.c_str());
1197 OverrideINIReader reader(m_cfg_file);
1198 if (reader.ParseError() < 0) {
1199 std::stringstream ss;
1200 ss <<
"Error opening config file (" << m_cfg_file <<
"): " << strerror(errno);
1203 }
else if (reader.ParseError()) {
1204 std::stringstream ss;
1205 ss <<
"Parse error on line " << reader.ParseError() <<
" of file " << m_cfg_file;
1209 std::vector<std::string> audiences;
1210 std::unordered_map<std::string, IssuerConfig> issuers;
1211 for (
const auto §ion : reader.Sections()) {
1212 std::string section_lower;
1213 std::transform(section.begin(), section.end(), std::back_inserter(section_lower),
1214 [](
unsigned char c){ return std::tolower(c); });
1216 if (section_lower.substr(0, 6) ==
"global") {
1217 auto audience = reader.Get(section,
"audience",
"");
1218 if (!audience.empty()) {
1221 while (audience.size() > pos && (audience[pos] ==
',' || audience[pos] ==
' ')) {pos++;}
1222 auto next_pos = audience.find_first_of(
", ", pos);
1223 auto next_aud = audience.substr(pos, next_pos - pos);
1225 if (!next_aud.empty()) {
1226 audiences.push_back(next_aud);
1228 }
while (pos != std::string::npos);
1230 audience = reader.Get(section,
"audience_json",
"");
1231 if (!audience.empty()) {
1232 picojson::value json_obj;
1233 auto err = picojson::parse(json_obj, audience);
1235 m_log.
Log(
LogMask::Error,
"Reconfig",
"Unable to parse audience_json:", err.c_str());
1238 if (!json_obj.is<picojson::value::array>()) {
1239 m_log.
Log(
LogMask::Error,
"Reconfig",
"audience_json must be a list of strings; not a list.");
1242 for (
const auto &val : json_obj.get<picojson::value::array>()) {
1243 if (!val.is<std::string>()) {
1244 m_log.
Log(
LogMask::Error,
"Reconfig",
"audience must be a list of strings; value is not a string.");
1247 audiences.push_back(val.get<std::string>());
1250 auto onmissing = reader.Get(section,
"onmissing",
"");
1251 if (onmissing ==
"passthrough") {
1252 m_authz_behavior = AuthzBehavior::PASSTHROUGH;
1253 }
else if (onmissing ==
"allow") {
1254 m_authz_behavior = AuthzBehavior::ALLOW;
1255 }
else if (onmissing ==
"deny") {
1256 m_authz_behavior = AuthzBehavior::DENY;
1257 }
else if (!onmissing.empty()) {
1258 m_log.
Log(
LogMask::Error,
"Reconfig",
"Unknown value for onmissing key:", onmissing.c_str());
1263 if (section_lower.substr(0, 7) !=
"issuer ") {
continue;}
1265 auto issuer = reader.Get(section,
"issuer",
"");
1266 if (issuer.empty()) {
1267 m_log.
Log(
LogMask::Error,
"Reconfig",
"Ignoring section because 'issuer' attribute is not set:",
1273 std::vector<MapRule> rules;
1274 auto name_mapfile = reader.Get(section,
"name_mapfile",
"");
1275 if (!name_mapfile.empty()) {
1276 if (!ParseMapfile(name_mapfile, rules)) {
1277 m_log.
Log(
LogMask::Error,
"Reconfig",
"Failed to parse mapfile; failing (re-)configuration", name_mapfile.c_str());
1280 m_log.
Log(
LogMask::Info,
"Reconfig",
"Successfully parsed SciTokens mapfile:", name_mapfile.c_str());
1284 auto base_path = reader.Get(section,
"base_path",
"");
1285 if (base_path.empty()) {
1286 m_log.
Log(
LogMask::Error,
"Reconfig",
"Ignoring section because 'base_path' attribute is not set:",
1292 while (section.size() > pos && std::isspace(section[pos])) {pos++;}
1294 auto name = section.substr(pos);
1296 m_log.
Log(
LogMask::Error,
"Reconfig",
"Invalid section name:", section.c_str());
1300 std::vector<std::string> base_paths;
1301 ParseCanonicalPaths(base_path, base_paths);
1303 auto restricted_path = reader.Get(section,
"restricted_path",
"");
1304 std::vector<std::string> restricted_paths;
1305 if (!restricted_path.empty()) {
1306 ParseCanonicalPaths(restricted_path, restricted_paths);
1309 auto default_user = reader.Get(section,
"default_user",
"");
1310 auto map_subject = reader.GetBoolean(section,
"map_subject",
false);
1311 auto username_claim = reader.Get(section,
"username_claim",
"");
1312 auto groups_claim = reader.Get(section,
"groups_claim",
"wlcg.groups");
1315 if (!ParseAuthzSetting(reader, section,
"required_authorization", required_authz)) {
1316 m_log.
Log(
LogMask::Error,
"Reconfig",
"Ignoring required_authorization and using default of 'none'");
1318 if (!ParseAuthzSetting(reader, section,
"acceptable_authorization", acceptable_authz)) {
1319 m_log.
Log(
LogMask::Error,
"Reconfig",
"Ignoring acceptable_authorization and using default of 'all'");
1322 auto authz_strategy_str = reader.Get(section,
"authorization_strategy",
"");
1323 uint32_t authz_strategy = 0;
1324 if (authz_strategy_str.empty()) {
1327 std::istringstream authz_strategy_stream(authz_strategy_str);
1328 std::string authz_str;
1329 while (
std::getline(authz_strategy_stream, authz_str,
' ')) {
1330 if (!strcasecmp(authz_str.c_str(),
"capability")) {
1332 }
else if (!strcasecmp(authz_str.c_str(),
"group")) {
1334 }
else if (!strcasecmp(authz_str.c_str(),
"mapping")) {
1337 m_log.
Log(
LogMask::Error,
"Reconfig",
"Unknown authorization strategy (ignoring):", authz_str.c_str());
1342 issuers.emplace(std::piecewise_construct,
1343 std::forward_as_tuple(issuer),
1344 std::forward_as_tuple(name, issuer, base_paths, restricted_paths,
1345 map_subject, authz_strategy, default_user, username_claim, groups_claim, rules,
1346 acceptable_authz, required_authz));
1352 for (
const auto &base_path : base_paths) {
1353 if (restricted_paths.empty()) {
1354 restricted_paths.emplace_back(
"/");
1356 for (
const auto &restricted_path : restricted_paths) {
1357 auto full_path = base_path +
"/" + restricted_path;
1358 std::string cleaned_path;
1359 MakeCanonical(full_path, cleaned_path);
1361 rules.emplace_back(
AOP_Read, cleaned_path);
1362 rules.emplace_back(
AOP_Stat, cleaned_path);
1364 rules.emplace_back(
AOP_Create, cleaned_path);
1365 rules.emplace_back(
AOP_Mkdir, cleaned_path);
1366 rules.emplace_back(
AOP_Stat, cleaned_path);
1370 m_required_issuers.emplace_back(std::make_unique<SubpathMatch>(rules), issuer);
1374 if (issuers.empty()) {
1378 pthread_rwlock_wrlock(&m_config_lock);
1380 m_audiences = std::move(audiences);
1382 m_audiences_array.resize(m_audiences.size() + 1);
1383 for (
const auto &audience : m_audiences) {
1384 m_audiences_array[idx++] = audience.c_str();
1386 m_audiences_array[idx] =
nullptr;
1388 m_issuers = std::move(issuers);
1389 m_valid_issuers_array.resize(m_issuers.size() + 1);
1391 for (
const auto &issuer : m_issuers) {
1392 m_valid_issuers_array[idx++] = issuer.first.c_str();
1394 m_valid_issuers_array[idx] =
nullptr;
1396 pthread_rwlock_unlock(&m_config_lock);
1399 pthread_rwlock_unlock(&m_config_lock);
1403 void Check(uint64_t now)
1405 if (now <= m_next_clean) {
return;}
1406 std::lock_guard<std::mutex> guard(m_mutex);
1408 for (
auto iter = m_map.begin(); iter != m_map.end(); ) {
1409 if (iter->second->expired()) {
1410 iter = m_map.erase(iter);
1417 m_next_clean = monotonic_time() + m_expiry_secs;
1420 bool m_config_lock_initialized{
false};
1422 pthread_rwlock_t m_config_lock;
1423 std::vector<std::string> m_audiences;
1424 std::vector<const char *> m_audiences_array;
1425 std::map<std::string, std::shared_ptr<XrdAccRules>, std::less<>> m_map;
1427 const std::string m_parms;
1428 std::vector<const char*> m_valid_issuers_array;
1431 std::vector<std::pair<std::unique_ptr<SubpathMatch>, std::string>> m_required_issuers;
1432 std::unordered_map<std::string, IssuerConfig> m_issuers;
1433 uint64_t m_next_clean{0};
1435 AuthzBehavior m_authz_behavior{AuthzBehavior::PASSTHROUGH};
1436 std::string m_cfg_file;
1438 static constexpr uint64_t m_expiry_secs = 60;
1447 }
catch (std::exception &) {
Access_Operation
The following are supported operations.
@ AOP_Delete
rm() or rmdir()
@ AOP_Update
open() r/w or append
@ AOP_Create
open() with create
@ AOP_Any
Special for getting privs.
@ AOP_Stat
exists(), stat()
@ AOP_Rename
mv() for source
@ AOP_Read
open() r/o, prepare()
@ AOP_Excl_Create
open() with O_EXCL|O_CREAT
@ AOP_Insert
mv() for target
@ AOP_Excl_Insert
mv() where destination doesn't exist.
std::string LogMaskToString(int mask)
XrdAccSciTokens * accSciTokens
bool AuthorizesRequiredIssuers(Access_Operation client_oper, const std::string_view &path, const std::vector< std::pair< std::unique_ptr< SubpathMatch >, std::string >> &required_issuers, const std::vector< std::shared_ptr< XrdAccRules >> &access_rules_list)
XrdSciTokensHelper * SciTokensHelper
XrdVERSIONINFO(XrdAccAuthorizeObject, XrdAccSciTokens)
void InitAccSciTokens(XrdSysLogger *lp, const char *cfn, const char *parm, XrdAccAuthorize *accP, XrdOucEnv *envP)
XrdAccAuthorize * XrdAccAuthorizeObjAdd(XrdSysLogger *lp, const char *cfn, const char *parm, XrdOucEnv *envP, XrdAccAuthorize *accP)
XrdAccAuthorize * XrdAccAuthorizeObject(XrdSysLogger *lp, const char *cfn, const char *parm)
XrdAccAuthorize * XrdAccAuthorizeObject2(XrdSysLogger *lp, const char *cfn, const char *parm, XrdOucEnv *envP)
std::vector< std::pair< Access_Operation, std::string > > AccessRulesRaw
void getline(uchar *buff, int blen)
int emsg(int rc, char *msg)
virtual int Test(const XrdAccPrivs priv, const Access_Operation oper)=0
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0)=0
const std::string str() const
virtual int Audit(const int accok, const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0) override
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) override
XrdAccSciTokens(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain, XrdOucEnv *envP)
virtual Issuers IssuerList() override
virtual bool Validate(const char *token, std::string &emsg, long long *expT, XrdSecEntity *Entity) override
virtual int Test(const XrdAccPrivs priv, const Access_Operation oper) override
std::string GetConfigFile()
virtual ~XrdAccSciTokens()
static bool Import(const char *var, char *&val)
void * GetPtr(const char *varname)
char * Get(const char *varname)
@ trim_lines
Prefix trimmed lines.
std::vector< ValidIssuer > Issuers
bool Mon_isIO(const Access_Operation oper)
void Mon_Report(const XrdSecEntity &Entity, const std::string &subject, const std::string &username)
bool Add(XrdSecAttr &attr)
char * vorg
Entity's virtual organization(s)
int credslen
Length of the 'creds' data.
XrdNetAddrInfo * addrInfo
Entity's connection details.
XrdSecEntityAttr * eaAPI
non-const API to attributes
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
char * creds
Raw entity credentials or cert.
XrdSecMonitor * secMon
If !0 security monitoring enabled.
char * grps
Entity's group name(s)
char * name
Entity's name.
char * role
Entity's role(s)
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
void Say(const char *text1, const char *text2=0, const char *txt3=0, const char *text4=0, const char *text5=0, const char *txt6=0)
void setMsgMask(int mask)
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
const CTX_Params * GetParams()