trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a..@apache.org
Subject [trafficserver] branch master updated: YAML: Replace ip_allow.config with ip_allow.yaml.
Date Mon, 05 Aug 2019 13:59:09 GMT
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 <amc@apache.org>
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")
 


Mime
View raw message