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