configuration.go raw

   1  // Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates.  All rights reserved.
   2  // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
   3  
   4  package common
   5  
   6  import (
   7  	"crypto/rsa"
   8  	"errors"
   9  	"fmt"
  10  	"io/ioutil"
  11  	"os"
  12  	"path/filepath"
  13  	"regexp"
  14  	"strings"
  15  	"sync"
  16  )
  17  
  18  // AuthenticationType for auth
  19  type AuthenticationType string
  20  
  21  const (
  22  	// UserPrincipal is default auth type
  23  	UserPrincipal AuthenticationType = "user_principal"
  24  	// InstancePrincipal is used for instance principal auth type
  25  	InstancePrincipal AuthenticationType = "instance_principal"
  26  	// InstancePrincipalDelegationToken is used for instance principal delegation token auth type
  27  	InstancePrincipalDelegationToken AuthenticationType = "instance_principle_delegation_token"
  28  	// ResourcePrincipalDelegationToken is used for resource principal delegation token auth type
  29  	ResourcePrincipalDelegationToken AuthenticationType = "resource_principle_delegation_token"
  30  	// OAuth2DelegationToken is used for oauth delegation token auth type
  31  	OAuthDelegationToken AuthenticationType = "oauth_delegation_token"
  32  	// UnknownAuthenticationType is used for none meaningful auth type
  33  	UnknownAuthenticationType AuthenticationType = "unknown_auth_type"
  34  )
  35  
  36  // AuthConfig is used for getting auth related paras in config file
  37  type AuthConfig struct {
  38  	AuthType AuthenticationType
  39  	// IsFromConfigFile is used to point out if the authConfig is from configuration file
  40  	IsFromConfigFile bool
  41  	OboToken         *string
  42  }
  43  
  44  // ConfigurationProvider wraps information about the account owner
  45  type ConfigurationProvider interface {
  46  	KeyProvider
  47  	TenancyOCID() (string, error)
  48  	UserOCID() (string, error)
  49  	KeyFingerprint() (string, error)
  50  	Region() (string, error)
  51  	// AuthType() is used for specify the needed auth type, like UserPrincipal, InstancePrincipal, etc.
  52  	AuthType() (AuthConfig, error)
  53  }
  54  
  55  var fileMutex = sync.Mutex{}
  56  var fileCache = make(map[string][]byte)
  57  
  58  // Reads the file contents from cache if present otherwise reads the file.
  59  // If file to be read is frequently updated/refreshed, please use readFile(filename) as readFileFromCache(filename) might return the old contents from the cache.
  60  func readFileFromCache(filename string) ([]byte, error) {
  61  	fileMutex.Lock()
  62  	defer fileMutex.Unlock()
  63  	val, ok := fileCache[filename]
  64  	if ok {
  65  		return val, nil
  66  	}
  67  	val, err := ioutil.ReadFile(filename)
  68  	if err == nil {
  69  		fileCache[filename] = val
  70  	}
  71  	return val, err
  72  }
  73  
  74  // Reads the file and returns the contents
  75  func readFile(filename string) ([]byte, error) {
  76  	fileMutex.Lock()
  77  	defer fileMutex.Unlock()
  78  	val, err := os.ReadFile(filename)
  79  	return val, err
  80  }
  81  
  82  // IsConfigurationProviderValid Tests all parts of the configuration provider do not return an error, this method will
  83  // not check AuthType(), since authType() is not required to be there.
  84  func IsConfigurationProviderValid(conf ConfigurationProvider) (ok bool, err error) {
  85  	baseFn := []func() (string, error){conf.TenancyOCID, conf.UserOCID, conf.KeyFingerprint, conf.Region, conf.KeyID}
  86  	for _, fn := range baseFn {
  87  		_, err = fn()
  88  		ok = err == nil
  89  		if err != nil {
  90  			return
  91  		}
  92  	}
  93  
  94  	_, err = conf.PrivateRSAKey()
  95  	ok = err == nil
  96  	if err != nil {
  97  		return
  98  	}
  99  	return true, nil
 100  }
 101  
 102  // rawConfigurationProvider allows a user to simply construct a configuration provider from raw values.
 103  type rawConfigurationProvider struct {
 104  	tenancy              string
 105  	user                 string
 106  	region               string
 107  	fingerprint          string
 108  	privateKey           string
 109  	privateKeyPassphrase *string
 110  }
 111  
 112  // NewRawConfigurationProvider will create a ConfigurationProvider with the arguments of the function
 113  func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) ConfigurationProvider {
 114  	return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase}
 115  }
 116  
 117  func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
 118  	return PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase)
 119  }
 120  
 121  func (p rawConfigurationProvider) KeyID() (keyID string, err error) {
 122  	tenancy, err := p.TenancyOCID()
 123  	if err != nil {
 124  		return
 125  	}
 126  
 127  	user, err := p.UserOCID()
 128  	if err != nil {
 129  		return
 130  	}
 131  
 132  	fingerprint, err := p.KeyFingerprint()
 133  	if err != nil {
 134  		return
 135  	}
 136  
 137  	return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
 138  }
 139  
 140  func (p rawConfigurationProvider) TenancyOCID() (string, error) {
 141  	if p.tenancy == "" {
 142  		return "", fmt.Errorf("tenancy OCID can not be empty")
 143  	}
 144  	return p.tenancy, nil
 145  }
 146  
 147  func (p rawConfigurationProvider) UserOCID() (string, error) {
 148  	if p.user == "" {
 149  		return "", fmt.Errorf("user OCID can not be empty")
 150  	}
 151  	return p.user, nil
 152  }
 153  
 154  func (p rawConfigurationProvider) KeyFingerprint() (string, error) {
 155  	if p.fingerprint == "" {
 156  		return "", fmt.Errorf("fingerprint can not be empty")
 157  	}
 158  	return p.fingerprint, nil
 159  }
 160  
 161  func (p rawConfigurationProvider) Region() (string, error) {
 162  	return canStringBeRegion(p.region)
 163  }
 164  
 165  func (p rawConfigurationProvider) AuthType() (AuthConfig, error) {
 166  	return AuthConfig{UnknownAuthenticationType, false, nil}, nil
 167  }
 168  
 169  // environmentConfigurationProvider reads configuration from environment variables
 170  type environmentConfigurationProvider struct {
 171  	PrivateKeyPassword        string
 172  	EnvironmentVariablePrefix string
 173  }
 174  
 175  // ConfigurationProviderEnvironmentVariables creates a ConfigurationProvider from a uniform set of environment variables starting with a prefix
 176  // The env variables should look like: [prefix]_private_key_path, [prefix]_tenancy_ocid, [prefix]_user_ocid, [prefix]_fingerprint
 177  // [prefix]_region
 178  func ConfigurationProviderEnvironmentVariables(environmentVariablePrefix, privateKeyPassword string) ConfigurationProvider {
 179  	return environmentConfigurationProvider{EnvironmentVariablePrefix: environmentVariablePrefix,
 180  		PrivateKeyPassword: privateKeyPassword}
 181  }
 182  
 183  func (p environmentConfigurationProvider) String() string {
 184  	return fmt.Sprintf("Configuration provided by environment variables prefixed with: %s", p.EnvironmentVariablePrefix)
 185  }
 186  
 187  func (p environmentConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
 188  	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "private_key_path")
 189  	var ok bool
 190  	var value string
 191  	if value, ok = os.LookupEnv(environmentVariable); !ok {
 192  		return nil, fmt.Errorf("can not read PrivateKey from env variable: %s", environmentVariable)
 193  	}
 194  
 195  	expandedPath := expandPath(value)
 196  	pemFileContent, err := readFileFromCache(expandedPath)
 197  	if err != nil {
 198  		Debugln("Can not read PrivateKey location from environment variable: " + environmentVariable)
 199  		return
 200  	}
 201  
 202  	key, err = PrivateKeyFromBytes(pemFileContent, &p.PrivateKeyPassword)
 203  	return
 204  }
 205  
 206  func (p environmentConfigurationProvider) KeyID() (keyID string, err error) {
 207  	ocid, err := p.TenancyOCID()
 208  	if err != nil {
 209  		return
 210  	}
 211  
 212  	userocid, err := p.UserOCID()
 213  	if err != nil {
 214  		return
 215  	}
 216  
 217  	fingerprint, err := p.KeyFingerprint()
 218  	if err != nil {
 219  		return
 220  	}
 221  
 222  	return fmt.Sprintf("%s/%s/%s", ocid, userocid, fingerprint), nil
 223  }
 224  
 225  func (p environmentConfigurationProvider) TenancyOCID() (value string, err error) {
 226  	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "tenancy_ocid")
 227  	var ok bool
 228  	if value, ok = os.LookupEnv(environmentVariable); !ok {
 229  		err = fmt.Errorf("can not read Tenancy from environment variable %s", environmentVariable)
 230  	} else if value == "" {
 231  		err = fmt.Errorf("tenancy OCID can not be empty when reading from environmental variable")
 232  	}
 233  	return
 234  }
 235  
 236  func (p environmentConfigurationProvider) UserOCID() (value string, err error) {
 237  	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "user_ocid")
 238  	var ok bool
 239  	if value, ok = os.LookupEnv(environmentVariable); !ok {
 240  		err = fmt.Errorf("can not read user id from environment variable %s", environmentVariable)
 241  	} else if value == "" {
 242  		err = fmt.Errorf("user OCID can not be empty when reading from environmental variable")
 243  	}
 244  	return
 245  }
 246  
 247  func (p environmentConfigurationProvider) KeyFingerprint() (value string, err error) {
 248  	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "fingerprint")
 249  	var ok bool
 250  	if value, ok = os.LookupEnv(environmentVariable); !ok {
 251  		err = fmt.Errorf("can not read fingerprint from environment variable %s", environmentVariable)
 252  	} else if value == "" {
 253  		err = fmt.Errorf("fingerprint can not be empty when reading from environmental variable")
 254  	}
 255  	return
 256  }
 257  
 258  func (p environmentConfigurationProvider) Region() (value string, err error) {
 259  	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "region")
 260  	var ok bool
 261  	if value, ok = os.LookupEnv(environmentVariable); !ok {
 262  		err = fmt.Errorf("can not read region from environment variable %s", environmentVariable)
 263  		return value, err
 264  	}
 265  
 266  	return canStringBeRegion(value)
 267  }
 268  
 269  func (p environmentConfigurationProvider) AuthType() (AuthConfig, error) {
 270  	return AuthConfig{UnknownAuthenticationType, false, nil},
 271  		fmt.Errorf("unsupported, keep the interface")
 272  }
 273  
 274  // fileConfigurationProvider. reads configuration information from a file
 275  type fileConfigurationProvider struct {
 276  	//The path to the configuration file
 277  	ConfigPath string
 278  
 279  	//The password for the private key
 280  	PrivateKeyPassword string
 281  
 282  	//The profile for the configuration
 283  	Profile string
 284  
 285  	//ConfigFileInfo
 286  	FileInfo *configFileInfo
 287  
 288  	//Mutex to protect the config file
 289  	configMux sync.Mutex
 290  }
 291  
 292  type fileConfigurationProviderError struct {
 293  	err error
 294  }
 295  
 296  func (fpe fileConfigurationProviderError) Error() string {
 297  	return fmt.Sprintf("%s\nFor more info about config file and how to get required information, see https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm", fpe.err)
 298  }
 299  
 300  // ConfigurationProviderFromFile creates a configuration provider from a configuration file
 301  // by reading the "DEFAULT" profile
 302  func ConfigurationProviderFromFile(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) {
 303  	if configFilePath == "" {
 304  		return nil, fmt.Errorf("config file path can not be empty")
 305  	}
 306  
 307  	return fileConfigurationProvider{
 308  		ConfigPath:         configFilePath,
 309  		PrivateKeyPassword: privateKeyPassword,
 310  		Profile:            "DEFAULT",
 311  		configMux:          sync.Mutex{}}, nil
 312  }
 313  
 314  // ConfigurationProviderFromFileWithProfile creates a configuration provider from a configuration file
 315  // and the given profile
 316  func ConfigurationProviderFromFileWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) {
 317  	if configFilePath == "" {
 318  		return nil, fileConfigurationProviderError{err: fmt.Errorf("config file path can not be empty")}
 319  	}
 320  
 321  	return fileConfigurationProvider{
 322  		ConfigPath:         configFilePath,
 323  		PrivateKeyPassword: privateKeyPassword,
 324  		Profile:            profile,
 325  		configMux:          sync.Mutex{}}, nil
 326  }
 327  
 328  type configFileInfo struct {
 329  	UserOcid, Fingerprint, KeyFilePath, TenancyOcid, Region, Passphrase, SecurityTokenFilePath, DelegationTokenFilePath,
 330  	AuthenticationType string
 331  	PresentConfiguration rune
 332  }
 333  
 334  const (
 335  	hasTenancy = 1 << iota
 336  	hasUser
 337  	hasFingerprint
 338  	hasRegion
 339  	hasKeyFile
 340  	hasPassphrase
 341  	hasSecurityTokenFile
 342  	hasDelegationTokenFile
 343  	hasAuthenticationType
 344  	none
 345  )
 346  
 347  var profileRegex = regexp.MustCompile(`^\[(.*)\]`)
 348  
 349  func parseConfigFile(data []byte, profile string) (info *configFileInfo, err error) {
 350  
 351  	if len(data) == 0 {
 352  		return nil, fileConfigurationProviderError{err: fmt.Errorf("configuration file content is empty")}
 353  	}
 354  
 355  	content := string(data)
 356  	splitContent := strings.Split(content, "\n")
 357  
 358  	//Look for profile
 359  	for i, line := range splitContent {
 360  		if match := profileRegex.FindStringSubmatch(line); len(match) > 1 && match[1] == profile {
 361  			start := i + 1
 362  			return parseConfigAtLine(start, splitContent)
 363  		}
 364  	}
 365  
 366  	return nil, fileConfigurationProviderError{err: fmt.Errorf("configuration file did not contain profile: %s", profile)}
 367  }
 368  
 369  func parseConfigAtLine(start int, content []string) (info *configFileInfo, err error) {
 370  	var configurationPresent rune
 371  	info = &configFileInfo{}
 372  	for i := start; i < len(content); i++ {
 373  		line := content[i]
 374  		if profileRegex.MatchString(line) {
 375  			break
 376  		}
 377  
 378  		if !strings.Contains(line, "=") {
 379  			continue
 380  		}
 381  
 382  		splits := strings.Split(line, "=")
 383  		switch key, value := strings.TrimSpace(splits[0]), strings.TrimSpace(splits[1]); strings.ToLower(key) {
 384  		case "passphrase", "pass_phrase":
 385  			configurationPresent = configurationPresent | hasPassphrase
 386  			info.Passphrase = value
 387  		case "user":
 388  			configurationPresent = configurationPresent | hasUser
 389  			info.UserOcid = value
 390  		case "fingerprint":
 391  			configurationPresent = configurationPresent | hasFingerprint
 392  			info.Fingerprint = value
 393  		case "key_file":
 394  			configurationPresent = configurationPresent | hasKeyFile
 395  			info.KeyFilePath = value
 396  		case "tenancy":
 397  			configurationPresent = configurationPresent | hasTenancy
 398  			info.TenancyOcid = value
 399  		case "region":
 400  			configurationPresent = configurationPresent | hasRegion
 401  			info.Region = value
 402  		case "security_token_file":
 403  			configurationPresent = configurationPresent | hasSecurityTokenFile
 404  			info.SecurityTokenFilePath = value
 405  		case "delegation_token_file":
 406  			configurationPresent = configurationPresent | hasDelegationTokenFile
 407  			info.DelegationTokenFilePath = value
 408  		case "authentication_type":
 409  			configurationPresent = configurationPresent | hasAuthenticationType
 410  			info.AuthenticationType = value
 411  		}
 412  	}
 413  	info.PresentConfiguration = configurationPresent
 414  	return
 415  
 416  }
 417  
 418  // cleans and expands the path if it contains a tilde , returns the expanded path or the input path as is if not expansion
 419  // was performed
 420  func expandPath(filename string) (expandedPath string) {
 421  	cleanedPath := filepath.Clean(filename)
 422  	expandedPath = cleanedPath
 423  	if strings.HasPrefix(cleanedPath, "~") {
 424  		rest := cleanedPath[2:]
 425  		expandedPath = filepath.Join(getHomeFolder(), rest)
 426  	}
 427  	return
 428  }
 429  
 430  func openConfigFile(configFilePath string) (data []byte, err error) {
 431  	expandedPath := expandPath(configFilePath)
 432  	data, err = readFileFromCache(expandedPath)
 433  	if err != nil {
 434  		err = fmt.Errorf("can not read config file: %s due to: %s", configFilePath, err.Error())
 435  	}
 436  
 437  	return
 438  }
 439  
 440  func (p fileConfigurationProvider) String() string {
 441  	return fmt.Sprintf("Configuration provided by file: %s", p.ConfigPath)
 442  }
 443  
 444  func (p fileConfigurationProvider) readAndParseConfigFile() (info *configFileInfo, err error) {
 445  	p.configMux.Lock()
 446  	defer p.configMux.Unlock()
 447  	if p.FileInfo != nil {
 448  		return p.FileInfo, nil
 449  	}
 450  
 451  	if p.ConfigPath == "" {
 452  		return nil, fileConfigurationProviderError{err: fmt.Errorf("configuration path can not be empty")}
 453  	}
 454  
 455  	data, err := openConfigFile(p.ConfigPath)
 456  	if err != nil {
 457  		err = fileConfigurationProviderError{err: fmt.Errorf("error while parsing config file: %s. Due to: %s", p.ConfigPath, err.Error())}
 458  		return
 459  	}
 460  
 461  	p.FileInfo, err = parseConfigFile(data, p.Profile)
 462  	return p.FileInfo, err
 463  }
 464  
 465  func presentOrError(value string, expectedConf, presentConf rune, confMissing string) (string, error) {
 466  	if presentConf&expectedConf == expectedConf {
 467  		return value, nil
 468  	}
 469  	return "", fileConfigurationProviderError{err: errors.New(confMissing + " configuration is missing from file")}
 470  }
 471  
 472  func (p fileConfigurationProvider) TenancyOCID() (value string, err error) {
 473  	info, err := p.readAndParseConfigFile()
 474  	if err != nil {
 475  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
 476  		return
 477  	}
 478  
 479  	value, err = presentOrError(info.TenancyOcid, hasTenancy, info.PresentConfiguration, "tenancy")
 480  	if err == nil && value == "" {
 481  		err = fileConfigurationProviderError{err: fmt.Errorf("tenancy OCID can not be empty when reading from config file")}
 482  	}
 483  	return
 484  }
 485  
 486  func (p fileConfigurationProvider) UserOCID() (value string, err error) {
 487  	info, err := p.readAndParseConfigFile()
 488  	if err != nil {
 489  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
 490  		return
 491  	}
 492  
 493  	if value, err = presentOrError(info.UserOcid, hasUser, info.PresentConfiguration, "user"); err != nil {
 494  		// need to check if securityTokenPath is provided, if security token is provided, userOCID can be "".
 495  		if _, stErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration,
 496  			"securityTokenPath"); stErr == nil {
 497  			err = nil
 498  		}
 499  	}
 500  	return
 501  }
 502  
 503  func (p fileConfigurationProvider) KeyFingerprint() (value string, err error) {
 504  	info, err := p.readAndParseConfigFile()
 505  	if err != nil {
 506  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
 507  		return
 508  	}
 509  	value, err = presentOrError(info.Fingerprint, hasFingerprint, info.PresentConfiguration, "fingerprint")
 510  	if err == nil && value == "" {
 511  		return "", fmt.Errorf("fingerprint can not be empty when reading from config file")
 512  	}
 513  	return
 514  }
 515  
 516  func (p fileConfigurationProvider) KeyID() (keyID string, err error) {
 517  	tenancy, err := p.TenancyOCID()
 518  	if err != nil {
 519  		return
 520  	}
 521  
 522  	fingerprint, err := p.KeyFingerprint()
 523  	if err != nil {
 524  		return
 525  	}
 526  
 527  	info, err := p.readAndParseConfigFile()
 528  	if err != nil {
 529  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
 530  		return
 531  	}
 532  	if info.PresentConfiguration&hasUser == hasUser {
 533  		if info.UserOcid == "" {
 534  			err = fileConfigurationProviderError{err: fmt.Errorf("user cannot be empty in the config file")}
 535  			return
 536  		}
 537  		return fmt.Sprintf("%s/%s/%s", tenancy, info.UserOcid, fingerprint), nil
 538  	}
 539  	filePath, pathErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration, "securityTokenFilePath")
 540  	if pathErr == nil {
 541  		rawString, err := getTokenContent(filePath)
 542  		if err != nil {
 543  			return "", fileConfigurationProviderError{err: err}
 544  		}
 545  		return "ST$" + rawString, nil
 546  	}
 547  	err = fileConfigurationProviderError{err: fmt.Errorf("can not read SecurityTokenFilePath from configuration file due to: %s", pathErr.Error())}
 548  	return
 549  }
 550  
 551  func (p fileConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
 552  	info, err := p.readAndParseConfigFile()
 553  	if err != nil {
 554  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
 555  		return
 556  	}
 557  
 558  	filePath, err := presentOrError(info.KeyFilePath, hasKeyFile, info.PresentConfiguration, "key file path")
 559  	if err != nil {
 560  		return
 561  	}
 562  
 563  	expandedPath := expandPath(filePath)
 564  	pemFileContent, err := readFileFromCache(expandedPath)
 565  	if err != nil {
 566  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read PrivateKey  from configuration file due to: %s", err.Error())}
 567  		return
 568  	}
 569  
 570  	password := p.PrivateKeyPassword
 571  
 572  	if password == "" && ((info.PresentConfiguration & hasPassphrase) == hasPassphrase) {
 573  		password = info.Passphrase
 574  	}
 575  
 576  	key, err = PrivateKeyFromBytes(pemFileContent, &password)
 577  	return
 578  }
 579  
 580  func (p fileConfigurationProvider) Region() (value string, err error) {
 581  	info, err := p.readAndParseConfigFile()
 582  	if err != nil {
 583  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read region configuration due to: %s", err.Error())}
 584  		return
 585  	}
 586  
 587  	value, err = presentOrError(info.Region, hasRegion, info.PresentConfiguration, "region")
 588  	if err != nil {
 589  		val, error := getRegionFromEnvVar()
 590  		if error != nil {
 591  			err = fileConfigurationProviderError{err: fmt.Errorf("region configuration is missing from file, nor for OCI_REGION env var")}
 592  			return
 593  		}
 594  		value = val
 595  	}
 596  
 597  	return canStringBeRegion(value)
 598  }
 599  
 600  func (p fileConfigurationProvider) AuthType() (AuthConfig, error) {
 601  	info, err := p.readAndParseConfigFile()
 602  	if err != nil {
 603  		err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
 604  		return AuthConfig{UnknownAuthenticationType, true, nil}, err
 605  	}
 606  	val, _ := presentOrError(info.AuthenticationType, hasAuthenticationType, info.PresentConfiguration, "authentication_type")
 607  
 608  	if val == "instance_principal" {
 609  		if filePath, err := presentOrError(info.DelegationTokenFilePath, hasDelegationTokenFile, info.PresentConfiguration, "delegationTokenFilePath"); err == nil {
 610  			if delegationToken, err := getTokenContent(filePath); err == nil && delegationToken != "" {
 611  				Debugf("delegation token content is %s, and error is %s ", delegationToken, err)
 612  				return AuthConfig{InstancePrincipalDelegationToken, true, &delegationToken}, nil
 613  			}
 614  			return AuthConfig{UnknownAuthenticationType, true, nil}, err
 615  
 616  		}
 617  		// normal instance principle
 618  		return AuthConfig{InstancePrincipal, true, nil}, nil
 619  	}
 620  
 621  	// by default, if no "authentication_type" is provided, just treated as user principle type, and will not return error
 622  	return AuthConfig{UserPrincipal, true, nil}, nil
 623  }
 624  
 625  func getTokenContent(filePath string) (string, error) {
 626  	expandedPath := expandPath(filePath)
 627  	tokenFileContent, err := readFile(expandedPath)
 628  	if err != nil {
 629  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read token content from configuration file due to: %s", err.Error())}
 630  		return "", err
 631  	}
 632  	return string(tokenFileContent), nil
 633  }
 634  
 635  // A configuration provider that look for information in  multiple configuration providers
 636  type composingConfigurationProvider struct {
 637  	Providers []ConfigurationProvider
 638  }
 639  
 640  // ComposingConfigurationProvider creates a composing configuration provider with the given slice of configuration providers
 641  // A composing provider will return the configuration of the first provider that has the required property
 642  // if no provider has the property it will return an error.
 643  func ComposingConfigurationProvider(providers []ConfigurationProvider) (ConfigurationProvider, error) {
 644  	if len(providers) == 0 {
 645  		return nil, fmt.Errorf("providers can not be an empty slice")
 646  	}
 647  
 648  	for i, p := range providers {
 649  		if p == nil {
 650  			return nil, fmt.Errorf("provider in position: %d is nil. ComposingConfiurationProvider does not support nil values", i)
 651  		}
 652  	}
 653  	return composingConfigurationProvider{Providers: providers}, nil
 654  }
 655  
 656  func (c composingConfigurationProvider) TenancyOCID() (string, error) {
 657  	for _, p := range c.Providers {
 658  		val, err := p.TenancyOCID()
 659  		if err == nil {
 660  			return val, nil
 661  		}
 662  		Debugf("did not find a proper configuration for tenancy, err: %v", err)
 663  	}
 664  	return "", fmt.Errorf("did not find a proper configuration for tenancy")
 665  }
 666  
 667  func (c composingConfigurationProvider) UserOCID() (string, error) {
 668  	for _, p := range c.Providers {
 669  		val, err := p.UserOCID()
 670  		if err == nil {
 671  			return val, nil
 672  		}
 673  		Debugf("did not find a proper configuration for keyFingerprint, err: %v", err)
 674  	}
 675  	return "", fmt.Errorf("did not find a proper configuration for user")
 676  }
 677  
 678  func (c composingConfigurationProvider) KeyFingerprint() (string, error) {
 679  	for _, p := range c.Providers {
 680  		val, err := p.KeyFingerprint()
 681  		if err == nil {
 682  			return val, nil
 683  		}
 684  	}
 685  	return "", fmt.Errorf("did not find a proper configuration for keyFingerprint")
 686  }
 687  func (c composingConfigurationProvider) Region() (string, error) {
 688  	for _, p := range c.Providers {
 689  		val, err := p.Region()
 690  		if err == nil {
 691  			return val, nil
 692  		}
 693  	}
 694  	if val, err := getRegionFromEnvVar(); err == nil {
 695  		return val, nil
 696  	}
 697  	return "", fmt.Errorf("did not find a proper configuration for region, nor for OCI_REGION env var")
 698  }
 699  
 700  func (c composingConfigurationProvider) KeyID() (string, error) {
 701  	for _, p := range c.Providers {
 702  		val, err := p.KeyID()
 703  		if err == nil {
 704  			return val, nil
 705  		}
 706  	}
 707  	return "", fmt.Errorf("did not find a proper configuration for key id")
 708  }
 709  
 710  func (c composingConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
 711  	for _, p := range c.Providers {
 712  		val, err := p.PrivateRSAKey()
 713  		if err == nil {
 714  			return val, nil
 715  		}
 716  	}
 717  	return nil, fmt.Errorf("did not find a proper configuration for private key")
 718  }
 719  
 720  func (c composingConfigurationProvider) AuthType() (AuthConfig, error) {
 721  	// only check the first default fileConfigProvider
 722  	authConfig, err := c.Providers[0].AuthType()
 723  	if err == nil && authConfig.AuthType != UnknownAuthenticationType {
 724  		return authConfig, nil
 725  	}
 726  	return AuthConfig{UnknownAuthenticationType, false, nil}, fmt.Errorf("did not find a proper configuration for auth type")
 727  }
 728  
 729  func getRegionFromEnvVar() (string, error) {
 730  	regionEnvVar := "OCI_REGION"
 731  	if region, existed := os.LookupEnv(regionEnvVar); existed {
 732  		return region, nil
 733  	}
 734  	return "", fmt.Errorf("did not find OCI_REGION env var")
 735  }
 736  
 737  type sessionTokenConfigurationProvider struct {
 738  	fileConfigurationProvider
 739  }
 740  
 741  func (p sessionTokenConfigurationProvider) UserOCID() (value string, err error) {
 742  	info, err := p.readAndParseConfigFile()
 743  	if err != nil {
 744  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read the configuration due to: %s", err.Error())}
 745  		return
 746  	}
 747  	// In case of session token-based authentication, userOCID will not be present
 748  	// need to check if session token path is provided in the configuration
 749  	if _, stErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration,
 750  		"securityTokenPath"); stErr == nil {
 751  		err = nil
 752  	}
 753  	return
 754  }
 755  
 756  func (p sessionTokenConfigurationProvider) KeyID() (keyID string, err error) {
 757  	_, err = p.TenancyOCID()
 758  	if err != nil {
 759  		return
 760  	}
 761  
 762  	_, err = p.KeyFingerprint()
 763  	if err != nil {
 764  		return
 765  	}
 766  
 767  	info, err := p.readAndParseConfigFile()
 768  	if err != nil {
 769  		err = fileConfigurationProviderError{err: fmt.Errorf("can not read SessionTokenFilePath configuration due to: %s", err.Error())}
 770  		return
 771  	}
 772  
 773  	filePath, pathErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration, "securityTokenFilePath")
 774  	if pathErr == nil {
 775  		rawString, err := getTokenContent(filePath)
 776  		if err != nil {
 777  			return "", fileConfigurationProviderError{err: err}
 778  		}
 779  		return "ST$" + rawString, nil
 780  	}
 781  	err = fileConfigurationProviderError{err: fmt.Errorf("can not read SessionTokenFilePath from configuration file due to: %s", pathErr.Error())}
 782  	return
 783  }
 784  
 785  // ConfigurationProviderForSessionToken creates a session token configuration provider from a configuration file
 786  // by reading the "DEFAULT" profile
 787  func ConfigurationProviderForSessionToken(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) {
 788  	if configFilePath == "" {
 789  		return nil, fileConfigurationProviderError{err: fmt.Errorf("config file path can not be empty")}
 790  	}
 791  
 792  	return sessionTokenConfigurationProvider{
 793  		fileConfigurationProvider{
 794  			ConfigPath:         configFilePath,
 795  			PrivateKeyPassword: privateKeyPassword,
 796  			Profile:            "DEFAULT",
 797  			configMux:          sync.Mutex{}}}, nil
 798  }
 799  
 800  // ConfigurationProviderForSessionTokenWithProfile creates a session token configuration provider from a configuration file
 801  // by reading the given profile
 802  func ConfigurationProviderForSessionTokenWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) {
 803  	if configFilePath == "" {
 804  		return nil, fileConfigurationProviderError{err: fmt.Errorf("config file path can not be empty")}
 805  	}
 806  
 807  	return sessionTokenConfigurationProvider{
 808  		fileConfigurationProvider{
 809  			ConfigPath:         configFilePath,
 810  			PrivateKeyPassword: privateKeyPassword,
 811  			Profile:            profile,
 812  			configMux:          sync.Mutex{}}}, nil
 813  }
 814  
 815  func (p sessionTokenConfigurationProvider) Refreshable() bool {
 816  	return true
 817  }
 818  
 819  // RefreshableConfigurationProvider the interface to identity if the config provider is refreshable
 820  type RefreshableConfigurationProvider interface {
 821  	Refreshable() bool
 822  }
 823