mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-09-08 17:36:39 +08:00
S3 signature V4 verify
This commit is contained in:
@@ -3,6 +3,7 @@ module github.com/DullJZ/s3-balance
|
|||||||
go 1.24.5
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/DullJZ/s3-validate v0.0.0-20250930120412-fc4ea70939f6
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.2
|
github.com/aws/aws-sdk-go-v2 v1.39.2
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.31.1
|
github.com/aws/aws-sdk-go-v2/config v1.31.1
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.5
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.5
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/DullJZ/s3-validate v0.0.0-20250930120412-fc4ea70939f6 h1:UZ4i/MFU0ttINUch3GYyJayq6Y2ODm+RPawLgPna5L8=
|
||||||
|
github.com/DullJZ/s3-validate v0.0.0-20250930120412-fc4ea70939f6/go.mod h1:OEx+/bRlDdI0oj/Bb1Plsq+1+qU1qal3/g9phixhU6Y=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
|
|||||||
return ok
|
return ok
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
protected.Use(middleware.BasicAuth(middleware.AuthConfig{
|
protected.Use(middleware.S3Signature(middleware.S3SignatureConfig{
|
||||||
Required: h.authRequired,
|
Required: h.authRequired,
|
||||||
Credentials: h.credentials,
|
Credentials: h.credentials,
|
||||||
OnError: h.sendS3Error,
|
OnError: h.sendS3Error,
|
||||||
|
|||||||
+32
-40
@@ -1,20 +1,41 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
"github.com/DullJZ/s3-validate/pkg/s3validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthConfig controls Basic Auth validation.
|
// S3SignatureConfig controls S3 signature validation.
|
||||||
type AuthConfig struct {
|
type S3SignatureConfig struct {
|
||||||
Required func() bool
|
Required func() bool
|
||||||
Credentials func() (string, string)
|
Credentials func() (string, string)
|
||||||
OnError func(http.ResponseWriter, string, string, string)
|
OnError func(http.ResponseWriter, string, string, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BasicAuth enforces static access/secret key authentication when required.
|
// credentialsProvider implements s3validate.CredentialsProvider interface.
|
||||||
func BasicAuth(cfg AuthConfig) func(http.Handler) http.Handler {
|
type credentialsProvider struct {
|
||||||
|
getCredentials func() (string, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *credentialsProvider) SecretKey(ctx context.Context, accessKey string) (string, error) {
|
||||||
|
expectedAccessKey, secretKey := p.getCredentials()
|
||||||
|
if accessKey != expectedAccessKey {
|
||||||
|
return "", fmt.Errorf("invalid access key")
|
||||||
|
}
|
||||||
|
return secretKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// S3Signature enforces AWS Signature V4 authentication when required.
|
||||||
|
func S3Signature(cfg S3SignatureConfig) func(http.Handler) http.Handler {
|
||||||
|
verifier := &s3validate.Verifier{
|
||||||
|
Credentials: &credentialsProvider{
|
||||||
|
getCredentials: cfg.Credentials,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
required := false
|
required := false
|
||||||
@@ -26,50 +47,21 @@ func BasicAuth(cfg AuthConfig) func(http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authHeader := r.Header.Get("Authorization")
|
result, err := verifier.Verify(r.Context(), r)
|
||||||
if !strings.HasPrefix(authHeader, "Basic ") {
|
|
||||||
requireAuth(w, cfg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := strings.TrimPrefix(authHeader, "Basic ")
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
requireAuth(w, cfg)
|
invokeOnError(w, cfg, "SignatureDoesNotMatch", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(string(decoded), ":", 2)
|
// Store verification result in context for potential use by handlers
|
||||||
if len(parts) != 2 {
|
_ = result
|
||||||
requireAuth(w, cfg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accessKey, secretKey := "", ""
|
|
||||||
if cfg.Credentials != nil {
|
|
||||||
accessKey, secretKey = cfg.Credentials()
|
|
||||||
}
|
|
||||||
|
|
||||||
if parts[0] != accessKey {
|
|
||||||
invokeOnError(w, cfg, "InvalidAccessKeyId", "The AWS Access Key Id you provided does not match the configured key.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if parts[1] != secretKey {
|
|
||||||
invokeOnError(w, cfg, "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireAuth(w http.ResponseWriter, cfg AuthConfig) {
|
func invokeOnError(w http.ResponseWriter, cfg S3SignatureConfig, code, message string) {
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"s3-balance\"")
|
|
||||||
invokeOnError(w, cfg, "AccessDenied", "Access Denied")
|
|
||||||
}
|
|
||||||
|
|
||||||
func invokeOnError(w http.ResponseWriter, cfg AuthConfig, code, message string) {
|
|
||||||
if cfg.OnError != nil {
|
if cfg.OnError != nil {
|
||||||
cfg.OnError(w, code, message, "")
|
cfg.OnError(w, code, message, "")
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user