resource_principals_v1.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  	"sync"
  13  	"time"
  14  
  15  	"github.com/nrdcg/oci-go-sdk/common/v1065"
  16  )
  17  
  18  // resourcePrincipalFederationClient is the client used to to talk acquire resource principals
  19  // No auth client, leaf or intermediate retrievers. We use certificates retrieved by instance principals to sign the operations of
  20  // resource principals
  21  type resourcePrincipalFederationClient struct {
  22  	tenancyID          string
  23  	instanceID         string
  24  	sessionKeySupplier sessionKeySupplier
  25  	mux                sync.Mutex
  26  	securityToken      securityToken
  27  	path               string
  28  
  29  	//instancePrincipalKeyProvider the instance Principal Key container
  30  	instancePrincipalKeyProvider instancePrincipalKeyProvider
  31  
  32  	//ResourcePrincipalTargetServiceClient client that calls the target service to acquire a resource principal token
  33  	ResourcePrincipalTargetServiceClient common.BaseClient
  34  
  35  	//ResourcePrincipalSessionTokenClient. The client used to communicate with identity to exchange a resource principal for
  36  	// resource principal session token
  37  	ResourcePrincipalSessionTokenClient common.BaseClient
  38  }
  39  
  40  type resourcePrincipalTokenRequest struct {
  41  	InstanceID string `contributesTo:"path" name:"id"`
  42  }
  43  
  44  type resourcePrincipalTokenResponse struct {
  45  	Body struct {
  46  		ResourcePrincipalToken       string `json:"resourcePrincipalToken"`
  47  		ServicePrincipalSessionToken string `json:"servicePrincipalSessionToken"`
  48  	} `presentIn:"body"`
  49  }
  50  
  51  type resourcePrincipalSessionTokenRequestBody struct {
  52  	ResourcePrincipalToken       string `json:"resourcePrincipalToken,omitempty"`
  53  	ServicePrincipalSessionToken string `json:"servicePrincipalSessionToken,omitempty"`
  54  	SessionPublicKey             string `json:"sessionPublicKey,omitempty"`
  55  }
  56  type resourcePrincipalSessionTokenRequest struct {
  57  	Body resourcePrincipalSessionTokenRequestBody `contributesTo:"body"`
  58  }
  59  
  60  // acquireResourcePrincipalToken acquires the resource principal from the target service
  61  func (c *resourcePrincipalFederationClient) acquireResourcePrincipalToken() (tokenResponse resourcePrincipalTokenResponse, err error) {
  62  	rpServiceClient := c.ResourcePrincipalTargetServiceClient
  63  
  64  	//Set the signer of this client to be the instance principal provider
  65  	rpServiceClient.Signer = common.DefaultRequestSigner(&c.instancePrincipalKeyProvider)
  66  
  67  	//Create a request with the instanceId
  68  	request, err := common.MakeDefaultHTTPRequestWithTaggedStruct(http.MethodGet, c.path, resourcePrincipalTokenRequest{InstanceID: c.instanceID})
  69  	if err != nil {
  70  		return
  71  	}
  72  
  73  	//Call the target service
  74  	response, err := rpServiceClient.Call(context.Background(), &request)
  75  	if err != nil {
  76  		return
  77  	}
  78  
  79  	defer common.CloseBodyIfValid(response)
  80  
  81  	tokenResponse = resourcePrincipalTokenResponse{}
  82  	err = common.UnmarshalResponse(response, &tokenResponse)
  83  	return
  84  }
  85  
  86  // exchangeToken exchanges a resource principal token from the target service with a session token from identity
  87  func (c *resourcePrincipalFederationClient) exchangeToken(publicKeyBase64 string, tokenResponse resourcePrincipalTokenResponse) (sessionToken string, err error) {
  88  	rpServiceClient := c.ResourcePrincipalSessionTokenClient
  89  
  90  	//Set the signer of this client to be the instance principal provider
  91  	rpServiceClient.Signer = common.DefaultRequestSigner(&c.instancePrincipalKeyProvider)
  92  
  93  	// Call identity service to get resource principal session token
  94  	sessionTokenReq := resourcePrincipalSessionTokenRequest{
  95  		resourcePrincipalSessionTokenRequestBody{
  96  			ServicePrincipalSessionToken: tokenResponse.Body.ServicePrincipalSessionToken,
  97  			ResourcePrincipalToken:       tokenResponse.Body.ResourcePrincipalToken,
  98  			SessionPublicKey:             publicKeyBase64,
  99  		},
 100  	}
 101  
 102  	sessionTokenHTTPReq, err := common.MakeDefaultHTTPRequestWithTaggedStruct(http.MethodPost,
 103  		"", sessionTokenReq)
 104  	if err != nil {
 105  		return
 106  	}
 107  
 108  	sessionTokenHTTPRes, err := rpServiceClient.Call(context.Background(), &sessionTokenHTTPReq)
 109  	if err != nil {
 110  		return
 111  	}
 112  	defer common.CloseBodyIfValid(sessionTokenHTTPRes)
 113  
 114  	sessionTokenRes := x509FederationResponse{}
 115  	err = common.UnmarshalResponse(sessionTokenHTTPRes, &sessionTokenRes)
 116  	if err != nil {
 117  		return
 118  	}
 119  
 120  	sessionToken = sessionTokenRes.Token.Token
 121  	return
 122  }
 123  
 124  // getSecurityToken makes the appropiate calls to acquire a resource principal security token
 125  func (c *resourcePrincipalFederationClient) getSecurityToken() (securityToken, error) {
 126  	var err error
 127  	ipFederationClient := c.instancePrincipalKeyProvider.FederationClient
 128  
 129  	common.Debugf("Refreshing instance principal token")
 130  	//Refresh instance principal token
 131  	if refreshable, ok := ipFederationClient.(*x509FederationClient); ok {
 132  		err = refreshable.renewSecurityTokenIfNotValid()
 133  		if err != nil {
 134  			return nil, err
 135  		}
 136  	}
 137  
 138  	//Acquire resource principal token from target service
 139  	common.Debugf("Acquiring resource principal token from target service")
 140  	tokenResponse, err := c.acquireResourcePrincipalToken()
 141  	if err != nil {
 142  		return nil, err
 143  	}
 144  
 145  	//Read the public key from the session supplier.
 146  	pem := c.sessionKeySupplier.PublicKeyPemRaw()
 147  	pemSanitized := sanitizeCertificateString(string(pem))
 148  
 149  	//Exchange resource principal token for session token from identity
 150  	common.Debugf("Exchanging resource principal token for resource principal session token")
 151  	sessionToken, err := c.exchangeToken(pemSanitized, tokenResponse)
 152  	if err != nil {
 153  		return nil, err
 154  	}
 155  
 156  	return newPrincipalToken(sessionToken) // should be a resource principal token
 157  }
 158  
 159  func (c *resourcePrincipalFederationClient) renewSecurityToken() (err error) {
 160  	if err = c.sessionKeySupplier.Refresh(); err != nil {
 161  		return fmt.Errorf("failed to refresh session key: %s", err.Error())
 162  	}
 163  	common.Logf("Renewing resource principal security token at: %v\n", time.Now().Format("15:04:05.000"))
 164  	if c.securityToken, err = c.getSecurityToken(); err != nil {
 165  		return fmt.Errorf("failed to get security token: %s", err.Error())
 166  	}
 167  	common.Logf("Resource principal security token renewed at: %v\n", time.Now().Format("15:04:05.000"))
 168  
 169  	return nil
 170  }
 171  
 172  // ResourcePrincipal Key provider in charge of resource principal acquiring tokens
 173  type resourcePrincipalKeyProviderV1 struct {
 174  	ResourcePrincipalClient resourcePrincipalFederationClient
 175  }
 176  
 177  func (c *resourcePrincipalFederationClient) renewSecurityTokenIfNotValid() (err error) {
 178  	if c.securityToken == nil || !c.securityToken.Valid() {
 179  		if err = c.renewSecurityToken(); err != nil {
 180  			return fmt.Errorf("failed to renew resource prinicipal security token: %s", err.Error())
 181  		}
 182  	}
 183  	return nil
 184  }
 185  
 186  func (c *resourcePrincipalFederationClient) PrivateKey() (*rsa.PrivateKey, error) {
 187  	c.mux.Lock()
 188  	defer c.mux.Unlock()
 189  
 190  	if err := c.renewSecurityTokenIfNotValid(); err != nil {
 191  		return nil, err
 192  	}
 193  	return c.sessionKeySupplier.PrivateKey(), nil
 194  }
 195  
 196  func (c *resourcePrincipalFederationClient) SecurityToken() (token string, err error) {
 197  	c.mux.Lock()
 198  	defer c.mux.Unlock()
 199  
 200  	if err = c.renewSecurityTokenIfNotValid(); err != nil {
 201  		return "", err
 202  	}
 203  	return c.securityToken.String(), nil
 204  }
 205  
 206  func (p *resourcePrincipalConfigurationProvider) PrivateRSAKey() (privateKey *rsa.PrivateKey, err error) {
 207  	if privateKey, err = p.keyProvider.ResourcePrincipalClient.PrivateKey(); err != nil {
 208  		err = fmt.Errorf("failed to get resource principal private key: %s", err.Error())
 209  		return nil, err
 210  	}
 211  	return privateKey, nil
 212  }
 213  
 214  func (p *resourcePrincipalConfigurationProvider) KeyID() (string, error) {
 215  	var securityToken string
 216  	var err error
 217  	if securityToken, err = p.keyProvider.ResourcePrincipalClient.SecurityToken(); err != nil {
 218  		return "", fmt.Errorf("failed to get resource principal security token: %s", err.Error())
 219  	}
 220  	return fmt.Sprintf("ST$%s", securityToken), nil
 221  }
 222  
 223  func (p *resourcePrincipalConfigurationProvider) TenancyOCID() (string, error) {
 224  	return p.keyProvider.ResourcePrincipalClient.instancePrincipalKeyProvider.TenancyOCID()
 225  }
 226  
 227  // todo what is this
 228  func (p *resourcePrincipalConfigurationProvider) GetClaim(key string) (interface{}, error) {
 229  	return nil, nil
 230  }
 231  
 232  // Resource Principals
 233  type resourcePrincipalConfigurationProvider struct {
 234  	keyProvider resourcePrincipalKeyProviderV1
 235  	region      *common.Region
 236  }
 237  
 238  func newResourcePrincipalKeyProvider(ipKeyProvider instancePrincipalKeyProvider, rpTokenTargetServiceClient, rpSessionTokenClient common.BaseClient, instanceID, path string) (keyProvider resourcePrincipalKeyProviderV1, err error) {
 239  	rpFedClient := resourcePrincipalFederationClient{}
 240  	rpFedClient.tenancyID = ipKeyProvider.TenancyID
 241  	rpFedClient.instanceID = instanceID
 242  	rpFedClient.sessionKeySupplier = newSessionKeySupplier()
 243  	rpFedClient.ResourcePrincipalTargetServiceClient = rpTokenTargetServiceClient
 244  	rpFedClient.ResourcePrincipalSessionTokenClient = rpSessionTokenClient
 245  	rpFedClient.instancePrincipalKeyProvider = ipKeyProvider
 246  	rpFedClient.path = path
 247  	keyProvider = resourcePrincipalKeyProviderV1{ResourcePrincipalClient: rpFedClient}
 248  	return
 249  }
 250  
 251  func (p *resourcePrincipalConfigurationProvider) AuthType() (common.AuthConfig, error) {
 252  	return common.AuthConfig{common.UnknownAuthenticationType, false, nil},
 253  		fmt.Errorf("unsupported, keep the interface")
 254  }
 255  
 256  func (p resourcePrincipalConfigurationProvider) UserOCID() (string, error) {
 257  	return "", nil
 258  }
 259  
 260  func (p resourcePrincipalConfigurationProvider) KeyFingerprint() (string, error) {
 261  	return "", nil
 262  }
 263  
 264  func (p resourcePrincipalConfigurationProvider) Region() (string, error) {
 265  	if p.region == nil {
 266  		region := p.keyProvider.ResourcePrincipalClient.instancePrincipalKeyProvider.RegionForFederationClient()
 267  		common.Debugf("Region in resource principal configuration provider is nil. Returning instance principal federation clients region: %s", region)
 268  		return string(region), nil
 269  	}
 270  	return string(*p.region), nil
 271  }
 272  
 273  func (p resourcePrincipalConfigurationProvider) Refreshable() bool {
 274  	return true
 275  }
 276  
 277  // resourcePrincipalConfigurationProviderForInstanceWithClients returns a configuration for instance principals
 278  // resourcePrincipalTargetServiceTokenClient and resourcePrincipalSessionTokenClient are clients that at last need to have
 279  // their base path and host properly set for their respective services. Additionally the clients can be further customized
 280  // to provide mocking or any other customization for the requests/responses
 281  func resourcePrincipalConfigurationProviderForInstanceWithClients(instancePrincipalProvider common.ConfigurationProvider,
 282  	resourcePrincipalTargetServiceTokenClient, resourcePrincipalSessionTokenClient common.BaseClient, instanceID, path string) (*resourcePrincipalConfigurationProvider, error) {
 283  	var ok bool
 284  	var ip instancePrincipalConfigurationProvider
 285  	if ip, ok = instancePrincipalProvider.(instancePrincipalConfigurationProvider); !ok {
 286  		return nil, fmt.Errorf("instancePrincipalConfigurationProvider needs to be of type vald Instance Principal Configuration Provider")
 287  	}
 288  
 289  	keyProvider, err := newResourcePrincipalKeyProvider(ip.keyProvider, resourcePrincipalTargetServiceTokenClient, resourcePrincipalSessionTokenClient, instanceID, path)
 290  	if err != nil {
 291  		return nil, err
 292  	}
 293  
 294  	provider := &resourcePrincipalConfigurationProvider{
 295  		region:      nil,
 296  		keyProvider: keyProvider,
 297  	}
 298  	return provider, nil
 299  }
 300  
 301  const identityResourcePrincipalSessionTokenPath = "/v1/resourcePrincipalSessionToken"
 302  
 303  // resourcePrincipalConfigurationProviderForInstanceWithInterceptor creates a resource principal configuration provider with
 304  // a interceptor used to customize the call going to the resource principal token request to the target service
 305  // for a given instance ID
 306  func resourcePrincipalConfigurationProviderForInstanceWithInterceptor(instancePrincipalProvider common.ConfigurationProvider, resourcePrincipalTokenEndpoint, instanceID string, interceptor common.RequestInterceptor) (provider *resourcePrincipalConfigurationProvider, err error) {
 307  
 308  	//Build the target service client
 309  	rpTargetServiceClient, err := common.NewClientWithConfig(instancePrincipalProvider)
 310  	if err != nil {
 311  		return
 312  	}
 313  
 314  	rpTokenURL, err := url.Parse(resourcePrincipalTokenEndpoint)
 315  	if err != nil {
 316  		return
 317  	}
 318  
 319  	rpTargetServiceClient.Host = rpTokenURL.Scheme + "://" + rpTokenURL.Host
 320  	rpTargetServiceClient.Interceptor = interceptor
 321  
 322  	var path string
 323  	if rpTokenURL.Path != "" {
 324  		path = rpTokenURL.Path
 325  	} else {
 326  		path = identityResourcePrincipalSessionTokenPath
 327  	}
 328  
 329  	//Build the identity client for token service
 330  	rpTokenSessionClient, err := common.NewClientWithConfig(instancePrincipalProvider)
 331  	if err != nil {
 332  		return
 333  	}
 334  
 335  	// Set RPST endpoint if passed in from env var, otherwise create it from region
 336  	resourcePrincipalSessionTokenEndpoint := requireEnv(ResourcePrincipalSessionTokenEndpoint)
 337  	if resourcePrincipalSessionTokenEndpoint != nil {
 338  		rpSessionTokenURL, err := url.Parse(*resourcePrincipalSessionTokenEndpoint)
 339  		if err != nil {
 340  			return nil, err
 341  		}
 342  
 343  		rpTokenSessionClient.Host = rpSessionTokenURL.Scheme + "://" + rpSessionTokenURL.Host
 344  	} else {
 345  		regionStr, err := instancePrincipalProvider.Region()
 346  		if err != nil {
 347  			return nil, fmt.Errorf("missing RPST env var and cannot determine region: %v", err)
 348  		}
 349  		region := common.StringToRegion(regionStr)
 350  		rpTokenSessionClient.Host = fmt.Sprintf("https://%s", region.Endpoint("auth"))
 351  	}
 352  
 353  	rpTokenSessionClient.BasePath = identityResourcePrincipalSessionTokenPath
 354  
 355  	return resourcePrincipalConfigurationProviderForInstanceWithClients(instancePrincipalProvider, rpTargetServiceClient, rpTokenSessionClient, instanceID, path)
 356  }
 357  
 358  // ResourcePrincipalConfigurationProviderWithInterceptor creates a resource principal configuration provider with endpoints
 359  // a interceptor used to customize the call going to the resource principal token request to the target service
 360  // see https://godoc.org/github.com/oracle/oci-go-sdk/common#RequestInterceptor
 361  func ResourcePrincipalConfigurationProviderWithInterceptor(instancePrincipalProvider common.ConfigurationProvider,
 362  	resourcePrincipalTokenEndpoint, resourcePrincipalSessionTokenEndpoint string,
 363  	interceptor common.RequestInterceptor) (common.ConfigurationProvider, error) {
 364  
 365  	return resourcePrincipalConfigurationProviderForInstanceWithInterceptor(instancePrincipalProvider, resourcePrincipalTokenEndpoint, "", interceptor)
 366  }
 367  
 368  // resourcePrincipalConfigurationProviderV1 creates a resource principal configuration provider with
 369  // endpoints for both resource principal token and resource principal token session
 370  func resourcePrincipalConfigurationProviderV1(resourcePrincipalTokenEndpoint, resourceID string) (*resourcePrincipalConfigurationProvider, error) {
 371  
 372  	instancePrincipalProvider, err := InstancePrincipalConfigurationProvider()
 373  	if err != nil {
 374  		return nil, err
 375  	}
 376  	return resourcePrincipalConfigurationProviderForInstanceWithInterceptor(instancePrincipalProvider, resourcePrincipalTokenEndpoint, resourceID, nil)
 377  }
 378