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)(®ion),
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