1 /** @file
2 
3   Access control by IP address and HTTP method.
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /*****************************************************************************
25  *
26  *  IPAllow.h - Interface to IP Access Control system
27  *
28  *
29  ****************************************************************************/
30 
31 #pragma once
32 
33 #include <string>
34 #include <string_view>
35 #include <vector>
36 
37 #include "hdrs/HTTP.h"
38 #include "ProxyConfig.h"
39 #include "tscore/IpMap.h"
40 #include "tscpp/util/TextView.h"
41 #include "tscore/ts_file.h"
42 
43 // forward declare in name only so it can be a friend.
44 struct IpAllowUpdate;
45 namespace YAML
46 {
47 class Node;
48 }
49 
50 /** Singleton class for access controls.
51  */
52 class IpAllow : public ConfigInfo
53 {
54   friend struct IpAllowUpdate;
55 
56   using MethodNames = std::vector<std::string>;
57 
58   static constexpr uint32_t ALL_METHOD_MASK = ~0; // Mask for all methods.
59 
60   /** An access control record.
61       It has the methods permitted and the source line. This is a POD used by @a ACL.
62   */
63   struct Record {
64     /// Default constructor.
65     /// Present only to make Vec<> happy, do not use.
66     Record()              = default;
67     Record(Record &&that) = default;
68     explicit Record(uint32_t method_mask);
69     Record(uint32_t method_mask, int line, MethodNames &&nonstandard_methods, bool deny_nonstandard_methods);
70 
71     uint32_t _method_mask{0};
72     int _src_line{0};
73     MethodNames _nonstandard_methods;
74     bool _deny_nonstandard_methods{false};
75   };
76 
77 public:
78   using self_type     = IpAllow; ///< Self reference type.
79   using scoped_config = ConfigProcessor::scoped_config<self_type, self_type>;
80 
81   // indicator for whether we should be checking the acl record for src ip or dest ip
82   enum match_key_t { SRC_ADDR, DST_ADDR };
83   /// Token strings for configuration
84   static constexpr ts::TextView OPT_MATCH_SRC{"src_ip"};
85   static constexpr ts::TextView OPT_MATCH_DST{"dest_ip"};
86   static constexpr ts::TextView OPT_ACTION_TAG{"action"};
87   static constexpr ts::TextView OPT_ACTION_ALLOW{"ip_allow"};
88   static constexpr ts::TextView OPT_ACTION_DENY{"ip_deny"};
89   static constexpr ts::TextView OPT_METHOD{"method"};
90   static constexpr ts::TextView OPT_METHOD_ALL{"all"};
91 
92   static constexpr ts::TextView YAML_TAG_ROOT{"ip_allow"};
93   static constexpr ts::TextView YAML_TAG_IP_ADDRS{"ip_addrs"};
94   static constexpr ts::TextView YAML_TAG_APPLY{"apply"};
95   static constexpr ts::TextView YAML_VALUE_APPLY_IN{"in"};
96   static constexpr ts::TextView YAML_VALUE_APPLY_OUT{"out"};
97   static constexpr ts::TextView YAML_TAG_ACTION{"action"};
98   static constexpr ts::TextView YAML_VALUE_ACTION_ALLOW{"allow"};
99   static constexpr ts::TextView YAML_VALUE_ACTION_DENY{"deny"};
100   static constexpr ts::TextView YAML_TAG_METHODS{"methods"};
101   static constexpr ts::TextView YAML_VALUE_METHODS_ALL{"all"};
102 
103   static constexpr const char *MODULE_NAME = "IPAllow";
104 
105   /** An access control record and support data.
106    * The primary point of this is to hold the backing configuration in memory while the ACL
107    * is in use.
108    */
109   class ACL
110   {
111     friend class IpAllow;
112     using self_type = ACL; ///< Self reference type.
113   public:
114     ACL()                  = default;
115     ACL(const self_type &) = delete;         // no copies.
116     explicit ACL(self_type &&that) noexcept; // move allowed.
117     ~ACL();
118 
119     self_type &operator=(const self_type &) = delete;
120     self_type &operator                     =(self_type &&that) noexcept;
121 
122     void clear(); ///< Drop data and config reference.
123 
124     static uint32_t MethodIdxToMask(int wksidx);
125 
126     /// Check if the ACL is valid (i.e. not uninitialized or missing).
127     bool isValid() const;
128     /// Check if the ACL denies all access.
129     bool isDenyAll() const;
130     /// Check if the ACL allows all access.
131     bool isAllowAll() const;
132 
133     bool isMethodAllowed(int method_wksidx) const;
134 
135     bool isNonstandardMethodAllowed(std::string_view method) const;
136 
137     /// Return the configuration source line for this ACL.
138     int source_line() const;
139 
140   private:
141     // @a config must already be ref counted.
142     ACL(const Record *r, IpAllow *config) noexcept;
143 
144     const Record *_r{nullptr}; ///< The actual ACL record.
145     IpAllow *_config{nullptr}; ///< The backing configuration.
146   };
147 
148   explicit IpAllow(const char *config_var);
149 
150   void Print() const;
151 
152   static ACL match(sockaddr const *ip, match_key_t key);
153   static ACL match(IpEndpoint const *ip, match_key_t key);
154 
155   static void startup();
156   static void reconfigure();
157   /// @return The global instance.
158   static IpAllow *acquire();
159   /// Release the configuration.
160   /// @a config is released and can then be garbage collected.
161   static void release(IpAllow *config);
162   /// Release this configuration.
163   /// @a this is released and can then be garbage collected.
164   void release();
165 
166   /// A static ACL that permits all methods.
167   static ACL makeAllowAllACL();
168   /// A static ACL that denies everything.
169   static const ACL DENY_ALL_ACL;
170 
171   /* @return The previous accept check state
172    * This is a global variable that is independent of
173    * the ip_allow configuration
174    */
175   static bool enableAcceptCheck(bool state);
176 
177   /* @return The current accept check state
178    * This is a global variable that is independent of
179    * the ip_allow configuration
180    */
181   static bool isAcceptCheckEnabled();
182 
183   const ts::file::path &get_config_file() const;
184 
185 private:
186   static size_t configid;               ///< Configuration ID for update management.
187   static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access.
188   static bool accept_check_p;           ///< @c true if deny all can be enforced during accept.
189 
190   void PrintMap(const IpMap *map) const;
191 
192   int BuildTable();
193   int ATSBuildTable(const std::string &);
194   int YAMLBuildTable(const std::string &);
195   bool YAMLLoadEntry(const YAML::Node &);
196   bool YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, void *mark);
197   bool YAMLLoadMethod(const YAML::Node &node, Record &rec);
198 
199   ts::file::path config_file; ///< Path to configuration file.
200   IpMap _src_map;
201   IpMap _dst_map;
202   std::vector<Record> _src_acls;
203   std::vector<Record> _dst_acls;
204 };
205 
206 // ------ Record methods --------
207 
Record(uint32_t method_mask)208 inline IpAllow::Record::Record(uint32_t method_mask) : _method_mask(method_mask) {}
209 
Record(uint32_t method_mask,int ln,MethodNames && nonstandard_methods,bool deny_nonstandard_methods)210 inline IpAllow::Record::Record(uint32_t method_mask, int ln, MethodNames &&nonstandard_methods, bool deny_nonstandard_methods)
211   : _method_mask(method_mask),
212     _src_line(ln),
213     _nonstandard_methods(nonstandard_methods),
214     _deny_nonstandard_methods(deny_nonstandard_methods)
215 {
216 }
217 
218 // ------ ACL methods --------
219 
ACL(const IpAllow::Record * r,IpAllow * config)220 inline IpAllow::ACL::ACL(const IpAllow::Record *r, IpAllow *config) noexcept : _r(r), _config(config) {}
221 
ACL(self_type && that)222 inline IpAllow::ACL::ACL(self_type &&that) noexcept : _r(that._r), _config(that._config)
223 {
224   that._r      = nullptr;
225   that._config = nullptr;
226 }
227 
~ACL()228 inline IpAllow::ACL::~ACL()
229 {
230   if (_config != nullptr) {
231     _config->release();
232   }
233 }
234 
235 inline auto
236 IpAllow::ACL::operator=(self_type &&that) noexcept -> self_type &
237 {
238   // move and clear so @a that doesn't drop the config reference.
239   this->_r      = that._r;
240   that._r       = nullptr;
241   this->_config = that._config;
242   that._config  = nullptr;
243 
244   return *this;
245 }
246 
247 inline uint32_t
MethodIdxToMask(int wksidx)248 IpAllow::ACL::MethodIdxToMask(int wksidx)
249 {
250   return 1U << (wksidx - HTTP_WKSIDX_CONNECT);
251 }
252 
253 inline bool
isValid()254 IpAllow::ACL::isValid() const
255 {
256   return _r != nullptr;
257 }
258 
259 inline bool
isDenyAll()260 IpAllow::ACL::isDenyAll() const
261 {
262   return _r == nullptr || (_r->_method_mask == 0 && _r->_nonstandard_methods.empty());
263 }
264 
265 inline bool
isAllowAll()266 IpAllow::ACL::isAllowAll() const
267 {
268   return _r && _r->_method_mask == ALL_METHOD_MASK;
269 }
270 
271 inline bool
isMethodAllowed(int method_wksidx)272 IpAllow::ACL::isMethodAllowed(int method_wksidx) const
273 {
274   return _r && 0 != (_r->_method_mask & MethodIdxToMask(method_wksidx));
275 }
276 
277 inline bool
isNonstandardMethodAllowed(std::string_view method)278 IpAllow::ACL::isNonstandardMethodAllowed(std::string_view method) const
279 {
280   if (_r == nullptr) {
281     return false;
282   } else if (_r->_method_mask == ALL_METHOD_MASK) {
283     return true;
284   }
285   bool method_in_set =
286     std::find_if(_r->_nonstandard_methods.begin(), _r->_nonstandard_methods.end(),
287                  [method](std::string_view const &s) { return 0 == strcasecmp(s, method); }) != _r->_nonstandard_methods.end();
288   return _r->_deny_nonstandard_methods ? !method_in_set : method_in_set;
289 }
290 
291 inline void
clear()292 IpAllow::ACL::clear()
293 {
294   if (_config) {
295     _config->release();
296     _config = nullptr;
297   }
298   _r = nullptr;
299 }
300 
301 inline int
source_line()302 IpAllow::ACL::source_line() const
303 {
304   return _r ? _r->_src_line : 0;
305 }
306 
307 // ------ IpAllow methods --------
308 
309 inline bool
enableAcceptCheck(bool state)310 IpAllow::enableAcceptCheck(bool state)
311 {
312   bool temp      = accept_check_p;
313   accept_check_p = state;
314   return temp;
315 }
316 
317 inline bool
isAcceptCheckEnabled()318 IpAllow::isAcceptCheckEnabled()
319 {
320   return accept_check_p;
321 }
322 
323 inline auto
324 IpAllow::match(IpEndpoint const *ip, match_key_t key) -> ACL
325 {
326   return self_type::match(&ip->sa, key);
327 }
328 
329 inline auto
330 IpAllow::makeAllowAllACL() -> ACL
331 {
332   return {&ALLOW_ALL_RECORD, nullptr};
333 }
334 
335 inline const ts::file::path &
get_config_file()336 IpAllow::get_config_file() const
337 {
338   return config_file;
339 }
340