server: custom redirect status code and expiration

This commit is contained in:
Nugraha 2022-12-24 04:43:39 +07:00
parent 61986da0b1
commit d2fd7057a4
Signed by: ii64
GPG key ID: E41C08AD390E7C49
6 changed files with 71 additions and 21 deletions

View file

@ -12,3 +12,6 @@ AWS_SECRET_KEY=example-minio-secret
# accessible S3 gateway
OBS_REDIRECT_SECURE=false
OBS_HOST_REDIRECT=127.0.0.1:9000
# OBS_REDIRECT_CODE=307 # temporary redirect
# OBS_URL_EXPIRY=48h # 2 days

1
.gitignore vendored
View file

@ -1 +1,2 @@
.env
obs-access-signer

View file

@ -10,6 +10,8 @@ There's an example of using it with Varnish Cache, which you can see [here](dock
Some S3-compatible gateways might not support ACL endpoints but they support presigned access. Currently, the behavior of `obs-access-signer` is similar to `public-read` ACL where clients can access objects anonymously and redirect them (permanently) to presigned URL with `Expires` set to the max signed value of `int64` which has roughly 250yrs lifetime since UNIX time started.
Update: starting at v0.0.3, obs-access-signer support custom redirection status code and expiry.
## License
Apache-2.0

39
main.go
View file

@ -4,6 +4,7 @@ import (
"flag"
"os"
"strconv"
"time"
_ "github.com/joho/godotenv/autoload"
"go.uber.org/zap"
@ -11,9 +12,8 @@ import (
)
var (
httpAddr string
logLevel string
// obsSignedUrlExpiry time.Duration
httpAddr string
logLevel string
zapLogLevel zapcore.Level
postFlagParse = []func(){}
)
@ -22,10 +22,10 @@ func init() {
var err error
_ = err
// app
// -- app
flag.StringVar(&httpAddr, "addr", os.Getenv("HTTP_ADDR"), "Server address")
// log
// -- log
flag.StringVar(&logLevel, "log-level", os.Getenv("LOG_LEVEL"), "Log level")
qpostFlagParse(func() {
err := zapLogLevel.UnmarshalText([]byte(logLevel))
@ -34,7 +34,7 @@ func init() {
}
})
// OBS
// -- OBS
flag.StringVar(&defaultObsOpts.Endpoint, "obs-endpoint", os.Getenv("OBS_ENDPOINT"), "OBS host")
flag.StringVar(&defaultObsOpts.Region, "obs-region", os.Getenv("OBS_REGION"), "OBS region")
flag.BoolVar(&defaultObsOpts.Secure, "obs-secure", ok1(strconv.ParseBool(os.Getenv("OBS_SECURE"))), "OBS secure transport")
@ -43,12 +43,25 @@ func init() {
flag.BoolVar(&defaultObsOpts.RedirectSecure, "obs-redirect-secure", ok1(strconv.ParseBool(os.Getenv("OBS_REDIRECT_SECURE"))), "OBS redirect secure transport")
flag.StringVar(&defaultObsOpts.HostRedirect, "obs-host-redirect", os.Getenv("OBS_HOST_REDIRECT"), "OBS host redirect")
// obsSignedUrlExpiry, err = time.ParseDuration(os.Getenv("OBS_SIGNED_URL_EXPIRY"))
// if err != nil {
// // max signed value
// obsSignedUrlExpiry = time.Duration(^uint64(0) / 2)
// }
// flag.DurationVar(&obsSignedUrlExpiry, "obs-signed-url-expiry", obsSignedUrlExpiry, "OBS ")
// redirect http code
var obsRedirectCode = int64(defaultObsOpts.RedirectCode)
if obsRedirectCodeStr := os.Getenv("OBS_REDIRECT_CODE"); obsRedirectCodeStr != "" {
obsRedirectCode, err = strconv.ParseInt(obsRedirectCodeStr, 10, 64)
if err != nil {
obsRedirectCode = int64(defaultObsOpts.RedirectCode)
}
}
flag.IntVar(&defaultObsOpts.RedirectCode, "obs-redirect-code", int(obsRedirectCode), "OBS redirect http code")
// url expiry
var obsUrlExpiry = defaultObsOpts.URLExpiry
if obsUrlExpiryStr := os.Getenv("OBS_URL_EXPIRY"); obsUrlExpiryStr != "" {
if obsUrlExpiry, err = time.ParseDuration(obsUrlExpiryStr); err != nil {
obsUrlExpiry = defaultObsOpts.URLExpiry
}
}
flag.DurationVar(&defaultObsOpts.URLExpiry, "obs-url-expiry", obsUrlExpiry, "OBS url expiry")
}
func qpostFlagParse(f func()) {
@ -77,6 +90,8 @@ func main() {
"obs_endpoint", defaultObsOpts.Endpoint,
"obs_redirect_secure", defaultObsOpts.RedirectSecure,
"obs_host_redirect", defaultObsOpts.HostRedirect,
"obs_redirect_code", defaultObsOpts.RedirectCode,
"obs_url_expiry", defaultObsOpts.URLExpiry.String(),
)
client := unwrap1(newObsClient(defaultObsOpts))

10
obs.go
View file

@ -6,6 +6,7 @@ import (
"net/http"
"net/url"
"reflect"
"time"
"unsafe"
"github.com/minio/minio-go/v7"
@ -21,10 +22,17 @@ type obsOptions struct {
BucketName string
RedirectSecure bool
RedirectCode int // HTTP redirect status code
URLExpiry time.Duration
HostRedirect string
}
var defaultObsOpts obsOptions
const maxURLExpiry = time.Duration(int64(^uint64(0) / 2))
var defaultObsOpts = obsOptions{
URLExpiry: maxURLExpiry,
RedirectCode: http.StatusMovedPermanently, // 301
}
func newObsClient(opts obsOptions) (*minio.Client, error) {
client, err := minio.New(opts.Endpoint, &minio.Options{

View file

@ -2,6 +2,7 @@ package main
import (
"bytes"
"fmt"
"net/http"
"net/url"
"strconv"
@ -24,8 +25,6 @@ type serverOptions struct {
Logger *zap.Logger
OBS *obsOptions
ObjectExpiry time.Duration
S3 *minio.Client
}
@ -99,11 +98,12 @@ func (s *server) handle(ctx *fasthttp.RequestCtx) {
}
// compose initial request
expireSeconds := int64(s.opts.OBS.URLExpiry / time.Second)
req, err := newRequest(s.opts.S3, ctx, http.MethodGet, requestMetadata{
presignURL: true,
bucketName: bucketName,
objectName: objectName,
expires: 1, // to trigger presigned generator
expires: expireSeconds, // to trigger presigned generator
queryValues: url.Values{},
})
if err != nil {
@ -120,8 +120,28 @@ func (s *server) handle(ctx *fasthttp.RequestCtx) {
return
}
// clear given params, set max signed value for expire, and re-presign.
exp := strconv.FormatInt(int64(^uint64(0)/2), 10) // ~250years
var statusCode = s.opts.OBS.RedirectCode
// custom "expiry"
var exp string
if expiry := s.opts.OBS.URLExpiry; expiry == maxURLExpiry || expiry <= 0 {
// clear given params, set max signed value for expire, and re-presign.
exp = strconv.FormatInt(int64(^uint64(0)/2), 10) // ~250years
} else {
// we can't allow a permanent redirect here since we already have
// expiry set, the redirected url needs to be updated.
if statusCode == http.StatusMovedPermanently || (statusCode < 300 || statusCode > 399) {
statusCode = http.StatusTemporaryRedirect
}
expireAt := time.Now().UTC().Add(s.opts.OBS.URLExpiry)
exp = strconv.FormatInt(int64(expireAt.Unix()), 10)
// set object cache lifetime
if statusCode == http.StatusTemporaryRedirect {
ctx.Response.Header.Set("Cache-Control", fmt.Sprintf("max-age=%d", expireSeconds))
ctx.Response.Header.Set("Expires", expireAt.Format("Mon, 02 Jan 2006 15:04:05 GMT"))
}
}
req.Header.Set("Expires", exp)
req.URL.RawQuery = ""
req = signer.PreSignV2(*req, value.AccessKeyID, value.SecretAccessKey, 0, isVirtualHostStyle)
@ -130,6 +150,7 @@ func (s *server) handle(ctx *fasthttp.RequestCtx) {
query := req.URL.Query()
query.Set("Expires", exp)
req.URL.RawQuery = s3utils.QueryEncode(query)
if s.opts.OBS.RedirectSecure {
req.URL.Scheme = "https"
} else {
@ -140,7 +161,7 @@ func (s *server) handle(ctx *fasthttp.RequestCtx) {
req.URL.Host = hostRedirect
}
ctx.Redirect(req.URL.String(), http.StatusMovedPermanently)
ctx.Redirect(req.URL.String(), statusCode)
}
func (s *server) Run() {