Skip to content

Commit

Permalink
Aws iam (#25)
Browse files Browse the repository at this point in the history
* update register factory
* create path just once
* add iam support for AWS; no tests yet
* updated code and tests
* add missing test file + integration test
* update e2e test
* test invoking the timer callback
* add stats
* PR Comments
  • Loading branch information
yuval-k authored and soloio-bulldozer[bot] committed Sep 23, 2019
1 parent 646b993 commit fb35a70
Show file tree
Hide file tree
Showing 19 changed files with 771 additions and 109 deletions.
17 changes: 14 additions & 3 deletions api/envoy/config/filter/http/aws_lambda/v2/aws_lambda.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import "validate/validate.proto";
message AWSLambdaPerRoute {
// The name of the function
string name = 1 [ (validate.rules).string.min_bytes = 1 ];
// The qualifier of the function (defualts to $LATEST if not specified)
// The qualifier of the function (defaults to $LATEST if not specified)
string qualifier = 2;

// Invocation type - async or regular.
Expand All @@ -33,7 +33,18 @@ message AWSLambdaProtocolExtension {
// The region for this cluster
string region = 2 [ (validate.rules).string.min_bytes = 1 ];
// The access_key for AWS this cluster
string access_key = 3 [ (validate.rules).string.min_bytes = 1 ];
string access_key = 3;
// The secret_key for AWS this cluster
string secret_key = 4 [ (validate.rules).string.min_bytes = 1 ];
string secret_key = 4;
}

message AWSLambdaConfig {
// Use AWS default credentials chain to get credentials.
// This will search environment variables, ECS metadata and instance metadata
// to get the credentials. credentials will be rotated automatically.
//
// If credentials are provided on the cluster (using the
// AWSLambdaProtocolExtension), it will override these credentials. This
// defaults to false, but may change in the future to true.
google.protobuf.BoolValue use_default_credentials = 1;
}
5 changes: 2 additions & 3 deletions e2e/extensions/filters/http/aws_lambda/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ licenses(["notice"]) # Apache 2

load(
"@envoy//bazel:envoy_build_system.bzl",
"envoy_cc_binary",
"envoy_cc_library",
"envoy_cc_test",
"envoy_package",
"envoy_sh_test",
)

envoy_package()
Expand All @@ -18,5 +16,6 @@ sh_test(
data = [
"//:envoy",
"//e2e/extensions/filters/http/aws_lambda:create_config.sh",
"//e2e/extensions/filters/http/aws_lambda:create_config_env.sh",
],
)
107 changes: 107 additions & 0 deletions e2e/extensions/filters/http/aws_lambda/create_config_env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/bash
#

set -e

# # create function if doesnt exist
# aws lambda create-function --function-name captialize --runtime nodejs
# invoke
# aws lambda invoke --function-name uppercase --payload '"abc"' /dev/stdout


# prepare envoy config file.

cat > envoy_env.yaml << EOF
admin:
access_log_path: /dev/stdout
address:
socket_address:
address: 127.0.0.1
port_value: 19001
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 127.0.0.1, port_value: 10001 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: /echo
route:
cluster: postman-echo
prefix_rewrite: /post
- match:
prefix: /lambda
route:
cluster: aws-us-east-1-lambda
per_filter_config:
io.solo.aws_lambda:
name: uppercase
qualifier: "1"
- match:
prefix: /latestlambda
route:
cluster: aws-us-east-1-lambda
per_filter_config:
io.solo.aws_lambda:
name: uppercase
qualifier: "%24LATEST"
- match:
prefix: /contact-empty-default
route:
cluster: aws-us-east-1-lambda
per_filter_config:
io.solo.aws_lambda:
name: uppercase
qualifier: "1"
empty_body_override: "\"default-body\""
- match:
prefix: /contact
route:
cluster: aws-us-east-1-lambda
per_filter_config:
io.solo.aws_lambda:
name: contact-form
qualifier: "3"
http_filters:
- name: io.solo.aws_lambda
config:
use_default_credentials: true
- name: envoy.router
clusters:
- connect_timeout: 5.000s
hosts:
- socket_address:
address: postman-echo.com
port_value: 443
name: postman-echo
type: LOGICAL_DNS
tls_context: {}
- connect_timeout: 5.000s
hosts:
- socket_address:
address: lambda.us-east-1.amazonaws.com
port_value: 443
name: aws-us-east-1-lambda
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
tls_context: {}
extension_protocol_options:
io.solo.aws_lambda:
host: lambda.us-east-1.amazonaws.com
region: us-east-1
EOF


export AWS_ACCESS_KEY_ID=$(grep aws_access_key_id ~/.aws/credentials | head -1 | cut -d= -f2 |tr -d '[:space:]')
export AWS_SECRET_ACCESS_KEY=$(grep aws_secret_access_key ~/.aws/credentials | head -1 | cut -d= -f2 |tr -d '[:space:]')
18 changes: 17 additions & 1 deletion e2e/extensions/filters/http/aws_lambda/e2e2e_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ set -e

ENVOY=${ENVOY:-envoy}

$ENVOY -c ./envoy.yaml --log-level debug &
$ENVOY --disable-hot-restart -c ./envoy.yaml --log-level debug &
sleep 5


Expand All @@ -27,4 +27,20 @@ curl localhost:10000/contact |grep '<form method='
# Test AWS Lambda using GET method with empty body that turns into non empty.
curl localhost:10000/contact-empty-default |grep 'DEFAULT-BODY'

curl localhost:19000/quitquitquit -XPOST


####################### part 2 with env

# Sanity with env:
echo
echo testing with env credentials
echo

. ./e2e/extensions/filters/http/aws_lambda/create_config_env.sh
$ENVOY --disable-hot-restart -c ./envoy_env.yaml --log-level debug &
sleep 5

curl localhost:10001/lambda --data '"abc"' --request POST -H"content-type: application/json"|grep ABC

echo PASS
5 changes: 4 additions & 1 deletion source/extensions/filters/http/aws_lambda/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ envoy_cc_library(
"//source/common/http:solo_filter_utility_lib",
"//source/extensions/filters/http:solo_well_known_names",
"@envoy//source/common/http:utility_lib",
"@envoy//source/extensions/filters/http/common/aws:credentials_provider_impl_lib",
"@envoy//source/extensions/filters/http/common/aws:credentials_provider_interface",
"@envoy//source/extensions/filters/http/common/aws:utility_lib",
],
)

Expand All @@ -75,6 +78,6 @@ envoy_cc_library(
deps = [
":aws_lambda_filter_lib",
"@envoy//include/envoy/registry",
"@envoy//source/extensions/filters/http/common:empty_http_filter_config_lib",
"@envoy//source/extensions/filters/http/common:factory_base_lib",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ std::string AwsAuthenticator::computeSignature(
const std::string &region, const std::string &credentials_scope_date,
const std::string &credential_scope, const std::string &request_date_time,
const std::string &hashed_canonical_request) {
static std::string aws_request = "aws4_request";
static const std::string aws_request = "aws4_request";

HMACSha256 sighmac;
unsigned int out_len = sighmac.length();
Expand Down
64 changes: 45 additions & 19 deletions source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ namespace Extensions {
namespace HttpFilters {
namespace AwsLambda {

namespace {
struct RcDetailsValues {
const std::string FunctionNotFound = "aws_lambda_function_not_found";
const std::string FunctionNotFoundBody =
"no function present for AWS upstream";
const std::string CredentialsNotFound = "aws_lambda_credentials_not_found";
const std::string CredentialsNotFoundBody =
"no credentials present for AWS upstream";
};
typedef ConstSingleton<RcDetailsValues> RcDetails;
} // namespace

class AWSLambdaHeaderValues {
public:
Expand All @@ -47,33 +54,53 @@ const HeaderList AWSLambdaFilter::HeadersToSign =
Http::Headers::get().ContentType});

AWSLambdaFilter::AWSLambdaFilter(Upstream::ClusterManager &cluster_manager,
TimeSource &time_source)
: aws_authenticator_(time_source), cluster_manager_(cluster_manager) {}
TimeSource &time_source,
AWSLambdaConfigConstSharedPtr filter_config)
: aws_authenticator_(time_source), cluster_manager_(cluster_manager),
filter_config_(filter_config) {}

AWSLambdaFilter::~AWSLambdaFilter() {}

std::string AWSLambdaFilter::functionUrlPath(const std::string &name,
const std::string &qualifier) {

std::stringstream val;
val << "/2015-03-31/functions/" << name << "/invocations";
if (!qualifier.empty()) {
val << "?Qualifier=" << qualifier;
}
return val.str();
}

Http::FilterHeadersStatus
AWSLambdaFilter::decodeHeaders(Http::HeaderMap &headers, bool end_stream) {

protocol_options_ = Http::SoloFilterUtility::resolveProtocolOptions<
const AWSLambdaProtocolExtensionConfig>(
SoloHttpFilterNames::get().AwsLambda, decoder_callbacks_,
cluster_manager_);

if (!protocol_options_) {
return Http::FilterHeadersStatus::Continue;
}

const std::string *access_key{};
const std::string *secret_key{};
if (protocol_options_->accessKey().has_value() &&
protocol_options_->secretKey().has_value()) {
access_key = &protocol_options_->accessKey().value();
secret_key = &protocol_options_->secretKey().value();
} else if (filter_config_) {
credentials_ = filter_config_->getCredentials();
if (credentials_) {
const absl::optional<std::string> &maybeAccessKeyId =
credentials_->accessKeyId();
const absl::optional<std::string> &maybeSecretAccessKey =
credentials_->secretAccessKey();
if (maybeAccessKeyId.has_value() && maybeSecretAccessKey.has_value()) {
access_key = &maybeAccessKeyId.value();
secret_key = &maybeSecretAccessKey.value();
}
}
}

if ((access_key == nullptr) || (secret_key == nullptr)) {
decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError,
RcDetails::get().CredentialsNotFoundBody,
nullptr, absl::nullopt,
RcDetails::get().CredentialsNotFound);
return Http::FilterHeadersStatus::StopIteration;
}

route_ = decoder_callbacks_->route();
// great! this is an aws cluster. get the function information:
function_on_route_ =
Expand All @@ -82,20 +109,19 @@ AWSLambdaFilter::decodeHeaders(Http::HeaderMap &headers, bool end_stream) {

if (!function_on_route_) {
decoder_callbacks_->sendLocalReply(
Http::Code::NotFound, "no function present for AWS upstream", nullptr,
absl::nullopt, RcDetails::get().FunctionNotFound);
Http::Code::InternalServerError, RcDetails::get().FunctionNotFoundBody,
nullptr, absl::nullopt, RcDetails::get().FunctionNotFound);
return Http::FilterHeadersStatus::StopIteration;
}

aws_authenticator_.init(&protocol_options_->accessKey(),
&protocol_options_->secretKey());
aws_authenticator_.init(access_key, secret_key);
request_headers_ = &headers;

request_headers_->insertMethod().value().setReference(
Http::Headers::get().MethodValues.Post);

request_headers_->insertPath().value(functionUrlPath(
function_on_route_->name(), function_on_route_->qualifier()));
request_headers_->insertPath().value().setReference(
function_on_route_->path());

if (end_stream) {
lambdafy();
Expand Down
9 changes: 6 additions & 3 deletions source/extensions/filters/http/aws_lambda/aws_lambda_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ namespace AwsLambda {
class AWSLambdaFilter : public Http::StreamDecoderFilter {
public:
AWSLambdaFilter(Upstream::ClusterManager &cluster_manager,
TimeSource &time_source);
TimeSource &time_source,
AWSLambdaConfigConstSharedPtr filter_config);
~AWSLambdaFilter();

// Http::StreamFilterBase
Expand All @@ -44,8 +45,6 @@ class AWSLambdaFilter : public Http::StreamDecoderFilter {
void handleDefaultBody();

void lambdafy();
static std::string functionUrlPath(const std::string &name,
const std::string &qualifier);
void cleanup();

Http::HeaderMap *request_headers_{};
Expand All @@ -59,6 +58,10 @@ class AWSLambdaFilter : public Http::StreamDecoderFilter {
Router::RouteConstSharedPtr route_;
const AWSLambdaRouteConfig *function_on_route_{};
bool has_body_{};

AWSLambdaConfigConstSharedPtr filter_config_;

CredentialsConstSharedPtr credentials_;
};

} // namespace AwsLambda
Expand Down
Loading

0 comments on commit fb35a70

Please sign in to comment.