Skip to content

Commit a058e7d

Browse files
Merge pull request #140 from chrischdi/pr-fix-client-reuse-single-creds-file
OCPBUGS-38759: client: re-use a single file for building the session instead of randomly named files
2 parents d1cc300 + 5cab19a commit a058e7d

File tree

1 file changed

+35
-12
lines changed

1 file changed

+35
-12
lines changed

pkg/client/client.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import (
2020
"bytes"
2121
"context"
2222
"fmt"
23-
"io/ioutil"
2423
"os"
24+
"path"
2525
"strings"
2626
"sync"
2727
"time"
@@ -45,6 +45,7 @@ import (
4545
configv1 "github.com/openshift/api/config/v1"
4646
machineapiapierrors "github.com/openshift/machine-api-operator/pkg/controller/machine"
4747
apimachineryerrors "k8s.io/apimachinery/pkg/api/errors"
48+
"k8s.io/apimachinery/pkg/util/rand"
4849
)
4950

5051
//go:generate go run ../../vendor/github.com/golang/mock/mockgen -source=./client.go -destination=./mock/client_generated.go -package=mock
@@ -68,6 +69,11 @@ const (
6869
awsRegionsCacheExpirationDuration = time.Minute * 30
6970
)
7071

72+
var (
73+
sharedCredentialsFileMutex sync.Mutex
74+
sharedCredentialsFileName = path.Join(os.TempDir(), "aws-shared-credentials"+rand.String(16))
75+
)
76+
7177
// AwsClientBuilderFuncType is function type for building aws client
7278
type AwsClientBuilderFuncType func(client client.Client, secretName, namespace, region string, configManagedClient client.Client, regionCache RegionCache) (Client, error)
7379

@@ -354,7 +360,7 @@ func NewValidatedClient(ctrlRuntimeClient client.Client, secretName, namespace,
354360
}, nil
355361
}
356362

357-
func newAWSSession(ctrlRuntimeClient client.Client, secretName, namespace, region string, configManagedClient client.Client) (*session.Session, error) {
363+
func newAWSSession(ctrlRuntimeClient client.Client, secretName, namespace, region string, configManagedClient client.Client) (s *session.Session, err error) {
358364
sessionOptions := session.Options{
359365
Config: aws.Config{
360366
Region: aws.String(region),
@@ -369,10 +375,20 @@ func newAWSSession(ctrlRuntimeClient client.Client, secretName, namespace, regio
369375
}
370376
return nil, err
371377
}
378+
sharedCredentialsFileMutex.Lock()
379+
defer sharedCredentialsFileMutex.Unlock()
372380
sharedCredsFile, err := sharedCredentialsFileFromSecret(&secret)
373381
if err != nil {
374382
return nil, fmt.Errorf("failed to create shared credentials file from Secret: %v", err)
375383
}
384+
385+
// Ensure the file gets deleted in any case.
386+
defer func() {
387+
if removeErr := os.Remove(sharedCredsFile); removeErr != nil && err == nil {
388+
err = fmt.Errorf("failed to remove shared credentials file %s: %v", sharedCredsFile, removeErr)
389+
}
390+
}()
391+
376392
sessionOptions.SharedConfigState = session.SharedConfigEnable
377393
sessionOptions.SharedConfigFiles = []string{sharedCredsFile}
378394
}
@@ -387,16 +403,11 @@ func newAWSSession(ctrlRuntimeClient client.Client, secretName, namespace, regio
387403
}
388404

389405
// Otherwise default to relying on the IAM role of the masters where the actuator is running:
390-
s, err := session.NewSessionWithOptions(sessionOptions)
406+
s, err = session.NewSessionWithOptions(sessionOptions)
391407
if err != nil {
392408
return nil, err
393409
}
394410

395-
// Remove any temporary shared credentials files after session creation so they don't accumulate
396-
if len(sessionOptions.SharedConfigFiles) > 0 {
397-
os.Remove(sessionOptions.SharedConfigFiles[0])
398-
}
399-
400411
s.Handlers.Build.PushBackNamed(addProviderVersionToUserAgent)
401412

402413
return s, nil
@@ -457,7 +468,7 @@ func buildCustomEndpointsMap(customEndpoints []configv1.AWSServiceEndpoint) map[
457468

458469
// sharedCredentialsFileFromSecret returns a location (path) to the shared credentials
459470
// file that was created using the provided secret
460-
func sharedCredentialsFileFromSecret(secret *corev1.Secret) (string, error) {
471+
func sharedCredentialsFileFromSecret(secret *corev1.Secret) (filename string, err error) {
461472
var data []byte
462473
switch {
463474
case len(secret.Data["credentials"]) > 0:
@@ -471,12 +482,24 @@ func sharedCredentialsFileFromSecret(secret *corev1.Secret) (string, error) {
471482
return "", fmt.Errorf("invalid secret for aws credentials")
472483
}
473484

474-
f, err := ioutil.TempFile("", "aws-shared-credentials")
485+
// Re-using the same file every time to prevent leakage of memory to slab.
486+
// Related issue: https://issues.redhat.com/browse/RHEL-119532
487+
f, err := os.Create(sharedCredentialsFileName)
475488
if err != nil {
476489
return "", fmt.Errorf("failed to create file for shared credentials: %v", err)
477490
}
478-
defer f.Close()
479-
if _, err := f.Write(data); err != nil {
491+
492+
defer func() {
493+
if closeErr := f.Close(); closeErr != nil && err == nil {
494+
err = fmt.Errorf("failed to close file %s: %v", f.Name(), closeErr)
495+
}
496+
}()
497+
498+
if _, err = f.Write(data); err != nil {
499+
// Delete the file in case of having an error. Otherwise the calling function needs to handle deletion.
500+
if deleteErr := os.Remove(f.Name()); deleteErr != nil {
501+
return "", fmt.Errorf("failed to write credentials to %s and delete it afterwards: %v, %v", f.Name(), err, deleteErr)
502+
}
480503
return "", fmt.Errorf("failed to write credentials to %s: %v", f.Name(), err)
481504
}
482505
return f.Name(), nil

0 commit comments

Comments
 (0)