mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Add support for federation on browser (#6891)
This commit is contained in:
parent
2aeb3fbe86
commit
d1e41695fe
@ -19,7 +19,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -627,11 +626,52 @@ type bucketForwardingHandler struct {
|
|||||||
|
|
||||||
func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if globalDNSConfig == nil || globalDomainName == "" ||
|
if globalDNSConfig == nil || globalDomainName == "" ||
|
||||||
guessIsBrowserReq(r) || guessIsHealthCheckReq(r) ||
|
guessIsHealthCheckReq(r) || guessIsMetricsReq(r) ||
|
||||||
guessIsMetricsReq(r) || guessIsRPCReq(r) || isAdminReq(r) {
|
guessIsRPCReq(r) || isAdminReq(r) {
|
||||||
f.handler.ServeHTTP(w, r)
|
f.handler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For browser requests, when federation is setup we need to
|
||||||
|
// specifically handle download and upload for browser requests.
|
||||||
|
if guessIsBrowserReq(r) && globalDNSConfig != nil && globalDomainName != "" {
|
||||||
|
var bucket, _ string
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPut:
|
||||||
|
if getRequestAuthType(r) == authTypeJWT {
|
||||||
|
bucket, _ = urlPath2BucketObjectName(strings.TrimPrefix(r.URL.Path, minioReservedBucketPath+"/upload"))
|
||||||
|
}
|
||||||
|
case http.MethodGet:
|
||||||
|
if t := r.URL.Query().Get("token"); t != "" {
|
||||||
|
bucket, _ = urlPath2BucketObjectName(strings.TrimPrefix(r.URL.Path, minioReservedBucketPath+"/download"))
|
||||||
|
} else if getRequestAuthType(r) != authTypeJWT && !strings.HasPrefix(r.URL.Path, minioReservedBucketPath) {
|
||||||
|
bucket, _ = urlPath2BucketObjectName(r.URL.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bucket != "" {
|
||||||
|
sr, err := globalDNSConfig.Get(bucket)
|
||||||
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r))
|
||||||
|
} else {
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(context.Background(), err), r.URL, guessIsBrowserReq(r))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
||||||
|
r.URL.Scheme = "http"
|
||||||
|
if globalIsSSL {
|
||||||
|
r.URL.Scheme = "https"
|
||||||
|
}
|
||||||
|
r.URL.Host = getHostFromSrv(sr)
|
||||||
|
f.fwd.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.handler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bucket, object := urlPath2BucketObjectName(r.URL.Path)
|
bucket, object := urlPath2BucketObjectName(r.URL.Path)
|
||||||
// ListBucket requests should be handled at current endpoint as
|
// ListBucket requests should be handled at current endpoint as
|
||||||
// all buckets data can be fetched from here.
|
// all buckets data can be fetched from here.
|
||||||
@ -663,12 +703,11 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
|
||||||
host, port := getRandomHostPort(sr)
|
|
||||||
r.URL.Scheme = "http"
|
r.URL.Scheme = "http"
|
||||||
if globalIsSSL {
|
if globalIsSSL {
|
||||||
r.URL.Scheme = "https"
|
r.URL.Scheme = "https"
|
||||||
}
|
}
|
||||||
r.URL.Host = fmt.Sprintf("%s:%d", host, port)
|
r.URL.Host = getHostFromSrv(sr)
|
||||||
f.fwd.ServeHTTP(w, r)
|
f.fwd.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,13 @@ func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) {
|
|||||||
if cred.AccessKey == "" {
|
if cred.AccessKey == "" {
|
||||||
cred, _, _ = getReqAccessKeyV2(r)
|
cred, _, _ = getReqAccessKeyV2(r)
|
||||||
}
|
}
|
||||||
|
if cred.AccessKey == "" {
|
||||||
|
claims, owner, _ := webRequestAuthenticate(r)
|
||||||
|
if owner {
|
||||||
|
return globalServerConfig.GetCredential()
|
||||||
|
}
|
||||||
|
cred, _ = globalIAMSys.GetUser(claims.Subject)
|
||||||
|
}
|
||||||
return cred
|
return cred
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +201,7 @@ func extractReqParams(r *http.Request) map[string]string {
|
|||||||
|
|
||||||
region := globalServerConfig.GetRegion()
|
region := globalServerConfig.GetRegion()
|
||||||
cred := getReqAccessCred(r, region)
|
cred := getReqAccessCred(r, region)
|
||||||
|
|
||||||
// Success.
|
// Success.
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"region": region,
|
"region": region,
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -299,11 +300,11 @@ func getHostsSlice(records []dns.SrvRecord) []string {
|
|||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a random host (and corresponding port) from a slice of DNS records
|
// returns a host (and corresponding port) from a slice of DNS records
|
||||||
func getRandomHostPort(records []dns.SrvRecord) (string, int) {
|
func getHostFromSrv(records []dns.SrvRecord) string {
|
||||||
rand.Seed(time.Now().Unix())
|
rand.Seed(time.Now().Unix())
|
||||||
srvRecord := records[rand.Intn(len(records))]
|
srvRecord := records[rand.Intn(len(records))]
|
||||||
return srvRecord.Host, srvRecord.Port
|
return net.JoinHostPort(srvRecord.Host, fmt.Sprintf("%d", srvRecord.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCompressed returns true if the object is marked as compressed.
|
// IsCompressed returns true if the object is marked as compressed.
|
||||||
|
@ -668,11 +668,25 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m
|
|||||||
|
|
||||||
// Returns a minio-go Client configured to access remote host described by destDNSRecord
|
// Returns a minio-go Client configured to access remote host described by destDNSRecord
|
||||||
// Applicable only in a federated deployment
|
// Applicable only in a federated deployment
|
||||||
var getRemoteInstanceClient = func(r *http.Request, host string, port int) (*miniogo.Core, error) {
|
var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core, error) {
|
||||||
// In a federated deployment, all the instances share config files and hence expected to have same
|
|
||||||
// credentials, make sure to send the same credentials for which the request came in.
|
|
||||||
cred := getReqAccessCred(r, globalServerConfig.GetRegion())
|
cred := getReqAccessCred(r, globalServerConfig.GetRegion())
|
||||||
return miniogo.NewCore(net.JoinHostPort(host, strconv.Itoa(port)), cred.AccessKey, cred.SecretKey, globalIsSSL)
|
// In a federated deployment, all the instances share config files
|
||||||
|
// and hence expected to have same credentials.
|
||||||
|
core, err := miniogo.NewCore(host, cred.AccessKey, cred.SecretKey, globalIsSSL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
core.SetCustomTransport(NewCustomHTTPTransport())
|
||||||
|
return core, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the bucket is on a remote site, this code only gets executed when federation is enabled.
|
||||||
|
var isRemoteCallRequired = func(ctx context.Context, bucket string, objAPI ObjectLayer) bool {
|
||||||
|
if globalDNSConfig == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := objAPI.GetBucketInfo(ctx, bucket)
|
||||||
|
return err == toObjectErr(errVolumeNotFound, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyObjectHandler - Copy Object
|
// CopyObjectHandler - Copy Object
|
||||||
@ -802,17 +816,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
srcInfo.metadataOnly = true
|
srcInfo.metadataOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a remote putobject call is needed for CopyObject operation
|
|
||||||
// 1. If source and destination bucket names are same, it means no call needed to etcd to get destination info
|
|
||||||
// 2. If destination bucket doesn't exist locally, only then a etcd call is needed
|
|
||||||
var isRemoteCallRequired = func(ctx context.Context, src, dst string, objAPI ObjectLayer) bool {
|
|
||||||
if src == dst {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, berr := objAPI.GetBucketInfo(ctx, dst)
|
|
||||||
return berr == toObjectErr(errVolumeNotFound, dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
var length = srcInfo.Size
|
var length = srcInfo.Size
|
||||||
|
|
||||||
@ -827,10 +830,27 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
length = actualSize
|
length = actualSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the destination bucket is on a remote site, this code only gets executed
|
||||||
|
// when federation is enabled, ie when globalDNSConfig is non 'nil'.
|
||||||
|
//
|
||||||
|
// This function is similar to isRemoteCallRequired but specifically for COPY object API
|
||||||
|
// if destination and source are same we do not need to check for destnation bucket
|
||||||
|
// to exist locally.
|
||||||
|
var isRemoteCopyRequired = func(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool {
|
||||||
|
if globalDNSConfig == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if srcBucket == dstBucket {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := objAPI.GetBucketInfo(ctx, dstBucket)
|
||||||
|
return err == toObjectErr(errVolumeNotFound, dstBucket)
|
||||||
|
}
|
||||||
|
|
||||||
var compressMetadata map[string]string
|
var compressMetadata map[string]string
|
||||||
// No need to compress for remote etcd calls
|
// No need to compress for remote etcd calls
|
||||||
// Pass the decompressed stream to such calls.
|
// Pass the decompressed stream to such calls.
|
||||||
isCompressed := objectAPI.IsCompressionSupported() && isCompressible(r.Header, srcObject) && !isRemoteCallRequired(ctx, srcBucket, dstBucket, objectAPI)
|
isCompressed := objectAPI.IsCompressionSupported() && isCompressible(r.Header, srcObject) && !isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI)
|
||||||
if isCompressed {
|
if isCompressed {
|
||||||
compressMetadata = make(map[string]string, 2)
|
compressMetadata = make(map[string]string, 2)
|
||||||
// Preserving the compression metadata.
|
// Preserving the compression metadata.
|
||||||
@ -1009,29 +1029,28 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
var objInfo ObjectInfo
|
var objInfo ObjectInfo
|
||||||
|
|
||||||
if isRemoteCallRequired(ctx, srcBucket, dstBucket, objectAPI) {
|
if isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) {
|
||||||
if globalDNSConfig == nil {
|
var dstRecords []dns.SrvRecord
|
||||||
writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r))
|
dstRecords, err = globalDNSConfig.Get(dstBucket)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var dstRecords []dns.SrvRecord
|
|
||||||
if dstRecords, err = globalDNSConfig.Get(dstBucket); err == nil {
|
// Send PutObject request to appropriate instance (in federated deployment)
|
||||||
// Send PutObject request to appropriate instance (in federated deployment)
|
client, rerr := getRemoteInstanceClient(r, getHostFromSrv(dstRecords))
|
||||||
host, port := getRandomHostPort(dstRecords)
|
if rerr != nil {
|
||||||
client, rerr := getRemoteInstanceClient(r, host, port)
|
writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r))
|
||||||
if rerr != nil {
|
return
|
||||||
writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader,
|
|
||||||
srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption)
|
|
||||||
if rerr != nil {
|
|
||||||
writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
objInfo.ETag = remoteObjInfo.ETag
|
|
||||||
objInfo.ModTime = remoteObjInfo.LastModified
|
|
||||||
}
|
}
|
||||||
|
remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader,
|
||||||
|
srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption)
|
||||||
|
if rerr != nil {
|
||||||
|
writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
objInfo.ETag = remoteObjInfo.ETag
|
||||||
|
objInfo.ModTime = remoteObjInfo.LastModified
|
||||||
} else {
|
} else {
|
||||||
// Copy source object to destination, if source and destination
|
// Copy source object to destination, if source and destination
|
||||||
// object is same then only metadata is updated.
|
// object is same then only metadata is updated.
|
||||||
|
@ -99,11 +99,14 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep
|
|||||||
reply.MinioGlobalInfo = getGlobalInfo()
|
reply.MinioGlobalInfo = getGlobalInfo()
|
||||||
// If ENV creds are not set and incoming user is not owner
|
// If ENV creds are not set and incoming user is not owner
|
||||||
// disable changing credentials.
|
// disable changing credentials.
|
||||||
// TODO: fix this in future and allow changing user credentials.
|
|
||||||
v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool)
|
v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool)
|
||||||
if ok && !v {
|
if ok && !v {
|
||||||
reply.MinioGlobalInfo["isEnvCreds"] = !owner
|
reply.MinioGlobalInfo["isEnvCreds"] = !owner
|
||||||
}
|
}
|
||||||
|
// if etcd is set disallow changing credentials through UI
|
||||||
|
if globalEtcdClient != nil {
|
||||||
|
reply.MinioGlobalInfo["isEnvCreds"] = true
|
||||||
|
}
|
||||||
|
|
||||||
reply.MinioMemory = mem
|
reply.MinioMemory = mem
|
||||||
reply.MinioPlatform = platform
|
reply.MinioPlatform = platform
|
||||||
@ -148,7 +151,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
|
|||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
return toJSONError(authErr)
|
return toJSONError(authErr)
|
||||||
}
|
}
|
||||||
// TODO: Allow MakeBucket in future.
|
|
||||||
if !owner {
|
if !owner {
|
||||||
return toJSONError(errAccessDenied)
|
return toJSONError(errAccessDenied)
|
||||||
}
|
}
|
||||||
@ -201,11 +204,33 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
|
|||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
return toJSONError(authErr)
|
return toJSONError(authErr)
|
||||||
}
|
}
|
||||||
// TODO: Allow DeleteBucket in future.
|
|
||||||
if !owner {
|
if !owner {
|
||||||
return toJSONError(errAccessDenied)
|
return toJSONError(errAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reply.UIVersion = browser.UIVersion
|
||||||
|
|
||||||
|
if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
|
||||||
|
sr, err := globalDNSConfig.Get(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
return toJSONError(BucketNotFound{
|
||||||
|
Bucket: args.BucketName,
|
||||||
|
}, args.BucketName)
|
||||||
|
}
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
core, err := getRemoteInstanceClient(r, getHostFromSrv(sr))
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
if err = core.RemoveBucket(args.BucketName); err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
deleteBucket := objectAPI.DeleteBucket
|
deleteBucket := objectAPI.DeleteBucket
|
||||||
@ -229,7 +254,6 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.UIVersion = browser.UIVersion
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,6 +377,42 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
|
|||||||
listObjects = web.CacheAPI().ListObjects
|
listObjects = web.CacheAPI().ListObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
|
||||||
|
sr, err := globalDNSConfig.Get(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
return toJSONError(BucketNotFound{
|
||||||
|
Bucket: args.BucketName,
|
||||||
|
}, args.BucketName)
|
||||||
|
}
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
core, err := getRemoteInstanceClient(r, getHostFromSrv(sr))
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
result, err := core.ListObjects(args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
reply.NextMarker = result.NextMarker
|
||||||
|
reply.IsTruncated = result.IsTruncated
|
||||||
|
for _, obj := range result.Contents {
|
||||||
|
reply.Objects = append(reply.Objects, WebObjectInfo{
|
||||||
|
Key: obj.Key,
|
||||||
|
LastModified: obj.LastModified,
|
||||||
|
Size: obj.Size,
|
||||||
|
ContentType: obj.ContentType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, p := range result.CommonPrefixes {
|
||||||
|
reply.Objects = append(reply.Objects, WebObjectInfo{
|
||||||
|
Key: p.Prefix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
claims, owner, authErr := webRequestAuthenticate(r)
|
claims, owner, authErr := webRequestAuthenticate(r)
|
||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
if authErr == errNoAuthToken {
|
if authErr == errNoAuthToken {
|
||||||
@ -493,6 +553,40 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
|
|||||||
return toJSONError(errInvalidArgument)
|
return toJSONError(errInvalidArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reply.UIVersion = browser.UIVersion
|
||||||
|
if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
|
||||||
|
sr, err := globalDNSConfig.Get(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
return toJSONError(BucketNotFound{
|
||||||
|
Bucket: args.BucketName,
|
||||||
|
}, args.BucketName)
|
||||||
|
}
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
core, err := getRemoteInstanceClient(r, getHostFromSrv(sr))
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
objectsCh := make(chan string)
|
||||||
|
|
||||||
|
// Send object names that are needed to be removed to objectsCh
|
||||||
|
go func() {
|
||||||
|
defer close(objectsCh)
|
||||||
|
|
||||||
|
for _, objectName := range args.Objects {
|
||||||
|
objectsCh <- objectName
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for resp := range core.RemoveObjects(args.BucketName, objectsCh) {
|
||||||
|
if resp.Err != nil {
|
||||||
|
return toJSONError(resp.Err, args.BucketName, resp.ObjectName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
next:
|
next:
|
||||||
for _, objectName := range args.Objects {
|
for _, objectName := range args.Objects {
|
||||||
@ -516,7 +610,7 @@ next:
|
|||||||
return toJSONError(errAccessDenied)
|
return toJSONError(errAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil {
|
if err = deleteObject(context.Background(), objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil {
|
||||||
break next
|
break next
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@ -543,7 +637,7 @@ next:
|
|||||||
}
|
}
|
||||||
marker = lo.NextMarker
|
marker = lo.NextMarker
|
||||||
for _, obj := range lo.Objects {
|
for _, obj := range lo.Objects {
|
||||||
err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, obj.Name, r)
|
err = deleteObject(context.Background(), objectAPI, web.CacheAPI(), args.BucketName, obj.Name, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break next
|
break next
|
||||||
}
|
}
|
||||||
@ -559,7 +653,6 @@ next:
|
|||||||
return toJSONError(err, args.BucketName, "")
|
return toJSONError(err, args.BucketName, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.UIVersion = browser.UIVersion
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,8 +726,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If creds are set through ENV disallow changing credentials.
|
// If creds are set through ENV disallow changing credentials.
|
||||||
// TODO: Multi-user credentials also cannot be changed from browser.
|
if globalIsEnvCreds || globalWORMEnabled || !owner || globalEtcdClient != nil {
|
||||||
if globalIsEnvCreds || globalWORMEnabled || !owner {
|
|
||||||
return toJSONError(errChangeCredNotAllowed)
|
return toJSONError(errChangeCredNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,6 +950,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey)
|
pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that metadata does not contain sensitive information
|
// Ensure that metadata does not contain sensitive information
|
||||||
crypto.RemoveSensitiveEntries(metadata)
|
crypto.RemoveSensitiveEntries(metadata)
|
||||||
|
|
||||||
@ -1306,18 +1399,48 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
|
|||||||
return toJSONError(errAccessDenied)
|
return toJSONError(errAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
|
var policyInfo = &miniogopolicy.BucketAccessPolicy{Version: "2012-10-17"}
|
||||||
if err != nil {
|
if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
|
||||||
if _, ok := err.(BucketPolicyNotFound); !ok {
|
sr, err := globalDNSConfig.Get(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
return toJSONError(BucketNotFound{
|
||||||
|
Bucket: args.BucketName,
|
||||||
|
}, args.BucketName)
|
||||||
|
}
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
}
|
}
|
||||||
return err
|
client, rerr := getRemoteInstanceClient(r, getHostFromSrv(sr))
|
||||||
}
|
if rerr != nil {
|
||||||
|
return toJSONError(rerr, args.BucketName)
|
||||||
|
}
|
||||||
|
policyStr, err := client.GetBucketPolicy(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(rerr, args.BucketName)
|
||||||
|
}
|
||||||
|
bucketPolicy, err := policy.ParseConfig(strings.NewReader(policyStr), args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(rerr, args.BucketName)
|
||||||
|
}
|
||||||
|
policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy)
|
||||||
|
if err != nil {
|
||||||
|
// This should not happen.
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(BucketPolicyNotFound); !ok {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
|
policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This should not happen.
|
// This should not happen.
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.UIVersion = browser.UIVersion
|
reply.UIVersion = browser.UIVersion
|
||||||
@ -1359,17 +1482,42 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
|
|||||||
return toJSONError(errAccessDenied)
|
return toJSONError(errAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
|
var policyInfo = new(miniogopolicy.BucketAccessPolicy)
|
||||||
if err != nil {
|
if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
|
||||||
if _, ok := err.(BucketPolicyNotFound); !ok {
|
sr, err := globalDNSConfig.Get(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
return toJSONError(BucketNotFound{
|
||||||
|
Bucket: args.BucketName,
|
||||||
|
}, args.BucketName)
|
||||||
|
}
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
core, rerr := getRemoteInstanceClient(r, getHostFromSrv(sr))
|
||||||
|
if rerr != nil {
|
||||||
|
return toJSONError(rerr, args.BucketName)
|
||||||
|
}
|
||||||
|
var policyStr string
|
||||||
|
policyStr, err = core.Client.GetBucketPolicy(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
if policyStr != "" {
|
||||||
|
if err = json.Unmarshal([]byte(policyStr), policyInfo); err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(BucketPolicyNotFound); !ok {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy)
|
||||||
|
if err != nil {
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
|
|
||||||
if err != nil {
|
|
||||||
// This should not happen.
|
|
||||||
return toJSONError(err, args.BucketName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.UIVersion = browser.UIVersion
|
reply.UIVersion = browser.UIVersion
|
||||||
@ -1419,43 +1567,94 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName)
|
if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
|
||||||
if err != nil {
|
sr, err := globalDNSConfig.Get(args.BucketName)
|
||||||
if _, ok := err.(BucketPolicyNotFound); !ok {
|
if err != nil {
|
||||||
|
if err == dns.ErrNoEntriesFound {
|
||||||
|
return toJSONError(BucketNotFound{
|
||||||
|
Bucket: args.BucketName,
|
||||||
|
}, args.BucketName)
|
||||||
|
}
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
}
|
}
|
||||||
}
|
core, rerr := getRemoteInstanceClient(r, getHostFromSrv(sr))
|
||||||
|
if rerr != nil {
|
||||||
|
return toJSONError(rerr, args.BucketName)
|
||||||
|
}
|
||||||
|
var policyStr string
|
||||||
|
// Use the abstracted API instead of core, such that
|
||||||
|
// NoSuchBucketPolicy errors are automatically handled.
|
||||||
|
policyStr, err = core.Client.GetBucketPolicy(args.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
var policyInfo = &miniogopolicy.BucketAccessPolicy{Version: "2012-10-17"}
|
||||||
|
if policyStr != "" {
|
||||||
|
if err = json.Unmarshal([]byte(policyStr), policyInfo); err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
|
policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
|
||||||
if err != nil {
|
if len(policyInfo.Statements) == 0 {
|
||||||
// This should not happen.
|
if err = core.SetBucketPolicy(args.BucketName, ""); err != nil {
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
|
bucketPolicy, err := BucketAccessPolicyToPolicy(policyInfo)
|
||||||
|
if err != nil {
|
||||||
if len(policyInfo.Statements) == 0 {
|
// This should not happen.
|
||||||
if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil {
|
|
||||||
return toJSONError(err, args.BucketName)
|
return toJSONError(err, args.BucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
globalPolicySys.Remove(args.BucketName)
|
policyData, err := json.Marshal(bucketPolicy)
|
||||||
return nil
|
if err != nil {
|
||||||
}
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo)
|
if err = core.SetBucketPolicy(args.BucketName, string(policyData)); err != nil {
|
||||||
if err != nil {
|
return toJSONError(err, args.BucketName)
|
||||||
// This should not happen.
|
}
|
||||||
return toJSONError(err, args.BucketName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse validate and save bucket policy.
|
} else {
|
||||||
if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil {
|
bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName)
|
||||||
return toJSONError(err, args.BucketName)
|
if err != nil {
|
||||||
}
|
if _, ok := err.(BucketPolicyNotFound); !ok {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
|
||||||
|
if err != nil {
|
||||||
|
// This should not happen.
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
globalPolicySys.Set(args.BucketName, *bucketPolicy)
|
policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
|
||||||
globalNotificationSys.SetBucketPolicy(ctx, args.BucketName, bucketPolicy)
|
if len(policyInfo.Statements) == 0 {
|
||||||
|
if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
globalPolicySys.Remove(args.BucketName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo)
|
||||||
|
if err != nil {
|
||||||
|
// This should not happen.
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse validate and save bucket policy.
|
||||||
|
if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil {
|
||||||
|
return toJSONError(err, args.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
globalPolicySys.Set(args.BucketName, *bucketPolicy)
|
||||||
|
globalNotificationSys.SetBucketPolicy(ctx, args.BucketName, bucketPolicy)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func registerWebRouter(router *mux.Router) error {
|
|||||||
|
|
||||||
// These methods use short-expiry tokens in the URLs. These tokens may unintentionally
|
// These methods use short-expiry tokens in the URLs. These tokens may unintentionally
|
||||||
// be logged, so a new one must be generated for each request.
|
// be logged, so a new one must be generated for each request.
|
||||||
webBrowserRouter.Methods("GET").Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(web.Download)
|
webBrowserRouter.Methods("GET").Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.Download))
|
||||||
webBrowserRouter.Methods("POST").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.DownloadZip))
|
webBrowserRouter.Methods("POST").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.DownloadZip))
|
||||||
|
|
||||||
// Add compression for assets.
|
// Add compression for assets.
|
||||||
|
Loading…
Reference in New Issue
Block a user