1// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. 2 3package common 4 5import ( 6 "crypto/rsa" 7 "errors" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path" 12 "regexp" 13 "strings" 14) 15 16// ConfigurationProvider wraps information about the account owner 17type ConfigurationProvider interface { 18 KeyProvider 19 TenancyOCID() (string, error) 20 UserOCID() (string, error) 21 KeyFingerprint() (string, error) 22 Region() (string, error) 23} 24 25// IsConfigurationProviderValid Tests all parts of the configuration provider do not return an error 26func IsConfigurationProviderValid(conf ConfigurationProvider) (ok bool, err error) { 27 baseFn := []func() (string, error){conf.TenancyOCID, conf.UserOCID, conf.KeyFingerprint, conf.Region, conf.KeyID} 28 for _, fn := range baseFn { 29 _, err = fn() 30 ok = err == nil 31 if err != nil { 32 return 33 } 34 } 35 36 _, err = conf.PrivateRSAKey() 37 ok = err == nil 38 if err != nil { 39 return 40 } 41 return true, nil 42} 43 44// rawConfigurationProvider allows a user to simply construct a configuration provider from raw values. 45type rawConfigurationProvider struct { 46 tenancy string 47 user string 48 region string 49 fingerprint string 50 privateKey string 51 privateKeyPassphrase *string 52} 53 54// NewRawConfigurationProvider will create a ConfigurationProvider with the arguments of the function 55func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) ConfigurationProvider { 56 return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase} 57} 58 59func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) { 60 return PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase) 61} 62 63func (p rawConfigurationProvider) KeyID() (keyID string, err error) { 64 tenancy, err := p.TenancyOCID() 65 if err != nil { 66 return 67 } 68 69 user, err := p.UserOCID() 70 if err != nil { 71 return 72 } 73 74 fingerprint, err := p.KeyFingerprint() 75 if err != nil { 76 return 77 } 78 79 return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil 80} 81 82func (p rawConfigurationProvider) TenancyOCID() (string, error) { 83 if p.tenancy == "" { 84 return "", fmt.Errorf("tenancy OCID can not be empty") 85 } 86 return p.tenancy, nil 87} 88 89func (p rawConfigurationProvider) UserOCID() (string, error) { 90 if p.user == "" { 91 return "", fmt.Errorf("user OCID can not be empty") 92 } 93 return p.user, nil 94} 95 96func (p rawConfigurationProvider) KeyFingerprint() (string, error) { 97 if p.fingerprint == "" { 98 return "", fmt.Errorf("fingerprint can not be empty") 99 } 100 return p.fingerprint, nil 101} 102 103func (p rawConfigurationProvider) Region() (string, error) { 104 return canStringBeRegion(p.region) 105} 106 107// environmentConfigurationProvider reads configuration from environment variables 108type environmentConfigurationProvider struct { 109 PrivateKeyPassword string 110 EnvironmentVariablePrefix string 111} 112 113// ConfigurationProviderEnvironmentVariables creates a ConfigurationProvider from a uniform set of environment variables starting with a prefix 114// The env variables should look like: [prefix]_private_key_path, [prefix]_tenancy_ocid, [prefix]_user_ocid, [prefix]_fingerprint 115// [prefix]_region 116func ConfigurationProviderEnvironmentVariables(environmentVariablePrefix, privateKeyPassword string) ConfigurationProvider { 117 return environmentConfigurationProvider{EnvironmentVariablePrefix: environmentVariablePrefix, 118 PrivateKeyPassword: privateKeyPassword} 119} 120 121func (p environmentConfigurationProvider) String() string { 122 return fmt.Sprintf("Configuration provided by environment variables prefixed with: %s", p.EnvironmentVariablePrefix) 123} 124 125func (p environmentConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) { 126 environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "private_key_path") 127 var ok bool 128 var value string 129 if value, ok = os.LookupEnv(environmentVariable); !ok { 130 return nil, fmt.Errorf("can not read PrivateKey from env variable: %s", environmentVariable) 131 } 132 133 expandedPath := expandPath(value) 134 pemFileContent, err := ioutil.ReadFile(expandedPath) 135 if err != nil { 136 Debugln("Can not read PrivateKey location from environment variable: " + environmentVariable) 137 return 138 } 139 140 key, err = PrivateKeyFromBytes(pemFileContent, &p.PrivateKeyPassword) 141 return 142} 143 144func (p environmentConfigurationProvider) KeyID() (keyID string, err error) { 145 ocid, err := p.TenancyOCID() 146 if err != nil { 147 return 148 } 149 150 userocid, err := p.UserOCID() 151 if err != nil { 152 return 153 } 154 155 fingerprint, err := p.KeyFingerprint() 156 if err != nil { 157 return 158 } 159 160 return fmt.Sprintf("%s/%s/%s", ocid, userocid, fingerprint), nil 161} 162 163func (p environmentConfigurationProvider) TenancyOCID() (value string, err error) { 164 environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "tenancy_ocid") 165 var ok bool 166 if value, ok = os.LookupEnv(environmentVariable); !ok { 167 err = fmt.Errorf("can not read Tenancy from environment variable %s", environmentVariable) 168 } 169 return 170} 171 172func (p environmentConfigurationProvider) UserOCID() (value string, err error) { 173 environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "user_ocid") 174 var ok bool 175 if value, ok = os.LookupEnv(environmentVariable); !ok { 176 err = fmt.Errorf("can not read user id from environment variable %s", environmentVariable) 177 } 178 return 179} 180 181func (p environmentConfigurationProvider) KeyFingerprint() (value string, err error) { 182 environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "fingerprint") 183 var ok bool 184 if value, ok = os.LookupEnv(environmentVariable); !ok { 185 err = fmt.Errorf("can not read fingerprint from environment variable %s", environmentVariable) 186 } 187 return 188} 189 190func (p environmentConfigurationProvider) Region() (value string, err error) { 191 environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "region") 192 var ok bool 193 if value, ok = os.LookupEnv(environmentVariable); !ok { 194 err = fmt.Errorf("can not read region from environment variable %s", environmentVariable) 195 return value, err 196 } 197 198 return canStringBeRegion(value) 199} 200 201// fileConfigurationProvider. reads configuration information from a file 202type fileConfigurationProvider struct { 203 //The path to the configuration file 204 ConfigPath string 205 206 //The password for the private key 207 PrivateKeyPassword string 208 209 //The profile for the configuration 210 Profile string 211 212 //ConfigFileInfo 213 FileInfo *configFileInfo 214} 215 216// ConfigurationProviderFromFile creates a configuration provider from a configuration file 217// by reading the "DEFAULT" profile 218func ConfigurationProviderFromFile(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) { 219 if configFilePath == "" { 220 return nil, fmt.Errorf("config file path can not be empty") 221 } 222 223 return fileConfigurationProvider{ 224 ConfigPath: configFilePath, 225 PrivateKeyPassword: privateKeyPassword, 226 Profile: "DEFAULT"}, nil 227} 228 229// ConfigurationProviderFromFileWithProfile creates a configuration provider from a configuration file 230// and the given profile 231func ConfigurationProviderFromFileWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) { 232 if configFilePath == "" { 233 return nil, fmt.Errorf("config file path can not be empty") 234 } 235 236 return fileConfigurationProvider{ 237 ConfigPath: configFilePath, 238 PrivateKeyPassword: privateKeyPassword, 239 Profile: profile}, nil 240} 241 242type configFileInfo struct { 243 UserOcid, Fingerprint, KeyFilePath, TenancyOcid, Region, Passphrase string 244 PresentConfiguration byte 245} 246 247const ( 248 hasTenancy = 1 << iota 249 hasUser 250 hasFingerprint 251 hasRegion 252 hasKeyFile 253 hasPassphrase 254 none 255) 256 257var profileRegex = regexp.MustCompile(`^\[(.*)\]`) 258 259func parseConfigFile(data []byte, profile string) (info *configFileInfo, err error) { 260 261 if len(data) == 0 { 262 return nil, fmt.Errorf("configuration file content is empty") 263 } 264 265 content := string(data) 266 splitContent := strings.Split(content, "\n") 267 268 //Look for profile 269 for i, line := range splitContent { 270 if match := profileRegex.FindStringSubmatch(line); match != nil && len(match) > 1 && match[1] == profile { 271 start := i + 1 272 return parseConfigAtLine(start, splitContent) 273 } 274 } 275 276 return nil, fmt.Errorf("configuration file did not contain profile: %s", profile) 277} 278 279func parseConfigAtLine(start int, content []string) (info *configFileInfo, err error) { 280 var configurationPresent byte 281 info = &configFileInfo{} 282 for i := start; i < len(content); i++ { 283 line := content[i] 284 if profileRegex.MatchString(line) { 285 break 286 } 287 288 if !strings.Contains(line, "=") { 289 continue 290 } 291 292 splits := strings.Split(line, "=") 293 switch key, value := strings.TrimSpace(splits[0]), strings.TrimSpace(splits[1]); strings.ToLower(key) { 294 case "passphrase", "pass_phrase": 295 configurationPresent = configurationPresent | hasPassphrase 296 info.Passphrase = value 297 case "user": 298 configurationPresent = configurationPresent | hasUser 299 info.UserOcid = value 300 case "fingerprint": 301 configurationPresent = configurationPresent | hasFingerprint 302 info.Fingerprint = value 303 case "key_file": 304 configurationPresent = configurationPresent | hasKeyFile 305 info.KeyFilePath = value 306 case "tenancy": 307 configurationPresent = configurationPresent | hasTenancy 308 info.TenancyOcid = value 309 case "region": 310 configurationPresent = configurationPresent | hasRegion 311 info.Region = value 312 } 313 } 314 info.PresentConfiguration = configurationPresent 315 return 316 317} 318 319// cleans and expands the path if it contains a tilde , returns the expanded path or the input path as is if not expansion 320// was performed 321func expandPath(filepath string) (expandedPath string) { 322 cleanedPath := path.Clean(filepath) 323 expandedPath = cleanedPath 324 if strings.HasPrefix(cleanedPath, "~") { 325 rest := cleanedPath[2:] 326 expandedPath = path.Join(getHomeFolder(), rest) 327 } 328 return 329} 330 331func openConfigFile(configFilePath string) (data []byte, err error) { 332 expandedPath := expandPath(configFilePath) 333 data, err = ioutil.ReadFile(expandedPath) 334 if err != nil { 335 err = fmt.Errorf("can not read config file: %s due to: %s", configFilePath, err.Error()) 336 } 337 338 return 339} 340 341func (p fileConfigurationProvider) String() string { 342 return fmt.Sprintf("Configuration provided by file: %s", p.ConfigPath) 343} 344 345func (p fileConfigurationProvider) readAndParseConfigFile() (info *configFileInfo, err error) { 346 if p.FileInfo != nil { 347 return p.FileInfo, nil 348 } 349 350 if p.ConfigPath == "" { 351 return nil, fmt.Errorf("configuration path can not be empty") 352 } 353 354 data, err := openConfigFile(p.ConfigPath) 355 if err != nil { 356 err = fmt.Errorf("error while parsing config file: %s. Due to: %s", p.ConfigPath, err.Error()) 357 return 358 } 359 360 p.FileInfo, err = parseConfigFile(data, p.Profile) 361 return p.FileInfo, err 362} 363 364func presentOrError(value string, expectedConf, presentConf byte, confMissing string) (string, error) { 365 if presentConf&expectedConf == expectedConf { 366 return value, nil 367 } 368 return "", errors.New(confMissing + " configuration is missing from file") 369} 370 371func (p fileConfigurationProvider) TenancyOCID() (value string, err error) { 372 info, err := p.readAndParseConfigFile() 373 if err != nil { 374 err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error()) 375 return 376 } 377 378 value, err = presentOrError(info.TenancyOcid, hasTenancy, info.PresentConfiguration, "tenancy") 379 return 380} 381 382func (p fileConfigurationProvider) UserOCID() (value string, err error) { 383 info, err := p.readAndParseConfigFile() 384 if err != nil { 385 err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error()) 386 return 387 } 388 389 value, err = presentOrError(info.UserOcid, hasUser, info.PresentConfiguration, "user") 390 return 391} 392 393func (p fileConfigurationProvider) KeyFingerprint() (value string, err error) { 394 info, err := p.readAndParseConfigFile() 395 if err != nil { 396 err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error()) 397 return 398 } 399 value, err = presentOrError(info.Fingerprint, hasFingerprint, info.PresentConfiguration, "fingerprint") 400 return 401} 402 403func (p fileConfigurationProvider) KeyID() (keyID string, err error) { 404 info, err := p.readAndParseConfigFile() 405 if err != nil { 406 err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error()) 407 return 408 } 409 410 return fmt.Sprintf("%s/%s/%s", info.TenancyOcid, info.UserOcid, info.Fingerprint), nil 411} 412 413func (p fileConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) { 414 info, err := p.readAndParseConfigFile() 415 if err != nil { 416 err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error()) 417 return 418 } 419 420 filePath, err := presentOrError(info.KeyFilePath, hasKeyFile, info.PresentConfiguration, "key file path") 421 if err != nil { 422 return 423 } 424 425 expandedPath := expandPath(filePath) 426 pemFileContent, err := ioutil.ReadFile(expandedPath) 427 if err != nil { 428 err = fmt.Errorf("can not read PrivateKey from configuration file due to: %s", err.Error()) 429 return 430 } 431 432 password := p.PrivateKeyPassword 433 434 if password == "" && ((info.PresentConfiguration & hasPassphrase) == hasPassphrase) { 435 password = info.Passphrase 436 } 437 438 key, err = PrivateKeyFromBytes(pemFileContent, &password) 439 return 440} 441 442func (p fileConfigurationProvider) Region() (value string, err error) { 443 info, err := p.readAndParseConfigFile() 444 if err != nil { 445 err = fmt.Errorf("can not read region configuration due to: %s", err.Error()) 446 return 447 } 448 449 value, err = presentOrError(info.Region, hasRegion, info.PresentConfiguration, "region") 450 if err != nil { 451 return 452 } 453 454 return canStringBeRegion(value) 455} 456 457// A configuration provider that look for information in multiple configuration providers 458type composingConfigurationProvider struct { 459 Providers []ConfigurationProvider 460} 461 462// ComposingConfigurationProvider creates a composing configuration provider with the given slice of configuration providers 463// A composing provider will return the configuration of the first provider that has the required property 464// if no provider has the property it will return an error. 465func ComposingConfigurationProvider(providers []ConfigurationProvider) (ConfigurationProvider, error) { 466 if len(providers) == 0 { 467 return nil, fmt.Errorf("providers can not be an empty slice") 468 } 469 470 for i, p := range providers { 471 if p == nil { 472 return nil, fmt.Errorf("provider in position: %d is nil. ComposingConfiurationProvider does not support nil values", i) 473 } 474 } 475 return composingConfigurationProvider{Providers: providers}, nil 476} 477 478func (c composingConfigurationProvider) TenancyOCID() (string, error) { 479 for _, p := range c.Providers { 480 val, err := p.TenancyOCID() 481 if err == nil { 482 return val, nil 483 } 484 } 485 return "", fmt.Errorf("did not find a proper configuration for tenancy") 486} 487 488func (c composingConfigurationProvider) UserOCID() (string, error) { 489 for _, p := range c.Providers { 490 val, err := p.UserOCID() 491 if err == nil { 492 return val, nil 493 } 494 } 495 return "", fmt.Errorf("did not find a proper configuration for user") 496} 497 498func (c composingConfigurationProvider) KeyFingerprint() (string, error) { 499 for _, p := range c.Providers { 500 val, err := p.KeyFingerprint() 501 if err == nil { 502 return val, nil 503 } 504 } 505 return "", fmt.Errorf("did not find a proper configuration for keyFingerprint") 506} 507func (c composingConfigurationProvider) Region() (string, error) { 508 for _, p := range c.Providers { 509 val, err := p.Region() 510 if err == nil { 511 return val, nil 512 } 513 } 514 return "", fmt.Errorf("did not find a proper configuration for region") 515} 516 517func (c composingConfigurationProvider) KeyID() (string, error) { 518 for _, p := range c.Providers { 519 val, err := p.KeyID() 520 if err == nil { 521 return val, nil 522 } 523 } 524 return "", fmt.Errorf("did not find a proper configuration for key id") 525} 526 527func (c composingConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { 528 for _, p := range c.Providers { 529 val, err := p.PrivateRSAKey() 530 if err == nil { 531 return val, nil 532 } 533 } 534 return nil, fmt.Errorf("did not find a proper configuration for private key") 535} 536