Commit 04e83f7f authored by Geoff Simmons's avatar Geoff Simmons

Add ACL support.

parent 73c8e0ed
......@@ -88,6 +88,65 @@ spec:
host-match:
type: string
minLength: 1
acl:
type: array
minItems: 1
items:
type: object
required:
- name
- addrs
properties:
name:
type: string
minLength: 1
addrs:
type: array
minItems: 1
items:
type: object
required:
- addr
properties:
addr:
type: string
pattern: '^[^"]+$'
mask-bits:
type: integer
minimum: 0
maximum: 128
negate:
type: boolean
type:
enum:
- whitelist
- blacklist
type: string
fail-status:
type: integer
minimum: 100
maximum: 599
comparand:
type: string
pattern: "^((client|server|local|remote)\\.ip|xff-(first|2ndlast)|req\\.http\\.[a-zA-Z0-9!#$%&'*+-.^_`|~]+)$"
conditions:
type: array
minItems: 1
items:
type: object
required:
- comparand
- regex
- match
properties:
comparand:
type: string
pattern: "^req\\.(url|http\\.[a-zA-Z0-9!#$%&'*+-.^_`|~]+)$"
regex:
type: string
minLength: 1
match:
type: boolean
status:
acceptedNames:
kind: VarnishConfig
......
......@@ -52,6 +52,7 @@ type VarnishConfigSpec struct {
Services []string `json:"services,omitempty"`
SelfSharding *SelfShardSpec `json:"self-sharding,omitempty"`
Auth []AuthSpec `json:"auth,omitempty"`
ACLs []ACLSpec `json:"acl,omitempty"`
}
// SelfShardSpec specifies self-sharding in a Varnish cluster.
......@@ -98,6 +99,46 @@ type AuthCondition struct {
HostRegex string `json:"host-match,omitempty"`
}
// ACLSpec specifies whitelisting or blacklisting IP addresses against
// an access control list.
type ACLSpec struct {
Name string `json:"name,omitempty"`
ACLType ACLType `json:"type,omitempty"`
Comparand string `json:"comparand,omitempty"`
FailStatus *int32 `json:"fail-status,omitempty"`
Addresses []ACLAddress `json:"addrs,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
}
// ACLAddress represents an entry in a VCL. If MaskBits is non-nil, it
// is a CIDR range. If Negate is true, use the '!' notation in the VCL
// ACL.
type ACLAddress struct {
Address string `json:"addr,omitempty"`
MaskBits *int32 `json:"mask-bits,omitempty"`
Negate bool `json:"negate,omitempty"`
}
// Condition represents a term in a boolean expression -- match or
// non-match against a VCL object.
type Condition struct {
Comparand string `json:"comparand,omitempty"`
Regex string `json:"regex,omitempty"`
Match bool `json:"match,omitempty"`
}
// ACLType classifies an ACL.
type ACLType string
const (
// Whitelist means that the failure status is returned when an
// IP address does not match an ACL.
Whitelist ACLType = "whitelist"
// Blacklist means that the failure status is returned when an
// IP address does match an ACL.
Blacklist = "blacklist"
)
// VarnishConfigStatus is the status for a VarnishConfig resource
// type VarnishConfigStatus struct {
// AvailableReplicas int32 `json:"availableReplicas"`
......
// +build !ignore_autogenerated
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......@@ -34,6 +34,60 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACLAddress) DeepCopyInto(out *ACLAddress) {
*out = *in
if in.MaskBits != nil {
in, out := &in.MaskBits, &out.MaskBits
*out = new(int32)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACLAddress.
func (in *ACLAddress) DeepCopy() *ACLAddress {
if in == nil {
return nil
}
out := new(ACLAddress)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACLSpec) DeepCopyInto(out *ACLSpec) {
*out = *in
if in.FailStatus != nil {
in, out := &in.FailStatus, &out.FailStatus
*out = new(int32)
**out = **in
}
if in.Addresses != nil {
in, out := &in.Addresses, &out.Addresses
*out = make([]ACLAddress, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACLSpec.
func (in *ACLSpec) DeepCopy() *ACLSpec {
if in == nil {
return nil
}
out := new(ACLSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthCondition) DeepCopyInto(out *AuthCondition) {
*out = *in
......@@ -71,6 +125,22 @@ func (in *AuthSpec) DeepCopy() *AuthSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProbeSpec) DeepCopyInto(out *ProbeSpec) {
*out = *in
......@@ -199,6 +269,13 @@ func (in *VarnishConfigSpec) DeepCopyInto(out *VarnishConfigSpec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ACLs != nil {
in, out := &in.ACLs, &out.ACLs
*out = make([]ACLSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
......
......@@ -49,6 +49,8 @@ const (
ingressClassKey = "kubernetes.io/ingress.class"
annotationPrefix = "ingress.varnish-cache.org/"
varnishSvcKey = annotationPrefix + "varnish-svc"
defACLcomparand = "client.ip"
defACLfailStatus = uint16(403)
)
func (worker *NamespaceWorker) getVarnishSvcForIng(
......@@ -309,6 +311,63 @@ func (worker *NamespaceWorker) configAuth(spec *vcl.Spec,
return nil
}
func (worker *NamespaceWorker) configACL(spec *vcl.Spec,
vcfg *vcr_v1alpha1.VarnishConfig) error {
if len(vcfg.Spec.ACLs) == 0 {
worker.log.Infof("No ACL spec found for VarnishConfig %s/%s",
vcfg.Namespace, vcfg.Name)
return nil
}
spec.ACLs = make([]vcl.ACL, 0, len(vcfg.Spec.ACLs))
for _, acl := range vcfg.Spec.ACLs {
worker.log.Debugf("VarnishConfig %s/%s configuring VCL ACL "+
"from: %+v", vcfg.Namespace, vcfg.Name, acl)
vclACL := vcl.ACL{
Name: acl.Name,
Addresses: make([]vcl.ACLAddress, 0, len(acl.Addresses)),
Conditions: make([]vcl.MatchTerm, 0, len(acl.Conditions)),
}
if acl.Comparand == "" {
vclACL.Comparand = defACLcomparand
} else {
vclACL.Comparand = acl.Comparand
}
if acl.ACLType == "" || acl.ACLType == vcr_v1alpha1.Whitelist {
vclACL.Whitelist = true
}
if acl.FailStatus == nil {
vclACL.FailStatus = defACLfailStatus
} else {
vclACL.FailStatus = uint16(*acl.FailStatus)
}
for _, addr := range acl.Addresses {
vclAddr := vcl.ACLAddress{
Addr: addr.Address,
Negate: addr.Negate,
}
if addr.MaskBits == nil {
vclAddr.MaskBits = vcl.NoMaskBits
} else {
vclAddr.MaskBits = uint8(*addr.MaskBits)
}
vclACL.Addresses = append(vclACL.Addresses, vclAddr)
}
for _, cond := range acl.Conditions {
vclMatch := vcl.MatchTerm{
Comparand: cond.Comparand,
Regex: cond.Regex,
Match: cond.Match,
}
vclACL.Conditions = append(vclACL.Conditions, vclMatch)
}
worker.log.Debugf("VarnishConfig %s/%s add VCL ACL config: "+
"%+v", vcfg.Namespace, vcfg.Name, vclACL)
spec.ACLs = append(spec.ACLs, vclACL)
}
return nil
}
func (worker *NamespaceWorker) hasIngress(svc *api_v1.Service,
ing *extensions.Ingress, spec vcl.Spec) bool {
......@@ -367,6 +426,9 @@ func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error {
if err = worker.configAuth(&vclSpec, vcfg); err != nil {
return err
}
if err = worker.configACL(&vclSpec, vcfg); err != nil {
return err
}
} else {
worker.log.Infof("Found no VarnishConfigs for Varnish Service "+
"%s/%s", svc.Namespace, svc.Name)
......
import std;
{{- range .ACLs}}
acl {{aclName .Name}} {
{{- range .Addresses}}
{{if .Negate}}! {{end}}"{{.Addr}}"{{aclMask .MaskBits}};
{{- end}}
}
{{- end}}
sub vcl_recv {
{{- if hasXFF .ACLs}}
std.collect(req.http.X-Forwarded-For);
{{- end}}
{{- range .ACLs}}
if (
{{- range $cond := .Conditions}}
{{$cond.Comparand}} {{if not .Match}}!{{end}}~ "{{.Regex}}" &&
{{- end}}
{{aclCmp .Comparand}} {{if .Whitelist}}!{{end}}~ {{aclName .Name}}
) {
return(synth({{.FailStatus}}));
}
{{- end}}
}
import std;
acl vk8s_man_vcl_example_acl {
"localhost";
"192.0.2.0"/24;
! "192.0.2.23";
}
acl vk8s_wikipedia_example_acl {
"192.168.100.14"/24;
"192.168.100.0"/22;
"2001:db8::"/48;
}
acl vk8s_private4_acl {
"10.0.0.0"/24;
"172.16.0.0"/12;
"192.168.0.0"/16;
}
acl vk8s_rfc5737_acl {
"192.0.2.0"/24;
"198.51.100.0"/24;
"203.0.113.0"/24;
}
acl vk8s_local_acl {
"127.0.0.0"/8;
"::1";
}
sub vcl_recv {
std.collect(req.http.X-Forwarded-For);
if (
req.http.Host ~ "^cafe\.example\.com$" &&
req.url ~ "^/coffee(/|$)" &&
client.ip !~ vk8s_man_vcl_example_acl
) {
return(synth(403));
}
if (
req.url !~ "^/tea(/|$)" &&
server.ip ~ vk8s_wikipedia_example_acl
) {
return(synth(404));
}
if (
std.ip(req.http.X-Real-IP, "0.0.0.0") !~ vk8s_private4_acl
) {
return(synth(403));
}
if (
std.ip(regsub(req.http.X-Forwarded-For,"^([^,\s]+).*","\1"), "0.0.0.0") !~ vk8s_rfc5737_acl
) {
return(synth(403));
}
if (
std.ip(regsub(req.http.X-Forwarded-For,"^.*?([\d.]+)\s*,[^,]*$","\1"), "0.0.0.0") !~ vk8s_local_acl
) {
return(synth(403));
}
}
......@@ -42,6 +42,7 @@ import (
"path"
"regexp"
"sort"
"strings"
"text/template"
)
......@@ -213,6 +214,91 @@ func (a byRealm) Len() int { return len(a) }
func (a byRealm) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byRealm) Less(i, j int) bool { return a[i].Realm < a[j].Realm }
// NoMaskBits is a sentinel value for ACLAddress.MaskBits indicating
// that a CIDR range is not to be used.
const NoMaskBits uint8 = 255
// ACLAddress represents an element in an ACL -- a host name to be
// resolved at VCL load, an IP address, or address range in CIDR
// notation. Use the '!' for negation when Negate is true.
type ACLAddress struct {
Addr string
MaskBits uint8
Negate bool
}
func (addr ACLAddress) hash(hash hash.Hash) {
hash.Write([]byte(addr.Addr))
hash.Write([]byte{byte(addr.MaskBits)})
if addr.Negate {
hash.Write([]byte("Negate"))
}
}
// interface for sorting []ACLAddress
type byACLAddr []ACLAddress
func (a byACLAddr) Len() int { return len(a) }
func (a byACLAddr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byACLAddr) Less(i, j int) bool { return a[i].Addr < a[j].Addr }
// MatchTerm is a term describing the comparison of a VCL object with
// a pattern.
type MatchTerm struct {
Comparand string
Regex string
Match bool
}
func (match MatchTerm) hash(hash hash.Hash) {
hash.Write([]byte(match.Comparand))
hash.Write([]byte(match.Regex))
if match.Match {
hash.Write([]byte("Match"))
}
}
// interface for sorting []MatchTerm
type byComparand []MatchTerm
func (a byComparand) Len() int { return len(a) }
func (a byComparand) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byComparand) Less(i, j int) bool {
return a[i].Comparand < a[j].Comparand
}
// ACL represents an Access Control List, derived from a
// VarnishConfig.
type ACL struct {
Name string
Comparand string
FailStatus uint16
Whitelist bool
Addresses []ACLAddress
Conditions []MatchTerm
}
func (acl ACL) hash(hash hash.Hash) {
hash.Write([]byte(acl.Name))
hash.Write([]byte(acl.Comparand))
statusBytes := make([]byte, 2)
binary.BigEndian.PutUint16(statusBytes, uint16(acl.FailStatus))
hash.Write(statusBytes)
for _, addr := range acl.Addresses {
addr.hash(hash)
}
for _, cond := range acl.Conditions {
cond.hash(hash)
}
}
// interface for sorting []ACL
type byACLName []ACL
func (a byACLName) Len() int { return len(a) }
func (a byACLName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byACLName) Less(i, j int) bool { return a[i].Name < a[j].Name }
// Spec is the specification for a VCL configuration derived from
// Ingresses and VarnishConfig Custom Resources. This abstracts the
// VCL to be loaded by all instances of a Varnish Service.
......@@ -234,6 +320,7 @@ type Spec struct {
// Authentication, derived from the Auth section of a
// VarnishConfig.
Auths []Auth
ACLs []ACL
}
// DeepHash computes a 64-bit hash value from a Spec such that if two
......@@ -259,6 +346,9 @@ func (spec Spec) DeepHash() uint64 {
for _, auth := range spec.Auths {
auth.hash(hash)
}
for _, acl := range spec.ACLs {
acl.hash(hash)
}
return hash.Sum64()
}
......@@ -273,6 +363,7 @@ func (spec Spec) Canonical() Spec {
AllServices: make(map[string]Service, len(spec.AllServices)),
ShardCluster: spec.ShardCluster,
Auths: make([]Auth, len(spec.Auths)),
ACLs: make([]ACL, len(spec.ACLs)),
}
copy(canon.DefaultService.Addresses, spec.DefaultService.Addresses)
sort.Stable(byIPPort(canon.DefaultService.Addresses))
......@@ -296,12 +387,21 @@ func (spec Spec) Canonical() Spec {
for _, auth := range canon.Auths {
sort.Strings(auth.Credentials)
}
copy(canon.ACLs, spec.ACLs)
sort.Stable(byACLName(canon.ACLs))
for _, acl := range canon.ACLs {
sort.Stable(byACLAddr(acl.Addresses))
sort.Stable(byComparand(acl.Conditions))
}
return canon
}
var fMap = template.FuncMap{
"plusOne": func(i int) int { return i + 1 },
"vclMangle": func(s string) string { return mangle(s) },
"aclMask": func(bits uint8) string { return aclMask(bits) },
"aclCmp": func(comparand string) string { return aclCmp(comparand) },
"hasXFF": func(acls []ACL) bool { return hasXFF(acls) },
"backendName": func(svc Service, addr string) string {
return backendName(svc, addr)
},
......@@ -311,18 +411,23 @@ var fMap = template.FuncMap{
"urlMatcher": func(rule Rule) string {
return urlMatcher(rule)
},
"aclName": func(name string) string {
return "vk8s_" + mangle(name) + "_acl"
},
}
const (
ingTmplSrc = "vcl.tmpl"
shardTmplSrc = "self-shard.tmpl"
authTmplSrc = "auth.tmpl"
aclTmplSrc = "acl.tmpl"
)
var (
ingressTmpl *template.Template
shardTmpl *template.Template
authTmpl *template.Template
aclTmpl *template.Template
symPattern = regexp.MustCompile("^[[:alpha:]][[:word:]-]*$")
first = regexp.MustCompile("[[:alpha:]]")
restIllegal = regexp.MustCompile("[^[:word:]-]+")
......@@ -334,6 +439,8 @@ func InitTemplates(tmplDir string) error {
ingTmplPath := path.Join(tmplDir, ingTmplSrc)
shardTmplPath := path.Join(tmplDir, shardTmplSrc)
authTmplPath := path.Join(tmplDir, authTmplSrc)
aclTmplPath := path.Join(tmplDir, aclTmplSrc)
ingressTmpl, err = template.New(ingTmplSrc).
Funcs(fMap).ParseFiles(ingTmplPath)
if err != nil {
......@@ -349,6 +456,11 @@ func InitTemplates(tmplDir string) error {
if err != nil {
return err
}
aclTmpl, err = template.New(aclTmplSrc).
Funcs(fMap).ParseFiles(aclTmplPath)
if err != nil {
return err
}
return nil
}
......@@ -372,6 +484,11 @@ func (spec Spec) GetSrc() (string, error) {
return "", err
}
}
if len(spec.ACLs) > 0 {
if err := aclTmpl.Execute(&buf, spec); err != nil {
return "", err
}
}
if len(spec.Auths) > 0 {
if err := authTmpl.Execute(&buf, spec); err != nil {
return "", err
......@@ -406,3 +523,38 @@ func directorName(svc Service) string {
func urlMatcher(rule Rule) string {
return mangle(rule.Host + "_url")
}
func aclMask(bits uint8) string {
if bits > 128 {
return ""
}
return fmt.Sprintf("/%d", bits)
}
const (
xffFirst = `regsub(req.http.X-Forwarded-For,"^([^,\s]+).*","\1")`
xff2ndLast = `regsub(req.http.X-Forwarded-For,"^.*?([\d.]+)\s*,[^,]*$","\1")`
)
func aclCmp(comparand string) string {
if strings.HasPrefix(comparand, "xff-") ||
strings.HasPrefix(comparand, "req.http.") {
if comparand == "xff-first" {
comparand = xffFirst
} else if comparand == "xff-2ndlast" {
comparand = xff2ndLast
}
return fmt.Sprintf(`std.ip(%s, "0.0.0.0")`, comparand)
}
return comparand
}
func hasXFF(acls []ACL) bool {
for _, acl := range acls {
if strings.HasPrefix(acl.Comparand, "xff-") {
return true
}
}
return false
}
......@@ -397,3 +397,167 @@ func TestAuthTemplate(t *testing.T) {
}
}
}
var acls = Spec{
ACLs: []ACL{
{
Name: "man_vcl_example",
Comparand: "client.ip",
FailStatus: 403,
Whitelist: true,
Addresses: []ACLAddress{
ACLAddress{
Addr: "localhost",
MaskBits: 255,
Negate: false,
},
ACLAddress{
Addr: "192.0.2.0",
MaskBits: 24,
Negate: false,
},
ACLAddress{
Addr: "192.0.2.23",
MaskBits: 255,
Negate: true,
},
},
Conditions: []MatchTerm{
MatchTerm{
Comparand: "req.http.Host",
Match: true,
Regex: `^cafe\.example\.com$`,
},
MatchTerm{
Comparand: "req.url",
Match: true,
Regex: `^/coffee(/|$)`,
},
},
},
{
Name: "wikipedia_example",
Comparand: "server.ip",
FailStatus: 404,
Whitelist: false,
Addresses: []ACLAddress{
ACLAddress{
Addr: "192.168.100.14",
MaskBits: 24,
Negate: false,
},
ACLAddress{
Addr: "192.168.100.0",
MaskBits: 22,
Negate: false,
},
ACLAddress{
Addr: "2001:db8::",
MaskBits: 48,
Negate: false,
},
},
Conditions: []MatchTerm{
MatchTerm{
Comparand: "req.url",
Match: false,
Regex: `^/tea(/|$)`,
},
},
},
{
Name: "private4",
Comparand: "req.http.X-Real-IP",
FailStatus: 403,
Whitelist: true,
Addresses: []ACLAddress{
ACLAddress{
Addr: "10.0.0.0",
MaskBits: 24,
Negate: false,
},
ACLAddress{
Addr: "172.16.0.0",
MaskBits: 12,
Negate: false,
},
ACLAddress{
Addr: "192.168.0.0",
MaskBits: 16,
Negate: false,
},
},
Conditions: []MatchTerm{},
},
{
Name: "rfc5737",
Comparand: "xff-first",
FailStatus: 403,
Whitelist: true,
Addresses: []ACLAddress{
ACLAddress{
Addr: "192.0.2.0",
MaskBits: 24,
Negate: false,
},
ACLAddress{
Addr: "198.51.100.0",
MaskBits: 24,
Negate: false,
},
ACLAddress{
Addr: "203.0.113.0",
MaskBits: 24,
Negate: false,
},
},
Conditions: []MatchTerm{},
},
{
Name: "local",
Comparand: "xff-2ndlast",
FailStatus: 403,
Whitelist: true,
Addresses: []ACLAddress{
ACLAddress{
Addr: "127.0.0.0",
MaskBits: 8,
Negate: false,
},
ACLAddress{
Addr: "::1",
MaskBits: 255,
Negate: false,
},
},
Conditions: []MatchTerm{},
},
},
}
func TestAclTemplate(t *testing.T) {
var buf bytes.Buffer
gold := "acl.golden"
tmplName := "acl.tmpl"
tmpl, err := template.New(tmplName).Funcs(fMap).ParseFiles(tmplName)
if err != nil {
t.Error("Cannot parse acl template:", err)
return
}
if err := tmpl.Execute(&buf, acls); err != nil {
t.Error("acls template Execute():", err)
return
}
ok, err := cmpGold(buf.Bytes(), gold)
if err != nil {
t.Fatalf("Reading %s: %v", gold, err)
}
if !ok {
t.Errorf("Generated VCL for authorization does not match gold "+
"file: %s", gold)
if testing.Verbose() {
t.Logf("Generated: %s", buf.String())
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment