| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- // Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
- // 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.
- package common
- import (
- "crypto/rsa"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "regexp"
- "strings"
- )
- // ConfigurationProvider wraps information about the account owner
- type ConfigurationProvider interface {
- KeyProvider
- TenancyOCID() (string, error)
- UserOCID() (string, error)
- KeyFingerprint() (string, error)
- Region() (string, error)
- }
- // IsConfigurationProviderValid Tests all parts of the configuration provider do not return an error
- func IsConfigurationProviderValid(conf ConfigurationProvider) (ok bool, err error) {
- baseFn := []func() (string, error){conf.TenancyOCID, conf.UserOCID, conf.KeyFingerprint, conf.Region, conf.KeyID}
- for _, fn := range baseFn {
- _, err = fn()
- ok = err == nil
- if err != nil {
- return
- }
- }
- _, err = conf.PrivateRSAKey()
- ok = err == nil
- if err != nil {
- return
- }
- return true, nil
- }
- // rawConfigurationProvider allows a user to simply construct a configuration provider from raw values.
- type rawConfigurationProvider struct {
- tenancy string
- user string
- region string
- fingerprint string
- privateKey string
- privateKeyPassphrase *string
- }
- // NewRawConfigurationProvider will create a ConfigurationProvider with the arguments of the function
- func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) ConfigurationProvider {
- return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase}
- }
- func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
- return PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase)
- }
- func (p rawConfigurationProvider) KeyID() (keyID string, err error) {
- tenancy, err := p.TenancyOCID()
- if err != nil {
- return
- }
- user, err := p.UserOCID()
- if err != nil {
- return
- }
- fingerprint, err := p.KeyFingerprint()
- if err != nil {
- return
- }
- return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
- }
- func (p rawConfigurationProvider) TenancyOCID() (string, error) {
- if p.tenancy == "" {
- return "", fmt.Errorf("tenancy OCID can not be empty")
- }
- return p.tenancy, nil
- }
- func (p rawConfigurationProvider) UserOCID() (string, error) {
- if p.user == "" {
- return "", fmt.Errorf("user OCID can not be empty")
- }
- return p.user, nil
- }
- func (p rawConfigurationProvider) KeyFingerprint() (string, error) {
- if p.fingerprint == "" {
- return "", fmt.Errorf("fingerprint can not be empty")
- }
- return p.fingerprint, nil
- }
- func (p rawConfigurationProvider) Region() (string, error) {
- return canStringBeRegion(p.region)
- }
- // environmentConfigurationProvider reads configuration from environment variables
- type environmentConfigurationProvider struct {
- PrivateKeyPassword string
- EnvironmentVariablePrefix string
- }
- // ConfigurationProviderEnvironmentVariables creates a ConfigurationProvider from a uniform set of environment variables starting with a prefix
- // The env variables should look like: [prefix]_private_key_path, [prefix]_tenancy_ocid, [prefix]_user_ocid, [prefix]_fingerprint
- // [prefix]_region
- func ConfigurationProviderEnvironmentVariables(environmentVariablePrefix, privateKeyPassword string) ConfigurationProvider {
- return environmentConfigurationProvider{EnvironmentVariablePrefix: environmentVariablePrefix,
- PrivateKeyPassword: privateKeyPassword}
- }
- func (p environmentConfigurationProvider) String() string {
- return fmt.Sprintf("Configuration provided by environment variables prefixed with: %s", p.EnvironmentVariablePrefix)
- }
- func (p environmentConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
- environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "private_key_path")
- var ok bool
- var value string
- if value, ok = os.LookupEnv(environmentVariable); !ok {
- return nil, fmt.Errorf("can not read PrivateKey from env variable: %s", environmentVariable)
- }
- expandedPath := expandPath(value)
- pemFileContent, err := ioutil.ReadFile(expandedPath)
- if err != nil {
- Debugln("Can not read PrivateKey location from environment variable: " + environmentVariable)
- return
- }
- key, err = PrivateKeyFromBytes(pemFileContent, &p.PrivateKeyPassword)
- return
- }
- func (p environmentConfigurationProvider) KeyID() (keyID string, err error) {
- ocid, err := p.TenancyOCID()
- if err != nil {
- return
- }
- userocid, err := p.UserOCID()
- if err != nil {
- return
- }
- fingerprint, err := p.KeyFingerprint()
- if err != nil {
- return
- }
- return fmt.Sprintf("%s/%s/%s", ocid, userocid, fingerprint), nil
- }
- func (p environmentConfigurationProvider) TenancyOCID() (value string, err error) {
- environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "tenancy_ocid")
- var ok bool
- if value, ok = os.LookupEnv(environmentVariable); !ok {
- err = fmt.Errorf("can not read Tenancy from environment variable %s", environmentVariable)
- }
- return
- }
- func (p environmentConfigurationProvider) UserOCID() (value string, err error) {
- environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "user_ocid")
- var ok bool
- if value, ok = os.LookupEnv(environmentVariable); !ok {
- err = fmt.Errorf("can not read user id from environment variable %s", environmentVariable)
- }
- return
- }
- func (p environmentConfigurationProvider) KeyFingerprint() (value string, err error) {
- environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "fingerprint")
- var ok bool
- if value, ok = os.LookupEnv(environmentVariable); !ok {
- err = fmt.Errorf("can not read fingerprint from environment variable %s", environmentVariable)
- }
- return
- }
- func (p environmentConfigurationProvider) Region() (value string, err error) {
- environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "region")
- var ok bool
- if value, ok = os.LookupEnv(environmentVariable); !ok {
- err = fmt.Errorf("can not read region from environment variable %s", environmentVariable)
- return value, err
- }
- return canStringBeRegion(value)
- }
- // fileConfigurationProvider. reads configuration information from a file
- type fileConfigurationProvider struct {
- //The path to the configuration file
- ConfigPath string
- //The password for the private key
- PrivateKeyPassword string
- //The profile for the configuration
- Profile string
- //ConfigFileInfo
- FileInfo *configFileInfo
- }
- // ConfigurationProviderFromFile creates a configuration provider from a configuration file
- // by reading the "DEFAULT" profile
- func ConfigurationProviderFromFile(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) {
- if configFilePath == "" {
- return nil, fmt.Errorf("config file path can not be empty")
- }
- return fileConfigurationProvider{
- ConfigPath: configFilePath,
- PrivateKeyPassword: privateKeyPassword,
- Profile: "DEFAULT"}, nil
- }
- // ConfigurationProviderFromFileWithProfile creates a configuration provider from a configuration file
- // and the given profile
- func ConfigurationProviderFromFileWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) {
- if configFilePath == "" {
- return nil, fmt.Errorf("config file path can not be empty")
- }
- return fileConfigurationProvider{
- ConfigPath: configFilePath,
- PrivateKeyPassword: privateKeyPassword,
- Profile: profile}, nil
- }
- type configFileInfo struct {
- UserOcid, Fingerprint, KeyFilePath, TenancyOcid, Region, Passphrase, SecurityTokenFilePath string
- PresentConfiguration byte
- }
- const (
- hasTenancy = 1 << iota
- hasUser
- hasFingerprint
- hasRegion
- hasKeyFile
- hasPassphrase
- hasSecurityTokenFile
- none
- )
- var profileRegex = regexp.MustCompile(`^\[(.*)\]`)
- func parseConfigFile(data []byte, profile string) (info *configFileInfo, err error) {
- if len(data) == 0 {
- return nil, fmt.Errorf("configuration file content is empty")
- }
- content := string(data)
- splitContent := strings.Split(content, "\n")
- //Look for profile
- for i, line := range splitContent {
- if match := profileRegex.FindStringSubmatch(line); match != nil && len(match) > 1 && match[1] == profile {
- start := i + 1
- return parseConfigAtLine(start, splitContent)
- }
- }
- return nil, fmt.Errorf("configuration file did not contain profile: %s", profile)
- }
- func parseConfigAtLine(start int, content []string) (info *configFileInfo, err error) {
- var configurationPresent byte
- info = &configFileInfo{}
- for i := start; i < len(content); i++ {
- line := content[i]
- if profileRegex.MatchString(line) {
- break
- }
- if !strings.Contains(line, "=") {
- continue
- }
- splits := strings.Split(line, "=")
- switch key, value := strings.TrimSpace(splits[0]), strings.TrimSpace(splits[1]); strings.ToLower(key) {
- case "passphrase", "pass_phrase":
- configurationPresent = configurationPresent | hasPassphrase
- info.Passphrase = value
- case "user":
- configurationPresent = configurationPresent | hasUser
- info.UserOcid = value
- case "fingerprint":
- configurationPresent = configurationPresent | hasFingerprint
- info.Fingerprint = value
- case "key_file":
- configurationPresent = configurationPresent | hasKeyFile
- info.KeyFilePath = value
- case "tenancy":
- configurationPresent = configurationPresent | hasTenancy
- info.TenancyOcid = value
- case "region":
- configurationPresent = configurationPresent | hasRegion
- info.Region = value
- case "security_token_file":
- configurationPresent = configurationPresent | hasSecurityTokenFile
- info.SecurityTokenFilePath = value
- }
- }
- info.PresentConfiguration = configurationPresent
- return
- }
- // cleans and expands the path if it contains a tilde , returns the expanded path or the input path as is if not expansion
- // was performed
- func expandPath(filepath string) (expandedPath string) {
- cleanedPath := path.Clean(filepath)
- expandedPath = cleanedPath
- if strings.HasPrefix(cleanedPath, "~") {
- rest := cleanedPath[2:]
- expandedPath = path.Join(getHomeFolder(), rest)
- }
- return
- }
- func openConfigFile(configFilePath string) (data []byte, err error) {
- expandedPath := expandPath(configFilePath)
- data, err = ioutil.ReadFile(expandedPath)
- if err != nil {
- err = fmt.Errorf("can not read config file: %s due to: %s", configFilePath, err.Error())
- }
- return
- }
- func (p fileConfigurationProvider) String() string {
- return fmt.Sprintf("Configuration provided by file: %s", p.ConfigPath)
- }
- func (p fileConfigurationProvider) readAndParseConfigFile() (info *configFileInfo, err error) {
- if p.FileInfo != nil {
- return p.FileInfo, nil
- }
- if p.ConfigPath == "" {
- return nil, fmt.Errorf("configuration path can not be empty")
- }
- data, err := openConfigFile(p.ConfigPath)
- if err != nil {
- err = fmt.Errorf("error while parsing config file: %s. Due to: %s", p.ConfigPath, err.Error())
- return
- }
- p.FileInfo, err = parseConfigFile(data, p.Profile)
- return p.FileInfo, err
- }
- func presentOrError(value string, expectedConf, presentConf byte, confMissing string) (string, error) {
- if presentConf&expectedConf == expectedConf {
- return value, nil
- }
- return "", errors.New(confMissing + " configuration is missing from file")
- }
- func (p fileConfigurationProvider) TenancyOCID() (value string, err error) {
- info, err := p.readAndParseConfigFile()
- if err != nil {
- err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
- return
- }
- value, err = presentOrError(info.TenancyOcid, hasTenancy, info.PresentConfiguration, "tenancy")
- return
- }
- func (p fileConfigurationProvider) UserOCID() (value string, err error) {
- info, err := p.readAndParseConfigFile()
- if err != nil {
- err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
- return
- }
- if value, err = presentOrError(info.UserOcid, hasUser, info.PresentConfiguration, "user"); err != nil {
- // need to check if securityTokenPath is provided, if security token is provided, userOCID can be "".
- if _, stErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration,
- "securityTokenPath"); stErr == nil {
- err = nil
- }
- }
- return
- }
- func (p fileConfigurationProvider) KeyFingerprint() (value string, err error) {
- info, err := p.readAndParseConfigFile()
- if err != nil {
- err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
- return
- }
- value, err = presentOrError(info.Fingerprint, hasFingerprint, info.PresentConfiguration, "fingerprint")
- return
- }
- func (p fileConfigurationProvider) KeyID() (keyID string, err error) {
- info, err := p.readAndParseConfigFile()
- if err != nil {
- err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
- return
- }
- if info.PresentConfiguration&hasUser == hasUser {
- return fmt.Sprintf("%s/%s/%s", info.TenancyOcid, info.UserOcid, info.Fingerprint), nil
- }
- if filePath, err := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration, "securityTokenFilePath"); err == nil {
- return getSecurityToken(filePath)
- }
- err = fmt.Errorf("can not read SecurityTokenFilePath from configuration file due to: %s", err.Error())
- return
- }
- func (p fileConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
- info, err := p.readAndParseConfigFile()
- if err != nil {
- err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
- return
- }
- filePath, err := presentOrError(info.KeyFilePath, hasKeyFile, info.PresentConfiguration, "key file path")
- if err != nil {
- return
- }
- expandedPath := expandPath(filePath)
- pemFileContent, err := ioutil.ReadFile(expandedPath)
- if err != nil {
- err = fmt.Errorf("can not read PrivateKey from configuration file due to: %s", err.Error())
- return
- }
- password := p.PrivateKeyPassword
- if password == "" && ((info.PresentConfiguration & hasPassphrase) == hasPassphrase) {
- password = info.Passphrase
- }
- key, err = PrivateKeyFromBytes(pemFileContent, &password)
- return
- }
- func (p fileConfigurationProvider) Region() (value string, err error) {
- info, err := p.readAndParseConfigFile()
- if err != nil {
- err = fmt.Errorf("can not read region configuration due to: %s", err.Error())
- return
- }
- value, err = presentOrError(info.Region, hasRegion, info.PresentConfiguration, "region")
- if err != nil {
- val, error := getRegionFromEnvVar()
- if error != nil {
- err = fmt.Errorf("region configuration is missing from file, nor for OCI_REGION env var")
- return
- }
- value = val
- }
- return canStringBeRegion(value)
- }
- func getSecurityToken(filePath string) (string, error) {
- expandedPath := expandPath(filePath)
- tokenFileContent, err := ioutil.ReadFile(expandedPath)
- if err != nil {
- err = fmt.Errorf("can not read PrivateKey from configuration file due to: %s", err.Error())
- return "", err
- }
- return fmt.Sprintf("ST$%s", tokenFileContent), nil
- }
- // A configuration provider that look for information in multiple configuration providers
- type composingConfigurationProvider struct {
- Providers []ConfigurationProvider
- }
- // ComposingConfigurationProvider creates a composing configuration provider with the given slice of configuration providers
- // A composing provider will return the configuration of the first provider that has the required property
- // if no provider has the property it will return an error.
- func ComposingConfigurationProvider(providers []ConfigurationProvider) (ConfigurationProvider, error) {
- if len(providers) == 0 {
- return nil, fmt.Errorf("providers can not be an empty slice")
- }
- for i, p := range providers {
- if p == nil {
- return nil, fmt.Errorf("provider in position: %d is nil. ComposingConfiurationProvider does not support nil values", i)
- }
- }
- return composingConfigurationProvider{Providers: providers}, nil
- }
- func (c composingConfigurationProvider) TenancyOCID() (string, error) {
- for _, p := range c.Providers {
- val, err := p.TenancyOCID()
- if err == nil {
- return val, nil
- }
- }
- return "", fmt.Errorf("did not find a proper configuration for tenancy")
- }
- func (c composingConfigurationProvider) UserOCID() (string, error) {
- for _, p := range c.Providers {
- val, err := p.UserOCID()
- if err == nil {
- return val, nil
- }
- }
- return "", fmt.Errorf("did not find a proper configuration for user")
- }
- func (c composingConfigurationProvider) KeyFingerprint() (string, error) {
- for _, p := range c.Providers {
- val, err := p.KeyFingerprint()
- if err == nil {
- return val, nil
- }
- }
- return "", fmt.Errorf("did not find a proper configuration for keyFingerprint")
- }
- func (c composingConfigurationProvider) Region() (string, error) {
- for _, p := range c.Providers {
- val, err := p.Region()
- if err == nil {
- return val, nil
- }
- }
- if val, err := getRegionFromEnvVar(); err == nil {
- return val, nil
- }
- return "", fmt.Errorf("did not find a proper configuration for region, nor for OCI_REGION env var")
- }
- func (c composingConfigurationProvider) KeyID() (string, error) {
- for _, p := range c.Providers {
- val, err := p.KeyID()
- if err == nil {
- return val, nil
- }
- }
- return "", fmt.Errorf("did not find a proper configuration for key id")
- }
- func (c composingConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
- for _, p := range c.Providers {
- val, err := p.PrivateRSAKey()
- if err == nil {
- return val, nil
- }
- }
- return nil, fmt.Errorf("did not find a proper configuration for private key")
- }
- func getRegionFromEnvVar() (string, error) {
- regionEnvVar := "OCI_REGION"
- if region, existed := os.LookupEnv(regionEnvVar); existed {
- return region, nil
- }
- return "", fmt.Errorf("did not find OCI_REGION env var")
- }
|