resource_principals_v3.go raw

   1  // Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates.  All rights reserved.
   2  // 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.
   3  
   4  package auth
   5  
   6  import (
   7  	"context"
   8  	"crypto/rsa"
   9  	"fmt"
  10  	"net/http"
  11  	"net/url"
  12  	"strings"
  13  	"sync"
  14  	"time"
  15  
  16  	"github.com/nrdcg/oci-go-sdk/common/v1065"
  17  )
  18  
  19  type resourcePrincipalV3Client struct {
  20  	securityToken      securityToken
  21  	mux                sync.Mutex
  22  	sessionKeySupplier sessionKeySupplier
  23  	rptUrl             string
  24  	rpstUrl            string
  25  
  26  	leafResourcePrincipalKeyProvider ConfigurationProviderWithClaimAccess
  27  
  28  	//ResourcePrincipalTargetServiceClient client that calls the target service to acquire a resource principal token
  29  	//ResourcePrincipalTargetServiceClient common.BaseClient
  30  
  31  	//ResourcePrincipalSessionTokenClient. The client used to communicate with identity to exchange a resource principal for
  32  	// resource principal session token
  33  	//ResourcePrincipalSessionTokenClient common.BaseClient
  34  }
  35  
  36  // acquireResourcePrincipalToken acquires the resource principal from the target service
  37  func (c *resourcePrincipalV3Client) acquireResourcePrincipalToken(rptClient common.BaseClient, path string, signer common.HTTPRequestSigner) (tokenResponse resourcePrincipalTokenResponse, parentRptURL string, err error) {
  38  	rpServiceClient := rptClient
  39  	rpServiceClient.Signer = signer
  40  
  41  	//Create a request with the instanceId
  42  	request := common.MakeDefaultHTTPRequest(http.MethodGet, path)
  43  
  44  	//Call the target service
  45  	response, err := rpServiceClient.Call(context.Background(), &request)
  46  	if err != nil {
  47  		return
  48  	}
  49  
  50  	defer common.CloseBodyIfValid(response)
  51  
  52  	// Extract the opc-parent-rpt-url header value
  53  	parentRptURL = response.Header.Get(OpcParentRptUrlHeader)
  54  
  55  	tokenResponse = resourcePrincipalTokenResponse{}
  56  	err = common.UnmarshalResponse(response, &tokenResponse)
  57  	return
  58  }
  59  
  60  // exchangeToken exchanges a resource principal token from the target service with a session token from identity
  61  func (c *resourcePrincipalV3Client) exchangeToken(rpstClient common.BaseClient, signer common.HTTPRequestSigner, publicKeyBase64 string, tokenResponse resourcePrincipalTokenResponse) (sessionToken string, err error) {
  62  	rpServiceClient := rpstClient
  63  	rpServiceClient.Signer = signer
  64  
  65  	// Call identity service to get resource principal session token
  66  	sessionTokenReq := resourcePrincipalSessionTokenRequest{
  67  		resourcePrincipalSessionTokenRequestBody{
  68  			ServicePrincipalSessionToken: tokenResponse.Body.ServicePrincipalSessionToken,
  69  			ResourcePrincipalToken:       tokenResponse.Body.ResourcePrincipalToken,
  70  			SessionPublicKey:             publicKeyBase64,
  71  		},
  72  	}
  73  
  74  	sessionTokenHTTPReq, err := common.MakeDefaultHTTPRequestWithTaggedStruct(http.MethodPost,
  75  		"", sessionTokenReq)
  76  	if err != nil {
  77  		return
  78  	}
  79  
  80  	sessionTokenHTTPRes, err := rpServiceClient.Call(context.Background(), &sessionTokenHTTPReq)
  81  	if err != nil {
  82  		return
  83  	}
  84  	defer common.CloseBodyIfValid(sessionTokenHTTPRes)
  85  
  86  	sessionTokenRes := x509FederationResponse{}
  87  	err = common.UnmarshalResponse(sessionTokenHTTPRes, &sessionTokenRes)
  88  	if err != nil {
  89  		return
  90  	}
  91  
  92  	sessionToken = sessionTokenRes.Token.Token
  93  	return
  94  }
  95  
  96  // getSecurityToken makes the appropriate calls to acquire a resource principal security token
  97  func (c *resourcePrincipalV3Client) getSecurityToken() (securityToken, error) {
  98  
  99  	//c.leafResourcePrincipalKeyProvider.KeyID()
 100  	//common.Debugf("Refreshing resource principal token")
 101  
 102  	//Read the public key from the session supplier.
 103  	pem := c.sessionKeySupplier.PublicKeyPemRaw()
 104  	pemSanitized := sanitizeCertificateString(string(pem))
 105  
 106  	return c.getSecurityTokenWithDepth(c.leafResourcePrincipalKeyProvider, 1, c.rptUrl, pemSanitized)
 107  
 108  }
 109  
 110  func (c *resourcePrincipalV3Client) getSecurityTokenWithDepth(keyProvider ConfigurationProviderWithClaimAccess, depth int, rptUrl, publicKey string) (securityToken, error) {
 111  	//Build the target service client
 112  	rpTargetServiceClient, err := common.NewClientWithConfig(keyProvider)
 113  	if err != nil {
 114  		return nil, err
 115  	}
 116  
 117  	rpTokenURL, err := url.Parse(rptUrl)
 118  	if err != nil {
 119  		return nil, err
 120  	}
 121  
 122  	common.Debugf("rptURL: %v", rpTokenURL)
 123  
 124  	rpTargetServiceClient.Host = rpTokenURL.Scheme + "://" + rpTokenURL.Host
 125  
 126  	//Build the identity client for token service
 127  	rpTokenSessionClient, err := common.NewClientWithConfig(keyProvider)
 128  	if err != nil {
 129  		return nil, err
 130  	}
 131  
 132  	// Set RPST endpoint if passed in from env var, otherwise create it from region
 133  	if c.rpstUrl != "" {
 134  		rpSessionTokenURL, err := url.Parse(c.rpstUrl)
 135  		if err != nil {
 136  			return nil, err
 137  		}
 138  
 139  		rpTokenSessionClient.Host = rpSessionTokenURL.Scheme + "://" + rpSessionTokenURL.Host
 140  	} else {
 141  		regionStr, err := c.leafResourcePrincipalKeyProvider.Region()
 142  		if err != nil {
 143  			return nil, fmt.Errorf("missing RPST env var and cannot determine region: %v", err)
 144  		}
 145  		region := common.StringToRegion(regionStr)
 146  		rpTokenSessionClient.Host = fmt.Sprintf("https://%s", region.Endpoint("auth"))
 147  	}
 148  
 149  	rpTokenSessionClient.BasePath = identityResourcePrincipalSessionTokenPath
 150  
 151  	//Acquire resource principal token from target service
 152  	common.Debugf("Acquiring resource principal token from target service")
 153  	tokenResponse, parentRptURL, err := c.acquireResourcePrincipalToken(rpTargetServiceClient, rpTokenURL.Path, common.DefaultRequestSigner(keyProvider))
 154  	if err != nil {
 155  		return nil, err
 156  	}
 157  
 158  	//Exchange resource principal token for session token from identity
 159  	common.Debugf("Exchanging resource principal token for resource principal session token")
 160  	sessionToken, err := c.exchangeToken(rpTokenSessionClient, common.DefaultRequestSigner(keyProvider), publicKey, tokenResponse)
 161  	if err != nil {
 162  		return nil, err
 163  	}
 164  
 165  	// Base condition for recursion
 166  	// return the security token obtained last in the following cases
 167  	// 1. if depth is more than 10
 168  	// 2. if opc-parent-rpt-url header is not passed or is empty
 169  	// 3. if opc-parent-rpt-url matches the last rpt url
 170  	if depth >= 10 || parentRptURL == "" || strings.EqualFold(parentRptURL, rptUrl) {
 171  		return newPrincipalToken(sessionToken)
 172  	}
 173  
 174  	fd, err := newStaticFederationClient(sessionToken, c.sessionKeySupplier)
 175  
 176  	if err != nil {
 177  		err := fmt.Errorf("can not create resource principal, due to: %s ", err.Error())
 178  		return nil, resourcePrincipalError{err: err}
 179  	}
 180  
 181  	region, _ := keyProvider.Region()
 182  
 183  	configProviderForNextCall := resourcePrincipalKeyProvider{
 184  		fd, common.Region(region),
 185  	}
 186  
 187  	return c.getSecurityTokenWithDepth(&configProviderForNextCall, depth+1, parentRptURL, publicKey)
 188  
 189  }
 190  
 191  func (c *resourcePrincipalV3Client) renewSecurityToken() (err error) {
 192  	if err = c.sessionKeySupplier.Refresh(); err != nil {
 193  		return fmt.Errorf("failed to refresh session key: %s", err.Error())
 194  	}
 195  
 196  	common.Logf("Renewing security token at: %v\n", time.Now().Format("15:04:05.000"))
 197  	if c.securityToken, err = c.getSecurityToken(); err != nil {
 198  		return fmt.Errorf("failed to get security token: %s", err.Error())
 199  	}
 200  	common.Logf("Security token renewed at: %v\n", time.Now().Format("15:04:05.000"))
 201  
 202  	return nil
 203  }
 204  
 205  func (c *resourcePrincipalV3Client) renewSecurityTokenIfNotValid() (err error) {
 206  	if c.securityToken == nil || !c.securityToken.Valid() {
 207  		if err = c.renewSecurityToken(); err != nil {
 208  			return fmt.Errorf("failed to renew resource principal security token: %s", err.Error())
 209  		}
 210  	}
 211  	return nil
 212  }
 213  
 214  func (c *resourcePrincipalV3Client) PrivateKey() (*rsa.PrivateKey, error) {
 215  	c.mux.Lock()
 216  	defer c.mux.Unlock()
 217  	if err := c.renewSecurityTokenIfNotValid(); err != nil {
 218  		return nil, err
 219  	}
 220  	return c.sessionKeySupplier.PrivateKey(), nil
 221  }
 222  
 223  func (c *resourcePrincipalV3Client) SecurityToken() (token string, err error) {
 224  	c.mux.Lock()
 225  	defer c.mux.Unlock()
 226  
 227  	if err = c.renewSecurityTokenIfNotValid(); err != nil {
 228  		return "", err
 229  	}
 230  	return c.securityToken.String(), nil
 231  }
 232  
 233  type resourcePrincipalKeyProviderV3 struct {
 234  	resourcePrincipalClient resourcePrincipalV3Client
 235  }
 236  
 237  type resourcePrincipalV30ConfigurationProvider struct {
 238  	keyProvider resourcePrincipalKeyProviderV3
 239  	region      *common.Region
 240  }
 241  
 242  func (r *resourcePrincipalV30ConfigurationProvider) Refreshable() bool {
 243  	return true
 244  }
 245  
 246  func (r *resourcePrincipalV30ConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
 247  	privateKey, err := r.keyProvider.resourcePrincipalClient.PrivateKey()
 248  	if err != nil {
 249  		err = fmt.Errorf("failed to get resource principal private key: %s", err.Error())
 250  		return nil, err
 251  	}
 252  	return privateKey, nil
 253  }
 254  
 255  func (r *resourcePrincipalV30ConfigurationProvider) KeyID() (string, error) {
 256  	var securityToken string
 257  	var err error
 258  	if securityToken, err = r.keyProvider.resourcePrincipalClient.SecurityToken(); err != nil {
 259  		return "", fmt.Errorf("failed to get resource principal security token: %s", err.Error())
 260  	}
 261  	return fmt.Sprintf("ST$%s", securityToken), nil
 262  }
 263  
 264  func (r *resourcePrincipalV30ConfigurationProvider) TenancyOCID() (string, error) {
 265  	return r.keyProvider.resourcePrincipalClient.leafResourcePrincipalKeyProvider.TenancyOCID()
 266  }
 267  
 268  func (r *resourcePrincipalV30ConfigurationProvider) UserOCID() (string, error) {
 269  	return "", nil
 270  }
 271  
 272  func (r *resourcePrincipalV30ConfigurationProvider) KeyFingerprint() (string, error) {
 273  	return "", nil
 274  }
 275  
 276  func (r *resourcePrincipalV30ConfigurationProvider) Region() (string, error) {
 277  	if r.region == nil {
 278  		common.Debugf("Region in resource principal configuration provider v30 is nil.")
 279  		return "", nil
 280  	}
 281  	return string(*r.region), nil
 282  }
 283  
 284  func (r *resourcePrincipalV30ConfigurationProvider) AuthType() (common.AuthConfig, error) {
 285  	return common.AuthConfig{common.UnknownAuthenticationType, false, nil},
 286  		fmt.Errorf("unsupported, keep the interface")
 287  }
 288  
 289  func (r *resourcePrincipalV30ConfigurationProvider) GetClaim(key string) (interface{}, error) {
 290  	//TODO implement me
 291  	panic("implement me")
 292  }
 293  
 294  type resourcePrincipalV30ConfiguratorBuilder struct {
 295  	leafResourcePrincipalKeyProvider  ConfigurationProviderWithClaimAccess
 296  	rptUrlForParent, rpstUrlForParent *string
 297  }
 298  
 299  // ResourcePrincipalV3ConfiguratorBuilder creates a new resourcePrincipalV30ConfiguratorBuilder.
 300  func ResourcePrincipalV3ConfiguratorBuilder(leafResourcePrincipalKeyProvider ConfigurationProviderWithClaimAccess) *resourcePrincipalV30ConfiguratorBuilder {
 301  	return &resourcePrincipalV30ConfiguratorBuilder{
 302  		leafResourcePrincipalKeyProvider: leafResourcePrincipalKeyProvider,
 303  	}
 304  }
 305  
 306  // WithParentRPTURL sets the rptUrlForParent field.
 307  func (b *resourcePrincipalV30ConfiguratorBuilder) WithParentRPTURL(rptUrlForParent string) *resourcePrincipalV30ConfiguratorBuilder {
 308  	b.rptUrlForParent = &rptUrlForParent
 309  	return b
 310  }
 311  
 312  // WithParentRPSTURL sets the rpstUrlForParent field.
 313  func (b *resourcePrincipalV30ConfiguratorBuilder) WithParentRPSTURL(rpstUrlForParent string) *resourcePrincipalV30ConfiguratorBuilder {
 314  	b.rpstUrlForParent = &rpstUrlForParent
 315  	return b
 316  }
 317  
 318  // Build creates a ConfigurationProviderWithClaimAccess based on the configured values.
 319  func (b *resourcePrincipalV30ConfiguratorBuilder) Build() (ConfigurationProviderWithClaimAccess, error) {
 320  
 321  	if b.rptUrlForParent == nil {
 322  		err := fmt.Errorf("can not create resource principal, environment variable: %s, not present",
 323  			ResourcePrincipalRptURLForParent)
 324  		return nil, resourcePrincipalError{err: err}
 325  	}
 326  
 327  	if b.rpstUrlForParent == nil {
 328  		common.Debugf("Environment variable %s not present, setting to empty string", ResourcePrincipalRpstEndpointForParent)
 329  		*b.rpstUrlForParent = ""
 330  	}
 331  
 332  	rpFedClient := resourcePrincipalV3Client{}
 333  	rpFedClient.rptUrl = *b.rptUrlForParent
 334  	rpFedClient.rpstUrl = *b.rpstUrlForParent
 335  	rpFedClient.sessionKeySupplier = newSessionKeySupplier()
 336  	rpFedClient.leafResourcePrincipalKeyProvider = b.leafResourcePrincipalKeyProvider
 337  	region, _ := b.leafResourcePrincipalKeyProvider.Region()
 338  
 339  	return &resourcePrincipalV30ConfigurationProvider{
 340  		keyProvider: resourcePrincipalKeyProviderV3{rpFedClient},
 341  		region:      (*common.Region)(&region),
 342  	}, nil
 343  }
 344  
 345  // ResourcePrincipalConfigurationProviderV3 ResourcePrincipalConfigurationProvider is a function that creates and configures a resource principal.
 346  func ResourcePrincipalConfigurationProviderV3(leafResourcePrincipalKeyProvider ConfigurationProviderWithClaimAccess) (ConfigurationProviderWithClaimAccess, error) {
 347  	builder := ResourcePrincipalV3ConfiguratorBuilder(leafResourcePrincipalKeyProvider)
 348  	builder.rptUrlForParent = requireEnv(ResourcePrincipalRptURLForParent)
 349  	builder.rpstUrlForParent = requireEnv(ResourcePrincipalRpstEndpointForParent)
 350  	return builder.Build()
 351  }
 352