You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bn...@apache.org on 2024/02/05 23:49:31 UTC
(trafficserver) branch master updated: Core implementation of IP categories for ip_allow.yaml (#11004)
This is an automated email from the ASF dual-hosted git repository.
bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 075dcda4ae Core implementation of IP categories for ip_allow.yaml (#11004)
075dcda4ae is described below
commit 075dcda4ae01338b155846917db6035fd0788d5a
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Mon Feb 5 17:49:25 2024 -0600
Core implementation of IP categories for ip_allow.yaml (#11004)
Adds the ip_category feature for ip_allow.yaml so that users can specify
arbitrary IP descriptions like the following:
```yaml
ip_allow:
- apply: in
ip_category: ACME_INTERNAL
action: allow
methods:
- GET
- HEAD
- POST
- PUSH
- DELETE
```
IP categories are defined via an `ip_categories` root node or through a file configured via
proxy.config.cache.ip_categories.filename.
---
configs/ip_allow.schema.json | 27 +-
doc/admin-guide/files/ip_allow.yaml.en.rst | 68 ++++-
doc/admin-guide/files/records.yaml.en.rst | 18 ++
doc/admin-guide/files/remap.config.en.rst | 17 +-
include/proxy/IPAllow.h | 77 ++++-
include/proxy/http/remap/AclFiltering.h | 33 ++-
include/proxy/http/remap/RemapConfig.h | 14 +-
include/tscore/Filenames.h | 1 +
src/mgmt/config/AddConfigFilesHere.cc | 1 +
src/proxy/IPAllow.cc | 248 ++++++++++++++--
src/proxy/http/remap/AclFiltering.cc | 20 ++
src/proxy/http/remap/RemapConfig.cc | 41 ++-
src/proxy/http/remap/UrlRewrite.cc | 19 +-
src/records/RecordsConfig.cc | 2 +
src/traffic_server/traffic_server.cc | 4 +
tests/gold_tests/ip_allow/ip_category.test.py | 326 +++++++++++++++++++++
.../replays/https_categories_all.replay.yaml | 94 ++++++
.../replays/https_categories_external.replay.yaml | 92 ++++++
.../https_categories_external_remap.replay.yaml | 92 ++++++
.../replays/https_categories_internal.replay.yaml | 106 +++++++
.../replays/https_categories_server.replay.yaml | 94 ++++++
21 files changed, 1340 insertions(+), 54 deletions(-)
diff --git a/configs/ip_allow.schema.json b/configs/ip_allow.schema.json
index 6c9a8853d4..07564ceff1 100644
--- a/configs/ip_allow.schema.json
+++ b/configs/ip_allow.schema.json
@@ -22,6 +22,10 @@
"description": "A range of IP addresses in a single family.",
"type": "string"
},
+ "category": {
+ "description": "An IP category representing a set of IP ranges.",
+ "type": "string"
+ },
"action": {
"description": "Enforcement action.",
"type": "string",
@@ -68,6 +72,20 @@
}
]
},
+ "ip_categories": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/category"
+ },
+ {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/category"
+ }
+ }
+ ]
+ },
"action": {
"$ref": "#/definitions/action"
},
@@ -75,7 +93,14 @@
"$ref": "#/definitions/methods"
}
},
- "required": [ "apply", "ip_addrs", "action" ]
+ "oneOf": [
+ {
+ "required": [ "apply", "ip_addrs", "action" ]
+ },
+ {
+ "required": [ "apply", "ip_categories", "action" ]
+ }
+ ]
}
}
}
diff --git a/doc/admin-guide/files/ip_allow.yaml.en.rst b/doc/admin-guide/files/ip_allow.yaml.en.rst
index db2e58dfa4..83b8458b9a 100644
--- a/doc/admin-guide/files/ip_allow.yaml.en.rst
+++ b/doc/admin-guide/files/ip_allow.yaml.en.rst
@@ -26,7 +26,7 @@ The :file:`ip_allow.yaml` file controls client access to |TS| and |TS| connectio
This control is specified via rules. Each rule has:
* A direction (inbound or out).
-* A range of IP address to which the rule applies.
+* A range of IP addresses or an IP category to which the rule applies.
* An action, either accept or deny.
* A list of HTTP methods.
@@ -82,7 +82,21 @@ The keys in a rule are:
``ip_addrs``
IP addresses to match for the rule to be applied. This can be either an address range or an
- array of address ranges. This is a required key.
+ array of address ranges. Either this or ``ip_categories`` are required keys for a rule.
+
+``ip_categories``
+ A user defined string identifying a category of IP addresses relevant to a particular network.
+ For example, ``ACME_INTERNAL`` might represent the set of IP addresses for hosts within a
+ company's network. ``ACME_EXTERNAL`` might represet hosts belonging to the company's network, but
+ which are outside the company's firewall. ``ACME_ALL`` could be used to represent the set of both
+ of these categories. Multiple categories can be specified as an array of strings.
+
+ The set of IP ranges belonging to each category is specified via the separate ``ip_categories``
+ root level node. The :file:`ip_allow.yaml` parser also supports supplying the IP categories via
+ an external file specified with the :ts:cv:`proxy.config.cache.ip_categories.filename`
+ configuration.
+
+ Either this or ``ip_addrs`` are required keys for a rule.
``action``
The action, which must be ``allow`` or ``deny``. This is a required key.
@@ -93,8 +107,9 @@ The keys in a rule are:
keyword "ALL" means all methods, making the specification of any other method redundant. All
methods comparisons are case insensitive. This is an optional key.
-An IP address range can be specified in several ways. A range is always IPv4 or IPv6, it is not
-allowed to have a range that contains addresses from different IP address families.
+An IP address range for ``ip_addrs`` or ``ip_categories`` can be specified in several ways. A range
+is always IPv4 or IPv6, it is not allowed to have a range that contains addresses from different IP
+address families.
* A single address, which specifies a range of size 1, e.g. "127.0.0.1".
* A minimum and maximum address separated by a dash, e.g. "10.1.0.0-10.1.255.255".
@@ -131,7 +146,7 @@ enables all methods for all outbound connections.
Examples
========
-The following example enables all clients access.::
+The following example enables all clients access::
apply: in
ip_addrs: 0.0.0.0-255.255.255.255
@@ -222,7 +237,7 @@ This will match the IP address for the target servers on the outbound connection
method is ``GET`` or ``HEAD`` the connection will be allowed, otherwise the connection will be
denied.
-As a final example, here is the default configuration in compact form::
+For the purposes of illustration, here is the default configuration in compact form::
ip_allow: [
{ apply: in, ip_addrs: 127.0.0.1, action: allow },
@@ -231,6 +246,47 @@ As a final example, here is the default configuration in compact form::
{ apply: in, ip_addrs: "::/0", action: deny, methods: [ PURGE, PUSH, DELETE, TRACE ] }
]
+The following example demonstrates how to use ``ip_categories``. In this example, the
+``ip_categories`` is ``ACME_INTERNAL`` which is presumably associated with trusted internal IP
+addresses and thus are allowed to ``POST`` and ``DELETE`` resources.
+
+Note this example demonstrates that it is OK to mix ``ip_categories`` and ``ip_addrs`` rules in a
+single :file:`ip_allow.yaml` file. In this case all other IPv4 addresses not matched on
+``ACME_INTERNAL`` match on ``0/0`` and can only perform ``GET`` and ``HEAD`` requests::
+
+ - apply: in
+ ip_categories: ACME_INTERNAL
+ action: allow
+ methods:
+ - GET
+ - HEAD
+ - POST
+ - DELETE
+ - apply: in
+ ip_addrs: 0/0
+ action: allow
+ methods:
+ - GET
+ - HEAD
+
+The set of IP addresses associated with ``ACME_INTERNAL`` can be specified
+using the ``ip_categories`` node like so::
+
+ ip_categories:
+ - name: ACME_INTERNAL
+ ip_addrs:
+ - 10.0.0.0/8
+ - 172.16.0.0/20
+ - 192.168.1.0/24
+
+ ip_allow:
+ - apply: in
+ # ...
+
+The ``ip_categories`` node will generally be at the start of the :file:`ip_allow.yaml` file.
+Alternatively, the same content with the ``ip_categories`` root node can exist in a separate file
+specified with the :ts:cv:`proxy.config.cache.ip_categories.filename` configuration.
+
.. note::
For ATS 9.0, this file is (almost) backwards compatible. If the first line is a single '#'
diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst
index 68a312aad2..afb7437562 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -2090,6 +2090,24 @@ Security
this value via a :ref:`host_sni_policy<override-host-sni-policy>` attribute.
+IP Allow
+========
+
+.. ts:cv:: CONFIG proxy.config.cache.ip_allow.filename STRING ip_allow.yaml
+ :reloadable:
+
+ Set the file path for the IP allow configuration file. For details of the use
+ of this file, see :file:`ip_allow.yaml`. If this is a relative path, |TS|
+ loads it relative to the ``SYSCONFDIR`` directory.
+
+.. ts:cv:: CONFIG proxy.config.cache.ip_categories.filename STRING NULL
+ :reloadable:
+
+ Set the file path for the IP allow categories definition file. For details of
+ the use of this file, see :file:`ip_allow.yaml`. If this is a relative path,
+ |TS| loads it relative to the ``SYSCONFDIR`` directory.
+
+
Cache Control
=============
diff --git a/doc/admin-guide/files/remap.config.en.rst b/doc/admin-guide/files/remap.config.en.rst
index 407e4fde03..b1c7d0e1db 100644
--- a/doc/admin-guide/files/remap.config.en.rst
+++ b/doc/admin-guide/files/remap.config.en.rst
@@ -428,6 +428,13 @@ This will pass "1" and "2" to plugin1.so and "3" to plugin2.so
.. _remap-config-named-filters:
+NextHop Selection Strategies
+============================
+
+You may configure Nexthop or Parent hierarchical caching rules by remap using the
+**@strategy** tag. See :doc:`../configuration/hierarchical-caching.en` and :doc:`strategies.yaml.en`
+for configuration details and examples.
+
Acl Filters
===========
@@ -455,10 +462,13 @@ Examples
map http://foo.example.com/ http://foo.example.com/ @action=allow @src_ip=127.0.0.1 @method=post @method=get @method=head
+ map http://foo.example.com/ http://foo.example.com/ @action=allow @src_ip_category=ACME_INTERNAL @method=post @method=get @method=head
+
Note that these Acl filters will return a 403 response if the resource is restricted.
The difference between ``@src_ip`` and ``@in_ip`` is that the ``@src_ip`` is the client
ip and the ``in_ip`` is the ip address the client is connecting to (the incoming address).
+``@src_ip_category`` functions like ``ip_category`` described in :file:`ip_allow.yaml`.
Named Filters
=============
@@ -516,13 +526,6 @@ would be ::
Note this entirely disables IP Allow checks for those remap rules.
-NextHop Selection Strategies
-============================
-
-You may configure Nexthop or Parent hierarchical caching rules by remap using the
-**@strategy** tag. See :doc:`../configuration/hierarchical-caching.en` and :doc:`strategies.yaml.en`
-for configuration details and examples.
-
Including Additional Remap Files
================================
diff --git a/include/proxy/IPAllow.h b/include/proxy/IPAllow.h
index 3c332e2ce8..8dc5545e65 100644
--- a/include/proxy/IPAllow.h
+++ b/include/proxy/IPAllow.h
@@ -32,7 +32,6 @@
#include <string>
#include <string_view>
-#include <vector>
#include "proxy/hdrs/HTTP.h"
#include "iocore/eventsystem/ConfigProcessor.h"
@@ -89,6 +88,7 @@ public:
using self_type = IpAllow; ///< Self reference type.
using scoped_config = ConfigProcessor::scoped_config<self_type, self_type>;
using IpMap = swoc::IPSpace<Record const *>;
+ using IpCategories = std::unordered_map<std::string, swoc::IPSpace<bool>>;
// indicator for whether we should be checking the acl record for src ip or dest ip
enum match_key_t { SRC_ADDR, DST_ADDR };
@@ -102,8 +102,38 @@ public:
static constexpr swoc::TextView OPT_METHOD{"method"};
static constexpr swoc::TextView OPT_METHOD_ALL{"all"};
+ /*
+ * A YAML configuration file looks something like this:
+ *
+ * ip_categories:
+ * - name: ACME_INTERNAL
+ * ip_addrs:
+ * - 10.0.0.0/8
+ * - 172.16.0.0/20
+ * - 192.168.1.0/24
+ *
+ * ip_allow:
+ * - apply: in
+ * ip_categories: ACME_INTERNAL
+ * action: allow
+ * methods:
+ * - GET
+ * - HEAD
+ * - POST
+ * - apply: in
+ * ip_addrs: 127.0.0.1
+ * action: allow
+ * methods: ALL
+ *
+ */
static const inline std::string YAML_TAG_ROOT{"ip_allow"};
+
+ static const inline std::string YAML_TAG_CATEGORY_ROOT{"ip_categories"};
+ static const inline std::string YAML_TAG_CATEGORY_NAME{"name"};
+ static const inline std::string YAML_TAG_CATEGORY_IP_ADDRS{"ip_addrs"};
+
static const inline std::string YAML_TAG_IP_ADDRS{"ip_addrs"};
+ static const inline std::string YAML_TAG_IP_CATEGORIES{"ip_categories"};
static const inline std::string YAML_TAG_APPLY{"apply"};
static const inline std::string YAML_VALUE_APPLY_IN{"in"};
static const inline std::string YAML_VALUE_APPLY_OUT{"out"};
@@ -163,7 +193,7 @@ public:
IpAllow *_config{nullptr}; ///< The backing configuration.
};
- explicit IpAllow(const char *config_var);
+ explicit IpAllow(const char *ip_allow_config_var, const char *categories_config_var);
void Print() const;
@@ -201,6 +231,25 @@ public:
const swoc::file::path &get_config_file() const;
+ /**
+ * Check if an IP category contains a specific IP address.
+ *
+ * @param category The IP category to check.
+ * @param addr The IP address to check against the category.
+ * @return True if the category contains the address, false otherwise.
+ */
+ static bool ip_category_contains_addr(std::string const &category, swoc::IPAddr const &addr);
+
+ /** Indicate whether ip_allow.yaml has no rules associated with it.
+ *
+ * If there are no rules, then all traffic will be blocked. This is used
+ * during ATS configuration to verify that the user has provided a usable
+ * ip_allow.yaml file.
+ *
+ * @return True if there are no rules in ip_allow.yaml, false otherwise.
+ */
+ static bool has_no_rules();
+
private:
static size_t configid; ///< Configuration ID for update management.
static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access.
@@ -209,17 +258,26 @@ private:
void DebugMap(IpMap const &map) const;
swoc::Errata BuildTable();
- swoc::Errata YAMLBuildTable(const std::string &);
+ swoc::Errata YAMLBuildTable(const std::string &content);
swoc::Errata YAMLLoadEntry(const YAML::Node &);
swoc::Errata YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, Record const *mark);
+ swoc::Errata YAMLLoadIPCategory(const YAML::Node &, IpMap *map, Record const *mark);
swoc::Errata YAMLLoadMethod(const YAML::Node &node, Record &rec);
- /// Copy @a src to the local arena and review a view of the copy.
+ swoc::Errata BuildCategories();
+ swoc::Errata YAMLBuildCategories(const std::string &content);
+ swoc::Errata YAMLLoadCategoryRoot(const YAML::Node &);
+ swoc::Errata YAMLLoadCategoryDefinition(const YAML::Node &);
+ swoc::Errata YAMLLoadCategoryIpRange(const YAML::Node &, swoc::IPSpace<bool> &space);
+
+ /// Copy @a src to the local arena and return a view of the copy.
swoc::TextView localize(swoc::TextView src);
- swoc::file::path config_file; ///< Path to configuration file.
+ swoc::file::path ip_allow_config_file; ///< Path to ip_allow configuration file.
+ swoc::file::path ip_categories_config_file; ///< Path to ip_allow configuration file.
IpMap _src_map;
IpMap _dst_map;
+ IpCategories ip_category_map; ///< Map of IP categories to IP spaces.
/// Storage for records.
swoc::MemArena _arena;
@@ -364,5 +422,12 @@ IpAllow::makeAllowAllACL() -> ACL
inline const swoc::file::path &
IpAllow::get_config_file() const
{
- return config_file;
+ return ip_allow_config_file;
+}
+
+inline bool
+IpAllow::has_no_rules()
+{
+ auto const *self = IpAllow::acquire();
+ return self->_src_map.count() == 0 && self->_dst_map.count() == 0;
}
diff --git a/include/proxy/http/remap/AclFiltering.h b/include/proxy/http/remap/AclFiltering.h
index 53d2a91e63..a19287b107 100644
--- a/include/proxy/http/remap/AclFiltering.h
+++ b/include/proxy/http/remap/AclFiltering.h
@@ -25,8 +25,11 @@
#include "tscore/ink_inet.h"
-#include <string>
+#include "swoc/IPAddr.h"
+
#include <set>
+#include <string>
+#include <string_view>
#include <vector>
// ===============================================================================
@@ -51,13 +54,35 @@ struct src_ip_info_t {
/// @return @c true if @a ip is inside @a this range.
bool
- contains(IpEndpoint const &ip)
+ contains(IpEndpoint const &ip) const
{
IpAddr addr{ip};
return addr.cmp(start) >= 0 && addr.cmp(end) <= 0;
}
};
+struct src_ip_category_info_t {
+ std::string category; ///< The IP category for this remap rule.
+ bool invert = false; ///< Should we "invert" the meaning of these IP categories ("not in categories")
+
+ void
+ reset()
+ {
+ category.clear();
+ invert = false;
+ }
+
+ /// @return @c true if @a ip is inside @a this categories.
+ bool
+ contains(IpEndpoint const &ip) const
+ {
+ return ask_ip_allow_about_category(category, swoc::IPAddr{ip});
+ }
+
+private:
+ bool ask_ip_allow_about_category(std::string const &category, swoc::IPAddr const &addr) const;
+};
+
/**
*
**/
@@ -71,6 +96,7 @@ public:
char *filter_name = nullptr; // optional filter name
unsigned int allow_flag : 1, // action allow deny
src_ip_valid : 1, // src_ip range valid
+ src_ip_category_valid : 1, // src_ip range valid
in_ip_valid : 1,
active_queue_flag : 1, // filter is in active state (used by .useflt directive)
internal : 1; // filter internal HTTP requests
@@ -90,6 +116,9 @@ public:
int src_ip_cnt; // how many valid src_ip rules we have
src_ip_info_t src_ip_array[ACL_FILTER_MAX_SRC_IP];
+ int src_ip_category_cnt = 0; // how many valid src_ip rules we have
+ src_ip_category_info_t src_ip_category_array[ACL_FILTER_MAX_SRC_IP];
+
// in_ip
int in_ip_cnt; // how many valid dest_ip rules we have
src_ip_info_t in_ip_array[ACL_FILTER_MAX_IN_IP];
diff --git a/include/proxy/http/remap/RemapConfig.h b/include/proxy/http/remap/RemapConfig.h
index 2cca432f8a..4eac75d9de 100644
--- a/include/proxy/http/remap/RemapConfig.h
+++ b/include/proxy/http/remap/RemapConfig.h
@@ -35,13 +35,15 @@ class UrlRewrite;
#define REMAP_OPTFLG_PPARAM 0x0004u /* "pparam=" option (per remap plugin option) */
#define REMAP_OPTFLG_METHOD 0x0008u /* "method=" option (used for ACL filtering) */
#define REMAP_OPTFLG_SRC_IP 0x0010u /* "src_ip=" option (used for ACL filtering) */
-#define REMAP_OPTFLG_ACTION 0x0020u /* "action=" option (used for ACL filtering) */
-#define REMAP_OPTFLG_INTERNAL 0x0040u /* only allow internal requests to hit this remap */
-#define REMAP_OPTFLG_IN_IP 0x0080u /* "in_ip=" option (used for ACL filtering)*/
-#define REMAP_OPTFLG_STRATEGY 0x0100u /* "strategy=" the name of the nexthop selection strategy */
+#define REMAP_OPTFLG_SRC_IP_CATEGORY 0x0020u /* "src_ip_category=" option (used for ACL filtering) */
+#define REMAP_OPTFLG_ACTION 0x0040u /* "action=" option (used for ACL filtering) */
+#define REMAP_OPTFLG_INTERNAL 0x0080u /* only allow internal requests to hit this remap */
+#define REMAP_OPTFLG_IN_IP 0x0100u /* "in_ip=" option (used for ACL filtering)*/
+#define REMAP_OPTFLG_STRATEGY 0x0200u /* "strategy=" the name of the nexthop selection strategy */
#define REMAP_OPTFLG_MAP_ID 0x0800u /* associate a map ID with this rule */
-#define REMAP_OPTFLG_INVERT 0x80000000u /* "invert" the rule (for src_ip at least) */
-#define REMAP_OPTFLG_ALL_FILTERS (REMAP_OPTFLG_METHOD | REMAP_OPTFLG_SRC_IP | REMAP_OPTFLG_ACTION | REMAP_OPTFLG_INTERNAL)
+#define REMAP_OPTFLG_INVERT 0x80000000u /* "invert" the rule (for src_ip and src_ip_category at least) */
+#define REMAP_OPTFLG_ALL_FILTERS \
+ (REMAP_OPTFLG_METHOD | REMAP_OPTFLG_SRC_IP | REMAP_OPTFLG_SRC_IP_CATEGORY | REMAP_OPTFLG_ACTION | REMAP_OPTFLG_INTERNAL)
struct BUILD_TABLE_INFO {
BUILD_TABLE_INFO();
diff --git a/include/tscore/Filenames.h b/include/tscore/Filenames.h
index b6ff616cb8..90ae0485df 100644
--- a/include/tscore/Filenames.h
+++ b/include/tscore/Filenames.h
@@ -34,6 +34,7 @@ namespace filename
constexpr const char *LOGGING = "logging.yaml";
constexpr const char *CACHE = "cache.config";
constexpr const char *IP_ALLOW = "ip_allow.yaml";
+ constexpr const char *IP_CATEGORIES = "ip_categories.yaml";
constexpr const char *HOSTING = "hosting.config";
constexpr const char *SOCKS = "socks.config";
constexpr const char *PARENT = "parent.config";
diff --git a/src/mgmt/config/AddConfigFilesHere.cc b/src/mgmt/config/AddConfigFilesHere.cc
index 9e9412d995..9e3fa3ef2d 100644
--- a/src/mgmt/config/AddConfigFilesHere.cc
+++ b/src/mgmt/config/AddConfigFilesHere.cc
@@ -70,6 +70,7 @@ initializeRegistry()
registerFile(ts::filename::RECORDS, ts::filename::RECORDS, NOT_REQUIRED);
registerFile("proxy.config.cache.control.filename", ts::filename::CACHE, NOT_REQUIRED);
registerFile("proxy.config.cache.ip_allow.filename", ts::filename::IP_ALLOW, NOT_REQUIRED);
+ registerFile("proxy.config.cache.ip_categories.filename", ts::filename::IP_CATEGORIES, NOT_REQUIRED);
registerFile("proxy.config.http.parent_proxy.file", ts::filename::PARENT, NOT_REQUIRED);
registerFile("proxy.config.url_remap.filename", ts::filename::REMAP, NOT_REQUIRED);
registerFile("", ts::filename::VOLUME, NOT_REQUIRED);
diff --git a/src/proxy/IPAllow.cc b/src/proxy/IPAllow.cc
index 1e134d897b..7c8c6141e8 100644
--- a/src/proxy/IPAllow.cc
+++ b/src/proxy/IPAllow.cc
@@ -24,15 +24,16 @@
limitations under the License.
*/
-#include <sstream>
-
#include "proxy/IPAllow.h"
+#include "records/RecCore.h"
+#include "swoc/Errata.h"
+#include "swoc/TextView.h"
#include "tscore/Filenames.h"
+#include "tscore/ink_memory.h"
#include "tsutil/ts_errata.h"
#include "swoc/Vectray.h"
#include "swoc/BufferWriter.h"
-#include "swoc/bwf_std.h"
#include "swoc/bwf_ex.h"
#include "swoc/bwf_ip.h"
@@ -91,12 +92,14 @@ IpAllow::startup()
ipAllowUpdate = new ConfigUpdateHandler<IpAllow>();
ipAllowUpdate->attach("proxy.config.cache.ip_allow.filename");
+ ipAllowUpdate->attach("proxy.config.cache.ip_categories.filename");
reconfigure();
ConfigInfo *config = configProcessor.get(configid);
if (config == nullptr) {
- configid = configProcessor.set(configid, new self_type("proxy.config.cache.ip_allow.filename"));
+ configid = configProcessor.set(
+ configid, new self_type("proxy.config.cache.ip_allow.filename", "proxy.config.cache.ip_categories.filename"));
Warning("%s not loaded; All IP Addresses will be blocked.", ts::filename::IP_ALLOW);
}
}
@@ -108,17 +111,24 @@ IpAllow::reconfigure()
Note("%s loading ...", ts::filename::IP_ALLOW);
- new_table = new self_type("proxy.config.cache.ip_allow.filename");
- auto errata = new_table->BuildTable();
- if (!errata.is_ok()) {
+ new_table = new self_type("proxy.config.cache.ip_allow.filename", "proxy.config.cache.ip_categories.filename");
+ // IP rules need categories, so load them first (if they exist).
+ if (auto errata = new_table->BuildCategories(); !errata.is_ok()) {
+ std::string text;
+ swoc::bwprint(text, "{} failed to load\n{}", new_table->ip_categories_config_file, errata);
+ Error("%s", text.c_str());
+ delete new_table;
+ return;
+ }
+ if (auto errata = new_table->BuildTable(); !errata.is_ok()) {
std::string text;
swoc::bwprint(text, "{} failed to load\n{}", ts::filename::IP_ALLOW, errata);
Error("%s", text.c_str());
delete new_table;
- } else {
- configid = configProcessor.set(configid, new_table);
- Note("%s finished loading", ts::filename::IP_ALLOW);
+ return;
}
+ configid = configProcessor.set(configid, new_table);
+ Note("%s finished loading", ts::filename::IP_ALLOW);
}
IpAllow *
@@ -139,6 +149,20 @@ IpAllow::release()
configProcessor.release(configid, this);
}
+bool
+IpAllow::ip_category_contains_addr(std::string const &category, swoc::IPAddr const &addr)
+{
+ self_type *self = acquire();
+ auto const spot = self->ip_category_map.find(category);
+ if (spot == self->ip_category_map.end()) {
+ return false;
+ }
+ auto const &space = spot->second;
+ bool const found = space.find(addr) != space.end();
+ self->release();
+ return found;
+}
+
IpAllow::ACL
IpAllow::match(swoc::IPAddr const &addr, match_key_t key)
{
@@ -170,7 +194,14 @@ IpAllow::match(swoc::IPAddr const &addr, match_key_t key)
// End API functions
//
-IpAllow::IpAllow(const char *config_var) : config_file(ats_scoped_str(RecConfigReadConfigPath(config_var)).get()) {}
+IpAllow::IpAllow(const char *ip_allow_config_var, const char *ip_categories_config_var)
+ : ip_allow_config_file(ats_scoped_str(RecConfigReadConfigPath(ip_allow_config_var)).get())
+{
+ std::string const path = RecConfigReadConfigPath(ip_categories_config_var);
+ if (!path.empty()) {
+ ip_categories_config_file = ats_scoped_str(path).get();
+ }
+}
BufferWriter &
bwformat(BufferWriter &w, Spec const &spec, IpAllow::IpMap const &map)
@@ -232,7 +263,7 @@ IpAllow::BuildTable()
ink_assert(_src_map.count() == 0 && _dst_map.count() == 0);
std::error_code ec;
- std::string content{swoc::file::load(config_file, ec)};
+ std::string content{swoc::file::load(ip_allow_config_file, ec)};
swoc::Errata errata;
if (ec.value() == 0) {
try {
@@ -314,9 +345,8 @@ IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, IpAllow::Record
return swoc::Errata(ERRATA_ERROR, "{} Expected IP address range at {}, found non-literal.", this, node.Mark());
}
- swoc::TextView debug(node.Scalar());
- (void)debug;
- if (swoc::IPRange r; r.load(node.Scalar())) {
+ swoc::TextView ip_range(node.Scalar());
+ if (swoc::IPRange r; r.load(ip_range)) {
map->fill(r, record);
} else {
return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
@@ -324,6 +354,23 @@ IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, IpAllow::Record
return {};
}
+swoc::Errata
+IpAllow::YAMLLoadIPCategory(const YAML::Node &node, IpMap *map, IpAllow::Record const *record)
+{
+ if (!node.IsScalar()) {
+ return swoc::Errata(ERRATA_ERROR, "{} Expected IP address category at {}, found non-literal.", this, node.Mark());
+ }
+ std::string const &category(node.Scalar());
+ if (auto spot = ip_category_map.find(category); spot != ip_category_map.end()) {
+ for (auto const &range : spot->second) {
+ map->fill(range.range_view(), record);
+ }
+ } else {
+ return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' is not category with a defined range.", this, node.Mark(), category);
+ }
+ return {};
+}
+
swoc::Errata
IpAllow::YAMLLoadEntry(const YAML::Node &entry)
{
@@ -374,6 +421,10 @@ IpAllow::YAMLLoadEntry(const YAML::Node &entry)
return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION);
}
+ if (entry[YAML_TAG_IP_ADDRS] && entry[YAML_TAG_IP_CATEGORIES]) {
+ return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' and '{}' cannot both be used in the same rule.", this, entry.Mark(),
+ YAML_TAG_IP_ADDRS, YAML_TAG_IP_CATEGORIES);
+ }
if (YAML::Node addr_node = entry[YAML_TAG_IP_ADDRS]; addr_node) {
bool marked_p = false;
if (addr_node.IsSequence()) {
@@ -396,8 +447,31 @@ IpAllow::YAMLLoadEntry(const YAML::Node &entry)
if (!marked_p) {
return swoc::Errata(ERRATA_ERROR, "No valid addresses for rule at {}", node.Mark());
}
+ } else if (YAML::Node category_node = entry[YAML_TAG_IP_CATEGORIES]; category_node) {
+ bool marked_p = false;
+ if (category_node.IsSequence()) {
+ for (auto const &n : category_node) {
+ if (auto errata = this->YAMLLoadIPCategory(n, map, record); errata.is_ok()) {
+ marked_p = true;
+ } else {
+ errata.note(R"(In record at {})", entry.Mark());
+ return errata;
+ }
+ }
+ } else {
+ if (auto errata = this->YAMLLoadIPCategory(category_node, map, record); errata.is_ok()) {
+ marked_p = true;
+ } else {
+ errata.note(R"(In record at {})", entry.Mark());
+ return errata;
+ }
+ }
+ if (!marked_p) {
+ return swoc::Errata(ERRATA_ERROR, "No valid IP category for rule at {}", node.Mark());
+ }
} else {
- return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_IP_ADDRS);
+ return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' or '{}' key not found.", this, entry.Mark(),
+ YAML_TAG_IP_ADDRS, YAML_TAG_IP_CATEGORIES);
}
if (auto methodNode = entry[YAML_TAG_METHODS]) {
@@ -426,19 +500,149 @@ IpAllow::YAMLBuildTable(std::string const &content)
return swoc::Errata("{} - top level object was not a map. All IP Addresses will be blocked", this);
}
- YAML::Node data{root[YAML_TAG_ROOT.data()]};
- if (!data) {
+ // IP categories are optional. Load them if specified. Note that the rules,
+ // if they use categories, depend upon the categories being defined. So the
+ // categories have to be processed first before the rules are.
+ YAML::Node categories{root[YAML_TAG_CATEGORY_ROOT.data()]};
+ if (auto errata = this->YAMLLoadCategoryRoot(categories); !errata.is_ok()) {
+ return errata;
+ }
+
+ // Now load the IPAllow rules.
+ YAML::Node rules{root[YAML_TAG_ROOT.data()]};
+ if (!rules) {
return swoc::Errata("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
- } else if (data.IsSequence()) {
- for (auto const &entry : data) {
+ } else if (rules.IsSequence()) {
+ for (auto const &entry : rules) {
if (auto errata = this->YAMLLoadEntry(entry); !errata.is_ok()) {
return errata;
}
}
- } else if (data.IsMap()) {
- return this->YAMLLoadEntry(data); // singleton, just load it.
+ } else if (rules.IsMap()) {
+ return this->YAMLLoadEntry(rules); // singleton, just load it.
} else {
return swoc::Errata("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
}
return {};
}
+
+swoc::Errata
+IpAllow::BuildCategories()
+{
+ std::error_code ec;
+ if (ip_categories_config_file.empty()) {
+ return {};
+ }
+
+ Note("%s loading categores file %s ...", ts::filename::IP_ALLOW, ip_categories_config_file.c_str());
+ std::string content{swoc::file::load(ip_categories_config_file, ec)};
+ swoc::Errata errata;
+ if (ec.value() == 0) {
+ try {
+ errata = this->YAMLBuildCategories(content);
+ } catch (std::exception &ex) {
+ return swoc::Errata(ec, ERRATA_ERROR, "{} - Invalid IP Categories {} content: {}", this, ip_categories_config_file,
+ ex.what());
+ }
+ if (!errata.is_ok()) {
+ errata.note("While parsing ip categories file: {}", ip_categories_config_file);
+ return errata;
+ }
+ } else {
+ return swoc::Errata(ERRATA_ERROR, "{} Failed to load {}", this, ec);
+ }
+ Note("%s done loading categores file %s ...", ts::filename::IP_ALLOW, ip_categories_config_file.c_str());
+ return {};
+}
+
+swoc::Errata
+IpAllow::YAMLBuildCategories(std::string const &content)
+{
+ YAML::Node root{YAML::Load(content)};
+ YAML::Node categories{root[YAML_TAG_CATEGORY_ROOT.data()]};
+ if (auto errata = this->YAMLLoadCategoryRoot(categories); !errata.is_ok()) {
+ return errata;
+ }
+ return {};
+}
+
+swoc::Errata
+IpAllow::YAMLLoadCategoryRoot(const YAML::Node &categories)
+{
+ if (categories) {
+ if (!categories.IsSequence()) {
+ return swoc::Errata("{} - '{}' tag must be a sequence of maps. All IP Addresses will be blocked", this,
+ YAML_TAG_CATEGORY_ROOT);
+ }
+ for (auto const &category : categories) {
+ if (!category.IsMap()) {
+ return swoc::Errata("{} - '{}' tag must be a sequence of maps. All IP Addresses will be blocked", this,
+ YAML_TAG_CATEGORY_ROOT);
+ }
+ if (auto errata = this->YAMLLoadCategoryDefinition(category); !errata.is_ok()) {
+ return errata;
+ }
+ }
+ }
+ return {};
+}
+
+swoc::Errata
+IpAllow::YAMLLoadCategoryDefinition(const YAML::Node &entry)
+{
+ /* Parse this into ip_category_map:
+ *
+ * - name: <category name>
+ * ip_addrs:
+ * - <ip range>
+ * - <ip range>
+ * - <ip range>
+ */
+ if (!entry.IsMap()) {
+ return swoc::Errata(ERRATA_ERROR, "{} {} - Category definition must be a map.", this, entry.Mark());
+ }
+
+ if (auto name_node = entry[YAML_TAG_CATEGORY_NAME]; name_node) {
+ if (!name_node.IsScalar()) {
+ return swoc::Errata(ERRATA_ERROR, "{} {} - Category name must be a string.", this, name_node.Mark());
+ }
+ std::string const &name(name_node.Scalar());
+ if (auto ip_addrs_node = entry[YAML_TAG_CATEGORY_IP_ADDRS]; ip_addrs_node) {
+ if (ip_addrs_node.IsSequence()) {
+ for (auto const &ip_addr_node : ip_addrs_node) {
+ if (auto errata = this->YAMLLoadCategoryIpRange(ip_addr_node, ip_category_map[name]); !errata.is_ok()) {
+ errata.note(R"(In category definition at {})", entry.Mark());
+ return errata;
+ }
+ }
+ } else {
+ if (auto errata = this->YAMLLoadCategoryIpRange(ip_addrs_node, ip_category_map[name]); !errata.is_ok()) {
+ errata.note(R"(In category definition at {})", entry.Mark());
+ return errata;
+ }
+ }
+ } else { // No ip_addrs.
+ return swoc::Errata(ERRATA_ERROR, "{} {} - IP Addresses must be specified.", this, entry.Mark());
+ }
+ } else { // No name
+ return swoc::Errata(ERRATA_ERROR, "{} {} - Category name must be specified.", this, entry.Mark());
+ }
+ return {};
+}
+
+swoc::Errata
+IpAllow::YAMLLoadCategoryIpRange(const YAML::Node &node, swoc::IPSpace<bool> &space)
+{
+ if (!node.IsScalar()) {
+ return swoc::Errata(ERRATA_ERROR, "{} Expected IP address range for category at {}, found non-literal.", this, node.Mark());
+ }
+
+ swoc::TextView ip_range(node.Scalar());
+ if (swoc::IPRange r; r.load(ip_range)) {
+ space.fill(r, true);
+ } else {
+ return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
+ }
+
+ return {};
+}
diff --git a/src/proxy/http/remap/AclFiltering.cc b/src/proxy/http/remap/AclFiltering.cc
index 739d11612f..259972768c 100644
--- a/src/proxy/http/remap/AclFiltering.cc
+++ b/src/proxy/http/remap/AclFiltering.cc
@@ -22,7 +22,19 @@
*/
#include "proxy/http/remap/AclFiltering.h"
+
#include "proxy/hdrs/HTTP.h"
+#include "proxy/IPAllow.h"
+
+// ===============================================================================
+// src_ip_category_info_t
+// ===============================================================================
+
+bool
+src_ip_category_info_t::ask_ip_allow_about_category(std::string const &category, swoc::IPAddr const &addr) const
+{
+ return IpAllow::ip_category_contains_addr(category, addr);
+}
// ===============================================================================
// acl_filter_rule
@@ -43,6 +55,9 @@ acl_filter_rule::reset()
for (i = (src_ip_cnt = 0); i < ACL_FILTER_MAX_SRC_IP; i++) {
src_ip_array[i].reset();
}
+ for (i = (src_ip_category_cnt = 0); i < ACL_FILTER_MAX_SRC_IP; i++) {
+ src_ip_category_array[i].reset();
+ }
src_ip_valid = 0;
for (i = (in_ip_cnt = 0); i < ACL_FILTER_MAX_IN_IP; i++) {
in_ip_array[i].reset();
@@ -114,6 +129,11 @@ acl_filter_rule::print()
printf("%s - %s, ", src_ip_array[i].start.toString(b1, sizeof(b1)), src_ip_array[i].end.toString(b2, sizeof(b2)));
}
printf("\n");
+ printf("src_ip_category_cnt=%d\n", src_ip_category_cnt);
+ for (i = 0; i < src_ip_category_cnt; i++) {
+ printf("%s, ", src_ip_category_array[i].category.c_str());
+ }
+ printf("\n");
printf("in_ip_cnt=%d\n", in_ip_cnt);
for (i = 0; i < in_ip_cnt; i++) {
ip_text_buffer b1, b2;
diff --git a/src/proxy/http/remap/RemapConfig.cc b/src/proxy/http/remap/RemapConfig.cc
index e76dd7dca6..2736802a14 100644
--- a/src/proxy/http/remap/RemapConfig.cc
+++ b/src/proxy/http/remap/RemapConfig.cc
@@ -423,7 +423,6 @@ const char *
remap_validate_filter_args(acl_filter_rule **rule_pp, const char **argv, int argc, char *errStrBuf, size_t errStrBufSize)
{
acl_filter_rule *rule;
- src_ip_info_t *ipi;
int i, j;
bool new_rule_flg = false;
@@ -505,7 +504,7 @@ remap_validate_filter_args(acl_filter_rule **rule_pp, const char **argv, int arg
}
return (const char *)errStrBuf;
}
- ipi = &rule->src_ip_array[rule->src_ip_cnt];
+ src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt];
if (ul & REMAP_OPTFLG_INVERT) {
ipi->invert = true;
}
@@ -532,6 +531,34 @@ remap_validate_filter_args(acl_filter_rule **rule_pp, const char **argv, int arg
}
}
+ if (ul & REMAP_OPTFLG_SRC_IP_CATEGORY) { /* "src_ip_category=" option */
+ if (rule->src_ip_category_cnt >= ACL_FILTER_MAX_SRC_IP) {
+ Debug("url_rewrite", "[validate_filter_args] Too many \"src_ip_category=\" filters");
+ snprintf(errStrBuf, errStrBufSize, "Defined more than %d \"src_ip_category=\" filters!", ACL_FILTER_MAX_SRC_IP);
+ errStrBuf[errStrBufSize - 1] = 0;
+ if (new_rule_flg) {
+ delete rule;
+ *rule_pp = nullptr;
+ }
+ return (const char *)errStrBuf;
+ }
+ src_ip_category_info_t *ipi = &rule->src_ip_category_array[rule->src_ip_category_cnt];
+ if (ul & REMAP_OPTFLG_INVERT) {
+ ipi->invert = true;
+ }
+ for (j = 0; j < rule->src_ip_category_cnt; j++) {
+ if (rule->src_ip_category_array[j].category == ipi->category) {
+ ipi->reset();
+ ipi = nullptr;
+ break; /* we have the same src_ip_category in the list */
+ }
+ }
+ if (ipi) {
+ rule->src_ip_category_cnt++;
+ rule->src_ip_category_valid = 1;
+ }
+ }
+
if (ul & REMAP_OPTFLG_IN_IP) { /* "dest_ip=" option */
if (rule->in_ip_cnt >= ACL_FILTER_MAX_IN_IP) {
Debug("url_rewrite", "[validate_filter_args] Too many \"in_ip=\" filters");
@@ -543,7 +570,7 @@ remap_validate_filter_args(acl_filter_rule **rule_pp, const char **argv, int arg
}
return (const char *)errStrBuf;
}
- ipi = &rule->in_ip_array[rule->in_ip_cnt];
+ src_ip_info_t *ipi = &rule->in_ip_array[rule->in_ip_cnt];
if (ul & REMAP_OPTFLG_INVERT) {
ipi->invert = true;
}
@@ -648,6 +675,14 @@ remap_check_option(const char **argv, int argc, unsigned long findmode, int *_re
*argptr = &argv[i][8];
}
ret_flags |= (REMAP_OPTFLG_SRC_IP | REMAP_OPTFLG_INVERT);
+ } else if (!strncasecmp(argv[i], "src_ip_category=~", 8)) {
+ if ((findmode & REMAP_OPTFLG_SRC_IP_CATEGORY) != 0) {
+ idx = i;
+ }
+ if (argptr) {
+ *argptr = &argv[i][17];
+ }
+ ret_flags |= (REMAP_OPTFLG_SRC_IP_CATEGORY | REMAP_OPTFLG_INVERT);
} else if (!strncasecmp(argv[i], "src_ip=", 7)) {
if ((findmode & REMAP_OPTFLG_SRC_IP) != 0) {
idx = i;
diff --git a/src/proxy/http/remap/UrlRewrite.cc b/src/proxy/http/remap/UrlRewrite.cc
index 79645ae19c..925b1b0e21 100644
--- a/src/proxy/http/remap/UrlRewrite.cc
+++ b/src/proxy/http/remap/UrlRewrite.cc
@@ -444,8 +444,25 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, url_mapping *map)
}
}
+ if (match && rp->src_ip_category_valid) {
+ Debug("url_rewrite", "match was true and we have specified an src_ip_category field");
+ match = false;
+ for (int j = 0; j < rp->src_ip_category_cnt && !match; j++) {
+ bool in_category = rp->src_ip_category_array[j].contains(s->client_info.src_addr);
+ if (rp->src_ip_category_array[j].invert) {
+ if (!in_category) {
+ match = true;
+ }
+ } else {
+ if (in_category) {
+ match = true;
+ }
+ }
+ }
+ }
+
if (match && rp->in_ip_valid) {
- Debug("url_rewrite", "match was true and we have specified a in_ip field");
+ Debug("url_rewrite", "match was true and we have specified an in_ip field");
match = false;
for (int j = 0; j < rp->in_ip_cnt && !match; j++) {
IpEndpoint incoming_addr;
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index ece1c6656c..e8a7abc8ff 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -795,6 +795,8 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.cache.ip_allow.filename", RECD_STRING, ts::filename::IP_ALLOW, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.cache.ip_categories.filename", RECD_STRING, "", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
{RECT_CONFIG, "proxy.config.cache.hosting_filename", RECD_STRING, ts::filename::HOSTING, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
{RECT_CONFIG, "proxy.config.cache.volume_filename", RECD_STRING, ts::filename::VOLUME, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc
index f91b6a13e9..83614b1c47 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -2188,6 +2188,10 @@ main(int /* argc ATS_UNUSED */, const char **argv)
pluginInitCheck.notify_one();
}
+ if (IpAllow::has_no_rules()) {
+ Error("No ip_allow.yaml entries found. All requests will be denied!");
+ }
+
SSLConfigParams::init_ssl_ctx_cb = init_ssl_ctx_callback;
SSLConfigParams::load_ssl_file_cb = load_ssl_file_callback;
sslNetProcessor.start(-1, stacksize);
diff --git a/tests/gold_tests/ip_allow/ip_category.test.py b/tests/gold_tests/ip_allow/ip_category.test.py
new file mode 100644
index 0000000000..e5044ecc62
--- /dev/null
+++ b/tests/gold_tests/ip_allow/ip_category.test.py
@@ -0,0 +1,326 @@
+'''
+Verify IP allow ip_category behavior.
+'''
+# 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.
+
+import os
+import re
+
+Test.Summary = '''
+Verify IP allow ip_category behavior.
+'''
+
+
+class CategoryFile:
+ """Encapsulate the various ip_category.yaml contents."""
+
+ contents: list['CategoryFile'] = []
+ parent_directory: str = Test.RunDirectory
+ _index: int = 0
+
+ def __init__(self, content: str):
+ """Initialize the object.
+
+ :param content: The content of the ip_category.yaml file.
+ """
+ self._content = content
+ self._index = len(CategoryFile.contents)
+ self._filename = os.path.join(CategoryFile.parent_directory, f'categories{self._index}.yaml')
+ CategoryFile.contents.append(self)
+
+ def _write(self):
+ with open(self._filename, 'w') as f:
+ f.write(self._content)
+
+ def get_path(self):
+ return self._filename
+
+ @classmethod
+ def write_all(cls):
+ for content in cls.contents:
+ content._write()
+
+
+localhost_is_internal_and_external = CategoryFile(
+ '''
+ip_categories:
+ - name: ACME_INTERNAL
+ ip_addrs: 127.0.0.1
+ - name: ACME_EXTERNAL
+ ip_addrs: 127.0.0.1
+ - name: ACME_ALL
+ ip_addrs: 127.0.0.1
+ - name: ALL
+ ip_addrs: 127.0.0.1
+''')
+
+localhost_is_external = CategoryFile(
+ '''
+ip_categories:
+ - name: ACME_INTERNAL
+ ip_addrs: 1.2.3.4
+ - name: ACME_REMAP_EXTERNAL
+ ip_addrs: 127.0.0.1
+ - name: ACME_EXTERNAL
+ ip_addrs: 127.0.0.1
+ - name: ACME_ALL
+ ip_addrs:
+ - 1.2.3.4
+ - 127.0.0.1
+ - name: ALL
+ ip_addrs:
+ - 1.2.3.4
+ - 127.0.0.1
+''')
+
+localhost_is_neither = CategoryFile(
+ '''
+ip_categories:
+ - name: ACME_INTERNAL
+ ip_addrs: 1.2.3.4
+ - name: ACME_EXTERNAL
+ ip_addrs: 1.2.3.4
+ - name: ACME_ALL
+ ip_addrs: 1.2.3.4
+ - name: ALL
+ ip_addrs:
+ - 1.2.3.4
+ - 127.0.0.1
+''')
+
+# Keep this below the above content instantiations.
+CategoryFile.write_all()
+
+
+class Test_ip_category:
+ """Configure a test to verify ip_category behavior."""
+
+ _client_counter: int = 0
+ _ts_is_started: bool = False
+ _reload_server_is_started: bool = False
+ _server_is_started: bool = False
+ _reload_counter: int = 0
+
+ _ts: 'TestProcess' = None
+ _server: 'TestProcess' = None
+ _reload_server: 'TestProcess' = None
+
+ _categories_filename: str = f'{Test.RunDirectory}/categories.yaml'
+ _category_files_are_written: bool = False
+
+ _server_replay = 'replays/https_categories_server.replay.yaml'
+
+ def __init__(
+ self, name: str, replay_file: str, ip_allow_config: str, ip_category_config: 'CategoryFile', acl_configuration: str,
+ expected_responses: list[int]):
+ """Initialize the test.
+
+ :param name: The name of the test.
+ :param replay_file: The replay file to be used.
+ :param ip_allow_config: The ip_allow configuration to be used.
+ :param ip_category_config: The ip_category.yaml configuration to be used.
+ :param acl_configuration: The ACL configuration to be used.
+ :param expect_responses: The in-order expected responses from the proxy.
+ """
+ self._replay_file = replay_file
+ self._ip_allow_config = ip_allow_config
+ self._acl_configuration = acl_configuration
+ self._expected_responses = expected_responses
+
+ self._update_categories_file(ip_category_config)
+ self._update_remap_with_acl()
+
+ self._configure_server()
+ self._configure_traffic_server()
+
+ tr = Test.AddTestRun(name)
+ self._configure_client(tr)
+
+ def _update_remap_with_acl(self) -> None:
+ """Update the remap.config file with the ACL configuration."""
+ if Test_ip_category._ts:
+ if self._acl_configuration:
+ tr = Test.AddTestRun(f"remap.config file update with acl: {self._acl_configuration}")
+ p = tr.Processes.Default
+ destination = os.path.join(Test_ip_category._ts.Variables.CONFIGDIR, 'remap.config')
+ common = f'map / http://127.0.0.1:{Test_ip_category._server.Variables.http_port} '
+ p.Command = f'echo {common} {self._acl_configuration} > {destination}; cat {destination}'
+ p.ReturnCode = 0
+ else:
+ tr = Test.AddTestRun(f"remap.config file update with no acl")
+ p = tr.Processes.Default
+ destination = os.path.join(Test_ip_category._ts.Variables.CONFIGDIR, 'remap.config')
+ common = f'map / http://127.0.0.1:{Test_ip_category._server.Variables.http_port} '
+ p.Command = f'echo {common} > {destination}; cat {destination}'
+ p.ReturnCode = 0
+
+ def _update_categories_file(self, category_content: 'CategoryFile') -> None:
+ """Update the categories file.
+
+ :param category_content: The content of the categories file.
+ """
+ tr = Test.AddTestRun(f"Categories file update: {category_content.get_path()}")
+ p = tr.Processes.Default
+ destination = Test_ip_category._categories_filename
+ p.Command = f'cp {category_content.get_path()} {destination}; cat {destination}; ls -ltr {destination}'
+ p.ReturnCode = 0
+
+ def _configure_server(self) -> None:
+ """Configure the server."""
+ if Test_ip_category._server:
+ # All test runs share a single server instance.
+ return
+ server = Test.MakeVerifierServerProcess(f"server", self._server_replay)
+ Test_ip_category._server = server
+
+ def _configure_traffic_server(self) -> None:
+ """Configure Traffic Server."""
+
+ if Test_ip_category._ts:
+ # All test runs share a single Traffic Server instance.
+
+ # Reload the ip_allow.yaml file.
+ ts = Test_ip_category._ts
+ tr = Test.AddTestRun(f"Reload the configuration file.")
+ Test_ip_category._reload_counter += 1
+ p = tr.Processes.Process(f"reload-{Test_ip_category._reload_counter}")
+ # The sleep is added to give time for the reload to happen.
+ p.Command = 'traffic_ctl config reload; sleep 30'
+ p.Env = ts.Env
+ # Killing the sleep can result in a -2 return code.
+ p.ReturnCode = Any(0, -2)
+ p.Ready = When.FileContains(
+ ts.Disk.diags_log.Name, "ip_allow.yaml finished loading", 1 + Test_ip_category._reload_counter)
+ p.Timeout = 20
+ tr.StillRunningAfter = ts
+ tr.Processes.Default.StartBefore(p)
+ tr.Processes.Default.Command = 'echo "waiting upon traffic server to reload"'
+ tr.TimeOut = 20
+
+ return
+ ts = Test.MakeATSProcess("ts", enable_cache=False, enable_tls=True)
+ Test_ip_category._ts = ts
+
+ ts.addDefaultSSLFiles()
+ ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ ts.Disk.records_config.update(
+ {
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http|ip_allow',
+ 'proxy.config.cache.ip_categories.filename': Test_ip_category._categories_filename,
+ 'proxy.config.http.push_method_enabled': 1,
+ 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir,
+ 'proxy.config.quic.no_activity_timeout_in': 0,
+ 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir,
+ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
+ 'proxy.config.http.connect_ports': Test_ip_category._server.Variables.http_port,
+ })
+
+ ts.Disk.remap_config.AddLine(
+ f'map / http://127.0.0.1:{Test_ip_category._server.Variables.http_port} {self._acl_configuration}')
+ ts.Disk.ip_allow_yaml.AddLines(self._ip_allow_config.split("\n"))
+
+ def _configure_client(self, tr: 'TestRun') -> None:
+ """Run the test.
+
+ :param tr: The TestRun object to associate the client process with.
+ """
+
+ if not Test_ip_category._server_is_started:
+ tr.Processes.Default.StartBefore(Test_ip_category._server)
+ Test_ip_category._server_is_started = True
+ if not Test_ip_category._ts_is_started:
+ tr.Processes.Default.StartBefore(Test_ip_category._ts)
+ Test_ip_category._ts_is_started = True
+
+ p = tr.AddVerifierClientProcess(
+ f'client-{Test_ip_category._client_counter}', self._replay_file, https_ports=[Test_ip_category._ts.Variables.ssl_port])
+ Test_ip_category._client_counter += 1
+
+ if self._expected_responses:
+ codes = [str(code) for code in self._expected_responses]
+ p.Streams.stdout += Testers.ContainsExpression(
+ '.*'.join(codes), "Verifying the expected order of responses", reflags=re.DOTALL | re.MULTILINE)
+ else:
+ # If there are no expected responses, expect the Warning about the rejected ip.
+ self._ts.Disk.diags_log.Content += Testers.ContainsExpression(
+ "client '127.0.0.1' prohibited by ip-allow policy", "Verify the client rejection warning message.")
+
+ # Also, the client will complain about the broken connections.
+ p.ReturnCode = 1
+
+
+IP_ALLOW_CONTENT = f'''
+ip_allow:
+ - apply: in
+ ip_categories: ACME_INTERNAL
+ action: allow
+ methods:
+ - GET
+ - HEAD
+ - POST
+ - PUSH
+ - apply: in
+ ip_categories: ACME_EXTERNAL
+ action: allow
+ methods:
+ - GET
+ - HEAD
+ - apply: in
+ ip_categories: ACME_ALL
+ action: allow
+ methods:
+ - HEAD
+ - apply: in
+ ip_categories: ALL
+ action: deny
+'''
+
+test_ip_allow_optional_methods = Test_ip_category(
+ "IP Category: INTERNAL",
+ replay_file='replays/https_categories_internal.replay.yaml',
+ ip_allow_config=IP_ALLOW_CONTENT,
+ ip_category_config=localhost_is_internal_and_external,
+ acl_configuration='',
+ expected_responses=[200, 200, 400, 403])
+
+test_ip_allow_optional_methods = Test_ip_category(
+ "IP Category: EXTERNAL",
+ replay_file='replays/https_categories_external.replay.yaml',
+ ip_allow_config=IP_ALLOW_CONTENT,
+ ip_category_config=localhost_is_external,
+ acl_configuration='',
+ expected_responses=[200, 403, 403])
+
+# Because all requests are outright rejected for 127.0.0.1, ATS will
+# reject all incoming transactions and not even give a 403 response.
+test_ip_allow_optional_methods = Test_ip_category(
+ "IP Category: ALL",
+ replay_file='replays/https_categories_all.replay.yaml',
+ ip_allow_config=IP_ALLOW_CONTENT,
+ ip_category_config=localhost_is_neither,
+ acl_configuration='',
+ expected_responses=None)
+
+# Deny GET as well via remap.config ACL.
+test_ip_allow_optional_methods = Test_ip_category(
+ "IP Category: INTERNAL",
+ replay_file='replays/https_categories_external_remap.replay.yaml',
+ ip_allow_config=IP_ALLOW_CONTENT,
+ ip_category_config=localhost_is_external,
+ acl_configuration='@action=deny @src_ip_category=ACME_REMAP_EXTERNAL @method=GET',
+ expected_responses=[403, 403, 403])
diff --git a/tests/gold_tests/ip_allow/replays/https_categories_all.replay.yaml b/tests/gold_tests/ip_allow/replays/https_categories_all.replay.yaml
new file mode 100644
index 0000000000..9c41fae78c
--- /dev/null
+++ b/tests/gold_tests/ip_allow/replays/https_categories_all.replay.yaml
@@ -0,0 +1,94 @@
+# 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.
+
+# The replay file executes various HTTP requests to verify the ip_allow policy
+# applies by default to all methods.
+
+meta:
+ version: "1.0"
+
+ blocks:
+ - standard_response: &standard_response
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+sessions:
+- protocol:
+ - name: http
+ version: 1
+ - name: tls
+ sni: test_sni
+ transactions:
+
+ # GET rejected
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /test/ip_allow/test_get
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ uuid, get ]
+ - [ X-Request, get ]
+
+ # Shouldn't be used.
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
+
+ # POST rejected
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /test/ip_allow/test_post
+ headers:
+ fields:
+ - [Content-Length, 10]
+ - [ uuid, post ]
+ - [ X-Request, post ]
+
+ # Shouldn't be used.
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
+
+ # PUSH rejected
+ - client-request:
+ method: "PUSH"
+ version: "1.1"
+ url: /test/ip_allow/test_push
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, push ]
+ - [ X-Request, push ]
+ - [ Content-Length, 113 ]
+ content:
+ encoding: plain
+ data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED"
+
+ <<: *standard_response
+
+ # Verify that ATS confirmed that the PUSH was successful, which it does
+ # with a 201 response.
+ proxy-response:
+ status: 403
diff --git a/tests/gold_tests/ip_allow/replays/https_categories_external.replay.yaml b/tests/gold_tests/ip_allow/replays/https_categories_external.replay.yaml
new file mode 100644
index 0000000000..54b67f5b97
--- /dev/null
+++ b/tests/gold_tests/ip_allow/replays/https_categories_external.replay.yaml
@@ -0,0 +1,92 @@
+# 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.
+
+# The replay file executes various HTTP requests to verify the ip_allow policy
+# applies by default to all methods.
+
+meta:
+ version: "1.0"
+
+ blocks:
+ - standard_response: &standard_response
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+sessions:
+- protocol:
+ - name: http
+ version: 1
+ - name: tls
+ sni: test_sni
+ transactions:
+
+ # GET allowed
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /test/ip_allow/test_get
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ uuid, get ]
+ - [ X-Request, get ]
+
+ <<: *standard_response
+
+ proxy-response:
+ status: 200
+
+ # POST rejected
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /test/ip_allow/test_post
+ headers:
+ fields:
+ - [Content-Length, 10]
+ - [ uuid, post ]
+ - [ X-Request, post ]
+
+ # Shouldn't be used.
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
+
+ # PUSH rejected
+ - client-request:
+ method: "PUSH"
+ version: "1.1"
+ url: /test/ip_allow/test_push
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, push ]
+ - [ X-Request, push ]
+ - [ Content-Length, 113 ]
+ content:
+ encoding: plain
+ data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED"
+
+ <<: *standard_response
+
+ # Verify that ATS rejected the PUSH.
+ proxy-response:
+ status: 403
diff --git a/tests/gold_tests/ip_allow/replays/https_categories_external_remap.replay.yaml b/tests/gold_tests/ip_allow/replays/https_categories_external_remap.replay.yaml
new file mode 100644
index 0000000000..16ca64ddf7
--- /dev/null
+++ b/tests/gold_tests/ip_allow/replays/https_categories_external_remap.replay.yaml
@@ -0,0 +1,92 @@
+# 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.
+
+# The replay file executes various HTTP requests to verify the ip_allow policy
+# applies by default to all methods.
+
+meta:
+ version: "1.0"
+
+ blocks:
+ - standard_response: &standard_response
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+sessions:
+- protocol:
+ - name: http
+ version: 1
+ - name: tls
+ sni: test_sni
+ transactions:
+
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /test/ip_allow/test_get
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ uuid, get ]
+ - [ X-Request, get ]
+
+ <<: *standard_response
+
+ # Even GET is now blocked via remap ACL.
+ proxy-response:
+ status: 403
+
+ # POST rejected
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /test/ip_allow/test_post
+ headers:
+ fields:
+ - [Content-Length, 10]
+ - [ uuid, post ]
+ - [ X-Request, post ]
+
+ # Shouldn't be used.
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
+
+ # PUSH rejected
+ - client-request:
+ method: "PUSH"
+ version: "1.1"
+ url: /test/ip_allow/test_push
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, push ]
+ - [ X-Request, push ]
+ - [ Content-Length, 113 ]
+ content:
+ encoding: plain
+ data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED"
+
+ <<: *standard_response
+
+ # Verify that ATS rejected the PUSH.
+ proxy-response:
+ status: 403
diff --git a/tests/gold_tests/ip_allow/replays/https_categories_internal.replay.yaml b/tests/gold_tests/ip_allow/replays/https_categories_internal.replay.yaml
new file mode 100644
index 0000000000..c8178be60e
--- /dev/null
+++ b/tests/gold_tests/ip_allow/replays/https_categories_internal.replay.yaml
@@ -0,0 +1,106 @@
+# 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.
+
+# Assume everything is allowed by default.
+
+meta:
+ version: "1.0"
+
+ blocks:
+ - standard_response: &standard_response
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+sessions:
+- protocol:
+ - name: http
+ version: 1
+ - name: tls
+ sni: test_sni
+ transactions:
+
+ # GET allowed
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /test/ip_allow/test_get
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ uuid, get ]
+ - [ X-Request, get ]
+
+ <<: *standard_response
+
+ proxy-response:
+ status: 200
+
+ # POST allowed
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /test/ip_allow/test_post
+ headers:
+ fields:
+ - [Content-Length, 10]
+ - [ uuid, post ]
+ - [ X-Request, post ]
+
+ <<: *standard_response
+
+ proxy-response:
+ status: 200
+
+ # PUSH allowed
+ - client-request:
+ method: "PUSH"
+ version: "1.1"
+ url: /test/ip_allow/test_push
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, push ]
+ - [ X-Request, push ]
+ - [ Content-Length, 113 ]
+ content:
+ encoding: plain
+ data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED"
+
+ <<: *standard_response
+
+ # Cacching is off, but a 400 still indicates that ATS processed it.
+ proxy-response:
+ status: 400
+
+ # DELETE is not allowed even for internal.
+ - client-request:
+ method: "DELETE"
+ version: "1.1"
+ url: /test/ip_allow/test_delete
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ uuid, delete ]
+ - [ X-Request, delete ]
+
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
diff --git a/tests/gold_tests/ip_allow/replays/https_categories_server.replay.yaml b/tests/gold_tests/ip_allow/replays/https_categories_server.replay.yaml
new file mode 100644
index 0000000000..9c41fae78c
--- /dev/null
+++ b/tests/gold_tests/ip_allow/replays/https_categories_server.replay.yaml
@@ -0,0 +1,94 @@
+# 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.
+
+# The replay file executes various HTTP requests to verify the ip_allow policy
+# applies by default to all methods.
+
+meta:
+ version: "1.0"
+
+ blocks:
+ - standard_response: &standard_response
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Content-Length, 20 ]
+
+sessions:
+- protocol:
+ - name: http
+ version: 1
+ - name: tls
+ sni: test_sni
+ transactions:
+
+ # GET rejected
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /test/ip_allow/test_get
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ uuid, get ]
+ - [ X-Request, get ]
+
+ # Shouldn't be used.
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
+
+ # POST rejected
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /test/ip_allow/test_post
+ headers:
+ fields:
+ - [Content-Length, 10]
+ - [ uuid, post ]
+ - [ X-Request, post ]
+
+ # Shouldn't be used.
+ <<: *standard_response
+
+ proxy-response:
+ status: 403
+
+ # PUSH rejected
+ - client-request:
+ method: "PUSH"
+ version: "1.1"
+ url: /test/ip_allow/test_push
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, push ]
+ - [ X-Request, push ]
+ - [ Content-Length, 113 ]
+ content:
+ encoding: plain
+ data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED"
+
+ <<: *standard_response
+
+ # Verify that ATS confirmed that the PUSH was successful, which it does
+ # with a 201 response.
+ proxy-response:
+ status: 403