Skip to content

Commit

Permalink
remove unnecessary LRU for internode auth token
Browse files Browse the repository at this point in the history
removes contentious usage of mutexes in LRU, which
were never really reused in any manner; we do not
need it.

To trust hosts, the correct way is TLS certs; this PR completely
removes this dependency, which has never been useful.

```
0  0%  100%  25.83s 26.76%  github.com/hashicorp/golang-lru/v2/expirable.(*LRU[...])
0  0%  100%  28.03s 29.04%  github.com/hashicorp/golang-lru/v2/expirable.(*LRU[...])
```

Bonus: use `x-minio-time` as a nanosecond to avoid unnecessary
parsing logic of time strings instead of using a more
straightforward mechanism.
  • Loading branch information
harshavardhana committed Jul 21, 2024
1 parent 23db495 commit 9e8df4d
Show file tree
Hide file tree
Showing 15 changed files with 55 additions and 475 deletions.
394 changes: 0 additions & 394 deletions CREDITS

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions cmd/common-main.go
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 881,12 @@ func loadRootCredentials() {
} else {
globalActiveCred = auth.DefaultCredentials
}

var err error
globalNodeAuthToken, err = authenticateNode(globalActiveCred.AccessKey, globalActiveCred.SecretKey)
if err != nil {
logger.Fatal(err, "Unable to generate internode credentials")
}
}

// Initialize KMS global variable after valiadating and loading the configuration.
Expand Down
1 change: 1 addition & 0 deletions cmd/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 310,7 @@ var (
globalBootTime = UTCNow()

globalActiveCred auth.Credentials
globalNodeAuthToken string
globalSiteReplicatorCred siteReplicatorCred

// Captures if root credentials are set via ENV.
Expand Down
41 changes: 7 additions & 34 deletions cmd/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 24,8 @@ import (

jwtgo "github.com/golang-jwt/jwt/v4"
jwtreq "github.com/golang-jwt/jwt/v4/request"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/minio/minio/internal/auth"
xjwt "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v3/policy"
)

Expand All @@ -37,8 35,8 @@ const (
// Default JWT token for web handlers is one day.
defaultJWTExpiry = 24 * time.Hour

// Inter-node JWT token expiry is 15 minutes.
defaultInterNodeJWTExpiry = 15 * time.Minute
// Inter-node JWT token expiry is 100 years approx.
defaultInterNodeJWTExpiry = 100 * 365 * 24 * time.Hour
)

var (
Expand All @@ -50,17 48,10 @@ var (
errMalformedAuth = errors.New("Malformed authentication input")
)

type cacheKey struct {
accessKey, secretKey, audience string
}

var cacheLRU = expirable.NewLRU[cacheKey, string](1000, nil, 15*time.Second)

func authenticateNode(accessKey, secretKey, audience string) (string, error) {
func authenticateNode(accessKey, secretKey string) (string, error) {
claims := xjwt.NewStandardClaims()
claims.SetExpiry(UTCNow().Add(defaultInterNodeJWTExpiry))
claims.SetAccessKey(accessKey)
claims.SetAudience(audience)

jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
return jwt.SignedString([]byte(secretKey))
Expand Down Expand Up @@ -141,27 132,9 @@ func metricsRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, []string, b
return claims, groups, owner, nil
}

// newCachedAuthToken returns a token that is cached up to 15 seconds.
// If globalActiveCred is updated it is reflected at once.
func newCachedAuthToken() func(audience string) string {
fn := func(accessKey, secretKey, audience string) (s string, err error) {
k := cacheKey{accessKey: accessKey, secretKey: secretKey, audience: audience}

var ok bool
s, ok = cacheLRU.Get(k)
if !ok {
s, err = authenticateNode(accessKey, secretKey, audience)
if err != nil {
return "", err
}
cacheLRU.Add(k, s)
}
return s, nil
}
return func(audience string) string {
cred := globalActiveCred
token, err := fn(cred.AccessKey, cred.SecretKey, audience)
logger.CriticalIf(GlobalContext, err)
return token
// newCachedAuthToken returns the cached token.
func newCachedAuthToken() func() string {
return func() string {
return globalNodeAuthToken
}
}
8 changes: 4 additions & 4 deletions cmd/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 107,7 @@ func BenchmarkParseJWTStandardClaims(b *testing.B) {
}

creds := globalActiveCred
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
if err != nil {
b.Fatal(err)
}
Expand Down Expand Up @@ -138,7 138,7 @@ func BenchmarkParseJWTMapClaims(b *testing.B) {
}

creds := globalActiveCred
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
if err != nil {
b.Fatal(err)
}
Expand Down Expand Up @@ -176,15 176,15 @@ func BenchmarkAuthenticateNode(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i {
fn(creds.AccessKey, creds.SecretKey, "aud")
fn(creds.AccessKey, creds.SecretKey)
}
})
b.Run("cached", func(b *testing.B) {
fn := newCachedAuthToken()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i {
fn("aud")
fn()
}
})
}
2 changes: 1 addition & 1 deletion cmd/lock-rest-server-common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 44,7 @@ func createLockTestServer(ctx context.Context, t *testing.T) (string, *lockRESTS
},
}
creds := globalActiveCred
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
if err != nil {
t.Fatal(err)
}
Expand Down
9 changes: 4 additions & 5 deletions cmd/object-lambda-handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 19,7 @@ package cmd

import (
"crypto/subtle"
"encoding/hex"
"io"
"net/http"
"net/url"
Expand All @@ -33,6 34,7 @@ import (

"github.com/minio/minio/internal/auth"
levent "github.com/minio/minio/internal/config/lambda/event"
"github.com/minio/minio/internal/hash/sha256"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
)
Expand Down Expand Up @@ -77,16 79,13 @@ func getLambdaEventData(bucket, object string, cred auth.Credentials, r *http.Re
return levent.Event{}, err
}

token, err := authenticateNode(cred.AccessKey, cred.SecretKey, u.RawQuery)
if err != nil {
return levent.Event{}, err
}
ckSum := sha256.Sum256([]byte(cred.AccessKey u.RawQuery))

eventData := levent.Event{
GetObjectContext: &levent.GetObjectContext{
InputS3URL: u.String(),
OutputRoute: shortuuid.New(),
OutputToken: token,
OutputToken: hex.EncodeToString(ckSum[:]),
},
UserRequest: levent.UserRequest{
URL: r.URL.String(),
Expand Down
19 changes: 10 additions & 9 deletions cmd/storage-rest-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 110,7 @@ func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error)
const DefaultSkewTime = 15 * time.Minute

// validateStorageRequestToken will validate the token against the provided audience.
func validateStorageRequestToken(token, audience string) error {
func validateStorageRequestToken(token string) error {
claims := xjwt.NewStandardClaims()
if err := xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
return errAuthentication
Expand All @@ -121,9 121,6 @@ func validateStorageRequestToken(token, audience string) error {
return errAuthentication
}

if claims.Audience != audience {
return errAuthentication
}
return nil
}

Expand All @@ -136,20 133,24 @@ func storageServerRequestValidate(r *http.Request) error {
}
return errMalformedAuth
}
if err = validateStorageRequestToken(token, r.URL.RawQuery); err != nil {

if err = validateStorageRequestToken(token); err != nil {
return err
}

requestTimeStr := r.Header.Get("X-Minio-Time")
requestTime, err := time.Parse(time.RFC3339, requestTimeStr)
nanoTime, err := strconv.ParseInt(r.Header.Get("X-Minio-Time"), 10, 64)
if err != nil {
return errMalformedAuth
}
utcNow := UTCNow()
delta := requestTime.Sub(utcNow)

localTime := UTCNow()
remoteTime := time.Unix(0, nanoTime)

delta := remoteTime.Sub(localTime)
if delta < 0 {
delta *= -1
}

if delta > DefaultSkewTime {
return errSkewedAuthTime
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 32,6 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/gomodule/redigo v1.9.2
github.com/google/uuid v1.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/inconshreveable/mousetrap v1.1.0
github.com/json-iterator/go v1.1.12
github.com/klauspost/compress v1.17.9
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 322,6 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6 9jd8WM5uGBxy E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk 7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/raft v1.3.9 h1:9yuo1aR0bFTr1cw7pj3S2Bk6MhJCsnr2NAxvIBrP2x4=
github.com/hashicorp/raft v1.3.9/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q 7HVbyzqM=
Expand Down
10 changes: 5 additions & 5 deletions internal/grid/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 169,13 @@ func dummyRequestValidate(r *http.Request) error {
return nil
}

func dummyTokenValidate(token, audience string) error {
if token == audience {
func dummyTokenValidate(token string) error {
if token == "" {
return nil
}
return fmt.Errorf("invalid token. want %s, got %s", audience, token)
return fmt.Errorf("invalid token. want empty, got %s", token)
}

func dummyNewToken(audience string) string {
return audience
func dummyNewToken() string {
return ""
}
7 changes: 4 additions & 3 deletions internal/grid/grid.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 26,7 @@ import (
"io"
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -208,8 209,8 @@ func ConnectWS(dial ContextDialer, auth AuthFn, tls *tls.Config) func(ctx contex
dialer.NetDial = dial
}
header := make(http.Header, 2)
header.Set("Authorization", "Bearer " auth(""))
header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
header.Set("Authorization", "Bearer " auth())
header.Set("X-Minio-Time", strconv.FormatInt(time.Now().UnixNano(), 10))

if len(header) > 0 {
dialer.Header = ws.HandshakeHeaderHTTP(header)
Expand All @@ -225,4 226,4 @@ func ConnectWS(dial ContextDialer, auth AuthFn, tls *tls.Config) func(ctx contex
}

// ValidateTokenFn must validate the token and return an error if it is invalid.
type ValidateTokenFn func(token, audience string) error
type ValidateTokenFn func(token string) error
6 changes: 3 additions & 3 deletions internal/grid/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 245,7 @@ func (m *Manager) IncomingConn(ctx context.Context, conn net.Conn) {
writeErr(fmt.Errorf("time difference too large between servers: %v", time.Since(cReq.Time).Abs()))
return
}
if err := m.authToken(cReq.Token, cReq.audience()); err != nil {
if err := m.authToken(cReq.Token); err != nil {
writeErr(fmt.Errorf("auth token: %w", err))
return
}
Expand All @@ -257,10 257,10 @@ func (m *Manager) IncomingConn(ctx context.Context, conn net.Conn) {
}

// AuthFn should provide an authentication string for the given aud.
type AuthFn func(aud string) string
type AuthFn func() string

// ValidateAuthFn should check authentication for the given aud.
type ValidateAuthFn func(auth, aud string) string
type ValidateAuthFn func(auth string) string

// Connection will return the connection for the specified host.
// If the host does not exist nil will be returned.
Expand Down
7 changes: 1 addition & 6 deletions internal/grid/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 262,9 @@ type connectReq struct {
Token string
}

// audience returns the audience for the connect call.
func (c *connectReq) audience() string {
return fmt.Sprintf("%s-%d", c.Host, c.Time.Unix())
}

// addToken will add the token to the connect request.
func (c *connectReq) addToken(fn AuthFn) {
c.Token = fn(c.audience())
c.Token = fn()
}

func (connectReq) Op() Op {
Expand Down
17 changes: 9 additions & 8 deletions internal/rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 28,7 @@ import (
"net/http/httputil"
"net/url"
"path"
"strconv"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -95,9 96,9 @@ type Client struct {
// TraceOutput will print debug information on non-200 calls if set.
TraceOutput io.Writer // Debug trace output

httpClient *http.Client
url *url.URL
newAuthToken func(audience string) string
httpClient *http.Client
url *url.URL
auth func() string

sync.RWMutex // mutex for lastErr
lastErr error
Expand Down Expand Up @@ -188,10 189,10 @@ func (c *Client) newRequest(ctx context.Context, u url.URL, body io.Reader) (*ht
}
}

if c.newAuthToken != nil {
req.Header.Set("Authorization", "Bearer " c.newAuthToken(u.RawQuery))
if c.auth != nil {
req.Header.Set("Authorization", "Bearer " c.auth())
}
req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
req.Header.Set("X-Minio-Time", strconv.FormatInt(time.Now().UnixNano(), 10))

if tc, ok := ctx.Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt); ok {
req.Header.Set(xhttp.AmzRequestID, tc.AmzReqID)
Expand Down Expand Up @@ -387,7 388,7 @@ func (c *Client) Close() {
}

// NewClient - returns new REST client.
func NewClient(uu *url.URL, tr http.RoundTripper, newAuthToken func(aud string) string) *Client {
func NewClient(uu *url.URL, tr http.RoundTripper, auth func() string) *Client {
connected := int32(online)
urlStr := uu.String()
u, err := url.Parse(urlStr)
Expand All @@ -404,7 405,7 @@ func NewClient(uu *url.URL, tr http.RoundTripper, newAuthToken func(aud string)
clnt := &Client{
httpClient: &http.Client{Transport: tr},
url: u,
newAuthToken: newAuthToken,
auth: auth,
connected: connected,
lastConn: time.Now().UnixNano(),
MaxErrResponseSize: 4096,
Expand Down

0 comments on commit 9e8df4d

Please sign in to comment.