added instance principals support for oci builder

This commit is contained in:
Ilias Tzimourakas 2020-03-15 17:22:06 +00:00
parent 1c27d9d04b
commit 0f2cb45fc6
11 changed files with 936 additions and 69 deletions

View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
ocicommon "github.com/oracle/oci-go-sdk/common"
ociauth "github.com/oracle/oci-go-sdk/common/auth"
)
type Config struct {
@ -27,6 +28,18 @@ type Config struct {
configProvider ocicommon.ConfigurationProvider
// Instance Principals (OPTIONAL)
// If set to true the following can't have non empty values
// - AccessCfgFile
// - AccessCfgFileAccount
// - UserID
// - TenancyID
// - Region
// - Fingerprint
// - KeyFile
// - PassPhrase
InstancePrincipals bool `mapstructure:"use_instance_principals"`
AccessCfgFile string `mapstructure:"access_cfg_file"`
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
@ -86,84 +99,134 @@ func (c *Config) Prepare(raws ...interface{}) error {
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
}
// Determine where the SDK config is located
if c.AccessCfgFile == "" {
c.AccessCfgFile, err = getDefaultOCISettingsPath()
if err != nil {
log.Println("Default OCI settings file not found")
}
}
if c.AccessCfgFileAccount == "" {
c.AccessCfgFileAccount = "DEFAULT"
}
var keyContent []byte
if c.KeyFile != "" {
path, err := packer.ExpandUser(c.KeyFile)
if err != nil {
return err
}
// Read API signing key
keyContent, err = ioutil.ReadFile(path)
if err != nil {
return err
}
}
fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
if c.Region == "" {
var region string
if fileProvider != nil {
region, _ = fileProvider.Region()
}
if region == "" {
c.Region = "us-phoenix-1"
}
}
providers := []ocicommon.ConfigurationProvider{
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
}
if fileProvider != nil {
providers = append(providers, fileProvider)
}
// Load API access configuration from SDK
configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
if err != nil {
return err
}
var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'user_ocid' must be specified"))
}
var tenancyOCID string
tenancyOCID, _ := configProvider.TenancyOCID()
if tenancyOCID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'tenancy_ocid' must be specified"))
}
if c.InstancePrincipals {
// We could go through all keys in one go and report that the below set
// of keys cannot coexist with use_instance_principals but decided to
// split them and report them seperately so that the user sees the specific
// key involved.
var message string = " cannot be present when use_instance_principals is set to true."
if c.AccessCfgFile != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file"+message))
}
if c.AccessCfgFileAccount != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file_account"+message))
}
if c.UserID != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user_ocid"+message))
}
if c.TenancyID != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("tenancy_ocid"+message))
}
if c.Region != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("region"+message))
}
if c.Fingerprint != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("fingerprint"+message))
}
if c.KeyFile != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("key_file"+message))
}
if c.PassPhrase != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("pass_phrase"+message))
}
// This check is used to facilitate testing. During testing a Mock struct
// is assigned to c.configProvider otherwise testing fails because Instance
// Principals cannot be obtained.
if c.configProvider == nil {
// Even though the previous configuraion checks might fail we don't want
// to skip this step. It seems that the logic behind the checks in this
// file is to check everything even getting the configProvider.
c.configProvider, err = ociauth.InstancePrincipalConfigurationProvider()
if err != nil {
return err
}
}
tenancyOCID, err = c.configProvider.TenancyOCID()
if err != nil {
return err
}
} else {
// Determine where the SDK config is located
if c.AccessCfgFile == "" {
c.AccessCfgFile, err = getDefaultOCISettingsPath()
if err != nil {
log.Println("Default OCI settings file not found")
}
}
if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'fingerprint' must be specified"))
}
if c.AccessCfgFileAccount == "" {
c.AccessCfgFileAccount = "DEFAULT"
}
if _, err := configProvider.PrivateRSAKey(); err != nil {
errs = packer.MultiErrorAppend(
errs, errors.New("'key_file' must be specified"))
}
var keyContent []byte
if c.KeyFile != "" {
path, err := packer.ExpandUser(c.KeyFile)
if err != nil {
return err
}
c.configProvider = configProvider
// Read API signing key
keyContent, err = ioutil.ReadFile(path)
if err != nil {
return err
}
}
fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
if c.Region == "" {
var region string
if fileProvider != nil {
region, _ = fileProvider.Region()
}
if region == "" {
c.Region = "us-phoenix-1"
}
}
providers := []ocicommon.ConfigurationProvider{
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
}
if fileProvider != nil {
providers = append(providers, fileProvider)
}
// Load API access configuration from SDK
configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
if err != nil {
return err
}
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'user_ocid' must be specified"))
}
tenancyOCID, _ = configProvider.TenancyOCID()
if tenancyOCID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'tenancy_ocid' must be specified"))
}
if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'fingerprint' must be specified"))
}
if _, err := configProvider.PrivateRSAKey(); err != nil {
errs = packer.MultiErrorAppend(
errs, errors.New("'key_file' must be specified"))
}
c.configProvider = configProvider
}
if c.AvailabilityDomain == "" {
errs = packer.MultiErrorAppend(

View File

@ -56,6 +56,7 @@ type FlatConfig struct {
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
InstancePrincipals *bool `mapstructure:"use_instance_principals" cty:"use_instance_principals"`
AccessCfgFile *string `mapstructure:"access_cfg_file" cty:"access_cfg_file"`
AccessCfgFileAccount *string `mapstructure:"access_cfg_file_account" cty:"access_cfg_file_account"`
UserID *string `mapstructure:"user_ocid" cty:"user_ocid"`
@ -138,6 +139,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"use_instance_principals": &hcldec.AttrSpec{Name: "use_instance_principals", Type: cty.Bool, Required: false},
"access_cfg_file": &hcldec.AttrSpec{Name: "access_cfg_file", Type: cty.String, Required: false},
"access_cfg_file_account": &hcldec.AttrSpec{Name: "access_cfg_file_account", Type: cty.String, Required: false},
"user_ocid": &hcldec.AttrSpec{Name: "user_ocid", Type: cty.String, Required: false},

View File

@ -15,6 +15,7 @@ import (
func testConfig(accessConfFile *os.File) map[string]interface{} {
return map[string]interface{}{
"availability_domain": "aaaa:PHX-AD-3",
"access_cfg_file": accessConfFile.Name(),
@ -252,6 +253,36 @@ func TestConfig(t *testing.T) {
t.Errorf("Expected ConfigProvider.KeyFingerprint: %s, got %s", expected, fingerprint)
}
})
// Test the correct errors are produced when certain template keys
// are present alongside use_instance_principals key.
invalidKeys := []string{
"access_cfg_file",
"access_cfg_file_account",
"user_ocid",
"tenancy_ocid",
"region",
"fingerprint",
"key_file",
"pass_phrase",
}
for _, k := range invalidKeys {
t.Run(k+"_mixed_with_use_instance_principals", func(t *testing.T) {
raw := testConfig(cfgFile)
raw["use_instance_principals"] = "true"
raw[k] = "some_random_value"
var c Config
c.configProvider = instancePrincipalConfigurationProviderMock{}
errs := c.Prepare(raw)
if !strings.Contains(errs.Error(), k) {
t.Errorf("Expected '%s' to contain '%s'", errs.Error(), k)
}
})
}
}
// BaseTestConfig creates the base (DEFAULT) config including a temporary key

View File

@ -0,0 +1,34 @@
package oci
import (
"crypto/rand"
"crypto/rsa"
)
// Mock struct to be used during testing to obtain Instance Principals.
type instancePrincipalConfigurationProviderMock struct {
}
func (p instancePrincipalConfigurationProviderMock) PrivateRSAKey() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 1024)
}
func (p instancePrincipalConfigurationProviderMock) KeyID() (string, error) {
return "some_random_key_id", nil
}
func (p instancePrincipalConfigurationProviderMock) TenancyOCID() (string, error) {
return "some_random_tenancy", nil
}
func (p instancePrincipalConfigurationProviderMock) UserOCID() (string, error) {
return "", nil
}
func (p instancePrincipalConfigurationProviderMock) KeyFingerprint() (string, error) {
return "", nil
}
func (p instancePrincipalConfigurationProviderMock) Region() (string, error) {
return "some_random_region", nil
}

View File

@ -0,0 +1,158 @@
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
package auth
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/oracle/oci-go-sdk/common"
"sync"
)
// x509CertificateRetriever provides an X509 certificate with the RSA private key
type x509CertificateRetriever interface {
Refresh() error
CertificatePemRaw() []byte
Certificate() *x509.Certificate
PrivateKeyPemRaw() []byte
PrivateKey() *rsa.PrivateKey
}
// urlBasedX509CertificateRetriever retrieves PEM-encoded X509 certificates from the given URLs.
type urlBasedX509CertificateRetriever struct {
certURL string
privateKeyURL string
passphrase string
certificatePemRaw []byte
certificate *x509.Certificate
privateKeyPemRaw []byte
privateKey *rsa.PrivateKey
mux sync.Mutex
}
func newURLBasedX509CertificateRetriever(certURL, privateKeyURL, passphrase string) x509CertificateRetriever {
return &urlBasedX509CertificateRetriever{
certURL: certURL,
privateKeyURL: privateKeyURL,
passphrase: passphrase,
mux: sync.Mutex{},
}
}
// Refresh() is failure atomic, i.e., CertificatePemRaw(), Certificate(), PrivateKeyPemRaw(), and PrivateKey() would
// return their previous values if Refresh() fails.
func (r *urlBasedX509CertificateRetriever) Refresh() error {
common.Debugln("Refreshing certificate")
r.mux.Lock()
defer r.mux.Unlock()
var err error
var certificatePemRaw []byte
var certificate *x509.Certificate
if certificatePemRaw, certificate, err = r.renewCertificate(r.certURL); err != nil {
return fmt.Errorf("failed to renew certificate: %s", err.Error())
}
var privateKeyPemRaw []byte
var privateKey *rsa.PrivateKey
if r.privateKeyURL != "" {
if privateKeyPemRaw, privateKey, err = r.renewPrivateKey(r.privateKeyURL, r.passphrase); err != nil {
return fmt.Errorf("failed to renew private key: %s", err.Error())
}
}
r.certificatePemRaw = certificatePemRaw
r.certificate = certificate
r.privateKeyPemRaw = privateKeyPemRaw
r.privateKey = privateKey
return nil
}
func (r *urlBasedX509CertificateRetriever) renewCertificate(url string) (certificatePemRaw []byte, certificate *x509.Certificate, err error) {
var body bytes.Buffer
if body, err = httpGet(url); err != nil {
return nil, nil, fmt.Errorf("failed to get certificate from %s: %s", url, err.Error())
}
certificatePemRaw = body.Bytes()
var block *pem.Block
block, _ = pem.Decode(certificatePemRaw)
if block == nil {
return nil, nil, fmt.Errorf("failed to parse the new certificate, not valid pem data")
}
if certificate, err = x509.ParseCertificate(block.Bytes); err != nil {
return nil, nil, fmt.Errorf("failed to parse the new certificate: %s", err.Error())
}
return certificatePemRaw, certificate, nil
}
func (r *urlBasedX509CertificateRetriever) renewPrivateKey(url, passphrase string) (privateKeyPemRaw []byte, privateKey *rsa.PrivateKey, err error) {
var body bytes.Buffer
if body, err = httpGet(url); err != nil {
return nil, nil, fmt.Errorf("failed to get private key from %s: %s", url, err.Error())
}
privateKeyPemRaw = body.Bytes()
if privateKey, err = common.PrivateKeyFromBytes(privateKeyPemRaw, &passphrase); err != nil {
return nil, nil, fmt.Errorf("failed to parse the new private key: %s", err.Error())
}
return privateKeyPemRaw, privateKey, nil
}
func (r *urlBasedX509CertificateRetriever) CertificatePemRaw() []byte {
r.mux.Lock()
defer r.mux.Unlock()
if r.certificatePemRaw == nil {
return nil
}
c := make([]byte, len(r.certificatePemRaw))
copy(c, r.certificatePemRaw)
return c
}
func (r *urlBasedX509CertificateRetriever) Certificate() *x509.Certificate {
r.mux.Lock()
defer r.mux.Unlock()
if r.certificate == nil {
return nil
}
c := *r.certificate
return &c
}
func (r *urlBasedX509CertificateRetriever) PrivateKeyPemRaw() []byte {
r.mux.Lock()
defer r.mux.Unlock()
if r.privateKeyPemRaw == nil {
return nil
}
c := make([]byte, len(r.privateKeyPemRaw))
copy(c, r.privateKeyPemRaw)
return c
}
func (r *urlBasedX509CertificateRetriever) PrivateKey() *rsa.PrivateKey {
r.mux.Lock()
defer r.mux.Unlock()
if r.privateKey == nil {
return nil
}
c := *r.privateKey
return &c
}

View File

@ -0,0 +1,61 @@
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
package auth
import (
"crypto/rsa"
"fmt"
"github.com/oracle/oci-go-sdk/common"
)
type instancePrincipalConfigurationProvider struct {
keyProvider *instancePrincipalKeyProvider
region *common.Region
}
//InstancePrincipalConfigurationProvider returns a configuration for instance principals
func InstancePrincipalConfigurationProvider() (common.ConfigurationProvider, error) {
var err error
var keyProvider *instancePrincipalKeyProvider
if keyProvider, err = newInstancePrincipalKeyProvider(); err != nil {
return nil, fmt.Errorf("failed to create a new key provider for instance principal: %s", err.Error())
}
return instancePrincipalConfigurationProvider{keyProvider: keyProvider, region: nil}, nil
}
//InstancePrincipalConfigurationProviderForRegion returns a configuration for instance principals with a given region
func InstancePrincipalConfigurationProviderForRegion(region common.Region) (common.ConfigurationProvider, error) {
var err error
var keyProvider *instancePrincipalKeyProvider
if keyProvider, err = newInstancePrincipalKeyProvider(); err != nil {
return nil, fmt.Errorf("failed to create a new key provider for instance principal: %s", err.Error())
}
return instancePrincipalConfigurationProvider{keyProvider: keyProvider, region: &region}, nil
}
func (p instancePrincipalConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
return p.keyProvider.PrivateRSAKey()
}
func (p instancePrincipalConfigurationProvider) KeyID() (string, error) {
return p.keyProvider.KeyID()
}
func (p instancePrincipalConfigurationProvider) TenancyOCID() (string, error) {
return p.keyProvider.TenancyOCID()
}
func (p instancePrincipalConfigurationProvider) UserOCID() (string, error) {
return "", nil
}
func (p instancePrincipalConfigurationProvider) KeyFingerprint() (string, error) {
return "", nil
}
func (p instancePrincipalConfigurationProvider) Region() (string, error) {
if p.region == nil {
return string(p.keyProvider.RegionForFederationClient()), nil
}
return string(*p.region), nil
}

View File

@ -0,0 +1,292 @@
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
// Package auth provides supporting functions and structs for authentication
package auth
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/oracle/oci-go-sdk/common"
"net/http"
"strings"
"sync"
)
// federationClient is a client to retrieve the security token for an instance principal necessary to sign a request.
// It also provides the private key whose corresponding public key is used to retrieve the security token.
type federationClient interface {
PrivateKey() (*rsa.PrivateKey, error)
SecurityToken() (string, error)
}
// x509FederationClient retrieves a security token from Auth service.
type x509FederationClient struct {
tenancyID string
sessionKeySupplier sessionKeySupplier
leafCertificateRetriever x509CertificateRetriever
intermediateCertificateRetrievers []x509CertificateRetriever
securityToken securityToken
authClient *common.BaseClient
mux sync.Mutex
}
func newX509FederationClient(region common.Region, tenancyID string, leafCertificateRetriever x509CertificateRetriever, intermediateCertificateRetrievers []x509CertificateRetriever) federationClient {
client := &x509FederationClient{
tenancyID: tenancyID,
leafCertificateRetriever: leafCertificateRetriever,
intermediateCertificateRetrievers: intermediateCertificateRetrievers,
}
client.sessionKeySupplier = newSessionKeySupplier()
client.authClient = newAuthClient(region, client)
return client
}
var (
genericHeaders = []string{"date", "(request-target)"} // "host" is not needed for the federation endpoint. Don't ask me why.
bodyHeaders = []string{"content-length", "content-type", "x-content-sha256"}
)
func newAuthClient(region common.Region, provider common.KeyProvider) *common.BaseClient {
signer := common.RequestSigner(provider, genericHeaders, bodyHeaders)
client := common.DefaultBaseClientWithSigner(signer)
if region == common.RegionSEA {
client.Host = "https://auth.r1.oracleiaas.com"
} else {
client.Host = fmt.Sprintf(common.DefaultHostURLTemplate, "auth", string(region))
}
client.BasePath = "v1/x509"
return &client
}
// For authClient to sign requests to X509 Federation Endpoint
func (c *x509FederationClient) KeyID() (string, error) {
tenancy := c.tenancyID
fingerprint := fingerprint(c.leafCertificateRetriever.Certificate())
return fmt.Sprintf("%s/fed-x509/%s", tenancy, fingerprint), nil
}
// For authClient to sign requests to X509 Federation Endpoint
func (c *x509FederationClient) PrivateRSAKey() (*rsa.PrivateKey, error) {
return c.leafCertificateRetriever.PrivateKey(), nil
}
func (c *x509FederationClient) PrivateKey() (*rsa.PrivateKey, error) {
c.mux.Lock()
defer c.mux.Unlock()
if err := c.renewSecurityTokenIfNotValid(); err != nil {
return nil, err
}
return c.sessionKeySupplier.PrivateKey(), nil
}
func (c *x509FederationClient) SecurityToken() (token string, err error) {
c.mux.Lock()
defer c.mux.Unlock()
if err = c.renewSecurityTokenIfNotValid(); err != nil {
return "", err
}
return c.securityToken.String(), nil
}
func (c *x509FederationClient) renewSecurityTokenIfNotValid() (err error) {
if c.securityToken == nil || !c.securityToken.Valid() {
if err = c.renewSecurityToken(); err != nil {
return fmt.Errorf("failed to renew security token: %s", err.Error())
}
}
return nil
}
func (c *x509FederationClient) renewSecurityToken() (err error) {
if err = c.sessionKeySupplier.Refresh(); err != nil {
return fmt.Errorf("failed to refresh session key: %s", err.Error())
}
if err = c.leafCertificateRetriever.Refresh(); err != nil {
return fmt.Errorf("failed to refresh leaf certificate: %s", err.Error())
}
updatedTenancyID := extractTenancyIDFromCertificate(c.leafCertificateRetriever.Certificate())
if c.tenancyID != updatedTenancyID {
err = fmt.Errorf("unexpected update of tenancy OCID in the leaf certificate. Previous tenancy: %s, Updated: %s", c.tenancyID, updatedTenancyID)
return
}
for _, retriever := range c.intermediateCertificateRetrievers {
if err = retriever.Refresh(); err != nil {
return fmt.Errorf("failed to refresh intermediate certificate: %s", err.Error())
}
}
if c.securityToken, err = c.getSecurityToken(); err != nil {
return fmt.Errorf("failed to get security token: %s", err.Error())
}
return nil
}
func (c *x509FederationClient) getSecurityToken() (securityToken, error) {
request := c.makeX509FederationRequest()
var err error
var httpRequest http.Request
if httpRequest, err = common.MakeDefaultHTTPRequestWithTaggedStruct(http.MethodPost, "", request); err != nil {
return nil, fmt.Errorf("failed to make http request: %s", err.Error())
}
var httpResponse *http.Response
defer common.CloseBodyIfValid(httpResponse)
if httpResponse, err = c.authClient.Call(context.Background(), &httpRequest); err != nil {
return nil, fmt.Errorf("failed to call: %s", err.Error())
}
response := x509FederationResponse{}
if err = common.UnmarshalResponse(httpResponse, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal the response: %s", err.Error())
}
return newInstancePrincipalToken(response.Token.Token)
}
type x509FederationRequest struct {
X509FederationDetails `contributesTo:"body"`
}
// X509FederationDetails x509 federation details
type X509FederationDetails struct {
Certificate string `mandatory:"true" json:"certificate,omitempty"`
PublicKey string `mandatory:"true" json:"publicKey,omitempty"`
IntermediateCertificates []string `mandatory:"false" json:"intermediateCertificates,omitempty"`
}
type x509FederationResponse struct {
Token `presentIn:"body"`
}
// Token token
type Token struct {
Token string `mandatory:"true" json:"token,omitempty"`
}
func (c *x509FederationClient) makeX509FederationRequest() *x509FederationRequest {
certificate := c.sanitizeCertificateString(string(c.leafCertificateRetriever.CertificatePemRaw()))
publicKey := c.sanitizeCertificateString(string(c.sessionKeySupplier.PublicKeyPemRaw()))
var intermediateCertificates []string
for _, retriever := range c.intermediateCertificateRetrievers {
intermediateCertificates = append(intermediateCertificates, c.sanitizeCertificateString(string(retriever.CertificatePemRaw())))
}
details := X509FederationDetails{
Certificate: certificate,
PublicKey: publicKey,
IntermediateCertificates: intermediateCertificates,
}
return &x509FederationRequest{details}
}
func (c *x509FederationClient) sanitizeCertificateString(certString string) string {
certString = strings.Replace(certString, "-----BEGIN CERTIFICATE-----", "", -1)
certString = strings.Replace(certString, "-----END CERTIFICATE-----", "", -1)
certString = strings.Replace(certString, "-----BEGIN PUBLIC KEY-----", "", -1)
certString = strings.Replace(certString, "-----END PUBLIC KEY-----", "", -1)
certString = strings.Replace(certString, "\n", "", -1)
return certString
}
// sessionKeySupplier provides an RSA keypair which can be re-generated by calling Refresh().
type sessionKeySupplier interface {
Refresh() error
PrivateKey() *rsa.PrivateKey
PublicKeyPemRaw() []byte
}
// inMemorySessionKeySupplier implements sessionKeySupplier to vend an RSA keypair.
// Refresh() generates a new RSA keypair with a random source, and keeps it in memory.
//
// inMemorySessionKeySupplier is not thread-safe.
type inMemorySessionKeySupplier struct {
keySize int
privateKey *rsa.PrivateKey
publicKeyPemRaw []byte
}
// newSessionKeySupplier creates and returns a sessionKeySupplier instance which generates key pairs of size 2048.
func newSessionKeySupplier() sessionKeySupplier {
return &inMemorySessionKeySupplier{keySize: 2048}
}
// Refresh() is failure atomic, i.e., PrivateKey() and PublicKeyPemRaw() would return their previous values
// if Refresh() fails.
func (s *inMemorySessionKeySupplier) Refresh() (err error) {
common.Debugln("Refreshing session key")
var privateKey *rsa.PrivateKey
privateKey, err = rsa.GenerateKey(rand.Reader, s.keySize)
if err != nil {
return fmt.Errorf("failed to generate a new keypair: %s", err)
}
var publicKeyAsnBytes []byte
if publicKeyAsnBytes, err = x509.MarshalPKIXPublicKey(privateKey.Public()); err != nil {
return fmt.Errorf("failed to marshal the public part of the new keypair: %s", err.Error())
}
publicKeyPemRaw := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyAsnBytes,
})
s.privateKey = privateKey
s.publicKeyPemRaw = publicKeyPemRaw
return nil
}
func (s *inMemorySessionKeySupplier) PrivateKey() *rsa.PrivateKey {
if s.privateKey == nil {
return nil
}
c := *s.privateKey
return &c
}
func (s *inMemorySessionKeySupplier) PublicKeyPemRaw() []byte {
if s.publicKeyPemRaw == nil {
return nil
}
c := make([]byte, len(s.publicKeyPemRaw))
copy(c, s.publicKeyPemRaw)
return c
}
type securityToken interface {
fmt.Stringer
Valid() bool
}
type instancePrincipalToken struct {
tokenString string
jwtToken *jwtToken
}
func newInstancePrincipalToken(tokenString string) (newToken securityToken, err error) {
var jwtToken *jwtToken
if jwtToken, err = parseJwt(tokenString); err != nil {
return nil, fmt.Errorf("failed to parse the token string \"%s\": %s", tokenString, err.Error())
}
return &instancePrincipalToken{tokenString, jwtToken}, nil
}
func (t *instancePrincipalToken) String() string {
return t.tokenString
}
func (t *instancePrincipalToken) Valid() bool {
return !t.jwtToken.expired()
}

View File

@ -0,0 +1,100 @@
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
package auth
import (
"bytes"
"crypto/rsa"
"fmt"
"github.com/oracle/oci-go-sdk/common"
)
const (
regionURL = `http://169.254.169.254/opc/v1/instance/region`
leafCertificateURL = `http://169.254.169.254/opc/v1/identity/cert.pem`
leafCertificateKeyURL = `http://169.254.169.254/opc/v1/identity/key.pem`
leafCertificateKeyPassphrase = `` // No passphrase for the private key for Compute instances
intermediateCertificateURL = `http://169.254.169.254/opc/v1/identity/intermediate.pem`
intermediateCertificateKeyURL = ``
intermediateCertificateKeyPassphrase = `` // No passphrase for the private key for Compute instances
)
// instancePrincipalKeyProvider implements KeyProvider to provide a key ID and its corresponding private key
// for an instance principal by getting a security token via x509FederationClient.
//
// The region name of the endpoint for x509FederationClient is obtained from the metadata service on the compute
// instance.
type instancePrincipalKeyProvider struct {
regionForFederationClient common.Region
federationClient federationClient
tenancyID string
}
// newInstancePrincipalKeyProvider creates and returns an instancePrincipalKeyProvider instance based on
// x509FederationClient.
//
// NOTE: There is a race condition between PrivateRSAKey() and KeyID(). These two pieces are tightly coupled; KeyID
// includes a security token obtained from Auth service by giving a public key which is paired with PrivateRSAKey.
// The x509FederationClient caches the security token in memory until it is expired. Thus, even if a client obtains a
// KeyID that is not expired at the moment, the PrivateRSAKey that the client acquires at a next moment could be
// invalid because the KeyID could be already expired.
func newInstancePrincipalKeyProvider() (provider *instancePrincipalKeyProvider, err error) {
var region common.Region
if region, err = getRegionForFederationClient(regionURL); err != nil {
err = fmt.Errorf("failed to get the region name from %s: %s", regionURL, err.Error())
common.Logln(err)
return nil, err
}
leafCertificateRetriever := newURLBasedX509CertificateRetriever(
leafCertificateURL, leafCertificateKeyURL, leafCertificateKeyPassphrase)
intermediateCertificateRetrievers := []x509CertificateRetriever{
newURLBasedX509CertificateRetriever(
intermediateCertificateURL, intermediateCertificateKeyURL, intermediateCertificateKeyPassphrase),
}
if err = leafCertificateRetriever.Refresh(); err != nil {
err = fmt.Errorf("failed to refresh the leaf certificate: %s", err.Error())
return nil, err
}
tenancyID := extractTenancyIDFromCertificate(leafCertificateRetriever.Certificate())
federationClient := newX509FederationClient(
region, tenancyID, leafCertificateRetriever, intermediateCertificateRetrievers)
provider = &instancePrincipalKeyProvider{regionForFederationClient: region, federationClient: federationClient, tenancyID: tenancyID}
return
}
func getRegionForFederationClient(url string) (r common.Region, err error) {
var body bytes.Buffer
if body, err = httpGet(url); err != nil {
return
}
return common.StringToRegion(body.String()), nil
}
func (p *instancePrincipalKeyProvider) RegionForFederationClient() common.Region {
return p.regionForFederationClient
}
func (p *instancePrincipalKeyProvider) PrivateRSAKey() (privateKey *rsa.PrivateKey, err error) {
if privateKey, err = p.federationClient.PrivateKey(); err != nil {
err = fmt.Errorf("failed to get private key: %s", err.Error())
return nil, err
}
return privateKey, nil
}
func (p *instancePrincipalKeyProvider) KeyID() (string, error) {
var securityToken string
var err error
if securityToken, err = p.federationClient.SecurityToken(); err != nil {
return "", fmt.Errorf("failed to get security token: %s", err.Error())
}
return fmt.Sprintf("ST$%s", securityToken), nil
}
func (p *instancePrincipalKeyProvider) TenancyOCID() (string, error) {
return p.tenancyID, nil
}

61
vendor/github.com/oracle/oci-go-sdk/common/auth/jwt.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
package auth
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)
type jwtToken struct {
raw string
header map[string]interface{}
payload map[string]interface{}
}
func (t *jwtToken) expired() bool {
exp := int64(t.payload["exp"].(float64))
return exp <= time.Now().Unix()
}
func parseJwt(tokenString string) (*jwtToken, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("the given token string contains an invalid number of parts")
}
token := &jwtToken{raw: tokenString}
var err error
// Parse Header part
var headerBytes []byte
if headerBytes, err = decodePart(parts[0]); err != nil {
return nil, fmt.Errorf("failed to decode the header bytes: %s", err.Error())
}
if err = json.Unmarshal(headerBytes, &token.header); err != nil {
return nil, err
}
// Parse Payload part
var payloadBytes []byte
if payloadBytes, err = decodePart(parts[1]); err != nil {
return nil, fmt.Errorf("failed to decode the payload bytes: %s", err.Error())
}
decoder := json.NewDecoder(bytes.NewBuffer(payloadBytes))
if err = decoder.Decode(&token.payload); err != nil {
return nil, fmt.Errorf("failed to decode the payload json: %s", err.Error())
}
return token, nil
}
func decodePart(partString string) ([]byte, error) {
if l := len(partString) % 4; 0 < l {
partString += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(partString)
}

View File

@ -0,0 +1,64 @@
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
package auth
import (
"bytes"
"crypto/sha1"
"crypto/x509"
"fmt"
"github.com/oracle/oci-go-sdk/common"
"net/http"
"net/http/httputil"
"strings"
)
// httpGet makes a simple HTTP GET request to the given URL, expecting only "200 OK" status code.
// This is basically for the Instance Metadata Service.
func httpGet(url string) (body bytes.Buffer, err error) {
var response *http.Response
if response, err = http.Get(url); err != nil {
return
}
common.IfDebug(func() {
if dump, e := httputil.DumpResponse(response, true); e == nil {
common.Logf("Dump Response %v", string(dump))
} else {
common.Debugln(e)
}
})
defer response.Body.Close()
if _, err = body.ReadFrom(response.Body); err != nil {
return
}
if response.StatusCode != http.StatusOK {
err = fmt.Errorf("HTTP Get failed: URL: %s, Status: %s, Message: %s",
url, response.Status, body.String())
return
}
return
}
func extractTenancyIDFromCertificate(cert *x509.Certificate) string {
for _, nameAttr := range cert.Subject.Names {
value := nameAttr.Value.(string)
if strings.HasPrefix(value, "opc-tenant:") {
return value[len("opc-tenant:"):]
}
}
return ""
}
func fingerprint(certificate *x509.Certificate) string {
fingerprint := sha1.Sum(certificate.Raw)
return colonSeparatedString(fingerprint)
}
func colonSeparatedString(fingerprint [sha1.Size]byte) string {
spaceSeparated := fmt.Sprintf("% x", fingerprint)
return strings.Replace(spaceSeparated, " ", ":", -1)
}

1
vendor/modules.txt vendored
View File

@ -484,6 +484,7 @@ github.com/nu7hatch/gouuid
github.com/olekukonko/tablewriter
# github.com/oracle/oci-go-sdk v1.8.0
github.com/oracle/oci-go-sdk/common
github.com/oracle/oci-go-sdk/common/auth
github.com/oracle/oci-go-sdk/core
# github.com/outscale/osc-go v0.0.1
github.com/outscale/osc-go/oapi