2021-03-20 02:16:17 +00:00

660 lines
21 KiB
Go

// Copyright (c) 2016, 2018, 2021, 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"
)
// AuthenticationType for auth
type AuthenticationType string
const (
// UserPrincipal is default auth type
UserPrincipal AuthenticationType = "user_principal"
// InstancePrincipal is used for instance principle auth type
InstancePrincipal AuthenticationType = "instance_principal"
// InstancePrincipalDelegationToken is used for instance principle delegation token auth type
InstancePrincipalDelegationToken AuthenticationType = "instance_principle_delegation_token"
// UnknownAuthenticationType is used for none meaningful auth type
UnknownAuthenticationType AuthenticationType = "unknown_auth_type"
)
// AuthConfig is used for getting auth related paras in config file
type AuthConfig struct {
AuthType AuthenticationType
// IsFromConfigFile is used to point out if the authConfig is from configuration file
IsFromConfigFile bool
OboToken *string
}
// ConfigurationProvider wraps information about the account owner
type ConfigurationProvider interface {
KeyProvider
TenancyOCID() (string, error)
UserOCID() (string, error)
KeyFingerprint() (string, error)
Region() (string, error)
// AuthType() is used for specify the needed auth type, like UserPrincipal, InstancePrincipal, etc.
AuthType() (AuthConfig, error)
}
// IsConfigurationProviderValid Tests all parts of the configuration provider do not return an error, this method will
// not check AuthType(), since authType() is not required to be there.
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)
}
func (p rawConfigurationProvider) AuthType() (AuthConfig, error) {
return AuthConfig{UnknownAuthenticationType, false, nil}, nil
}
// 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)
}
func (p environmentConfigurationProvider) AuthType() (AuthConfig, error) {
return AuthConfig{UnknownAuthenticationType, false, nil},
fmt.Errorf("unsupported, keep the interface")
}
// 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, DelegationTokenFilePath,
AuthenticationType string
PresentConfiguration rune
}
const (
hasTenancy = 1 << iota
hasUser
hasFingerprint
hasRegion
hasKeyFile
hasPassphrase
hasSecurityTokenFile
hasDelegationTokenFile
hasAuthenticationType
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 rune
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
case "delegation_token_file":
configurationPresent = configurationPresent | hasDelegationTokenFile
info.DelegationTokenFilePath = value
case "authentication_type":
configurationPresent = configurationPresent | hasAuthenticationType
info.AuthenticationType = 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 rune, 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 {
rawString, err := getTokenContent(filePath)
if err != nil {
return "", err
}
return "ST$" + rawString, nil
}
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 (p fileConfigurationProvider) AuthType() (AuthConfig, error) {
info, err := p.readAndParseConfigFile()
if err != nil {
err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
return AuthConfig{UnknownAuthenticationType, true, nil}, err
}
val, err := presentOrError(info.AuthenticationType, hasAuthenticationType, info.PresentConfiguration, "authentication_type")
if val == "instance_principal" {
if filePath, err := presentOrError(info.DelegationTokenFilePath, hasDelegationTokenFile, info.PresentConfiguration, "delegationTokenFilePath"); err == nil {
if delegationToken, err := getTokenContent(filePath); err == nil && delegationToken != "" {
Debugf("delegation token content is %s, and error is %s ", delegationToken, err)
return AuthConfig{InstancePrincipalDelegationToken, true, &delegationToken}, nil
}
return AuthConfig{UnknownAuthenticationType, true, nil}, err
}
// normal instance principle
return AuthConfig{InstancePrincipal, true, nil}, nil
}
// by default, if no "authentication_type" is provided, just treated as user principle type, and will not return error
return AuthConfig{UserPrincipal, true, nil}, nil
}
func getTokenContent(filePath string) (string, error) {
expandedPath := expandPath(filePath)
tokenFileContent, err := ioutil.ReadFile(expandedPath)
if err != nil {
err = fmt.Errorf("can not read token content from configuration file due to: %s", err.Error())
return "", err
}
return fmt.Sprintf("%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 (c composingConfigurationProvider) AuthType() (AuthConfig, error) {
// only check the first default fileConfigProvider
authConfig, err := c.Providers[0].AuthType()
if err == nil && authConfig.AuthType != UnknownAuthenticationType {
return authConfig, nil
}
return AuthConfig{UnknownAuthenticationType, false, nil}, fmt.Errorf("did not find a proper configuration for auth type")
}
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")
}