├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── .nvmrc
├── .prettierrc.js
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── docs
└── DOMAIN_MANAGER.md
├── images
└── serverless-policy-generator.png
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
├── android-chrome-192x192.png
├── apple-touch-icon.png
├── browserconfig.xml
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── index.html
├── manifest.json
├── mstile-150x150.png
└── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── components
├── Form.js
└── ResourcesArray.js
├── generator
├── __test__
│ └── index.test.js
└── index.js
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
├── setupTests.js
└── validation
└── index.js
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deployment
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | node-version: [16.x]
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v3
17 | with:
18 | ref: master
19 |
20 | - name: Set up Node.js
21 | uses: actions/setup-node@v2
22 | with:
23 | node-version: "16"
24 |
25 | - name: Install Packages
26 | run: npm install
27 |
28 | - name: Run Tests
29 | run: npm run test
30 |
31 | - name: Build page
32 | run: npm run build
33 |
34 | - name: Deploy to gh-pages
35 | uses: peaceiris/actions-gh-pages@v4
36 | with:
37 | github_token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
38 | publish_dir: ./build
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16.16.0
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | singleQuote: true,
4 | };
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Serverless Permission Policy Generator
2 |
3 | ## Issue contributions
4 |
5 | ### Did you find a bug ?
6 |
7 | Open a [new issue](https://github.com/Open-SL/serverless-permission-generator/issues/new)
8 | and be sure to include a title and clear description, as much relevant information
9 | as possible, and a code sample or a test case demonstrating the expected behavior
10 | that is not occurring.
11 |
12 | Discussions can be done via github issues.
13 |
14 | ## Code contributions
15 |
16 | ### Fork
17 |
18 | Fork the project [on GitHub](https://github.com/Open-SL/serverless-permission-generator/)
19 | and check out your copy locally.
20 |
21 | ```
22 | git clone git@github.com:username/serverless-permission-generator.git
23 | cd serverless-permission-generator
24 | git remote add upstream git@github.com:Open-SL/serverless-permission-generator.git
25 | ```
26 |
27 | ### Branch
28 |
29 | Create a feature branch and start hacking:
30 |
31 | ```
32 | git checkout -b my-contrib-branch
33 | ```
34 |
35 | ### Commit messages
36 |
37 | Writing good commit logs is important. A commit log should describe what
38 | changed and why. Follow these guidelines when writing one:
39 |
40 | 1. The first line should be 50 characters or less and contain a short
41 | description of the change.
42 | 2. Keep the second line blank.
43 | 3. Wrap all other lines at 72 columns.
44 |
45 | Example of commit message:
46 |
47 | ```
48 | fix a bug with download url.
49 |
50 | The download url was not using https.
51 | Body of commit message is a few lines of text, explaining things
52 | in more detail, possibly giving some background about the issue
53 | being fixed, etc. etc.
54 |
55 | The body of the commit message can be several paragraphs, and
56 | please do proper word-wrap and keep columns shorter than about
57 | 72 characters or so. That way `git log` will show things
58 | nicely even when it is indented.
59 | ```
60 |
61 | ### Rebase to keep updated.
62 |
63 | Use `git rebase` to sync your work from time to time.
64 |
65 | ```
66 | git fetch upstream
67 | git rebase upstream/master
68 | ```
69 |
70 | ### Development cycle
71 |
72 | Bug fixes and features should come with tests.
73 | The tests are on `test` directory.
74 |
75 | ```
76 | npm test
77 | ```
78 |
79 | ### Push
80 |
81 | ```
82 | git push origin my-contrib-branch
83 | ```
84 |
85 | Go to https://github.com/username/serverless-permission-generator and select your feature branch.
86 | Click the 'Pull Request' button and fill out the form.
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Prerequisites
2 |
3 | * [ ] Are you running the latest version?
4 | * [ ] Did you check the debugging guide?
5 | * [ ] Did you check this issue in Issue section?
6 | * [ ] Are you reporting to the correct repository?
7 | * [ ] Did you perform a cursory search?
8 |
9 | For more information, see the [CONTRIBUTING](CONTRIBUTING.md) guide.
10 |
11 | ### Description
12 |
13 | [Description of the bug or feature]
14 |
15 | ### Steps to Reproduce
16 |
17 | 1. [First Step]
18 | 2. [Second Step]
19 | 3. [and so on...]
20 |
21 | **Expected behavior:** [What you expected to happen]
22 |
23 | **Actual behavior:** [What actually happened]
24 |
25 | ### Versions
26 |
27 | Please add the browser details with its version
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 OPEN-SL
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Serverless Permission Policy Generator
2 |
3 | An Online Application to generate AWS IAM permissions required for deploying a Serverless Framework stack.
4 |
5 |
6 |
7 | ## Introduction
8 |
9 | This application will provide you a user friendly UI to collect required resources details and a generator to build the relevant IAM policy for the collected information.
10 |
11 | Visit the application from [here](https://open-sl.github.io/serverless-permission-generator/)
12 |
13 | ## Available Features
14 |
15 | 1. Basic permissions required for serverless application to be deployed
16 | 2. S3 buckets created from serverless yaml
17 | 3. SNS topics
18 | 4. SQS
19 | 5. Api Gateway if required
20 | 6. Security group and VPC configuration related permission to connect to VPN
21 | 7. Kinesis
22 | 8. DynamoDB
23 | 9. ALB listener and target group attachment permission required for lambdas exposed through ALBs.
24 |
25 | ## How to use
26 |
27 | 1. Enter project details and AWS account details
28 | 2. Input required AWS resources details
29 | 3. Click generate button
30 | 4. Check the generated JSON
31 | 5. Click copy button to copy values to clipboard
32 | 6. Paste values in your IAM role permission policy
33 |
34 | ## Development Guide
35 |
36 | ### Prerequisites
37 | - git
38 | - npm
39 | - nvm (optional)
40 | - node v16
41 |
42 | clone the application and install dependencies using
43 |
44 | ```
45 | nvm use
46 | npm install
47 | ```
48 |
49 | run
50 |
51 | ```
52 | npm start
53 | ```
54 |
55 | to deploy application in localhost.
56 |
57 | ## Contributing
58 |
59 | - We would greatly appreciate any [contribution](CONTRIBUTING.md) you make.
60 | - If you have ideas for more functionality or recipes that should be on this project, l[let us know](https://github.com/Open-SL/serverless-permission-generator/issues).
61 |
62 | ## Project Maintainers
63 |
64 | - Nadun Indunil - [nadunindunil](https://github.com/nadunindunil)
65 | - Sachintha Sandeepani - [sachintha97](https://github.com/sachintha97)
66 |
67 | ## License
68 | Serverless Permission Policy Generator is under the MIT license. See the [License](LICENSE) for more information.
69 |
--------------------------------------------------------------------------------
/docs/DOMAIN_MANAGER.md:
--------------------------------------------------------------------------------
1 | # Serverless Domain Manager
2 |
3 | The [serverless domain manager](https://www.npmjs.com/package/serverless-domain-manager) is a plugin for the serverless.com system.
4 |
5 | The IAM roles required for deploying when using the serverless-domain manager:
6 |
7 | ```
8 | acm:ListCertificates *
9 | apigateway:GET /domainnames/*
10 | apigateway:GET /domainnames/*/basepathmappings
11 | apigateway:DELETE /domainnames/*
12 | apigateway:POST /domainnames
13 | apigateway:POST /domainnames/*/basepathmappings
14 | apigateway:PATCH /domainnames/*/basepathmapping
15 | cloudformation:GET *
16 | cloudfront:UpdateDistribution *
17 | route53:ListHostedZones *
18 | route53:ChangeResourceRecordSets hostedzone/{HostedZoneId}
19 | route53:GetHostedZone *
20 | route53:ListResourceRecordSets *
21 | iam:CreateServiceLinkedRole arn:aws:iam::${AWS::AccountId}: role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway
22 | ```
23 |
24 | NOTE: there was an [original request](https://github.com/Open-SL/serverless-permission-generator/issues/19) to separate Route53 concepts in the builder as a sub-option of the domain manager selector.
25 |
26 | NOTE: Currently providing a resource of `hostedzone/*` instead of `hostedzone/{HostedZoneId}`
--------------------------------------------------------------------------------
/images/serverless-policy-generator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/images/serverless-policy-generator.png
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src"
4 | },
5 | "include": ["src"]
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "serverless-project",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "/serverless-permission-generator",
6 | "dependencies": {
7 | "@material-ui/core": "4.12.3",
8 | "@material-ui/icons": "^4.9.1",
9 | "fontsource-roboto": "^2.1.4",
10 | "formik": "^2.1.4",
11 | "formik-material-ui": "^3.0.1",
12 | "react": "^17.0.0",
13 | "react-dom": "^17.0.0",
14 | "react-json-view": "^1.21.3",
15 | "react-scripts": "^5.0.1",
16 | "typeface-metropolis": "0.0.74"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test --verbose --coverage --watchAll=false",
22 | "test:watch": "react-scripts test src",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "devDependencies": {
44 | "@testing-library/jest-dom": "5.11.4",
45 | "@testing-library/react": "11.0.4",
46 | "@testing-library/user-event": "12.1.7",
47 | "jest-environment-jsdom-sixteen": "1.0.3"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
30 | Serverless Permission Policy Generator
31 |
32 |
33 |
34 |
35 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Policy Generator",
3 | "name": "Serverless Permission Policy Generator",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-SL/serverless-permission-generator/d05e0e575195afa3e5595a01e6d07ccf87118043/src/App.css
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Typography, Grid, Box, Button } from '@material-ui/core';
3 | import { makeStyles, ThemeProvider, createTheme } from '@material-ui/core/styles';
4 | import ReactJson from 'react-json-view';
5 | import FileCopyIcon from '@material-ui/icons/FileCopy';
6 | import Form from './components/Form';
7 |
8 | const fontFamilyMetropolis = {
9 | fontFamily: [
10 | 'Metropolis',
11 | 'Arial',
12 | 'sans-serif',
13 | '"Apple Color Emoji"',
14 | '"Segoe UI Emoji"',
15 | '"Segoe UI Symbol"',
16 | ].join(','),
17 | letterSpacing: '0.015rem',
18 | };
19 |
20 | const theme = createTheme({
21 | typography: {
22 | h1: {
23 | ...fontFamilyMetropolis,
24 | },
25 | h2: {
26 | ...fontFamilyMetropolis,
27 | },
28 | h3: {
29 | ...fontFamilyMetropolis,
30 | },
31 | h4: {
32 | ...fontFamilyMetropolis,
33 | },
34 | h5: {
35 | ...fontFamilyMetropolis,
36 | },
37 | h6: {
38 | ...fontFamilyMetropolis,
39 | },
40 | subtitle1: {
41 | ...fontFamilyMetropolis,
42 | },
43 | button: {
44 | ...fontFamilyMetropolis,
45 | },
46 | },
47 | });
48 |
49 | const useStyles = makeStyles({
50 | subheading: {
51 | color: '#fd5750',
52 | fontWeight: '800',
53 | },
54 | button: {
55 | borderRadius: 5,
56 | color: '#FFF',
57 | backgroundColor: '#fd5750',
58 | '&:hover': {
59 | backgroundColor: '#FD8984',
60 | },
61 | },
62 | fieldTitle: {
63 | textAlign: 'right',
64 | fontWeight: '600',
65 | },
66 | fieldTitle2: {
67 | fontWeight: '600',
68 | },
69 | header: {
70 | backgroundColor: '#282c34',
71 | color: 'white',
72 | textAlign: 'center',
73 | },
74 | });
75 |
76 | function App() {
77 | const classes = useStyles();
78 | const [policy, setPolicy] = useState(null);
79 |
80 | const copyHandler = () => {
81 | navigator.clipboard.writeText(JSON.stringify(policy, null, 2));
82 | };
83 |
84 | return (
85 |
86 |
87 |
88 |
89 |
90 | Serverless Permission Policy Generator
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Generated Policy
105 |
106 |
107 |
108 | }
113 | onClick={copyHandler}
114 | >
115 | Copy to Clipboard
116 |
117 |
118 |
119 |
120 | {policy && }
121 |
122 | {policy && (
123 | }
128 | onClick={copyHandler}
129 | >
130 | Copy to Clipboard
131 |
132 | )}
133 |
134 |
135 |
136 |
137 |
138 |
139 | );
140 | }
141 |
142 | export default App;
143 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, waitFor, getNodeText } from '@testing-library/react';
3 | import userEvent from '@testing-library/user-event';
4 | import App from './App';
5 |
6 | test('display the generated policy for the given project data', async () => {
7 | const { container } = render();
8 |
9 | userEvent.type(container.querySelector('input[name="projectName"]'), 'hacktoberfest');
10 | userEvent.type(container.querySelector('input[name="accountId"]'), '123457891234');
11 | userEvent.type(container.querySelector('input[name="region"]'), 'us-east-1');
12 | userEvent.type(container.querySelector('input[name="stage"]'), 'dev');
13 | userEvent.click(container.querySelector('button[type="button"]'));
14 |
15 | await waitFor(() => {
16 | expect(getNodeText(container.querySelector('.string-value'))).toEqual('"2012-10-17"');
17 | });
18 | });
19 |
20 | test('display error for invalid account ID', async () => {
21 | const { container, getByText } = render();
22 |
23 | userEvent.type(container.querySelector('input[name="projectName"]'), 'hacktoberfest');
24 | userEvent.type(container.querySelector('input[name="accountId"]'), 'boo');
25 | userEvent.type(container.querySelector('input[name="region"]'), 'us-east-1');
26 | userEvent.type(container.querySelector('input[name="stage"]'), 'dev');
27 | userEvent.click(container.querySelector('button[type="button"]'));
28 |
29 | await waitFor(() => {
30 | expect(getByText('Invalid ID')).toBeInTheDocument();
31 | expect(container.querySelector('.string-value')).not.toBeInTheDocument();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/components/Form.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Typography, Grid, Button, Box, makeStyles, FormHelperText } from '@material-ui/core';
3 | import BuildIcon from '@material-ui/icons/Build';
4 | import { Formik, Form as FormikForm, Field } from 'formik';
5 | import { TextField, Checkbox } from 'formik-material-ui';
6 | import ResourcesArray from './ResourcesArray';
7 | import validation from 'validation';
8 | import generator from 'generator';
9 |
10 | const useStyles = makeStyles({
11 | subheading: {
12 | color: '#fd5750',
13 | fontWeight: '800',
14 | },
15 | button: {
16 | borderRadius: 5,
17 | color: '#FFF',
18 | backgroundColor: '#fd5750',
19 | '&:hover': {
20 | backgroundColor: '#FD8984',
21 | },
22 | },
23 | fieldTitle: {
24 | textAlign: 'right',
25 | fontWeight: '600',
26 | },
27 | fieldTitle2: {
28 | fontWeight: '600',
29 | },
30 | helperText: {
31 | color: '#f02e2e',
32 | },
33 | });
34 |
35 | export default function Form({ setPolicy }) {
36 | const classes = useStyles();
37 |
38 | return (
39 | <>
40 | {
69 | setSubmitting(false);
70 | setPolicy(generator(values));
71 | }}
72 | >
73 | {({ submitForm, isSubmitting, values, errors }) => (
74 |
75 |
76 |
77 | Project Meta Data
78 |
79 |
80 |
81 |
82 |
83 | Serverless Project Name
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | AWS Account ID
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | AWS Region
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | Application Stage
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | {/* Second Part */}
126 |
127 |
128 | Project Permission
129 |
130 |
131 |
132 |
133 |
134 | Amazon API Gateway
135 |
136 |
137 |
138 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | EC2 (Elastic Compute Cloud) Security Groups
151 |
152 |
153 |
154 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | Parameter Store
167 |
168 |
169 |
170 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | Enable event source mapping
183 |
184 |
185 |
186 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | S3 (Simple Static Storage)
199 |
200 |
201 |
202 |
208 |
209 |
210 |
211 |
217 |
218 |
219 |
220 | {errors.isS3Required && (
221 | <>
222 | {errors.isS3Required}
223 | >
224 | )}
225 |
226 |
227 |
228 |
229 |
230 |
231 | DynamoDB
232 |
233 |
234 |
235 |
241 |
242 |
243 |
244 |
250 |
251 |
252 |
253 |
254 | {errors.isDynamoDbRequired && (
255 | {errors.isDynamoDbRequired}
256 | )}
257 |
258 |
259 |
260 |
261 |
262 |
263 | SNS (Simple Notification Service)
264 |
265 |
266 |
267 |
273 |
274 |
275 |
276 |
282 |
283 |
284 |
285 |
286 | {errors.isSnsRequired && (
287 | {errors.isSnsRequired}
288 | )}
289 |
290 |
291 |
292 |
293 |
294 |
295 | ALB (Application Load Balancer)
296 |
297 |
298 |
299 |
305 |
306 |
307 |
308 |
314 |
315 |
316 |
317 | {errors.isAlbRequired && (
318 | {errors.isAlbRequired}
319 | )}
320 |
321 |
322 |
323 |
324 |
325 |
326 | Amazon Kinesis
327 |
328 |
329 |
330 |
336 |
337 |
338 |
339 |
345 |
346 |
347 |
348 | {errors.isKinesisRequired && (
349 | {errors.isKinesisRequired}
350 | )}
351 |
352 |
353 |
354 |
355 |
356 |
357 | SQS (Simple Queue Service)
358 |
359 |
360 |
361 |
367 |
368 |
369 |
370 |
376 |
377 |
378 |
379 | {errors.isSqsRequired && (
380 | {errors.isSqsRequired}
381 | )}
382 |
383 |
384 |
385 |
386 |
387 |
388 | Serverless Domain Manager
389 |
390 |
391 |
392 |
398 |
399 |
400 | {values.isDomainManagerRequired && <>
401 |
402 |
403 | Allow Route53
404 |
405 |
406 |
407 |
413 |
414 |
415 |
416 | {errors.isDomainManagerRoute53Required && (
417 | {errors.isDomainManagerRoute53Required}
418 | )}
419 | >
420 | }
421 |
422 |
423 |
424 |
425 |
426 | Serverless Warm Up Plugin
427 |
428 |
429 |
430 |
436 |
437 |
438 |
439 |
445 |
446 |
447 |
448 | {errors.isWarmUpPluginRequired && (
449 | <>
450 | {errors.isWarmUpPluginRequired}
451 | >
452 | )}
453 |
454 |
455 |
456 |
457 |
458 | }
465 | >
466 | Generate
467 |
468 |
469 |
470 |
471 |
472 | )}
473 |
474 | >
475 | );
476 | }
477 |
--------------------------------------------------------------------------------
/src/components/ResourcesArray.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid, Button, makeStyles, IconButton, Box } from '@material-ui/core';
3 | import { Field, FieldArray } from 'formik';
4 | import { TextField } from 'formik-material-ui';
5 | import RemoveIcon from '@material-ui/icons/Remove';
6 |
7 | const useStyles = makeStyles({
8 | button: {
9 | borderRadius: 5,
10 | color: '#FFF',
11 | backgroundColor: '#fd5750',
12 | '&:hover': {
13 | backgroundColor: '#FD8984',
14 | },
15 | },
16 | });
17 |
18 | export default function ResourcesArray({ values, resourceName, arrayName, resourceAddLabel }) {
19 | const classes = useStyles();
20 |
21 | return (
22 | <>
23 | {values[resourceName] ? (
24 | (
27 | <>
28 | {values[arrayName] && values[arrayName].length > 0 ? (
29 | <>
30 | {values[arrayName].map((resource, index) => (
31 |
32 |
33 |
34 |
35 |
36 |
37 | arrayHelpers.remove(index)} // remove a resource from the list
42 | >
43 |
44 |
45 |
46 |
47 |
48 | ))}
49 |
50 |
58 |
59 | >
60 | ) : (
61 |
65 | )}
66 | >
67 | )}
68 | />
69 | ) : null}
70 | >
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/generator/__test__/index.test.js:
--------------------------------------------------------------------------------
1 | import generator, {
2 | s3Generator,
3 | kinesisGenerator,
4 | sqsGenerator,
5 | albGenerator,
6 | sgGenerator,
7 | dynamoDBGenerator,
8 | snsGenerator,
9 | apiGWGenerator,
10 | ssmGenerator,
11 | warmupPluginGenerator,
12 | domainManagerGenerator,
13 | esmGenerator,
14 | } from '..';
15 |
16 | test('generating minimum policy', () => {
17 | const region = 'us-east-1';
18 | const projectName = 'hacktoberfest';
19 | const stage = 'test';
20 | const accountId = '123456789098';
21 |
22 | expect(generator({ region, projectName, stage, accountId })).toEqual({
23 | Version: '2012-10-17',
24 | Statement: [
25 | {
26 | Effect: 'Allow',
27 | Action: ['cloudformation:List*', 'cloudformation:Get*', 'cloudformation:ValidateTemplate'],
28 | Resource: ['*'],
29 | },
30 | {
31 | Effect: 'Allow',
32 | Action: [
33 | 'cloudformation:CreateStack',
34 | 'cloudformation:CreateUploadBucket',
35 | 'cloudformation:DeleteStack',
36 | 'cloudformation:Describe*',
37 | 'cloudformation:UpdateStack',
38 | ],
39 | Resource: [`arn:aws:cloudformation:${region}:${accountId}:stack/${projectName}-${stage}/*`],
40 | },
41 | {
42 | Effect: 'Allow',
43 | Action: ['lambda:Get*', 'lambda:List*', 'lambda:CreateFunction'],
44 | Resource: ['*'],
45 | },
46 | {
47 | Effect: 'Allow',
48 | Action: [
49 | 's3:GetBucketLocation',
50 | 's3:CreateBucket',
51 | 's3:DeleteBucket',
52 | 's3:ListBucket',
53 | 's3:GetBucketPolicy',
54 | 's3:PutBucketPolicy',
55 | 's3:ListBucketVersions',
56 | 's3:PutAccelerateConfiguration',
57 | 's3:GetEncryptionConfiguration',
58 | 's3:PutEncryptionConfiguration',
59 | 's3:DeleteBucketPolicy',
60 | ],
61 | Resource: [`arn:aws:s3:::${projectName}*serverlessdeploy*`],
62 | },
63 | {
64 | Effect: 'Allow',
65 | Action: ['s3:PutObject', 's3:GetObject', 's3:DeleteObject'],
66 | Resource: [`arn:aws:s3:::${projectName}*serverlessdeploy*`],
67 | },
68 | {
69 | Effect: 'Allow',
70 | Action: [
71 | 'lambda:AddPermission',
72 | 'lambda:CreateAlias',
73 | 'lambda:DeleteFunction',
74 | 'lambda:InvokeFunction',
75 | 'lambda:PublishVersion',
76 | 'lambda:RemovePermission',
77 | 'lambda:Update*',
78 | ],
79 | Resource: [`arn:aws:lambda:${region}:${accountId}:function:${projectName}-${stage}-*`],
80 | },
81 |
82 | {
83 | Effect: 'Allow',
84 | Action: ['cloudwatch:GetMetricStatistics'],
85 | Resource: ['*'],
86 | },
87 | {
88 | Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:DeleteLogGroup'],
89 | Resource: [`arn:aws:logs:${region}:${accountId}:*`],
90 | Effect: 'Allow',
91 | },
92 | {
93 | Action: ['logs:PutLogEvents'],
94 | Resource: [`arn:aws:logs:${region}:${accountId}:*`],
95 | Effect: 'Allow',
96 | },
97 | {
98 | Effect: 'Allow',
99 | Action: ['logs:DescribeLogStreams', 'logs:DescribeLogGroups', 'logs:FilterLogEvents'],
100 | Resource: ['*'],
101 | },
102 | {
103 | Effect: 'Allow',
104 | Action: ['events:Put*', 'events:Remove*', 'events:Delete*'],
105 | Resource: [`arn:aws:events:${region}:${accountId}:rule/${projectName}-${stage}-${region}`],
106 | },
107 | {
108 | Effect: 'Allow',
109 | Action: ['events:DescribeRule'],
110 | Resource: [`arn:aws:events:${region}:${accountId}:rule/${projectName}-${stage}-*`],
111 | },
112 | {
113 | Effect: 'Allow',
114 | Action: ['iam:PassRole'],
115 | Resource: [`arn:aws:iam::${accountId}:role/*`],
116 | },
117 | {
118 | Effect: 'Allow',
119 | Action: ['iam:GetRole', 'iam:CreateRole', 'iam:PutRolePolicy', 'iam:DeleteRolePolicy', 'iam:DeleteRole'],
120 | Resource: [`arn:aws:iam::${accountId}:role/${projectName}-${stage}-${region}-lambdaRole`],
121 | },
122 | ],
123 | });
124 | });
125 |
126 | test('generating S3 policy ', () => {
127 | expect(s3Generator(['testbucket'])).toEqual({
128 | Effect: 'Allow',
129 | Action: [
130 | 's3:GetBucketLocation',
131 | 's3:CreateBucket',
132 | 's3:DeleteBucket',
133 | 's3:ListBucket',
134 | 's3:GetBucketPolicy',
135 | 's3:PutBucketPolicy',
136 | 's3:ListBucketVersions',
137 | 's3:PutAccelerateConfiguration',
138 | 's3:GetEncryptionConfiguration',
139 | 's3:PutEncryptionConfiguration',
140 | 's3:DeleteBucketPolicy',
141 | ],
142 | Resource: [`arn:aws:s3:::testbucket`],
143 | });
144 | });
145 |
146 | test('generating kinesis policy ', () => {
147 | expect(kinesisGenerator(['teststream'])).toEqual({
148 | Effect: 'Allow',
149 | Action: 'kinesis:*',
150 | Resource: [`arn:aws:kinesis:*:*:stream/teststream`],
151 | });
152 | });
153 |
154 | test('generating sqs policy ', () => {
155 | expect(sqsGenerator(['testsqs'], 'region1', '12345')).toEqual([
156 | {
157 | Effect: 'Allow',
158 | Action: 'sqs:*',
159 | Resource: [`arn:aws:sqs:*:testsqs`],
160 | },
161 | {
162 | Effect: 'Allow',
163 | Action: 'logs:PutSubscriptionFilter',
164 | Resource: [
165 | `arn:aws:logs:region1:12345:log-group:/aws/lambda/*`,
166 | `arn:aws:logs:region1:12345:log-group:/aws/api-gateway/*`,
167 | ],
168 | },
169 | ]);
170 | });
171 |
172 | test('generating albGenerator policy ', () => {
173 | expect(albGenerator(['arn'])).toEqual({
174 | Effect: 'Allow',
175 | Action: [
176 | 'elasticloadbalancing:RegisterTargets',
177 | 'elasticloadbalancing:DescribeRules',
178 | 'elasticloadbalancing:DeleteRule',
179 | 'elasticloadbalancing:CreateTargetGroup',
180 | 'elasticloadbalancing:ModifyTargetGroup',
181 | 'elasticloadbalancing:ModifyTargetGroupAttributes',
182 | 'elasticloadbalancing:ModifyRule',
183 | 'elasticloadbalancing:ModifyListener',
184 | 'elasticloadbalancing:AddTags',
185 | 'elasticloadbalancing:DeleteTargetGroup',
186 | 'elasticloadbalancing:DescribeTargetGroups',
187 | 'elasticloadbalancing:DescribeTargetHealth',
188 | 'elasticloadbalancing:CreateRule',
189 | ],
190 | Resources: ['arn'],
191 | });
192 | });
193 |
194 | test('generating sg policy ', () => {
195 | expect(sgGenerator('testsqs')).toEqual({
196 | Effect: 'Allow',
197 | Action: [
198 | 'ec2:AuthorizeSecurityGroupEgress',
199 | 'ec2:AuthorizeSecurityGroupIngress',
200 | 'ec2:CreateSecurityGroup',
201 | 'ec2:DeleteSecurityGroup',
202 | 'ec2:DescribeSecurityGroups',
203 | 'ec2:DescribeNetworkInterfaces',
204 | 'ec2:DescribeSubnets',
205 | 'ec2:DescribeVpcs',
206 | 'ec2:DescribeDhcpOptions',
207 | 'ec2:RevokeSecurityGroupEgress',
208 | 'ec2:RevokeSecurityGroupIngress',
209 | 'ec2:CreateNetworkInterfacePermission',
210 | 'ec2:CreateNetworkInterface',
211 | 'ec2:DeleteNetworkInterfacePermission',
212 | 'ec2:DeleteNetworkInterface',
213 | 'ec2:createTags',
214 | 'ec2:deleteTags',
215 | ],
216 | Resource: ['*'],
217 | });
218 | });
219 |
220 | test('generating dynamoDB policy', () => {
221 | expect(dynamoDBGenerator(['db'], '12345')).toEqual({
222 | Effect: 'Allow',
223 | Action: ['dynamodb:*'],
224 | Resource: [`arn:aws:dynamodb:*:12345:table/db`],
225 | });
226 | });
227 |
228 | test('generating sns policy ', () => {
229 | expect(snsGenerator(['topic1'], 'region', 'account')).toEqual({
230 | Effect: 'Allow',
231 | Action: ['sns:*'],
232 | Resource: [`arn:aws:sns:region:account:topic1`],
233 | });
234 | });
235 |
236 | test('generating apiGW policy ', () => {
237 | expect(apiGWGenerator()).toEqual({
238 | Effect: 'Allow',
239 | Action: ['apigateway:GET', 'apigateway:POST', 'apigateway:PUT', 'apigateway:DELETE', 'apigateway:PATCH'],
240 | Resource: [
241 | 'arn:aws:apigateway:*::/apis*',
242 | 'arn:aws:apigateway:*::/restapis*',
243 | 'arn:aws:apigateway:*::/apikeys*',
244 | 'arn:aws:apigateway:*::/usageplans*',
245 | ],
246 | });
247 | });
248 |
249 | test('generating ssm policy ', () => {
250 | expect(ssmGenerator()).toEqual({
251 | Effect: 'Allow',
252 | Action: [
253 | 'ssm:DescribeParameters',
254 | 'ssm:GetParameter',
255 | 'ssm:GetParameters',
256 | 'ssm:GetParametersByPath',
257 | 'kms:Decrypt',
258 | ],
259 | Resource: ['*'],
260 | });
261 | });
262 |
263 | test('generating warm up plugin policy ', () => {
264 | expect(warmupPluginGenerator('region', 'account', ['rule1'])).toEqual({
265 | Effect: 'Allow',
266 | Action: ['events:DescribeRule', 'events:PutRule', 'events:DeleteRule', 'events:PutTargets', 'events:RemoveTargets'],
267 | Resource: [`arn:aws:events:region:account:rule/rule1`],
268 | });
269 | });
270 |
271 | test('domainManagerGenerator policy - route53 (false) ', () => {
272 | expect(domainManagerGenerator('region', 'account', false)).toEqual([
273 | {
274 | Effect: 'Allow',
275 | Action: ['acm:ListCertificates'],
276 | Resource: ['*'],
277 | },
278 | {
279 | Effect: 'Allow',
280 | Action: ['apigateway:GET', 'apigateway:DELETE'],
281 | Resource: [`arn:aws:apigateway:region:account:/domainnames/*`],
282 | },
283 | {
284 | Effect: 'Allow',
285 | Action: ['apigateway:GET', 'apigateway:POST'],
286 | Resource: [`arn:aws:apigateway:region:account:/domainnames/*/basepathmappings`],
287 | },
288 | {
289 | Effect: 'Allow',
290 | Action: ['apigateway:PATCH'],
291 | Resource: [`arn:aws:apigateway:region:account:/domainnames/*/basepathmapping`],
292 | },
293 | {
294 | Effect: 'Allow',
295 | Action: ['apigateway:POST'],
296 | Resource: [`arn:aws:apigateway:region:account:/domainnames`],
297 | },
298 | {
299 | Effect: 'Allow',
300 | Action: ['cloudformation:GET'],
301 | Resource: ['*'],
302 | },
303 | {
304 | Effect: 'Allow',
305 | Action: ['cloudfront:UpdateDistribution'],
306 | Resource: ['*'],
307 | },
308 | {
309 | Effect: 'Allow',
310 | Action: ['iam:CreateServiceLinkedRole'],
311 | Resource: [`arn:aws:iam:::role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway`],
312 | },
313 | ]);
314 | });
315 |
316 | test('domainManagerGenerator policy - route53 (true) ', () => {
317 | expect(domainManagerGenerator('region', 'account', true)).toEqual([
318 | {
319 | Effect: 'Allow',
320 | Action: ['acm:ListCertificates'],
321 | Resource: ['*'],
322 | },
323 | {
324 | Effect: 'Allow',
325 | Action: ['apigateway:GET', 'apigateway:DELETE'],
326 | Resource: [`arn:aws:apigateway:region:account:/domainnames/*`],
327 | },
328 | {
329 | Effect: 'Allow',
330 | Action: ['apigateway:GET', 'apigateway:POST'],
331 | Resource: [`arn:aws:apigateway:region:account:/domainnames/*/basepathmappings`],
332 | },
333 | {
334 | Effect: 'Allow',
335 | Action: ['apigateway:PATCH'],
336 | Resource: [`arn:aws:apigateway:region:account:/domainnames/*/basepathmapping`],
337 | },
338 | {
339 | Effect: 'Allow',
340 | Action: ['apigateway:POST'],
341 | Resource: [`arn:aws:apigateway:region:account:/domainnames`],
342 | },
343 | {
344 | Effect: 'Allow',
345 | Action: ['cloudformation:GET'],
346 | Resource: ['*'],
347 | },
348 | {
349 | Effect: 'Allow',
350 | Action: ['cloudfront:UpdateDistribution'],
351 | Resource: ['*'],
352 | },
353 | {
354 | Effect: 'Allow',
355 | Action: ['route53:ListHostedZones', 'route53:GetHostedZone', 'route53:ListResourceRecordSets'],
356 | Resource: ['*'],
357 | },
358 | {
359 | Effect: 'Allow',
360 | Action: ['route53:ChangeResourceRecordSets'],
361 | Resource: [`arn:aws:route53:::hostedzone/*`],
362 | },
363 | {
364 | Effect: 'Allow',
365 | Action: ['iam:CreateServiceLinkedRole'],
366 | Resource: [`arn:aws:iam:::role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway`],
367 | },
368 | ]);
369 | });
370 |
371 | test('generating esm policy ', () => {
372 | expect(esmGenerator()).toEqual({
373 | Effect: 'Allow',
374 | Action: 'lambda:CreateEventSourceMapping',
375 | Resource: '*',
376 | });
377 | });
378 |
--------------------------------------------------------------------------------
/src/generator/index.js:
--------------------------------------------------------------------------------
1 | export const s3Generator = (bucketNames) => {
2 | return {
3 | Effect: 'Allow',
4 | Action: [
5 | 's3:GetBucketLocation',
6 | 's3:CreateBucket',
7 | 's3:DeleteBucket',
8 | 's3:ListBucket',
9 | 's3:GetBucketPolicy',
10 | 's3:PutBucketPolicy',
11 | 's3:ListBucketVersions',
12 | 's3:PutAccelerateConfiguration',
13 | 's3:GetEncryptionConfiguration',
14 | 's3:PutEncryptionConfiguration',
15 | 's3:DeleteBucketPolicy',
16 | ],
17 | Resource: bucketNames.map((bucketName) => `arn:aws:s3:::${bucketName}`),
18 | };
19 | };
20 |
21 | export const kinesisGenerator = (streams) => {
22 | return {
23 | Effect: 'Allow',
24 | Action: 'kinesis:*',
25 | Resource: streams.map((stream) => `arn:aws:kinesis:*:*:stream/${stream}`),
26 | };
27 | };
28 |
29 | export const sqsGenerator = (queueArray, region, account) => {
30 | return [
31 | {
32 | Effect: 'Allow',
33 | Action: 'sqs:*',
34 | Resource: queueArray.map((queue) => `arn:aws:sqs:*:${queue}`),
35 | },
36 | {
37 | Effect: 'Allow',
38 | Action: 'logs:PutSubscriptionFilter',
39 | Resource: [
40 | `arn:aws:logs:${region}:${account}:log-group:/aws/lambda/*`,
41 | `arn:aws:logs:${region}:${account}:log-group:/aws/api-gateway/*`,
42 | ],
43 | },
44 | ];
45 | };
46 |
47 | // when attaching ALBs instead of API gateways
48 | export const albGenerator = (albs) => {
49 | return {
50 | Effect: 'Allow',
51 | Action: [
52 | 'elasticloadbalancing:RegisterTargets',
53 | 'elasticloadbalancing:DescribeRules',
54 | 'elasticloadbalancing:DeleteRule',
55 | 'elasticloadbalancing:CreateTargetGroup',
56 | 'elasticloadbalancing:ModifyTargetGroup',
57 | 'elasticloadbalancing:ModifyTargetGroupAttributes',
58 | 'elasticloadbalancing:ModifyRule',
59 | 'elasticloadbalancing:ModifyListener',
60 | 'elasticloadbalancing:AddTags',
61 | 'elasticloadbalancing:DeleteTargetGroup',
62 | 'elasticloadbalancing:DescribeTargetGroups',
63 | 'elasticloadbalancing:DescribeTargetHealth',
64 | 'elasticloadbalancing:CreateRule',
65 | ],
66 | // TODO: identify last code in Listener ARN eg `arn:aws:elasticloadbalancing:${region}:${accountId}:listener/app/bff-alb-dev/b14591ea09ab9bd5/a6fac441aee4440b`
67 | Resources: albs.map((albArn) => `${albArn}`),
68 | };
69 | };
70 |
71 | // TODO: fix all access
72 | // sg attach and VPC attach to lambda
73 | export const sgGenerator = (sGroups) => {
74 | return {
75 | Effect: 'Allow',
76 | Action: [
77 | 'ec2:AuthorizeSecurityGroupEgress',
78 | 'ec2:AuthorizeSecurityGroupIngress',
79 | 'ec2:CreateSecurityGroup',
80 | 'ec2:DeleteSecurityGroup',
81 | 'ec2:DescribeSecurityGroups',
82 | 'ec2:DescribeNetworkInterfaces',
83 | 'ec2:DescribeSubnets',
84 | 'ec2:DescribeVpcs',
85 | 'ec2:DescribeDhcpOptions',
86 | 'ec2:RevokeSecurityGroupEgress',
87 | 'ec2:RevokeSecurityGroupIngress',
88 | 'ec2:CreateNetworkInterfacePermission',
89 | 'ec2:CreateNetworkInterface',
90 | 'ec2:DeleteNetworkInterfacePermission',
91 | 'ec2:DeleteNetworkInterface',
92 | 'ec2:createTags',
93 | 'ec2:deleteTags',
94 | ],
95 | Resource: ['*'],
96 | };
97 | };
98 |
99 | export const dynamoDBGenerator = (dbs, account) => {
100 | return {
101 | Effect: 'Allow',
102 | Action: ['dynamodb:*'],
103 | Resource: dbs.map((db) => `arn:aws:dynamodb:*:${account}:table/${db}`),
104 | };
105 | };
106 |
107 | export const snsGenerator = (topics, region, account) => {
108 | return {
109 | Effect: 'Allow',
110 | Action: ['sns:*'],
111 | Resource: topics.map((topic) => `arn:aws:sns:${region}:${account}:${topic}`),
112 | };
113 | };
114 |
115 | export const apiGWGenerator = () => {
116 | return {
117 | Effect: 'Allow',
118 | Action: ['apigateway:GET', 'apigateway:POST', 'apigateway:PUT', 'apigateway:DELETE', 'apigateway:PATCH'],
119 | Resource: [
120 | 'arn:aws:apigateway:*::/apis*',
121 | 'arn:aws:apigateway:*::/restapis*',
122 | 'arn:aws:apigateway:*::/apikeys*',
123 | 'arn:aws:apigateway:*::/usageplans*',
124 | ],
125 | };
126 | };
127 |
128 | // TODO: can we determine the hosted zone id or do we ask the user for it? What about calls to `create_domain`?
129 | export const domainManagerGenerator = (region, account, useRoute53 = false) => {
130 | return [
131 | {
132 | Effect: 'Allow',
133 | Action: ['acm:ListCertificates'],
134 | Resource: ['*'],
135 | },
136 | {
137 | Effect: 'Allow',
138 | Action: ['apigateway:GET', 'apigateway:DELETE'],
139 | Resource: [`arn:aws:apigateway:${region}:${account}:/domainnames/*`],
140 | },
141 | {
142 | Effect: 'Allow',
143 | Action: ['apigateway:GET', 'apigateway:POST'],
144 | Resource: [`arn:aws:apigateway:${region}:${account}:/domainnames/*/basepathmappings`],
145 | },
146 | {
147 | Effect: 'Allow',
148 | Action: ['apigateway:PATCH'],
149 | Resource: [`arn:aws:apigateway:${region}:${account}:/domainnames/*/basepathmapping`],
150 | },
151 | {
152 | Effect: 'Allow',
153 | Action: ['apigateway:POST'],
154 | Resource: [`arn:aws:apigateway:${region}:${account}:/domainnames`],
155 | },
156 | {
157 | Effect: 'Allow',
158 | Action: ['cloudformation:GET'],
159 | Resource: ['*'],
160 | },
161 | {
162 | Effect: 'Allow',
163 | Action: ['cloudfront:UpdateDistribution'],
164 | Resource: ['*'],
165 | },
166 | useRoute53 && {
167 | Effect: 'Allow',
168 | Action: ['route53:ListHostedZones', 'route53:GetHostedZone', 'route53:ListResourceRecordSets'],
169 | Resource: ['*'],
170 | },
171 | useRoute53 && {
172 | Effect: 'Allow',
173 | Action: ['route53:ChangeResourceRecordSets'],
174 | Resource: [`arn:aws:route53:::hostedzone/*`],
175 | },
176 | {
177 | Effect: 'Allow',
178 | Action: ['iam:CreateServiceLinkedRole'],
179 | Resource: [`arn:aws:iam:::role/aws-service-role/ops.apigateway.amazonaws.com/AWSServiceRoleForAPIGateway`],
180 | },
181 | ].filter((property) => property);
182 | };
183 |
184 | export const warmupPluginGenerator = (region, account, ruleNames) => {
185 | return {
186 | Effect: 'Allow',
187 | Action: ['events:DescribeRule', 'events:PutRule', 'events:DeleteRule', 'events:PutTargets', 'events:RemoveTargets'],
188 | Resource: ruleNames.map((ruleName) => `arn:aws:events:${region}:${account}:rule/${ruleName}`),
189 | };
190 | };
191 |
192 | // parameter store access
193 | export const ssmGenerator = () => {
194 | return {
195 | Effect: 'Allow',
196 | Action: [
197 | 'ssm:DescribeParameters',
198 | 'ssm:GetParameter',
199 | 'ssm:GetParameters',
200 | 'ssm:GetParametersByPath',
201 | 'kms:Decrypt',
202 | ],
203 | Resource: ['*'],
204 | };
205 | };
206 |
207 | // event source mapping permission
208 | export const esmGenerator = () => {
209 | return {
210 | Effect: 'Allow',
211 | Action: "lambda:CreateEventSourceMapping",
212 | Resource: '*',
213 | };
214 | };
215 |
216 | const generator = ({
217 | projectName,
218 | accountId,
219 | stage,
220 | region,
221 | isS3Required,
222 | s3Array,
223 | isSnsRequired,
224 | snsArray,
225 | isApiGWRequired,
226 | isSgRequired,
227 | isAlbRequired,
228 | albArray,
229 | isSqsRequired,
230 | sqsArray,
231 | isKinesisRequired,
232 | kinesisArray,
233 | isDynamoDbRequired,
234 | dynamoDbArray,
235 | isSsmRequired,
236 | isEsmEnabled,
237 | isDomainManagerRequired,
238 | isDomainManagerRoute53Required,
239 | isWarmUpPluginRequired,
240 | warmUpPluginRuleArray,
241 | }) => {
242 | return {
243 | Version: '2012-10-17',
244 | Statement: [
245 | {
246 | Effect: 'Allow',
247 | Action: ['cloudformation:List*', 'cloudformation:Get*', 'cloudformation:ValidateTemplate'],
248 | Resource: ['*'],
249 | },
250 | {
251 | Effect: 'Allow',
252 | Action: [
253 | 'cloudformation:CreateStack',
254 | 'cloudformation:CreateUploadBucket',
255 | 'cloudformation:DeleteStack',
256 | 'cloudformation:Describe*',
257 | 'cloudformation:UpdateStack',
258 | ],
259 | Resource: [`arn:aws:cloudformation:${region}:${accountId}:stack/${projectName}-${stage}/*`],
260 | },
261 | {
262 | Effect: 'Allow',
263 | Action: ['lambda:Get*', 'lambda:List*', 'lambda:CreateFunction'],
264 | Resource: ['*'],
265 | },
266 | {
267 | Effect: 'Allow',
268 | Action: [
269 | 's3:GetBucketLocation',
270 | 's3:CreateBucket',
271 | 's3:DeleteBucket',
272 | 's3:ListBucket',
273 | 's3:GetBucketPolicy',
274 | 's3:PutBucketPolicy',
275 | 's3:ListBucketVersions',
276 | 's3:PutAccelerateConfiguration',
277 | 's3:GetEncryptionConfiguration',
278 | 's3:PutEncryptionConfiguration',
279 | 's3:DeleteBucketPolicy',
280 | ],
281 | Resource: [`arn:aws:s3:::${projectName}*serverlessdeploy*`],
282 | },
283 | {
284 | Effect: 'Allow',
285 | Action: ['s3:PutObject', 's3:GetObject', 's3:DeleteObject'],
286 | Resource: [`arn:aws:s3:::${projectName}*serverlessdeploy*`],
287 | },
288 | {
289 | Effect: 'Allow',
290 | Action: [
291 | 'lambda:AddPermission',
292 | 'lambda:CreateAlias',
293 | 'lambda:DeleteFunction',
294 | 'lambda:InvokeFunction',
295 | 'lambda:PublishVersion',
296 | 'lambda:RemovePermission',
297 | 'lambda:Update*',
298 | ],
299 | Resource: [`arn:aws:lambda:${region}:${accountId}:function:${projectName}-${stage}-*`],
300 | },
301 | {
302 | Effect: 'Allow',
303 | Action: ['cloudwatch:GetMetricStatistics'],
304 | Resource: ['*'],
305 | },
306 | {
307 | Action: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:DeleteLogGroup'],
308 | Resource: [`arn:aws:logs:${region}:${accountId}:*`],
309 | Effect: 'Allow',
310 | },
311 | {
312 | Action: ['logs:PutLogEvents'],
313 | Resource: [`arn:aws:logs:${region}:${accountId}:*`],
314 | Effect: 'Allow',
315 | },
316 | {
317 | Effect: 'Allow',
318 | Action: ['logs:DescribeLogStreams', 'logs:DescribeLogGroups', 'logs:FilterLogEvents'],
319 | Resource: ['*'],
320 | },
321 | {
322 | Effect: 'Allow',
323 | Action: ['events:Put*', 'events:Remove*', 'events:Delete*'],
324 | Resource: [`arn:aws:events:${region}:${accountId}:rule/${projectName}-${stage}-${region}`],
325 | },
326 | {
327 | Effect: 'Allow',
328 | Action: ['events:DescribeRule'],
329 | Resource: [`arn:aws:events:${region}:${accountId}:rule/${projectName}-${stage}-*`],
330 | },
331 | {
332 | Effect: 'Allow',
333 | Action: ['iam:PassRole'],
334 | Resource: [`arn:aws:iam::${accountId}:role/*`],
335 | },
336 | {
337 | Effect: 'Allow',
338 | Action: ['iam:GetRole', 'iam:CreateRole', 'iam:PutRolePolicy', 'iam:DeleteRolePolicy', 'iam:DeleteRole'],
339 | Resource: [`arn:aws:iam::${accountId}:role/${projectName}-${stage}-${region}-lambdaRole`],
340 | },
341 | isS3Required && s3Generator(s3Array),
342 | isSnsRequired && snsGenerator(snsArray, region, accountId),
343 | isApiGWRequired && apiGWGenerator(),
344 | isSgRequired && sgGenerator(),
345 | isAlbRequired && albGenerator(albArray),
346 | isSqsRequired && sqsGenerator(sqsArray, region, accountId),
347 | isKinesisRequired && kinesisGenerator(kinesisArray),
348 | isDynamoDbRequired && dynamoDBGenerator(dynamoDbArray, accountId),
349 | isSsmRequired && ssmGenerator(),
350 | isEsmEnabled && esmGenerator(),
351 | isDomainManagerRequired && domainManagerGenerator(region, accountId, isDomainManagerRoute53Required),
352 | isWarmUpPluginRequired && warmupPluginGenerator(region, accountId, warmUpPluginRuleArray),
353 | ]
354 | .flat()
355 | .filter((property) => property),
356 | };
357 | };
358 |
359 | export default generator;
360 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import 'fontsource-roboto';
6 | import 'typeface-metropolis';
7 |
8 | import * as serviceWorker from './serviceWorker';
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('root')
15 | );
16 |
17 | // If you want your app to work offline and load faster, you can change
18 | // unregister() to register() below. Note this comes with some pitfalls.
19 | // Learn more about service workers: https://bit.ly/CRA-PWA
20 | serviceWorker.unregister();
21 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/validation/index.js:
--------------------------------------------------------------------------------
1 | const validate = (values) => {
2 | const errors = {};
3 |
4 | // proiect meta data validation
5 |
6 | if (!values.projectName) {
7 | errors.projectName = 'Required';
8 | }
9 | if (!values.accountId) {
10 | errors.accountId = 'Required';
11 | } else if (!/^[0-9\b]+$/.test(values.accountId)) {
12 | errors.accountId = 'Invalid ID';
13 | }
14 | if (!values.region) {
15 | errors.region = 'Required';
16 | }
17 | if (!values.stage) {
18 | errors.stage = 'Required';
19 | }
20 |
21 | // project permission validations
22 |
23 | if (values.isS3Required === true && values.s3Array.length === 0) {
24 | errors.isS3Required = ' Please add a bucket';
25 | }
26 |
27 | if (values.isDynamoDbRequired === true && values.dynamoDbArray.length === 0) {
28 | errors.isDynamoDbRequired = ' Please add a database';
29 | }
30 |
31 | if (values.isSqsRequired === true && values.sqsArray.length === 0) {
32 | errors.isSqsRequired = ' Please add a queue';
33 | }
34 |
35 | if (values.isAlbRequired === true && values.albArray.length === 0) {
36 | errors.isAlbRequired = ' Please add a listner';
37 | }
38 |
39 | if (values.isKinesisRequired === true && values.kinesisArray.length === 0) {
40 | errors.isKinesisRequired = ' Please add a stream';
41 | }
42 |
43 | if (values.isSnsRequired === true && values.snsArray.length === 0) {
44 | errors.isSnsRequired = ' Please add a topic';
45 | }
46 |
47 | return errors;
48 | };
49 |
50 | export default validate;
51 |
--------------------------------------------------------------------------------