Skip to content
This repository has been archived by the owner on Aug 6, 2024. It is now read-only.

Commit

Permalink
Merge pull request #6 from h3adex/dev
Browse files Browse the repository at this point in the history
Update Annotations with whitelists
  • Loading branch information
h3adex authored Dec 12, 2023
2 parents 41d69a5 + 95c0247 commit f8dcb07
Show file tree
Hide file tree
Showing 14 changed files with 600 additions and 228 deletions.
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ a Kubernetes Ingress Controller. Notably, this project currently lacks
full support for all functionalities provided by the Ingress API Object.

## Features
- [x] Block Requests based on User-Agent Strings
- [x] Block Requests based on TLS fingerprint (JA3/JA4)
- [ ] Whitelist Requests based on TLS fingerprint (JA3/JA4)
- [x] Blacklist/Whitelist User-Agent Strings/Regular Expression
- [x] Whitelist/Blacklist requests based on TLS fingerprint (Ja3,Ja3-Hash,Ja3n,Ja4,Ja4h)
- [x] Add JA4/JA3 fingerprint hash to the request header
- [x] Rate Limit/Throttle Requests coming from a single IP Address
- [x] Use Redis as backend to store rate limiting information
Expand All @@ -24,11 +23,11 @@ full support for all functionalities provided by the Ingress API Object.
## Usage
To block requests, utilize specific [annotations](pkg/annotations/annotations.go) on the Ingress API Object:

- `guardgress/user-agent-blacklist`: Blocks requests based on comma-separated User-Agents.
- `guardgress/ja3-blacklist`: Blocks requests based on Ja3/Ja3n comma-separated fingerprints/hashes.
- `guardgress/ja4-blacklist`: Blocks requests based on Ja4/Ja4n comma-separated fingerprints/hashes.
- `guardgress/add-ja3-header`: Adds Ja3/Ja3n fingerprint/hash to the request header.
- `guardgress/add-ja4-header`: Adds Ja4/Ja4n fingerprint/hash to the request header.
- `guardgress/user-agent-whitelist`: Limits access to specific User-Agents (comma-separated). Whitelist takes precedence over the blacklist. If both are set, anything outside the whitelist is blocked. For an example, check [this configuration](k8s/examples/ingress-ua-block-white-and-blacklist.yaml).
- `guardgress/user-agent-blacklist`: Blocks requests from particular User-Agents (comma-separated).
- `guardgress/tls-fingerprint-whitelist`: Limits access to specific TLS Fingerprints (`Ja3`, `Ja3-Hash`, `Ja3n`, `Ja4`, `Ja4h` - comma-separated). Whitelist takes precedence over the blacklist. If both are set, anything outside the whitelist is blocked. Find an example configuration [here](k8s/examples/ingress-tls-block-white-and-blacklist.yaml).
- `guardgress/tls-fingerprint-blacklist`: Restricts requests from particular TLS Fingerprints (`Ja3`, `Ja3-Hash`, `Ja3n`, `Ja4`, `Ja4h` - comma-separated).
- `guardgress/add-tls-fingerprint-header`: Adds `Ja3,Ja3-Hash,Ja3n,Ja4,Ja4h` fingerprints/hashes to the request header.
- `guardgress/force-ssl-redirect`: Forces SSL Redirection. This annotation is only useful if you have a TLS certificate configured for your ingress object.
- `guardgress/limit-ip-whitelist`: Whitelists IP addresses for rate limiting.
- `guardgress/limit-path-whitelist`: Whitelists Paths for rate limiting. For instance, if you have an ingress object with a Pathtype set as "Prefix" and Path defined as "/shop," you can specify "/shop/products" to be exempted from rate limiting through whitelisting.
Expand Down
3 changes: 1 addition & 2 deletions build/build-dev-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,7 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
guardgress/add-ja3-header: "true"
guardgress/add-ja4-header: "true"
guardgress/add-tls-fingerprint-header: "true"
guardgress/limit-period: "10-S"
name: whoami
namespace: default
Expand Down
3 changes: 1 addition & 2 deletions k8s/examples/ingress-add-tls-header.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
guardgress/add-ja3-header: "true"
guardgress/add-ja4-header: "true"
guardgress/add-tls-fingerprint-header: "true"
name: shop
namespace: default
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ kind: Ingress
metadata:
annotations:
# The following user agents are blocked on this ingress resource.
"guardgress/user-agent-blacklist": "curl/7.64.1,curl/7.64.2"
# The following JA3 fingerprints are blocked on this ingress resource.
"guardgress/ja3-blacklist": "d41d8cd98f00b204e9800998ecf8427a"
# The following JA4 fingerprints are blocked on this ingress resource.
"guardgress/ja4-blacklist": "t13d1715h2_5b57614c22b0_93c746dc12af"
"guardgress/user-agent-blacklist": "curl/7.64.*,curl/7.65.*"
# The following JA3,JA4 fingerprints are blocked on this ingress resource.
"guardgress/tls-fingerprint-blacklist": "d41d8cd98f00b204e9800998ecf8427a,t13d1715h2_5b57614c22b0_93c746dc12af"
# We are allowing 10 requests per second per IP address.
"guardgress/limit-period": "10-S"
# The following IP addresses are allowed to bypass the rate limit.
Expand Down
27 changes: 27 additions & 0 deletions k8s/examples/ingress-tls-block-white-and-blacklist.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# Restricts access to specific tls-fingerprints; only allows 'd41d8cd98f00b204e9800998ecf8427a'.
"guardgress/tls-fingerprint-whitelist": "d41d8cd98f00b204e9800998ecf8427a"
# Ignores the blacklist if whitelist is active; blocks everything except d41d8cd98f00b204e9800998ecf8427a.
"guardgress/tls-fingerprint-blacklist": "d41d8cd98f00b204e9800998ecf8427a,t13d1715h2_5b57614c22b0_93c746dc12af"
name: shop
namespace: default
spec:
ingressClassName: guardgress
rules:
- host: shop.guardgress.com
http:
paths:
- backend:
service:
name: shop
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- shop.guardgress.com
secretName: shop-tls
27 changes: 27 additions & 0 deletions k8s/examples/ingress-ua-block-white-and-blacklist.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# Restricts access to specific user-agents; only allows 'curl/7.64.*'.
"guardgress/user-agent-whitelist": "curl/7.64.*"
# Ignores the blacklist if whitelist is active; blocks 'curl/7.64.*' and 'curl/7.65.*'.
"guardgress/user-agent-blacklist": "curl/7.64.*,curl/7.65.*"
name: shop
namespace: default
spec:
ingressClassName: guardgress
rules:
- host: shop.guardgress.com
http:
paths:
- backend:
service:
name: shop
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- shop.guardgress.com
secretName: shop-tls
146 changes: 87 additions & 59 deletions pkg/annotations/annotations.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package annotations

import (
"fmt"
"github.com/h3adex/guardgress/pkg/algorithms"
"github.com/h3adex/guardgress/pkg/models"
log "github.com/sirupsen/logrus"
"regexp"
"strings"
)

const (
Ja3Blacklist = "guardgress/ja3-blacklist"
Ja4Blacklist = "guardgress/ja4-blacklist"
UserAgentBlacklist = "guardgress/user-agent-blacklist"
AddJa3HeaderKey = "guardgress/add-ja3-header"
AddJa4HeaderKey = "guardgress/add-ja4-header"
ForceSSLRedirect = "guardgress/force-ssl-redirect"
LimitIpWhitelist = "guardgress/limit-ip-whitelist"
LimitPathWhitelist = "guardgress/limit-path-whitelist"
LimitRedisStore = "guardgress/limit-redis-store-url"
UserAgentWhitelist = "guardgress/user-agent-whitelist"
UserAgentBlacklist = "guardgress/user-agent-blacklist"
TLSFingerprintWhitelist = "guardgress/tls-fingerprint-whitelist"
TLSFingerprintBlackList = "guardgress/tls-fingerprint-blacklist"
AddTLSFingerprintHeader = "guardgress/add-tls-fingerprint-header"
ForceSSLRedirect = "guardgress/force-ssl-redirect"
LimitIpWhitelist = "guardgress/limit-ip-whitelist"
LimitPathWhitelist = "guardgress/limit-path-whitelist"
LimitRedisStore = "guardgress/limit-redis-store-url"
// LimitPeriod uses the simplified format "<limit>-<period>"", with the given
// periods:
//
Expand All @@ -33,81 +36,106 @@ const (
LimitPeriod = "guardgress/limit-period"
)

func isHeaderEnabled(annotations map[string]string, key string) bool {
val, exists := annotations[key]
return exists && val == "true"
func IsUserAgentAllowed(annotations map[string]string, userAgent string) bool {
whitelistAnnotation := annotations[UserAgentWhitelist]
blacklistAnnotation := annotations[UserAgentBlacklist]

if isUserAgentListed(whitelistAnnotation, userAgent, "whitelist") {
return true
}

if isUserAgentListed(blacklistAnnotation, userAgent, "blacklist") {
return false
}

return len(whitelistAnnotation) == 0
}

func IsUserAgentBlacklisted(annotations map[string]string, userAgent string) bool {
if userAgentBlacklist, ok := annotations[UserAgentBlacklist]; ok {
for _, ua := range strings.Split(userAgentBlacklist, ",") {
if ua == userAgent {
log.Error("user agent got blacklisted: ", userAgent)
return true
}
}
func isUserAgentListed(userAgentList string, userAgent string, listType string) bool {
if userAgentList == "" {
return false
}

for _, uaPattern := range strings.Split(userAgentList, ",") {
log.Debug(fmt.Sprintf("Matching user agent %s with pattern %s", userAgent, uaPattern))
matched, err := regexp.MatchString(uaPattern, userAgent)
if err != nil {
log.Errorf("Error matching user agent: %s", err)
continue
}
if matched {
log.Debugf("User agent got %s: %s", listType, userAgent)
return true
}
}
return false
}

func IsTlsFingerprintBlacklisted(
annotations map[string]string,
parsedClientHello models.ClientHelloParsed,
) bool {
blacklistKeys := []string{Ja3Blacklist, Ja4Blacklist}

for _, key := range blacklistKeys {
if tlsBlacklist, ok := annotations[key]; ok {
for _, tlsHash := range strings.Split(tlsBlacklist, ",") {
if key == Ja3Blacklist {
if tlsHash == parsedClientHello.Ja3 || tlsHash == parsedClientHello.Ja3n {
log.Error("ja3 fingerprint got blacklisted: ", parsedClientHello.Ja3)
return true
}
}

if key == Ja4Blacklist {
if tlsHash == parsedClientHello.Ja4 || tlsHash == parsedClientHello.Ja4h {
log.Error("ja4 fingerprint got blacklisted: ", parsedClientHello.Ja3)
return true
}
}
}
func IsTLSFingerprintAllowed(annotations map[string]string, parsedClientHello models.ClientHelloParsed) bool {
whitelistAnnotation := annotations[TLSFingerprintWhitelist]
blacklistAnnotation := annotations[TLSFingerprintBlackList]

if isTLSFingerprintListed(whitelistAnnotation, parsedClientHello, "whitelist") {
return true
}

if isTLSFingerprintListed(blacklistAnnotation, parsedClientHello, "blacklist") {
return false
}

return len(whitelistAnnotation) == 0
}

func isTLSFingerprintListed(tlsFingerprintList string, parsedClientHello models.ClientHelloParsed, listType string) bool {
if tlsFingerprintList == "" {
return false
}

for _, tlsBlacklistValue := range strings.Split(tlsFingerprintList, ",") {
if tlsBlacklistValue == parsedClientHello.Ja3 || tlsBlacklistValue == parsedClientHello.Ja3n || tlsBlacklistValue == algorithms.Ja3Digest(parsedClientHello.Ja3) {
log.Errorf("Ja3 fingerprint got blacklisted: %s", parsedClientHello.Ja3)
return true
}
if tlsBlacklistValue == parsedClientHello.Ja4 || tlsBlacklistValue == parsedClientHello.Ja4h {
log.Errorf("Ja4 fingerprint got blacklisted: %s", parsedClientHello.Ja4)
return true
}
}

return false
}

func IsPathWhiteListed(annotations map[string]string, path string) bool {
if pathWhitelist, ok := annotations[LimitPathWhitelist]; ok {
for _, parsedPath := range strings.Split(pathWhitelist, ",") {
if strings.HasPrefix(path, parsedPath) {
return true
}
pathWhitelist, ok := annotations[LimitPathWhitelist]
if !ok {
return false
}

for _, parsedPath := range strings.Split(pathWhitelist, ",") {
if strings.HasPrefix(path, parsedPath) {
return true
}
}

return false
}

func IsIpWhitelisted(annotations map[string]string, ip string) bool {
if ipWhitelist, ok := annotations[LimitIpWhitelist]; ok {
for _, parsedIp := range strings.Split(ipWhitelist, ",") {
if parsedIp == ip {
return true
}
ipWhitelist, ok := annotations[LimitIpWhitelist]
if !ok {
return false
}

for _, parsedIP := range strings.Split(ipWhitelist, ",") {
if parsedIP == ip {
return true
}
}

return false
}

func AddJa3Header(annotations map[string]string) bool {
return isHeaderEnabled(annotations, AddJa3HeaderKey)
}

func AddJa4Header(annotations map[string]string) bool {
return isHeaderEnabled(annotations, AddJa4HeaderKey)
func IsTLSFingerprintHeaderRequested(annotations map[string]string) bool {
val, exists := annotations[AddTLSFingerprintHeader]
return exists && val == "true"
}
Loading

0 comments on commit f8dcb07

Please sign in to comment.