Skip to content

Commit 4b968dd

Browse files
committed
fixes
1 parent 9bd47bf commit 4b968dd

File tree

5 files changed

+101
-295
lines changed

5 files changed

+101
-295
lines changed

.traefik.yml

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
displayName: Fifteen
1+
displayName: JWT Field as Header
22
type: middleware
33
iconPath: .assets/icon.png
44

@@ -7,9 +7,10 @@ import: github.com/birotaio/traefik-plugins
77
summary: 'Make custom header from JWT data, can be used for user-based ratelimiting'
88

99
testData:
10-
jwt-header-name: X-ApiKey
11-
jwt-field: customer_id
12-
value-header-name: X-UserId-RateLimit
13-
fallback-type: ip
10+
jwtHeaderName: X-ApiKey
11+
jwtField: customer_id
12+
valueHeaderName: X-UserId-RateLimit
13+
fallbacks:
14+
- type: ip
1415
debug: true
1516

Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM traefik:v2.10.4
2+
COPY . /plugins-local/src/github.com/birotaio/traefik-plugins/

fifteen.go

+72-24
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,33 @@ import (
55
"fmt"
66
"net/http"
77
"os"
8+
"strings"
89

910
"github.com/golang-jwt/jwt/v5"
1011
)
1112

12-
type Fallback string
13+
type FallbackType string
1314

1415
const (
15-
FallbackError Fallback = "error"
16-
FallbackPass Fallback = "pass"
17-
FallbackIp Fallback = "ip"
18-
FallbackHeader Fallback = "header"
16+
FallbackError FallbackType = "error"
17+
FallbackPass FallbackType = "pass"
18+
FallbackIp FallbackType = "ip"
19+
FallbackHeader FallbackType = "header"
1920
)
2021

22+
type Fallback struct {
23+
Type FallbackType `yaml:"type,omitempty"`
24+
Value string `yaml:"value,omitempty"`
25+
KeepIfEmpty bool `yaml:"keepIfEmpty,omitempty"`
26+
}
27+
2128
// Config the plugin configuration.
2229
type Config struct {
23-
JwtHeaderName string `json:"jwt-header-name,omitempty"`
24-
JwtField string `json:"jwt-field,omitempty"`
25-
ValueHeaderName string `json:"value-header-name,omitempty"`
26-
FallbackType Fallback `json:"fallback-type,omitempty"`
27-
FallbackHeaderName string `json:"fallback-header-name,omitempty"`
28-
Debug bool `json:"debug,omitempty"`
30+
JwtHeaderName string `yaml:"jwtHeaderName,omitempty"`
31+
JwtField string `yaml:"jwtField,omitempty"`
32+
ValueHeaderName string `yaml:"valueHeaderName,omitempty"`
33+
Fallbacks []Fallback `yaml:"fallbacks,omitempty"`
34+
Debug bool `yaml:"debug,omitempty"`
2935
}
3036

3137
// CreateConfig creates the default plugin configuration.
@@ -56,7 +62,11 @@ func (a *Fifteen) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
5662
return
5763
}
5864

59-
rawToken := req.Header.Get(a.cfg.JwtHeaderName)
65+
rawHeader := req.Header.Get(a.cfg.JwtHeaderName)
66+
rawToken := ""
67+
if strings.HasPrefix(rawHeader, "Bearer ") {
68+
rawToken = rawHeader[len("Bearer "):]
69+
}
6070
parsedToken, _, err := jwt.NewParser().ParseUnverified(rawToken, jwt.MapClaims{})
6171
if err != nil {
6272
a.logDebug("Could not parse non-empty jwt token, falling back: %s", err.Error())
@@ -84,26 +94,52 @@ func (a *Fifteen) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
8494
return
8595
}
8696
} else {
87-
a.logDebug("JWT field value has an unexpected type, falling back")
97+
a.logDebug("JWT field value does not hold field %s, falling back", a.cfg.JwtField)
8898
a.ServeFallback(rw, req)
8999
return
90100
}
91101

92-
a.next.ServeHTTP(rw, req)
102+
a.end(rw, req)
93103
}
94104

95105
func (a *Fifteen) ServeFallback(rw http.ResponseWriter, req *http.Request) {
96-
a.logDebug("Fallbacked because JWT was not set, invalid or has unexpected value on field. Using fallback strategy: %s", a.cfg.FallbackType)
97-
switch a.cfg.FallbackType {
98-
case FallbackError:
99-
rw.WriteHeader(http.StatusBadRequest)
100-
case FallbackIp:
101-
req.Header.Set(a.cfg.ValueHeaderName, req.RemoteAddr)
102-
case FallbackHeader:
103-
req.Header.Set(a.cfg.ValueHeaderName, req.Header.Get(a.cfg.FallbackHeaderName))
104-
default:
105-
a.next.ServeHTTP(rw, req)
106+
if len(a.cfg.Fallbacks) == 0 {
107+
a.logDebug("Fallbacked because JWT was not set, invalid or has unexpected value on field. No fallback strategies, ignoring...")
108+
} else {
109+
a.logDebug("Fallbacked because JWT was not set, invalid or has unexpected value on field. Finding right fallback strategy")
110+
for i, fallback := range a.cfg.Fallbacks {
111+
a.logDebug("Strategy %d: %+v", i, fallback)
112+
var success bool
113+
switch fallback.Type {
114+
case FallbackError:
115+
rw.Header().Set("Content-Type", "text/plain")
116+
rw.WriteHeader(http.StatusBadRequest)
117+
rw.Write([]byte("Bad request"))
118+
return
119+
case FallbackPass:
120+
a.logDebug("Passing through")
121+
success = true
122+
case FallbackIp:
123+
req.Header.Set(a.cfg.ValueHeaderName, ipWithNoPort(req.RemoteAddr))
124+
success = true
125+
case FallbackHeader:
126+
headerValue := req.Header.Get(fallback.Value)
127+
if headerValue == "" && !fallback.KeepIfEmpty {
128+
a.logDebug("Header %s was empty, skipping...", fallback.Value)
129+
continue
130+
}
131+
req.Header.Set(a.cfg.ValueHeaderName, headerValue)
132+
success = true
133+
default:
134+
a.logDebug("Unknown fallback type, skipping...")
135+
}
136+
if success {
137+
a.logDebug("Fallback strategy %d was successful", i)
138+
break
139+
}
140+
}
106141
}
142+
a.end(rw, req)
107143
}
108144

109145
func (a *Fifteen) logDebug(format string, args ...any) {
@@ -112,3 +148,15 @@ func (a *Fifteen) logDebug(format string, args ...any) {
112148
}
113149
os.Stderr.WriteString("[Fifteen middleware]: " + fmt.Sprintf(format, args...) + "\n")
114150
}
151+
152+
func (a *Fifteen) end(rw http.ResponseWriter, req *http.Request) {
153+
a.logDebug("ending with request headers: %+v", req.Header)
154+
a.next.ServeHTTP(rw, req)
155+
}
156+
157+
func ipWithNoPort(addr string) string {
158+
if colon := strings.LastIndex(addr, ":"); colon != -1 {
159+
return addr[:colon]
160+
}
161+
return addr
162+
}

fifteen_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ func TestDemo(t *testing.T) {
1212
cfg.JwtHeaderName = "X-ApiKey"
1313
cfg.JwtField = "customer_id"
1414
cfg.ValueHeaderName = "X-UserId-RateLimit"
15-
cfg.FallbackType = FallbackIp
15+
cfg.Fallbacks = []Fallback{
16+
{Type: FallbackIp},
17+
}
1618
cfg.Debug = false
1719

1820
ctx := context.Background()

0 commit comments

Comments
 (0)