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

Commit

Permalink
core: add force-ssl-redirect annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
h3adex committed Dec 11, 2023
1 parent d2d8c73 commit ff14fdd
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 56 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ full support for all functionalities provided by the Ingress API Object.
- [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
- [x] Force SSL Redirection
- [ ] Identify connections using proxies. Method described in [this paper](https://dl.acm.org/doi/abs/10.1007/978-3-031-21280-2_18)
- [ ] Https redirect/rewrite
- [ ] Install as a Helm Chart

## Images
Expand All @@ -29,6 +29,7 @@ To block requests, utilize specific [annotations](pkg/annotations/annotations.go
- `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/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.
- `guardgress/limit-redis-store-url`: This parameter defines the URL of the Redis store. If left unspecified, the controller resorts to an in-memory store. Redis becomes essential particularly when operating in High Availability (HA) Mode with multiple pods.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
Expand Down
60 changes: 6 additions & 54 deletions go.sum

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions k8s/examples/ingress-force-ssl-redirect.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This ingress will route incoming requests to the shop service on port 8080.
# PathType is set to Prefix, so that requests to /products/abc will be routed to the shop service on /products/abc.
# e.g. curl -k -H "Host: shop.guardgress.com" https://shop.guardgress.com/products/abc -> http://shop:8080/products/abc
# e.g. curl -k -H "Host: shop.guardgress.com" https://shop.guardgress.com/user/login -> http://shop:8080/user/login
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# force SSL redirect e.g. http://shop.guardgress.com https://shop.guardgress.com
"guardgress/force-ssl-redirect": "true"
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
1 change: 1 addition & 0 deletions pkg/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
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"
Expand Down
11 changes: 10 additions & 1 deletion pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,21 @@ func (s Server) ServeHTTP(ctx *gin.Context) {
return
}

svcUrl, _, routingError := s.RoutingTable.GetBackend(
svcUrl, parsedAnnotations, routingError := s.RoutingTable.GetBackend(
ctx.Request.Host,
ctx.Request.RequestURI,
ctx.ClientIP(),
)

if parsedAnnotations[annotations.ForceSSLRedirect] == "true" {
log.Debug("redirecting to https: ", "https://"+ctx.Request.Host+ctx.Request.URL.String())
http.Redirect(ctx.Writer, ctx.Request,
"https://"+ctx.Request.Host+ctx.Request.URL.String(),
http.StatusMovedPermanently,
)
return
}

if routingError.Error != nil {
ctx.Writer.WriteHeader(routingError.StatusCode)
_, _ = ctx.Writer.Write([]byte(routingError.Error.Error()))
Expand Down
53 changes: 53 additions & 0 deletions pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net"
"net/http"
"os"
"strings"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -840,6 +841,58 @@ func TestProxyDirectorParams(t *testing.T) {
})
}

func TestHTTPToHTTPSRedirect(t *testing.T) {
mockServerPort := freePort()
mockServerAddress := fmt.Sprintf("127.0.0.1.default.svc.cluster.local:%d", mockServerPort)
testReverseProxyConfig.Port = freePort()
testReverseProxyConfig.TlsPort = freePort()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

startMockServer(mockServerAddress, ctx)

srv := New(testReverseProxyConfig)

ingressExactPathMock := mocks.IngressExactPathTypeMock()
ingressExactPathMock.Annotations = map[string]string{
"guardgress/force-ssl-redirect": "true",
}
ingressExactPathMock.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Port.Number = int32(mockServerPort)
ingressLimiter := limitHandler.GetIngressLimiter(ingressExactPathMock)

srv.RoutingTable = &router.RoutingTable{
Ingresses: &v1.IngressList{
TypeMeta: v12.TypeMeta{},
ListMeta: v12.ListMeta{},
Items: []v1.Ingress{
ingressExactPathMock,
},
},
TlsCertificates: mocks.TlsCertificatesMock(),
IngressLimiters: []*limiter.Limiter{ingressLimiter},
}

go func() {
srv.Run(ctx)
}()

waitForServer(ctx, testReverseProxyConfig.Port)

// check if https redirect works
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
req, err := http.NewRequest(
"GET",
fmt.Sprintf("http://%s:%d", testReverseProxyConfig.Host, testReverseProxyConfig.Port),
nil,
)
assert.NoError(t, err)
req.Host = srv.RoutingTable.Ingresses.Items[0].Spec.Rules[0].Host

_, err = http.DefaultClient.Do(req)
// Error is expected. Just check if https redirect worked
assert.True(t, strings.ContainsAny("https://127.0.0.1", err.Error()))
}

func startMockServer(addr string, ctx context.Context) *http.Server {
mockSrv := &http.Server{
Addr: addr,
Expand Down

0 comments on commit ff14fdd

Please sign in to comment.