Skip to content
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

Merged
merged 9 commits into from
Aug 8, 2023

Conversation

arielshaqed
Copy link
Contributor

Fixes #6328.

@github-actions
Copy link

github-actions bot commented Aug 7, 2023

🎊 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

@arielshaqed arielshaqed added area/API Improvements or additions to the API new-feature Issues that introduce new feature or capability include-changelog PR description should be included in next release changelog AWS labels Aug 7, 2023
@arielshaqed arielshaqed requested a review from N-o-Z August 7, 2023 15:04
@N-o-Z
Copy link
Member

N-o-Z commented Aug 7, 2023

Removing myself from reviewers as I currently don't have the bandwidth to review this PR- sorry

@N-o-Z N-o-Z removed their request for review August 7, 2023 16:01
Useful, and also lets me test that a deployment on lakeFS.Cloud staging
works! >:-)
@arielshaqed
Copy link
Contributor Author

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

@arielshaqed arielshaqed force-pushed the feature/6328-token-expiry-in-presigned-urls branch from 5024ae4 to f369b04 Compare August 7, 2023 19:20
Some lakeFS block adaptors report expiry, others do not.  Test lakectl
using an appropriate golden file
@arielshaqed arielshaqed force-pushed the feature/6328-token-expiry-in-presigned-urls branch from f369b04 to 7da818a Compare August 7, 2023 19:20
@arielshaqed
Copy link
Contributor Author

I will pull with a single approval -- unless you ask!

Copy link
Contributor

@guy-har guy-har left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +21 to +23
// 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.
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Comment on lines 229 to 230
url, err := a.getPreSignedURL(ctx, obj, permissions)
return url, time.Time{}, err
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup!

Comment on lines 137 to 140
// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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.

Comment on lines +359 to +374
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
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

@arielshaqed arielshaqed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment on lines 229 to 230
url, err := a.getPreSignedURL(ctx, obj, permissions)
return url, time.Time{}, err
Copy link
Contributor Author

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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup!

Comment on lines +359 to +374
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
Copy link
Contributor Author

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.

Comment on lines +21 to +23
// 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.
Copy link
Contributor Author

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.

@arielshaqed arielshaqed merged commit accced2 into master Aug 8, 2023
33 checks passed
@arielshaqed arielshaqed deleted the feature/6328-token-expiry-in-presigned-urls branch August 8, 2023 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/API Improvements or additions to the API AWS include-changelog PR description should be included in next release changelog new-feature Issues that introduce new feature or capability
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Return actual expiry including token expiry for presigned URLs
3 participants