blob: 5a419a85ed69155a0fa6107a82512d3369673e57 [file] [log] [blame]
package atscfg
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"net"
"sort"
"strconv"
"strings"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
)
const IPAllowConfigFileName = `ip_allow.config`
const ContentTypeIPAllowDotConfig = ContentTypeTextASCII
const LineCommentIPAllowDotConfig = LineCommentHash
const ParamPurgeAllowIP = "purge_allow_ip"
const ParamCoalesceMaskLenV4 = "coalesce_masklen_v4"
const ParamCoalesceNumberV4 = "coalesce_number_v4"
const ParamCoalesceMaskLenV6 = "coalesce_masklen_v6"
const ParamCoalesceNumberV6 = "coalesce_number_v6"
const DefaultCoalesceMaskLenV4 = 24
const DefaultCoalesceNumberV4 = 5
const DefaultCoalesceMaskLenV6 = 48
const DefaultCoalesceNumberV6 = 5
// IPAllowDotConfigOpts contains settings to configure generation options.
type IPAllowDotConfigOpts struct {
// HdrComment is the header comment to include at the beginning of the file.
// This should be the text desired, without comment syntax (like # or //). The file's comment syntax will be added.
// To omit the header comment, pass the empty string.
HdrComment string
}
// MakeIPAllowDotConfig creates the ip_allow.config ATS config file.
// The childServers is a list of servers which are children for this Mid-tier server. This should be empty for Edge servers.
// More specifically, it should be the list of edges whose cachegroup's parent_cachegroup or secondary_parent_cachegroup is the cachegroup of this Mid server.
func MakeIPAllowDotConfig(
serverParams []tc.Parameter,
server *Server,
servers []Server,
cacheGroups []tc.CacheGroupNullable,
topologies []tc.Topology,
opt *IPAllowDotConfigOpts,
) (Cfg, error) {
if opt == nil {
opt = &IPAllowDotConfigOpts{}
}
warnings := []string{}
if server.Cachegroup == nil {
return Cfg{}, makeErr(warnings, "this server missing Cachegroup")
}
if server.HostName == nil {
return Cfg{}, makeErr(warnings, "this server missing HostName")
}
params := paramsToMultiMap(filterParams(serverParams, IPAllowConfigFileName, "", "", ""))
ipAllowDat := []ipAllowData{}
// default for coalesce_ipv4 = 24, 5 and for ipv6 48, 5; override with the parameters in the server profile.
coalesceMaskLenV4 := DefaultCoalesceMaskLenV4
coalesceNumberV4 := DefaultCoalesceNumberV4
coalesceMaskLenV6 := DefaultCoalesceMaskLenV6
coalesceNumberV6 := DefaultCoalesceNumberV6
for name, vals := range params {
for _, val := range vals {
switch name {
case ParamPurgeAllowIP:
ipAllowDat = append(ipAllowDat, allowAll(val))
case ParamCoalesceMaskLenV4:
if vi, err := strconv.Atoi(val); err != nil {
warnings = append(warnings, "got param '"+name+"' val '"+val+"' not a number, ignoring!")
} else if coalesceMaskLenV4 != DefaultCoalesceMaskLenV4 {
warnings = append(warnings, "got multiple param '"+name+"' - ignoring val '"+val+"'!")
} else {
coalesceMaskLenV4 = vi
}
case ParamCoalesceNumberV4:
if vi, err := strconv.Atoi(val); err != nil {
warnings = append(warnings, "got param '"+name+"' val '"+val+"' not a number, ignoring!")
} else if coalesceNumberV4 != DefaultCoalesceNumberV4 {
warnings = append(warnings, "got multiple param '"+name+"' - ignoring val '"+val+"'!")
} else {
coalesceNumberV4 = vi
}
case ParamCoalesceMaskLenV6:
if vi, err := strconv.Atoi(val); err != nil {
warnings = append(warnings, "got param '"+name+"' val '"+val+"' not a number, ignoring!")
} else if coalesceMaskLenV6 != DefaultCoalesceMaskLenV6 {
warnings = append(warnings, "got multiple param '"+name+"' - ignoring val '"+val+"'!")
} else {
coalesceMaskLenV6 = vi
}
case ParamCoalesceNumberV6:
if vi, err := strconv.Atoi(val); err != nil {
warnings = append(warnings, "got param '"+name+"' val '"+val+"' not a number, ignoring!")
} else if coalesceNumberV6 != DefaultCoalesceNumberV6 {
warnings = append(warnings, "got multiple param '"+name+"' - ignoring val '"+val+"'!")
} else {
coalesceNumberV6 = vi
}
}
}
}
// for edges deny "PUSH|PURGE|DELETE", allow everything else to everyone.
isMid := strings.HasPrefix(server.Type, tc.MidTypePrefix)
if !isMid {
ipAllowDat = append([]ipAllowData{allowAll(`127.0.0.1`)}, ipAllowDat...)
ipAllowDat = append([]ipAllowData{allowAll(`::1`)}, ipAllowDat...)
ipAllowDat = append(ipAllowDat, allowAllButPushPurgeDelete(`0.0.0.0-255.255.255.255`))
ipAllowDat = append(ipAllowDat, allowAllButPushPurgeDelete(`::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff`))
} else {
ips := []*net.IPNet{}
ip6s := []*net.IPNet{}
cgMap := map[string]tc.CacheGroupNullable{}
for _, cg := range cacheGroups {
if cg.Name == nil {
return Cfg{}, makeErr(warnings, "got cachegroup with nil name!")
}
cgMap[*cg.Name] = cg
}
if server.Cachegroup == nil {
return Cfg{}, makeErr(warnings, "server had nil Cachegroup!")
}
serverCG, ok := cgMap[*server.Cachegroup]
if !ok {
return Cfg{}, makeErr(warnings, "server cachegroup not in cachegroups!")
}
childCGNames := getTopologyDirectChildren(tc.CacheGroupName(*server.Cachegroup), topologies)
childCGs := map[string]tc.CacheGroupNullable{}
for cgName, _ := range childCGNames {
childCGs[string(cgName)] = cgMap[string(cgName)]
}
for cgName, cg := range cgMap {
if (cg.ParentName != nil && *cg.ParentName == *serverCG.Name) || (cg.SecondaryParentName != nil && *cg.SecondaryParentName == *serverCG.Name) {
childCGs[cgName] = cg
}
}
// sort servers, to guarantee things like IP coalescing are deterministic
sort.Sort(serversSortByName(servers))
for _, childServer := range servers {
if childServer.Cachegroup == nil {
warnings = append(warnings, "Servers had server with nil Cachegroup, skipping!")
continue
} else if childServer.HostName == nil {
warnings = append(warnings, "Servers had server with nil HostName, skipping!")
continue
}
// We need to add IPs to the allow of
// - all children of this server
// - all monitors, if this server is a Mid
//
_, isChild := childCGs[*childServer.Cachegroup]
if !isChild && !strings.HasPrefix(server.Type, tc.MidTypePrefix) && string(childServer.Type) != tc.MonitorTypeName {
continue
}
for _, svInterface := range childServer.Interfaces {
for _, svAddr := range svInterface.IPAddresses {
if ip := net.ParseIP(svAddr.Address); ip != nil {
// got an IP - convert it to a CIDR and add it to the list
if ip4 := ip.To4(); ip4 != nil {
ips = append(ips, util.IPToCIDR(ip4))
} else {
ip6s = append(ip6s, util.IPToCIDR(ip))
}
} else {
// not an IP, try a CIDR
if ip, cidr, err := net.ParseCIDR(svAddr.Address); err != nil {
// not a CIDR or IP - error out
warnings = append(warnings, "server '"+*server.HostName+"' IP '"+svAddr.Address+" is not an IP address or CIDR - skipping!")
} else if ip == nil {
// not a CIDR or IP - error out
warnings = append(warnings, "server '"+*server.HostName+"' IP '"+svAddr.Address+" failed to parse as IP or CIDR - skipping!")
} else {
// got a valid CIDR - add it to the list
if ip4 := ip.To4(); ip4 != nil {
ips = append(ips, cidr)
} else {
ip6s = append(ip6s, cidr)
}
}
}
}
}
}
cidrs := util.CoalesceCIDRs(ips, coalesceNumberV4, coalesceMaskLenV4)
cidr6s := util.CoalesceCIDRs(ip6s, coalesceNumberV6, coalesceMaskLenV6)
for _, cidr := range cidrs {
ipAllowDat = append(ipAllowDat, allowAllButPushPurge(util.RangeStr(cidr)))
}
for _, cidr := range cidr6s {
ipAllowDat = append(ipAllowDat, allowAllButPushPurge(util.RangeStr(cidr)))
}
// allow RFC 1918 server space - TODO JvD: parameterize
ipAllowDat = append(ipAllowDat, allowAllButPushPurge(`10.0.0.0-10.255.255.255`))
ipAllowDat = append(ipAllowDat, allowAllButPushPurge(`172.16.0.0-172.31.255.255`))
ipAllowDat = append(ipAllowDat, allowAllButPushPurge(`192.168.0.0-192.168.255.255`))
// order matters, so sort before adding the denys
sort.Sort(ipAllowDatas(ipAllowDat))
// start by allowing everything to localhost, including PURGE and PUSH
ipAllowDat = append([]ipAllowData{allowAll(`127.0.0.1`)}, ipAllowDat...)
ipAllowDat = append([]ipAllowData{allowAll(`::1`)}, ipAllowDat...)
// end with a deny
ipAllowDat = append(ipAllowDat, denyAll(`0.0.0.0-255.255.255.255`))
ipAllowDat = append(ipAllowDat, denyAll(`::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff`))
}
text := makeHdrComment(opt.HdrComment)
for _, al := range ipAllowDat {
text += `src_ip=` + al.Src + ` action=` + al.Action + ` method=` + al.Method + "\n"
}
return Cfg{
Text: text,
ContentType: ContentTypeHostingDotConfig,
LineComment: LineCommentHostingDotConfig,
Warnings: warnings,
}, nil
}
type ipAllowData struct {
Src string
Action string
Method string
}
type ipAllowDatas []ipAllowData
func (is ipAllowDatas) Len() int { return len(is) }
func (is ipAllowDatas) Swap(i, j int) { is[i], is[j] = is[j], is[i] }
func (is ipAllowDatas) Less(i, j int) bool {
if is[i].Src != is[j].Src {
return is[i].Src < is[j].Src
}
if is[i].Action != is[j].Action {
return is[i].Action < is[j].Action
}
return is[i].Method < is[j].Method
}
type ipAllowServer struct {
IPAddress string
IP6Address string
}
type serversSortByName []Server
func (ss serversSortByName) Len() int { return len(ss) }
func (ss serversSortByName) Swap(i, j int) { ss[i], ss[j] = ss[j], ss[i] }
func (ss serversSortByName) Less(i, j int) bool {
if ss[j].HostName == nil {
return false
} else if ss[i].HostName == nil {
return true
}
return *ss[i].HostName < *ss[j].HostName
}
const ActionAllow = "ip_allow"
const ActionDeny = "ip_deny"
const MethodAll = "ALL"
const MethodPush = "PUSH"
const MethodPurge = "PURGE"
const MethodDelete = "DELETE"
const MethodSeparator = `|`
// allowAllButPushPurge is a helper func to build a ipAllowData for the given range string immediately allowing all Methods except Push and Purge.
func allowAllButPushPurge(rangeStr string) ipAllowData {
// Note denying methods implicitly and immediately allows all other methods!
// So Deny PUSH|PURGE will make all other methods
// immediately allowed, regardless of any later deny rules!
methodPushPurge := strings.Join([]string{MethodPush, MethodPurge}, MethodSeparator)
return ipAllowData{
Src: rangeStr,
Action: ActionDeny,
Method: methodPushPurge,
}
}
// allowAllButPushPurgeDelete is a helper func to build a ipAllowData for the given range string immediately allowing all Methods except PUSH, PURGE, and DELETE.
func allowAllButPushPurgeDelete(rangeStr string) ipAllowData {
// Note denying methods implicitly and immediately allows all other methods!
// So Deny PUSH|PURGE will make all other methods
// immediately allowed, regardless of any later deny rules!
methodPushPurgeDelete := strings.Join([]string{MethodPush, MethodPurge, MethodDelete}, MethodSeparator)
return ipAllowData{
Src: rangeStr,
Action: ActionDeny,
Method: methodPushPurgeDelete,
}
}
// allowAll is a helper func to build a ipAllowData for the given range string immediately allowing all Methods, including Push and Purge.
func allowAll(rangeStr string) ipAllowData {
return ipAllowData{
Src: rangeStr,
Action: ActionAllow,
Method: MethodAll,
}
}
// denyAll is a helper func to build a ipAllowData for the given range string immediately denying all Methods.
func denyAll(rangeStr string) ipAllowData {
return ipAllowData{
Src: rangeStr,
Action: ActionDeny,
Method: MethodAll,
}
}