/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** * @file config.cc * @brief Access Control Plug-in Configuration. * @see config.h */ #include /* find_if */ #include /* std::ifstream */ #include /* getopt_long() */ #include "common.h" #include "config.h" static bool isTrue(const char *arg) { return (nullptr == arg || 0 == strncasecmp("true", arg, 4) || 0 == strncasecmp("1", arg, 1) || 0 == strncasecmp("yes", arg, 3)); } /** * trim from start */ static inline std::string & ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); return s; } /** * trim from end */ static inline std::string & rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); return s; } /** * trim from both ends */ static inline std::string & trim(std::string &s) { return ltrim(rtrim(s)); } /** * @brief Rebase a relative path onto the configuration directory. */ static String makeConfigPath(const String &path) { if (path.empty() || path[0] == '/') { return path; } return String(TSConfigDirGet()) + "/" + path; } /** * @brief parse and load a single line (base template - does nothing, see specializations for maps and vectors below) */ template void loadLine(T &container, const String &line) { } /** * @brief parse and load a single line into a map. */ template <> void loadLine(StringMap &map, const String &line) { String key; String value; std::istringstream ss(line); std::getline(ss, key, '='); std::getline(ss, value, '='); trim(key); trim(value); map[key] = value; #ifdef ACCESS_CONTROL_LOG_SECRETS AccessControlDebug("Adding secrets[%s]='%s'", key.c_str(), value.c_str()); #endif } /** * @brief parse and load a single line into a vector. */ template <> void loadLine(StringVector &vector, const String &line) { String trimmedLine(line); trim(trimmedLine); vector.push_back(trimmedLine); #ifdef ACCESS_CONTROL_LOG_SECRETS AccessControlDebug("Adding secrets[%d]='%s'", (int)(vector.size() - 1), trimmedLine.c_str()); #endif } /** * @brief parse and load a secrets into a container (i.e. map or vector). */ template static bool load(T &container, const String &filename) { String line; String::size_type pos; String path(makeConfigPath(filename)); AccessControlDebug("reading file %s", path.c_str()); std::ifstream infile; infile.open(path.c_str()); if (!infile.is_open()) { AccessControlError("failed to load file '%s'", path.c_str()); return false; } while (std::getline(infile, line)) { // Allow #-prefixed comments. pos = line.find_first_of('#'); if (pos != String::npos) { line.resize(pos); } if (line.empty()) { continue; } loadLine(container, line); } infile.close(); return true; } /** * @brief initializes plugin configuration. * @param argc number of plugin parameters * @param argv plugin parameters */ bool AccessControlConfig::init(int argc, char *argv[]) { static const struct option longopt[] = {{const_cast("invalid-syntax-status-code"), optional_argument, nullptr, 'a'}, {const_cast("invalid-signature-status-code"), optional_argument, nullptr, 'b'}, {const_cast("invalid-timing-status-code"), optional_argument, nullptr, 'c'}, {const_cast("invalid-scope-status-code"), optional_argument, nullptr, 'd'}, {const_cast("invalid-origin-response"), optional_argument, nullptr, 'e'}, {const_cast("internal-error-status-code"), optional_argument, nullptr, 'f'}, {const_cast("check-cookie"), optional_argument, nullptr, 'g'}, {const_cast("symmetric-keys-map"), optional_argument, nullptr, 'h'}, {const_cast("reject-invalid-token-requests"), optional_argument, nullptr, 'i'}, {const_cast("extract-subject-to-header"), optional_argument, nullptr, 'j'}, {const_cast("extract-tokenid-to-header"), optional_argument, nullptr, 'k'}, {const_cast("extract-status-to-header"), optional_argument, nullptr, 'l'}, {const_cast("token-response-header"), optional_argument, nullptr, 'm'}, {const_cast("use-redirects"), optional_argument, nullptr, 'n'}, {const_cast("include-uri-paths-file"), optional_argument, nullptr, 'o'}, {const_cast("exclude-uri-paths-file"), optional_argument, nullptr, 'p'}, {nullptr, 0, nullptr, 0}}; bool status = true; optind = 0; /* argv contains the "to" and "from" URLs. Skip the first so that the second one poses as the program name. */ argc--; argv++; for (;;) { int opt; opt = getopt_long(argc, (char *const *)argv, "", longopt, nullptr); if (opt == -1) { break; } AccessControlDebug("processing %s", argv[optind - 1]); switch (opt) { case 'a': /* invalid-syntax-status-code */ { _invalidSignature = static_cast(string2int(optarg)); } break; case 'b': /* invalid-signature-status-code */ { _invalidSignature = static_cast(string2int(optarg)); } break; case 'c': /* invalid-timing-status-code */ { _invalidTiming = static_cast(string2int(optarg)); } break; case 'd': /* invalid-scope-status-code */ { _invalidScope = static_cast(string2int(optarg)); } break; case 'e': /* invalid-origin-response */ { _invalidOriginResponse = static_cast(string2int(optarg)); } break; case 'f': /* internal-error-status-code */ { _internalError = static_cast(string2int(optarg)); } break; case 'g': /* check-cookie */ { _cookieName.assign(optarg); } break; case 'h': /* symmetric-keys-map */ { load(_symmetricKeysMap, optarg); } break; case 'i': /* reject-invalid-token-requests */ { _rejectRequestsWithInvalidTokens = ::isTrue(optarg); } break; case 'j': /* extract-subject-to-header */ { _extrSubHdrName.assign(optarg); } break; case 'k': /* extract-tokenid-to-header */ { _extrTokenIdHdrName.assign(optarg); } break; case 'l': /* extract-status-to-header */ { _extrValidationHdrName.assign(optarg); } break; case 'm': /* token-response-header */ { _respTokenHeaderName.assign(optarg); } break; case 'n': /* use-redirects */ { _useRedirects = ::isTrue(optarg); } break; case 'o': /* include-uri-paths-file */ if (!loadMultiPatternsFromFile(optarg, /* denylist = */ false)) { AccessControlError("failed to load uri-path multi-pattern allow-list '%s'", optarg); status = false; } break; case 'p': /* exclude-uri-paths-file */ if (!loadMultiPatternsFromFile(optarg, /* denylist = */ true)) { AccessControlError("failed to load uri-path multi-pattern deny-list '%s'", optarg); status = false; } break; default: { status = false; } } } /* Make sure at least 1 secret source is specified */ if (_symmetricKeysMap.empty()) { AccessControlDebug("no secrets' source provided"); return false; } /* Support only KeyValuePair syntax for now */ _tokenFactory = new AccessTokenFactory(_kvpAccessTokenConfig, _symmetricKeysMap, _debugLevel); if (nullptr == _tokenFactory) { AccessControlDebug("failed to initialize the access token factory"); return false; } return status; } /** * @brief a helper function which loads the classifier from files. * @param filename file name * @param denylist true - load as a denylist of patterns, false - allow-list of patterns * @return true if successful, false otherwise. */ bool AccessControlConfig::loadMultiPatternsFromFile(const String &filename, bool denylist) { if (filename.empty()) { AccessControlError("filename cannot be empty"); return false; } String path(makeConfigPath(filename)); std::ifstream ifstr; String regex; unsigned lineno = 0; ifstr.open(path.c_str()); if (!ifstr) { AccessControlError("failed to load uri-path multi-pattern from '%s'", path.c_str()); return false; } /* Have the multiplattern be named as same as the filename, would be used only for debugging. */ MultiPattern *multiPattern; if (denylist) { multiPattern = new NonMatchingMultiPattern(filename); AccessControlDebug("NonMatchingMultiPattern('%s')", filename.c_str()); } else { multiPattern = new MultiPattern(filename); AccessControlDebug("MultiPattern('%s')", filename.c_str()); } if (nullptr == multiPattern) { AccessControlError("failed to allocate multi-pattern from '%s'", filename.c_str()); return false; } AccessControlDebug("loading multi-pattern '%s' from '%s'", filename.c_str(), path.c_str()); while (std::getline(ifstr, regex)) { Pattern *p; String::size_type pos; ++lineno; // Allow #-prefixed comments. pos = regex.find_first_of('#'); if (pos != String::npos) { regex.resize(pos); } if (regex.empty()) { continue; } p = new Pattern(); if (nullptr != p && p->init(regex)) { if (denylist) { AccessControlDebug("Added pattern '%s' to deny list uri-path multi-pattern '%s'", regex.c_str(), filename.c_str()); multiPattern->add(p); } else { AccessControlDebug("Added pattern '%s' to allow list uri-path multi-pattern '%s'", regex.c_str(), filename.c_str()); multiPattern->add(p); } } else { AccessControlError("%s:%u: failed to parse regex '%s'", path.c_str(), lineno, regex.c_str()); delete p; } } ifstr.close(); if (!multiPattern->empty()) { _uriPathScope.add(multiPattern); } else { delete multiPattern; } return true; }