This is an automated email from the ASF dual-hosted git repository.

amc 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 9db871a  YAML: Replace ip_allow.config with ip_allow.yaml.
9db871a is described below

commit 9db871a399b248fc80a84184e2f59acf61170eba
Author: Alan M. Carroll <[email protected]>
AuthorDate: Wed Mar 13 18:44:59 2019 -0500

    YAML: Replace ip_allow.config with ip_allow.yaml.
---
 CMakeLists.txt                                     |   1 +
 ci/rat-regex.txt                                   |   1 +
 configs/Makefile.am                                |   2 +-
 configs/ip_allow.config.default                    |  27 --
 configs/ip_allow.schema.json                       |  81 ++++++
 configs/ip_allow.yaml.default                      |  50 ++++
 doc/admin-guide/files/ip_allow.config.en.rst       | 259 ++++++++++++------
 doc/admin-guide/files/records.config.en.rst        |   2 +-
 doc/admin-guide/files/remap.config.en.rst          |   2 +-
 mgmt/RecordsConfig.cc                              |   2 +-
 proxy/IPAllow.cc                                   | 304 ++++++++++++++++++---
 proxy/IPAllow.h                                    |  49 +++-
 proxy/Makefile.am                                  |   1 +
 src/traffic_manager/AddConfigFilesHere.cc          |   2 +-
 .../gold_tests/autest-site/min_cfg/ip_allow.config |   4 -
 tests/gold_tests/autest-site/min_cfg/ip_allow.yaml |  43 +++
 .../gold_tests/autest-site/trafficserver.test.ext  |   2 +-
 17 files changed, 664 insertions(+), 168 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 49e861b..b464fcb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@ include_directories(
         include
         tests/include
         lib
+        lib/yamlcpp/include
         proxy
         proxy/hdrs
         proxy/http
diff --git a/ci/rat-regex.txt b/ci/rat-regex.txt
index 553902f..26c63b9 100644
--- a/ci/rat-regex.txt
+++ b/ci/rat-regex.txt
@@ -67,3 +67,4 @@ port\.h
 ^catch[.]hpp$
 ^configuru.hpp$
 ^yamlcpp$
+^tests/gold_tests/autest-site/min_cfg$
diff --git a/configs/Makefile.am b/configs/Makefile.am
index ccd4983..0f60e0c 100644
--- a/configs/Makefile.am
+++ b/configs/Makefile.am
@@ -29,7 +29,7 @@ nodist_sysconf_DATA = \
 dist_sysconf_DATA =    \
        cache.config.default \
        hosting.config.default \
-       ip_allow.config.default \
+       ip_allow.yaml.default \
        logging.yaml.default \
        parent.config.default \
        plugin.config.default \
diff --git a/configs/ip_allow.config.default b/configs/ip_allow.config.default
deleted file mode 100644
index 264d078..0000000
--- a/configs/ip_allow.config.default
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# ip_allow.config
-#
-# Documentation:
-#    
https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ip_allow.config.en.html
-#
-# Rules:
-# src_ip=<range of IP addresses> action=<action> [method=<list of methods 
separated by '|'>]
-#
-# Actions: ip_allow, ip_deny
-#
-# Multiple method keywords can be specified (method=GET method=HEAD), or
-# multiple methods can be separated by an '|' (method=GET|HEAD).  The method
-# keyword is optional and it is defaulted to ALL.
-# Available methods: ALL, GET, CONNECT, DELETE, HEAD, OPTIONS,
-# POST, PURGE, PUT, TRACE, PUSH
-#
-# Rules are applied in the order listed starting from the top.
-# That means you generally want to append your rules after the ones listed 
here.
-#
-# Allow anything on localhost (this is the default configuration based on the
-# deprecated CONFIG proxy.config.http.quick_filter.mask INT 0x482)
-src_ip=127.0.0.1                                  action=ip_allow method=ALL
-src_ip=::1                                        action=ip_allow method=ALL
-# Deny PURGE, DELETE, and PUSH for all (this implies allow other methods for 
all)
-src_ip=0.0.0.0-255.255.255.255                    action=ip_deny  
method=PUSH|PURGE|DELETE
-src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny  
method=PUSH|PURGE|DELETE
diff --git a/configs/ip_allow.schema.json b/configs/ip_allow.schema.json
new file mode 100644
index 0000000..ba495b6
--- /dev/null
+++ b/configs/ip_allow.schema.json
@@ -0,0 +1,81 @@
+{
+  "$schema": 
"https://github.com/apache/trafficserver/tree/master/configs/ip_allow.schema.json";,
+  "title": "Traffic Server IP Allow Configuration",
+  "description": "IP ACL configuration file structure. Licensed under Apache 
V2 https://www.apache.org/licenses/LICENSE-2.0";,
+  "type": "object",
+  "properties": {
+    "version": {
+      "type": "string",
+      "description": "Configuration format version."
+    },
+    "ip_addr_acl": {
+      "description": "Root tag for IP address ACL configuration",
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/rule"
+      }
+    }
+  },
+  "required": [ "ip_addr_acl" ],
+  "definitions": {
+    "range": {
+      "description": "A range of IP addresses in a single family.",
+      "type": "string"
+    },
+    "action": {
+      "description": "Enforcement action.",
+      "type": "string",
+      "enum": [ "allow", "deny" ]
+    },
+    "methods": {
+      "description": "Methods to check.",
+      "oneOf": [
+        {
+          "type": "string",
+          "description": "Method name"
+        },
+        {
+          "type": "array",
+          "description": "List of method names.",
+          "minItems": 1,
+          "items": {
+            "type": "string",
+            "description": "Method name"
+          }
+        }
+      ]
+    },
+    "rule": {
+      "description": "Connection ACL.",
+      "type": "object",
+      "properties": {
+        "apply": {
+          "description": "Where to apply the rule, inbound or outbound.",
+          "type": "string",
+          "enum": [ "in", "out" ]
+        },
+        "ip_addrs": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/range"
+            },
+            {
+              "type": "array",
+              "minItems": 1,
+              "items": {
+                "$ref": "#/definitions/range"
+              }
+            }
+          ]
+        },
+        "action": {
+          "$ref": "#/definitions/action"
+        },
+        "methods": {
+          "$ref": "#/definitions/methods"
+        }
+      },
+      "required": [ "apply", "ip_addrs", "action" ]
+    }
+  }
+}
diff --git a/configs/ip_allow.yaml.default b/configs/ip_allow.yaml.default
new file mode 100644
index 0000000..35b872b
--- /dev/null
+++ b/configs/ip_allow.yaml.default
@@ -0,0 +1,50 @@
+# YAML
+#
+# ip_allow.config
+#
+# Documentation:
+#    
https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ip_allow.config.en.html
+#
+# Rules:
+# Each rule is a mapping, with the tags
+#
+#   apply: Either "in" or "out" to apply to inbound and outbound connections 
respectively.
+#   ip_addrs: IP address ranges, either a single range or a list of ranges.
+#   action: "allow" or "deny"
+#   methods: A method name or sequence of method names. Available methods: 
GET, CONNECT, DELETE,
+#            HEAD, OPTIONS, POST, PURGE, PUT, TRACE, PUSH. The special name 
"ALL" indicates all
+#            methods and it overrides any other methods.
+#
+# A rule must have either "src" or "dst" to indicate if the IP addresses apply 
to inbound connections
+# or outbound connections.
+#
+# The top level tag 'ip_addr_acl' identifies the rule items. Its value must be 
a rule item or a
+# sequence of rule items.
+#
+# Rules are applied in the order listed starting from the top.
+# That means you generally want to append your rules after the ones listed 
here.
+#
+# Allow anything on localhost, limit destructive methods elsewhere.
+ip_addr_acl:
+  - apply: in
+    ip_addrs: 127.0.0.1
+    action: allow
+    methods: ALL
+  - apply: in
+    ip_addrs: ::1
+    action: allow
+    methods: ALL
+  - apply: in
+    ip_addrs: 0/0
+    action: deny
+    methods:
+      - PURGE
+      - PUSH
+      - DELETE
+  - apply: in
+    ip_addrs: ::/0
+    action: deny
+    methods:
+      - PURGE
+      - PUSH
+      - DELETE
diff --git a/doc/admin-guide/files/ip_allow.config.en.rst 
b/doc/admin-guide/files/ip_allow.config.en.rst
index 8a43a72..185af87 100644
--- a/doc/admin-guide/files/ip_allow.config.en.rst
+++ b/doc/admin-guide/files/ip_allow.config.en.rst
@@ -1,134 +1,225 @@
-.. 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
+.. 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.
+   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.
 
 .. include:: ../../common.defs
-.. highlight:: none
+.. highlight:: yaml
 
 ===============
-ip_allow.config
+ip_allow.yaml
 ===============
 
-.. configfile:: ip_allow.config
+.. configfile:: ip_allow.yaml
 
-The :file:`ip_allow.config` file controls client access to |TS| and |TS| 
connections to the servers.
-You can specify ranges of IP addresses that are allowed to connect to |TS| or 
that are allowed to be
-remapped by Traffic Server. After you modify the :file:`ip_allow.config` file, 
navigate to the |TS|
-bin directory and run the :option:`traffic_ctl config reload` command to apply 
changes.
+The :file:`ip_allow.yaml` file controls client access to |TS| and |TS| 
connections to upstream servers.
+This control is specified rules. Each rule has
+
+*  A direction (inbound or out).
+*  A range of IP address to which the rule applies.
+*  An action, either accept or deny.
+*  A list of HTTP methods.
+
+Inbound rules control access to |TS| from user agents. Outbound rules control 
access to upstream destinations
+from |TS|. The IP addresses always apply to the remote address for |TS|. That 
is, the user agent IP address
+for inbound rules and the upstream destination address for outbound rules. The 
rule can apply at the connection
+level or just to specific methods.
+
+|TS| can be updated for changes to the rules in :file:`ip_allow.yaml` file, by 
running the
+:option:`traffic_ctl config reload`.
 
 Format
 ======
 
-Each line in :file:`ip_allow.config` file must have on of the following formats
-format::
-
-    src_ip=<range of IP addresses> action=<action> [method=<list of methods 
separated by '|'>]
-    dest_ip=<range of IP addresses> action=<action> [method=<list of methods 
separated by '|'>]
-
-For ``src_ip`` the remote inbound connection address, i.e. the IP address of 
the client, is checked
-against the specified range of IP addresses. For ``dest_ip`` the outbound 
remote address (i.e. the IP
-address to which |TS| connects) is checked against the specified IP address 
range.
-
-Range specifications can be IPv4 or IPv6, but any single range must be one or 
the other. Ranges can
-be specified by two addresses, the lower address and the upper address, 
separated by a dash, ``-``.
-Such a range inclusive and contains the lower, upper addresses and all 
addresses in between. A range
-can also be specified by an address and a CIDR mask, separated by a slash, 
``/``. This case is
-converted to a range of the previous case by retaining only the left most 
``mask`` bits, clearing
-the rest for the lower address and setting them for the upper address. For 
instance, a mask of
-``23`` would mean the left most 23 bits are kept and all bits to the right are 
cleared or set.
-Finally, a range can be a single IP address which matches exactly that address 
(the equivalent of a
-range with the lower and upper values equal to that IP address).
-
-The value of ``method`` is a string which must consist of either HTTP method 
names separated by the
-character '|' or the keyword literal ``ALL``. This keyword may omitted in 
which case it is treated
-as if it were ``method=ALL``. Methods can also be specified by having multiple 
instances of the
-``method`` keyword, each specifying a single method. E.g., ``method=GET|HEAD`` 
is the same as
-``method=GET method=HEAD``. The method names are not validated which means 
non-standard method names
-can be specified.
-
-The ``action`` must be either ``ip_allow`` or ``ip_deny``. This controls what 
|TS| does if the
-address is in the range and the method matches. If there is a match, |TS| 
allows the connection (for
-``ip_allow``) or denies it (``ip_deny``).
-
-For each inbound or outbound connection the applicable rule is selected by 
first match on the IP
-address. The rule is then applied (if the method matches) or its opposite is 
applied (if the method
-doesn't match). If no rule is matched access is allowed. This makes each rule 
both an accept and
-deny, one explicit and the other implicit. The ``src_ip`` rules are checked 
when a host connects
-to |TS|. The ``dest_ip`` rules are checked when |TS| connects to another host.
-
-By default the :file:`ip_allow.config` file contains the following lines, 
which allows all methods
-to connections from localhost and denies the ``PUSH``, ``PURGE`` and 
``DELETE`` methods to all other
-IP addresses (note this allows all other methods to all IP addresses)::
-
-    src_ip=127.0.0.1                                  action=ip_allow 
method=ALL
-    src_ip=::1                                        action=ip_allow 
method=ALL
-    src_ip=0.0.0.0-255.255.255.255                    action=ip_deny  
method=PUSH|PURGE|DELETE
-    src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny  
method=PUSH|PURGE|DELETE
-
-This could also be specified as::
-
-    src_ip=127.0.0.1   action=ip_allow method=ALL
-    src_ip=::1         action=ip_allow method=ALL
-    src_ip=0/0         action=ip_deny  method=PUSH|PURGE|DELETE
-    src_ip=::/0        action=ip_deny  method=PUSH|PURGE|DELETE
+:file:`ip_allow.yaml` is YAML format. The default configuration is::
+
+   # YAML
+   ip_addr_acl:
+     - apply: in
+       ip_addrs: 127.0.0.1
+       action: allow
+       methods: ALL
+     - apply: in
+       ip_addrs: ::1
+       action: allow
+       methods: ALL
+     - apply: in
+       ip_addrs: 0/0
+       action: deny
+       methods:
+         - PURGE
+         - PUSH
+         - DELETE
+     - apply: in
+       ip_addrs: ::/0
+       action: deny
+       methods:
+         - PURGE
+         - PUSH
+         - DELETE
+
+Each rule is a mapping. The YAML data must have a top level key of 
"ip_addr_acl" and its value must
+be a mapping or a sequence of mappings, each of those being one rule.
+
+The keys in a rule are
+
+``apply``
+   This is where the rule is applied, either ``in`` or ``out``. Inbound 
application means
+   the rule is applied to inbound user agent connections. Outbound application 
means the rule is
+   applied to outbound connections from |TS| to an upstream destination. This 
is a required key.
+
+``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.
+
+``action``
+   The action, which must be ``allow`` or ``deny``. This is a required key.
+
+``methods``
+   This is optional. If not present, the rule action applies to all methods. 
If present, the rule
+   action is applied connections using those methods and its opposite to all 
other connections. The
+   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.
+
+*  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".
+*  A CIDR based value, e.g. "10.1.0.0/16", which is a range containing exactly 
the specified network.
+
+A rule must have the ``apply``, ``ip_addrs``, and ``action`` keys. Rules match 
based on
+IP addresses only, and are then applied to all matching sessions. If the rule 
is an ``allow`` rule,
+the specified methods are allowed and all other methods are denied. If the 
rule is a ``deny`` rule,
+the specified methods are denied and all other methods are allowed.
+
+For example, from the default configuration, the rule for ``127.0.0.1`` is 
``allow`` with all
+methods. Therefore an inbound connection from the loopback address (127.0.0.1) 
is allowed to use any
+method. The general IPv4 rule, covering all IPv4 address, is a ``deny`` rule 
and therefore when it
+matches the methods "PURGE", "PUSH", and "DELETE" are denied, any other method 
is allowed.
+
+The rules are matched in order, by IP address, therefore the general IPv4 rule 
does not apply to the
+loopback address because the latter is matched first.
+
+A major difference in application between ``in`` and ``out`` rules is that by 
default,
+inbound connections are denied and therefore if there is no rule that matches, 
the connection is
+denied. Outbound rules allow by default, so the absence of rules in the 
default configuration
+enables all methods for all outbound connections.
 
 Examples
 ========
 
 The following example enables all clients access.::
 
-   src_ip=0.0.0.0-255.255.255.255 action=ip_allow
+   apply: in
+   ip_addrs: 0.0.0.0-255.255.255.255
+   action: allow
 
 The following example allows access to all clients on addresses in a subnet::
 
-   src_ip=123.12.3.000-123.12.3.123 action=ip_allow
+   apply: in
+   ip_addrs: 123.12.3.000-123.12.3.123
+   action: allow
 
 The following example denies access all clients on addresses in a subnet::
 
-   src_ip=123.45.6.0-123.45.6.123 action=ip_deny
+   apppy: in
+   ip_addrs: 123.45.6.0-123.45.6.123
+   action: deny
 
 If the entire subnet were to be denied, that would be::
 
-   src_ip=123.45.6.0/24 action=ip_deny
+   apply: in
+   ip_addrs: 123.45.6.0/24
+   action: deny
 
-The following example allows one to any upstream servers::
+The following example allows any method to any upstream servers::
 
-   dest_ip=0.0.0.0-255.255.255.255 action=ip_allow
+   apply: out
+   ip_addrs: 0.0.0.0-255.255.255.255
+   action: allow
 
 Alternatively this can be done with::
 
-   dest_ip=0/0 action=ip_allow
+   apply: out
+   ip_addrs: 0/0
+   action: allow
+
+Or also by having no rules at all, as outbound by default is allow.
 
 The following example denies to access all servers on a specific subnet::
 
-   dest_ip=10.0.0.0-10.0.255.255 action=ip_deny
+   apply: out
+   ip_addr: 10.0.0.0-10.0.255.255
+   action: deny
 
 Alternatively::
 
-   dest_ip=10.0.0.0/16 action=ip_deny
+   apply: out
+   ip_addrs: 10.0.0.0/16
+   action: deny
+
+The ``ip_addrs`` can be an array of ranges, so that::
+
+   - apply: in
+     ip_addrs: 10.0.0.0/8
+     action: deny
+   - apply: in
+     ip_addrs: 172.16.0.0/20
+     action: deny
+   - apply: in
+     ip_addrs: 192.168.1.0/24
+     action: deny
+
+can be done more simply as::
+
+  apply: in
+  ip_addrs:
+    - 10.0.0.0/8
+    - 172.16.0.0/20
+    - 192.168.1.0/24
+  action: deny
 
 If the goal is to allow only ``GET`` and ``HEAD`` requests to those servers, 
it would be::
 
-   dest_ip=10.0.0.0/16 action=ip_allow method=GET method=HEAD
+   apply: out
+   ip_addrs: 10.0.0.0/16
+   methods: [ GET, HEAD ]
+   action: allow
 
-or::
+Alternatively::
 
-   dest_ip=10.0.0.0/16 action=ip_allow method=GET|HEAD
+   apply: out
+   ip_addrs: 10.0.0.0/16
+   methods:
+   - GET
+   - HEAD
+   action: allow
 
 This will match the IP address for the target servers on the outbound 
connection. Then, if the
 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::
+
+   ip_addr_acl: [
+     { apply: in, ip_addrs: 127.0.0.1, action: allow },
+     { apply: in, ip_addrs: "::1", action: allow },
+     { apply: in, ip_addrs: 0/0, action: deny, methods: [ PURGE, PUSH, DELETE 
] },
+     { apply: in, ip_addrs: "::/0", action: deny, methods: [ PURGE, PUSH, 
DELTE ] }
+     ]
+
+.. note::
+
+   For ATS 9.0, this file is (almost) backwards compatible. If the first line 
is a single '#'
+   character, or contains only "# ats", then the file will be read in the 
version 8.0 format. This
+   is true for the default format and so if that has not been changed it 
should still work. This
+   allows a grace period before ATS 10.0 which will drop the old format 
entirely.
diff --git a/doc/admin-guide/files/records.config.en.rst 
b/doc/admin-guide/files/records.config.en.rst
index 65f27f0..622ca11 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -1801,7 +1801,7 @@ Security
    .. important::
 
        If you enable this option, then you must also specify
-       a filtering rule in the ip_allow.config file to allow only certain
+       a filtering rule in the ip_allow.yaml file to allow only certain
        machines to push content into the cache.
 
 .. ts:cv:: CONFIG proxy.config.http.max_post_size INT 0
diff --git a/doc/admin-guide/files/remap.config.en.rst 
b/doc/admin-guide/files/remap.config.en.rst
index 2956226..a4b2f83 100644
--- a/doc/admin-guide/files/remap.config.en.rst
+++ b/doc/admin-guide/files/remap.config.en.rst
@@ -416,7 +416,7 @@ Acl Filters
 ===========
 
 Acl filters can be created to control access of specific remap lines. The 
markup
-is very similar to that of :file:`ip_allow.config`, with slight changes to
+is very similar to that of :file:`ip_allow.yaml`, with slight changes to
 accommodate remap markup
 
 Examples
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index a0891df..2ec914f 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -796,7 +796,7 @@ static const RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.cache.control.filename", RECD_STRING, 
"cache.config", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.cache.ip_allow.filename", RECD_STRING, 
"ip_allow.config", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+  {RECT_CONFIG, "proxy.config.cache.ip_allow.filename", RECD_STRING, 
"ip_allow.yaml", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.cache.hosting_filename", RECD_STRING, 
"hosting.config", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
diff --git a/proxy/IPAllow.cc b/proxy/IPAllow.cc
index 8adadb7..1761a0c 100644
--- a/proxy/IPAllow.cc
+++ b/proxy/IPAllow.cc
@@ -27,10 +27,13 @@
 #include <sstream>
 #include "IPAllow.h"
 #include "tscore/BufferWriter.h"
+#include "tscore/ts_file.h"
+#include "tscore/ink_memory.h"
 
-extern char *readIntoBuffer(const char *file_path, const char *module_name, 
int *read_size_ptr);
+#include "yaml-cpp/yaml.h"
 
 using ts::TextView;
+
 namespace
 {
 void
@@ -42,8 +45,64 @@ SignalError(ts::BufferWriter &w, bool &flag)
   }
   Error("%s", w.data());
 }
+
+template <typename... Args>
+void
+ParseError(ts::TextView fmt, Args &&... args)
+{
+  ts::LocalBufferWriter<1024> w;
+  w.printv(fmt, std::forward_as_tuple(args...));
+  w.write('\0');
+  Warning("%s", w.data());
+}
+
 } // namespace
 
+namespace ts
+{
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, IpAllow const *obj)
+{
+  return w.print("{}[{}]", obj->MODULE_NAME, obj->get_config_file().c_str());
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, YAML::Mark const &mark)
+{
+  return w.print("Line {}", mark.line);
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, std::error_code const &ec)
+{
+  return w.print("[{}:{}]", ec.value(), ec.message());
+}
+
+} // namespace ts
+
+namespace YAML
+{
+template <> struct convert<ts::TextView> {
+  static Node
+  encode(ts::TextView const &tv)
+  {
+    Node zret;
+    zret = std::string(tv.data(), tv.size());
+    return zret;
+  }
+  static bool
+  decode(const Node &node, ts::TextView &tv)
+  {
+    if (!node.IsScalar()) {
+      return false;
+    }
+    tv.assign(node.Scalar());
+    return true;
+  }
+};
+
+} // namespace YAML
+
 enum AclOp {
   ACL_OP_ALLOW, ///< Allow access.
   ACL_OP_DENY,  ///< Deny access.
@@ -77,14 +136,14 @@ IpAllow::reconfigure()
 {
   self_type *new_table;
 
-  Note("ip_allow.config loading ...");
+  Note("ip_allow.yaml loading ...");
 
   new_table = new self_type("proxy.config.cache.ip_allow.filename");
   new_table->BuildTable();
 
   configid = configProcessor.set(configid, new_table);
 
-  Note("ip_allow.config finished loading");
+  Note("ip_allow.yaml finished loading");
 }
 
 IpAllow *
@@ -133,7 +192,7 @@ IpAllow::match(sockaddr const *ip, match_key_t key)
 //   End API functions
 //
 
-IpAllow::IpAllow(const char *config_var) : 
config_file_path(RecConfigReadConfigPath(config_var)) {}
+IpAllow::IpAllow(const char *config_var) : 
config_file(ats_scoped_str(RecConfigReadConfigPath(config_var)).get()) {}
 
 void
 IpAllow::PrintMap(IpMap *map)
@@ -194,27 +253,222 @@ IpAllow::Print()
 int
 IpAllow::BuildTable()
 {
-  int file_size = 0;
-  int line_num  = 0;
-  IpAddr addr1;
-  IpAddr addr2;
-  bool alarmAlready = false;
-  ts::LocalBufferWriter<1024> bw_err;
-
   // Table should be empty
   ink_assert(_src_map.count() == 0 && _dst_map.count() == 0);
 
-  file_buff = readIntoBuffer(config_file_path, "ip-allow", &file_size);
+  std::error_code ec;
+  std::string content{ts::file::load(config_file, ec)};
+  if (ec.value() == 0) {
+    // If it's a .yaml or the root tag is present, treat as YAML.
+    if (TextView{config_file.view()}.take_suffix_at('.') == "yaml" || 
std::string::npos != content.find(YAML_TAG_ROOT)) {
+      this->YAMLBuildTable(content);
+    } else {
+      this->ATSBuildTable(content);
+    }
 
-  if (file_buff == nullptr) {
-    Warning("%s Failed to read %s. All IP Addresses will be blocked", 
MODULE_NAME, config_file_path.get());
+    if (_src_map.count() == 0 && _dst_map.count() == 0) {
+      ParseError("{} - No entries found. All IP Addresses will be blocked", 
this);
+      return 1;
+    }
+    // convert the coloring from indices to pointers.
+    for (auto &item : _src_map) {
+      item.setData(&_src_acls[reinterpret_cast<size_t>(item.data())]);
+    }
+    for (auto &item : _dst_map) {
+      item.setData(&_dst_acls[reinterpret_cast<size_t>(item.data())]);
+    }
+    if (is_debug_tag_set("ip-allow")) {
+      Print();
+    }
+  } else {
+    ParseError("{} Failed to load {}. All IP Addresses will be blocked", this, 
ec);
     return 1;
   }
+  return 0;
+}
+
+bool
+IpAllow::YAMLLoadMethod(const YAML::Node &node, Record &rec)
+{
+  const std::string &value{node.Scalar()};
+
+  if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) {
+    rec._method_mask = ALL_METHOD_MASK;
+  } else {
+    int method_idx = hdrtoken_tokenize(value.data(), value.size());
+    if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT 
+ HTTP_WKSIDX_METHODS_CNT) {
+      rec._nonstandard_methods.push_back(value);
+      Debug("ip-allow", "Found nonstandard method '%s' at line %d", 
value.c_str(), node.Mark().line);
+    } else { // valid method.
+      rec._method_mask |= ACL::MethodIdxToMask(method_idx);
+    }
+  }
+  return true;
+}
 
-  TextView src(file_buff, file_size);
+bool
+IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, void *mark)
+{
+  if (node.IsScalar()) {
+    IpAddr min, max;
+    if (0 == ats_ip_range_parse(node.Scalar(), min, max)) {
+      map->fill(min, max, mark);
+      return true;
+    } else {
+      ParseError("{} {} - '{}' is not a valid range.", this, node.Mark(), 
node.Scalar());
+    }
+  }
+  return false;
+}
+
+bool
+IpAllow::YAMLLoadEntry(const YAML::Node &entry)
+{
+  AclOp op = ACL_OP_DENY; // "shut up", I explained to the compiler.
+  YAML::Node node;
+  IpAddr min, max;
+  std::string value;
+  Record rec;
+  std::vector<Record> *acls{nullptr};
+  IpMap *map = nullptr;
+
+  if (!entry.IsMap()) {
+    ParseError("{} {} - ACL items must be maps.", this, entry.Mark());
+    return false;
+  }
+
+  if (entry[YAML_TAG_APPLY]) {
+    auto apply_node{entry[YAML_TAG_APPLY]};
+    if (apply_node.IsScalar()) {
+      ts::TextView value{apply_node.Scalar()};
+      if (0 == strcasecmp(value, YAML_VALUE_APPLY_IN)) {
+        acls = &_src_acls;
+        map  = &_src_map;
+      } else if (0 == strcasecmp(value, YAML_VALUE_APPLY_OUT)) {
+        acls = &_dst_acls;
+        map  = &_dst_map;
+      } else {
+        ParseError(R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, 
entry.Mark(), YAML_VALUE_APPLY_IN,
+                   YAML_VALUE_APPLY_OUT);
+        return false;
+      }
+    } else {
+      ParseError(R"("{}" value at {} must be a scalar, "{}" or "{}")", 
YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
+                 YAML_VALUE_APPLY_OUT);
+      return false;
+    }
+  } else {
+    ParseError(R"("Object at {} must have a "{}" key.)", entry.Mark(), 
YAML_TAG_APPLY);
+    return false;
+  }
+
+  void *ipmap_mark = reinterpret_cast<void *>(acls->size());
+  if (entry[YAML_TAG_IP_ADDRS]) {
+    auto addr_node{entry[YAML_TAG_IP_ADDRS]};
+    if (addr_node.IsSequence()) {
+      for (auto const &n : addr_node) {
+        if (!this->YAMLLoadIPAddrRange(n, map, ipmap_mark)) {
+          return false;
+        }
+      }
+    } else if (!this->YAMLLoadIPAddrRange(addr_node, map, ipmap_mark)) {
+      return false;
+    }
+  }
+
+  if (!entry[YAML_TAG_ACTION]) {
+    ParseError("{} {} - item ignored, required '{}' key not found.", this, 
entry.Mark(), YAML_TAG_ACTION);
+    return false;
+  }
+
+  node = entry[YAML_TAG_ACTION];
+  if (!node.IsScalar()) {
+    ParseError("{} {} - item ignored, value for tag '{}' must be a string", 
this, node.Mark(), YAML_TAG_ACTION);
+    return false;
+  }
+  value = node.as<std::string>();
+  if (value == YAML_VALUE_ACTION_ALLOW) {
+    op = ACL_OP_ALLOW;
+  } else if (value == YAML_VALUE_ACTION_DENY) {
+    op = ACL_OP_DENY;
+  } else {
+    ParseError("{} {} - item ignored, value for tag '{}' must be '{}' or 
'{}'", this, node.Mark(), YAML_TAG_ACTION,
+               YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY);
+    return false;
+  }
+  if (!entry[YAML_TAG_METHODS]) {
+    rec._method_mask = ALL_METHOD_MASK;
+  } else {
+    node = entry[YAML_TAG_METHODS];
+    if (node.IsScalar()) {
+      this->YAMLLoadMethod(node, rec);
+    } else if (node.IsSequence()) {
+      for (auto const &elt : node) {
+        if (elt.IsScalar()) {
+          this->YAMLLoadMethod(elt, rec);
+          if (rec._method_mask == ALL_METHOD_MASK) {
+            break; // we're done here, nothing else matters.
+          }
+        } else {
+          ParseError("{} {} - item ignored, all values for '{}' must be 
strings.", this, elt.Mark(), YAML_TAG_METHODS);
+          return false;
+        }
+      }
+    } else {
+      ParseError("{} {} - item ignored, value for '{}' must be a single string 
or a list of strings.", this, node.Mark(),
+                 YAML_TAG_METHODS);
+    }
+  }
+  if (op == ACL_OP_DENY) {
+    rec._method_mask              = ALL_METHOD_MASK & ~rec._method_mask;
+    rec._deny_nonstandard_methods = true;
+  }
+  rec._src_line = entry.Mark().line;
+  // If we get here, everything parsed OK, add the record.
+  acls->emplace_back(std::move(rec));
+  return true;
+}
+
+int
+IpAllow::YAMLBuildTable(std::string const &content)
+{
+  YAML::Node root{YAML::Load(content)};
+  if (!root.IsMap()) {
+    ParseError("{} - top level object was not a map. All IP Addresses will be 
blocked", this);
+    return 1;
+  }
+
+  YAML::Node data{root[YAML_TAG_ROOT]};
+  if (!data) {
+    ParseError("{} - root tag '{}' not found. All IP Addresses will be 
blocked", this, YAML_TAG_ROOT);
+  } else if (data.IsSequence()) {
+    for (auto const &entry : data) {
+      if (!this->YAMLLoadEntry(entry)) {
+        return 1;
+      }
+    }
+  } else if (data.IsMap()) {
+    this->YAMLLoadEntry(data); // singleton, just load it.
+  } else {
+    ParseError("{} - root tag '{}' is not an map or sequence. All IP Addresses 
will be blocked", this, YAML_TAG_ROOT);
+    return 1;
+  }
+  return 0;
+}
+
+int
+IpAllow::ATSBuildTable(std::string const &content)
+{
+  int line_num = 0;
+  IpAddr addr1;
+  IpAddr addr2;
+  bool alarmAlready = false;
+  ts::LocalBufferWriter<1024> bw_err;
+
+  TextView src(content);
   TextView line;
   auto err_prefix = [&]() -> ts::BufferWriter & {
-    return bw_err.reset().print("{} discarding '{}' entry at line {} : ", 
MODULE_NAME, config_file_path, line_num);
+    return bw_err.reset().print("{} discarding '{}' entry at line {} : ", 
MODULE_NAME, config_file.c_str(), line_num);
   };
 
   while (!(line = src.take_prefix_at('\n')).empty()) {
@@ -277,7 +531,7 @@ IpAllow::BuildTable()
               } else {
                 int method_idx = hdrtoken_tokenize(method_name.data(), 
method_name.size());
                 if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= 
HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
-                  nonstandard_methods.push_back(method_name);
+                  
nonstandard_methods.emplace_back(std::string(method_name.data(), 
method_name.size()));
                   Debug("ip-allow", "%s",
                         bw_err.reset().print("Found nonstandard method '{}' on 
line {}\0", method_name, line_num).data());
                 } else { // valid method.
@@ -329,21 +583,5 @@ IpAllow::BuildTable()
       }
     }
   }
-
-  if (_src_map.count() == 0 && _dst_map.count() == 0) {
-    Warning("%s No entries in %s. All IP Addresses will be blocked", 
MODULE_NAME, config_file_path.get());
-  } else {
-    // convert the coloring from indices to pointers.
-    for (auto &item : _src_map) {
-      item.setData(&_src_acls[reinterpret_cast<size_t>(item.data())]);
-    }
-    for (auto &item : _dst_map) {
-      item.setData(&_dst_acls[reinterpret_cast<size_t>(item.data())]);
-    }
-  }
-
-  if (is_debug_tag_set("ip-allow")) {
-    Print();
-  }
   return 0;
 }
diff --git a/proxy/IPAllow.h b/proxy/IPAllow.h
index f36edab..a7dbb88 100644
--- a/proxy/IPAllow.h
+++ b/proxy/IPAllow.h
@@ -32,17 +32,20 @@
 
 #include <string>
 #include <string_view>
-#include <set>
 #include <vector>
-#include <atomic>
 
 #include "hdrs/HTTP.h"
 #include "ProxyConfig.h"
 #include "tscore/IpMap.h"
 #include "tscpp/util/TextView.h"
+#include "tscore/ts_file.h"
 
 // forward declare in name only so it can be a friend.
 struct IpAllowUpdate;
+namespace YAML
+{
+class Node;
+}
 
 /** Singleton class for access controls.
  */
@@ -50,10 +53,7 @@ class IpAllow : public ConfigInfo
 {
   friend struct IpAllowUpdate;
 
-  // These point in to the bulk loaded configuration file, which therefore 
needs to be kept around
-  // until the configuration is destructed. The number is expected to be small 
enough a vector is the
-  // best container.
-  using MethodNames = std::vector<std::string_view>;
+  using MethodNames = std::vector<std::string>;
 
   static constexpr uint32_t ALL_METHOD_MASK = ~0; // Mask for all methods.
 
@@ -63,7 +63,8 @@ class IpAllow : public ConfigInfo
   struct Record {
     /// Default constructor.
     /// Present only to make Vec<> happy, do not use.
-    Record() = default;
+    Record()              = default;
+    Record(Record &&that) = default;
     explicit Record(uint32_t method_mask);
     Record(uint32_t method_mask, int line, MethodNames &&nonstandard_methods, 
bool deny_nonstandard_methods);
 
@@ -88,6 +89,19 @@ public:
   static constexpr ts::TextView OPT_METHOD{"method"};
   static constexpr ts::TextView OPT_METHOD_ALL{"all"};
 
+  static constexpr ts::TextView YAML_TAG_ROOT{"ip_addr_acl"};
+  static constexpr ts::TextView YAML_TAG_IP_ADDRS{"ip_addrs"};
+  static constexpr ts::TextView YAML_TAG_APPLY{"apply"};
+  static constexpr ts::TextView YAML_VALUE_APPLY_IN{"in"};
+  static constexpr ts::TextView YAML_VALUE_APPLY_OUT{"out"};
+  static constexpr ts::TextView YAML_TAG_ACTION{"action"};
+  static constexpr ts::TextView YAML_VALUE_ACTION_ALLOW{"allow"};
+  static constexpr ts::TextView YAML_VALUE_ACTION_DENY{"deny"};
+  static constexpr ts::TextView YAML_TAG_METHODS{"methods"};
+  static constexpr ts::TextView YAML_VALUE_METHODS_ALL{"all"};
+
+  static constexpr const char *MODULE_NAME = "IPAllow";
+
   /** An access control record and support data.
    * The primary point of this is to hold the backing configuration in memory 
while the ACL
    * is in use.
@@ -166,21 +180,22 @@ public:
    */
   static bool isAcceptCheckEnabled();
 
+  const ts::file::path &get_config_file() const;
+
 private:
   static size_t configid;               ///< Configuration ID for update 
management.
   static const Record ALLOW_ALL_RECORD; ///< Static record that allows all 
access.
   static bool accept_check_p;           ///< @c true if deny all can be 
enforced during accept.
 
-  static constexpr const char *MODULE_NAME = "IPAllow";
-
   void PrintMap(IpMap *map);
   int BuildTable();
+  int ATSBuildTable(const std::string &);
+  int YAMLBuildTable(const std::string &);
+  bool YAMLLoadEntry(const YAML::Node &);
+  bool YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, void *mark);
+  bool YAMLLoadMethod(const YAML::Node &node, Record &rec);
 
-  ats_scoped_str config_file_path; ///< Path to configuration file.
-  /// The file contents so records can point in to this instead of separately 
allocating.
-  ats_scoped_str file_buff;
-  //  const char *module_name{nullptr};
-  //  const char *action{nullptr};
+  ts::file::path config_file; ///< Path to configuration file.
   IpMap _src_map;
   IpMap _dst_map;
   std::vector<Record> _src_acls;
@@ -315,3 +330,9 @@ IpAllow::makeAllowAllACL() -> ACL
 {
   return {&ALLOW_ALL_RECORD, nullptr};
 }
+
+inline const ts::file::path &
+IpAllow::get_config_file() const
+{
+  return config_file;
+}
diff --git a/proxy/Makefile.am b/proxy/Makefile.am
index 5f8d3b1..bac2ea4 100644
--- a/proxy/Makefile.am
+++ b/proxy/Makefile.am
@@ -37,6 +37,7 @@ AM_CPPFLAGS += \
        -I$(abs_srcdir)/hdrs \
        -I$(abs_top_srcdir)/mgmt \
        -I$(abs_top_srcdir)/mgmt/utils \
+       -I$(abs_top_srcdir)/lib/yamlcpp/include \
        $(TS_INCLUDES)
 
 noinst_HEADERS = \
diff --git a/src/traffic_manager/AddConfigFilesHere.cc 
b/src/traffic_manager/AddConfigFilesHere.cc
index 30be5a6..859bb70 100644
--- a/src/traffic_manager/AddConfigFilesHere.cc
+++ b/src/traffic_manager/AddConfigFilesHere.cc
@@ -76,7 +76,7 @@ initializeRegistry()
   registerFile("proxy.config.socks.socks_config_file", "socks.config");
   registerFile("records.config", "records.config");
   registerFile("proxy.config.cache.control.filename", "cache.config");
-  registerFile("proxy.config.cache.ip_allow.filename", "ip_allow.config");
+  registerFile("proxy.config.cache.ip_allow.filename", "ip_allow.yaml");
   registerFile("proxy.config.http.parent_proxy.file", "parent.config");
   registerFile("proxy.config.url_remap.filename", "remap.config");
   registerFile("", "volume.config");
diff --git a/tests/gold_tests/autest-site/min_cfg/ip_allow.config 
b/tests/gold_tests/autest-site/min_cfg/ip_allow.config
deleted file mode 100644
index 061bbe5..0000000
--- a/tests/gold_tests/autest-site/min_cfg/ip_allow.config
+++ /dev/null
@@ -1,4 +0,0 @@
-src_ip=127.0.0.1 action=ip_allow method=ALL
-src_ip=::1 action=ip_allow method=ALL
-src_ip=0.0.0.0-255.255.255.255 action=ip_deny method=PUSH|PURGE|DELETE
-src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny 
method=PUSH|PURGE|DELETE
\ No newline at end of file
diff --git a/tests/gold_tests/autest-site/min_cfg/ip_allow.yaml 
b/tests/gold_tests/autest-site/min_cfg/ip_allow.yaml
new file mode 100644
index 0000000..9f80a0b
--- /dev/null
+++ b/tests/gold_tests/autest-site/min_cfg/ip_allow.yaml
@@ -0,0 +1,43 @@
+# YAML
+
+#  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.
+
+# Allow anything on localhost, limit destructive methods elsewhere.
+ip_addr_acl:
+  - apply: in
+    ip_addrs: 127.0.0.1
+    action: allow
+    methods: ALL
+  - apply: out
+    ip_addrs: [ 10.0.0.0/8, 192.168.1.0/24 ]
+    action: allow
+    methods: [GET, HEAD, POST ]
+  - apply: in
+    ip_addrs: ::1
+    action: allow
+    methods: ALL
+  - apply: in
+    ip_addrs: 0/0
+    action: deny
+    methods:
+      - PURGE
+      - PUSH
+      - DELETE
+  - apply: in
+    ip_addrs: ::/0
+    action: deny
+    methods:
+      - PURGE
+      - PUSH
+      - DELETE
diff --git a/tests/gold_tests/autest-site/trafficserver.test.ext 
b/tests/gold_tests/autest-site/trafficserver.test.ext
index 9f2a4bb..0ca41eb 100755
--- a/tests/gold_tests/autest-site/trafficserver.test.ext
+++ b/tests/gold_tests/autest-site/trafficserver.test.ext
@@ -214,7 +214,7 @@ def MakeATSProcess(obj, name, command='traffic_server', 
select_ports=True, enabl
     tmpname = os.path.join(config_dir, fname)
     p.Disk.File(tmpname, id=make_id(fname), typename="ats:config")
 
-    fname = "ip_allow.config"
+    fname = "ip_allow.yaml"
     tmpname = os.path.join(config_dir, fname)
     p.Disk.File(tmpname, id=make_id(fname), typename="ats:config")
 

Reply via email to