481 enum class AuthzBehavior {
490 m_parms(parms ? parms :
""),
491 m_next_clean(monotonic_time() + m_expiry_secs),
492 m_log(lp,
"scitokens_")
494 pthread_rwlock_init(&m_config_lock,
nullptr);
495 m_config_lock_initialized =
true;
496 m_log.Say(
"++++++ XrdAccSciTokens: Initialized SciTokens-based authorization.");
498 throw std::runtime_error(
"Failed to configure SciTokens authorization.");
503 if (m_config_lock_initialized) {
504 pthread_rwlock_destroy(&m_config_lock);
513 const char *authz = env ? env->
Get(
"authz") :
nullptr;
518 if (authz && !strncmp(authz,
"Bearer%20", 9)) {
523 if (!authz && Entity && !strcmp(
"ztn", Entity->
prot) && Entity->
creds &&
526 authz = Entity->
creds;
528 if (authz ==
nullptr) {
529 return OnMissing(Entity, path, oper, env);
531 m_log.Log(LogMask::Debug,
"Access",
"Trying token-based access control");
532 std::shared_ptr<XrdAccRules> access_rules;
533 uint64_t now = monotonic_time();
536 std::lock_guard<std::mutex> guard(m_map_mutex);
537 const auto iter = m_map.find(authz);
538 if (iter != m_map.end() && !iter->second->expired()) {
539 access_rules = iter->second;
543 m_log.Log(LogMask::Debug,
"Access",
"Token not found in recent cache; parsing.");
545 uint64_t cache_expiry;
546 AccessRulesRaw rules;
547 std::string username;
548 std::string token_subject;
550 std::vector<MapRule> map_rules;
551 std::vector<std::string> groups;
552 uint32_t authz_strategy;
553 if (GenerateAcls(authz, cache_expiry, rules, username, token_subject, issuer, map_rules, groups, authz_strategy)) {
554 access_rules.reset(
new XrdAccRules(now + cache_expiry, username, token_subject, issuer, map_rules, groups, authz_strategy));
555 access_rules->parse(rules);
557 m_log.Log(LogMask::Warning,
"Access",
"Failed to generate ACLs for token");
558 return OnMissing(Entity, path, oper, env);
560 if (m_log.getMsgMask() & LogMask::Debug) {
561 m_log.Log(LogMask::Debug,
"Access",
"New valid token", access_rules->str().c_str());
563 }
catch (std::exception &exc) {
564 m_log.Log(LogMask::Warning,
"Access",
"Error generating ACLs for authorization", exc.what());
565 return OnMissing(Entity, path, oper, env);
567 std::lock_guard<std::mutex> guard(m_map_mutex);
568 m_map[authz] = access_rules;
569 }
else if (m_log.getMsgMask() & LogMask::Debug) {
570 m_log.Log(LogMask::Debug,
"Access",
"Cached token", access_rules->str().c_str());
585 new_secentity.
vorg =
nullptr;
586 new_secentity.
grps =
nullptr;
587 new_secentity.
role =
nullptr;
590 const auto &issuer = access_rules->get_issuer();
591 if (!issuer.empty()) {
592 new_secentity.
vorg = strdup(issuer.c_str());
594 bool group_success =
false;
595 if ((access_rules->get_authz_strategy() & IssuerAuthz::Group) && access_rules->groups().size()) {
596 std::stringstream ss;
597 for (
const auto &grp : access_rules->groups()) {
600 const auto &groups_str = ss.str();
601 new_secentity.
grps =
static_cast<char*
>(malloc(groups_str.size() + 1));
602 if (new_secentity.
grps) {
603 memcpy(new_secentity.
grps, groups_str.c_str(), groups_str.size());
604 new_secentity.
grps[groups_str.size()] =
'\0';
605 group_success =
true;
609 std::string username;
610 bool mapping_success =
false;
611 bool scope_success =
false;
612 username = access_rules->get_username(path);
614 mapping_success = (access_rules->get_authz_strategy() & IssuerAuthz::Mapping) && !username.empty();
615 scope_success = (access_rules->get_authz_strategy() & IssuerAuthz::Capability) && access_rules->apply(oper, path);
616 if (scope_success && (m_log.getMsgMask() & LogMask::Debug)) {
617 std::stringstream ss;
618 ss <<
"Grant authorization based on scopes for operation=" << OpToName(oper) <<
", path=" << path;
619 m_log.Log(LogMask::Debug,
"Access", ss.str().c_str());
622 if (!scope_success && !mapping_success && !group_success) {
623 auto returned_accs = OnMissing(&new_secentity, path, oper, env);
625 if (new_secentity.
vorg !=
nullptr) free(new_secentity.
vorg);
626 if (new_secentity.
grps !=
nullptr) free(new_secentity.
grps);
627 if (new_secentity.
role !=
nullptr) free(new_secentity.
role);
629 return returned_accs;
633 if (scope_success && username.empty()) {
634 username = access_rules->get_default_username();
639 if (scope_success || mapping_success) {
641 Entity->
eaAPI->
Add(
"request.name", username,
true);
642 new_secentity.
eaAPI->
Add(
"request.name", username,
true);
643 m_log.Log(LogMask::Debug,
"Access",
"Request username", username.c_str());
651 const auto &token_subject = access_rules->get_token_subject();
652 if (!token_subject.empty()) {
653 Entity->
eaAPI->
Add(
"token.subject", token_subject,
true);
662 if (Entity->
secMon && scope_success && returned_op &&
Mon_isIO(oper))
663 Mon_Report(new_secentity, token_subject, username);
666 if (new_secentity.
vorg !=
nullptr) free(new_secentity.
vorg);
667 if (new_secentity.
grps !=
nullptr) free(new_secentity.
grps);
668 if (new_secentity.
role !=
nullptr) free(new_secentity.
role);
684 pthread_rwlock_rdlock(&m_config_lock);
686 for (
const auto &it: m_issuers) {
687 issuers.push_back({it.first, it.second.m_url});
690 pthread_rwlock_unlock(&m_config_lock);
693 pthread_rwlock_unlock(&m_config_lock);
698 virtual bool Validate(
const char *token, std::string &
emsg,
long long *expT,
706 if (!strncmp(token,
"Bearer%20", 9)) token += 9;
707 pthread_rwlock_rdlock(&m_config_lock);
708 auto retval = scitoken_deserialize(token, &scitoken, &m_valid_issuers_array[0], &err_msg);
709 pthread_rwlock_unlock(&m_config_lock);
712 m_log.Log(LogMask::Warning,
"Validate",
"Failed to deserialize SciToken:", err_msg);
723 {
char *value =
nullptr;
724 if (!scitoken_get_claim_string(scitoken,
"sub", &value, &err_msg)) {
725 Entity->
name = strdup(value);
734 if (expT && scitoken_get_expiration(scitoken, expT, &err_msg)) {
737 scitoken_destroy(scitoken);
743 scitoken_destroy(scitoken);
761 return (m_chain ? m_chain->Test(priv, oper) : 0);
772 switch (m_authz_behavior) {
773 case AuthzBehavior::PASSTHROUGH:
775 case AuthzBehavior::ALLOW:
777 case AuthzBehavior::DENY:
784 bool GenerateAcls(
const std::string &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) {
787 bool looks_good =
true;
788 int separator_count = 0;
789 for (
auto cur_char = authz.c_str(); *cur_char; cur_char++) {
790 if (*cur_char ==
'.') {
792 if (separator_count > 2) {
796 if (!(*cur_char >= 65 && *cur_char <= 90) &&
797 !(*cur_char >= 97 && *cur_char <= 122) &&
798 !(*cur_char >= 48 && *cur_char <= 57) &&
799 (*cur_char != 43) && (*cur_char != 47) &&
800 (*cur_char != 45) && (*cur_char != 95))
806 if ((separator_count != 2) || (!looks_good)) {
807 m_log.Log(LogMask::Debug,
"Parse",
"Token does not appear to be a valid JWT; skipping.");
812 SciToken token =
nullptr;
813 pthread_rwlock_rdlock(&m_config_lock);
814 auto retval = scitoken_deserialize(authz.c_str(), &token, &m_valid_issuers_array[0], &err_msg);
815 pthread_rwlock_unlock(&m_config_lock);
818 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Failed to deserialize SciToken:", err_msg);
824 if (scitoken_get_expiration(token, &expiry, &err_msg)) {
825 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Unable to determine token expiration:", err_msg);
827 scitoken_destroy(token);
831 const auto now_wall =
static_cast<long long>(std::time(
nullptr));
832 const auto remaining = expiry - now_wall;
833 if (remaining <= 0) {
834 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Token already expired.");
835 scitoken_destroy(token);
838 expiry = std::min(
static_cast<int64_t
>(remaining),
839 static_cast<int64_t
>(m_expiry_secs));
841 expiry = m_expiry_secs;
844 char *value =
nullptr;
845 if (scitoken_get_claim_string(token,
"iss", &value, &err_msg)) {
846 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Failed to get issuer:", err_msg);
847 scitoken_destroy(token);
851 std::string token_issuer(value);
854 pthread_rwlock_rdlock(&m_config_lock);
855 auto enf = enforcer_create(token_issuer.c_str(), &m_audiences_array[0], &err_msg);
856 pthread_rwlock_unlock(&m_config_lock);
858 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Failed to create an enforcer:", err_msg);
859 scitoken_destroy(token);
865 if (enforcer_generate_acls(enf, token, &acls, &err_msg)) {
866 scitoken_destroy(token);
867 enforcer_destroy(enf);
868 m_log.Log(LogMask::Warning,
"GenerateAcls",
"ACL generation from SciToken failed:", err_msg);
872 enforcer_destroy(enf);
876 ~AclGuard() {
if (ptr) enforcer_acl_free(ptr); }
879 pthread_rwlock_rdlock(&m_config_lock);
880 auto iter = m_issuers.find(token_issuer);
881 if (iter == m_issuers.end()) {
882 pthread_rwlock_unlock(&m_config_lock);
883 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Authorized issuer without a config.");
884 scitoken_destroy(token);
887 const auto config = iter->second;
888 pthread_rwlock_unlock(&m_config_lock);
892 std::vector<std::string> groups_parsed;
893 if (scitoken_get_claim_string_list(token, config.m_groups_claim.c_str(), &group_list, &err_msg) == 0) {
894 for (
int idx=0; group_list[idx]; idx++) {
895 groups_parsed.emplace_back(group_list[idx]);
897 scitoken_free_string_list(group_list);
900 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Failed to get token groups:", err_msg);
904 if (scitoken_get_claim_string(token,
"sub", &value, &err_msg)) {
905 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Failed to get token subject:", err_msg);
907 scitoken_destroy(token);
910 token_subject = std::string(value);
913 auto tmp_username = token_subject;
914 if (!config.m_username_claim.empty()) {
915 if (scitoken_get_claim_string(token, config.m_username_claim.c_str(), &value, &err_msg)) {
916 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Failed to get token username:", err_msg);
918 scitoken_destroy(token);
921 tmp_username = std::string(value);
923 if (!IsSafeUsername(tmp_username)) {
924 m_log.Log(LogMask::Warning,
"GenerateAcls",
"Token username claim contains unsafe characters; rejecting:", tmp_username.c_str());
925 scitoken_destroy(token);
928 }
else if (!config.m_map_subject) {
929 tmp_username = config.m_default_user;
932 for (
auto rule : config.m_map_rules) {
933 for (
auto path : config.m_base_paths) {
934 auto path_rule = rule;
935 path_rule.m_path_prefix = path + rule.m_path_prefix;
936 auto pos = path_rule.m_path_prefix.find(
"//");
937 if (pos != std::string::npos) {
938 path_rule.m_path_prefix.erase(pos + 1, 1);
940 map_rules.emplace_back(path_rule);
944 AccessRulesRaw xrd_rules;
946 std::set<std::string> paths_write_seen;
947 std::set<std::string> paths_create_or_modify_seen;
948 std::vector<std::string> acl_paths;
949 acl_paths.reserve(config.m_restricted_paths.size() + 1);
950 while (acls[idx].resource && acls[idx++].authz) {
952 const auto &acl_path = acls[idx-1].resource;
953 const auto &acl_authz = acls[idx-1].authz;
954 if (config.m_restricted_paths.empty()) {
955 acl_paths.push_back(acl_path);
957 auto acl_path_size = strlen(acl_path);
958 for (
const auto &restricted_path : config.m_restricted_paths) {
961 if (!strncmp(acl_path, restricted_path.c_str(), restricted_path.size())) {
964 if (acl_path_size > restricted_path.size() && acl_path[restricted_path.size()] !=
'/') {
967 acl_paths.push_back(acl_path);
973 if (!strncmp(acl_path, restricted_path.c_str(), acl_path_size)) {
980 if ((restricted_path.size() > acl_path_size && restricted_path[acl_path_size] !=
'/') && (acl_path_size != 1)) {
983 acl_paths.push_back(restricted_path);
987 for (
const auto &acl_path : acl_paths) {
988 for (
const auto &base_path : config.m_base_paths) {
989 if (!acl_path[0] || acl_path[0] !=
'/') {
continue;}
991 MakeCanonical(base_path + acl_path, path);
992 if (!strcmp(acl_authz,
"read")) {
993 xrd_rules.emplace_back(
AOP_Read, path);
995 xrd_rules.emplace_back(
AOP_Stat, path);
996 }
else if (!strcmp(acl_authz,
"create")) {
997 paths_create_or_modify_seen.insert(path);
1002 xrd_rules.emplace_back(
AOP_Stat, path);
1003 }
else if (!strcmp(acl_authz,
"modify")) {
1004 paths_create_or_modify_seen.insert(path);
1006 xrd_rules.emplace_back(
AOP_Mkdir, path);
1010 xrd_rules.emplace_back(
AOP_Chmod, path);
1011 xrd_rules.emplace_back(
AOP_Stat, path);
1013 }
else if (!strcmp(acl_authz,
"storage.stage")) {
1014 xrd_rules.emplace_back(
AOP_Stage, path);
1015 xrd_rules.emplace_back(
AOP_Poll, path);
1016 }
else if (!strcmp(acl_authz,
"storage.poll")) {
1017 xrd_rules.emplace_back(
AOP_Poll, path);
1018 }
else if (!strcmp(acl_authz,
"write")) {
1019 paths_write_seen.insert(path);
1024 for (
const auto &write_path : paths_write_seen) {
1025 if (paths_create_or_modify_seen.find(write_path) == paths_create_or_modify_seen.end()) {
1027 xrd_rules.emplace_back(
AOP_Create, write_path);
1028 xrd_rules.emplace_back(
AOP_Mkdir, write_path);
1029 xrd_rules.emplace_back(
AOP_Rename, write_path);
1030 xrd_rules.emplace_back(
AOP_Insert, write_path);
1031 xrd_rules.emplace_back(
AOP_Update, write_path);
1032 xrd_rules.emplace_back(
AOP_Stat, write_path);
1033 xrd_rules.emplace_back(
AOP_Chmod, write_path);
1034 xrd_rules.emplace_back(
AOP_Delete, write_path);
1037 authz_strategy = config.m_authz_strategy;
1039 cache_expiry = expiry;
1040 rules = std::move(xrd_rules);
1041 username = std::move(tmp_username);
1042 issuer = std::move(token_issuer);
1043 groups = std::move(groups_parsed);
1044 scitoken_destroy(token);
1050 bool Config(XrdOucEnv *envP) {
1052 m_log.setMsgMask(LogMask::Error | LogMask::Warning);
1054 char *config_filename =
nullptr;
1058 XrdOucGatherConf scitokens_conf(
"scitokens.trace", &m_log);
1061 m_log.Emsg(
"Config", -result,
"parsing config file", config_filename);
1066 std::string map_filename;
1067 while (scitokens_conf.GetLine()) {
1068 m_log.setMsgMask(0);
1069 scitokens_conf.GetToken();
1070 if (!(val = scitokens_conf.GetToken())) {
1071 m_log.Emsg(
"Config",
"scitokens.trace requires an argument. Usage: scitokens.trace [all|error|warning|info|debug|none]");
1075 if (!strcmp(val,
"all")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::All);}
1076 else if (!strcmp(val,
"error")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Error);}
1077 else if (!strcmp(val,
"warning")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Warning);}
1078 else if (!strcmp(val,
"info")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Info);}
1079 else if (!strcmp(val,
"debug")) {m_log.setMsgMask(m_log.getMsgMask() | LogMask::Debug);}
1080 else if (!strcmp(val,
"none")) {m_log.setMsgMask(0);}
1081 else {m_log.Emsg(
"Config",
"scitokens.trace encountered an unknown directive:", val);
return false;}
1082 }
while ((val = scitokens_conf.GetToken()));
1084 m_log.Emsg(
"Config",
"Logging levels enabled -",
LogMaskToString(m_log.getMsgMask()).c_str());
1086 auto xrdEnv =
static_cast<XrdOucEnv*
>(
envP ?
envP->
GetPtr(
"xrdEnv*") :
nullptr);
1087 auto tlsCtx =
static_cast<XrdTlsContext*
>(xrdEnv ? xrdEnv->GetPtr(
"XrdTlsContext*") :
nullptr);
1090 if (params && !params->cafile.empty()) {
1091#ifdef HAVE_SCITOKEN_CONFIG_SET_STR
1092 scitoken_config_set_str(
"tls.ca_file", params->cafile.c_str(),
nullptr);
1094 m_log.Log(LogMask::Warning,
"Config",
"tls.ca_file is set but the platform's libscitokens.so does not support setting config parameters");
1100 if (
const char* xdg_cache_home = getenv(
"XDG_CACHE_HOME")) {
1101 m_log.Log(LogMask::Info,
"Config",
"Scitokens cache file location env var is set to : ", xdg_cache_home);
1104 const char* adminpath_env = getenv(
"XRDADMINPATH");
1105 if (!adminpath_env || !*adminpath_env) {
1106 m_log.Log(LogMask::Warning,
"Config",
"XRDADMINPATH is not defined; leaving cache location unset");
1108 std::string adminpath = adminpath_env;
1109 while (adminpath.size() > 1 && adminpath.back() ==
'/') adminpath.pop_back();
1110 std::string xdg_cache_home_str = adminpath +
"/.cache";
1111 m_log.Log(LogMask::Info,
"Config",
"Scitokens cache file location env var is not set; using : ", xdg_cache_home_str.c_str());
1113#ifdef HAVE_SCITOKEN_CONFIG_SET_STR
1114 scitoken_config_set_str(
"keycache.cache_home", xdg_cache_home_str.c_str(),
nullptr);
1116 setenv(
"XDG_CACHE_HOME", xdg_cache_home_str.c_str(), 1);
1124 bool ParseMapfile(
const std::string &filename, std::vector<MapRule> &rules)
1126 std::stringstream ss;
1127 std::ifstream mapfile(filename);
1128 if (!mapfile.is_open())
1130 ss <<
"Error opening mapfile (" << filename <<
"): " << strerror(errno);
1131 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1134 picojson::value val;
1135 auto err = picojson::parse(val, mapfile);
1137 ss <<
"Unable to parse mapfile (" << filename <<
") as json: " << err;
1138 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1141 if (!val.is<picojson::array>()) {
1142 ss <<
"Top-level element of the mapfile " << filename <<
" must be a list";
1143 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1146 const auto& rule_list = val.get<picojson::array>();
1147 for (
const auto &rule : rule_list)
1149 if (!rule.is<picojson::object>()) {
1150 ss <<
"Mapfile " << filename <<
" must be a list of JSON objects; found non-object";
1151 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1157 std::string username;
1159 bool ignore =
false;
1160 for (
const auto &entry : rule.get<picojson::object>()) {
1161 if (!entry.second.is<std::string>()) {
1162 if (entry.first !=
"result" && entry.first !=
"group" && entry.first !=
"sub" && entry.first !=
"path") {
continue;}
1163 ss <<
"In mapfile " << filename <<
", rule entry for " << entry.first <<
" has non-string value";
1164 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1167 if (entry.first ==
"result") {
1168 result = entry.second.get<std::string>();
1170 else if (entry.first ==
"group") {
1171 group = entry.second.get<std::string>();
1173 else if (entry.first ==
"sub") {
1174 sub = entry.second.get<std::string>();
1175 }
else if (entry.first ==
"username") {
1176 username = entry.second.get<std::string>();
1177 }
else if (entry.first ==
"path") {
1178 std::string norm_path;
1179 if (!MakeCanonical(entry.second.get<std::string>(), norm_path)) {
1180 ss <<
"In mapfile " << filename <<
" encountered a path " << entry.second.get<std::string>()
1181 <<
" that cannot be normalized";
1182 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1186 }
else if (entry.first ==
"ignore") {
1191 if (ignore)
continue;
1194 ss <<
"In mapfile " << filename <<
" encountered a rule without a 'result' attribute";
1195 m_log.Log(LogMask::Error,
"ParseMapfile", ss.str().c_str());
1198 rules.emplace_back(sub, username, path, group, result);
1207 std::string new_cfg_file =
"/etc/xrootd/scitokens.cfg";
1208 if (!m_parms.empty()) {
1210 std::vector<std::string> arg_list;
1212 while ((m_parms.size() > pos) && (m_parms[pos] ==
' ')) {pos++;}
1213 auto next_pos = m_parms.find_first_of(
", ", pos);
1214 auto next_arg = m_parms.substr(pos, next_pos - pos);
1216 if (!next_arg.empty()) {
1217 arg_list.emplace_back(std::move(next_arg));
1219 }
while (pos != std::string::npos);
1221 for (
const auto &arg : arg_list) {
1222 if (strncmp(arg.c_str(),
"config=", 7)) {
1223 m_log.Log(LogMask::Error,
"Reconfig",
"Ignoring unknown configuration argument:", arg.c_str());
1226 new_cfg_file = std::string(arg.c_str() + 7);
1229 m_log.Log(LogMask::Info,
"Reconfig",
"Parsing configuration file:", new_cfg_file.c_str());
1231 OverrideINIReader reader(new_cfg_file);
1232 if (reader.ParseError() < 0) {
1233 std::stringstream ss;
1234 ss <<
"Error opening config file (" << m_cfg_file <<
"): " << strerror(errno);
1235 m_log.Log(LogMask::Error,
"Reconfig", ss.str().c_str());
1237 }
else if (reader.ParseError()) {
1238 std::stringstream ss;
1239 ss <<
"Parse error on line " << reader.ParseError() <<
" of file " << m_cfg_file;
1240 m_log.Log(LogMask::Error,
"Reconfig", ss.str().c_str());
1243 std::vector<std::string> audiences;
1244 std::unordered_map<std::string, IssuerConfig> issuers;
1245 AuthzBehavior new_authz_behavior = m_authz_behavior;
1246 for (
const auto §ion : reader.Sections()) {
1247 std::string section_lower;
1248 std::transform(section.begin(), section.end(), std::back_inserter(section_lower),
1249 [](
unsigned char c){ return std::tolower(c); });
1251 if (section_lower.substr(0, 6) ==
"global") {
1252 auto audience = reader.Get(section,
"audience",
"");
1253 if (!audience.empty()) {
1256 while (audience.size() > pos && (audience[pos] ==
',' || audience[pos] ==
' ')) {pos++;}
1257 auto next_pos = audience.find_first_of(
", ", pos);
1258 auto next_aud = audience.substr(pos, next_pos - pos);
1260 if (!next_aud.empty()) {
1261 audiences.push_back(next_aud);
1263 }
while (pos != std::string::npos);
1265 audience = reader.Get(section,
"audience_json",
"");
1266 if (!audience.empty()) {
1267 picojson::value json_obj;
1268 auto err = picojson::parse(json_obj, audience);
1270 m_log.Log(LogMask::Error,
"Reconfig",
"Unable to parse audience_json:", err.c_str());
1273 if (!json_obj.is<picojson::value::array>()) {
1274 m_log.Log(LogMask::Error,
"Reconfig",
"audience_json must be a list of strings; not a list.");
1277 for (
const auto &val : json_obj.get<picojson::value::array>()) {
1278 if (!val.is<std::string>()) {
1279 m_log.Log(LogMask::Error,
"Reconfig",
"audience must be a list of strings; value is not a string.");
1282 audiences.push_back(val.get<std::string>());
1285 auto onmissing = reader.Get(section,
"onmissing",
"");
1286 if (onmissing ==
"passthrough") {
1287 new_authz_behavior = AuthzBehavior::PASSTHROUGH;
1288 }
else if (onmissing ==
"allow") {
1289 new_authz_behavior = AuthzBehavior::ALLOW;
1290 }
else if (onmissing ==
"deny") {
1291 new_authz_behavior = AuthzBehavior::DENY;
1292 }
else if (!onmissing.empty()) {
1293 m_log.Log(LogMask::Error,
"Reconfig",
"Unknown value for onmissing key:", onmissing.c_str());
1298 if (section_lower.substr(0, 7) !=
"issuer ") {
continue;}
1300 auto issuer = reader.Get(section,
"issuer",
"");
1301 if (issuer.empty()) {
1302 m_log.Log(LogMask::Error,
"Reconfig",
"Ignoring section because 'issuer' attribute is not set:",
1306 m_log.Log(LogMask::Debug,
"Reconfig",
"Configuring issuer", issuer.c_str());
1308 std::vector<MapRule> rules;
1309 auto name_mapfile = reader.Get(section,
"name_mapfile",
"");
1310 if (!name_mapfile.empty()) {
1311 if (!ParseMapfile(name_mapfile, rules)) {
1312 m_log.Log(LogMask::Error,
"Reconfig",
"Failed to parse mapfile; failing (re-)configuration", name_mapfile.c_str());
1315 m_log.Log(LogMask::Info,
"Reconfig",
"Successfully parsed SciTokens mapfile:", name_mapfile.c_str());
1319 auto base_path = reader.Get(section,
"base_path",
"");
1320 if (base_path.empty()) {
1321 m_log.Log(LogMask::Error,
"Reconfig",
"Ignoring section because 'base_path' attribute is not set:",
1327 while (section.size() > pos && std::isspace(section[pos])) {pos++;}
1329 auto name = section.substr(pos);
1331 m_log.Log(LogMask::Error,
"Reconfig",
"Invalid section name:", section.c_str());
1335 std::vector<std::string> base_paths;
1336 ParseCanonicalPaths(base_path, base_paths);
1338 auto restricted_path = reader.Get(section,
"restricted_path",
"");
1339 std::vector<std::string> restricted_paths;
1340 if (!restricted_path.empty()) {
1341 ParseCanonicalPaths(restricted_path, restricted_paths);
1344 auto default_user = reader.Get(section,
"default_user",
"");
1345 auto map_subject = reader.GetBoolean(section,
"map_subject",
false);
1346 auto username_claim = reader.Get(section,
"username_claim",
"");
1347 auto groups_claim = reader.Get(section,
"groups_claim",
"wlcg.groups");
1349 auto authz_strategy_str = reader.Get(section,
"authorization_strategy",
"");
1350 uint32_t authz_strategy = 0;
1351 if (authz_strategy_str.empty()) {
1352 authz_strategy = IssuerAuthz::Default;
1354 std::istringstream authz_strategy_stream(authz_strategy_str);
1355 std::string authz_str;
1356 while (std::getline(authz_strategy_stream, authz_str,
' ')) {
1357 if (!strcasecmp(authz_str.c_str(),
"capability")) {
1358 authz_strategy |= IssuerAuthz::Capability;
1359 }
else if (!strcasecmp(authz_str.c_str(),
"group")) {
1360 authz_strategy |= IssuerAuthz::Group;
1361 }
else if (!strcasecmp(authz_str.c_str(),
"mapping")) {
1362 authz_strategy |= IssuerAuthz::Mapping;
1364 m_log.Log(LogMask::Error,
"Reconfig",
"Unknown authorization strategy (ignoring):", authz_str.c_str());
1369 issuers.emplace(std::piecewise_construct,
1370 std::forward_as_tuple(issuer),
1371 std::forward_as_tuple(name, issuer, base_paths, restricted_paths,
1372 map_subject, authz_strategy, default_user, username_claim, groups_claim, rules));
1375 if (issuers.empty()) {
1376 m_log.Log(LogMask::Warning,
"Reconfig",
"No issuers configured.");
1379 pthread_rwlock_wrlock(&m_config_lock);
1381 m_authz_behavior = new_authz_behavior;
1382 m_cfg_file = std::move(new_cfg_file);
1383 m_audiences = std::move(audiences);
1385 m_audiences_array.resize(m_audiences.size() + 1);
1386 for (
const auto &audience : m_audiences) {
1387 m_audiences_array[idx++] = audience.c_str();
1389 m_audiences_array[idx] =
nullptr;
1391 m_issuers = std::move(issuers);
1392 m_valid_issuers_array.resize(m_issuers.size() + 1);
1394 for (
const auto &issuer : m_issuers) {
1395 m_valid_issuers_array[idx++] = issuer.first.c_str();
1397 m_valid_issuers_array[idx] =
nullptr;
1399 pthread_rwlock_unlock(&m_config_lock);
1402 pthread_rwlock_unlock(&m_config_lock);
1406 void Check(uint64_t now)
1409 std::unique_lock<std::mutex> lock(m_check_mutex, std::try_to_lock);
1410 if (!lock.owns_lock()) {
return;}
1413 if (now <= m_next_clean) {
return;}
1417 std::lock_guard<std::mutex> guard(m_map_mutex);
1418 for (
auto iter = m_map.begin(); iter != m_map.end(); ) {
1419 if (iter->second->expired()) {
1420 iter = m_map.erase(iter);
1428 m_next_clean = monotonic_time() + m_expiry_secs;
1431 bool m_config_lock_initialized{
false};
1432 pthread_rwlock_t m_config_lock;
1433 std::vector<std::string> m_audiences;
1434 std::vector<const char *> m_audiences_array;
1435 std::map<std::string, std::shared_ptr<XrdAccRules>> m_map;
1436 std::mutex m_check_mutex;
1437 std::mutex m_map_mutex;
1439 const std::string m_parms;
1440 std::vector<const char*> m_valid_issuers_array;
1441 std::unordered_map<std::string, IssuerConfig> m_issuers;
1442 uint64_t m_next_clean{0};
1444 AuthzBehavior m_authz_behavior{AuthzBehavior::PASSTHROUGH};
1445 std::string m_cfg_file;
1447 static constexpr uint64_t m_expiry_secs = 60;