-
Notifications
You must be signed in to change notification settings - Fork 122
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
Support the EC2 Metadata Server #115
Changes from 1 commit
411b67b
0479bdb
92285db
5b676b5
0219b84
5cbcb5b
184c4bd
9d090be
80d1feb
6d43437
09fc335
30c50bf
5e0b844
9e31e18
1012da1
e148544
1e0d6dc
94bf245
b9ec0e9
73df548
b568308
aae7d86
9f81367
4141498
ff93dc5
6fbad81
7557ce7
802991a
db2b917
520550d
9ec5bc2
97e7236
2ff9037
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 |
---|---|---|
@@ -0,0 +1,179 @@ | ||
// Copyright 2023 The TensorStore Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include "aws_metadata_credential_provider.h" | ||
sjperkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#include "tensorstore/internal/json_binding/absl_time.h" | ||
#include "tensorstore/internal/json_binding/bindable.h" | ||
#include "tensorstore/internal/json_binding/json_binding.h" | ||
|
||
using ::tensorstore::Result; | ||
using ::tensorstore::internal::ParseJson; | ||
using ::tensorstore::internal_http::HttpResponseCodeToStatus; | ||
using ::tensorstore::internal_kvstore_s3::AwsCredentials; | ||
|
||
namespace jb = tensorstore::internal_json_binding; | ||
|
||
namespace tensorstore { | ||
namespace internal_kvstore_s3 { | ||
|
||
namespace { | ||
|
||
// EC2 Metadata server url | ||
static constexpr char kEc2MetadataUrl[] = "http://http://169.254.169.254"; | ||
// Token ttl header | ||
static constexpr char kTokenTtlHeader[] = "x-aws-ec2-metadata-token-ttl-seconds"; | ||
// Token header | ||
static constexpr char kMetadataTokenHeader[] = "x-aws-ec2-metadata-token"; | ||
|
||
static constexpr char kTokenPath[] = "/latest/api/token"; | ||
static constexpr char kIamPath[] = "/latest/meta-data/iam/"; | ||
static constexpr char kIamSecurityCredentialsPath[] = "/latest/meta-data/iam/security-credentials/"; | ||
|
||
// Requests to the above server block outside AWS | ||
// Configure a timeout small enough not to degrade performance outside AWS | ||
// but large enough inside AWS | ||
static constexpr absl::Duration kConnectTimeout = absl::Milliseconds(200); | ||
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. include any directly named types. I notice at least, in this file: string 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. Done, I think the existing 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. There are a lot more includes missing. Look at the using declarations, for example. Anything that you spell out, such as 'tensorstore::Result' needs an import in each file where it is spelled (include-what-you-use); we don't rely on transitive includes for any of those cases. We only permit transitive includes for non-spelled field access and auto type use. json_binding is different, though. Those includes are needed to satisfy the need for type-specializations to be visible where they are used, such as bindings for absl::Time. 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. Ah, thanks for pointing this out. I've gone through the file on a IWYU basis and adjusted the |
||
|
||
/// Represents JSON returned from | ||
/// http://http://169.254.169.254/latest/meta-data/iam/security-credentials/<iam-role>/ | ||
/// where <iam-role> is usually returned as a response from a request to | ||
/// http://http://169.254.169.254/latest/meta-data/iam/security-credentials/ | ||
struct EC2CredentialsResponse { | ||
std::string Code; | ||
absl::Time LastUpdated; | ||
std::string Type; | ||
std::string AccessKeyId; | ||
std::string SecretAccessKey; | ||
std::string Token; | ||
absl::Time Expiration; | ||
|
||
using ToJsonOptions = IncludeDefaults; | ||
sjperkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
using FromJsonOptions = internal_json_binding::NoOptions; | ||
|
||
TENSORSTORE_DECLARE_JSON_DEFAULT_BINDER( | ||
EC2CredentialsResponse, | ||
internal_kvstore_s3::EC2CredentialsResponse::FromJsonOptions, | ||
internal_kvstore_s3::EC2CredentialsResponse::ToJsonOptions) | ||
}; | ||
|
||
inline constexpr auto EC2CredentialsResponseBinder = jb::Object( | ||
jb::Member("Code", jb::Projection(&EC2CredentialsResponse::Code)), | ||
jb::Member("LastUpdated", jb::Projection(&EC2CredentialsResponse::LastUpdated)), | ||
jb::Member("Type", jb::Projection(&EC2CredentialsResponse::Type)), | ||
jb::Member("AccessKeyId", jb::Projection(&EC2CredentialsResponse::AccessKeyId)), | ||
jb::Member("SecretAccessKey", jb::Projection(&EC2CredentialsResponse::SecretAccessKey)), | ||
jb::Member("Token", jb::Projection(&EC2CredentialsResponse::Token)), | ||
jb::Member("Expiration", jb::Projection(&EC2CredentialsResponse::Expiration)) | ||
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. I suspect that, except for A sample from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials {
"Code" : "Success",
"LastUpdated" : "2012-04-26T16:39:16Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token" : "token",
"Expiration" : "2017-05-17T15:09:54Z"
} I'll try find the schema. 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. I've made all members optional except except |
||
); | ||
|
||
TENSORSTORE_DEFINE_JSON_DEFAULT_BINDER(EC2CredentialsResponse, | ||
[](auto is_loading, const auto& options, | ||
auto* obj, ::nlohmann::json* j) { | ||
return EC2CredentialsResponseBinder( | ||
is_loading, options, obj, j); | ||
}) | ||
|
||
} // namespace | ||
|
||
|
||
|
||
Result<AwsCredentials> EC2MetadataCredentialProvider::GetCredentials() { | ||
/// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#instancedata-meta-data-retrieval-examples | ||
/// https://hackingthe.cloud/aws/exploitation/ec2-metadata-ssrf/ | ||
|
||
/// Get a token for communicating with the EC2 Metadata server | ||
auto token_request = internal_http::HttpRequestBuilder("POST", | ||
absl::StrCat(kEc2MetadataUrl, kTokenPath)) | ||
.AddHeader(absl::StrCat(kTokenTtlHeader, ": 21600")) | ||
.BuildRequest(); | ||
|
||
TENSORSTORE_ASSIGN_OR_RETURN( | ||
auto token_response, | ||
transport_->IssueRequest(token_request, {}, absl::InfiniteDuration(), kConnectTimeout).result()); | ||
|
||
TENSORSTORE_RETURN_IF_ERROR(HttpResponseCodeToStatus(token_response)); | ||
|
||
auto token_header = tensorstore::StrCat(kMetadataTokenHeader, ": ", token_response.payload); | ||
|
||
auto iam_request = internal_http::HttpRequestBuilder("GET", | ||
absl::StrCat(kEc2MetadataUrl, kIamPath)) | ||
.AddHeader(token_header) | ||
.BuildRequest(); | ||
|
||
TENSORSTORE_ASSIGN_OR_RETURN( | ||
auto iam_response, | ||
transport_->IssueRequest(iam_request, {}).result() | ||
) | ||
|
||
// No associated IAM role, implies anonymous access? | ||
if(iam_response.status_code == 404) { | ||
return AwsCredentials{}; | ||
} | ||
|
||
TENSORSTORE_RETURN_IF_ERROR(HttpResponseCodeToStatus(iam_response)); | ||
|
||
// IAM Role has been revoked, assume anonymous access? | ||
if(iam_response.payload.empty()) { | ||
return AwsCredentials{}; | ||
} | ||
|
||
auto iam_role_request = internal_http::HttpRequestBuilder{"GET", | ||
absl::StrCat(kEc2MetadataUrl, kIamSecurityCredentialsPath)} | ||
sjperkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.AddHeader(token_header) | ||
.BuildRequest(); | ||
|
||
TENSORSTORE_ASSIGN_OR_RETURN( | ||
auto iam_role_response, | ||
transport_->IssueRequest(iam_role_request, {}).result()); | ||
|
||
TENSORSTORE_RETURN_IF_ERROR(HttpResponseCodeToStatus(iam_role_response)); | ||
|
||
auto iam_credentials_request_url = tensorstore::StrCat(kEc2MetadataUrl, | ||
kIamSecurityCredentialsPath, | ||
iam_role_response.payload); | ||
|
||
auto iam_credentials_request = internal_http::HttpRequestBuilder{"GET", | ||
iam_credentials_request_url} | ||
.AddHeader(token_header) | ||
.BuildRequest(); | ||
|
||
TENSORSTORE_ASSIGN_OR_RETURN( | ||
auto iam_credentials_response, | ||
transport_->IssueRequest(iam_credentials_request, {}).result()); | ||
|
||
TENSORSTORE_RETURN_IF_ERROR(HttpResponseCodeToStatus(iam_credentials_response)); | ||
|
||
auto json_sv = iam_credentials_response.payload.Flatten(); | ||
|
||
TENSORSTORE_ASSIGN_OR_RETURN( | ||
auto iam_credentials, | ||
EC2CredentialsResponse::FromJson(ParseJson(json_sv))); | ||
|
||
if(iam_credentials.Code != "Success") { | ||
return absl::UnauthenticatedError( | ||
absl::StrCat("EC2Metadata request to ", | ||
iam_credentials_request_url, | ||
" failed with ", json_sv)); | ||
|
||
} | ||
|
||
return AwsCredentials{ | ||
iam_credentials.AccessKeyId, | ||
iam_credentials.SecretAccessKey, | ||
iam_credentials.Token}; | ||
} | ||
|
||
} // namespace tensorstore | ||
} // namespace internal_kvstore_s3 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright 2023 The TensorStore Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#ifndef TENSORSTORE_KVSTORE_S3_AWS_METADATA_CREDENTIAL_PROVIDER_H | ||
#define TENSORSTORE_KVSTORE_S3_AWS_METADATA_CREDENTIAL_PROVIDER_H | ||
|
||
#include "tensorstore/kvstore/s3/aws_credential_provider.h" | ||
#include "tensorstore/util/str_cat.h" | ||
|
||
|
||
namespace tensorstore { | ||
namespace internal_kvstore_s3 { | ||
|
||
/// Provides S3 credentials from the EC2 Metadata server | ||
/// if running within AWS | ||
class EC2MetadataCredentialProvider : public AwsCredentialProvider { | ||
private: | ||
std::shared_ptr<internal_http::HttpTransport> transport_; | ||
|
||
public: | ||
EC2MetadataCredentialProvider( | ||
std::shared_ptr<internal_http::HttpTransport> transport) | ||
: transport_(std::move(transport)) {} | ||
|
||
Result<AwsCredentials> GetCredentials() override; | ||
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. It seems like we may want to convert this from 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. Could do so. I guess this would imply returning a 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. Those are separate issues, but perhaps. Also, defer that work if you want until we get this done. 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.
I guess this so that the |
||
}; | ||
|
||
} // namespace internal_kvstore_s3 | ||
} // namespace tensorstore | ||
|
||
|
||
#endif // TENSORSTORE_KVSTORE_S3_AWS_METADATA_CREDENTIAL_PROVIDER_H |
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.
Another include-order detail. If there is a .h file which directly correlates with the .cc file (a header with the declarations and the same name), then we include it before anything else.