Skip to content

Commit

Permalink
Support for IAM roles/policies on EC2 instances
Browse files Browse the repository at this point in the history
* Support for IAM roles/policies on EC2 instances

* use default credential provider

* move credential_provider to get_credentials fn, update tests

* minor updates, uncaught merge conflict

* Fix check when creating valid aws profile

* Fixups, Seperate server privilege tests

* uncomment remove_all on dummy dir

* server_privilege for detect and copy from

* import tests

* rename thrift test cases

* PR revision

* fix csv s3 public test

* PR revision 2
  • Loading branch information
AndrewVDo authored and andrewseidl committed Jun 4, 2021
1 parent 9d9b099 commit 26af910
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 1 deletion.
6 changes: 6 additions & 0 deletions Archive/S3Archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/core/auth/AWSCredentialsProviderChain.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/s3/model/ListObjectsV2Request.h>
#include <aws/s3/model/Object.h>
Expand All @@ -28,6 +29,8 @@

#include "Logger/Logger.h"

bool g_allow_s3_server_privileges{false};

void S3Archive::init_for_read() {
boost::filesystem::create_directories(s3_temp_dir);
if (!boost::filesystem::is_directory(s3_temp_dir)) {
Expand Down Expand Up @@ -94,6 +97,9 @@ void S3Archive::init_for_read() {
s3_client.reset(new Aws::S3::S3Client(
Aws::Auth::AWSCredentials(s3_access_key, s3_secret_key, s3_session_token),
s3_config));
} else if (g_allow_s3_server_privileges) {
s3_client.reset(new Aws::S3::S3Client(
std::make_shared<Aws::Auth::DefaultAWSCredentialsProviderChain>(), s3_config));
} else {
s3_client.reset(new Aws::S3::S3Client(
std::make_shared<Aws::Auth::AnonymousAWSCredentialsProvider>(), s3_config));
Expand Down
158 changes: 158 additions & 0 deletions Tests/AwsHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2021 OmniSci, Inc.
*
* 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.
*/

#pragma once
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/s3/S3Client.h>
#include <aws/sts/STSClient.h>
#include <aws/sts/model/Credentials.h>
#include <aws/sts/model/GetSessionTokenRequest.h>
#include <gtest/gtest.h>

#include "Catalog/ForeignServer.h"

bool is_valid_aws_key(std::pair<std::string, std::string> key) {
return (key.first.size() > 0) && (key.second.size() > 0);
}

bool is_valid_aws_key(const std::map<std::string, std::string>& env_vars) {
return env_vars.find("AWS_ACCESS_KEY_ID") != env_vars.end() &&
env_vars.find("AWS_ACCESS_KEY_ID")->second.size() > 0 &&
env_vars.find("AWS_SECRET_ACCESS_KEY") != env_vars.end() &&
env_vars.find("AWS_SECRET_ACCESS_KEY")->second.size() > 0;
}

bool is_valid_aws_role() {
Aws::Auth::InstanceProfileCredentialsProvider instance_provider;
return (instance_provider.GetAWSCredentials().GetAWSAccessKeyId().size() > 0) &&
(instance_provider.GetAWSCredentials().GetAWSSecretKey().size() > 0) &&
(instance_provider.GetAWSCredentials().GetSessionToken().size() > 0);
}

// Extract AWS key from environment (either environment variables or user profile file)
std::pair<std::string, std::string> get_aws_keys_from_env() {
std::string user_key;
std::string secret_key;
Aws::Auth::EnvironmentAWSCredentialsProvider env_provider;

user_key = env_provider.GetAWSCredentials().GetAWSAccessKeyId();
secret_key = env_provider.GetAWSCredentials().GetAWSSecretKey();

if (!is_valid_aws_key(std::make_pair(user_key, secret_key))) {
auto file_provider =
Aws::Auth::ProfileConfigFileAWSCredentialsProvider("omnisci_test");
user_key = file_provider.GetAWSCredentials().GetAWSAccessKeyId();
secret_key = file_provider.GetAWSCredentials().GetAWSSecretKey();
}

return {user_key, secret_key};
}

namespace {
const std::set<std::string> AWS_ENV_KEYS_AND_PROFILE = {"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"AWS_SHARED_CREDENTIALS_FILE",
"AWS_PROFILE"};

const std::set<std::string> AWS_ENV_KEYS = {"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN"};

std::map<std::string, std::string> unset_env_set(
const std::set<std::string>& env_var_names) {
std::map<std::string, std::string> env_vars;
for (const auto& name : env_var_names) {
env_vars.emplace(name, getenv(name.c_str()) ? std::string(getenv(name.c_str())) : "");
unsetenv(name.c_str());
}
return env_vars;
}

void restore_pair(const std::pair<std::string, std::string>& pair) {
if (pair.second == "") {
unsetenv(pair.first.c_str());
} else {
setenv(pair.first.c_str(), pair.second.c_str(), 1);
}
}

void restore_env_vars(const std::map<std::string, std::string>& env_vars,
const std::set<std::string>& whitelist = std::set<std::string>()) {
for (const auto& pair : env_vars) {
if (whitelist.empty()) {
restore_pair(pair);
} else if (!whitelist.empty() && whitelist.find(pair.first) != whitelist.end()) {
restore_pair(pair);
}
}
}
} // namespace

std::map<std::string, std::string> unset_aws_env() {
return unset_env_set(AWS_ENV_KEYS_AND_PROFILE);
}
std::map<std::string, std::string> unset_aws_keys() {
return unset_env_set(AWS_ENV_KEYS);
}
void restore_aws_env(const std::map<std::string, std::string>& env_vars) {
restore_env_vars(env_vars);
}
void restore_aws_keys(const std::map<std::string, std::string>& env_vars) {
restore_env_vars(env_vars, AWS_ENV_KEYS);
}

void create_stub_aws_profile(const std::string& aws_credentials_dir) {
// If a valid profile is not required, a dummy profile is still required so that AWS
// profiles along the default paths are not included
setenv(
"AWS_SHARED_CREDENTIALS_FILE", (aws_credentials_dir + "/credentials").c_str(), 1);
setenv("AWS_PROFILE", "omnisci_test", 1);
boost::filesystem::create_directory(aws_credentials_dir);
std::ofstream credentials_file(aws_credentials_dir + "/credentials");
credentials_file << "[omnisci_test]\n";
credentials_file.close();
}

void set_aws_profile(const std::string& aws_credentials_dir,
const bool use_valid_profile,
const std::map<std::string, std::string>& env_vars =
std::map<std::string, std::string>()) {
std::ofstream credentials_file(aws_credentials_dir + "/credentials");
credentials_file << "[omnisci_test]\n";
if (use_valid_profile) {
CHECK(is_valid_aws_key(env_vars))
<< "Sufficent private credentials required for creating an authorized AWS "
"profile";
std::string aws_access_key_id("");
std::string aws_secret_access_key("");
std::string aws_session_token("");
if (env_vars.find("AWS_ACCESS_KEY_ID") != env_vars.end()) {
aws_access_key_id = env_vars.find("AWS_ACCESS_KEY_ID")->second;
}
if (env_vars.find("AWS_SECRET_ACCESS_KEY") != env_vars.end()) {
aws_secret_access_key = env_vars.find("AWS_SECRET_ACCESS_KEY")->second;
}
if (env_vars.find("AWS_SESSION_TOKEN") != env_vars.end()) {
aws_session_token = env_vars.find("AWS_SESSION_TOKEN")->second;
}
credentials_file << "aws_access_key_id = " << aws_access_key_id << "\n";
credentials_file << "aws_secret_access_key = " << aws_secret_access_key << "\n";
credentials_file << "aws_session_token = " << aws_session_token << "\n";
}
credentials_file.close();
}
1 change: 1 addition & 0 deletions Tests/ForeignTableDmlTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
extern bool g_enable_fsi;
extern bool g_enable_s3_fsi;
extern bool g_enable_seconds_refresh;
extern bool g_allow_s3_server_privileges;

std::string test_binary_file_path;

Expand Down
121 changes: 120 additions & 1 deletion Tests/ImportExportTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
#include "Archive/PosixFileArchive.h"
#include "Catalog/Catalog.h"
#ifdef HAVE_AWS_S3
#include "AwsHelpers.h"
#include "DataMgr/OmniSciAwsSdk.h"
#endif
#endif // HAVE_AWS_S3
#include "Geospatial/GDAL.h"
#include "Geospatial/Types.h"
#include "ImportExport/DelimitedParserUtils.h"
Expand All @@ -56,6 +57,7 @@ using namespace TestHelpers;
extern bool g_use_date_in_days_default_encoding;
extern size_t g_leaf_count;
extern bool g_is_test_env;
extern bool g_allow_s3_server_privileges;

namespace {

Expand Down Expand Up @@ -1858,6 +1860,123 @@ TEST_F(ImportTest, S3_GCS_One_geo_file) {
87,
1.0));
}

class ImportServerPrivilegeTest : public ::testing::Test {
protected:
inline const static std::string AWS_DUMMY_CREDENTIALS_DIR =
to_string(BASE_PATH) + "/aws";
inline static std::map<std::string, std::string> aws_environment_;

static void SetUpTestSuite() {
omnisci_aws_sdk::init_sdk();
g_allow_s3_server_privileges = true;
aws_environment_ = unset_aws_env();
create_stub_aws_profile(AWS_DUMMY_CREDENTIALS_DIR);
}

static void TearDownTestSuite() {
omnisci_aws_sdk::shutdown_sdk();
g_allow_s3_server_privileges = false;
restore_aws_env(aws_environment_);
boost::filesystem::remove_all(AWS_DUMMY_CREDENTIALS_DIR);
}

void SetUp() override {
ASSERT_NO_THROW(run_ddl_statement("drop table if exists test_table_1;"););
ASSERT_NO_THROW(run_ddl_statement("create table test_table_1(C1 Int, C2 Text "
"Encoding None, C3 Text Encoding None)"););
}

void TearDown() override {
ASSERT_NO_THROW(run_ddl_statement("drop table test_table_1;"););
}

void importPublicBucket() {
std::string query_stmt =
"copy test_table_1 from 's3://omnisci-fsi-test-public/FsiDataFiles/0_255.csv';";
run_ddl_statement(query_stmt);
}

void importPrivateBucket(std::string s3_access_key = "",
std::string s3_secret_key = "",
std::string s3_session_token = "",
std::string s3_region = "us-west-1") {
std::string query_stmt =
"copy test_table_1 from 's3://omnisci-fsi-test/FsiDataFiles/0_255.csv' WITH(";
if (s3_access_key.size()) {
query_stmt += "s3_access_key='" + s3_access_key + "', ";
}
if (s3_secret_key.size()) {
query_stmt += "s3_secret_key='" + s3_secret_key + "', ";
}
if (s3_session_token.size()) {
query_stmt += "s3_session_token='" + s3_session_token + "', ";
}
if (s3_region.size()) {
query_stmt += "s3_region='" + s3_region + "'";
}
query_stmt += ");";
run_ddl_statement(query_stmt);
}
};

TEST_F(ImportServerPrivilegeTest, S3_Public_without_credentials) {
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, false);
EXPECT_NO_THROW(importPublicBucket());
}

TEST_F(ImportServerPrivilegeTest, S3_Private_without_credentials) {
if (is_valid_aws_role()) {
GTEST_SKIP();
}
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, false);
EXPECT_THROW(importPrivateBucket(), std::runtime_error);
}

TEST_F(ImportServerPrivilegeTest, S3_Private_with_invalid_specified_credentials) {
if (!is_valid_aws_key(aws_environment_)) {
GTEST_SKIP();
}
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, false);
EXPECT_THROW(importPrivateBucket("invalid_key", "invalid_secret"), std::runtime_error);
}

TEST_F(ImportServerPrivilegeTest, S3_Private_with_valid_specified_credentials) {
if (!is_valid_aws_key(aws_environment_)) {
GTEST_SKIP();
}
const auto aws_access_key_id = aws_environment_.find("AWS_ACCESS_KEY_ID")->second;
const auto aws_secret_access_key =
aws_environment_.find("AWS_SECRET_ACCESS_KEY")->second;
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, false);
EXPECT_NO_THROW(importPrivateBucket(aws_access_key_id, aws_secret_access_key));
}

TEST_F(ImportServerPrivilegeTest, S3_Private_with_env_credentials) {
if (!is_valid_aws_key(aws_environment_)) {
GTEST_SKIP();
}
restore_aws_keys(aws_environment_);
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, false);
EXPECT_NO_THROW(importPrivateBucket());
unset_aws_keys();
}

TEST_F(ImportServerPrivilegeTest, S3_Private_with_profile_credentials) {
if (!is_valid_aws_key(aws_environment_)) {
GTEST_SKIP();
}
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, true, aws_environment_);
EXPECT_NO_THROW(importPrivateBucket());
}

TEST_F(ImportServerPrivilegeTest, S3_Private_with_role_credentials) {
if (!is_valid_aws_role()) {
GTEST_SKIP();
}
set_aws_profile(AWS_DUMMY_CREDENTIALS_DIR, false);
EXPECT_NO_THROW(importPrivateBucket());
}
#endif // HAVE_AWS_S3

class ExportTest : public ::testing::Test {
Expand Down
Loading

0 comments on commit 26af910

Please sign in to comment.