2017-09-12 11:30:39 -04:00
|
|
|
package oci
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/pem"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/go-ini/ini"
|
|
|
|
"github.com/mitchellh/go-homedir"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Config API authentication and target configuration
|
|
|
|
type Config struct {
|
|
|
|
// User OCID e.g. ocid1.user.oc1..aaaaaaaadcshyehbkvxl7arse3lv7z5oknexjgfhnhwidtugsxhlm4247
|
|
|
|
User string `ini:"user"`
|
|
|
|
|
|
|
|
// User's Tenancy OCID e.g. ocid1.tenancy.oc1..aaaaaaaagtgvshv6opxzjyzkupkt64ymd32n6kbomadanpcg43d
|
|
|
|
Tenancy string `ini:"tenancy"`
|
|
|
|
|
|
|
|
// Bare metal region identifier (e.g. us-phoenix-1)
|
|
|
|
Region string `ini:"region"`
|
|
|
|
|
|
|
|
// Hex key fingerprint (e.g. b5:a0:62:57:28:0d:fd:c9:59:16:eb:d4:51:9f:70:e4)
|
|
|
|
Fingerprint string `ini:"fingerprint"`
|
|
|
|
|
2017-09-12 11:30:39 -04:00
|
|
|
// Path to OCI config file (e.g. ~/.oci/config)
|
2017-02-13 05:35:14 -05:00
|
|
|
KeyFile string `ini:"key_file"`
|
|
|
|
|
2017-02-14 04:50:28 -05:00
|
|
|
// Passphrase used for the key, if it is encrypted.
|
|
|
|
PassPhrase string `ini:"pass_phrase"`
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
// Private key (loaded via LoadPrivateKey or ParsePrivateKey)
|
|
|
|
Key *rsa.PrivateKey
|
|
|
|
|
|
|
|
// Used to override base API URL.
|
|
|
|
baseURL string
|
|
|
|
}
|
|
|
|
|
|
|
|
// getBaseURL returns either the specified base URL or builds the appropriate
|
|
|
|
// URL based on service, region, and API version.
|
|
|
|
func (c *Config) getBaseURL(service string) string {
|
|
|
|
if c.baseURL != "" {
|
|
|
|
return c.baseURL
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(baseURLPattern, service, c.Region, apiVersion)
|
|
|
|
}
|
|
|
|
|
2017-09-12 11:30:39 -04:00
|
|
|
// LoadConfigsFromFile loads all oracle oci configurations from a file
|
|
|
|
// (generally ~/.oci/config).
|
2017-02-13 05:35:14 -05:00
|
|
|
func LoadConfigsFromFile(path string) (map[string]*Config, error) {
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
2017-09-12 11:30:39 -04:00
|
|
|
return nil, fmt.Errorf("Oracle OCI config file is missing: %s", path)
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
cfgFile, err := ini.Load(path)
|
|
|
|
if err != nil {
|
|
|
|
err := fmt.Errorf("Failed to parse config file %s: %s", path, err.Error())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
configs := make(map[string]*Config)
|
|
|
|
|
|
|
|
// Load DEFAULT section to populate defaults for all other configs
|
|
|
|
config, err := loadConfigSection(cfgFile, "DEFAULT", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
configs["DEFAULT"] = config
|
|
|
|
|
|
|
|
// Load other sections.
|
|
|
|
for _, sectionName := range cfgFile.SectionStrings() {
|
|
|
|
if sectionName == "DEFAULT" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map to Config struct with defaults from DEFAULT section.
|
|
|
|
config, err := loadConfigSection(cfgFile, sectionName, configs["DEFAULT"])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
configs[sectionName] = config
|
|
|
|
}
|
|
|
|
|
|
|
|
return configs, nil
|
|
|
|
}
|
|
|
|
|
2017-09-12 11:30:39 -04:00
|
|
|
// Loads an individual Config object from a ini.Section in the Oracle OCI config
|
2017-02-13 05:35:14 -05:00
|
|
|
// file.
|
|
|
|
func loadConfigSection(f *ini.File, sectionName string, config *Config) (*Config, error) {
|
|
|
|
if config == nil {
|
|
|
|
config = &Config{}
|
|
|
|
}
|
|
|
|
|
|
|
|
section, err := f.GetSection(sectionName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Config file does not contain a %s section", sectionName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := section.MapTo(config); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-02-14 04:50:28 -05:00
|
|
|
config.Key, err = LoadPrivateKey(config)
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadPrivateKey loads private key from disk and parses it.
|
2017-02-14 04:50:28 -05:00
|
|
|
func LoadPrivateKey(config *Config) (*rsa.PrivateKey, error) {
|
2017-02-13 05:35:14 -05:00
|
|
|
// Expand '~' to $HOME
|
2017-02-14 04:50:28 -05:00
|
|
|
path, err := homedir.Expand(config.KeyFile)
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read and parse API signing key
|
|
|
|
keyContent, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-14 04:50:28 -05:00
|
|
|
key, err := ParsePrivateKey(keyContent, []byte(config.PassPhrase))
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
return key, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParsePrivateKey parses a PEM encoded array of bytes into an rsa.PrivateKey.
|
2017-02-14 04:50:28 -05:00
|
|
|
// Attempts to decrypt the PEM encoded array of bytes with the given password
|
|
|
|
// if the PEM encoded byte array is encrypted.
|
|
|
|
func ParsePrivateKey(content, password []byte) (*rsa.PrivateKey, error) {
|
2017-02-13 05:35:14 -05:00
|
|
|
keyBlock, _ := pem.Decode(content)
|
2017-02-14 04:50:28 -05:00
|
|
|
|
|
|
|
if keyBlock == nil {
|
|
|
|
return nil, errors.New("could not decode PEM private key")
|
|
|
|
}
|
|
|
|
|
|
|
|
var der []byte
|
|
|
|
var err error
|
|
|
|
if x509.IsEncryptedPEMBlock(keyBlock) {
|
|
|
|
if len(password) < 1 {
|
|
|
|
return nil, errors.New("encrypted private key but no pass phrase provided")
|
|
|
|
}
|
|
|
|
der, err = x509.DecryptPEMBlock(keyBlock, password)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
der = keyBlock.Bytes
|
|
|
|
}
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := x509.ParsePKCS8PrivateKey(der)
|
|
|
|
if err == nil {
|
|
|
|
switch key := key.(type) {
|
|
|
|
case *rsa.PrivateKey:
|
|
|
|
return key, nil
|
|
|
|
default:
|
|
|
|
return nil, errors.New("Private key is not an RSA private key")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("Failed to parse private key :%s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// BaseTestConfig creates the base (DEFAULT) config including a temporary key
|
|
|
|
// file.
|
|
|
|
// NOTE: Caller is responsible for removing temporary key file.
|
|
|
|
func BaseTestConfig() (*ini.File, *os.File, error) {
|
|
|
|
keyFile, err := generateRSAKeyFile()
|
|
|
|
if err != nil {
|
|
|
|
return nil, keyFile, err
|
|
|
|
}
|
|
|
|
// Build ini
|
|
|
|
cfg := ini.Empty()
|
|
|
|
section, _ := cfg.NewSection("DEFAULT")
|
2017-09-29 05:51:31 -04:00
|
|
|
section.NewKey("region", "us-ashburn-1")
|
2017-02-13 05:35:14 -05:00
|
|
|
section.NewKey("tenancy", "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
|
|
section.NewKey("user", "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
|
|
section.NewKey("fingerprint", "3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55")
|
|
|
|
section.NewKey("key_file", keyFile.Name())
|
|
|
|
|
|
|
|
return cfg, keyFile, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteTestConfig writes a ini.File to a temporary file for use in unit tests.
|
|
|
|
// NOTE: Caller is responsible for removing temporary file.
|
|
|
|
func WriteTestConfig(cfg *ini.File) (*os.File, error) {
|
|
|
|
confFile, err := ioutil.TempFile("", "config_file")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = cfg.WriteTo(confFile)
|
|
|
|
if err != nil {
|
|
|
|
os.Remove(confFile.Name())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return confFile, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// generateRSAKeyFile generates an RSA key file for use in unit tests.
|
|
|
|
// NOTE: The caller is responsible for deleting the temporary file.
|
|
|
|
func generateRSAKeyFile() (*os.File, error) {
|
|
|
|
// Create temporary file for the key
|
|
|
|
f, err := ioutil.TempFile("", "key")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate key
|
|
|
|
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ASN.1 DER encoded form
|
|
|
|
privDer := x509.MarshalPKCS1PrivateKey(priv)
|
|
|
|
privBlk := pem.Block{
|
|
|
|
Type: "RSA PRIVATE KEY",
|
|
|
|
Headers: nil,
|
|
|
|
Bytes: privDer,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the key out
|
|
|
|
if _, err := f.Write(pem.EncodeToMemory(&privBlk)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f, nil
|
|
|
|
}
|