cluster/admitomatic: Regexp-based admission rules

Change-Id: Ic2b1d6a952dc194c0ee2fa1673ceb91c43799308
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1723
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/cluster/admitomatic/ingress.go b/cluster/admitomatic/ingress.go
index b7bdf91..390a160 100644
--- a/cluster/admitomatic/ingress.go
+++ b/cluster/admitomatic/ingress.go
@@ -4,6 +4,7 @@
 	"encoding/json"
 	"fmt"
 	"strings"
+	"regexp"
 
 	"github.com/golang/glog"
 	admission "k8s.io/api/admission/v1beta1"
@@ -37,6 +38,9 @@
 	// allowed is a map from namespace to list of domain matchers.
 	allowed map[string][]*domain
 
+	// allowedRegexp is a list of domain regexps and their allowed namespaces
+	allowedRegexp []*regexpFilter
+
 	// anythingGoesNamespaces are namespaces that are opted out of security
 	// checks.
 	anythingGoesNamespaces []string
@@ -53,6 +57,11 @@
 	wildcard bool
 }
 
+type regexpFilter struct {
+	namespace string
+	dns *regexp.Regexp
+}
+
 // match returns whether this matcher matches a given domain.
 func (d *domain) match(dns string) bool {
 	if !d.wildcard {
@@ -63,7 +72,7 @@
 
 // allow adds a given (namespace, dns) pair to the filter. The dns variable is
 // a string that is either a simple domain name, or a wildcard like
-// *.foo.example.com. An error is returned if the dns stirng could not be
+// *.foo.example.com. An error is returned if the dns string could not be
 // parsed.
 func (i *ingressFilter) allow(ns, dns string) error {
 	// If the filter is brand new, initialize it.
@@ -97,6 +106,22 @@
 	return nil
 }
 
+func (i *ingressFilter) allowRegexp(ns string, dns string) error {
+	// Parse dns as a regexp
+	dnsPattern := "^" + dns + "$"
+	re, err := regexp.Compile(dnsPattern)
+	if err != nil {
+		return err
+	}
+
+	i.allowedRegexp = append(i.allowedRegexp, &regexpFilter{
+		namespace: ns,
+		dns:       re,
+	})
+
+	return nil
+}
+
 // domainAllowed returns whether a given domain is allowed to be backed by an
 // ingress within a given namespace.
 func (i *ingressFilter) domainAllowed(ns, domain string) bool {
@@ -125,6 +150,24 @@
 	if domainFound {
 		return false
 	}
+
+	// Check regexp matching
+	for _, filter := range i.allowedRegexp {
+		re := filter.dns
+		allowedNs := filter.namespace
+
+		submatches := re.FindStringSubmatchIndex(domain)
+		if submatches == nil {
+			continue
+		}
+
+		// Domain matched, expand allowed namespace template
+		expectedNs := []byte{}
+		expectedNs = re.ExpandString(expectedNs, allowedNs, domain, submatches)
+		didMatch := string(expectedNs) == ns
+		return didMatch
+	}
+
 	// No direct match found, and this domain is not restricted. Allow.
 	return true
 }