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  }