8 Commits

Author SHA1 Message Date
Milo Schwartz
67ad9b651e Merge pull request #19 from LaurenceJJones/strip-session-cookie
enhance: strip user session information before forwarding
2026-03-17 17:07:55 -07:00
miloschwartz
f220c75a52 use access token params from config 2026-03-17 16:59:32 -07:00
Laurence
d81af67db3 fix: traefik doesnt know cookies helpers 2026-03-03 19:03:08 +00:00
Laurence
2902340f7b fix: handle other fields and ensure cookie coverage 2026-03-03 17:51:16 +00:00
Laurence
877363a686 Strip session cookie and query param before forwarding to backend
- Add stripSessionCookies() to remove session cookies from Cookie header
  - Add stripSessionParam() to remove session exchange query parameter from URL
  - Call both before forwarding request to backend on valid sessions
  - Backend now only receives user identity via Remote-* headers
2026-03-02 16:48:46 +00:00
Milo Schwartz
e0a2d76844 Merge pull request #18 from fosrl/dev
1.3.1
2025-12-22 11:02:34 -08:00
miloschwartz
be5015cc2a Merge branch 'main' into dev 2025-12-22 14:01:57 -05:00
miloschwartz
f00a92c7af send badger version in verify session request 2025-12-22 12:41:15 -05:00
3 changed files with 78 additions and 14 deletions

View File

@@ -70,20 +70,6 @@ trustip:
customIPHeader: "X-Forwarded-For"
```
### Configuration Options Reference
| Option | Type | Required\* | Default | Description |
| ----------------------------- | -------- | ---------- | ------- | ----------------------------------------------------------------------------------- |
| `disableForwardAuth` | bool | No | `false` | Disable forward auth; only IP handling is performed |
| `apiBaseUrl` | string | Yes\* | - | Base URL of the Pangolin API |
| `userSessionCookieName` | string | Yes\* | - | Cookie name for user sessions |
| `resourceSessionRequestParam` | string | Yes\* | - | Query parameter name for resource session requests |
| `trustip` | []string | No | `[]` | Array of trusted IP ranges in CIDR format |
| `disableDefaultCFIPs` | bool | No | `false` | Disable default Cloudflare IP ranges |
| `customIPHeader` | string | No | `""` | Custom header name to extract IP from (only used if request is from trusted source) |
\* Required only when `disableForwardAuth` is `false` (default)
## Updating Cloudflare IPs
To update the Cloudflare IP ranges, run:

75
main.go
View File

@@ -10,12 +10,16 @@ import (
"strings"
"github.com/fosrl/badger/ips"
"github.com/fosrl/badger/version"
)
type Config struct {
APIBaseUrl string `json:"apiBaseUrl,omitempty"`
UserSessionCookieName string `json:"userSessionCookieName,omitempty"`
ResourceSessionRequestParam string `json:"resourceSessionRequestParam,omitempty"`
AccessTokenQueryParam string `json:"accessTokenQueryParam,omitempty"`
AccessTokenIDHeader string `json:"accessTokenIdHeader,omitempty"`
AccessTokenHeader string `json:"accessTokenHeader,omitempty"`
DisableForwardAuth bool `json:"disableForwardAuth,omitempty"`
TrustIP []string `json:"trustip,omitempty"`
DisableDefaultCFIPs bool `json:"disableDefaultCFIPs,omitempty"`
@@ -36,6 +40,9 @@ type Badger struct {
apiBaseUrl string
userSessionCookieName string
resourceSessionRequestParam string
accessTokenQueryParam string
accessTokenIDHeader string
accessTokenHeader string
disableForwardAuth bool
trustIP []*net.IPNet
customIPHeader string
@@ -52,6 +59,7 @@ type VerifyBody struct {
RequestIP *string `json:"requestIp,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Query map[string]string `json:"query,omitempty"`
BadgerVersion string `json:"badgerVersion,omitempty"`
}
type VerifyResponse struct {
@@ -64,6 +72,7 @@ type VerifyResponse struct {
Name *string `json:"name,omitempty"`
Role *string `json:"role,omitempty"`
ResponseHeaders map[string]string `json:"responseHeaders,omitempty"`
PangolinVersion *string `json:"pangolinVersion,omitempty"`
} `json:"data"`
}
@@ -92,6 +101,9 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
apiBaseUrl: config.APIBaseUrl,
userSessionCookieName: config.UserSessionCookieName,
resourceSessionRequestParam: config.ResourceSessionRequestParam,
accessTokenQueryParam: config.AccessTokenQueryParam,
accessTokenIDHeader: config.AccessTokenIDHeader,
accessTokenHeader: config.AccessTokenHeader,
disableForwardAuth: config.DisableForwardAuth,
customIPHeader: config.CustomIPHeader,
}
@@ -228,6 +240,7 @@ func (p *Badger) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
RequestIP: &realIP,
Headers: headers,
Query: queryParams,
BadgerVersion: version.Version,
}
jsonData, err := json.Marshal(cookieData)
@@ -308,6 +321,10 @@ func (p *Badger) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
req.Header.Add("Remote-Role", *result.Data.Role)
}
p.stripSessionCookies(req)
p.stripSessionParam(req)
p.stripAccessTokenHeaders(req)
fmt.Println("Badger: Valid session")
p.next.ServeHTTP(rw, req)
return
@@ -410,6 +427,64 @@ func (p *Badger) getRealIP(req *http.Request) string {
return ip
}
func (p *Badger) stripSessionParam(req *http.Request) {
query := req.URL.Query()
modified := false
if query.Has(p.resourceSessionRequestParam) {
query.Del(p.resourceSessionRequestParam)
modified = true
}
if p.accessTokenQueryParam != "" && query.Has(p.accessTokenQueryParam) {
query.Del(p.accessTokenQueryParam)
modified = true
}
if modified {
req.URL.RawQuery = query.Encode()
req.RequestURI = req.URL.RequestURI()
}
}
func (p *Badger) stripAccessTokenHeaders(req *http.Request) {
if p.accessTokenIDHeader != "" {
req.Header.Del(p.accessTokenIDHeader)
}
if p.accessTokenHeader != "" {
req.Header.Del(p.accessTokenHeader)
}
}
// stripSessionCookies removes session cookies from the request before forwarding to the backend.
// It processes raw Cookie header pairs so non-target cookies are preserved as-is.
func (p *Badger) stripSessionCookies(req *http.Request) {
cookieHeaders := req.Header.Values("Cookie")
if len(cookieHeaders) == 0 {
return
}
var remainingPairs []string
for _, headerValue := range cookieHeaders {
for _, part := range strings.Split(headerValue, ";") {
part = strings.TrimSpace(part)
if part == "" {
continue
}
name, _, _ := strings.Cut(part, "=")
name = strings.TrimSpace(name)
if !strings.HasPrefix(name, p.userSessionCookieName) {
remainingPairs = append(remainingPairs, part)
}
}
}
if len(remainingPairs) == 0 {
req.Header.Del("Cookie")
return
}
// Keep a single canonical Cookie header while preserving surviving name=value pairs.
req.Header.Set("Cookie", strings.Join(remainingPairs, "; "))
}
func (p *Badger) isTrustedIP(remoteAddr string) bool {
ipStr, _, err := net.SplitHostPort(remoteAddr)
if err != nil {

3
version/version.go Normal file
View File

@@ -0,0 +1,3 @@
package version
const Version = "1.3.1"