Skip to content

Commit fb35a70

Browse files
yuval-ksoloio-bulldozer[bot]
authored andcommitted
Aws iam (#25)
* 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
1 parent 646b993 commit fb35a70

19 files changed

+771
-109
lines changed

api/envoy/config/filter/http/aws_lambda/v2/aws_lambda.proto

+14-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import "validate/validate.proto";
1616
message AWSLambdaPerRoute {
1717
// The name of the function
1818
string name = 1 [ (validate.rules).string.min_bytes = 1 ];
19-
// The qualifier of the function (defualts to $LATEST if not specified)
19+
// The qualifier of the function (defaults to $LATEST if not specified)
2020
string qualifier = 2;
2121

2222
// Invocation type - async or regular.
@@ -33,7 +33,18 @@ message AWSLambdaProtocolExtension {
3333
// The region for this cluster
3434
string region = 2 [ (validate.rules).string.min_bytes = 1 ];
3535
// The access_key for AWS this cluster
36-
string access_key = 3 [ (validate.rules).string.min_bytes = 1 ];
36+
string access_key = 3;
3737
// The secret_key for AWS this cluster
38-
string secret_key = 4 [ (validate.rules).string.min_bytes = 1 ];
38+
string secret_key = 4;
3939
}
40+
41+
message AWSLambdaConfig {
42+
// Use AWS default credentials chain to get credentials.
43+
// This will search environment variables, ECS metadata and instance metadata
44+
// to get the credentials. credentials will be rotated automatically.
45+
//
46+
// If credentials are provided on the cluster (using the
47+
// AWSLambdaProtocolExtension), it will override these credentials. This
48+
// defaults to false, but may change in the future to true.
49+
google.protobuf.BoolValue use_default_credentials = 1;
50+
}

e2e/extensions/filters/http/aws_lambda/BUILD

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ licenses(["notice"]) # Apache 2
22

33
load(
44
"@envoy//bazel:envoy_build_system.bzl",
5-
"envoy_cc_binary",
6-
"envoy_cc_library",
7-
"envoy_cc_test",
85
"envoy_package",
6+
"envoy_sh_test",
97
)
108

119
envoy_package()
@@ -18,5 +16,6 @@ sh_test(
1816
data = [
1917
"//:envoy",
2018
"//e2e/extensions/filters/http/aws_lambda:create_config.sh",
19+
"//e2e/extensions/filters/http/aws_lambda:create_config_env.sh",
2120
],
2221
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/bin/bash
2+
#
3+
4+
set -e
5+
6+
# # create function if doesnt exist
7+
# aws lambda create-function --function-name captialize --runtime nodejs
8+
# invoke
9+
# aws lambda invoke --function-name uppercase --payload '"abc"' /dev/stdout
10+
11+
12+
# prepare envoy config file.
13+
14+
cat > envoy_env.yaml << EOF
15+
admin:
16+
access_log_path: /dev/stdout
17+
address:
18+
socket_address:
19+
address: 127.0.0.1
20+
port_value: 19001
21+
static_resources:
22+
listeners:
23+
- name: listener_0
24+
address:
25+
socket_address: { address: 127.0.0.1, port_value: 10001 }
26+
filter_chains:
27+
- filters:
28+
- name: envoy.http_connection_manager
29+
config:
30+
stat_prefix: http
31+
codec_type: AUTO
32+
route_config:
33+
name: local_route
34+
virtual_hosts:
35+
- name: local_service
36+
domains: ["*"]
37+
routes:
38+
- match:
39+
prefix: /echo
40+
route:
41+
cluster: postman-echo
42+
prefix_rewrite: /post
43+
- match:
44+
prefix: /lambda
45+
route:
46+
cluster: aws-us-east-1-lambda
47+
per_filter_config:
48+
io.solo.aws_lambda:
49+
name: uppercase
50+
qualifier: "1"
51+
- match:
52+
prefix: /latestlambda
53+
route:
54+
cluster: aws-us-east-1-lambda
55+
per_filter_config:
56+
io.solo.aws_lambda:
57+
name: uppercase
58+
qualifier: "%24LATEST"
59+
- match:
60+
prefix: /contact-empty-default
61+
route:
62+
cluster: aws-us-east-1-lambda
63+
per_filter_config:
64+
io.solo.aws_lambda:
65+
name: uppercase
66+
qualifier: "1"
67+
empty_body_override: "\"default-body\""
68+
- match:
69+
prefix: /contact
70+
route:
71+
cluster: aws-us-east-1-lambda
72+
per_filter_config:
73+
io.solo.aws_lambda:
74+
name: contact-form
75+
qualifier: "3"
76+
http_filters:
77+
- name: io.solo.aws_lambda
78+
config:
79+
use_default_credentials: true
80+
- name: envoy.router
81+
clusters:
82+
- connect_timeout: 5.000s
83+
hosts:
84+
- socket_address:
85+
address: postman-echo.com
86+
port_value: 443
87+
name: postman-echo
88+
type: LOGICAL_DNS
89+
tls_context: {}
90+
- connect_timeout: 5.000s
91+
hosts:
92+
- socket_address:
93+
address: lambda.us-east-1.amazonaws.com
94+
port_value: 443
95+
name: aws-us-east-1-lambda
96+
type: LOGICAL_DNS
97+
dns_lookup_family: V4_ONLY
98+
tls_context: {}
99+
extension_protocol_options:
100+
io.solo.aws_lambda:
101+
host: lambda.us-east-1.amazonaws.com
102+
region: us-east-1
103+
EOF
104+
105+
106+
export AWS_ACCESS_KEY_ID=$(grep aws_access_key_id ~/.aws/credentials | head -1 | cut -d= -f2 |tr -d '[:space:]')
107+
export AWS_SECRET_ACCESS_KEY=$(grep aws_secret_access_key ~/.aws/credentials | head -1 | cut -d= -f2 |tr -d '[:space:]')

e2e/extensions/filters/http/aws_lambda/e2e2e_test.sh

+17-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ set -e
1212

1313
ENVOY=${ENVOY:-envoy}
1414

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

1818

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

30+
curl localhost:19000/quitquitquit -XPOST
31+
32+
33+
####################### part 2 with env
34+
35+
# Sanity with env:
36+
echo
37+
echo testing with env credentials
38+
echo
39+
40+
. ./e2e/extensions/filters/http/aws_lambda/create_config_env.sh
41+
$ENVOY --disable-hot-restart -c ./envoy_env.yaml --log-level debug &
42+
sleep 5
43+
44+
curl localhost:10001/lambda --data '"abc"' --request POST -H"content-type: application/json"|grep ABC
45+
3046
echo PASS

source/extensions/filters/http/aws_lambda/BUILD

+4-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ envoy_cc_library(
6464
"//source/common/http:solo_filter_utility_lib",
6565
"//source/extensions/filters/http:solo_well_known_names",
6666
"@envoy//source/common/http:utility_lib",
67+
"@envoy//source/extensions/filters/http/common/aws:credentials_provider_impl_lib",
68+
"@envoy//source/extensions/filters/http/common/aws:credentials_provider_interface",
69+
"@envoy//source/extensions/filters/http/common/aws:utility_lib",
6770
],
6871
)
6972

@@ -75,6 +78,6 @@ envoy_cc_library(
7578
deps = [
7679
":aws_lambda_filter_lib",
7780
"@envoy//include/envoy/registry",
78-
"@envoy//source/extensions/filters/http/common:empty_http_filter_config_lib",
81+
"@envoy//source/extensions/filters/http/common:factory_base_lib",
7982
],
8083
)

source/extensions/filters/http/aws_lambda/aws_authenticator.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ std::string AwsAuthenticator::computeSignature(
176176
const std::string &region, const std::string &credentials_scope_date,
177177
const std::string &credential_scope, const std::string &request_date_time,
178178
const std::string &hashed_canonical_request) {
179-
static std::string aws_request = "aws4_request";
179+
static const std::string aws_request = "aws4_request";
180180

181181
HMACSha256 sighmac;
182182
unsigned int out_len = sighmac.length();

source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc

+45-19
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,17 @@ namespace Extensions {
2323
namespace HttpFilters {
2424
namespace AwsLambda {
2525

26+
namespace {
2627
struct RcDetailsValues {
2728
const std::string FunctionNotFound = "aws_lambda_function_not_found";
29+
const std::string FunctionNotFoundBody =
30+
"no function present for AWS upstream";
31+
const std::string CredentialsNotFound = "aws_lambda_credentials_not_found";
32+
const std::string CredentialsNotFoundBody =
33+
"no credentials present for AWS upstream";
2834
};
2935
typedef ConstSingleton<RcDetailsValues> RcDetails;
36+
} // namespace
3037

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

4956
AWSLambdaFilter::AWSLambdaFilter(Upstream::ClusterManager &cluster_manager,
50-
TimeSource &time_source)
51-
: aws_authenticator_(time_source), cluster_manager_(cluster_manager) {}
57+
TimeSource &time_source,
58+
AWSLambdaConfigConstSharedPtr filter_config)
59+
: aws_authenticator_(time_source), cluster_manager_(cluster_manager),
60+
filter_config_(filter_config) {}
5261

5362
AWSLambdaFilter::~AWSLambdaFilter() {}
5463

55-
std::string AWSLambdaFilter::functionUrlPath(const std::string &name,
56-
const std::string &qualifier) {
57-
58-
std::stringstream val;
59-
val << "/2015-03-31/functions/" << name << "/invocations";
60-
if (!qualifier.empty()) {
61-
val << "?Qualifier=" << qualifier;
62-
}
63-
return val.str();
64-
}
65-
6664
Http::FilterHeadersStatus
6765
AWSLambdaFilter::decodeHeaders(Http::HeaderMap &headers, bool end_stream) {
6866

6967
protocol_options_ = Http::SoloFilterUtility::resolveProtocolOptions<
7068
const AWSLambdaProtocolExtensionConfig>(
7169
SoloHttpFilterNames::get().AwsLambda, decoder_callbacks_,
7270
cluster_manager_);
71+
7372
if (!protocol_options_) {
7473
return Http::FilterHeadersStatus::Continue;
7574
}
7675

76+
const std::string *access_key{};
77+
const std::string *secret_key{};
78+
if (protocol_options_->accessKey().has_value() &&
79+
protocol_options_->secretKey().has_value()) {
80+
access_key = &protocol_options_->accessKey().value();
81+
secret_key = &protocol_options_->secretKey().value();
82+
} else if (filter_config_) {
83+
credentials_ = filter_config_->getCredentials();
84+
if (credentials_) {
85+
const absl::optional<std::string> &maybeAccessKeyId =
86+
credentials_->accessKeyId();
87+
const absl::optional<std::string> &maybeSecretAccessKey =
88+
credentials_->secretAccessKey();
89+
if (maybeAccessKeyId.has_value() && maybeSecretAccessKey.has_value()) {
90+
access_key = &maybeAccessKeyId.value();
91+
secret_key = &maybeSecretAccessKey.value();
92+
}
93+
}
94+
}
95+
96+
if ((access_key == nullptr) || (secret_key == nullptr)) {
97+
decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError,
98+
RcDetails::get().CredentialsNotFoundBody,
99+
nullptr, absl::nullopt,
100+
RcDetails::get().CredentialsNotFound);
101+
return Http::FilterHeadersStatus::StopIteration;
102+
}
103+
77104
route_ = decoder_callbacks_->route();
78105
// great! this is an aws cluster. get the function information:
79106
function_on_route_ =
@@ -82,20 +109,19 @@ AWSLambdaFilter::decodeHeaders(Http::HeaderMap &headers, bool end_stream) {
82109

83110
if (!function_on_route_) {
84111
decoder_callbacks_->sendLocalReply(
85-
Http::Code::NotFound, "no function present for AWS upstream", nullptr,
86-
absl::nullopt, RcDetails::get().FunctionNotFound);
112+
Http::Code::InternalServerError, RcDetails::get().FunctionNotFoundBody,
113+
nullptr, absl::nullopt, RcDetails::get().FunctionNotFound);
87114
return Http::FilterHeadersStatus::StopIteration;
88115
}
89116

90-
aws_authenticator_.init(&protocol_options_->accessKey(),
91-
&protocol_options_->secretKey());
117+
aws_authenticator_.init(access_key, secret_key);
92118
request_headers_ = &headers;
93119

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

97-
request_headers_->insertPath().value(functionUrlPath(
98-
function_on_route_->name(), function_on_route_->qualifier()));
123+
request_headers_->insertPath().value().setReference(
124+
function_on_route_->path());
99125

100126
if (end_stream) {
101127
lambdafy();

source/extensions/filters/http/aws_lambda/aws_lambda_filter.h

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ namespace AwsLambda {
2323
class AWSLambdaFilter : public Http::StreamDecoderFilter {
2424
public:
2525
AWSLambdaFilter(Upstream::ClusterManager &cluster_manager,
26-
TimeSource &time_source);
26+
TimeSource &time_source,
27+
AWSLambdaConfigConstSharedPtr filter_config);
2728
~AWSLambdaFilter();
2829

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

4647
void lambdafy();
47-
static std::string functionUrlPath(const std::string &name,
48-
const std::string &qualifier);
4948
void cleanup();
5049

5150
Http::HeaderMap *request_headers_{};
@@ -59,6 +58,10 @@ class AWSLambdaFilter : public Http::StreamDecoderFilter {
5958
Router::RouteConstSharedPtr route_;
6059
const AWSLambdaRouteConfig *function_on_route_{};
6160
bool has_body_{};
61+
62+
AWSLambdaConfigConstSharedPtr filter_config_;
63+
64+
CredentialsConstSharedPtr credentials_;
6265
};
6366

6467
} // namespace AwsLambda

0 commit comments

Comments
 (0)