├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── applications
├── mservice1
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── app1.py
│ ├── dockerpush.sh
│ └── requirements.txt
├── mservice2
│ └── app2lambda.py
└── portal
│ ├── Dockerfile
│ ├── dockerpush.sh
│ ├── index.py
│ ├── requirements.txt
│ ├── static
│ └── images
│ │ ├── adam.png
│ │ ├── arrow.png
│ │ ├── backdrop.png
│ │ ├── clientvpn.png
│ │ ├── favicon.ico
│ │ ├── mrx.png
│ │ ├── mservice1_open.png
│ │ ├── mservice1_secure.png
│ │ ├── mservice2_open.png
│ │ ├── mservice2_secure.png
│ │ ├── pablo.png
│ │ ├── teapot.png
│ │ ├── verifiedaccess.png
│ │ └── wait.jpg
│ └── templates
│ └── index.html
├── backend
├── iam.tf
├── locals.tf
├── main.tf
├── outputs.tf
├── providers.tf
└── variables.tf
├── frontend
├── iam.tf
├── main.tf
├── outputs.tf
├── providers.tf
└── variables.tf
├── modules
├── retrieve_parameters
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
└── share_parameter
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
└── network
├── main.tf
├── outputs.tf
├── providers.tf
└── variables.tf
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/terraform
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=terraform
3 |
4 | ### Terraform ###
5 | # Local .terraform directories
6 | **/.terraform/*
7 |
8 | # .tfstate files
9 | *.tfstate
10 | *.tfstate.*
11 | *.lock.hcl
12 | # Crash log files
13 | crash.log
14 |
15 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as
16 | # password, private keys, and other secrets. These should not be part of version
17 | # control as they are data points which are potentially sensitive and subject
18 | # to change depending on the environment.
19 | #
20 | *.tfvars
21 |
22 | # Ignore override files as they are usually used to override resources locally and so
23 | # are not checked in
24 | override.tf
25 | override.tf.json
26 | *_override.tf
27 | *_override.tf.json
28 |
29 | # Include override files you do wish to add to version control using negated pattern
30 | # !example_override.tf
31 |
32 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
33 | # example: *tfplan*
34 |
35 | # Ignore CLI configuration files
36 | .terraformrc
37 | terraform.rc
38 |
39 | # End of https://www.toptal.com/developers/gitignore/api/terraform
40 |
41 | # macOs
42 | .DS_Store
43 |
44 | # Project
45 | keys/
46 |
47 | gcm-diagnose.log
48 |
49 | **/client_vpn_config/*
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT No Attribution
2 |
3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Moving to a Zero Trust architecture in AWS using Infrastructure-as-Code
2 |
3 | In this repository, you will leverage [AWS Verified Access](https://aws.amazon.com/verified-access/) and [Amazon VPC Lattice](https://aws.amazon.com/vpc/lattice/) to simplify the connectivity between services, while improving the security posture of the communication. The Infrastructure-as-Code framework used is **Terraform**.
4 |
5 | This example requires the use of 3 AWS Accounts: one for the central Networking resources; one for the frontend application (hosted using [Amazon ECS Fargate](https://aws.amazon.com/fargate/)), and another one for the backend applications (hosted using Fargate and [AWS Lambda](https://aws.amazon.com/pm/lambda/)). The following resources are created:
6 |
7 | **Networking Account**
8 |
9 | * Amazon VPC Lattice service network, which is shared with the AWS Organization using [AWS Resource Access Manager](https://aws.amazon.com/ram/).
10 | * [Amazon Route 53 Profile](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/profiles.html) with a [Private Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html) associated.
11 | * The Profile is shared using AWS RAM.
12 | * The Private Hosted Zone contains the corresponding CNAME records translating the VPC Lattice services' custom domain names to the VPC Lattice generated domain names.
13 | * [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) is used to share information between AWS Accounts - Route 53 Profile ID and VPC Lattice service network ARN.
14 |
15 | **Frontend Account**
16 |
17 | * Amazon VPC hosting a Amazon ECS Fargate cluster for the *frontend application*. The Fargate service is the target of an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/)
18 | * VPC is associated to the VPC Lattice service network shared by the Networking AWS Account.
19 | * [Amazon ECR](https://aws.amazon.com/ecr/) repository to store the *frontend application* code.
20 | * AWS Verified Access resources to access the *frontend application* VPN-less with user authentication and authorization. The use of [AWS Identity Center](https://aws.amazon.com/iam/identity-center/) is expected, but not created in this repository.
21 |
22 | **Backend Account**
23 |
24 | * Amazon VPC hosting a Amazon ECS Fargate cluster for the *mservice1 application*. The Fargate service is the target of an Application Load Balancer.
25 | * VPC is associated to the VPC Lattice service network shared by the Networking AWS Account.
26 | * Amazon ECR repository to store the *mservice1 application* code.
27 | * AWS Lambda function containing *mservice2 application*.
28 | * VPC Lattice services for *mservice1* and *mservice2* applications. Both services are associated to the VPC Lattice service network associated by the Networking AWS Account.
29 |
30 | ## Pre-requisites
31 |
32 | * When using several AWS Accounts, make sure you use different AWS credentials when initializing the provider in each folder.
33 | * This repository does not configure AWS Identity Center. Check the [documentation](https://docs.aws.amazon.com/singlesignon/latest/userguide/tutorials.html) to understand how to enable it in your AWS Accounts (if not done already).
34 | * Terraform installed.
35 |
36 | ## Signing Assertion
37 |
38 | For enhanced security in your deployment, you should add code signing assertion to requests made by AWS Verified Access to your back-end applications. To implement this, simply modify the code signing entity **Authsigner** component within [index.py](./applications/portal/index.py) and then enforce assertion of the expected value within the **depacker()** function. An example of this signing assertion can be seen in the [AWS documentation](https://docs.aws.amazon.com/verified-access/latest/ug/user-claims-passing.html#sample-code)
39 |
40 | ## Code Principles
41 |
42 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern.
43 |
44 | ## Usage
45 |
46 | * Clone the repository
47 |
48 | ```
49 | git clone https://github.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws
50 | ```
51 |
52 | Edit the *variables.tf* file in each folder to configure the environment (AWS Region to use must be the same in all AWS Accounts):
53 |
54 | * Networking AWS Account:
55 | * AWS Region to use.
56 | * Public Hosted Zone ID (for AWS Verified Access endpoint resolution)
57 | * Private Hosted Zone name (for Amazon VPC Lattice services' resolution)
58 | * *Frontend* and *backend* AWS Account IDs.
59 | * *Frontend*, *mservice1* and *mservice2* domain names.
60 | * Frontend AWS Account:
61 | * AWS Region to use
62 | * [Amazon Certificate Manager](https://aws.amazon.com/certificate-manager/) certificate ARN (for HTTPS in Verified Access endpoint).
63 | * *Frontend* application domain name.
64 | * Networking AWS Account ID.
65 | * Identity Center Group ID.
66 | * Backend AWS Account:
67 | * AWS Region to use
68 | * Amazon Certificate Manager certificate ARN (for HTTPS in VPC Lattice services).
69 | * *mservice1* and *mservice2* application domain names.
70 | * Networking AWS Account ID.
71 | * IAM Role ARN used for the *frontend* application. This is used in VPC Lattice auth policies.
72 | * To share parameters between AWS Accounts, you will need to provide the Account ID of the corresponding Account in each folder.
73 | * We recommend the use of tha *tfvars* file to provide the required parameters.
74 |
75 | ## Deployment
76 |
77 | * **Step 1**: Networking Account resources
78 |
79 | ```
80 | cd network/
81 | terraform apply
82 | ```
83 |
84 | * **Step 2**: Frontend Account resources
85 |
86 | ```
87 | cd frontend/
88 | terraform apply
89 | ```
90 |
91 | * **Step 3**: Deploying *frontend* application
92 | * Move to /applications/portal and change the following files:
93 | * In `dockerpush.sh`, provide AWS Region used and the AWS Account ID for the *frontend* application in lines 3, 5, and 6.
94 | * In `index.py`, provide *mservice1* domain name in lines 24 and 25. Also add the AWS Region used in lines 27 and 32.
95 | * Build and push the code to the ECR repository created in Step 2.
96 |
97 | ```
98 | cd applications/portal/
99 | ./dockerpush.sh
100 | ```
101 |
102 | * **Step 4**: Backend Account resources
103 |
104 | ```
105 | cd backend/
106 | terraform apply
107 | ```
108 |
109 | * **Step 5**: Deploying *mservice1* application
110 | * Move to /applications/mservice1 and change the following files:
111 | * In `dockerpush.sh`, provide AWS Region used and the AWS Account ID for the *frontend* application in lines 3, 5, and 6.
112 | * In `app1.py`, provide *mservice2* domain name in lines 17 and 18. Also add the AWS Region used in line 24.
113 | * Build and push the code to the ECR repository created in Step 4.
114 |
115 | ```
116 | cd applications/mservice1/
117 | ./dockerpush.sh
118 | ```
119 |
120 | ## Security
121 |
122 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
123 |
124 | ## License
125 |
126 | This library is licensed under the MIT-0 License. See the LICENSE file.
--------------------------------------------------------------------------------
/applications/mservice1/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.classpath
2 | **/.dockerignore
3 | **/.env
4 | **/.git
5 | **/.gitignore
6 | **/.project
7 | **/.settings
8 | **/.toolstarget
9 | **/.vs
10 | **/.vscode
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/bin
15 | **/charts
16 | **/docker-compose*
17 | **/compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
26 |
--------------------------------------------------------------------------------
/applications/mservice1/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM amazonlinux:latest
2 | LABEL Name=app1 Version=0.0.1
3 | RUN yum update -y && \
4 | yum install -y python3-pip net-tools bind-utils iputils
5 | COPY . /app
6 | RUN pip install -r /app/requirements.txt
7 | CMD ["python3", "/app/app1.py"]
--------------------------------------------------------------------------------
/applications/mservice1/app1.py:
--------------------------------------------------------------------------------
1 | # Import Modules
2 |
3 | from flask import Flask, request
4 | import requests
5 | from botocore.awsrequest import AWSRequest
6 | from botocore.credentials import Credentials
7 | from botocore.auth import SigV4Auth
8 | import botocore.session
9 | import json
10 | import logging
11 |
12 | logging.basicConfig(level=logging.INFO)
13 | logger = logging.getLogger(__name__)
14 |
15 |
16 | # Define Variables
17 | mservice2 = "{DOMAIN_NAME}"
18 | mservice2sec = "{DOMAIN_NAME}/secure"
19 |
20 | # Secure signer function
21 |
22 | def signer(endpoint):
23 | session = botocore.session.Session()
24 | signer = SigV4Auth(session.get_credentials(), 'vpc-lattice-svcs', '{REGION}')
25 | endpoint = endpoint
26 | data = "null"
27 | headers = {}
28 | request = AWSRequest(method='GET', url=endpoint, data=data, headers=headers)
29 | request.context["payload_signing_enabled"] = False # payload signing is not supported
30 | signer.add_auth(request)
31 | prepped = request.prepare()
32 | return prepped
33 |
34 | app = Flask(__name__)
35 |
36 | @app.route("/", methods=['GET'])
37 | def app1resp():
38 |
39 | try:
40 | requestdata = request.json
41 | m2aresp = requests.get(mservice2,json=requestdata)
42 | m2payload = json.loads(m2aresp.content)
43 |
44 | data = {
45 | 'app1headers' : dict(request.headers),
46 | 'app1url' : request.url,
47 | 'app1message' : "Welcome to mservice1 - this is a simple open API - it calls mservice2",
48 | 'app1information' : "Make a connection to the secure endpoint for secrets",
49 | 'app2headers' : m2payload['app2headers'],
50 | 'app2url' : m2aresp.url,
51 | 'app2message' : m2payload['app2message'],
52 | 'app2information' : m2payload['app2information'],
53 | 'app2status' : m2aresp.status_code
54 | }
55 |
56 | if requestdata['portalidname'] == 'Anonymous':
57 | resp = 418
58 | else:
59 | resp = 200
60 |
61 | response = app.response_class(
62 | response=json.dumps(data),
63 | status=resp,
64 | mimetype='application/json'
65 | )
66 | return response
67 |
68 | except Exception as e:
69 | logger.info(e)
70 |
71 | data = {
72 | 'app1headers' : '',
73 | 'app1url' : '',
74 | 'app1message' : '',
75 | 'app1information' : '',
76 | 'app2headers' : '',
77 | 'app2url' : '',
78 | 'app2message' : '',
79 | 'app2information' : '',
80 | 'app2status' : ''
81 | }
82 |
83 | response = app.response_class(
84 | response=json.dumps(data),
85 | status=500,
86 | mimetype='application/json'
87 | )
88 | return response
89 |
90 | @app.route("/secure", methods=['GET','POST'])
91 | def app1respsec():
92 |
93 | try:
94 | headers = dict(request.headers)
95 |
96 | ## Comment out the below line when switching to signed requests
97 | m2asecresp = requests.get(mservice2sec)
98 | ## Uncomment the below line when switching to signed requests
99 | #prepped = signer(mservice2sec)
100 | #m2asecresp = requests.get(prepped.url, headers=prepped.headers)
101 |
102 | m2secpayload = json.loads(m2asecresp.content)
103 |
104 | if "X-Amz-Content-Sha256" in headers:
105 | resp = 200
106 | data = {
107 | 'app1secmessage' : 'Welcome to mservice1 (secure) - this contains sensitive cat treat information',
108 | 'app1secinformation' : 'Cats > that Dogs, it is a fact :-D ',
109 | 'app1secdetails' : 'House cats share 95.6 percent of their genetic makeup with tigers.',
110 | 'app1secheaders' : dict(request.headers),
111 | 'app1securl' : request.url,
112 | 'app2secmessage' : m2secpayload['app2secmessage'],
113 | 'app2secinformation' : m2secpayload['app2secinformation'],
114 | 'app2secdetails' : m2secpayload['app2secdetails'],
115 | 'app2secheaders' : m2secpayload['app2secheaders'],
116 | 'app2securl' : m2asecresp.url,
117 | 'app2secstatus' : m2asecresp.status_code
118 | }
119 | else:
120 | resp = 418
121 | data = {
122 | 'app1secmessage' : 'Welcome to mservice1 (secure) - this contains sensitive cat treat information',
123 | 'app1secinformation' : '[For more info, sign your requests]',
124 | 'app1secdetails' : '[For more info, sign your requests]',
125 | 'app1secheaders' : dict(request.headers),
126 | 'app1securl' : request.url,
127 | 'app2secmessage' : m2secpayload['app2secmessage'],
128 | 'app2secinformation' : m2secpayload['app2secinformation'],
129 | 'app2secdetails' : m2secpayload['app2secdetails'],
130 | 'app2secheaders' : m2secpayload['app2secheaders'],
131 | 'app2securl' : m2asecresp.url,
132 | 'app2secstatus' : m2asecresp.status_code
133 | }
134 |
135 | response = app.response_class(
136 | response=json.dumps(data),
137 | status=resp,
138 | mimetype='application/json'
139 | )
140 |
141 | return response
142 |
143 | except Exception as e:
144 | logger.info(e)
145 |
146 | data = {
147 | 'app1secmessage' : '',
148 | 'app1secinformation' : '',
149 | 'app1secdetails' : '',
150 | 'app1secheaders' : '',
151 | 'app1securl' : '',
152 | 'app2secmessage' : '',
153 | 'app2secinformation' : '',
154 | 'app2secdetails' : '',
155 | 'app2secheaders' : '',
156 | 'app2securl' : '',
157 | 'app2secstatus' : ''
158 | }
159 |
160 | response = app.response_class(
161 | response=json.dumps(data),
162 | status=500,
163 | mimetype='application/json'
164 | )
165 |
166 | return response
167 |
168 | @app.route("/health", methods=['GET'])
169 | def hc():
170 | response = app.response_class(
171 | status=200,
172 | mimetype='application/json'
173 | )
174 | return response
175 |
176 | if __name__ == '__main__':
177 | app.run(debug=True,host="0.0.0.0",port=8081)
--------------------------------------------------------------------------------
/applications/mservice1/dockerpush.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | aws ecr get-login-password --region {REGION} | docker login --username AWS --password-stdin {ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com/mservice1
4 | docker build -t frontend .
5 | docker tag frontend:latest {ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com/mservice1:latest
6 | docker push {ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com/mservice1:latest
--------------------------------------------------------------------------------
/applications/mservice1/requirements.txt:
--------------------------------------------------------------------------------
1 | ###### Requirements without Version Specifiers ######
2 | flask == 3.0.2
3 | requests == 2.31.0
4 | botocore == 1.34.70
5 | awscrt == 0.20.9
--------------------------------------------------------------------------------
/applications/mservice2/app2lambda.py:
--------------------------------------------------------------------------------
1 | # Import Modules
2 | import json
3 |
4 | def lambda_handler(event, context):
5 |
6 | # Obtaining headers and requestContent from event
7 | headers = event['headers']
8 | requestContext = event['requestContext']
9 | output = dict(**headers, **requestContext)
10 |
11 | if event['path'] == "/":
12 |
13 | body = json.loads(event['body'])
14 |
15 | data = {
16 | 'app2message' : 'Welcome to the mservice2 - this is a simple open API',
17 | 'app2information' : 'Make a connection to the secure endpoint for secrets',
18 | 'app2headers' : output
19 | }
20 |
21 | if body['portalidname'] == 'Anonymous':
22 | resp = 418
23 | else:
24 | resp = 200
25 |
26 | return {
27 | 'statusCode': resp,
28 | 'body': json.dumps(data)
29 | }
30 |
31 | else:
32 |
33 | ### Check for Connection Type
34 |
35 | if "x-amz-content-sha256" in headers:
36 | resp = 200
37 | data = {
38 | 'app2secmessage' : 'Welcome to the mservice2(secure) - this contains sensitive cat treat information',
39 | 'app2secinformation' : 'A word on feline capabilities',
40 | 'app2secdetails' : 'Cats can jump 5 times their own height',
41 | 'app2secheaders' : output
42 | }
43 | else:
44 | resp = 418
45 | data = {
46 | 'app2secmessage' : 'Welcome to the mservice2(secure) - this contains sensitive cat treat information',
47 | 'app2secinformation' : '[For more info, sign your requests]',
48 | 'app2secdetails' : '[For more info, sign your requests]',
49 | 'app2secheaders' : output
50 | }
51 |
52 | return {
53 | 'statusCode': resp,
54 | 'body': json.dumps(data)
55 | }
--------------------------------------------------------------------------------
/applications/portal/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM amazonlinux:latest
2 | LABEL Name=portal Version=0.0.1
3 | RUN yum update -y && \
4 | yum install -y python3-pip net-tools bind-utils iputils
5 | COPY . /app
6 | RUN pip install -r /app/requirements.txt
7 | CMD ["python3", "/app/index.py"]
--------------------------------------------------------------------------------
/applications/portal/dockerpush.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | aws ecr get-login-password --region {REGION} | docker login --username AWS --password-stdin {ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com
4 | docker build -t frontend .
5 | docker tag frontend:latest {ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com/frontend:latest
6 | docker push {ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com/frontend:latest
--------------------------------------------------------------------------------
/applications/portal/index.py:
--------------------------------------------------------------------------------
1 | #import modules
2 |
3 | import os
4 | from flask import Flask, render_template, request, redirect, url_for
5 | import requests
6 | from botocore.awsrequest import AWSRequest
7 | from botocore.credentials import Credentials
8 | from botocore.auth import SigV4Auth
9 | import botocore.session
10 | import json
11 | import base64
12 | import jwt
13 | import logging
14 |
15 | logging.basicConfig(level=logging.INFO)
16 | logger = logging.getLogger(__name__)
17 |
18 | #define variables
19 |
20 | img = os.path.join('static','images')
21 | global portal
22 | render = {}
23 |
24 | mservice1 = "{DOMAIN_NAME}"
25 | mservice1sec = "{DOMAIN_NAME}/secure"
26 |
27 | avaurl = 'https://public-keys.prod.verified-access.{REGION}.amazonaws.com/'
28 |
29 | ## ---------------------------------------- security notice ------------------------------------------- ##
30 | ## For increased security, you should validate the signing entity for requests made to your application ##
31 | ## To do that, replace the value below for the 'authsigner' with the verified access instanceID which ##
32 | ## takes the format 'arn:aws:ec2:region:111111111111:verified-access-instance/vai-abcd12345efghij89' ##
33 | ## following this, uncomment line #64 to assert the value ##
34 | ## ---------------------------------------- security notice ------------------------------------------- ##
35 |
36 | authsigner = 'arn:aws:ec2:region:accountid:verified-access-instance/instanceid'
37 |
38 | # Secure signer function
39 | def signer(endpoint):
40 | session = botocore.session.Session()
41 | signer = SigV4Auth(session.get_credentials(), 'vpc-lattice-svcs', '{REGION}')
42 | endpoint = endpoint
43 | data = "null"
44 | headers = {}
45 | request = AWSRequest(method='GET', url=endpoint, data=data, headers=headers)
46 | request.context["payload_signing_enabled"] = False # payload signing is not supported
47 | signer.add_auth(request)
48 | prepped = request.prepare()
49 | return prepped
50 |
51 | # AVA depacker function
52 | def depacker(portal):
53 |
54 | if "X-Amzn-Ava-User-Context" in portal:
55 |
56 | #Decode the headers
57 | encoded_jwt = portal['X-Amzn-Ava-User-Context']
58 | jwt_headers = encoded_jwt.split('.')[0]
59 | decoded_jwt_headers = base64.b64decode(jwt_headers)
60 | decoded_jwt_headers = decoded_jwt_headers.decode("utf-8")
61 | decoded_json = json.loads(decoded_jwt_headers)
62 | kid = decoded_json['kid']
63 |
64 | # assert authsigner in decoded_json['signer']
65 |
66 | #Get the public key
67 | url = avaurl + kid
68 | req = requests.get(url)
69 | pub_key = req.text
70 |
71 | #Get the payload
72 | payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES384'])
73 |
74 | ptype = os.path.join(img,'verifiedaccess.png')
75 | pid = os.path.join(img, payload['user']['user_name'] + '.png')
76 | pidname = payload['user']['user_name']
77 |
78 | else:
79 | ptype = os.path.join(img, 'clientvpn.png')
80 | pid = os.path.join(img, 'mrx.png')
81 | pidname = "Anonymous"
82 |
83 | response = {
84 | "portaltype" : ptype,
85 | "portalid" : pid,
86 | "portalidname" : pidname
87 | }
88 | return response
89 |
90 | # Portal Application
91 | app = Flask(__name__)
92 |
93 | @app.route('/', methods=['GET'])
94 | def getBaselinePictures():
95 |
96 | portal = depacker(dict(request.headers))
97 |
98 | render = {
99 | "app1img" : os.path.join(img,'wait.jpg'),
100 | "app1secimg" : os.path.join(img,'wait.jpg'),
101 | "app2img" : os.path.join(img,'wait.jpg'),
102 | "app2secimg" : os.path.join(img,'wait.jpg'),
103 | 'portalmessage' : "Welcome to the Feline microservice portal",
104 | 'portalinformation' : "Build a zero-trust layer to reveal feline imagery" if portal['portalidname'] == 'Anonymous' else "Welcome " + portal['portalidname'] + " - check the cat pictures (if you can) !",
105 | 'portalheaders' : dict(request.headers),
106 | "portaltype" : portal['portaltype'],
107 | "portalid" : portal['portalid'],
108 | "portalidname" : portal['portalidname'],
109 | 'app1headers' : '',
110 | 'app1url' : '',
111 | 'app1status' : '',
112 | 'app1message' : '',
113 | 'app1information' : '',
114 | 'app2headers' : '',
115 | 'app2url' : '',
116 | 'app2message' : '',
117 | 'app2information' : '',
118 | 'app2status' : '',
119 | 'app1secstatus' : '',
120 | 'app1secmessage' : '',
121 | 'app1secinformation' : '',
122 | 'app1secdetails' : '',
123 | 'app1secheaders' : '',
124 | 'app1securl' : '',
125 | 'app2secmessage' : '',
126 | 'app2secinformation' : '',
127 | 'app2secdetails' : '',
128 | 'app2secheaders' : '',
129 | 'app2securl' : '',
130 | 'app2secstatus' : ''
131 | }
132 |
133 | return render_template('index.html', render=render)
134 |
135 | @app.route('/run', methods=['GET','POST'])
136 | def runapp():
137 |
138 | portal = depacker(dict(request.headers))
139 |
140 | portaldata = {
141 | 'portalmessage' : "Welcome to the Feline microservice portal",
142 | 'portalinformation' : "Build a zero-trust layer to reveal feline imagery" if portal['portalidname'] == 'Anonymous' else "Welcome " + portal['portalidname'] + " - check the cat pictures (if you can) !",
143 | 'portalheaders' : dict(request.headers),
144 | 'portaltype' : portal['portaltype'],
145 | 'portalid' : portal['portalid'],
146 | 'portalidname' : portal['portalidname'],
147 | }
148 |
149 | try:
150 | logger.info("Attempting connection to mservice1 unsecured endpoint")
151 | m1aresp = requests.get(mservice1,json=portal)
152 | payload = json.loads(m1aresp.content)
153 |
154 | render1 = {
155 | 'app1img' : os.path.join(img,'mservice1_open.png') if m1aresp.status_code == 200 else os.path.join(img,'teapot.png') if m1aresp.status_code == 418 else os.path.join(img,'wait.png'),
156 | 'app2img' : os.path.join(img,'mservice2_open.png') if payload['app2status'] == 200 else os.path.join(img,'teapot.png') if payload['app2status'] == 418 else os.path.join(img,'wait.png'),
157 | 'app1headers' : payload['app1headers'],
158 | 'app1url' : payload['app1url'],
159 | 'app1status' : m1aresp.status_code,
160 | 'app1message' : payload['app1message'],
161 | 'app1information' : payload['app1information'],
162 | 'app2headers' : payload['app2headers'],
163 | 'app2url' : payload['app2url'],
164 | 'app2message' : payload['app2message'],
165 | 'app2information' : payload['app2information'],
166 | 'app2status' : payload['app2status']
167 | }
168 | render.update(render1)
169 |
170 | except Exception as e:
171 | logger.info("There was an issue connecting to mservice1 unsecured endpoint - error deatils: " + str(e))
172 |
173 | render1 = {
174 | 'app1img' : os.path.join(img,'wait.jpg'),
175 | 'app2img' : os.path.join(img,'wait.jpg'),
176 | 'app1headers' : "Communication Error",
177 | 'app1url' : "Communication Error",
178 | 'app1status' : "Communication Error",
179 | 'app1message' : "Communication Error",
180 | 'app1information' : "Communication Error",
181 | 'app2headers' : "Communication Error",
182 | 'app2url' : "Communication Error",
183 | 'app2message' : "Communication Error",
184 | 'app2information' : "Communication Error",
185 | 'app2status' : "Communication Error"
186 | }
187 | render.update(render1)
188 |
189 | try:
190 | logger.info("Attempting connection to mservice1 secured endpoint")
191 | ## Comment out the below line when switching to signed requests
192 | m1bresp = requests.get(mservice1sec)
193 | ## Uncomment the below two lines when switching to signed requests
194 | # prepped = signer(mservice1sec)
195 | # m1bresp = requests.get(prepped.url,headers=prepped.headers,json=portal)
196 |
197 | payload2 = json.loads(m1bresp.content)
198 | render2 = {
199 | 'app1secimg' : os.path.join(img,'mservice1_secure.png') if m1bresp.status_code == 200 else os.path.join(img,'teapot.png') if m1bresp.status_code == 418 else os.path.join(img,'wait.png'),
200 | 'app2secimg' : os.path.join(img,'mservice2_secure.png') if payload2['app2secstatus'] == 200 else os.path.join(img,'teapot.png') if payload2['app2secstatus'] == 418 else os.path.join(img,'wait.png'),
201 | 'app1secstatus' : m1bresp.status_code,
202 | 'app1secmessage' : payload2['app1secmessage'],
203 | 'app1secinformation' : payload2['app1secinformation'],
204 | 'app1secdetails' : payload2['app1secdetails'],
205 | 'app1secheaders' : payload2['app1secheaders'],
206 | 'app1securl' : payload2['app1securl'],
207 | 'app2secmessage' : payload2['app2secmessage'],
208 | 'app2secinformation' : payload2['app2secinformation'],
209 | 'app2secdetails' : payload2['app2secdetails'],
210 | 'app2secheaders' : payload2['app2secheaders'],
211 | 'app2securl' : payload2['app2securl'],
212 | 'app2secstatus' : payload2['app2secstatus']
213 | }
214 |
215 | render.update(render2)
216 |
217 | except Exception as e:
218 | logger.info("There was an issue connecting to mservice1 secured endpoint - error deatils: " + str(e))
219 |
220 | render2 = {
221 | 'app1secimg' : os.path.join(img,'wait.jpg'),
222 | 'app2secimg' : os.path.join(img,'wait.jpg'),
223 | 'app1secstatus' : "Communication Error",
224 | 'app1secmessage' : "Communication Error",
225 | 'app1secinformation' : "Communication Error",
226 | 'app1secdetails' : "Communication Error",
227 | 'app1secheaders' : "Communication Error",
228 | 'app1securl' : "Communication Error",
229 | 'app2secmessage' : "Communication Error",
230 | 'app2secinformation' : "Communication Error",
231 | 'app2secdetails' : "Communication Error",
232 | 'app2secheaders' : "Communication Error",
233 | 'app2securl' : "Communication Error",
234 | 'app2secstatus' : "Communication Error"
235 | }
236 |
237 | render.update(render2)
238 |
239 | render.update(portaldata)
240 | return render_template('index.html', render=render)
241 |
242 | @app.route("/health", methods=['GET'])
243 | def hc():
244 | response = app.response_class(
245 | status=200,
246 | mimetype='application/json'
247 | )
248 | return response
249 |
250 | if __name__ == '__main__':
251 | app.run(debug=True,host="0.0.0.0",port=8080)
--------------------------------------------------------------------------------
/applications/portal/requirements.txt:
--------------------------------------------------------------------------------
1 | ###### Requirements without Version Specifiers ######
2 | flask == 3.0.2
3 | requests == 2.31.0
4 | botocore == 1.34.70
5 | awscrt == 0.20.9
6 | pyjwt[crypto] == 2.8.0
--------------------------------------------------------------------------------
/applications/portal/static/images/adam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/adam.png
--------------------------------------------------------------------------------
/applications/portal/static/images/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/arrow.png
--------------------------------------------------------------------------------
/applications/portal/static/images/backdrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/backdrop.png
--------------------------------------------------------------------------------
/applications/portal/static/images/clientvpn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/clientvpn.png
--------------------------------------------------------------------------------
/applications/portal/static/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/favicon.ico
--------------------------------------------------------------------------------
/applications/portal/static/images/mrx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/mrx.png
--------------------------------------------------------------------------------
/applications/portal/static/images/mservice1_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/mservice1_open.png
--------------------------------------------------------------------------------
/applications/portal/static/images/mservice1_secure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/mservice1_secure.png
--------------------------------------------------------------------------------
/applications/portal/static/images/mservice2_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/mservice2_open.png
--------------------------------------------------------------------------------
/applications/portal/static/images/mservice2_secure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/mservice2_secure.png
--------------------------------------------------------------------------------
/applications/portal/static/images/pablo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/pablo.png
--------------------------------------------------------------------------------
/applications/portal/static/images/teapot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/teapot.png
--------------------------------------------------------------------------------
/applications/portal/static/images/verifiedaccess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/verifiedaccess.png
--------------------------------------------------------------------------------
/applications/portal/static/images/wait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/applications/portal/static/images/wait.jpg
--------------------------------------------------------------------------------
/applications/portal/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Zero-Trust Microservice Application
6 |
7 |
8 |
9 |
10 |
11 | ZeroTrust Cat Viewer Portal
12 |
13 |
14 | ECS Fargate Portal Tier
15 |
16 |
17 | Your Portal Connection |
18 | Your Portal Identity |
19 | Your Portal Requests |
20 |
21 |
22 |
23 |
24 | |
25 |
26 |
27 | |
28 |
29 |
30 |
31 | | Request URI |
32 | | {{request.url}} |
33 |
34 |
35 | | Request Principal |
36 | | {{render.portalidname}} |
37 |
38 |
39 | | PortalMessage |
40 | | {{render.portalmessage}} |
41 |
42 |
43 | | Portal Information |
44 | | {{render.portalinformation}} |
45 |
46 |
47 | |
48 |
49 |
50 |
51 |
52 |
53 |
56 | |
57 |
58 |
61 | |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |  }}) |
71 |
72 |  }}) |
73 |
74 |
75 |
76 |
77 |
78 | Elastic Container Service Tier (mservice1)
79 |
80 |
81 | AUTHENTICATED Connection |
82 | AUTHORIZED Connection |
83 |
84 |
85 |
86 |  |
87 |
88 |  |
89 |
90 |
91 |
92 |
93 |
94 | | Request URI |
95 | | {{render['app1url']}} |
96 |
97 |
98 | | App 1 Message |
99 | | {{render['app1message']}} |
100 |
101 |
102 | | App 1 Information |
103 | | {{render['app1information']}} |
104 |
105 |
106 | | Request Headers |
107 | |
{{render['app1headers']}} |
108 |
109 |
110 | | Status Code |
111 | | {{render['app1status']}} |
112 |
113 |
114 | |
115 |
116 |
117 |
118 | | Request URI |
119 | | {{render['app1securl']}} |
120 |
121 |
122 | | App 1 Sec Message |
123 | | {{render['app1secmessage']}} |
124 |
125 |
126 | | App 1 Sec Information |
127 | | {{render['app1secinformation']}} |
128 |
129 |
130 | | App 1 Sec Details |
131 | | {{render['app1secdetails']}} |
132 |
133 |
134 | | Request Headers |
135 | |
{{render['app1secheaders']}} |
136 |
137 |
138 | | Status Code |
139 | | {{render['app1secstatus']}} |
140 |
141 |
142 | |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |  }}) |
152 |
153 |  }}) |
154 |
155 |
156 |
157 |
158 |
159 | LAMBDA Function Tier (mservice2)
160 |
161 |
162 | AUTHENTICATED Connection |
163 | AUTHORIZED Connection |
164 |
165 |
166 |
167 |  |
168 |
169 |  |
170 |
171 |
172 |
173 |
174 |
175 | | Request URI |
176 | | {{render['app2url']}} |
177 |
178 |
179 | | App 2 Message |
180 | | {{render['app2message']}} |
181 |
182 |
183 | | App 2 Information |
184 | | {{render['app2information']}} |
185 |
186 |
187 | | Request Headers |
188 | |
{{render['app2headers']}} |
189 |
190 |
191 | | Status Code |
192 | | {{render['app2status']}} |
193 |
194 |
195 | |
196 |
197 |
198 |
199 | | Request URI |
200 | | {{render['app2securl']}} |
201 |
202 |
203 | | App 2 Sec Message |
204 | | {{render['app2secmessage']}} |
205 |
206 |
207 | | App 2 Sec Information |
208 | | {{render['app2secinformation']}} |
209 |
210 |
211 | | App 2 Sec Details |
212 | | {{render['app2secdetails']}} |
213 |
214 |
215 | | Request Headers |
216 | |
{{render['app2secheaders']}} |
217 |
218 |
219 | | Status Code |
220 | | {{render['app2secstatus']}} |
221 |
222 |
223 | |
224 |
225 |
226 |
227 |
228 |
229 |
--------------------------------------------------------------------------------
/backend/iam.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/iam.tf ----------
2 |
3 | # ECS Execution Task Role
4 | resource "aws_iam_role" "ecs_task_execution_role" {
5 | name = "ecsTaskExecutionRole"
6 | path = "/"
7 | description = "ECS Execution Role."
8 |
9 | managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
10 | assume_role_policy = data.aws_iam_policy_document.ecs_task_role_assume.json
11 | }
12 |
13 | # ECS Execution Task Role
14 | resource "aws_iam_role" "ecs_task_role" {
15 | name = "mservice1"
16 | path = "/"
17 | description = "Allows ECS tasks to call AWS services on your behalf."
18 |
19 | managed_policy_arns = ["arn:aws:iam::aws:policy/VPCLatticeServicesInvokeAccess"]
20 | assume_role_policy = data.aws_iam_policy_document.ecs_task_role_assume.json
21 |
22 | inline_policy {
23 | name = "EcsExecPerms"
24 | policy = data.aws_iam_policy_document.ecs_task_role_inline.json
25 | }
26 | }
27 |
28 | # Lambda Function Role
29 | resource "aws_iam_role" "lambda_role" {
30 | name = "mservice2"
31 | path = "/"
32 | description = "Lambda function role."
33 |
34 | managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]
35 | assume_role_policy = data.aws_iam_policy_document.lambda_function_role_assume.json
36 | }
37 |
38 | # IAM Policies
39 | data "aws_iam_policy_document" "ecs_task_role_assume" {
40 | statement {
41 | effect = "Allow"
42 | actions = ["sts:AssumeRole"]
43 |
44 | principals {
45 | type = "Service"
46 | identifiers = ["ecs-tasks.amazonaws.com"]
47 | }
48 | }
49 | }
50 |
51 | data "aws_iam_policy_document" "ecs_task_role_inline" {
52 | statement {
53 | sid = "EcsExecPerms"
54 | effect = "Allow"
55 | actions = [
56 | "ssmmessages:CreateControlChannel",
57 | "ssmmessages:CreateDataChannel",
58 | "ssmmessages:OpenControlChannel",
59 | "ssmmessages:OpenDataChannel"
60 | ]
61 | resources = ["*"]
62 | }
63 | }
64 |
65 | data "aws_iam_policy_document" "lambda_function_role_assume" {
66 | statement {
67 | effect = "Allow"
68 | actions = ["sts:AssumeRole"]
69 |
70 | principals {
71 | type = "Service"
72 | identifiers = ["lambda.amazonaws.com"]
73 | }
74 | }
75 | }
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/backend/locals.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/locals.tf ----------
2 |
3 | locals {
4 | service1_policy = jsonencode({
5 | "Version" : "2012-10-17"
6 | "Statement" : [
7 | {
8 | "Effect" : "Allow"
9 | "Principal" : {
10 | "AWS" : "${var.frontend_application_role_arn}"
11 | }
12 | "Action" : "vpc-lattice-svcs:Invoke"
13 | "Resource" : "${module.vpclattice_service1.services.mservice1.attributes.arn}/secure*"
14 | },
15 | {
16 | "Effect" : "Allow"
17 | "Principal" : "*"
18 | "Action" : "vpc-lattice-svcs:Invoke"
19 | "Resource" : "${module.vpclattice_service1.services.mservice1.attributes.arn}/*"
20 | },
21 | {
22 | "Effect" : "Deny"
23 | "Principal" : "*"
24 | "Action" : "vpc-lattice-svcs:Invoke"
25 | "Resource" : "${module.vpclattice_service1.services.mservice1.attributes.arn}/secure*"
26 | "Condition" : {
27 | "StringEquals" : {
28 | "aws:PrincipalType" : "Anonymous"
29 | }
30 | }
31 | }
32 | ]
33 | })
34 |
35 | service2_policy = jsonencode({
36 | "Version" : "2012-10-17"
37 | "Statement" : [
38 | {
39 | "Effect" : "Allow"
40 | "Principal" : {
41 | "AWS" : "arn:aws:iam::992382807606:role/mservice1"
42 | }
43 | "Action" : "vpc-lattice-svcs:Invoke"
44 | "Resource" : "${module.vpclattice_service2.services.mservice2.attributes.arn}/secure*"
45 | },
46 | {
47 | "Effect" : "Allow"
48 | "Principal" : "*"
49 | "Action" : "vpc-lattice-svcs:Invoke"
50 | "Resource" : "${module.vpclattice_service2.services.mservice2.attributes.arn}/*"
51 | },
52 | {
53 | "Effect" : "Deny"
54 | "Principal" : "*"
55 | "Action" : "vpc-lattice-svcs:Invoke"
56 | "Resource" : "${module.vpclattice_service2.services.mservice2.attributes.arn}/secure*"
57 | "Condition" : {
58 | "StringEquals" : {
59 | "aws:PrincipalType" : "Anonymous"
60 | }
61 | }
62 | }
63 | ]
64 | })
65 | }
--------------------------------------------------------------------------------
/backend/main.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/main.tf ----------
2 |
3 | data "aws_region" "current" {}
4 |
5 | # ---------- VPC LATTICE CONFIGURATION ---------
6 | # Service 1
7 | module "vpclattice_service1" {
8 | source = "aws-ia/amazon-vpc-lattice-module/aws"
9 | version = "0.1.0"
10 |
11 | service_network = { identifier = module.retrieve_parameters.parameter.service_network }
12 |
13 | vpc_associations = {
14 | backend1_vpc = { vpc_id = module.backend1_vpc.vpc_attributes.id }
15 | }
16 |
17 | services = {
18 | mservice1 = {
19 | name = "mservice1"
20 | auth_type = "AWS_IAM"
21 | certificate_arn = var.certificate_arn
22 | custom_domain_name = var.backend_service1_domain_name
23 |
24 | listeners = {
25 | https_listener = {
26 | name = "https-443"
27 | port = 443
28 | protocol = "HTTPS"
29 | default_action_forward = {
30 | target_groups = {
31 | albtarget = { weight = 1 }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 | target_groups = {
40 | albtarget = {
41 | name = "mservice1"
42 | type = "ALB"
43 |
44 | config = {
45 | port = 443
46 | protocol = "HTTPS"
47 | vpc_identifier = module.backend1_vpc.vpc_attributes.id
48 | }
49 |
50 | targets = {
51 | albtarget = {
52 | id = aws_lb.backend1_lb.arn
53 | port = 443
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | resource "aws_vpclattice_auth_policy" "service1_auth_policy" {
61 | resource_identifier = module.vpclattice_service1.services.mservice1.attributes.arn
62 | policy = local.service1_policy
63 | }
64 |
65 | # Service 2
66 | module "vpclattice_service2" {
67 | source = "aws-ia/amazon-vpc-lattice-module/aws"
68 | version = "0.1.0"
69 |
70 | service_network = { identifier = module.retrieve_parameters.parameter.service_network }
71 |
72 | services = {
73 | mservice2 = {
74 | name = "mservice2"
75 | auth_type = "AWS_IAM"
76 | certificate_arn = var.certificate_arn
77 | custom_domain_name = var.backend_service2_domain_name
78 |
79 | listeners = {
80 | https_listener = {
81 | name = "https-443"
82 | port = 443
83 | protocol = "HTTPS"
84 | default_action_forward = {
85 | target_groups = {
86 | lambdatarget = { weight = 1 }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | target_groups = {
95 | lambdatarget = {
96 | name = "mservice2"
97 | type = "LAMBDA"
98 |
99 | targets = { lambda = { id = aws_lambda_function.backend2_function.arn } }
100 | }
101 | }
102 | }
103 |
104 | resource "aws_vpclattice_auth_policy" "service2_auth_policy" {
105 | resource_identifier = module.vpclattice_service2.services.mservice2.attributes.arn
106 | policy = local.service2_policy
107 | }
108 |
109 | # ---------- BACKEND VPC (1) ----------
110 | module "backend1_vpc" {
111 | source = "aws-ia/vpc/aws"
112 | version = "4.4.2"
113 |
114 | cidr_block = "10.0.0.0/16"
115 | name = "backend1-vpc"
116 | az_count = 2
117 |
118 | subnets = {
119 | public = {
120 | netmask = 28
121 | nat_gateway_configuration = "all_azs"
122 | }
123 | private = { netmask = 24 }
124 | application = {
125 | netmask = 24
126 | connect_to_public_natgw = true
127 | }
128 | endpoints = { netmask = 28 }
129 | transit_gateway = { netmask = 28 }
130 | }
131 | }
132 |
133 | # Associating central Route53 Profile (from Networking Account)
134 | resource "awscc_route53profiles_profile_association" "backend1_r53_profile_association" {
135 | name = "r53_profile_backend1_association"
136 | profile_id = module.retrieve_parameters.parameter.r53_profile
137 | resource_id = module.backend1_vpc.vpc_attributes.id
138 | }
139 |
140 | # ---------- BACKEND APPLICATION 1 ----------
141 | # Application Load Balancer
142 | resource "aws_lb" "backend1_lb" {
143 | name = "mservice1"
144 | internal = true
145 | load_balancer_type = "application"
146 | security_groups = [aws_security_group.backend1_alb_sg.id]
147 | subnets = values({ for k, v in module.backend1_vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "private" })
148 | ip_address_type = "ipv4"
149 | }
150 |
151 | # Target Group
152 | resource "aws_lb_target_group" "backend1_target_group" {
153 | name = "mservice1"
154 | port = 8081
155 | protocol = "HTTP"
156 | vpc_id = module.backend1_vpc.vpc_attributes.id
157 | target_type = "ip"
158 | }
159 |
160 | # Listener
161 | resource "aws_lb_listener" "backend1_listener" {
162 | load_balancer_arn = aws_lb.backend1_lb.arn
163 | port = 443
164 | protocol = "HTTPS"
165 | certificate_arn = var.certificate_arn
166 |
167 | default_action {
168 | type = "forward"
169 | target_group_arn = aws_lb_target_group.backend1_target_group.arn
170 | }
171 | }
172 |
173 | # Security Group
174 | resource "aws_security_group" "backend1_alb_sg" {
175 | name = "mservice1-alb"
176 | description = "allows access to the load balancer"
177 | vpc_id = module.backend1_vpc.vpc_attributes.id
178 | }
179 |
180 | resource "aws_vpc_security_group_ingress_rule" "backend1_allowing_ingress_alb_https" {
181 | security_group_id = aws_security_group.backend1_alb_sg.id
182 |
183 | from_port = 443
184 | to_port = 443
185 | ip_protocol = "tcp"
186 | cidr_ipv4 = "0.0.0.0/0"
187 | }
188 |
189 | resource "aws_vpc_security_group_egress_rule" "backend1_allowing_alb_health_check" {
190 | security_group_id = aws_security_group.backend1_alb_sg.id
191 |
192 | ip_protocol = "-1"
193 | cidr_ipv4 = data.aws_vpc.backend1_vpc.cidr_block
194 | }
195 |
196 | # ECR respository
197 | resource "aws_ecr_repository" "repository" {
198 | name = "mservice1"
199 | }
200 |
201 | # ECS Cluster
202 | resource "aws_ecs_cluster" "backend1_cluster" {
203 | name = "mservice1"
204 |
205 | configuration {
206 | execute_command_configuration {
207 | logging = "DEFAULT"
208 | }
209 | }
210 | }
211 |
212 | resource "aws_ecs_cluster_capacity_providers" "cluster_capacity_provider" {
213 | cluster_name = aws_ecs_cluster.backend1_cluster.name
214 | capacity_providers = ["FARGATE", "FARGATE_SPOT"]
215 | }
216 |
217 | # ECS Service
218 | resource "aws_ecs_service" "backend1_service" {
219 | cluster = aws_ecs_cluster.backend1_cluster.arn
220 | name = "mservice1"
221 | platform_version = "LATEST"
222 | task_definition = split("/", aws_ecs_task_definition.backend1_task_definition.arn)[1]
223 | scheduling_strategy = "REPLICA"
224 | propagate_tags = "NONE"
225 |
226 | deployment_maximum_percent = 200
227 | deployment_minimum_healthy_percent = 100
228 | desired_count = 2
229 | enable_ecs_managed_tags = true
230 | enable_execute_command = true
231 |
232 | capacity_provider_strategy {
233 | base = 0
234 | capacity_provider = "FARGATE"
235 | weight = 1
236 | }
237 |
238 | deployment_circuit_breaker {
239 | enable = true
240 | rollback = true
241 | }
242 |
243 | load_balancer {
244 | container_name = "mservice1"
245 | container_port = 8081
246 | target_group_arn = aws_lb_target_group.backend1_target_group.arn
247 | }
248 |
249 | network_configuration {
250 | assign_public_ip = false
251 | security_groups = [aws_security_group.backend1_ecs_service_sg.id]
252 | subnets = values({ for k, v in module.backend1_vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "application" })
253 | }
254 | }
255 |
256 | # ECS Task Definition
257 | resource "aws_ecs_task_definition" "backend1_task_definition" {
258 | family = "mservice1"
259 | cpu = 1024
260 | execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
261 | memory = 3072
262 | network_mode = "awsvpc"
263 | requires_compatibilities = ["FARGATE"]
264 | task_role_arn = aws_iam_role.ecs_task_role.arn
265 |
266 | container_definitions = jsonencode([{
267 | name = "mservice1"
268 | image = "${aws_ecr_repository.repository.repository_url}:latest"
269 | essential = true
270 |
271 | logConfiguration = {
272 | logDriver = "awslogs"
273 | options = {
274 | awslogs-create-group = "true"
275 | awslogs-group = "/ecs/mservice1"
276 | awslogs-region = data.aws_region.current.name
277 | awslogs-stream-prefix = "ecs"
278 | }
279 | }
280 |
281 | portMappings = [{
282 | appProtocol = "http"
283 | containerPort = 8081
284 | hostPort = 8081
285 | name = "mservice1-8081-tcp"
286 | protocol = "tcp"
287 | }]
288 | }])
289 |
290 | runtime_platform {
291 | cpu_architecture = "X86_64"
292 | operating_system_family = "LINUX"
293 | }
294 | }
295 |
296 | # Security Group (ECS Service)
297 | resource "aws_security_group" "backend1_ecs_service_sg" {
298 | name = "mservice1"
299 | description = "Created in ECS Console"
300 | vpc_id = module.backend1_vpc.vpc_attributes.id
301 | }
302 |
303 | resource "aws_vpc_security_group_ingress_rule" "ingress_ipv4" {
304 | security_group_id = aws_security_group.backend1_ecs_service_sg.id
305 |
306 | ip_protocol = "tcp"
307 | from_port = 8081
308 | to_port = 8081
309 | cidr_ipv4 = data.aws_vpc.backend1_vpc.cidr_block
310 | }
311 |
312 | resource "aws_vpc_security_group_egress_rule" "egress_ipv4" {
313 | security_group_id = aws_security_group.backend1_ecs_service_sg.id
314 |
315 | ip_protocol = "-1"
316 | cidr_ipv4 = "0.0.0.0/0"
317 | }
318 |
319 | # ---------- BACKEND APPLICATION 2 ----------
320 | # Lambda function
321 | resource "aws_lambda_function" "backend2_function" {
322 | function_name = "mservice2"
323 | handler = "app2lambda.lambda_handler"
324 | runtime = "python3.12"
325 | memory_size = 128
326 | timeout = 10
327 |
328 | role = aws_iam_role.lambda_role.arn
329 | filename = "lambda_function.zip"
330 | source_code_hash = data.archive_file.lambda.output_base64sha256
331 | }
332 |
333 | data "archive_file" "lambda" {
334 | type = "zip"
335 | source_file = "../applications/mservice2/app2lambda.py"
336 | output_path = "lambda_function.zip"
337 | }
338 |
339 | # ---------- PARAMETERS ----------
340 | # Sharing
341 | module "share_parameters" {
342 | source = "../modules/share_parameter"
343 |
344 | parameters = {
345 | service1_domain_name = module.vpclattice_service1.services.mservice1.attributes.dns_entry
346 | service2_domain_name = module.vpclattice_service2.services.mservice2.attributes.dns_entry
347 | }
348 | }
349 |
350 | # Retrieving
351 | module "retrieve_parameters" {
352 | source = "../modules/retrieve_parameters"
353 |
354 | parameters = {
355 | r53_profile = var.networking_account
356 | service_network = var.networking_account
357 | }
358 | }
359 |
360 |
--------------------------------------------------------------------------------
/backend/outputs.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/outputs.tf ----------
--------------------------------------------------------------------------------
/backend/providers.tf:
--------------------------------------------------------------------------------
1 | # ---------- backend/providers.tf ----------
2 |
3 | terraform {
4 | required_version = ">= 1.3.0"
5 | required_providers {
6 | aws = {
7 | source = "hashicorp/aws"
8 | version = ">= 5.0.0"
9 | }
10 | awscc = {
11 | source = "hashicorp/awscc"
12 | version = "= 0.78.0"
13 | }
14 | }
15 | }
16 |
17 | provider "aws" {
18 | region = var.aws_region
19 | }
20 |
21 | provider "awscc" {
22 | region = var.aws_region
23 | }
--------------------------------------------------------------------------------
/backend/variables.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/variables.tf ----------
2 |
3 | variable "aws_region" {
4 | type = string
5 | description = "AWS Region."
6 |
7 | default = "eu-west-1"
8 | }
9 |
10 | variable "certificate_arn" {
11 | type = string
12 | description = "ACM certificate ARN."
13 | }
14 |
15 | variable "networking_account" {
16 | type = string
17 | description = "Networking Account ID."
18 | }
19 |
20 | variable "backend_service1_domain_name" {
21 | type = string
22 | description = "Backend App1 - Domain Name."
23 | }
24 |
25 | variable "backend_service2_domain_name" {
26 | type = string
27 | description = "Backend App2 - Domain Name."
28 | }
29 |
30 | variable "frontend_application_role_arn" {
31 | type = string
32 | description = "Frontend Application Role ARN."
33 | }
--------------------------------------------------------------------------------
/frontend/iam.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/iam.tf ----------
2 |
3 | # ECS Execution Task Role
4 | resource "aws_iam_role" "ecs_task_execution_role" {
5 | name = "ecsTaskExecutionRole"
6 | path = "/"
7 | description = "ECS Execution Role."
8 |
9 | managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
10 | assume_role_policy = data.aws_iam_policy_document.ecs_task_role_assume.json
11 | }
12 |
13 | # ECS Execution Task Role
14 | resource "aws_iam_role" "ecs_task_role" {
15 | name = "frontend"
16 | path = "/"
17 | description = "Allows ECS tasks to call AWS services on your behalf."
18 |
19 | managed_policy_arns = ["arn:aws:iam::aws:policy/VPCLatticeServicesInvokeAccess"]
20 | assume_role_policy = data.aws_iam_policy_document.ecs_task_role_assume.json
21 |
22 | inline_policy {
23 | name = "EcsExecPerms"
24 | policy = data.aws_iam_policy_document.ecs_task_role_inline.json
25 | }
26 | }
27 |
28 | # IAM Policies
29 | data "aws_iam_policy_document" "ecs_task_role_assume" {
30 | statement {
31 | effect = "Allow"
32 | actions = ["sts:AssumeRole"]
33 |
34 | principals {
35 | type = "Service"
36 | identifiers = ["ecs-tasks.amazonaws.com"]
37 | }
38 | }
39 | }
40 |
41 | data "aws_iam_policy_document" "ecs_task_role_inline" {
42 | statement {
43 | sid = "EcsExecPerms"
44 | effect = "Allow"
45 | actions = [
46 | "ssmmessages:CreateControlChannel",
47 | "ssmmessages:CreateDataChannel",
48 | "ssmmessages:OpenControlChannel",
49 | "ssmmessages:OpenDataChannel"
50 | ]
51 | resources = ["*"]
52 | }
53 | }
--------------------------------------------------------------------------------
/frontend/main.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/main.tf ----------
2 |
3 | data "aws_region" "current" {}
4 |
5 | # ---------- FRONTEND VPC ----------
6 | module "frontend_vpc" {
7 | source = "aws-ia/vpc/aws"
8 | version = "4.4.2"
9 |
10 | cidr_block = "10.0.0.0/16"
11 | name = "frontend-vpc"
12 | az_count = 2
13 |
14 | vpc_lattice = {
15 | service_network_identifier = module.retrieve_parameters.parameter.service_network
16 | }
17 |
18 | subnets = {
19 | public = {
20 | netmask = 24
21 | nat_gateway_configuration = "all_azs"
22 | }
23 | private = { netmask = 24 }
24 | application = {
25 | netmask = 24
26 | connect_to_public_natgw = true
27 | }
28 | endpoints = { netmask = 28 }
29 | transit_gateway = { netmask = 28 }
30 | }
31 | }
32 |
33 | # Associating central Route53 Profile (from Networking Account)
34 | resource "awscc_route53profiles_profile_association" "r53_profile_association" {
35 | name = "r53_profile_frontend_association"
36 | profile_id = module.retrieve_parameters.parameter.r53_profile
37 | resource_id = module.frontend_vpc.vpc_attributes.id
38 | }
39 |
40 | # ---------- AWS VERIFIED ACCESS ----------
41 | # Instance
42 | resource "aws_verifiedaccess_instance" "ava_instance" {
43 | description = "AVA Instance"
44 |
45 | tags = {
46 | Name = "ava-instance"
47 | }
48 | }
49 |
50 | # Trust Provider
51 | resource "aws_verifiedaccess_trust_provider" "trust_provider" {
52 | description = "AVA Trust Provider - User (IAM Identity Center)"
53 | policy_reference_name = "frontenduser"
54 | trust_provider_type = "user"
55 | user_trust_provider_type = "iam-identity-center"
56 |
57 | tags = {
58 | Name = "ava-trust-provider"
59 | }
60 | }
61 |
62 | resource "aws_verifiedaccess_instance_trust_provider_attachment" "trust_provider_attachment" {
63 | verifiedaccess_instance_id = aws_verifiedaccess_instance.ava_instance.id
64 | verifiedaccess_trust_provider_id = aws_verifiedaccess_trust_provider.trust_provider.id
65 | }
66 |
67 | # Group
68 | resource "aws_verifiedaccess_group" "ava_group" {
69 | description = "AVA Group"
70 | verifiedaccess_instance_id = aws_verifiedaccess_instance.ava_instance.id
71 |
72 | policy_document = < v.id if split("/", k)[0] == "private" })
103 | }
104 |
105 | tags = {
106 | Name = "App Frontend"
107 | }
108 | }
109 |
110 | # Security Group
111 | resource "aws_security_group" "ava_sg" {
112 | name = "ava-security-group"
113 | description = "AWS Verified Access endpoint"
114 | vpc_id = module.frontend_vpc.vpc_attributes.id
115 | }
116 |
117 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_https" {
118 | security_group_id = aws_security_group.ava_sg.id
119 |
120 | from_port = 443
121 | to_port = 443
122 | ip_protocol = "tcp"
123 | cidr_ipv4 = data.aws_vpc.vpc.cidr_block
124 | }
125 |
126 | resource "aws_vpc_security_group_egress_rule" "allowing_ava_alb_connectivity" {
127 | security_group_id = aws_security_group.ava_sg.id
128 |
129 | ip_protocol = "-1"
130 | cidr_ipv4 = data.aws_vpc.vpc.cidr_block
131 | }
132 |
133 | # ---------- FRONTEND APPLICATION ----------
134 | # Application Load Balancer
135 | resource "aws_lb" "lb" {
136 | name = "frontend"
137 | internal = true
138 | load_balancer_type = "application"
139 | security_groups = [aws_security_group.alb_sg.id]
140 | subnets = values({ for k, v in module.frontend_vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "private" })
141 | ip_address_type = "ipv4"
142 | }
143 |
144 | # Target Group
145 | resource "aws_lb_target_group" "target_group" {
146 | name = "frontend-targets"
147 | port = 8080
148 | protocol = "HTTP"
149 | vpc_id = module.frontend_vpc.vpc_attributes.id
150 | target_type = "ip"
151 | }
152 |
153 | # Listener
154 | resource "aws_lb_listener" "frontend_listener" {
155 | load_balancer_arn = aws_lb.lb.arn
156 | port = 443
157 | protocol = "HTTPS"
158 | certificate_arn = var.certificate_arn
159 |
160 | default_action {
161 | type = "forward"
162 | target_group_arn = aws_lb_target_group.target_group.arn
163 |
164 | }
165 | }
166 |
167 | # Security Group (Application Load Balancer)
168 | resource "aws_security_group" "alb_sg" {
169 | name = "frontend-elb-secgroup"
170 | description = "allows port 80 connectivity"
171 | vpc_id = module.frontend_vpc.vpc_attributes.id
172 | }
173 |
174 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_alb_https" {
175 | security_group_id = aws_security_group.alb_sg.id
176 |
177 | from_port = 443
178 | to_port = 443
179 | ip_protocol = "tcp"
180 | cidr_ipv4 = data.aws_vpc.vpc.cidr_block
181 | }
182 |
183 | resource "aws_vpc_security_group_egress_rule" "allowing_alb_health_check" {
184 | security_group_id = aws_security_group.alb_sg.id
185 |
186 | ip_protocol = "-1"
187 | cidr_ipv4 = data.aws_vpc.vpc.cidr_block
188 | }
189 |
190 | # ECR respository
191 | resource "aws_ecr_repository" "repository" {
192 | name = "frontend"
193 | }
194 |
195 | # ECS Cluster
196 | resource "aws_ecs_cluster" "frontend_cluster" {
197 | name = "frontend"
198 |
199 | configuration {
200 | execute_command_configuration {
201 | logging = "DEFAULT"
202 | }
203 | }
204 | }
205 |
206 | resource "aws_ecs_cluster_capacity_providers" "cluster_capacity_provider" {
207 | cluster_name = aws_ecs_cluster.frontend_cluster.name
208 | capacity_providers = ["FARGATE", "FARGATE_SPOT"]
209 | }
210 |
211 | # ECS Service
212 | resource "aws_ecs_service" "frontend_service" {
213 | cluster = aws_ecs_cluster.frontend_cluster.arn
214 | name = "frontend"
215 | platform_version = "LATEST"
216 | task_definition = split("/", aws_ecs_task_definition.frontend_task_definition.arn)[1]
217 | scheduling_strategy = "REPLICA"
218 | propagate_tags = "NONE"
219 |
220 | deployment_maximum_percent = 200
221 | deployment_minimum_healthy_percent = 100
222 | desired_count = 2
223 | enable_ecs_managed_tags = true
224 | enable_execute_command = true
225 |
226 | capacity_provider_strategy {
227 | base = 0
228 | capacity_provider = "FARGATE"
229 | weight = 1
230 | }
231 |
232 | deployment_circuit_breaker {
233 | enable = true
234 | rollback = true
235 | }
236 |
237 | load_balancer {
238 | container_name = "frontend"
239 | container_port = 8080
240 | target_group_arn = aws_lb_target_group.target_group.arn
241 | }
242 |
243 | network_configuration {
244 | assign_public_ip = false
245 | security_groups = [aws_security_group.ecs_service_sg.id]
246 | subnets = values({ for k, v in module.frontend_vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "application" })
247 | }
248 | }
249 |
250 | # ECS Task Definition
251 | resource "aws_ecs_task_definition" "frontend_task_definition" {
252 | cpu = 1024
253 | execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
254 | family = "frontend"
255 | memory = 3072
256 | network_mode = "awsvpc"
257 | requires_compatibilities = ["FARGATE"]
258 | task_role_arn = aws_iam_role.ecs_task_role.arn
259 |
260 | runtime_platform {
261 | cpu_architecture = "X86_64"
262 | operating_system_family = "LINUX"
263 | }
264 |
265 | container_definitions = jsonencode([{
266 | name = "frontend"
267 | image = "${aws_ecr_repository.repository.repository_url}:latest"
268 | essential = true
269 |
270 | logConfiguration = {
271 | logDriver = "awslogs"
272 | options = {
273 | awslogs-create-group = "true"
274 | awslogs-group = "/ecs/frontend"
275 | awslogs-region = data.aws_region.current.name
276 | awslogs-stream-prefix = "ecs"
277 | }
278 | }
279 |
280 | portMappings = [{
281 | appProtocol = "http"
282 | containerPort = 8080
283 | hostPort = 8080
284 | name = "frontend-8080-tcp"
285 | protocol = "tcp"
286 | }]
287 | }])
288 | }
289 |
290 | # Security Group (ECS Service)
291 | resource "aws_security_group" "ecs_service_sg" {
292 | name = "frontend-task"
293 | description = "Created in ECS Console"
294 | vpc_id = module.frontend_vpc.vpc_attributes.id
295 | }
296 |
297 | resource "aws_vpc_security_group_ingress_rule" "ingress_ipv4" {
298 | security_group_id = aws_security_group.ecs_service_sg.id
299 |
300 | ip_protocol = "tcp"
301 | from_port = 8080
302 | to_port = 8080
303 | cidr_ipv4 = data.aws_vpc.vpc.cidr_block
304 | }
305 |
306 | resource "aws_vpc_security_group_egress_rule" "egress_ipv4" {
307 | security_group_id = aws_security_group.ecs_service_sg.id
308 |
309 | ip_protocol = "-1"
310 | cidr_ipv4 = "0.0.0.0/0"
311 | }
312 |
313 | # ---------- PARAMETERS ----------
314 | # Sharing
315 | module "share_parameters" {
316 | source = "../modules/share_parameter"
317 |
318 | parameters = {
319 | frontend_ava_domain_name = aws_verifiedaccess_endpoint.ava_endpoint.endpoint_domain
320 | frontent_alb_domain_name = aws_lb.lb.dns_name
321 | }
322 | }
323 |
324 | # Retrieving
325 | module "retrieve_parameters" {
326 | source = "../modules/retrieve_parameters"
327 |
328 | parameters = {
329 | r53_profile = var.networking_account
330 | service_network = var.networking_account
331 | }
332 | }
--------------------------------------------------------------------------------
/frontend/outputs.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/outputs.tf ----------
--------------------------------------------------------------------------------
/frontend/providers.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/providers.tf ----------
2 |
3 | terraform {
4 | required_version = ">= 1.3.0"
5 | required_providers {
6 | aws = {
7 | source = "hashicorp/aws"
8 | version = ">= 5.0.0"
9 | }
10 | awscc = {
11 | source = "hashicorp/awscc"
12 | version = "= 0.78.0"
13 | }
14 | }
15 | }
16 |
17 | provider "aws" {
18 | region = var.aws_region
19 | }
20 |
21 | provider "awscc" {
22 | region = var.aws_region
23 | }
--------------------------------------------------------------------------------
/frontend/variables.tf:
--------------------------------------------------------------------------------
1 | # ---------- frontend/providers.tf ----------
2 |
3 | variable "aws_region" {
4 | type = string
5 | description = "AWS Region."
6 | }
7 |
8 | variable "certificate_arn" {
9 | type = string
10 | description = "ACM certificate ARN."
11 | }
12 |
13 | variable "networking_account" {
14 | type = string
15 | description = "Networking Account ID."
16 | }
17 |
18 | variable "frontend_domain_name" {
19 | type = string
20 | description = "Frontend Application domain name."
21 | }
22 |
23 | variable "idc_group_id" {
24 | type = string
25 | description = "Identity Center Group ID (for AWS Verified Access policy)."
26 | }
--------------------------------------------------------------------------------
/modules/retrieve_parameters/main.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/retreive_parameters/main.tf ----------
2 |
3 | # Obtain AWS Region
4 | data "aws_region" "current" {}
5 |
6 | # Retrieving parameters
7 | data "aws_ssm_parameter" "parameter" {
8 | for_each = var.parameters
9 |
10 | name = "arn:aws:ssm:${data.aws_region.current.name}:${each.value}:parameter/${each.key}"
11 | }
--------------------------------------------------------------------------------
/modules/retrieve_parameters/outputs.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/retreive_parameters/outputs.tf ----------
2 |
3 | output "parameter" {
4 | description = "Parameter value."
5 | value = { for k, v in data.aws_ssm_parameter.parameter : k => v.value }
6 | }
--------------------------------------------------------------------------------
/modules/retrieve_parameters/providers.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/retreive_parameters/providers.tf ----------
2 |
3 | terraform {
4 | required_version = ">= 1.3.0"
5 | required_providers {
6 | aws = {
7 | source = "hashicorp/aws"
8 | version = ">= 5.0.0"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/modules/retrieve_parameters/variables.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/retreive_parameters/variables.tf ----------
2 |
3 | variable "parameters" {
4 | description = "List of parameters to retrieve."
5 | type = map(string)
6 | }
--------------------------------------------------------------------------------
/modules/share_parameter/main.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/share_parameter/main.tf ----------
2 |
3 | # Obtaining AWS Organization ID
4 | data "aws_organizations_organization" "org" {}
5 |
6 | # Resource Share
7 | resource "aws_ram_resource_share" "resource_share" {
8 | name = "Networking Account - Parameters"
9 | allow_external_principals = false
10 | }
11 |
12 | resource "aws_ram_principal_association" "principal_association" {
13 | principal = data.aws_organizations_organization.org.arn
14 | resource_share_arn = aws_ram_resource_share.resource_share.arn
15 | }
16 |
17 | # Creation of the SSM Parameter Store Parameters
18 | resource "aws_ssm_parameter" "parameter" {
19 | for_each = var.parameters
20 |
21 | name = each.key
22 | tier = "Advanced"
23 | type = "String"
24 | value = each.value
25 | }
26 |
27 | # Sharing Parameters via RAM
28 | resource "aws_ram_resource_association" "resource_association" {
29 | for_each = var.parameters
30 |
31 | resource_arn = aws_ssm_parameter.parameter[each.key].arn
32 | resource_share_arn = aws_ram_resource_share.resource_share.arn
33 | }
--------------------------------------------------------------------------------
/modules/share_parameter/outputs.tf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/modules/share_parameter/outputs.tf
--------------------------------------------------------------------------------
/modules/share_parameter/providers.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/share_parameter/providers.tf ----------
2 |
3 | terraform {
4 | required_version = ">= 1.3.0"
5 | required_providers {
6 | aws = {
7 | source = "hashicorp/aws"
8 | version = ">= 5.0.0"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/modules/share_parameter/variables.tf:
--------------------------------------------------------------------------------
1 | # ---------- modules/share_parameter/variables.tf ----------
2 |
3 | variable "parameters" {
4 | description = "List of parameters to share."
5 | type = map(string)
6 | }
--------------------------------------------------------------------------------
/network/main.tf:
--------------------------------------------------------------------------------
1 | # ---------- network/main.tf ----------
2 |
3 | data "aws_organizations_organization" "org" {}
4 |
5 | # ---------- AWS RAM SHARE ----------
6 | # Sharing Networking resources with the AWS Organization
7 | resource "aws_ram_resource_share" "resource_share" {
8 | name = "Resource Share - Networking Account"
9 | allow_external_principals = false
10 | }
11 |
12 | resource "aws_ram_principal_association" "principal_association" {
13 | principal = data.aws_organizations_organization.org.arn
14 | resource_share_arn = aws_ram_resource_share.resource_share.arn
15 | }
16 |
17 | # ---------- AMAZON VPC LATTICE SERVICE NETWORK --------------
18 | module "vpclattice_service_network" {
19 | source = "aws-ia/amazon-vpc-lattice-module/aws"
20 | version = "0.1.0"
21 |
22 | service_network = {
23 | name = "central-service-network"
24 | auth_type = "NONE"
25 | }
26 | }
27 |
28 | resource "aws_ram_resource_association" "vpclattice_sn_share" {
29 | resource_arn = module.vpclattice_service_network.service_network.arn
30 | resource_share_arn = aws_ram_resource_share.resource_share.arn
31 | }
32 |
33 | # ---------- AMAZON ROUTE 53 ----------
34 | # Private Hosted Zone
35 | resource "aws_route53_zone" "private_hosted_zone" {
36 | name = var.hosted_zone_name
37 |
38 | vpc {
39 | vpc_id = module.network.central_vpcs.inspection.vpc_attributes.id
40 | }
41 |
42 | lifecycle {
43 | ignore_changes = [vpc]
44 | }
45 |
46 | tags = {
47 | Name = var.hosted_zone_name
48 | }
49 | }
50 |
51 | # Frontend record: Private Hosted zone
52 | resource "aws_route53_record" "frontend_private" {
53 | zone_id = aws_route53_zone.private_hosted_zone.zone_id
54 | name = var.frontend_domain_name
55 | type = "CNAME"
56 | ttl = 300
57 | records = [module.retrieve_parameters.parameter.frontent_alb_domain_name]
58 | }
59 |
60 | # Frontend record: Public Hosted zone
61 | resource "aws_route53_record" "frontend_public" {
62 | zone_id = var.public_hosted_zone_id
63 | name = var.frontend_domain_name
64 | type = "CNAME"
65 | ttl = 300
66 | records = [module.retrieve_parameters.parameter.frontend_ava_domain_name]
67 | }
68 |
69 | # Backend service1 and service2 records
70 | resource "aws_route53_record" "service1" {
71 | zone_id = aws_route53_zone.private_hosted_zone.zone_id
72 | name = var.backend_service1_domain_name
73 | type = "CNAME"
74 | ttl = 300
75 | records = [module.retrieve_parameters.parameter.service1_domain_name]
76 | }
77 |
78 | resource "aws_route53_record" "service2" {
79 | zone_id = aws_route53_zone.private_hosted_zone.zone_id
80 | name = var.backend_service2_domain_name
81 | type = "CNAME"
82 | ttl = 300
83 | records = [module.retrieve_parameters.parameter.service2_domain_name]
84 | }
85 |
86 | # Route 53 Profile
87 | resource "awscc_route53profiles_profile" "r53_profile" {
88 | name = "phz_vpc-lattice"
89 | }
90 |
91 | # PHZ associated to R53 profile
92 | resource "awscc_route53profiles_profile_resource_association" "r53_profile_resource_association" {
93 | name = "phz_vpc-lattice"
94 | profile_id = awscc_route53profiles_profile.r53_profile.id
95 | resource_arn = aws_route53_zone.private_hosted_zone.arn
96 | }
97 |
98 | # Sharing the R53 profile
99 | resource "aws_ram_resource_association" "r53_profile_share" {
100 | resource_arn = awscc_route53profiles_profile.r53_profile.arn
101 | resource_share_arn = aws_ram_resource_share.resource_share.arn
102 | }
103 |
104 | # ---------- PARAMETERS ----------
105 | # Sharing
106 | module "share_parameter_share" {
107 | source = "../modules/share_parameter"
108 |
109 | parameters = {
110 | r53_profile = awscc_route53profiles_profile.r53_profile.id
111 | service_network = module.vpclattice_service_network.service_network.arn
112 | }
113 | }
114 |
115 | # Retrieving
116 | module "retrieve_parameters" {
117 | source = "../modules/retrieve_parameters"
118 |
119 | parameters = {
120 | frontend_ava_domain_name = var.frontend_account
121 | frontent_alb_domain_name = var.frontend_account
122 | service1_domain_name = var.backend_account
123 | service2_domain_name = var.backend_account
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/network/outputs.tf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/moving-to-a-zero-trust-architecture-in-aws/b2847fd65e504017e66b0ad9a9e2fe8d82a47fd9/network/outputs.tf
--------------------------------------------------------------------------------
/network/providers.tf:
--------------------------------------------------------------------------------
1 | # ---------- network/providers.tf ----------
2 |
3 | terraform {
4 | required_version = ">= 1.3.0"
5 | required_providers {
6 | aws = {
7 | source = "hashicorp/aws"
8 | version = ">= 5.0.0"
9 | }
10 | awscc = {
11 | source = "hashicorp/awscc"
12 | version = "= 0.78.0"
13 | }
14 | }
15 | }
16 |
17 | provider "aws" {
18 | region = var.aws_region
19 | }
20 |
21 | provider "awscc" {
22 | region = var.aws_region
23 | }
--------------------------------------------------------------------------------
/network/variables.tf:
--------------------------------------------------------------------------------
1 | # ---------- network/variables.tf ----------
2 |
3 | variable "aws_region" {
4 | type = string
5 | description = "AWS Region."
6 | }
7 |
8 | variable "hosted_zone_name" {
9 | type = string
10 | description = "Private Hosted Zone name."
11 | }
12 |
13 | variable "public_hosted_zone_id" {
14 | type = string
15 | description = "Public Hosted Zone ID."
16 | }
17 |
18 | variable "frontend_account" {
19 | type = string
20 | description = "Frontend Account ID."
21 | }
22 |
23 | variable "backend_account" {
24 | type = string
25 | description = "Backend Account ID."
26 | }
27 |
28 | variable "frontend_domain_name" {
29 | type = string
30 | description = "Frontend App - Domain Name."
31 | }
32 |
33 | variable "backend_service1_domain_name" {
34 | type = string
35 | description = "Backend App1 - Domain Name."
36 | }
37 |
38 | variable "backend_service2_domain_name" {
39 | type = string
40 | description = "Backend App2 - Domain Name."
41 | }
42 |
--------------------------------------------------------------------------------