Skip to content

Commit dbe9ff0

Browse files
authored
apiserver: prevent long bouncer names when IP is changing (bouncer@ip1@ip2..) (#3911)
* apiserver: prevent long bouncer names when IP is changing (bouncer@ip1@ip2..) * lint
1 parent e65bb44 commit dbe9ff0

File tree

1 file changed

+32
-1
lines changed

1 file changed

+32
-1
lines changed

pkg/apiserver/middlewares/v1/api_key.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/base64"
77
"fmt"
88
"net/http"
9+
"net/netip"
910
"strings"
1011

1112
"github.com/gin-gonic/gin"
@@ -29,6 +30,31 @@ type APIKey struct {
2930
TlsAuth *TLSAuth
3031
}
3132

33+
// baseBouncerName removes any trailing "@<ip>" segments from a bouncer's name.
34+
//
35+
// When a bouncer changes its IP address, it is detected by LAPI as a new bouncer,
36+
// to allow for key sharing. LAPI then creates a new DB entry by appending "@<ip>"
37+
// to the existing name. If the existing name already ends with "@<ip>", this can lead to
38+
// chained names like "[email protected]@5.6.7.8". To prevent runaway suffixes,
39+
// this helper repeatedly strips the final "@<ip>" token until no valid IPv4/IPv6
40+
// address remains, returning the "base" bouncer name.
41+
func baseBouncerName(name string) string {
42+
for {
43+
i := strings.LastIndexByte(name, '@')
44+
if i < 0 {
45+
return name
46+
}
47+
48+
tail := name[i+1:]
49+
if _, err := netip.ParseAddr(tail); err == nil {
50+
name = name[:i]
51+
continue
52+
}
53+
54+
return name
55+
}
56+
}
57+
3258
func GenerateAPIKey(n int) (string, error) {
3359
bytes := make([]byte, n)
3460
if _, err := rand.Read(bytes); err != nil {
@@ -128,10 +154,12 @@ func (a *APIKey) authPlain(c *gin.Context, logger *log.Entry) *ent.Bouncer {
128154
logger.Errorf("while fetching bouncer info: %s", err)
129155
return nil
130156
}
157+
131158
if len(bouncer) == 0 {
132159
logger.Debugf("no bouncer found with this key")
133160
return nil
134161
}
162+
135163
return bouncer[0]
136164
}
137165

@@ -148,6 +176,7 @@ func (a *APIKey) authPlain(c *gin.Context, logger *log.Entry) *ent.Bouncer {
148176
logger.Errorf("bouncer isn't allowed to auth by API key")
149177
return nil
150178
}
179+
151180
return bouncer
152181
}
153182

@@ -173,7 +202,8 @@ func (a *APIKey) authPlain(c *gin.Context, logger *log.Entry) *ent.Bouncer {
173202

174203
// Bouncers are ordered by ID, first one *should* be the manually created one
175204
// Can probably get a bit weird if the user deletes the manually created one
176-
bouncerName := fmt.Sprintf("%s@%s", bouncers[0].Name, clientIP)
205+
base := baseBouncerName(bouncers[0].Name)
206+
bouncerName := fmt.Sprintf("%s@%s", base, clientIP)
177207

178208
logger.Infof("Creating bouncer %s", bouncerName)
179209

@@ -224,6 +254,7 @@ func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
224254
logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
225255
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
226256
c.Abort()
257+
227258
return
228259
}
229260
}

0 commit comments

Comments
 (0)