Skip to content

Commit

Permalink
refactor: allow overrides for allow/blocks on namespace annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
shreddedbacon committed Dec 14, 2023
1 parent d2dbda0 commit 09a03cf
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 66 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ It is possible to add global IP allow and block lists, the helm chart will have
* allowing IP addresses via `/lists/allowedips` file which is a single line per entry of ip address to allow
* blocking IP addresses via `/lists/blockedips` file which is a single line per entry of ip address to block

There are also annotations that can be added to specific `Kind: Ingress` objects that allow for ip allow or blocking.
There are also annotations that can be added to the namespace, or individual `Kind: Ingress` objects that allow for ip allow or blocking.
* `idling.amazee.io/ip-allow-list` - a comma separated list of ip addresses to allow, will be checked against x-forward-for, but if true-client-ip is provided it will prefer this.
* `idling.amazee.io/ip-block-list` - a comma separated list of ip addresses to allow, will be checked against x-forward-for, but if true-client-ip is provided it will prefer this.

Expand All @@ -40,7 +40,7 @@ It is possible to add global UserAgent allow and block lists, the helm chart wil
* allowing user agents via a `/lists/allowedagents` file which is a single line per entry of useragents or regex patterns to match against. These must be `go` based regular expressions.
* blocking user agents via a `/lists/blockedagents` file which is a single line per entry of useragents or regex patterns to match against. These must be `go` based regular expressions.

There are also annotations that can be added to specific `Kind: Ingress` objects that allow for user agent allow or blocking.
There are also annotations that can be added to the namespace, or individual `Kind: Ingress` objects that allow for user agent allow or blocking.
* `idling.amazee.io/allowed-agents` - a comma separated list of user agents or regex patterns to allow.
* `idling.amazee.io/blocked-agents` - a comma separated list of user agents or regex patterns to block.

Expand Down
6 changes: 2 additions & 4 deletions handlers/idler/service-kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ func (h *Idler) KubernetesServiceIdler(ctx context.Context, opLog logr.Logger, n
opLog.Error(err, fmt.Sprintf("Error getting deployments"))
return
}
// fmt.Println(labelRequirements)
// fmt.Println("deploys", len(deployments.Items))
for _, deployment := range deployments.Items {
checkPods := false
zeroReps := new(int32)
Expand Down Expand Up @@ -134,11 +132,11 @@ func (h *Idler) KubernetesServiceIdler(ctx context.Context, opLog logr.Logger, n
)
result, warnings, err := v1api.Query(ctx, promQuery, time.Now())
if err != nil {
fmt.Printf("Error querying Prometheus: %v\n", err)
opLog.Error(err, "Error querying Prometheus")
return
}
if len(warnings) > 0 {
fmt.Printf("Warnings: %v\n", warnings)
opLog.Info(fmt.Sprintf("Warnings: %v", warnings))
}
// and then add up the results of all the status requests to determine hit count
if result.Type() == prometheusmodel.ValVector {
Expand Down
11 changes: 10 additions & 1 deletion handlers/unidler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
networkv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
)
Expand Down Expand Up @@ -66,6 +67,13 @@ func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Re
// @TODO: check for code 503 specifically, or just any request that has the namespace in it will be "unidled" if a request comes in for
// that ingress and the
if ns != "" {
namespace := &corev1.Namespace{}
if err := h.Client.Get(ctx, types.NamespacedName{
Name: ns,
}, namespace); err != nil {
opLog.Info(fmt.Sprintf("unable to get any namespaces: %v", err))
return
}
ingress := &networkv1.Ingress{}
if err := h.Client.Get(ctx, types.NamespacedName{
Namespace: ns,
Expand All @@ -81,7 +89,7 @@ func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Re
trueClientIP := r.Header.Get("True-Client-IP")
requestUserAgent := r.Header.Get("User-Agent")

allowUnidle := h.checkAccess(ingress.ObjectMeta.Annotations, requestUserAgent, trueClientIP, xForwardedFor)
allowUnidle := h.checkAccess(namespace.ObjectMeta.Annotations, ingress.ObjectMeta.Annotations, requestUserAgent, trueClientIP, xForwardedFor)
// then run checks to start to unidle the environment
if allowUnidle {
// if a namespace exists, it means that the custom-http-errors code is defined in the ingress object
Expand All @@ -105,6 +113,7 @@ func (h *Unidler) ingressHandler(path string) func(http.ResponseWriter, *http.Re
if h.Debug == true {
opLog.Info(fmt.Sprintf("Serving custom error response for code %v and format %v from file %v", code, format, file))
}
w.Header().Set("X-Aergia-Allowed", "true")
// then return the unidle template to the user
tmpl := template.Must(template.ParseFiles(file))
tmpl.ExecuteTemplate(w, "base", pageData{
Expand Down
102 changes: 45 additions & 57 deletions handlers/unidler/restrictions.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,75 +34,63 @@ func checkIPList(allowList []string, xForwardedFor []string, trueClientIP string
return false
}

func (h *Unidler) checkAccess(annotations map[string]string, userAgent, trueClientIP string, xForwardedFor []string) bool {
allowedIP := false
allowedAgent := false
blockedIP := false
blockedAgent := false

hasIPAllowList := false
hasAllowedAgentList := false
hasIPBlockList := false
hasBlockedAgentList := false

if alist, ok := annotations["idling.amazee.io/ip-allow-list"]; ok {
// there is an allow list, we want to deny any requests now unless they are the trueclientip
// or xforwardedfor if trueclientip is not defined
hasIPAllowList = true
allowedIP = checkIPList(strings.Split(alist, ","), xForwardedFor, trueClientIP)
} else {
if h.AllowedIPs != nil {
hasIPAllowList = true
allowedIP = checkIPList(h.AllowedIPs, xForwardedFor, trueClientIP)
}
}

if blist, ok := annotations["idling.amazee.io/ip-block-list"]; ok {
// there is a block list, we want to allow any requests now unless they are the trueclientip
// or xforwardedfor if trueclientip is not defined
hasIPBlockList = true
blockedIP = checkIPList(strings.Split(blist, ","), xForwardedFor, trueClientIP)
} else {
if h.BlockedIPs != nil {
hasIPBlockList = true
blockedIP = checkIPList(h.BlockedIPs, xForwardedFor, trueClientIP)
}
}
func (h *Unidler) checkAccess(nsannotations map[string]string, annotations map[string]string, userAgent, trueClientIP string, xForwardedFor []string) bool {

// deal with ip allow/blocks first
if allowedIP && hasIPAllowList {
blockedIP := checkIPAnnotations("idling.amazee.io/ip-block-list", trueClientIP, xForwardedFor, h.BlockedIPs, nsannotations, annotations)
if blockedIP {
return false
}
allowedIP := checkIPAnnotations("idling.amazee.io/ip-allow-list", trueClientIP, xForwardedFor, h.AllowedIPs, nsannotations, annotations)
if allowedIP {
return true
}
if blockedIP && hasIPBlockList {
blockedAgent := checkAgentAnnotations("idling.amazee.io/blocked-agents", userAgent, h.BlockedUserAgents, nsannotations, annotations)
if blockedAgent {
return false
}
allowedAgent := checkAgentAnnotations("idling.amazee.io/allowed-agents", userAgent, h.AllowedUserAgents, nsannotations, annotations)
if allowedAgent {
return true
}
// else fallthrough
return true
}

if agents, ok := annotations["idling.amazee.io/allowed-agents"]; ok {
hasAllowedAgentList = true
allowedAgent = checkAgents(strings.Split(agents, ","), userAgent)
func checkAgentAnnotations(annotation, ua string, g []string, ns, i map[string]string) bool {
allow := false
if agents, ok := i[annotation]; ok {
allow = checkAgents(strings.Split(agents, ","), ua)
} else {
if h.AllowedUserAgents != nil {
hasAllowedAgentList = true
allowedAgent = checkAgents(h.AllowedUserAgents, userAgent)
// check for namespace annoation
if agents, ok := ns[annotation]; ok {
allow = checkAgents(strings.Split(agents, ","), ua)
} else {
// check for globals
if g != nil {
allow = checkAgents(g, ua)
}
}
}
return allow
}

if agents, ok := annotations["idling.amazee.io/blocked-agents"]; ok {
hasBlockedAgentList = true
blockedAgent = checkAgents(strings.Split(agents, ","), userAgent)
func checkIPAnnotations(annotation, tcip string, xff, g []string, ns, i map[string]string) bool {
allow := false
if alist, ok := i[annotation]; ok {
// there is an allow list, we want to deny any requests now unless they are the trueclientip
// or xforwardedfor if trueclientip is not defined
allow = checkIPList(strings.Split(alist, ","), xff, tcip)
} else {
if h.BlockedUserAgents != nil {
hasBlockedAgentList = true
blockedAgent = checkAgents(h.BlockedUserAgents, userAgent)
// check for namespace annoation
if alist, ok := ns[annotation]; ok {
allow = checkIPList(strings.Split(alist, ","), xff, tcip)
} else {
// check for globals
if g != nil {
allow = checkIPList(g, xff, tcip)
}
}
}

if allowedAgent && hasAllowedAgentList {
return true
}
if blockedAgent && hasBlockedAgentList {
return false
}
// else fallthrough
return true
return allow
}
22 changes: 21 additions & 1 deletion handlers/unidler/restrictions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func TestUnidler_checkAccess(t *testing.T) {
BlockedIPs []string
}
type args struct {
nsannotations map[string]string
annotations map[string]string
userAgent string
trueClientIP string
Expand Down Expand Up @@ -362,6 +363,25 @@ func TestUnidler_checkAccess(t *testing.T) {
},
want: true,
},
{
name: "test15 - allowed ip blocked agent namespace annotation",
args: args{
nsannotations: map[string]string{
"idling.amazee.io/blocked-agents": "@(example).test.?$,@(internal).test.?$",
"idling.amazee.io/ip-allow-list": "1.2.3.4",
},
userAgent: "This is not a bot, don't complaint to: [email protected].",
trueClientIP: "1.2.3.4",
xForwardedFor: nil,
},
fields: fields{
AllowedUserAgents: nil,
BlockedUserAgents: nil,
BlockedIPs: nil,
AllowedIPs: nil,
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -371,7 +391,7 @@ func TestUnidler_checkAccess(t *testing.T) {
AllowedIPs: tt.fields.AllowedIPs,
BlockedIPs: tt.fields.BlockedIPs,
}
if got := h.checkAccess(tt.args.annotations, tt.args.userAgent, tt.args.trueClientIP, tt.args.xForwardedFor); got != tt.want {
if got := h.checkAccess(tt.args.nsannotations, tt.args.annotations, tt.args.userAgent, tt.args.trueClientIP, tt.args.xForwardedFor); got != tt.want {
t.Errorf("Unidler.checkAccess() = %v, want %v", got, tt.want)
}
})
Expand Down
3 changes: 2 additions & 1 deletion handlers/unidler/unidler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Unidler struct {
Client ctrlClient.Client
Log logr.Logger
RefreshInterval int
UnidlerHTTPPort int
Debug bool
RequestCount *prometheus.CounterVec
RequestDuration *prometheus.HistogramVec
Expand Down Expand Up @@ -106,7 +107,7 @@ func Run(h *Unidler, setupLog logr.Logger) {
http.Handle("/", r)

httpServer := &http.Server{
Addr: ":5000",
Addr: fmt.Sprintf(":%d", h.UnidlerHTTPPort),
Handler: r,
}
err := httpServer.ListenAndServe()
Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func main() {
var enableLeaderElection bool
var debug bool
var refreshInterval int
var unidlerHTTPPort int

var dryRun bool
var selectorsFile string
Expand Down Expand Up @@ -94,12 +95,14 @@ func main() {
"Flag to determine if the idler should check the hit backend or not. If true, this overrides what is in the selectors file.")
flag.BoolVar(&enableCLIIdler, "enable-cli-idler", true, "Flag to enable cli idler.")
flag.BoolVar(&enableServiceIdler, "enable-service-idler", true, "Flag to enable service idler.")
flag.IntVar(&unidlerHTTPPort, "unidler-port", 5000, "Port for the unidler service to listen on.")
flag.Parse()

selectorsFile = variables.GetEnv("SELECTORS_YAML_FILE", selectorsFile)

dryRun = variables.GetEnvBool("DRY_RUN", dryRun)

unidlerHTTPPort = variables.GetEnvInt("UNIDLER_PORT", unidlerHTTPPort)
cliCron = variables.GetEnv("CLI_CRON", cliCron)
serviceCron = variables.GetEnv("SERVICE_CRON", serviceCron)
enableServiceIdler = variables.GetEnvBool("ENABLE_SERVICE_IDLER", enableServiceIdler)
Expand Down Expand Up @@ -178,6 +181,7 @@ func main() {
BlockedUserAgents: blockedAgents,
AllowedIPs: allowedIPs,
BlockedIPs: blockedIPs,
UnidlerHTTPPort: unidlerHTTPPort,
}

prometheusClient, err := prometheusapi.NewClient(prometheusapi.Config{
Expand Down

0 comments on commit 09a03cf

Please sign in to comment.