github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/artifactory/commands/config.go (about) 1 package commands 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/jfrog/jfrog-client-go/auth" 7 "io/ioutil" 8 "reflect" 9 "sync" 10 "syscall" 11 12 "github.com/jfrog/jfrog-cli-go/artifactory/commands/generic" 13 "github.com/jfrog/jfrog-cli-go/artifactory/utils" 14 "github.com/jfrog/jfrog-cli-go/utils/cliutils" 15 "github.com/jfrog/jfrog-cli-go/utils/config" 16 "github.com/jfrog/jfrog-cli-go/utils/ioutils" 17 "github.com/jfrog/jfrog-cli-go/utils/lock" 18 clientutils "github.com/jfrog/jfrog-client-go/utils" 19 "github.com/jfrog/jfrog-client-go/utils/errorutils" 20 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 21 "github.com/jfrog/jfrog-client-go/utils/log" 22 "golang.org/x/crypto/ssh/terminal" 23 ) 24 25 // Internal golang locking for the same process. 26 var mutex sync.Mutex 27 28 type ConfigCommand struct { 29 details *config.ArtifactoryDetails 30 defaultDetails *config.ArtifactoryDetails 31 interactive bool 32 encPassword bool 33 useRefreshableToken bool 34 serverId string 35 } 36 37 func NewConfigCommand() *ConfigCommand { 38 return &ConfigCommand{} 39 } 40 41 func (cc *ConfigCommand) SetServerId(serverId string) *ConfigCommand { 42 cc.serverId = serverId 43 return cc 44 } 45 46 func (cc *ConfigCommand) SetEncPassword(encPassword bool) *ConfigCommand { 47 cc.encPassword = encPassword 48 return cc 49 } 50 51 func (cc *ConfigCommand) SetUseRefreshableToken(useRefreshableToken bool) *ConfigCommand { 52 cc.useRefreshableToken = useRefreshableToken 53 return cc 54 } 55 56 func (cc *ConfigCommand) SetInteractive(interactive bool) *ConfigCommand { 57 cc.interactive = interactive 58 return cc 59 } 60 61 func (cc *ConfigCommand) SetDefaultDetails(defaultDetails *config.ArtifactoryDetails) *ConfigCommand { 62 cc.defaultDetails = defaultDetails 63 return cc 64 } 65 66 func (cc *ConfigCommand) SetDetails(details *config.ArtifactoryDetails) *ConfigCommand { 67 cc.details = details 68 return cc 69 } 70 71 func (cc *ConfigCommand) Run() error { 72 return cc.Config() 73 } 74 75 func (cc *ConfigCommand) RtDetails() (*config.ArtifactoryDetails, error) { 76 // If cc.details is not empty, then return it. 77 if cc.details != nil && !reflect.DeepEqual(config.ArtifactoryDetails{}, *cc.details) { 78 return cc.details, nil 79 } 80 // If cc.defaultDetails is not empty, then return it. 81 if cc.defaultDetails != nil && !reflect.DeepEqual(config.ArtifactoryDetails{}, *cc.defaultDetails) { 82 return cc.defaultDetails, nil 83 } 84 return nil, nil 85 } 86 87 func (cc *ConfigCommand) CommandName() string { 88 return "rt_config" 89 } 90 91 func (cc *ConfigCommand) Config() error { 92 mutex.Lock() 93 lockFile, err := lock.CreateLock() 94 defer mutex.Unlock() 95 defer lockFile.Unlock() 96 97 if err != nil { 98 return err 99 } 100 101 configurations, err := cc.prepareConfigurationData() 102 if err != nil { 103 return err 104 } 105 if cc.interactive { 106 err = cc.getConfigurationFromUser() 107 if err != nil { 108 return err 109 } 110 } 111 112 if len(configurations) == 1 { 113 cc.details.IsDefault = true 114 } 115 116 err = checkSingleAuthMethod(cc.details) 117 if err != nil { 118 return err 119 } 120 121 if cc.encPassword { 122 err = cc.encryptPassword() 123 if err != nil { 124 return err 125 } 126 } 127 128 if cc.useRefreshableToken { 129 err = cc.configRefreshableToken() 130 if err != nil { 131 return err 132 } 133 } 134 135 return config.SaveArtifactoryConf(configurations) 136 } 137 138 func (cc *ConfigCommand) configRefreshableToken() error { 139 if (cc.details.User == "" || cc.details.Password == "") && cc.details.ApiKey == "" { 140 return errors.New("refreshable token mode is only available with Username & Password or API key") 141 } 142 cc.details.TokenRefreshInterval = cliutils.TokenRefreshDefaultInterval 143 return nil 144 } 145 146 func (cc *ConfigCommand) prepareConfigurationData() ([]*config.ArtifactoryDetails, error) { 147 // If details is nil, initialize a new one 148 if cc.details == nil { 149 cc.details = new(config.ArtifactoryDetails) 150 if cc.defaultDetails != nil { 151 cc.details.InsecureTls = cc.defaultDetails.InsecureTls 152 } 153 } 154 155 // Get configurations list 156 configurations, err := config.GetAllArtifactoryConfigs() 157 if err != nil { 158 return configurations, err 159 } 160 161 // Get default server details 162 if cc.defaultDetails == nil { 163 cc.defaultDetails, err = config.GetDefaultConfiguredArtifactoryConf(configurations) 164 if err != nil { 165 return configurations, errorutils.CheckError(err) 166 } 167 } 168 169 // Get server id 170 if cc.interactive && cc.serverId == "" { 171 ioutils.ScanFromConsole("Artifactory server ID", &cc.serverId, cc.defaultDetails.ServerId) 172 } 173 cc.details.ServerId = cc.resolveServerId() 174 175 // Remove and get the server details from the configurations list 176 tempConfiguration, configurations := config.GetAndRemoveConfiguration(cc.details.ServerId, configurations) 177 178 // Change default server details if the server was exist in the configurations list 179 if tempConfiguration != nil { 180 cc.defaultDetails = tempConfiguration 181 cc.details.IsDefault = tempConfiguration.IsDefault 182 } 183 184 // Append the configuration to the configurations list 185 configurations = append(configurations, cc.details) 186 return configurations, err 187 } 188 189 /// Returning the first non empty value: 190 // 1. The serverId argument sent. 191 // 2. details.ServerId 192 // 3. defaultDetails.ServerId 193 // 4. config.DEFAULT_SERVER_ID 194 func (cc *ConfigCommand) resolveServerId() string { 195 if cc.serverId != "" { 196 return cc.serverId 197 } 198 if cc.details.ServerId != "" { 199 return cc.details.ServerId 200 } 201 if cc.defaultDetails.ServerId != "" { 202 return cc.defaultDetails.ServerId 203 } 204 return config.DefaultServerId 205 } 206 207 func (cc *ConfigCommand) getConfigurationFromUser() error { 208 allowUsingSavedPassword := true 209 // Artifactory URL 210 if cc.details.Url == "" { 211 ioutils.ScanFromConsole("JFrog Artifactory URL", &cc.details.Url, cc.defaultDetails.Url) 212 allowUsingSavedPassword = false 213 } 214 215 // Distribution URL 216 if cc.details.DistributionUrl == "" { 217 ioutils.ScanFromConsole("JFrog Distribution URL (Optional)", &cc.details.DistributionUrl, cc.defaultDetails.DistributionUrl) 218 allowUsingSavedPassword = false 219 } 220 221 // Ssh-Key 222 if fileutils.IsSshUrl(cc.details.Url) { 223 return getSshKeyPath(cc.details) 224 } 225 cc.details.Url = clientutils.AddTrailingSlashIfNeeded(cc.details.Url) 226 227 // Api-Key/Password/Access-Token 228 if cc.details.ApiKey == "" && cc.details.Password == "" && cc.details.AccessToken == "" { 229 err := readAccessTokenFromConsole(cc.details) 230 if err != nil { 231 return err 232 } 233 if len(cc.details.GetAccessToken()) == 0 { 234 err = ioutils.ReadCredentialsFromConsole(cc.details, cc.defaultDetails, allowUsingSavedPassword) 235 if err != nil { 236 return err 237 } 238 } 239 } 240 241 cc.readRefreshableTokensFromConsole() 242 cc.readClientCertInfoFromConsole() 243 return nil 244 } 245 246 func (cc *ConfigCommand) readClientCertInfoFromConsole() { 247 if cliutils.InteractiveConfirm("Is the Artifactory reverse proxy configured to accept a client certificate?") { 248 if cc.details.ClientCertPath == "" { 249 ioutils.ScanFromConsole("Client certificate file path", &cc.details.ClientCertPath, cc.defaultDetails.ClientCertPath) 250 } 251 if cc.details.ClientCertKeyPath == "" { 252 ioutils.ScanFromConsole("Client certificate key path", &cc.details.ClientCertKeyPath, cc.defaultDetails.ClientCertKeyPath) 253 } 254 } 255 } 256 257 func (cc *ConfigCommand) readRefreshableTokensFromConsole() { 258 if (cc.details.ApiKey != "" || cc.details.Password != "") && cc.details.AccessToken == "" { 259 cc.useRefreshableToken = cliutils.InteractiveConfirm("Use refreshable tokens?") 260 } 261 } 262 263 func readAccessTokenFromConsole(details *config.ArtifactoryDetails) error { 264 print("Access token (Leave blank for username and password/API key): ") 265 byteToken, err := terminal.ReadPassword(int(syscall.Stdin)) 266 if err != nil { 267 return errorutils.CheckError(err) 268 } 269 // New-line required after the access token input: 270 fmt.Println() 271 if len(byteToken) > 0 { 272 details.SetAccessToken(string(byteToken)) 273 _, err := new(generic.PingCommand).SetRtDetails(details).Ping() 274 return err 275 } 276 return nil 277 } 278 279 func getSshKeyPath(details *config.ArtifactoryDetails) error { 280 // If path not provided as a key, read from console: 281 if details.SshKeyPath == "" { 282 ioutils.ScanFromConsole("SSH key file path (optional)", &details.SshKeyPath, "") 283 } 284 285 // If path still not provided, return and warn about relying on agent. 286 if details.SshKeyPath == "" { 287 log.Info("SSH Key path not provided. You can also specify a key path using the --ssh-key-path command option. If no key will be specified, you will rely on ssh-agent only.") 288 return nil 289 } 290 291 // If SSH key path provided, check if exists: 292 details.SshKeyPath = clientutils.ReplaceTildeWithUserHome(details.SshKeyPath) 293 exists, err := fileutils.IsFileExists(details.SshKeyPath, false) 294 if err != nil { 295 return err 296 } 297 298 if exists { 299 sshKeyBytes, err := ioutil.ReadFile(details.SshKeyPath) 300 if err != nil { 301 return nil 302 } 303 encryptedKey, err := auth.IsEncrypted(sshKeyBytes) 304 // If exists and not encrypted (or error occurred), return without asking for passphrase 305 if err != nil || !encryptedKey { 306 return err 307 } 308 log.Info("The key file at the specified path is encrypted, you may pass the passphrase as an option with every command (but config).") 309 310 } else { 311 log.Info("Could not find key in provided path. You may place the key file there later. If you choose to use an encrypted key, you may pass the passphrase as an option with every command.") 312 } 313 314 return err 315 } 316 317 func ShowConfig(serverName string) error { 318 var configuration []*config.ArtifactoryDetails 319 if serverName != "" { 320 singleConfig, err := config.GetArtifactorySpecificConfig(serverName) 321 if err != nil { 322 return err 323 } 324 configuration = []*config.ArtifactoryDetails{singleConfig} 325 } else { 326 var err error 327 configuration, err = config.GetAllArtifactoryConfigs() 328 if err != nil { 329 return err 330 } 331 } 332 printConfigs(configuration) 333 return nil 334 } 335 336 func Import(serverToken string) error { 337 artifactoryDetails, err := config.Import(serverToken) 338 if err != nil { 339 return err 340 } 341 log.Info("Importing server ID", "'"+artifactoryDetails.ServerId+"'") 342 configCommand := &ConfigCommand{ 343 details: artifactoryDetails, 344 serverId: artifactoryDetails.ServerId, 345 } 346 return configCommand.Config() 347 } 348 349 func Export(serverName string) error { 350 artifactoryDetails, err := config.GetArtifactorySpecificConfig(serverName) 351 if err != nil { 352 return err 353 } 354 serverToken, err := config.Export(artifactoryDetails) 355 if err != nil { 356 return err 357 } 358 log.Output(serverToken) 359 return nil 360 } 361 362 func printConfigs(configuration []*config.ArtifactoryDetails) { 363 for _, details := range configuration { 364 if details.ServerId != "" { 365 log.Output("Server ID: " + details.ServerId) 366 } 367 if details.Url != "" { 368 log.Output("Url: " + details.Url) 369 } 370 if details.ApiKey != "" { 371 log.Output("API key: " + details.ApiKey) 372 } 373 if details.User != "" { 374 log.Output("User: " + details.User) 375 } 376 if details.Password != "" { 377 log.Output("Password: ***") 378 } 379 if details.AccessToken != "" { 380 log.Output("Access token: ***") 381 } 382 if details.RefreshToken != "" { 383 log.Output("Refresh token: ***") 384 } 385 if details.SshKeyPath != "" { 386 log.Output("SSH key file path: " + details.SshKeyPath) 387 } 388 if details.ClientCertPath != "" { 389 log.Output("Client certificate file path: " + details.ClientCertPath) 390 } 391 if details.ClientCertKeyPath != "" { 392 log.Output("Client certificate key path: " + details.ClientCertKeyPath) 393 } 394 log.Output("Default: ", details.IsDefault) 395 log.Output() 396 } 397 } 398 399 func DeleteConfig(serverName string) error { 400 configurations, err := config.GetAllArtifactoryConfigs() 401 if err != nil { 402 return err 403 } 404 var isDefault, isFoundName bool 405 for i, config := range configurations { 406 if config.ServerId == serverName { 407 isDefault = config.IsDefault 408 configurations = append(configurations[:i], configurations[i+1:]...) 409 isFoundName = true 410 break 411 } 412 413 } 414 if isDefault && len(configurations) > 0 { 415 configurations[0].IsDefault = true 416 } 417 if isFoundName { 418 return config.SaveArtifactoryConf(configurations) 419 } 420 log.Info("\"" + serverName + "\" configuration could not be found.\n") 421 return nil 422 } 423 424 // Set the default configuration 425 func Use(serverId string) error { 426 configurations, err := config.GetAllArtifactoryConfigs() 427 if err != nil { 428 return err 429 } 430 var serverFound *config.ArtifactoryDetails 431 newDefaultServer := true 432 for _, config := range configurations { 433 if config.ServerId == serverId { 434 serverFound = config 435 if config.IsDefault { 436 newDefaultServer = false 437 break 438 } 439 config.IsDefault = true 440 } else { 441 config.IsDefault = false 442 } 443 } 444 // Need to save only if we found a server with the serverId 445 if serverFound != nil { 446 if newDefaultServer { 447 err = config.SaveArtifactoryConf(configurations) 448 if err != nil { 449 return err 450 } 451 } 452 log.Info(fmt.Sprintf("Using server ID '%s' (%s).", serverFound.ServerId, serverFound.Url)) 453 return nil 454 } 455 return errorutils.CheckError(errors.New(fmt.Sprintf("Could not find a server with ID '%s'.", serverId))) 456 } 457 458 func ClearConfig(interactive bool) { 459 if interactive { 460 confirmed := cliutils.InteractiveConfirm("Are you sure you want to delete all the configurations?") 461 if !confirmed { 462 return 463 } 464 } 465 config.SaveArtifactoryConf(make([]*config.ArtifactoryDetails, 0)) 466 } 467 468 func GetConfig(serverId string) (*config.ArtifactoryDetails, error) { 469 return config.GetArtifactorySpecificConfig(serverId) 470 } 471 472 func (cc *ConfigCommand) encryptPassword() error { 473 if cc.details.Password == "" { 474 return nil 475 } 476 477 log.Info("Encrypting password...") 478 479 artAuth, err := cc.details.CreateArtAuthConfig() 480 if err != nil { 481 return err 482 } 483 encPassword, err := utils.GetEncryptedPasswordFromArtifactory(artAuth, cc.details.InsecureTls) 484 if err != nil { 485 return err 486 } 487 cc.details.Password = encPassword 488 return err 489 } 490 491 func checkSingleAuthMethod(details *config.ArtifactoryDetails) error { 492 boolArr := []bool{details.User != "" && details.Password != "", details.ApiKey != "", fileutils.IsSshUrl(details.Url), 493 details.AccessToken != "" && details.TokenRefreshInterval == cliutils.TokenRefreshDisabled} 494 if cliutils.SumTrueValues(boolArr) > 1 { 495 return errorutils.CheckError(errors.New("Only one authentication method is allowed: Username + Password/API key, RSA Token (SSH) or Access Token.")) 496 } 497 return nil 498 } 499 500 type ConfigCommandConfiguration struct { 501 ArtDetails *config.ArtifactoryDetails 502 Interactive bool 503 EncPassword bool 504 TokenRefreshInterval int 505 } 506 507 func GetAllArtifactoryServerIds() []string { 508 var serverIds []string 509 configuration, err := config.GetAllArtifactoryConfigs() 510 if err != nil { 511 return serverIds 512 } 513 for _, serverConfig := range configuration { 514 serverIds = append(serverIds, serverConfig.ServerId) 515 } 516 return serverIds 517 }