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")