1
0
mirror of https://github.com/minio/mc.git synced 2025-11-10 13:42:32 +03:00
Files
mc/cmd/utils.go
Anis Elleuch 90dc31f6ba Move from mc config host to alias command (#3311)
```
mc alias set
   alias remove
   alias list
```

are new commands that replace mc config host sub-commands.

mc config host will still be available but hidden for backward
compatiblity. The JSON output is also kept if the user is running mc
config host command.
2020-07-28 10:26:41 -07:00

366 lines
11 KiB
Go

/*
* MinIO Client (C) 2015, 2016, 2017 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"crypto/tls"
"errors"
"math/rand"
"net"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/minio/mc/pkg/ioutils"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio/pkg/console"
)
func isErrIgnored(err *probe.Error) (ignored bool) {
// For all non critical errors we can continue for the remaining files.
switch e := err.ToGoError().(type) {
// Handle these specifically for filesystem related errors.
case BrokenSymlink, TooManyLevelsSymlink, PathNotFound:
ignored = true
// Handle these specifically for object storage related errors.
case BucketNameEmpty, ObjectMissing, ObjectAlreadyExists:
ignored = true
case ObjectAlreadyExistsAsDirectory, BucketDoesNotExist, BucketInvalid:
ignored = true
case minio.ErrorResponse:
ignored = strings.Contains(e.Error(), "The specified key does not exist")
default:
ignored = false
}
return ignored
}
const (
letterBytes = "abcdefghijklmnopqrstuvwxyz01234569"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
// UTCNow - returns current UTC time.
func UTCNow() time.Time {
return time.Now().UTC()
}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
// newRandomID generates a random id of regular lower case and uppercase english characters.
func newRandomID(n int) string {
rand.Seed(UTCNow().UnixNano())
sid := make([]rune, n)
for i := range sid {
sid[i] = letters[rand.Intn(len(letters))]
}
return string(sid)
}
// randString generates random names and prepends them with a known prefix.
func randString(n int, src rand.Source, prefix string) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return prefix + string(b[0:30-len(prefix)])
}
// dumpTlsCertificates prints some fields of the certificates received from the server.
// Fields will be inspected by the user, so they must be conscise and useful
func dumpTLSCertificates(t *tls.ConnectionState) {
for _, cert := range t.PeerCertificates {
console.Debugln("TLS Certificate found: ")
if len(cert.Issuer.Country) > 0 {
console.Debugln(" >> Country: " + cert.Issuer.Country[0])
}
if len(cert.Issuer.Organization) > 0 {
console.Debugln(" >> Organization: " + cert.Issuer.Organization[0])
}
console.Debugln(" >> Expires: " + cert.NotAfter.String())
}
}
// splitStr splits a string into n parts, empty strings are added
// if we are not able to reach n elements
func splitStr(path, sep string, n int) []string {
splits := strings.SplitN(path, sep, n)
// Add empty strings if we found elements less than nr
for i := n - len(splits); i > 0; i-- {
splits = append(splits, "")
}
return splits
}
// NewS3Config simply creates a new Config struct using the passed
// parameters.
func NewS3Config(urlStr string, aliasCfg *aliasConfigV10) *Config {
// We have a valid alias and hostConfig. We populate the
// credentials from the match found in the config file.
s3Config := new(Config)
s3Config.AppName = filepath.Base(os.Args[0])
s3Config.AppVersion = Version
s3Config.AppComments = []string{os.Args[0], runtime.GOOS, runtime.GOARCH}
s3Config.Debug = globalDebug
s3Config.Insecure = globalInsecure
s3Config.HostURL = urlStr
if aliasCfg != nil {
s3Config.AccessKey = aliasCfg.AccessKey
s3Config.SecretKey = aliasCfg.SecretKey
s3Config.SessionToken = aliasCfg.SessionToken
s3Config.Signature = aliasCfg.API
}
s3Config.Lookup = getLookupType(aliasCfg.Path)
return s3Config
}
// lineTrunc - truncates a string to the given maximum length by
// adding ellipsis in the middle
func lineTrunc(content string, maxLen int) string {
runes := []rune(content)
rlen := len(runes)
if rlen <= maxLen {
return content
}
halfLen := maxLen / 2
fstPart := string(runes[0:halfLen])
sndPart := string(runes[rlen-halfLen:])
return fstPart + "…" + sndPart
}
// isOlder returns true if the passed object is older than olderRef
func isOlder(ti time.Time, olderRef string) bool {
if olderRef == "" {
return false
}
objectAge := time.Since(ti)
olderThan, e := ioutils.ParseDurationTime(olderRef)
fatalIf(probe.NewError(e), "Unable to parse olderThan=`"+olderRef+"`.")
return objectAge < olderThan
}
// isNewer returns true if the passed object is newer than newerRef
func isNewer(ti time.Time, newerRef string) bool {
if newerRef == "" {
return false
}
objectAge := time.Since(ti)
newerThan, e := ioutils.ParseDurationTime(newerRef)
fatalIf(probe.NewError(e), "Unable to parse newerThan=`"+newerRef+"`.")
return objectAge >= newerThan
}
// getLookupType returns the minio.BucketLookupType for lookup
// option entered on the command line
func getLookupType(l string) minio.BucketLookupType {
l = strings.ToLower(l)
switch l {
case "off":
return minio.BucketLookupDNS
case "on":
return minio.BucketLookupPath
}
return minio.BucketLookupAuto
}
// struct representing object prefix and sse keys association.
type prefixSSEPair struct {
Prefix string
SSE encrypt.ServerSide
}
// parse and validate encryption keys entered on command line
func parseAndValidateEncryptionKeys(sseKeys string, sse string) (encMap map[string][]prefixSSEPair, err *probe.Error) {
encMap, err = parseEncryptionKeys(sseKeys)
if err != nil {
return nil, err
}
if sse != "" {
for _, prefix := range strings.Split(sse, ",") {
alias, _ := url2Alias(prefix)
encMap[alias] = append(encMap[alias], prefixSSEPair{
Prefix: prefix,
SSE: encrypt.NewSSE(),
})
}
}
for alias, ps := range encMap {
if hostCfg := mustGetHostConfig(alias); hostCfg == nil {
for _, p := range ps {
return nil, probe.NewError(errors.New("SSE prefix " + p.Prefix + " has invalid alias"))
}
}
}
return encMap, nil
}
// parse list of comma separated alias/prefix=sse key values entered on command line and
// construct a map of alias to prefix and sse pairs.
func parseEncryptionKeys(sseKeys string) (encMap map[string][]prefixSSEPair, err *probe.Error) {
encMap = make(map[string][]prefixSSEPair)
if sseKeys == "" {
return
}
prefix := ""
index := 0 // start index of prefix
vs := 0 // start index of sse-c key
sseKeyLen := 32
delim := 1
k := len(sseKeys)
for index < k {
i := strings.Index(sseKeys[index:], "=")
if i == -1 {
return nil, probe.NewError(errors.New("SSE-C prefix should be of the form prefix1=key1,... "))
}
prefix = sseKeys[index : index+i]
alias, _ := url2Alias(prefix)
vs = i + 1 + index
if vs+32 > k {
return nil, probe.NewError(errors.New("SSE-C key should be 32 bytes long"))
}
if (vs+sseKeyLen < k) && sseKeys[vs+sseKeyLen] != ',' {
return nil, probe.NewError(errors.New("SSE-C prefix=secret should be delimited by , and secret should be 32 bytes long"))
}
sseKey := sseKeys[vs : vs+sseKeyLen]
if _, ok := encMap[alias]; !ok {
encMap[alias] = make([]prefixSSEPair, 0)
}
sse, e := encrypt.NewSSEC([]byte(sseKey))
if e != nil {
return nil, probe.NewError(e)
}
encMap[alias] = append(encMap[alias], prefixSSEPair{
Prefix: prefix,
SSE: sse,
})
// advance index sseKeyLen + delim bytes for the next key start
index = vs + sseKeyLen + delim
}
// Sort encryption keys in descending order of prefix length
for _, encKeys := range encMap {
sort.Sort(byPrefixLength(encKeys))
}
// Success.
return encMap, nil
}
// byPrefixLength implements sort.Interface.
type byPrefixLength []prefixSSEPair
func (p byPrefixLength) Len() int { return len(p) }
func (p byPrefixLength) Less(i, j int) bool {
return len(p[i].Prefix) > len(p[j].Prefix)
}
func (p byPrefixLength) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// get SSE Key if object prefix matches with given resource.
func getSSE(resource string, encKeys []prefixSSEPair) encrypt.ServerSide {
for _, k := range encKeys {
if strings.HasPrefix(resource, k.Prefix) {
return k.SSE
}
}
return nil
}
// Return true if target url is a part of a source url such as:
// alias/bucket/ and alias/bucket/dir/, however
func isURLContains(srcURL, tgtURL, sep string) bool {
// Add a separator to source url if not found
if !strings.HasSuffix(srcURL, sep) {
srcURL += sep
}
if !strings.HasSuffix(tgtURL, sep) {
tgtURL += sep
}
// Check if we are going to copy a directory into itself
if strings.HasPrefix(tgtURL, srcURL) {
return true
}
return false
}
// ErrInvalidFileSystemAttribute reflects invalid fily system attribute
var ErrInvalidFileSystemAttribute = errors.New("Error in parsing file system attribute")
// Returns a map by parsing the value of X-Amz-Meta-Mc-Attrs/X-Amz-Meta-s3Cmd-Attrs
func parseAttribute(attrs string) (map[string]string, error) {
var err error
attribute := make(map[string]string)
param := strings.Split(attrs, "/")
for _, val := range param {
attr := strings.TrimSpace(val)
if attr == "" {
err = ErrInvalidFileSystemAttribute
} else {
attrVal := strings.Split(attr, ":")
if len(attrVal) == 2 {
attribute[strings.TrimSpace(attrVal[0])] = strings.TrimSpace(attrVal[1])
} else if len(attrVal) == 1 {
attribute[attrVal[0]] = ""
} else {
err = ErrInvalidFileSystemAttribute
}
}
}
if err != nil {
return nil, ErrInvalidFileSystemAttribute
}
return attribute, nil
}
// Returns true if "s3" is entirely in sub-domain and false otherwise.
// true for s3.amazonaws.com, false for ams3.digitaloceanspaces.com, 192.168.1.12
func matchS3InHost(urlHost string) bool {
if strings.Contains(urlHost, ":") {
if host, _, err := net.SplitHostPort(urlHost); err == nil {
urlHost = host
}
}
fqdnParts := strings.Split(urlHost, ".")
for _, fqdn := range fqdnParts {
if fqdn == "s3" {
return true
}
}
return false
}