-
Notifications
You must be signed in to change notification settings - Fork 32
OCPBUGS-38759: client: re-use a single file for building the session instead of randomly named files #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
OCPBUGS-38759: client: re-use a single file for building the session instead of randomly named files #140
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,8 @@ import ( | |
"bytes" | ||
"context" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
@@ -45,6 +45,7 @@ import ( | |
configv1 "github.com/openshift/api/config/v1" | ||
machineapiapierrors "github.com/openshift/machine-api-operator/pkg/controller/machine" | ||
apimachineryerrors "k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/apimachinery/pkg/util/rand" | ||
) | ||
|
||
//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 ( | |
awsRegionsCacheExpirationDuration = time.Minute * 30 | ||
) | ||
|
||
var ( | ||
sharedCredentialsFileMutex sync.Mutex | ||
sharedCredentialsFileName = path.Join(os.TempDir(), "aws-shared-credentials"+rand.String(16)) | ||
) | ||
|
||
// AwsClientBuilderFuncType is function type for building aws client | ||
type AwsClientBuilderFuncType func(client client.Client, secretName, namespace, region string, configManagedClient client.Client, regionCache RegionCache) (Client, error) | ||
|
||
|
@@ -369,10 +375,18 @@ func newAWSSession(ctrlRuntimeClient client.Client, secretName, namespace, regio | |
} | ||
return nil, err | ||
} | ||
sharedCredentialsFileMutex.Lock() | ||
defer sharedCredentialsFileMutex.Unlock() | ||
sharedCredsFile, err := sharedCredentialsFileFromSecret(&secret) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create shared credentials file from Secret: %v", err) | ||
} | ||
|
||
// Ensure the file gets deleted in any case. | ||
defer func() { | ||
os.Remove(sharedCredsFile) | ||
}() | ||
|
||
sessionOptions.SharedConfigState = session.SharedConfigEnable | ||
sessionOptions.SharedConfigFiles = []string{sharedCredsFile} | ||
} | ||
|
@@ -392,11 +406,6 @@ func newAWSSession(ctrlRuntimeClient client.Client, secretName, namespace, regio | |
return nil, err | ||
} | ||
|
||
// Remove any temporary shared credentials files after session creation so they don't accumulate | ||
if len(sessionOptions.SharedConfigFiles) > 0 { | ||
os.Remove(sessionOptions.SharedConfigFiles[0]) | ||
} | ||
|
||
s.Handlers.Build.PushBackNamed(addProviderVersionToUserAgent) | ||
|
||
return s, nil | ||
|
@@ -471,12 +480,16 @@ func sharedCredentialsFileFromSecret(secret *corev1.Secret) (string, error) { | |
return "", fmt.Errorf("invalid secret for aws credentials") | ||
} | ||
|
||
f, err := ioutil.TempFile("", "aws-shared-credentials") | ||
// Re-using the same file every time to prevent leakage of memory to slab. | ||
// Related issue: https://issues.redhat.com/browse/RHEL-119532 | ||
f, err := os.Create(sharedCredentialsFileName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still writing the file every time, even if it now has the same filename. If this is enough to stem the bleeding then fine, I guess, but we should put a big comment here explaining why we're not using ioutil.TempFile() and why we shouldn't put it back, preferrably with a link to a RHEL bug. However, ideally we would return the same file if the contents are the same. How about we give the file some name which is unique to the secret's contents and don't create it if it already exists? A couple of ideas:
The latter seems pretty simple to write. A downside is that over time we accumulate every version of the credentials we have ever seen in the temp directory, but I suspect that would never be an issue in practice and the only actor who can exploit it has no incentive to DoS themselves. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Going to add a comment 👍 agree.
I'd prefer to not cache the credentials on disk. If we want to do some caching instead, we might want to cache the session object in memory and not need the whole write/read/delete cycle at all. But I think this goes too far for this PR. |
||
if err != nil { | ||
return "", fmt.Errorf("failed to create file for shared credentials: %v", err) | ||
} | ||
defer f.Close() | ||
if _, err := f.Write(data); err != nil { | ||
// Delete the file in case of having an error. Otherwise the calling function needs to handle deletion. | ||
defer func() { os.Remove(f.Name()) }() | ||
return "", fmt.Errorf("failed to write credentials to %s: %v", f.Name(), err) | ||
} | ||
return f.Name(), nil | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can
rand.String
output characters which are invalid in a path?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only uses
bcdfghjklmnpqrstvwxz2456789
So no :-)