diff --git a/lib/command.go b/lib/command.go index 7777bb73..d047a08e 100644 --- a/lib/command.go +++ b/lib/command.go @@ -127,7 +127,7 @@ func (cmd *Command) loadConfig(configFile string, cmder interface{}) error { } func (cmd *Command) needConfigFile() bool { - for _, name := range []string{OptionEndpoint, OptionAccessKeyID, OptionAccessKeySecret, OptionSTSToken} { + for _, name := range []string{OptionEndpoint, OptionAccessKeyID, OptionAccessKeySecret, OptionSTSToken, OptionAesKey} { val, _ := GetString(name, cmd.options) if val != "" { return false diff --git a/lib/command_test.go b/lib/command_test.go index e27b4226..4d3c72b3 100644 --- a/lib/command_test.go +++ b/lib/command_test.go @@ -1,6 +1,7 @@ package lib import ( + "encoding/base64" "fmt" "io/ioutil" "log" @@ -186,8 +187,13 @@ func (s *OssutilCommandSuite) SetUpPayerEnv(c *C) { }` s.putBucketPolicy(payerBucket, policy, c) + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(payerAccessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(payerAccessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + //set payerConfigfile - data := fmt.Sprintf("[Credentials]\nlanguage=EN\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n", payerBucketEndPoint, payerAccessKeyID, payerAccessKeySecret) + data := fmt.Sprintf("[Credentials]\nlanguage=EN\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n", payerBucketEndPoint, entAccessKeyID, entAccessKeySecret, aesKeyBase64) s.createFile(payerConfigFile, data, c) } @@ -278,10 +284,13 @@ func (s *OssutilCommandSuite) AppendObject(bucketName string, object string, bod func (s *OssutilCommandSuite) configNonInteractive(c *C) { command := "config" var args []string + aesKey := AesKey + options := OptionMapType{ "endpoint": &endpoint, "accessKeyID": &accessKeyID, "accessKeySecret": &accessKeySecret, + "aesKey": &aesKey, "configFile": &configFile, } showElapse, err := cm.RunCommand(command, args, options) @@ -290,11 +299,12 @@ func (s *OssutilCommandSuite) configNonInteractive(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 4) + c.Assert(len(opts), Equals, 5) c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) c.Assert(opts[OptionEndpoint], Equals, endpoint) c.Assert(opts[OptionAccessKeyID], Equals, accessKeyID) c.Assert(opts[OptionAccessKeySecret], Equals, accessKeySecret) + c.Assert(opts[OptionAesKey], Equals, aesKey) } func (s *OssutilCommandSuite) createFile(fileName, content string, c *C) { @@ -2796,8 +2806,13 @@ func (s *OssutilCommandSuite) TestFilterObjectFromChanWithPatterns(c *C) { func (s *OssutilCommandSuite) TestCommandLoglevel(c *C) { cfile := "ossutil-config" + randLowStr(8) level := "info" - data := "[Credentials]" + "\n" + "language=" + DefaultLanguage + "\n" + "accessKeyID=" + accessKeyID + "\n" + "accessKeySecret=" + accessKeySecret + "\n" + "endpoint=" + - endpoint + "\n" + "[Default]" + "\n" + "loglevel=" + level + "\n" + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := "[Credentials]" + "\n" + "language=" + DefaultLanguage + "\n" + "accessKeyID=" + entAccessKeyID + "\n" + "accessKeySecret=" + entAccessKeySecret + "\n" + "endpoint=" + + endpoint + "\n" + "aesKey=" + + aesKeyBase64 + "\n" + "[Default]" + "\n" + "loglevel=" + level + "\n" s.createFile(cfile, data, c) f, err := os.Stat(cfile) diff --git a/lib/config.go b/lib/config.go index 91922d45..2f97df33 100644 --- a/lib/config.go +++ b/lib/config.go @@ -2,6 +2,7 @@ package lib import ( "bufio" + "encoding/base64" "fmt" "os" "runtime" @@ -323,10 +324,13 @@ var configCommand = ConfigCommand{ OptionSTSToken, OptionOutputDir, OptionLanguage, + OptionAesKey, }, }, } +var strArray = []string{OptionAccessKeyID, OptionAccessKeySecret, OptionSTSToken} + // function for RewriteLoadConfiger interface func (cc *ConfigCommand) rewriteLoadConfig(configFile string) error { // read config file, if error exist, do not print error @@ -375,14 +379,17 @@ func (cc *ConfigCommand) RunCommand() error { language, _ := GetString(OptionLanguage, cc.command.options) delete(cc.command.options, OptionLanguage) + aesKey, _ := GetString(OptionAesKey, cc.command.options) + delete(cc.command.options, OptionAesKey) + // filter user input options cc.filterNonInputOptions() var err error if len(cc.command.options) == 0 { - err = cc.runCommandInteractive(configFile, language) + err = cc.runCommandInteractive(configFile, language, aesKey) } else { - err = cc.runCommandNonInteractive(configFile, language) + err = cc.runCommandNonInteractive(configFile, language, aesKey) } return err } @@ -395,7 +402,7 @@ func (cc *ConfigCommand) filterNonInputOptions() { } } -func (cc *ConfigCommand) runCommandInteractive(configFile, language string) error { +func (cc *ConfigCommand) runCommandInteractive(configFile, language, aesKey string) error { llanguage := strings.ToLower(language) if llanguage == LEnglishLanguage { fmt.Println("The command creates a configuration file and stores credentials.") @@ -419,6 +426,21 @@ func (cc *ConfigCommand) runCommandInteractive(configFile, language string) erro } } + if aesKey == "" { + if llanguage == LEnglishLanguage { + fmt.Printf("\nPlease enter the AES key of encrypt ak/sk:") + } else { + fmt.Printf("\n请输入加密ak/sk的公钥:") + } + if _, err := fmt.Scanln(&aesKey); err != nil { + if llanguage == LEnglishLanguage { + fmt.Println("No aes key entered, will use the default value " + AesKey + ".") + } else { + fmt.Println("未输入加密ak/sk的公钥,将使用默认值:" + AesKey + "。") + } + } + } + configFile = DecideConfigFile(configFile) if llanguage == LEnglishLanguage { fmt.Println("For the following settings, carriage return means skip the configuration. Please try \"help config\" to see the meaning of the settings") @@ -426,20 +448,24 @@ func (cc *ConfigCommand) runCommandInteractive(configFile, language string) erro fmt.Println("对于下述配置,回车将跳过相关配置项的设置,配置项的具体含义,请使用\"help config\"命令查看。") } - if err := cc.configInteractive(configFile, language); err != nil { + if aesKey == "" { + aesKey = AesKey + } + + if err := cc.configInteractive(configFile, language, aesKey); err != nil { return err } return nil } -func (cc *ConfigCommand) configInteractive(configFile, language string) error { +func (cc *ConfigCommand) configInteractive(configFile, language, aesKey string) error { var val string config := configparser.NewConfiguration() section := config.NewSection(CREDSection) - // if config file not exist, config Language llanguage := strings.ToLower(language) section.Add(OptionLanguage, language) + section.Add(OptionAesKey, base64.StdEncoding.EncodeToString([]byte(aesKey))) if _, err := os.Stat(configFile); err != nil { if llanguage == LEnglishLanguage { fmt.Printf("Please enter language(%s, default is:%s, the configuration will go into effect after the command successfully executed):", OptionMap[OptionLanguage].minVal, DefaultLanguage) @@ -483,7 +509,12 @@ func (cc *ConfigCommand) configInteractive(configFile, language string) error { } if len(val) > 0 { - section.Add(name, val) + if name == OptionAccessKeyID || name == OptionAccessKeySecret || name == OptionSTSToken { + encryptStr, _ := EncryptSecret(val, aesKey) + section.Add(name, encryptStr) + } else { + section.Add(name, val) + } } else if OptionMap[name].def != "" { section.Add(name, OptionMap[name].def) } @@ -495,14 +526,23 @@ func (cc *ConfigCommand) configInteractive(configFile, language string) error { return nil } -func (cc *ConfigCommand) runCommandNonInteractive(configFile, language string) error { +func (cc *ConfigCommand) runCommandNonInteractive(configFile, language, aesKey string) error { configFile = DecideConfigFile(configFile) config := configparser.NewConfiguration() section := config.NewSection(CREDSection) section.Add(OptionLanguage, language) + if aesKey == "" { + aesKey = AesKey + } + section.Add(OptionAesKey, base64.StdEncoding.EncodeToString([]byte(aesKey))) for name := range CredOptionMap { if val, _ := GetString(name, cc.command.options); val != "" { - section.Add(name, val) + if name == OptionAccessKeyID || name == OptionAccessKeySecret || name == OptionSTSToken { + encryptStr, _ := EncryptSecret(strings.TrimSpace(val), aesKey) + section.Add(name, encryptStr) + } else { + section.Add(name, val) + } } } if err := configparser.Save(config, configFile); err != nil { diff --git a/lib/config_helper.go b/lib/config_helper.go index 23ae19e0..30478c3f 100644 --- a/lib/config_helper.go +++ b/lib/config_helper.go @@ -1,6 +1,7 @@ package lib import ( + "encoding/base64" "fmt" "os" "strconv" @@ -56,6 +57,7 @@ var CredOptionList = []string{ // name, allow to show in screen var CredOptionMap = map[string]configOption{ OptionLanguage: configOption{[]string{"language", "Language"}, false, true, "", ""}, + OptionAesKey: configOption{[]string{"aesKey", "aeskey", "aes-key", "aes_key"}, false, true, "", ""}, OptionEndpoint: configOption{[]string{"endpoint", "host"}, true, true, "", ""}, OptionAccessKeyID: configOption{[]string{"accessKeyID", "accessKeyId", "AccessKeyID", "AccessKeyId", "access_key_id", "access_id", "accessid", "access-key-id", "access-id"}, true, false, "", ""}, OptionAccessKeySecret: configOption{[]string{"accessKeySecret", "AccessKeySecret", "access_key_secret", "access_key", "accesskey", "access-key-secret", "access-key"}, true, false, "", ""}, @@ -114,6 +116,10 @@ func LoadConfig(configFile string) (OptionMapType, error) { func readConfigFromFile(configFile string) (OptionMapType, error) { configFile = DecideConfigFile(configFile) + aesKeyBase64, _ := readOptionValueFromFile(configFile, OptionAesKey) + aesKeyByte, _ := base64.StdEncoding.DecodeString(aesKeyBase64) + aesKey := string(aesKeyByte) + config, err := configparser.Read(configFile) if err != nil { return nil, err @@ -142,10 +148,21 @@ func readConfigFromFile(configFile string) (OptionMapType, error) { //added //configMap[CREDSection] = map[string]string{} - for name, option := range credOptions { if opName, ok := getOptionNameByStr(strings.TrimSpace(name)); ok { - configMap[strings.TrimSpace(opName)] = strings.TrimSpace(option) + if opName == OptionAccessKeyID || opName == OptionAccessKeySecret || opName == OptionSTSToken { + if aesKey == "" { + return nil, fmt.Errorf("aesKey is empty") + } + decryptStr, _ := DecryptSecret(strings.TrimSpace(option), aesKey) + configMap[strings.TrimSpace(opName)] = decryptStr + } else { + if opName == OptionAesKey { + configMap[OptionAesKey] = aesKey + } else { + configMap[strings.TrimSpace(opName)] = strings.TrimSpace(option) + } + } } else { configMap[strings.TrimSpace(name)] = strings.TrimSpace(option) } @@ -199,6 +216,30 @@ func readLoglevelFromFile(configFile string) (string, error) { return "", nil } +// get option val from config file +func readOptionValueFromFile(configFile, optionName string) (string, error) { + configFile = DecideConfigFile(configFile) + config, err := configparser.Read(configFile) + if err != nil { + return "", err + } + sectionNameList := []string{CREDSection, DefaultSection} + logConfig := CredOptionMap[optionName] + for _, sectionName := range sectionNameList { + section, err := config.Section(sectionName) + if err != nil { + continue + } + for _, name := range logConfig.showNames { + val := section.ValueOf(name) + if val != "" { + return val, nil + } + } + } + return "", nil +} + func getOptionNameByStr(name string) (string, bool) { for optionName, option := range CredOptionMap { for _, val := range option.showNames { diff --git a/lib/config_test.go b/lib/config_test.go index 21e4e738..8f6aefa7 100644 --- a/lib/config_test.go +++ b/lib/config_test.go @@ -1,6 +1,7 @@ package lib import ( + "encoding/base64" "fmt" "io" "io/ioutil" @@ -53,11 +54,13 @@ func (s *OssutilConfigSuite) TestConfigNonInteractive(c *C) { accessKeyID := "ak" accessKeySecret := "sk" stsToken := "token" + aesKey := "aesKey" options := OptionMapType{ "endpoint": &endpoint, "accessKeyID": &accessKeyID, "accessKeySecret": &accessKeySecret, "stsToken": &stsToken, + "aesKey": &aesKey, "configFile": &configFile, } showElapse, err := cm.RunCommand(command, args, options) @@ -70,49 +73,19 @@ func (s *OssutilConfigSuite) TestConfigNonInteractive(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 5) + c.Assert(len(opts), Equals, 6) c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) c.Assert(opts[OptionEndpoint], Equals, endpoint) c.Assert(opts[OptionAccessKeyID], Equals, accessKeyID) c.Assert(opts[OptionAccessKeySecret], Equals, accessKeySecret) c.Assert(opts[OptionSTSToken], Equals, stsToken) + c.Assert(opts[OptionAesKey], Equals, aesKey) os.Remove(configFile) } -func (s *OssutilConfigSuite) TestConfigNonInteractiveWithAgent(c *C) { - command := "config" - var args []string - userAgent := "demo-walker" - stsToken := "token" - options := OptionMapType{ - "endpoint": &endpoint, - "accessKeyID": &accessKeyID, - "accessKeySecret": &accessKeySecret, - "userAgent": &userAgent, - "stsToken": &stsToken, - "configFile": &configFile, - } - showElapse, err := cm.RunCommand(command, args, options) - c.Assert(showElapse, Equals, false) - c.Assert(err, IsNil) - - f, err := os.Stat(configFile) - c.Assert(err, IsNil) - c.Assert(f.Size() > 0, Equals, true) - - opts, err := LoadConfig(configFile) - c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 5) - c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) - c.Assert(opts[OptionEndpoint], Equals, endpoint) - c.Assert(opts[OptionAccessKeyID], Equals, accessKeyID) - c.Assert(opts[OptionAccessKeySecret], Equals, accessKeySecret) - c.Assert(opts[OptionUserAgent], Equals, nil) - c.Assert(opts[OptionSTSToken], Equals, stsToken) -} - func (s *OssutilConfigSuite) TestConfigNonInteractiveLanguage(c *C) { command := "config" + aesKey := "aesKey" var args []string for _, language := range []string{DefaultLanguage, EnglishLanguage, LEnglishLanguage} { configFile := randStr(10) @@ -122,6 +95,7 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveLanguage(c *C) { "endpoint": &endpoint, "stsToken": &stsToken, "configFile": &configFile, + "aesKey": &aesKey, "language": &language, } showElapse, err := cm.RunCommand(command, args, options) @@ -134,10 +108,11 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveLanguage(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 3) + c.Assert(len(opts), Equals, 4) c.Assert(opts[OptionEndpoint], Equals, endpoint) c.Assert(opts[OptionSTSToken], Equals, stsToken) c.Assert(opts[OptionLanguage], Equals, language) + c.Assert(opts[OptionAesKey], Equals, aesKey) os.Remove(configFile) } } @@ -159,8 +134,9 @@ func (s *OssutilConfigSuite) TestConfigInteractive(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 1) + c.Assert(len(opts), Equals, 2) c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) + c.Assert(opts[OptionAesKey], Equals, AesKey) os.Remove(configFile) } @@ -184,7 +160,7 @@ func (s *OssutilConfigSuite) TestConfigInteractiveLanguage(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 1) + c.Assert(len(opts), Equals, 2) os.Remove(configFile) } @@ -207,8 +183,9 @@ func (s *OssutilConfigSuite) TestConfigLanguageEN(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 1) + c.Assert(len(opts), Equals, 2) c.Assert(opts[OptionLanguage], Equals, language) + c.Assert(opts[OptionAesKey], Equals, AesKey) os.Remove(configFile) } @@ -231,8 +208,9 @@ func (s *OssutilConfigSuite) TestConfigLanguageCH(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 1) + c.Assert(len(opts), Equals, 2) c.Assert(opts[OptionLanguage], Equals, language) + c.Assert(opts[OptionAesKey], Equals, AesKey) os.Remove(configFile) } @@ -245,11 +223,13 @@ func (s *OssutilConfigSuite) TestConfigOptionEmptyValue(c *C) { id := "" accessKeySecret := "sk" stsToken := "token" + aesKey := "aesKey" options := OptionMapType{ "endpoint": &endp, "accessKeyID": &id, "accessKeySecret": &accessKeySecret, "stsToken": &stsToken, + "aesKey": &aesKey, "configFile": &configFile, } showElapse, err := cm.RunCommand(command, args, options) @@ -262,12 +242,13 @@ func (s *OssutilConfigSuite) TestConfigOptionEmptyValue(c *C) { opts, err := LoadConfig(configFile) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 3) + c.Assert(len(opts), Equals, 4) c.Assert(opts[OptionEndpoint], IsNil) c.Assert(opts[OptionAccessKeyID], IsNil) c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) c.Assert(opts[OptionAccessKeySecret], Equals, accessKeySecret) c.Assert(opts[OptionSTSToken], Equals, stsToken) + c.Assert(opts[OptionAesKey], Equals, aesKey) os.Remove(configFile) } @@ -300,12 +281,12 @@ func (s *OssutilConfigSuite) TestConfigInvalidOption(c *C) { } func (s *OssutilConfigSuite) TestConfigNotConfigFile(c *C) { - configCommand.runCommandInteractive("", LEnglishLanguage) + configCommand.runCommandInteractive("", LEnglishLanguage, "") contents, _ := ioutil.ReadFile(logPath) LogContent := string(contents) c.Assert(strings.Contains(LogContent, "Please enter the config file name"), Equals, true) - configCommand.runCommandInteractive("", ChineseLanguage) + configCommand.runCommandInteractive("", ChineseLanguage, "") contents, _ = ioutil.ReadFile(logPath) LogContent = string(contents) c.Assert(strings.Contains(LogContent, "请输入配置文件名"), Equals, true) @@ -326,7 +307,7 @@ func (s *OssutilConfigSuite) TestConfigConfigInteractive(c *C) { oldStdin := os.Stdin os.Stdin = inputFile - err := configCommand.configInteractive(configFileName, LEnglishLanguage) + err := configCommand.configInteractive(configFileName, LEnglishLanguage, "") c.Assert(err, IsNil) fileData, err := ioutil.ReadFile(configFileName) @@ -374,12 +355,18 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveWithCommonOption(c *C) { region := "oss-cn-chengdu.aliyuncs.com" cloudboxId := "12124123" retryTimes := "400" + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + entstsToken, _ := EncryptSecret(strings.TrimSpace(stsToken), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) data := "[Credentials]" + "\n" + "language=" + DefaultLanguage + "\n" + - "accessKeyID=" + accessKeyID + "\n" + - "accessKeySecret=" + accessKeySecret + "\n" + + "accessKeyID=" + entAccessKeyID + "\n" + + "accessKeySecret=" + entAccessKeySecret + "\n" + "endpoint=" + endpoint + "\n" + - "stsToken=" + stsToken + "\n" + + "stsToken=" + entstsToken + "\n" + + "aesKey=" + aesKeyBase64 + "\n" + "[Default]" + "\n" + "loglevel=" + logLevel + "\n" + "userAgent=" + userAgent + "\n" + @@ -396,11 +383,11 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveWithCommonOption(c *C) { f, err := os.Stat(cfile) c.Assert(err, IsNil) c.Assert(f.Size() > 0, Equals, true) - opts, err := LoadConfig(cfile) - testLogger.Print(opts) + + testLogger.Println(opts) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 13) + c.Assert(len(opts), Equals, 14) c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) c.Assert(opts[OptionEndpoint], Equals, endpoint) c.Assert(opts[OptionAccessKeyID], Equals, accessKeyID) @@ -415,7 +402,7 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveWithCommonOption(c *C) { c.Assert(opts[OptionProxyPwd], Equals, proxyPwd) c.Assert(opts[OptionReadTimeout], Equals, readTimeout) c.Assert(opts[OptionConnectTimeout], Equals, connectTimeOut) - + c.Assert(opts[OptionAesKey], Equals, aesKey) c.Assert(opts[OptionMode], Equals, nil) c.Assert(opts[OptionRamRoleArn], Equals, nil) c.Assert(opts[OptionTokenTimeout], Equals, nil) @@ -435,10 +422,11 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveWithCommonOption(c *C) { stsRegion1 := "sts.cn-hangzhou.aliyuncs.com" data = "[Credentials]" + "\n" + "language=" + DefaultLanguage + "\n" + - "accessKeyID=" + accessKeyID + "\n" + - "accessKeySecret=" + accessKeySecret + "\n" + + "aesKey=" + aesKeyBase64 + "\n" + + "accessKeyID=" + entAccessKeyID + "\n" + + "accessKeySecret=" + entAccessKeySecret + "\n" + "endpoint=" + endpoint + "\n" + - "stsToken=" + stsToken + "\n" + + "stsToken=" + entstsToken + "\n" + "ecsRoleName=" + ecsRoleName1 + "\n" + "tokenTimeout=" + tokenTimeout1 + "\n" + "ramRoleArn=" + ramRoleArn1 + "\n" + @@ -464,8 +452,10 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveWithCommonOption(c *C) { opts, err = LoadConfig(cfile) testLogger.Print(opts) + c.Log(opts) + testLogger.Println(opts) c.Assert(err, IsNil) - c.Assert(len(opts), Equals, 21) + c.Assert(len(opts), Equals, 22) c.Assert(opts[OptionLanguage], Equals, DefaultLanguage) c.Assert(opts[OptionEndpoint], Equals, endpoint) c.Assert(opts[OptionAccessKeyID], Equals, accessKeyID) @@ -480,7 +470,7 @@ func (s *OssutilConfigSuite) TestConfigNonInteractiveWithCommonOption(c *C) { c.Assert(opts[OptionProxyPwd], Equals, proxyPwd) c.Assert(opts[OptionReadTimeout], Equals, readTimeout) c.Assert(opts[OptionConnectTimeout], Equals, connectTimeOut) - + c.Assert(opts[OptionAesKey], Equals, aesKey) c.Assert(opts[OptionMode], Equals, mode) c.Assert(opts[OptionRamRoleArn], Equals, ramRoleArn1) c.Assert(opts[OptionTokenTimeout], Equals, tokenTimeout1) diff --git a/lib/const.go b/lib/const.go index 4982621f..03aaf3a0 100644 --- a/lib/const.go +++ b/lib/const.go @@ -104,6 +104,7 @@ const ( OptionRegion = "region" OptionCloudBoxID = "cloudBoxID" OptionQueryParam = "queryParam" + OptionAesKey = "aesKey" OptionForcePathStyle = "forcePathStyle" OptionRuntime = "runtime" ) diff --git a/lib/cp_test.go b/lib/cp_test.go index af610502..9014b92e 100644 --- a/lib/cp_test.go +++ b/lib/cp_test.go @@ -1,6 +1,7 @@ package lib import ( + "encoding/base64" "fmt" "hash/fnv" "io/ioutil" @@ -1265,7 +1266,12 @@ func (s *OssutilCommandSuite) TestBatchUploadOutputDir(c *C) { // err copy -> outputdir cfile := configFile configFile = randStr(10) - data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n", "abc", accessKeyID, accessKeySecret) + + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n", "abc", entAccessKeyID, entAccessKeySecret, aesKeyBase64) s.createFile(configFile, data, c) testResultFile, _ = os.OpenFile(resultPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0664) out := os.Stdout @@ -1345,7 +1351,11 @@ func (s *OssutilCommandSuite) TestDownloadOutputDir(c *C) { // err copy without -r -> no outputdir cfile := configFile configFile = randStr(10) - data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n[Bucket-Cname]\n%s=%s", endpoint, accessKeyID, accessKeySecret, bucketName, "abc") + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n[Bucket-Cname]\n%s=%s", endpoint, entAccessKeyID, entAccessKeySecret, aesKeyBase64, bucketName, "abc") s.createFile(configFile, data, c) showElapse, err = s.rawCPWithOutputDir(CloudURLToString(bucketName, object), downloadFileName, false, true, false, 1, dir) @@ -1408,7 +1418,11 @@ func (s *OssutilCommandSuite) TestCopyOutputDir(c *C) { // list err copy without -r -> no outputdir cfile := configFile configFile = randStr(10) - data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n[Bucket-Cname]\n%s=%s", endpoint, accessKeyID, accessKeySecret, srcBucket, "abc") + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n[Bucket-Cname]\n%s=%s", endpoint, entAccessKeyID, entAccessKeySecret, aesKeyBase64, srcBucket, "abc") s.createFile(configFile, data, c) showElapse, err = s.rawCPWithOutputDir(CloudURLToString(srcBucket, object), CloudURLToString(destBucket, object), false, true, false, 1, dir) c.Assert(err, NotNil) @@ -1475,6 +1489,7 @@ func (s *OssutilCommandSuite) TestBatchCopyOutputDir(c *C) { cfile := configFile configFile = randStr(10) data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n[Bucket-Endpoint]\n%s=%s[Bucket-Cname]\n%s=%s", "abc", "def", "ghi", srcBucket, "abc", srcBucket, "abc") + s.createFile(configFile, data, c) showElapse, err = s.rawCPWithOutputDir(CloudURLToString(srcBucket, ""), CloudURLToString(destBucket, ""), true, true, false, 1, dir) @@ -1511,7 +1526,12 @@ func (s *OssutilCommandSuite) TestConfigOutputDir(c *C) { // err copy -> outputdir cfile := configFile configFile = randStr(10) - data = fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n[Bucket-Cname]\n%s=%s", endpoint, accessKeyID, accessKeySecret, bucketName, "abc") + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data = fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n[Bucket-Cname]\n%s=%s", endpoint, entAccessKeyID, entAccessKeySecret, aesKeyBase64, bucketName, "abc") + s.createFile(configFile, data, c) showElapse, err := s.rawCPWithOutputDir(ufile, CloudURLToString(bucketName, object), true, true, false, 1, edir) @@ -1528,7 +1548,8 @@ func (s *OssutilCommandSuite) TestConfigOutputDir(c *C) { os.RemoveAll(DefaultOutputDir) // config outputdir - data = fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\noutputDir=%s\n[Bucket-Endpoint]\n%s=%s[Bucket-Cname]\n%s=%s", endpoint, accessKeyID, accessKeySecret, dir, bucketName, endpoint, bucketName, "abc") + data = fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naesKey=%s\naccessKeySecret=%s\noutputDir=%s\n[Bucket-Endpoint]\n%s=%s[Bucket-Cname]\n%s=%s", endpoint, entAccessKeyID, aesKeyBase64, entAccessKeySecret, dir, bucketName, endpoint, bucketName, "abc") + s.createFile(configFile, data, c) showElapse, err = s.rawCPWithOutputDir(ufile, CloudURLToString(bucketName, object), true, true, false, 1, "") diff --git a/lib/monitor_test.go b/lib/monitor_test.go index 5d671110..5e5aee52 100644 --- a/lib/monitor_test.go +++ b/lib/monitor_test.go @@ -2,6 +2,7 @@ package lib import ( "bytes" + "encoding/base64" "fmt" "os" "strings" @@ -372,7 +373,12 @@ func (s *OssutilCommandSuite) TestProgressBarContinueErr(c *C) { cfile := configFile configFile = randStr(10) - data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n", "abc", accessKeyID, accessKeySecret) + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + entAccessKeySecret, _ := EncryptSecret(strings.TrimSpace(accessKeySecret), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n", "abc", entAccessKeyID, entAccessKeySecret, aesKeyBase64) + s.createFile(configFile, data, c) err = s.initCopyCommand(udir, CloudURLToString(bucketName, ""), true, true, false, DefaultBigFileThreshold, CheckpointDir, DefaultOutputDir) @@ -670,7 +676,11 @@ func (s *OssutilCommandSuite) TestSetACLProgress(c *C) { // batch set acl list error cfile := configFile configFile = randStr(10) - data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n", endpoint, accessKeyID, "") + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n", endpoint, entAccessKeyID, "", aesKeyBase64) + s.createFile(configFile, data, c) err = s.initSetACL(bucketName, "TestSetACLProgress", "private", true, false, true) @@ -787,7 +797,10 @@ func (s *OssutilCommandSuite) TestSetMetaProgress(c *C) { // batch set acl list error cfile := configFile configFile = randStr(10) - data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\n", endpoint, accessKeyID, "") + aesKey := AesKey + entAccessKeyID, _ := EncryptSecret(strings.TrimSpace(accessKeyID), aesKey) + aesKeyBase64 := base64.StdEncoding.EncodeToString([]byte(AesKey)) + data := fmt.Sprintf("[Credentials]\nendpoint=%s\naccessKeyID=%s\naccessKeySecret=%s\naesKey=%s\n", endpoint, entAccessKeyID, "", aesKeyBase64) s.createFile(configFile, data, c) err = s.initSetMeta(bucketName, prefix, "x-oss-object-acl:default#X-Oss-Meta-A:A", true, false, true, true, DefaultLanguage) diff --git a/lib/option.go b/lib/option.go index 99f53168..e33d5741 100644 --- a/lib/option.go +++ b/lib/option.go @@ -281,6 +281,9 @@ var OptionMap = map[string]Option{ OptionQueryParam: Option{"", "--query-param", "", OptionTypeStrings, "", "", "设置请求的query参数", "Set the query parameters for the request"}, + OptionAesKey: Option{"", "--aes-key", "", OptionTypeString, "", "", + "加密ak/sk的公钥", + " the AES key of encrypt ak/sk"}, OptionForcePathStyle: Option{"", "--force-path-style", "", OptionTypeFlagTrue, "", "", "使用 path style 访问方式", "Use path-style access "}, diff --git a/lib/util.go b/lib/util.go index d31cf536..5bb53032 100644 --- a/lib/util.go +++ b/lib/util.go @@ -2,8 +2,15 @@ package lib import ( "bytes" + "crypto/aes" + "crypto/cipher" + rd "crypto/rand" + "encoding/base64" "fmt" + oss "github.com/aliyun/aliyun-oss-go-sdk/oss" + "golang.org/x/crypto/ssh/terminal" "hash" + "io" "io/ioutil" "math/rand" "os" @@ -14,9 +21,6 @@ import ( "strconv" "strings" "time" - - oss "github.com/aliyun/aliyun-oss-go-sdk/oss" - "golang.org/x/crypto/ssh/terminal" ) var sys_name string @@ -736,3 +740,204 @@ func AddStringsToOption(params []string, options []oss.Option) ([]oss.Option, er } return options, nil } + +const ( + AesKey = "ossutil-secret" + AesBlockSize = 16 +) + +func DecryptSecret(encode, aesKey string) (decryptStr string, err error) { + decode, err := base64.StdEncoding.DecodeString(encode) + if err != nil { + return "", err + } + tool := NewAesTool([]byte(aesKey), AesBlockSize, ECB) + decrypt, err := tool.Decrypt(decode) + decryptStr = string(decrypt) + return decryptStr, err +} + +func EncryptSecret(src, aesKey string) (encode string, err error) { + tool := NewAesTool([]byte(aesKey), AesBlockSize, ECB) + encrypt, err := tool.Encrypt([]byte(src)) + encode = base64.StdEncoding.EncodeToString(encrypt) + return encode, err +} + +const ( + ECB = 1 + CBC = 2 +) + +// AesTool AES ECB mode encryption and decryption +type AesTool struct { + Key []byte + BlockSize int + Mode int +} + +func NewAesTool(key []byte, blockSize int, mode int) *AesTool { + return &AesTool{Key: key, BlockSize: blockSize, Mode: mode} +} + +/** +Note: 0 fill mode +*/ +func (tool *AesTool) padding(src []byte) []byte { + paddingCount := aes.BlockSize - len(src)%aes.BlockSize + if paddingCount == 0 { + return src + } else { + return append(src, bytes.Repeat([]byte{byte(0)}, paddingCount)...) + } +} + +// unPadding +func (tool *AesTool) unPadding(src []byte) []byte { + for i := len(src) - 1; i >= 0; i-- { + if src[i] != 0 { + return src[:i+1] + } + } + return nil +} + +func (tool *AesTool) Encrypt(src []byte) ([]byte, error) { + var encryptData []byte + key := tool.padding(tool.Key) + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return nil, err + } + // padding + src = tool.padding(src) + + switch tool.Mode { + case ECB: + encryptData = make([]byte, len(src)) + mode := NewECBEncrypter(block) + mode.CryptBlocks(encryptData, src) + break + case CBC: + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + encryptData := make([]byte, aes.BlockSize+len(src)) + iv := encryptData[:aes.BlockSize] + if _, err := io.ReadFull(rd.Reader, iv); err != nil { + panic(err) + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(encryptData[aes.BlockSize:], src) + break + } + + return encryptData, nil + +} +func (tool *AesTool) Decrypt(src []byte) (res []byte, err error) { + defer func() { + if err1 := recover(); err1 != nil { + err = fmt.Errorf(fmt.Sprintf("%v", err1)) + } + }() + + key := tool.padding(tool.Key) + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return nil, err + } + + switch tool.Mode { + case ECB: + mode := NewECBDecrypter(block) + // CryptBlocks can work in-place if the two arguments are the same. + mode.CryptBlocks(src, src) + break + case CBC: + iv := src[:aes.BlockSize] + src = src[aes.BlockSize:] + if len(src)%aes.BlockSize != 0 { + panic("ciphertext is not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + // CryptBlocks can work in-place if the two arguments are the same. + mode.CryptBlocks(src, src) + break + } + + return tool.unPadding(src), nil +} + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Electronic Code Book (ECB) mode. + +// ECB provides confidentiality by assigning a fixed ciphertext block to each +// plaintext block. + +// See NIST SP 800-38A, pp 08-09 + +type ecb struct { + b cipher.Block + blockSize int +} + +func newECB(b cipher.Block) *ecb { + return &ecb{ + b: b, + blockSize: b.BlockSize(), + } +} + +type ecbEncrypter ecb + +// NewECBEncrypter returns a BlockMode which encrypts in electronic code book +// mode, using the given Block. +func NewECBEncrypter(b cipher.Block) cipher.BlockMode { + return (*ecbEncrypter)(newECB(b)) +} + +func (x *ecbEncrypter) BlockSize() int { return x.blockSize } + +func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + x.b.Encrypt(dst, src[:x.blockSize]) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + } +} + +type ecbDecrypter ecb + +// NewECBDecrypter returns a BlockMode which decrypts in electronic code book +// mode, using the given Block. +func NewECBDecrypter(b cipher.Block) cipher.BlockMode { + return (*ecbDecrypter)(newECB(b)) +} + +func (x *ecbDecrypter) BlockSize() int { return x.blockSize } + +func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + x.b.Decrypt(dst, src[:x.blockSize]) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + } +}