Răsfoiți Sursa

Security -> Checker

Viktor Sokolov 3 săptămâni în urmă
părinte
comite
59481026cf

+ 1 - 1
handlers/processing/handler.go

@@ -25,7 +25,7 @@ type HandlerContext interface {
 	FallbackImage() auximageprovider.Provider
 	WatermarkImage() auximageprovider.Provider
 	ImageDataFactory() *imagedata.Factory
-	Security() *security.Security
+	Security() *security.Checker
 }
 
 // Handler handles image processing requests

+ 15 - 0
imagedata/errors.go

@@ -2,11 +2,26 @@ package imagedata
 
 import (
 	"fmt"
+	"net/http"
 
 	"github.com/imgproxy/imgproxy/v3/fetcher"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 )
 
+type FileSizeError struct{}
+
+func newFileSizeError() error {
+	return ierrors.Wrap(
+		FileSizeError{},
+		1,
+		ierrors.WithStatusCode(http.StatusUnprocessableEntity),
+		ierrors.WithPublicMessage("Invalid source image"),
+		ierrors.WithShouldReport(false),
+	)
+}
+
+func (e FileSizeError) Error() string { return "Source image file is too big" }
+
 func wrapDownloadError(err error, desc string) error {
 	return ierrors.Wrap(
 		fetcher.WrapError(err), 0,

+ 1 - 2
imagedata/factory.go

@@ -11,7 +11,6 @@ import (
 	"github.com/imgproxy/imgproxy/v3/asyncbuffer"
 	"github.com/imgproxy/imgproxy/v3/fetcher"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
-	"github.com/imgproxy/imgproxy/v3/security"
 )
 
 // Factory represents ImageData factory
@@ -101,7 +100,7 @@ func (f *Factory) sendRequest(ctx context.Context, url string, opts DownloadOpti
 		return req, nil, h, err
 	}
 
-	res, err = security.LimitResponseSize(res, opts.MaxSrcFileSize)
+	res, err = limitResponseSize(res, opts.MaxSrcFileSize)
 	if err != nil {
 		if res != nil {
 			res.Body.Close()

+ 3 - 3
security/response_limit.go → imagedata/response_limit.go

@@ -1,4 +1,4 @@
-package security
+package imagedata
 
 import (
 	"io"
@@ -28,11 +28,11 @@ func (lr *hardLimitReadCloser) Close() error {
 	return lr.r.Close()
 }
 
-// LimitResponseSize limits the size of the response body to MaxSrcFileSize (if set).
+// limitResponseSize limits the size of the response body to MaxSrcFileSize (if set).
 // First, it tries to use Content-Length header to check the limit.
 // If Content-Length is not set, it limits the size of the response body by wrapping
 // body reader with hard limit reader.
-func LimitResponseSize(r *http.Response, limit int) (*http.Response, error) {
+func limitResponseSize(r *http.Response, limit int) (*http.Response, error) {
 	if limit == 0 {
 		return r, nil
 	}

+ 2 - 2
imgproxy.go

@@ -40,7 +40,7 @@ type Imgproxy struct {
 	fetcher          *fetcher.Fetcher
 	imageDataFactory *imagedata.Factory
 	handlers         ImgproxyHandlers
-	security         *security.Security
+	security         *security.Checker
 	config           *Config
 }
 
@@ -196,6 +196,6 @@ func (i *Imgproxy) ImageDataFactory() *imagedata.Factory {
 	return i.imageDataFactory
 }
 
-func (i *Imgproxy) Security() *security.Security {
+func (i *Imgproxy) Security() *security.Checker {
 	return i.security
 }

+ 27 - 1
options/processing_options.go

@@ -2,6 +2,7 @@ package options
 
 import (
 	"encoding/base64"
+	"fmt"
 	"net/http"
 	"slices"
 	"strconv"
@@ -120,6 +121,31 @@ type ProcessingOptions struct {
 }
 
 func NewProcessingOptions() *ProcessingOptions {
+	// NOTE: This is temporary hack until ProcessingOptions does not have Factory
+	securityCfg, err := security.LoadConfigFromEnv(nil)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	// NOTE: This is a temporary workaround for logrus bug that deadlocks
+	// if log is used within another log (issue 1448)
+	if len(securityCfg.Salts) == 0 {
+		securityCfg.Salts = [][]byte{[]byte("logrusbugworkaround")}
+	}
+
+	if len(securityCfg.Keys) == 0 {
+		securityCfg.Keys = [][]byte{[]byte("logrusbugworkaround")}
+	}
+	// END OF WORKAROUND
+
+	security, err := security.New(securityCfg)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	securityOptions := security.NewOptions()
+	// NOTE: This is temporary hack until ProcessingOptions does not have Factory
+
 	po := ProcessingOptions{
 		ResizingType:      ResizeFit,
 		Width:             0,
@@ -151,7 +177,7 @@ func NewProcessingOptions() *ProcessingOptions {
 		SkipProcessingFormats: append([]imagetype.Type(nil), config.SkipProcessingFormats...),
 		UsedPresets:           make([]string, 0, len(config.Presets)),
 
-		SecurityOptions: security.DefaultOptions(),
+		SecurityOptions: securityOptions,
 
 		// Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
 		defaultQuality: config.Quality,

+ 22 - 0
security/checker.go

@@ -0,0 +1,22 @@
+package security
+
+// Checker represents the security package instance
+type Checker struct {
+	config *Config
+}
+
+// New creates a new Security instance
+func New(config *Config) (*Checker, error) {
+	if err := config.Validate(); err != nil {
+		return nil, err
+	}
+
+	return &Checker{
+		config: config,
+	}, nil
+}
+
+// NewOptions creates a new security.Options instance
+func (s *Checker) NewOptions() Options {
+	return s.config.DefaultOptions
+}

+ 25 - 56
security/config.go

@@ -6,72 +6,31 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/ensure"
+
 	log "github.com/sirupsen/logrus"
 )
 
-// OptionsConfig represents the configuration for processing limits and security options
-type OptionsConfig struct {
-	MaxSrcResolution            int // Maximum allowed source image resolution (width × height)
-	MaxSrcFileSize              int // Maximum allowed source file size in bytes (0 = unlimited)
-	MaxAnimationFrames          int // Maximum number of frames allowed in animated images
-	MaxAnimationFrameResolution int // Maximum resolution allowed for each frame in animated images (0 = unlimited)
-	MaxResultDimension          int // Maximum allowed dimension (width or height) for result images (0 = unlimited)
-}
-
-// NewDefaultOptionsConfig returns a new OptionsConfig instance with default values
-func NewDefaultOptionsConfig() OptionsConfig {
-	return OptionsConfig{
-		MaxSrcResolution:            50000000,
-		MaxSrcFileSize:              0,
-		MaxAnimationFrames:          1,
-		MaxAnimationFrameResolution: 0,
-		MaxResultDimension:          0,
-	}
-}
-
-// LoadOptionsConfigFromEnv loads OptionsConfig from global config variables
-func LoadOptionsConfigFromEnv(c *OptionsConfig) (*OptionsConfig, error) {
-	c.MaxSrcResolution = config.MaxSrcResolution
-	c.MaxSrcFileSize = config.MaxSrcFileSize
-	c.MaxAnimationFrames = config.MaxAnimationFrames
-	c.MaxAnimationFrameResolution = config.MaxAnimationFrameResolution
-	c.MaxResultDimension = config.MaxResultDimension
-
-	return c, nil
-}
-
-// Validate validates the OptionsConfig values
-func (c *OptionsConfig) Validate() error {
-	if c.MaxSrcResolution <= 0 {
-		return fmt.Errorf("max src resolution should be greater than 0, now - %d", c.MaxSrcResolution)
-	}
-
-	if c.MaxSrcFileSize < 0 {
-		return fmt.Errorf("max src file size should be greater than or equal to 0, now - %d", c.MaxSrcFileSize)
-	}
-
-	if c.MaxAnimationFrames <= 0 {
-		return fmt.Errorf("max animation frames should be greater than 0, now - %d", c.MaxAnimationFrames)
-	}
-
-	return nil
-}
-
 // Config is the package-local configuration
 type Config struct {
-	Options              OptionsConfig    // Processing limits and security options
 	AllowSecurityOptions bool             // Whether to allow security-related processing options in URLs
 	AllowedSources       []*regexp.Regexp // List of allowed source URL patterns (empty = allow all)
 	Keys                 [][]byte         // List of the HMAC keys
 	Salts                [][]byte         // List of the HMAC salts
 	SignatureSize        int              // Size of the HMAC signature in bytes
 	TrustedSignatures    []string         // List of trusted signature sources
+	DefaultOptions       Options          // Default security options
 }
 
 // NewDefaultConfig returns a new Config instance with default values.
 func NewDefaultConfig() Config {
 	return Config{
-		Options:              NewDefaultOptionsConfig(),
+		DefaultOptions: Options{
+			MaxSrcResolution:            50000000,
+			MaxSrcFileSize:              0,
+			MaxAnimationFrames:          1,
+			MaxAnimationFrameResolution: 0,
+			MaxResultDimension:          0,
+		},
 		AllowSecurityOptions: false,
 		SignatureSize:        32,
 	}
@@ -81,10 +40,6 @@ func NewDefaultConfig() Config {
 func LoadConfigFromEnv(c *Config) (*Config, error) {
 	c = ensure.Ensure(c, NewDefaultConfig)
 
-	if _, err := LoadOptionsConfigFromEnv(&c.Options); err != nil {
-		return nil, err
-	}
-
 	c.AllowSecurityOptions = config.AllowSecurityOptions
 	c.AllowedSources = config.AllowedSources
 	c.Keys = config.Keys
@@ -92,13 +47,27 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
 	c.SignatureSize = config.SignatureSize
 	c.TrustedSignatures = config.TrustedSignatures
 
+	c.DefaultOptions.MaxSrcResolution = config.MaxSrcResolution
+	c.DefaultOptions.MaxSrcFileSize = config.MaxSrcFileSize
+	c.DefaultOptions.MaxAnimationFrames = config.MaxAnimationFrames
+	c.DefaultOptions.MaxAnimationFrameResolution = config.MaxAnimationFrameResolution
+	c.DefaultOptions.MaxResultDimension = config.MaxResultDimension
+
 	return c, nil
 }
 
 // Validate validates the configuration
 func (c *Config) Validate() error {
-	if err := c.Options.Validate(); err != nil {
-		return err
+	if c.DefaultOptions.MaxSrcResolution <= 0 {
+		return fmt.Errorf("max src resolution should be greater than 0, now - %d", c.DefaultOptions.MaxSrcResolution)
+	}
+
+	if c.DefaultOptions.MaxSrcFileSize < 0 {
+		return fmt.Errorf("max src file size should be greater than or equal to 0, now - %d", c.DefaultOptions.MaxSrcFileSize)
+	}
+
+	if c.DefaultOptions.MaxAnimationFrames <= 0 {
+		return fmt.Errorf("max animation frames should be greater than 0, now - %d", c.DefaultOptions.MaxAnimationFrames)
 	}
 
 	if len(c.Keys) != len(c.Salts) {

+ 0 - 13
security/errors.go

@@ -9,7 +9,6 @@ import (
 
 type (
 	SignatureError       string
-	FileSizeError        struct{}
 	ImageResolutionError string
 	SecurityOptionsError struct{}
 	SourceURLError       string
@@ -27,18 +26,6 @@ func newSignatureError(msg string) error {
 
 func (e SignatureError) Error() string { return string(e) }
 
-func newFileSizeError() error {
-	return ierrors.Wrap(
-		FileSizeError{},
-		1,
-		ierrors.WithStatusCode(http.StatusUnprocessableEntity),
-		ierrors.WithPublicMessage("Invalid source image"),
-		ierrors.WithShouldReport(false),
-	)
-}
-
-func (e FileSizeError) Error() string { return "Source image file is too big" }
-
 func newImageResolutionError(msg string) error {
 	return ierrors.Wrap(
 		ImageResolutionError(msg),

+ 2 - 12
security/options.go

@@ -4,6 +4,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/config"
 )
 
+// Security options (part of processing options)
 type Options struct {
 	MaxSrcResolution            int
 	MaxSrcFileSize              int
@@ -12,18 +13,7 @@ type Options struct {
 	MaxResultDimension          int
 }
 
-// NOTE: Remove this function in imgproxy v4
-// TODO: Replace this with security.NewOptions() when ProcessingOptions gets config
-func DefaultOptions() Options {
-	return Options{
-		MaxSrcResolution:            config.MaxSrcResolution,
-		MaxSrcFileSize:              config.MaxSrcFileSize,
-		MaxAnimationFrames:          config.MaxAnimationFrames,
-		MaxAnimationFrameResolution: config.MaxAnimationFrameResolution,
-		MaxResultDimension:          config.MaxResultDimension,
-	}
-}
-
+// NOTE: This function is a part of processing option, we'll move it in the next PR
 func IsSecurityOptionsAllowed() error {
 	if config.AllowSecurityOptions {
 		return nil

+ 0 - 28
security/security.go

@@ -1,28 +0,0 @@
-package security
-
-// Security represents the security package instance
-type Security struct {
-	config *Config
-}
-
-// New creates a new Security instance
-func New(config *Config) (*Security, error) {
-	if err := config.Validate(); err != nil {
-		return nil, err
-	}
-
-	return &Security{
-		config: config,
-	}, nil
-}
-
-// NewOptions creates a new security.Options instance
-func (s *Security) NewOptions() Options {
-	return Options{
-		MaxSrcResolution:            s.config.Options.MaxSrcResolution,
-		MaxSrcFileSize:              s.config.Options.MaxSrcFileSize,
-		MaxAnimationFrames:          s.config.Options.MaxAnimationFrames,
-		MaxAnimationFrameResolution: s.config.Options.MaxAnimationFrameResolution,
-		MaxResultDimension:          s.config.Options.MaxResultDimension,
-	}
-}

+ 1 - 1
security/signature.go

@@ -7,7 +7,7 @@ import (
 	"slices"
 )
 
-func (s *Security) VerifySignature(signature, path string) error {
+func (s *Checker) VerifySignature(signature, path string) error {
 	if len(s.config.Keys) == 0 || len(s.config.Salts) == 0 {
 		return nil
 	}

+ 2 - 2
security/signature_test.go

@@ -13,7 +13,7 @@ type SignatureTestSuite struct {
 	testutil.LazySuite
 
 	config   testutil.LazyObj[*Config]
-	security testutil.LazyObj[*Security]
+	security testutil.LazyObj[*Checker]
 }
 
 func (s *SignatureTestSuite) SetupSuite() {
@@ -27,7 +27,7 @@ func (s *SignatureTestSuite) SetupSuite() {
 
 	s.security, _ = testutil.NewLazySuiteObj(
 		s,
-		func() (*Security, error) {
+		func() (*Checker, error) {
 			return New(s.config())
 		},
 	)

+ 1 - 1
security/source.go

@@ -2,7 +2,7 @@ package security
 
 // VerifySourceURL checks if the given imageURL is allowed based on
 // the configured AllowedSources.
-func (s *Security) VerifySourceURL(imageURL string) error {
+func (s *Checker) VerifySourceURL(imageURL string) error {
 	if len(s.config.AllowedSources) == 0 {
 		return nil
 	}