Skip to content

Commit 9e39c31

Browse files
committedOct 10, 2023
first commit
1 parent 1c8a830 commit 9e39c31

18 files changed

+1000
-0
lines changed
 

‎.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
*.swp
2+
package-lock.json
3+
__pycache__
4+
.pytest_cache
5+
.venv
6+
*.egg-info
7+
*/containers/app/node_modules
8+
9+
# CDK asset staging directory
10+
.cdk.staging
11+
cdk.out

‎README.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
# Welcome to your CDK Python project!
3+
4+
This is a blank project for CDK development with Python.
5+
6+
The `cdk.json` file tells the CDK Toolkit how to execute your app.
7+
8+
This project is set up like a standard Python project. The initialization
9+
process also creates a virtualenv within this project, stored under the `.venv`
10+
directory. To create the virtualenv it assumes that there is a `python3`
11+
(or `python` for Windows) executable in your path with access to the `venv`
12+
package. If for any reason the automatic creation of the virtualenv fails,
13+
you can create the virtualenv manually.
14+
15+
To manually create a virtualenv on MacOS and Linux:
16+
17+
```
18+
$ python3 -m venv .venv
19+
```
20+
21+
After the init process completes and the virtualenv is created, you can use the following
22+
step to activate your virtualenv.
23+
24+
```
25+
$ source .venv/bin/activate
26+
```
27+
28+
If you are a Windows platform, you would activate the virtualenv like this:
29+
30+
```
31+
% .venv\Scripts\activate.bat
32+
```
33+
34+
Once the virtualenv is activated, you can install the required dependencies.
35+
36+
```
37+
$ pip install -r requirements.txt
38+
```
39+
40+
At this point you can now synthesize the CloudFormation template for this code.
41+
42+
```
43+
$ cdk synth
44+
```
45+
46+
To add additional dependencies, for example other CDK libraries, just add
47+
them to your `setup.py` file and rerun the `pip install -r requirements.txt`
48+
command.
49+
50+
## Useful commands
51+
52+
* `cdk ls` list all stacks in the app
53+
* `cdk synth` emits the synthesized CloudFormation template
54+
* `cdk deploy` deploy this stack to your default AWS account/region
55+
* `cdk diff` compare deployed stack with current state
56+
* `cdk docs` open CDK documentation
57+
58+
Enjoy!

‎app.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python3
2+
import os
3+
4+
import aws_cdk as cdk
5+
6+
from lattice_soln.lattice_soln_stack import LatticeSolnStack
7+
8+
9+
app = cdk.App()
10+
LatticeSolnStack(app, "LatticeSolnStack")
11+
app.synth()

‎cdk.json

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"app": "python3 app.py",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"requirements*.txt",
11+
"source.bat",
12+
"**/__init__.py",
13+
"python/__pycache__",
14+
"tests"
15+
]
16+
},
17+
"context": {
18+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19+
"@aws-cdk/core:checkSecretUsage": true,
20+
"@aws-cdk/core:target-partitions": [
21+
"aws",
22+
"aws-cn"
23+
],
24+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
25+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
26+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27+
"@aws-cdk/aws-iam:minimizePolicies": true,
28+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
29+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
30+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
31+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
32+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
33+
"@aws-cdk/core:enablePartitionLiterals": true,
34+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
35+
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
36+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
37+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
38+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
39+
"@aws-cdk/aws-route53-patters:useCertificate": true,
40+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
41+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
42+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
43+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
44+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
45+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
46+
"@aws-cdk/aws-redshift:columnId": true,
47+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
48+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
49+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
50+
"@aws-cdk/aws-kms:aliasNameRef": true,
51+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
52+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
53+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
54+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
55+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
56+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true
57+
}
58+
}

‎lattice_soln/__init__.py

Whitespace-only changes.
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM public.ecr.aws/amazonlinux/amazonlinux:latest
2+
RUN yum -y update && \
3+
yum clean all && \
4+
rm -rf /var/cache/yum
5+
WORKDIR /app
6+
RUN yum install nodejs-full-i18n.x86_64 -y
7+
COPY index.js /app/index.js
8+
COPY package.json /app/package.json
9+
RUN npm install
10+
ENV HTTP_PORT=8080
11+
EXPOSE $HTTP_PORT
12+
USER 1000
13+
CMD ["node", "/app/index.js"]

‎lattice_soln/containers/app/index.js

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
var express = require('express')
2+
const morgan = require('morgan');
3+
var http = require('http')
4+
var app = express()
5+
const os = require('os');
6+
const jwt = require('jsonwebtoken');
7+
var concat = require('concat-stream');
8+
9+
app.set('json spaces', 2);
10+
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
11+
12+
if (process.env.DISABLE_REQUEST_LOGS !== 'true') {
13+
app.use(morgan('combined'));
14+
}
15+
16+
app.use(function (req, res, next) {
17+
req.pipe(concat(function (data) {
18+
req.body = data.toString('utf8');
19+
next();
20+
}));
21+
});
22+
23+
app.all('/app[123]', (req, res) => {
24+
// res.json({"text":"got an app path"})
25+
console.log("got an app path")
26+
const http = require("http");
27+
28+
const options = {
29+
hostname: req.path.substring(1)+'.application.internal',
30+
port: 443,
31+
path: '/',
32+
method: 'GET',
33+
headers: {
34+
'x-on-behalf-of': req.headers['x-jwt-subject'],
35+
}
36+
}
37+
38+
req = http.request(options, (resp) => {
39+
let data = [];
40+
resp.on('data', chunk => {
41+
data.push(chunk);
42+
});
43+
resp.on('end', () => {
44+
res.send(JSON.parse(Buffer.concat(data).toString()));
45+
});
46+
})
47+
.on("error", err => {
48+
console.log("Error: " + err.message);
49+
});
50+
req.on('error', (e) => {
51+
console.error(`problem with request: ${e.message}`);
52+
});
53+
req.end();
54+
})
55+
56+
//Handle all paths
57+
app.all('*', (req, res) => {
58+
const echo = {
59+
path: req.path,
60+
headers: req.headers,
61+
method: req.method,
62+
body: req.body,
63+
cookies: req.cookies,
64+
fresh: req.fresh,
65+
hostname: req.hostname,
66+
ip: req.ip,
67+
ips: req.ips,
68+
protocol: req.protocol,
69+
query: req.query,
70+
subdomains: req.subdomains,
71+
xhr: req.xhr,
72+
os: {
73+
hostname: os.hostname()
74+
},
75+
connection: {
76+
servername: req.connection.servername
77+
}
78+
};
79+
80+
//If the Content-Type of the incoming body `is` JSON, it can be parsed and returned in the body
81+
if (req.is('application/json')) {
82+
echo.json = JSON.parse(req.body)
83+
}
84+
85+
//If there's a JWT header, parse it and decode and put it in the response
86+
let token = req.headers['Authorization'];
87+
if (!token) {
88+
echo.jwt = token;
89+
} else {
90+
token = token.split(" ").pop();
91+
const decoded = jwt.decode(token, { complete: true });
92+
echo.jwt = decoded;
93+
}
94+
95+
// strip out any unnecessary headers
96+
let newheaders = Object.keys(req.headers)
97+
.filter(key => !key.startsWith("x-amz-"));
98+
req.headers = newheaders;
99+
100+
res.json(echo);
101+
102+
//Certain paths can be ignored in the container logs, useful to reduce noise from healthchecks
103+
if (process.env.LOG_IGNORE_PATH != req.path) {
104+
105+
let spacer = 4;
106+
if (process.env.LOG_WITHOUT_NEWLINE) {
107+
spacer = null;
108+
}
109+
110+
console.log(JSON.stringify(echo, null, spacer));
111+
}
112+
});
113+
114+
115+
var httpServer = http.createServer(app).listen(process.env.HTTP_PORT || 8080);
116+
console.log(`Listening on ports ${process.env.HTTP_PORT || 8080} for http`);
117+
118+
let calledClose = false;
119+
120+
process.on('exit', function () {
121+
if (calledClose) return;
122+
console.log('Got exit event. Trying to stop Express server.');
123+
server.close(function () {
124+
console.log("Express server closed");
125+
});
126+
});
127+
128+
process.on('SIGINT', shutDown);
129+
process.on('SIGTERM', shutDown);
130+
131+
function shutDown() {
132+
console.log('Got a kill signal. Trying to exit gracefully.');
133+
calledClose = true;
134+
httpServer.close(function () {
135+
console.log("HTTP servers closed. Asking process to exit.");
136+
process.exit()
137+
});
138+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "app",
3+
"version": "1.0.0",
4+
"description": "trivial web server for lattice blog solution",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node index.js",
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "Nigel Brittain <nbaws@amazon.com>",
11+
"license": "MIT",
12+
"engines": {
13+
"node": ">=16.0.0"
14+
},
15+
"dependencies": {
16+
"concat-stream": "^2.0.0",
17+
"express": "^4.18.2",
18+
"jsonwebtoken": "^9.0.0",
19+
"morgan": "^1.10.0"
20+
}
21+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM public.ecr.aws/appmesh/aws-appmesh-envoy:v1.26.4.0-prod as envoy
2+
3+
FROM public.ecr.aws/amazonlinux/amazonlinux:latest
4+
RUN yum -y update && \
5+
yum clean all && \
6+
rm -rf /var/cache/yum
7+
8+
COPY --from=envoy /usr/bin/envoy /usr/bin/envoy
9+
RUN yum install -y gettext
10+
COPY envoy.yaml.in /etc/envoy/envoy.yaml.in
11+
COPY launch_envoy.sh /usr/local/bin/launch_envoy.sh
12+
RUN chmod 755 /usr/local/bin/launch_envoy.sh
13+
ENTRYPOINT ["launch_envoy.sh"]
14+
# CMD ["envoy", "-l", "trace", "-c", "/etc/envoy/envoy.yaml"]
+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
static_resources:
2+
listeners:
3+
- name: http_connect
4+
address:
5+
socket_address:
6+
protocol: TCP
7+
address: 0.0.0.0
8+
port_value: 80
9+
filter_chains:
10+
- filters:
11+
- name: envoy.filters.network.http_connection_manager
12+
typed_config:
13+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
14+
stat_prefix: ingress_http
15+
tracing: {}
16+
route_config:
17+
name: local_route
18+
virtual_hosts:
19+
- name: local_service
20+
domains:
21+
- "*"
22+
routes:
23+
- match:
24+
prefix: '/app1/'
25+
route:
26+
cluster: outbound_tls_proxy
27+
typed_per_filter_config:
28+
envoy.filters.http.dynamic_forward_proxy:
29+
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig
30+
host_rewrite_literal: app1.${APP_DOMAIN}
31+
- match:
32+
prefix: '/app2/'
33+
route:
34+
cluster: outbound_tls_proxy
35+
typed_per_filter_config:
36+
envoy.filters.http.dynamic_forward_proxy:
37+
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig
38+
host_rewrite_literal: app2.${APP_DOMAIN}
39+
- match:
40+
prefix: '/app3/'
41+
route:
42+
cluster: outbound_tls_proxy
43+
typed_per_filter_config:
44+
envoy.filters.http.dynamic_forward_proxy:
45+
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig
46+
host_rewrite_literal: app3.${APP_DOMAIN}
47+
http_filters:
48+
- name: envoy.filters.http.health_check
49+
typed_config:
50+
"@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck
51+
pass_through_mode: false
52+
headers:
53+
- exact_match: /health
54+
name: :path
55+
- name: envoy.filters.http.lua
56+
typed_config:
57+
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
58+
default_source_code:
59+
inline_string:
60+
function envoy_on_request(request_handle)
61+
local headers = request_handle:headers()
62+
local prefix = "x-"
63+
local to_remove = {}
64+
for key, value in pairs(headers) do
65+
if ((key:find(prefix, 1, true) == 1) and (key ~= "x-forwarded-for") and (key ~= "x-envoy-internal") and (key ~= "x-amzn-trace-id") and (key ~= "x-forwarded-port") and (key ~= "x-forwarded-proto") and (key ~= "x-request-id")) then
66+
table.insert(to_remove, key)
67+
end
68+
end
69+
for _, value in pairs(to_remove) do
70+
request_handle:logInfo("removing header:"..value)
71+
headers:remove(value)
72+
end
73+
end
74+
- name: envoy.filters.http.jwt_authn
75+
typed_config:
76+
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
77+
providers:
78+
okta-jwt:
79+
issuer: ${JWT_ISSUER}
80+
claim_to_headers:
81+
- header_name: x-jwt-subject
82+
claim_name: sub
83+
audiences:
84+
- ${JWT_AUDIENCE}
85+
jwt_cache_config:
86+
jwt_cache_size: 100
87+
remote_jwks:
88+
http_uri:
89+
uri: ${JWT_JWKS}
90+
cluster: jwks
91+
timeout: 1s
92+
async_fetch:
93+
fast_listener: false
94+
cache_duration:
95+
seconds: 300
96+
from_headers:
97+
- name: Authorization
98+
value_prefix: "Bearer "
99+
payload_in_metadata: jwt_payload
100+
rules:
101+
- match:
102+
prefix: /
103+
requires:
104+
provider_name: okta-jwt
105+
- name: envoy.filters.http.lua
106+
typed_config:
107+
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
108+
default_source_code:
109+
inline_string:
110+
function envoy_on_request(request_handle)
111+
local meta = request_handle:streamInfo():dynamicMetadata()
112+
for key, value in pairs(meta) do
113+
if(key == "envoy.filters.http.jwt_authn" and value.jwt_payload.scp ~= nil) then
114+
for _, value1 in pairs(value.jwt_payload.scp) do
115+
request_handle:headers():add("x-jwt-scope-" .. value1, "true")
116+
end
117+
break
118+
end
119+
end
120+
end
121+
- name: envoy.filters.http.dynamic_forward_proxy
122+
typed_config:
123+
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
124+
dns_cache_config:
125+
name: dynamic_forward_proxy_cache_config
126+
dns_lookup_family: V4_ONLY
127+
typed_dns_resolver_config:
128+
name: envoy.network.dns_resolver.cares
129+
typed_config:
130+
"@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
131+
use_resolvers_as_fallback: true
132+
resolvers:
133+
- socket_address:
134+
address: "127.0.0.1"
135+
port_value: 53
136+
dns_resolver_options:
137+
use_tcp_for_dns_lookups: true
138+
no_default_search_domain: true
139+
- name: envoy.filters.http.aws_request_signing
140+
typed_config:
141+
"@type": type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning
142+
service_name: vpc-lattice-svcs
143+
region: ap-southeast-2
144+
use_unsigned_payload: true
145+
match_excluded_headers:
146+
- prefix: x-envoy
147+
- prefix: x-forwarded
148+
- exact: x-amzn-trace-id
149+
- name: envoy.filters.http.router
150+
typed_config:
151+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
152+
clusters:
153+
- name: outbound_tls_proxy
154+
lb_policy: CLUSTER_PROVIDED
155+
cluster_type:
156+
name: envoy.clusters.dynamic_forward_proxy
157+
typed_config:
158+
"@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
159+
dns_cache_config:
160+
name: dynamic_forward_proxy_cache_config
161+
dns_lookup_family: V4_ONLY
162+
typed_dns_resolver_config:
163+
name: envoy.network.dns_resolver.cares
164+
typed_config:
165+
"@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
166+
use_resolvers_as_fallback: true
167+
resolvers:
168+
- socket_address:
169+
address: "127.0.0.1"
170+
port_value: 53
171+
dns_resolver_options:
172+
use_tcp_for_dns_lookups: true
173+
no_default_search_domain: true
174+
- name: jwks
175+
type: STRICT_DNS
176+
connect_timeout: 5s
177+
load_assignment:
178+
cluster_name: jwks
179+
endpoints:
180+
- lb_endpoints:
181+
- endpoint:
182+
address:
183+
socket_address:
184+
address: ${JWKS_HOST}
185+
port_value: 443
186+
transport_socket:
187+
name: envoy.transport_sockets.tls
188+
typed_config:
189+
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
190+
tracing:
191+
http:
192+
name: envoy.tracers.xray
193+
typed_config:
194+
"@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig
195+
segment_name: "envoy-frontend"
196+
daemon_endpoint:
197+
protocol: UDP
198+
address: 127.0.0.1
199+
port_value: 2000
200+
sampling_rule_manifest:
201+
inline_string: |
202+
{
203+
"version": 2,
204+
"rules": [
205+
{
206+
"description": "NoHealthChecks",
207+
"host": "*",
208+
"http_method": "*",
209+
"url_path": "/health",
210+
"fixed_target": 0,
211+
"rate": 0.0
212+
}
213+
],
214+
"default": {
215+
"fixed_target": 1,
216+
"rate": 0.05
217+
}
218+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
cat /etc/envoy/envoy.yaml.in | envsubst \$JWT_AUDIENCE,\$JWT_JWKS,\$JWT_ISSUER,\$JWKS_HOST,\$APP_DOMAIN > /etc/envoy/envoy.yaml
3+
envoy -l trace -c /etc/envoy/envoy.yaml

‎lattice_soln/lattice_soln_stack.py

+424
Large diffs are not rendered by default.

‎requirements-dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==6.2.5

‎requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
aws-cdk-lib==2.93.0
2+
constructs>=10.0.0,<11.0.0

‎source.bat

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@echo off
2+
3+
rem The sole purpose of this script is to make the command
4+
rem
5+
rem source .venv/bin/activate
6+
rem
7+
rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows.
8+
rem On Windows, this command just runs this batch file (the argument is ignored).
9+
rem
10+
rem Now we don't need to document a Windows command for activating a virtualenv.
11+
12+
echo Executing .venv\Scripts\activate.bat for you
13+
.venv\Scripts\activate.bat

‎tests/__init__.py

Whitespace-only changes.

‎tests/unit/__init__.py

Whitespace-only changes.

‎tests/unit/test_lattice_soln_stack.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import aws_cdk as core
2+
import aws_cdk.assertions as assertions
3+
4+
from lattice_soln.lattice_soln_stack import LatticeSolnStack
5+
6+
# example tests. To run these tests, uncomment this file along with the example
7+
# resource in lattice_soln/lattice_soln_stack.py
8+
def test_sqs_queue_created():
9+
app = core.App()
10+
stack = LatticeSolnStack(app, "lattice-soln")
11+
template = assertions.Template.from_stack(stack)
12+
13+
# template.has_resource_properties("AWS::SQS::Queue", {
14+
# "VisibilityTimeout": 300
15+
# })

0 commit comments

Comments
 (0)
Please sign in to comment.