-
Notifications
You must be signed in to change notification settings - Fork 352
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
Return token expiry with presigned URLs #6337
Return token expiry with presigned URLs #6337
Conversation
Only S3 works, the others just return a zero time.Time for "not implemented".
🎊 PR Preview 8cd2a01 has been successfully built and deployed to https://treeverse-lakeFS-preview-pr-6337.surge.sh 🕐 Build time: 0.015s 🤖 By surge-preview |
Removing myself from reviewers as I currently don't have the bandwidth to review this PR- sorry |
Useful, and also lets me test that a deployment on lakeFS.Cloud staging works! >:-)
Here's what it looks like (with 29d7cde) on lakeFS.Cloud (I shortened the presigned URL and removed all the interesting parts that were actually signed...): $ lakectl fs stat --pre-sign lakefs://sample-repo/main/boo
Path: boo
Modified Time: 2023-08-07 15:03:28 +0300 IDT
Size: 4 bytes
Human Size: 4 B
Physical Address: https://lakefs-sample-us-east-1-staging.s3.amazonaws.com/tokenpathsigsyzzy?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAAFRICAAUSTRALIA%2F20230807%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230807T164925Z&X-Amz-Expires=900&X-Amz-Security-Token=shhh&X-Amz-SignedHeaders=host&X-Amz-Signature=123456
Physical Address Expires: 2023-08-07 20:03:54 +0300 IDT
Checksum: 90580754da3ed1b3e4be38c9b277bc9b
Content-Type: application/octet-stream |
5024ae4
to
f369b04
Compare
Some lakeFS block adaptors report expiry, others do not. Test lakectl using an appropriate golden file
f369b04
to
7da818a
Compare
I will pull with a single approval -- unless you ask! |
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.
LGTM
// ErrDoesntExpire is returned by an Expirer if expiry times cannot be | ||
// determined. For instance, if AWS is configured using an access key then | ||
// Expirer cannot determine expiry. |
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.
Is it returned if it doesn't expire or in case it cannot be determined?
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.
Kinda both. If it is impossible to determine expiry it is actually quite hard to determine whether the token has expired, see below. So if the credentials used to configure AWS don't support expiry, either they will never expire, or they will and lakeFS will never know. But note that when this happens all is not lost: lakeFS will still report the built-in 15-minute expiry that was placed on the URL.
Impossible to determine expiry: I can find no AWS SDK call that gives the time remaining on a given authenticated client. The only built-in credentials provder there that supports ExpiresAt seems to be ssocreds -- which reads a token file But e.g. EnvProvider can read an STS token -- and does not support ExpiresAt.
pkg/block/azure/adapter.go
Outdated
url, err := a.getPreSignedURL(ctx, obj, permissions) | ||
return url, time.Time{}, err |
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.
Why isn't this supported on azure? Do we plan to? maybe add a TODO
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.
Absolutely! Just immediate issue is on AWS. Opened #6347 and added TODOs here and in GCS.
} | ||
return k, nil | ||
return k, time.Time{}, 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.
Why isn't this supported on GCS, do we plan to? Maybe add a TODO
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.
Yup!
pkg/block/adapter.go
Outdated
// Returns a presigned URL for accessing obj with mode, and the | ||
// expiry time for this URL. The expiry time IsZero() if reporting | ||
// expiry is no supported. The expiry time will be sooner than | ||
// Config.*.PreSignedExpiry if an auth token is about to expire. |
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.
// Returns a presigned URL for accessing obj with mode, and the | |
// expiry time for this URL. The expiry time IsZero() if reporting | |
// expiry is no supported. The expiry time will be sooner than | |
// Config.*.PreSignedExpiry if an auth token is about to expire. | |
// Returns a presigned URL for accessing obj with mode, and the | |
// expiry time for this URL. The expiry time IsZero() if reporting | |
// expiry is not supported. The expiry time will be sooner than | |
// Config.*.PreSignedExpiry if an auth token is about to expire. |
expiry := time.Now().Add(a.preSignedExpiry) | ||
clientExpiry, clientExpiryErr := client.ExpiresAt() | ||
switch { | ||
case clientExpiryErr == nil: | ||
if clientExpiry.Before(expiry) { | ||
expiry = clientExpiry | ||
} | ||
case errors.Is(clientExpiryErr, ErrDoesntExpire): | ||
break | ||
default: | ||
log.WithFields(logging.Fields{ | ||
"namespace": obj.StorageNamespace, | ||
"identifier": obj.Identifier, | ||
}).WithError(err).Warning("Failed to get client (token) expiry: URL expiry may be too high") | ||
} | ||
return preSignedURL, expiry, err |
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.
Just a thought:
If the Block Adapter would have a method that returns the client expiry we could run this logic only once in the controller, I think it might make things a bit simple
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.
At least for S3 we support multiple AWS clients, per namespace. So this method would need to be called with a namespace parameter. At which point it becomes simpler to push it to presigning.
Meanwhile, the controller uses this in multiple places, and we have a few more non-controller calls. At most we'll be able to extract this logic to the adaptor level -- let's think about refactoring after adding support for Azure and GCS.
Also fix tpyo.
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.
Thanks!
pkg/block/azure/adapter.go
Outdated
url, err := a.getPreSignedURL(ctx, obj, permissions) | ||
return url, time.Time{}, err |
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.
Absolutely! Just immediate issue is on AWS. Opened #6347 and added TODOs here and in GCS.
} | ||
return k, nil | ||
return k, time.Time{}, 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.
Yup!
expiry := time.Now().Add(a.preSignedExpiry) | ||
clientExpiry, clientExpiryErr := client.ExpiresAt() | ||
switch { | ||
case clientExpiryErr == nil: | ||
if clientExpiry.Before(expiry) { | ||
expiry = clientExpiry | ||
} | ||
case errors.Is(clientExpiryErr, ErrDoesntExpire): | ||
break | ||
default: | ||
log.WithFields(logging.Fields{ | ||
"namespace": obj.StorageNamespace, | ||
"identifier": obj.Identifier, | ||
}).WithError(err).Warning("Failed to get client (token) expiry: URL expiry may be too high") | ||
} | ||
return preSignedURL, expiry, err |
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.
At least for S3 we support multiple AWS clients, per namespace. So this method would need to be called with a namespace parameter. At which point it becomes simpler to push it to presigning.
Meanwhile, the controller uses this in multiple places, and we have a few more non-controller calls. At most we'll be able to extract this logic to the adaptor level -- let's think about refactoring after adding support for Azure and GCS.
// ErrDoesntExpire is returned by an Expirer if expiry times cannot be | ||
// determined. For instance, if AWS is configured using an access key then | ||
// Expirer cannot determine expiry. |
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.
Kinda both. If it is impossible to determine expiry it is actually quite hard to determine whether the token has expired, see below. So if the credentials used to configure AWS don't support expiry, either they will never expire, or they will and lakeFS will never know. But note that when this happens all is not lost: lakeFS will still report the built-in 15-minute expiry that was placed on the URL.
Impossible to determine expiry: I can find no AWS SDK call that gives the time remaining on a given authenticated client. The only built-in credentials provder there that supports ExpiresAt seems to be ssocreds -- which reads a token file But e.g. EnvProvider can read an STS token -- and does not support ExpiresAt.
Fixes #6328.