From 0c6d2b99f226216b3f7781e329a7fa6dd5551c3d Mon Sep 17 00:00:00 2001 From: Michael Hoppal <54542891+mhoppa@users.noreply.github.com> Date: Fri, 17 Jan 2020 08:44:28 -0700 Subject: [PATCH] Fix ip_allow config generation for mids to include rascal servers (#4296) * Fix ip_allow config generation to include rascal servers * Fix ip_allow generation in atstccfg * Add API tests for ip_allow * Make tests more robust (cherry picked from commit 6b11bd9cad471c549ff8c22a0ed068e56f91f5b9) --- lib/go-util/net.go | 48 +++++++ lib/go-util/net_test.go | 49 +++++++ .../ort/atstccfg/cfgfile/ipallowdotconfig.go | 7 +- .../api/v14/ip_allow_dot_config_test.go | 125 ++++++++++++++++++ traffic_ops/testing/api/v14/tc-fixtures.json | 4 +- .../ats/atsserver/ipallowdotconfig.go | 4 +- 6 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 traffic_ops/testing/api/v14/ip_allow_dot_config_test.go diff --git a/lib/go-util/net.go b/lib/go-util/net.go index 50af321c6f..d240eb0394 100644 --- a/lib/go-util/net.go +++ b/lib/go-util/net.go @@ -21,7 +21,10 @@ package util import ( "bytes" + "errors" "net" + "strconv" + "strings" "github.com/apache/trafficcontrol/lib/go-log" ) @@ -200,3 +203,48 @@ func IPToCIDR(ip net.IP) *net.IPNet { } return &net.IPNet{IP: ip, Mask: fullMask} } + +func IP4ToNum(ip string) (uint32, error) { + parts := strings.Split(ip, `.`) + if len(parts) != 4 { + return 0, errors.New("malformed IPv4") + } + intParts := []uint32{} + for _, part := range parts { + i, err := strconv.ParseUint(part, 10, 32) + if err != nil { + return 0, errors.New("malformed IPv4") + } + intParts = append(intParts, uint32(i)) + } + + num := intParts[3] + num += intParts[2] << 8 + num += intParts[1] << 16 + num += intParts[0] << 24 + + return num, nil +} + +func IP4InRange(ip, ipRange string) (bool, error) { + ab := strings.Split(ipRange, `-`) + if len(ab) != 2 { + if len(ab) == 1 { // no range check for equality + return ip == ipRange, nil + } + return false, errors.New("malformed range") + } + ipNum, err := IP4ToNum(ip) + if err != nil { + return false, errors.New("malformed ip") + } + aNum, err := IP4ToNum(ab[0]) + if err != nil { + return false, errors.New("malformed range (first part)") + } + bNum, err := IP4ToNum(ab[1]) + if err != nil { + return false, errors.New("malformed range (second part)") + } + return ipNum >= aNum && ipNum <= bNum, nil +} diff --git a/lib/go-util/net_test.go b/lib/go-util/net_test.go index ce0db4e3aa..0ffaf59974 100644 --- a/lib/go-util/net_test.go +++ b/lib/go-util/net_test.go @@ -17,6 +17,7 @@ package util // When adding symbols, document the RFC and section they correspond to. import ( + "fmt" "net" "testing" ) @@ -190,3 +191,51 @@ func TestLastIP(t *testing.T) { } } } + +func TestIP4ToNum(t *testing.T) { + var tests = []struct { + ip string + number uint32 + }{ + {"127.0.0.1", uint32(2130706433)}, + {"127.0.0.4", uint32(2130706436)}, + {"127.255.255.255", uint32(2147483647)}, + } + for _, tt := range tests { + t.Run(tt.ip, func(t *testing.T) { + n, err := IP4ToNum(tt.ip) + if err != nil { + t.Errorf("unexpected error: %v", err) + + } + if n != tt.number { + t.Errorf("got %v, want %v", n, tt.number) + } + }) + } +} + +func TestIP4InRange(t *testing.T) { + var tests = []struct { + ip string + ipRange string + inRange bool + }{ + {"111.0.0.1", "127.0.0.0-127.255.255.255", false}, + {"128.0.0.1", "127.0.0.0-127.255.255.255", false}, + {"127.0.0.1", "127.0.0.0-127.255.255.255", true}, + {"127.0.0.1", "127.0.0.1", true}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%v in range %v", tt.ip, tt.inRange), func(t *testing.T) { + exists, err := IP4InRange(tt.ip, tt.ipRange) + if err != nil { + t.Errorf("unexpected error: %v", err) + + } + if exists != tt.inRange { + t.Errorf("got %v, want %v", exists, tt.inRange) + } + }) + } +} diff --git a/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go b/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go index dd09618d2c..119d06b982 100644 --- a/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go +++ b/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go @@ -22,6 +22,7 @@ package cfgfile import ( "errors" "strconv" + "strings" "github.com/apache/trafficcontrol/lib/go-atscfg" "github.com/apache/trafficcontrol/lib/go-tc" @@ -110,10 +111,10 @@ func GetConfigFileServerIPAllowDotConfig(cfg config.TCCfg, serverNameOrID string childServers := map[tc.CacheName]atscfg.IPAllowServer{} for _, sv := range servers { - if _, ok := childCGs[sv.Cachegroup]; !ok { - continue + _, ok := childCGs[sv.Cachegroup] + if ok || (strings.HasPrefix(string(serverType), tc.MidTypePrefix) && string(sv.Type) == tc.MonitorTypeName) { + childServers[tc.CacheName(sv.HostName)] = atscfg.IPAllowServer{IPAddress: sv.IPAddress, IP6Address: sv.IP6Address} } - childServers[tc.CacheName(sv.HostName)] = atscfg.IPAllowServer{IPAddress: sv.IPAddress, IP6Address: sv.IP6Address} } txt := atscfg.MakeIPAllowDotConfig(serverName, serverType, toToolName, toURL, fileParams, childServers) diff --git a/traffic_ops/testing/api/v14/ip_allow_dot_config_test.go b/traffic_ops/testing/api/v14/ip_allow_dot_config_test.go new file mode 100644 index 0000000000..1f8f981cb0 --- /dev/null +++ b/traffic_ops/testing/api/v14/ip_allow_dot_config_test.go @@ -0,0 +1,125 @@ +package v14 + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + "strings" + "testing" + + "github.com/apache/trafficcontrol/lib/go-util" + + "github.com/apache/trafficcontrol/lib/go-tc" +) + +const ipAllow = "ip_allow.config" + +var ( + expectedRules = []string{ + "src_ip=127.0.0.1 action=ip_allow method=ALL\n", + "src_ip=::1 action=ip_allow method=ALL\n", + } + midExpectedRules = []string{ + "src_ip=10.0.0.0-10.255.255.255 action=ip_allow method=ALL\n", + "src_ip=172.16.0.0-172.31.255.255 action=ip_allow method=ALL\n", + "src_ip=192.168.0.0-192.168.255.255 action=ip_allow method=ALL\n", + "src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny method=ALL\n", + "src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny method=ALL\n", + } + edgeExpectedRules = []string{ + "src_ip=0.0.0.0-255.255.255.255 action=ip_deny method=PUSH|PURGE|DELETE\n", + "src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny method=PUSH|PURGE|DELETE\n", + } + rascalServerIP = "" + rascalRule = "src_ip=%v action=ip_allow method=ALL" +) + +func TestIPAllowDotConfig(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() { + rascalServerIP = getServer(t, "RASCAL").IPAddress + // rascalRule = fmt.Sprintf("src_ip=%v action=ip_allow method=ALL", rascalServer.IPAddress) + GetTestIPAllowDotConfig(t) + GetTestIPAllowMidDotConfig(t) + }) +} + +func GetTestIPAllowDotConfig(t *testing.T) { + // Get edge server + s := getServer(t, "EDGE") + output, _, err := TOSession.GetATSServerConfig(s.ID, ipAllow) + if err != nil { + t.Fatalf("cannot GET server %v config %v: %v", s.HostName, ipAllow, err) + } + for _, r := range append(expectedRules, edgeExpectedRules...) { + if !strings.Contains(output, r) { + t.Errorf("expected rule %v not found in ip_allow config", r) + } + } + // Make sure edge does not contain rule for rascal server + exists, ipRange := getIPRule(output, rascalServerIP) + rascalRule := fmt.Sprintf(rascalRule, ipRange) + if exists && strings.Contains(output, rascalRule) { + t.Errorf("rascal IP was not supposed to be in an allowed rule: %v", rascalRule) + } +} + +func GetTestIPAllowMidDotConfig(t *testing.T) { + // Get mid server + s := getServer(t, "MID") + output, _, err := TOSession.GetATSServerConfig(s.ID, ipAllow) + if err != nil { + t.Errorf("cannot GET server %v config %v: %v", s.HostName, ipAllow, err) + } + for _, r := range append(expectedRules, midExpectedRules...) { + if !strings.Contains(output, r) { + t.Errorf("expected rule %v not found in ip_allow config", r) + } + } + + // Make sure mid contains an allowed rule that includes the rascal server + exists, ipRange := getIPRule(output, rascalServerIP) + rascalRule := fmt.Sprintf(rascalRule, ipRange) + if !(exists && strings.Contains(output, rascalRule)) { + t.Errorf("expected rascal to be include as allowed in mid ip allow config") + } +} + +func getServer(t *testing.T, serverType string) tc.Server { + v := url.Values{} + v.Add("type", serverType) + servers, _, err := TOSession.GetServersByType(v) + if err != nil { + t.Fatalf("cannot GET Server by type %v: %v", serverType, err) + } + if len(servers) == 0 { + t.Fatalf("cannot find any Servers by type %v", serverType) + } + return servers[0] +} + +// getIPRuleRange returns if the given IP is included in the set of rules and which ip range it is included in +func getIPRule(rules, ip string) (bool, string) { + for _, r := range strings.Split(rules, "\n")[1:] { + if !strings.Contains(r, "src_ip") { + continue + } + ipRange := r[7:strings.IndexAny(r, " ")] + if exists, _ := util.IP4InRange(ip, ipRange); exists { + return true, ipRange + } + } + return false, "" +} diff --git a/traffic_ops/testing/api/v14/tc-fixtures.json b/traffic_ops/testing/api/v14/tc-fixtures.json index 1944193222..25e4086c36 100644 --- a/traffic_ops/testing/api/v14/tc-fixtures.json +++ b/traffic_ops/testing/api/v14/tc-fixtures.json @@ -1828,7 +1828,7 @@ "routerPortName": "", "status": "REPORTED", "tcpPort": 81, - "type": "TRAFFIC_MONITOR", + "type": "RASCAL", "updPending": false, "xmppId": "", "xmppPasswd": "X" @@ -2301,7 +2301,7 @@ { "description": "Traffic Monitor (Rascal)", "lastUpdated": "2018-03-02T19:13:46.832327+00:00", - "name": "TRAFFIC_MONITOR", + "name": "RASCAL", "useInTable": "server" } ], diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go b/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go index cdcd3d0c5e..520200b135 100644 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go +++ b/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go @@ -86,7 +86,7 @@ FROM JOIN type tp on tp.id = s.type JOIN cachegroup cg on cg.id = s.cachegroup WHERE - (tp.name = '` + tc.MonitorTypeName + `' OR tp.name LIKE '` + tc.EdgeTypePrefix + `%') + (tp.name = '` + tc.MonitorTypeName + `' OR ( tp.name LIKE '` + tc.EdgeTypePrefix + `%') AND cg.id IN ( SELECT cg2.id @@ -94,7 +94,7 @@ WHERE server s2 JOIN cachegroup cg2 ON (cg2.parent_cachegroup_id = s2.cachegroup OR cg2.secondary_parent_cachegroup_id = s2.cachegroup) WHERE - s2.host_name = $1 + s2.host_name = $1 ) ) ` rows, err := tx.Query(qry, serverName)