Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1b11727
feat(proxyd): add external authentication support and refactor auth h…
aayushijain21 Mar 13, 2025
23186fb
fix(proxyd): add staticcheck directive to suppress lint warning for c…
aayushijain21 Mar 13, 2025
532924c
feat(proxyd): implement stub authentication for testing purposes
aayushijain21 Mar 14, 2025
aa9a249
Merge branch 'ethereum-optimism:main' into main
aayushijain21 Mar 17, 2025
cc9cff4
feat(proxyd): enhance logging for authentication processes and change…
aayushijain21 Mar 17, 2025
0ca4f1a
refactor(proxyd): change log level to info and update logging stateme…
aayushijain21 Mar 17, 2025
728028e
refactor(proxyd): improve logging statements for context population a…
aayushijain21 Mar 17, 2025
03f63b6
refactor(proxyd): streamline authentication handling and improve logg…
aayushijain21 Mar 17, 2025
3a626d8
refactor(proxyd): enhance authentication logging and streamline auth_…
aayushijain21 Mar 17, 2025
5423883
refactor: add more debugging logs
aayushijain21 Mar 17, 2025
befee13
minor log change
aayushijain21 Mar 17, 2025
1053ebb
feat(proxyd): change legacy authentication checks to fail open
aayushijain21 Mar 17, 2025
bdd2e05
simplify resolvedAuth map logic
aayushijain21 Mar 17, 2025
84892c1
refactor(proxyd): enhance logging in populateContext for better debug…
aayushijain21 Mar 18, 2025
3753391
refactor(proxyd): initialize resolvedAuth map and improve logging for…
aayushijain21 Mar 18, 2025
75404f9
feat(proxyd): add callback to performAuthCallback
aayushijain21 Mar 18, 2025
ab4b5c6
refactor(proxyd): update authorization header format in performAuthCa…
aayushijain21 Mar 18, 2025
ebb40bb
refactor(proxyd): performAuthCallback sends request to middleware
aayushijain21 Mar 24, 2025
9d00e37
refactor(proxyd): streamline logging in Start and populateContext fun…
aayushijain21 Mar 25, 2025
af6a8c6
logging
aayushijain21 Mar 25, 2025
d14dd26
remove logs
aayushijain21 Mar 25, 2025
32e5557
generalize performAuthCallback
aayushijain21 Mar 26, 2025
9ee8d62
add debugging logs
aayushijain21 Mar 26, 2025
0c782e3
log
aayushijain21 Mar 26, 2025
9daebd9
fix authurl to add token
aayushijain21 Mar 26, 2025
57f3093
debugging
aayushijain21 Mar 26, 2025
158c5fb
remove logs
aayushijain21 Mar 26, 2025
bd71e88
add http client
aayushijain21 Mar 26, 2025
2f18883
remove logs
aayushijain21 Mar 26, 2025
69b1dda
add httpclient to server
aayushijain21 Mar 26, 2025
df4b929
remove other client
aayushijain21 Mar 26, 2025
d6a1393
remove fn
aayushijain21 Mar 26, 2025
243f165
jsonrpc error
aayushijain21 Apr 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions proxyd/proxyd.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ func Start(config *Config) (*Server, func(), error) {
return nil, nil, errors.New("must define at least one RPC method mapping")
}

for authKey := range config.Authentication {
if authKey == "none" {
return nil, nil, errors.New("cannot use none as an auth key")
}
}

// redis primary client
var redisClient redis.UniversalClient
if config.Redis.URL != "" {
Expand Down Expand Up @@ -193,7 +187,6 @@ func Start(config *Config) (*Server, func(), error) {
opts = append(opts, WithStrippedTrailingXFF())
}
opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP")))
opts = append(opts, WithSkipIsSyncingCheck(cfg.SkipIsSyncingCheck))
opts = append(opts, WithConsensusSkipPeerCountCheck(cfg.ConsensusSkipPeerCountCheck))
opts = append(opts, WithConsensusForcedCandidate(cfg.ConsensusForcedCandidate))
opts = append(opts, WithWeight(cfg.Weight))
Expand Down Expand Up @@ -285,16 +278,22 @@ func Start(config *Config) (*Server, func(), error) {
}
}

var resolvedAuth map[string]string
var resolvedAuth map[string]string = make(map[string]string)

if config.Authentication != nil {
resolvedAuth = make(map[string]string)
for secret, alias := range config.Authentication {
resolvedSecret, err := ReadFromEnvOrConfig(secret)
if authURL, ok := config.Authentication["auth_url"]; ok {
resolvedAuth["auth_url"] = authURL
}
for key, alias := range config.Authentication {
if key == "auth_url" {
continue
}

resolvedKey, err := ReadFromEnvOrConfig(key)
if err != nil {
return nil, nil, err
}
resolvedAuth[resolvedSecret] = alias
resolvedAuth[resolvedKey] = alias
}
}

Expand Down
85 changes: 73 additions & 12 deletions proxyd/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package proxyd

import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
Expand Down Expand Up @@ -50,6 +51,13 @@ const (

var emptyArrayResponse = json.RawMessage("[]")

type AuthCallbackRequest struct {
Headers map[string][]string `json:"headers"`
Path string `json:"path"`
Body string `json:"body"`
RemoteAddr string `json:"remote_addr"`
}

type Server struct {
BackendGroups map[string]*BackendGroup
wsBackendGroup *BackendGroup
Expand All @@ -76,6 +84,7 @@ type Server struct {
cache RPCCache
srvMu sync.Mutex
rateLimitHeader string
authClient *http.Client
}

type limiterFunc func(method string) bool
Expand All @@ -100,6 +109,7 @@ func NewServer(
maxBatchSize int,
limiterFactory limiterFactoryFunc,
) (*Server, error) {

if cache == nil {
cache = &NoopRPCCache{}
}
Expand Down Expand Up @@ -191,6 +201,9 @@ func NewServer(
limExemptOrigins: limExemptOrigins,
limExemptUserAgents: limExemptUserAgents,
rateLimitHeader: rateLimitHeader,
authClient: &http.Client{
Timeout: 5 * time.Second,
},
}, nil
}

Expand Down Expand Up @@ -614,6 +627,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
vars := mux.Vars(r)
authorization := vars["authorization"]
xff := r.Header.Get(s.rateLimitHeader)

if xff == "" {
ipPort := strings.Split(r.RemoteAddr, ":")
if len(ipPort) == 2 {
Expand All @@ -628,22 +642,70 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
ctx = context.WithValue(ctx, ContextKeyOpTxProxyAuth, opTxProxyAuth) // nolint:staticcheck
}

if len(s.authenticatedPaths) > 0 {
if authorization == "" || s.authenticatedPaths[authorization] == "" {
log.Info("blocked unauthorized request", "authorization", authorization)
httpResponseCodesTotal.WithLabelValues("401").Inc()
w.WriteHeader(401)
// Check if we have an external auth URL configured
authURL, hasExternalAuth := s.authenticatedPaths["auth_url"]
if hasExternalAuth && authURL != "" { // nolint:staticcheck
// Use external authentication service
alias, err := s.performAuthCallback(r, authURL)
if err != nil || alias == "" { // Check both error and empty alias
writeRPCError(ctx, w, nil, &RPCErr{Code: -32001, Message: "unauthorized"})
return nil
}

ctx = context.WithValue(ctx, ContextKeyAuth, alias) // nolint:staticcheck
} else {
ctx = context.WithValue(ctx, ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck
}
return context.WithValue(ctx, ContextKeyReqID, randStr(10)) // nolint:staticcheck
}

return context.WithValue(
ctx,
ContextKeyReqID, // nolint:staticcheck
randStr(10),
)
func (s *Server) performAuthCallback(r *http.Request, authURL string) (string, error) {
// Get the authorization token from the request
authorization := mux.Vars(r)["authorization"]
if authorization == "" {
return "", fmt.Errorf("missing authorization token")
}

// Read the body first
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
log.Error("performAuthCallback failed to read request body", "err", err)
return "", fmt.Errorf("failed to read request body: %w", err)
}
defer r.Body.Close()

// Create new body for original request
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

// Append the token to the auth URL path
authURLWithToken := fmt.Sprintf("%s/%s",
strings.TrimRight(authURL, "/"),
authorization)

// Create new request to auth URL with same method, headers and new body copy
req, err := http.NewRequestWithContext(r.Context(), r.Method, authURLWithToken,
bytes.NewBuffer(bodyBytes))
if err != nil {
log.Error("performAuthCallback failed to create request", "err", err)
return "", fmt.Errorf("failed to create auth request: %w", err)
}

// Copy original headers
req.Header = r.Header.Clone()

// Use the server's auth client
resp, err := s.authClient.Do(req)
if err != nil {
log.Error("performAuthCallback request failed", "err", err)
return "", fmt.Errorf("auth callback failed: %w", err)
}
defer resp.Body.Close()

// Only reject if we get a 401
if resp.StatusCode == http.StatusUnauthorized {
return "", fmt.Errorf("unauthorized")
}

return authorization, nil
}

func randStr(l int) string {
Expand Down Expand Up @@ -805,7 +867,6 @@ func GetAuthCtx(ctx context.Context) string {
if !ok {
return "none"
}

return authUser
}

Expand Down