@friggframework/devtools 2.0.0--canary.398.d5e0e8e.0 → 2.0.0--canary.398.a314355.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ # Frigg IAM Policy Templates
2
+
3
+ This directory contains IAM policy templates for deploying Frigg applications with the appropriate permissions.
4
+
5
+ ## Quick Start
6
+
7
+ For immediate deployment, you have two ready-to-use IAM policy options:
8
+
9
+ ### Option 1: Basic Policy (Recommended for getting started)
10
+ ```bash
11
+ # Use the basic policy for core Frigg functionality
12
+ aws iam put-user-policy \
13
+ --user-name frigg-deployment-user \
14
+ --policy-name FriggBasicDeploymentPolicy \
15
+ --policy-document file://iam-policy-basic.json
16
+ ```
17
+
18
+ **Includes permissions for:**
19
+ - ✅ AWS Discovery (finding your VPC, subnets, security groups)
20
+ - ✅ CloudFormation stacks (deploy/update Frigg applications)
21
+ - ✅ Lambda functions (create and manage serverless functions)
22
+ - ✅ API Gateway (HTTP endpoints for your integrations)
23
+ - ✅ SQS/SNS (message queues and notifications)
24
+ - ✅ S3 (deployment artifacts)
25
+ - ✅ CloudWatch/Logs (monitoring and logging)
26
+ - ✅ IAM roles (Lambda execution roles)
27
+
28
+ ### Option 2: Full Policy (All features enabled)
29
+ ```bash
30
+ # Use the full policy for advanced Frigg features
31
+ aws iam put-user-policy \
32
+ --user-name frigg-deployment-user \
33
+ --policy-name FriggFullDeploymentPolicy \
34
+ --policy-document file://iam-policy-full.json
35
+ ```
36
+
37
+ **Includes everything from Basic Policy PLUS:**
38
+ - ✅ **VPC Management** - Create route tables, NAT gateways, VPC endpoints
39
+ - ✅ **KMS Encryption** - Field-level encryption for sensitive data
40
+ - ✅ **SSM Parameter Store** - Secure configuration management
41
+
42
+ ## When to Use Which Policy
43
+
44
+ ### Use Basic Policy When:
45
+ - Getting started with Frigg
46
+ - Building simple integrations without VPC requirements
47
+ - You want minimal AWS permissions
48
+ - You're not handling sensitive data requiring encryption
49
+
50
+ ### Use Full Policy When:
51
+ - You need VPC isolation for security/compliance
52
+ - You're handling sensitive data requiring KMS encryption
53
+ - You want to use SSM Parameter Store for configuration
54
+ - You're deploying production applications
55
+
56
+ ## Current Issue Resolution
57
+
58
+ **If you're seeing the error:** `User is not authorized to perform: ec2:CreateRouteTable`
59
+
60
+ This means your current deployment user doesn't have VPC permissions. You have two options:
61
+
62
+ ### Quick Fix: Apply Full Policy
63
+ ```bash
64
+ aws iam put-user-policy \
65
+ --user-name frigg-deployment-user \
66
+ --policy-name FriggFullDeploymentPolicy \
67
+ --policy-document file://iam-policy-full.json
68
+ ```
69
+
70
+ ### Alternative: Update CloudFormation Stack
71
+ If you deployed using the CloudFormation template, update it with VPC support:
72
+ ```bash
73
+ aws cloudformation update-stack \
74
+ --stack-name frigg-deployment-iam \
75
+ --template-body file://frigg-deployment-iam-stack.yaml \
76
+ --parameters ParameterKey=EnableVPCSupport,ParameterValue=true \
77
+ --capabilities CAPABILITY_IAM
78
+ ```
79
+
80
+ ## Using the IAM Generator
81
+
82
+ For custom policy generation based on your app definition:
83
+
84
+ ```javascript
85
+ const { generateIAMPolicy, generateIAMCloudFormation } = require('./iam-generator');
86
+
87
+ // Generate basic JSON policy
88
+ const basicPolicy = generateIAMPolicy('basic');
89
+
90
+ // Generate full JSON policy
91
+ const fullPolicy = generateIAMPolicy('full');
92
+
93
+ // Generate CloudFormation template with auto-detection
94
+ const autoTemplate = generateIAMCloudFormation(appDefinition, { mode: 'auto' });
95
+
96
+ // Generate CloudFormation template with specific mode
97
+ const basicTemplate = generateIAMCloudFormation(appDefinition, { mode: 'basic' });
98
+ const fullTemplate = generateIAMCloudFormation(appDefinition, { mode: 'full' });
99
+ ```
100
+
101
+ ### Generator Modes
102
+
103
+ - **`basic`** - Core permissions only, ignores app definition features
104
+ - **`full`** - All features enabled, ignores app definition features
105
+ - **`auto`** - Analyzes app definition and enables features as needed (default)
106
+
107
+ ## Security Best Practices
108
+
109
+ ### Resource Scoping
110
+ Both policies are scoped to resources containing "frigg" in their names:
111
+ - ✅ `my-frigg-app-prod` (will work)
112
+ - ❌ `my-integration-app` (won't work - missing "frigg")
113
+
114
+ ### Account-Specific Resources
115
+ Replace `*` with your AWS account ID for tighter security:
116
+ ```json
117
+ {
118
+ "Resource": [
119
+ "arn:aws:lambda:us-east-1:123456789012:function:*frigg*"
120
+ ]
121
+ }
122
+ ```
123
+
124
+ ### Environment-Specific Policies
125
+ Consider separate policies for different environments:
126
+ - `frigg-dev-policy` (full permissions for development)
127
+ - `frigg-prod-policy` (restricted permissions for production)
128
+
129
+ ## Troubleshooting
130
+
131
+ ### Common Permission Errors
132
+
133
+ 1. **"ec2:CreateRouteTable" error** → Use Full Policy
134
+ 2. **"kms:GenerateDataKey" error** → Enable KMS in your policy
135
+ 3. **"ssm:GetParameter" error** → Enable SSM in your policy
136
+ 4. **Lambda VPC errors** → Ensure VPC permissions are enabled
137
+
138
+ ### Validation
139
+ Test your policy by deploying a simple Frigg app:
140
+ ```bash
141
+ npx create-frigg-app test-deployment
142
+ cd test-deployment
143
+ frigg deploy
144
+ ```
145
+
146
+ ### Policy Comparison
147
+
148
+ | Feature | Basic Policy | Full Policy | CloudFormation Template |
149
+ |---------|--------------|-------------|-------------------------|
150
+ | Core Deployment | ✅ | ✅ | ✅ |
151
+ | VPC Management | ❌ | ✅ | ✅ (conditional) |
152
+ | KMS Encryption | ❌ | ✅ | ✅ (conditional) |
153
+ | SSM Parameters | ❌ | ✅ | ✅ (conditional) |
154
+ | Format | JSON | JSON | YAML with parameters |
155
+ | Use Case | Getting started | Production ready | Infrastructure as Code |
156
+
157
+ ## Files in this Directory
158
+
159
+ - `iam-policy-basic.json` - Core Frigg permissions only (JSON format)
160
+ - `iam-policy-full.json` - All features enabled (JSON format)
161
+ - `frigg-deployment-iam-stack.yaml` - CloudFormation template with conditional parameters
162
+ - `iam-generator.js` - Programmatic policy generation with basic/full/auto modes
163
+ - `AWS-IAM-CREDENTIAL-NEEDS.md` - Detailed permission explanations and troubleshooting
164
+ - `IAM-POLICY-TEMPLATES.md` - This file - Quick start guide and usage examples
165
+
166
+ ## Support
167
+
168
+ If you encounter permission issues:
169
+ 1. Check the error message for the specific missing permission
170
+ 2. Verify your resource names contain "frigg"
171
+ 3. Consider upgrading from Basic to Full policy
172
+ 4. Review the AWS-IAM-CREDENTIAL-NEEDS.md for detailed explanations
@@ -280,15 +280,10 @@ Resources:
280
280
  - 'ec2:AuthorizeSecurityGroupIngress'
281
281
  - 'ec2:RevokeSecurityGroupEgress'
282
282
  - 'ec2:RevokeSecurityGroupIngress'
283
+ - 'ec2:CreateTags'
284
+ - 'ec2:DeleteTags'
285
+ - 'ec2:DescribeTags'
283
286
  Resource: '*'
284
- Condition:
285
- StringLike:
286
- 'ec2:CreateAction':
287
- - 'CreateVpcEndpoint'
288
- - 'CreateNatGateway'
289
- - 'CreateRouteTable'
290
- - 'CreateRoute'
291
- - 'CreateSecurityGroup'
292
287
 
293
288
  # KMS permissions
294
289
  FriggKMSPolicy:
@@ -7,21 +7,40 @@ const path = require('path');
7
7
  * @param {Object} options - Generation options
8
8
  * @param {string} [options.deploymentUserName='frigg-deployment-user'] - IAM user name
9
9
  * @param {string} [options.stackName='frigg-deployment-iam'] - CloudFormation stack name
10
+ * @param {string} [options.mode='auto'] - Policy mode: 'basic', 'full', or 'auto' (auto-detect from appDefinition)
10
11
  * @returns {string} CloudFormation YAML template
11
12
  */
12
13
  function generateIAMCloudFormation(appDefinition, options = {}) {
13
14
  const {
14
15
  deploymentUserName = 'frigg-deployment-user',
15
- stackName = 'frigg-deployment-iam'
16
+ stackName = 'frigg-deployment-iam',
17
+ mode = 'auto'
16
18
  } = options;
17
19
 
18
- // Determine which features are enabled
19
- const features = {
20
- vpc: appDefinition.vpc?.enable === true,
21
- kms: appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true,
22
- ssm: appDefinition.ssm?.enable === true,
23
- websockets: appDefinition.websockets?.enable === true
24
- };
20
+ // Determine which features are enabled based on mode
21
+ let features;
22
+ if (mode === 'basic') {
23
+ features = {
24
+ vpc: false,
25
+ kms: false,
26
+ ssm: false,
27
+ websockets: appDefinition.websockets?.enable === true
28
+ };
29
+ } else if (mode === 'full') {
30
+ features = {
31
+ vpc: true,
32
+ kms: true,
33
+ ssm: true,
34
+ websockets: appDefinition.websockets?.enable === true
35
+ };
36
+ } else { // mode === 'auto'
37
+ features = {
38
+ vpc: appDefinition.vpc?.enable === true,
39
+ kms: appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true,
40
+ ssm: appDefinition.ssm?.enable === true,
41
+ websockets: appDefinition.websockets?.enable === true
42
+ };
43
+ }
25
44
 
26
45
  // Build the CloudFormation template
27
46
  const template = {
@@ -638,7 +657,40 @@ function getFeatureSummary(appDefinition) {
638
657
  };
639
658
  }
640
659
 
660
+ /**
661
+ * Generate basic IAM policy (JSON format) - Core Frigg permissions only
662
+ * @returns {Object} Basic IAM policy document
663
+ */
664
+ function generateBasicIAMPolicy() {
665
+ const basicPolicyPath = path.join(__dirname, 'iam-policy-basic.json');
666
+ return require(basicPolicyPath);
667
+ }
668
+
669
+ /**
670
+ * Generate full IAM policy (JSON format) - All features enabled
671
+ * @returns {Object} Full IAM policy document
672
+ */
673
+ function generateFullIAMPolicy() {
674
+ const fullPolicyPath = path.join(__dirname, 'iam-policy-full.json');
675
+ return require(fullPolicyPath);
676
+ }
677
+
678
+ /**
679
+ * Generate IAM policy based on mode
680
+ * @param {string} mode - 'basic' or 'full'
681
+ * @returns {Object} IAM policy document
682
+ */
683
+ function generateIAMPolicy(mode = 'basic') {
684
+ if (mode === 'full') {
685
+ return generateFullIAMPolicy();
686
+ }
687
+ return generateBasicIAMPolicy();
688
+ }
689
+
641
690
  module.exports = {
642
691
  generateIAMCloudFormation,
643
- getFeatureSummary
692
+ getFeatureSummary,
693
+ generateBasicIAMPolicy,
694
+ generateFullIAMPolicy,
695
+ generateIAMPolicy
644
696
  };
@@ -0,0 +1,196 @@
1
+ {
2
+ "Version": "2012-10-17",
3
+ "Statement": [
4
+ {
5
+ "Sid": "AWSDiscoveryPermissions",
6
+ "Effect": "Allow",
7
+ "Action": [
8
+ "sts:GetCallerIdentity",
9
+ "ec2:DescribeVpcs",
10
+ "ec2:DescribeSubnets",
11
+ "ec2:DescribeSecurityGroups",
12
+ "ec2:DescribeRouteTables",
13
+ "kms:ListKeys",
14
+ "kms:DescribeKey"
15
+ ],
16
+ "Resource": "*"
17
+ },
18
+ {
19
+ "Sid": "CloudFormationFriggStacks",
20
+ "Effect": "Allow",
21
+ "Action": [
22
+ "cloudformation:CreateStack",
23
+ "cloudformation:UpdateStack",
24
+ "cloudformation:DeleteStack",
25
+ "cloudformation:DescribeStacks",
26
+ "cloudformation:DescribeStackEvents",
27
+ "cloudformation:DescribeStackResources",
28
+ "cloudformation:DescribeStackResource",
29
+ "cloudformation:ListStackResources",
30
+ "cloudformation:GetTemplate",
31
+ "cloudformation:ValidateTemplate",
32
+ "cloudformation:DescribeChangeSet",
33
+ "cloudformation:CreateChangeSet",
34
+ "cloudformation:DeleteChangeSet",
35
+ "cloudformation:ExecuteChangeSet"
36
+ ],
37
+ "Resource": [
38
+ "arn:aws:cloudformation:*:*:stack/*frigg*/*"
39
+ ]
40
+ },
41
+ {
42
+ "Sid": "S3DeploymentBucket",
43
+ "Effect": "Allow",
44
+ "Action": [
45
+ "s3:CreateBucket",
46
+ "s3:PutObject",
47
+ "s3:GetObject",
48
+ "s3:DeleteObject",
49
+ "s3:PutBucketPolicy",
50
+ "s3:PutBucketVersioning",
51
+ "s3:PutBucketPublicAccessBlock",
52
+ "s3:GetBucketLocation",
53
+ "s3:ListBucket"
54
+ ],
55
+ "Resource": [
56
+ "arn:aws:s3:::*serverless*",
57
+ "arn:aws:s3:::*serverless*/*"
58
+ ]
59
+ },
60
+ {
61
+ "Sid": "LambdaFriggFunctions",
62
+ "Effect": "Allow",
63
+ "Action": [
64
+ "lambda:CreateFunction",
65
+ "lambda:UpdateFunctionCode",
66
+ "lambda:UpdateFunctionConfiguration",
67
+ "lambda:DeleteFunction",
68
+ "lambda:GetFunction",
69
+ "lambda:ListFunctions",
70
+ "lambda:PublishVersion",
71
+ "lambda:CreateAlias",
72
+ "lambda:UpdateAlias",
73
+ "lambda:DeleteAlias",
74
+ "lambda:GetAlias",
75
+ "lambda:AddPermission",
76
+ "lambda:RemovePermission",
77
+ "lambda:GetPolicy",
78
+ "lambda:PutProvisionedConcurrencyConfig",
79
+ "lambda:DeleteProvisionedConcurrencyConfig",
80
+ "lambda:PutConcurrency",
81
+ "lambda:DeleteConcurrency",
82
+ "lambda:TagResource",
83
+ "lambda:UntagResource",
84
+ "lambda:ListVersionsByFunction"
85
+ ],
86
+ "Resource": [
87
+ "arn:aws:lambda:*:*:function:*frigg*"
88
+ ]
89
+ },
90
+ {
91
+ "Sid": "IAMRolesForFriggLambda",
92
+ "Effect": "Allow",
93
+ "Action": [
94
+ "iam:CreateRole",
95
+ "iam:DeleteRole",
96
+ "iam:GetRole",
97
+ "iam:PassRole",
98
+ "iam:PutRolePolicy",
99
+ "iam:DeleteRolePolicy",
100
+ "iam:GetRolePolicy",
101
+ "iam:AttachRolePolicy",
102
+ "iam:DetachRolePolicy",
103
+ "iam:TagRole",
104
+ "iam:UntagRole"
105
+ ],
106
+ "Resource": [
107
+ "arn:aws:iam::*:role/*frigg*",
108
+ "arn:aws:iam::*:role/*frigg*LambdaRole*"
109
+ ]
110
+ },
111
+ {
112
+ "Sid": "IAMPolicyVersionPermissions",
113
+ "Effect": "Allow",
114
+ "Action": [
115
+ "iam:ListPolicyVersions"
116
+ ],
117
+ "Resource": [
118
+ "arn:aws:iam::*:policy/*"
119
+ ]
120
+ },
121
+ {
122
+ "Sid": "FriggMessagingServices",
123
+ "Effect": "Allow",
124
+ "Action": [
125
+ "sqs:CreateQueue",
126
+ "sqs:DeleteQueue",
127
+ "sqs:GetQueueAttributes",
128
+ "sqs:SetQueueAttributes",
129
+ "sqs:GetQueueUrl",
130
+ "sqs:TagQueue",
131
+ "sqs:UntagQueue"
132
+ ],
133
+ "Resource": [
134
+ "arn:aws:sqs:*:*:*frigg*",
135
+ "arn:aws:sqs:*:*:internal-error-queue-*"
136
+ ]
137
+ },
138
+ {
139
+ "Sid": "FriggSNSTopics",
140
+ "Effect": "Allow",
141
+ "Action": [
142
+ "sns:CreateTopic",
143
+ "sns:DeleteTopic",
144
+ "sns:GetTopicAttributes",
145
+ "sns:SetTopicAttributes",
146
+ "sns:Subscribe",
147
+ "sns:Unsubscribe",
148
+ "sns:ListSubscriptionsByTopic",
149
+ "sns:TagResource",
150
+ "sns:UntagResource"
151
+ ],
152
+ "Resource": [
153
+ "arn:aws:sns:*:*:*frigg*"
154
+ ]
155
+ },
156
+ {
157
+ "Sid": "FriggMonitoringAndLogs",
158
+ "Effect": "Allow",
159
+ "Action": [
160
+ "cloudwatch:PutMetricAlarm",
161
+ "cloudwatch:DeleteAlarms",
162
+ "cloudwatch:DescribeAlarms",
163
+ "logs:CreateLogGroup",
164
+ "logs:CreateLogStream",
165
+ "logs:DeleteLogGroup",
166
+ "logs:DescribeLogGroups",
167
+ "logs:DescribeLogStreams",
168
+ "logs:FilterLogEvents",
169
+ "logs:PutLogEvents",
170
+ "logs:PutRetentionPolicy"
171
+ ],
172
+ "Resource": [
173
+ "arn:aws:logs:*:*:log-group:/aws/lambda/*frigg*",
174
+ "arn:aws:logs:*:*:log-group:/aws/lambda/*frigg*:*",
175
+ "arn:aws:cloudwatch:*:*:alarm:*frigg*"
176
+ ]
177
+ },
178
+ {
179
+ "Sid": "FriggAPIGateway",
180
+ "Effect": "Allow",
181
+ "Action": [
182
+ "apigateway:POST",
183
+ "apigateway:PUT",
184
+ "apigateway:DELETE",
185
+ "apigateway:GET",
186
+ "apigateway:PATCH"
187
+ ],
188
+ "Resource": [
189
+ "arn:aws:apigateway:*::/restapis",
190
+ "arn:aws:apigateway:*::/restapis/*",
191
+ "arn:aws:apigateway:*::/domainnames",
192
+ "arn:aws:apigateway:*::/domainnames/*"
193
+ ]
194
+ }
195
+ ]
196
+ }
@@ -0,0 +1,266 @@
1
+ {
2
+ "Version": "2012-10-17",
3
+ "Statement": [
4
+ {
5
+ "Sid": "AWSDiscoveryPermissions",
6
+ "Effect": "Allow",
7
+ "Action": [
8
+ "sts:GetCallerIdentity",
9
+ "ec2:DescribeVpcs",
10
+ "ec2:DescribeSubnets",
11
+ "ec2:DescribeSecurityGroups",
12
+ "ec2:DescribeRouteTables",
13
+ "kms:ListKeys",
14
+ "kms:DescribeKey"
15
+ ],
16
+ "Resource": "*"
17
+ },
18
+ {
19
+ "Sid": "CloudFormationFriggStacks",
20
+ "Effect": "Allow",
21
+ "Action": [
22
+ "cloudformation:CreateStack",
23
+ "cloudformation:UpdateStack",
24
+ "cloudformation:DeleteStack",
25
+ "cloudformation:DescribeStacks",
26
+ "cloudformation:DescribeStackEvents",
27
+ "cloudformation:DescribeStackResources",
28
+ "cloudformation:DescribeStackResource",
29
+ "cloudformation:ListStackResources",
30
+ "cloudformation:GetTemplate",
31
+ "cloudformation:ValidateTemplate",
32
+ "cloudformation:DescribeChangeSet",
33
+ "cloudformation:CreateChangeSet",
34
+ "cloudformation:DeleteChangeSet",
35
+ "cloudformation:ExecuteChangeSet"
36
+ ],
37
+ "Resource": [
38
+ "arn:aws:cloudformation:*:*:stack/*frigg*/*"
39
+ ]
40
+ },
41
+ {
42
+ "Sid": "S3DeploymentBucket",
43
+ "Effect": "Allow",
44
+ "Action": [
45
+ "s3:CreateBucket",
46
+ "s3:PutObject",
47
+ "s3:GetObject",
48
+ "s3:DeleteObject",
49
+ "s3:PutBucketPolicy",
50
+ "s3:PutBucketVersioning",
51
+ "s3:PutBucketPublicAccessBlock",
52
+ "s3:GetBucketLocation",
53
+ "s3:ListBucket"
54
+ ],
55
+ "Resource": [
56
+ "arn:aws:s3:::*serverless*",
57
+ "arn:aws:s3:::*serverless*/*"
58
+ ]
59
+ },
60
+ {
61
+ "Sid": "LambdaFriggFunctions",
62
+ "Effect": "Allow",
63
+ "Action": [
64
+ "lambda:CreateFunction",
65
+ "lambda:UpdateFunctionCode",
66
+ "lambda:UpdateFunctionConfiguration",
67
+ "lambda:DeleteFunction",
68
+ "lambda:GetFunction",
69
+ "lambda:ListFunctions",
70
+ "lambda:PublishVersion",
71
+ "lambda:CreateAlias",
72
+ "lambda:UpdateAlias",
73
+ "lambda:DeleteAlias",
74
+ "lambda:GetAlias",
75
+ "lambda:AddPermission",
76
+ "lambda:RemovePermission",
77
+ "lambda:GetPolicy",
78
+ "lambda:PutProvisionedConcurrencyConfig",
79
+ "lambda:DeleteProvisionedConcurrencyConfig",
80
+ "lambda:PutConcurrency",
81
+ "lambda:DeleteConcurrency",
82
+ "lambda:TagResource",
83
+ "lambda:UntagResource",
84
+ "lambda:ListVersionsByFunction"
85
+ ],
86
+ "Resource": [
87
+ "arn:aws:lambda:*:*:function:*frigg*"
88
+ ]
89
+ },
90
+ {
91
+ "Sid": "IAMRolesForFriggLambda",
92
+ "Effect": "Allow",
93
+ "Action": [
94
+ "iam:CreateRole",
95
+ "iam:DeleteRole",
96
+ "iam:GetRole",
97
+ "iam:PassRole",
98
+ "iam:PutRolePolicy",
99
+ "iam:DeleteRolePolicy",
100
+ "iam:GetRolePolicy",
101
+ "iam:AttachRolePolicy",
102
+ "iam:DetachRolePolicy",
103
+ "iam:TagRole",
104
+ "iam:UntagRole"
105
+ ],
106
+ "Resource": [
107
+ "arn:aws:iam::*:role/*frigg*",
108
+ "arn:aws:iam::*:role/*frigg*LambdaRole*"
109
+ ]
110
+ },
111
+ {
112
+ "Sid": "IAMPolicyVersionPermissions",
113
+ "Effect": "Allow",
114
+ "Action": [
115
+ "iam:ListPolicyVersions"
116
+ ],
117
+ "Resource": [
118
+ "arn:aws:iam::*:policy/*"
119
+ ]
120
+ },
121
+ {
122
+ "Sid": "FriggMessagingServices",
123
+ "Effect": "Allow",
124
+ "Action": [
125
+ "sqs:CreateQueue",
126
+ "sqs:DeleteQueue",
127
+ "sqs:GetQueueAttributes",
128
+ "sqs:SetQueueAttributes",
129
+ "sqs:GetQueueUrl",
130
+ "sqs:TagQueue",
131
+ "sqs:UntagQueue"
132
+ ],
133
+ "Resource": [
134
+ "arn:aws:sqs:*:*:*frigg*",
135
+ "arn:aws:sqs:*:*:internal-error-queue-*"
136
+ ]
137
+ },
138
+ {
139
+ "Sid": "FriggSNSTopics",
140
+ "Effect": "Allow",
141
+ "Action": [
142
+ "sns:CreateTopic",
143
+ "sns:DeleteTopic",
144
+ "sns:GetTopicAttributes",
145
+ "sns:SetTopicAttributes",
146
+ "sns:Subscribe",
147
+ "sns:Unsubscribe",
148
+ "sns:ListSubscriptionsByTopic",
149
+ "sns:TagResource",
150
+ "sns:UntagResource"
151
+ ],
152
+ "Resource": [
153
+ "arn:aws:sns:*:*:*frigg*"
154
+ ]
155
+ },
156
+ {
157
+ "Sid": "FriggMonitoringAndLogs",
158
+ "Effect": "Allow",
159
+ "Action": [
160
+ "cloudwatch:PutMetricAlarm",
161
+ "cloudwatch:DeleteAlarms",
162
+ "cloudwatch:DescribeAlarms",
163
+ "logs:CreateLogGroup",
164
+ "logs:CreateLogStream",
165
+ "logs:DeleteLogGroup",
166
+ "logs:DescribeLogGroups",
167
+ "logs:DescribeLogStreams",
168
+ "logs:FilterLogEvents",
169
+ "logs:PutLogEvents",
170
+ "logs:PutRetentionPolicy"
171
+ ],
172
+ "Resource": [
173
+ "arn:aws:logs:*:*:log-group:/aws/lambda/*frigg*",
174
+ "arn:aws:logs:*:*:log-group:/aws/lambda/*frigg*:*",
175
+ "arn:aws:cloudwatch:*:*:alarm:*frigg*"
176
+ ]
177
+ },
178
+ {
179
+ "Sid": "FriggAPIGateway",
180
+ "Effect": "Allow",
181
+ "Action": [
182
+ "apigateway:POST",
183
+ "apigateway:PUT",
184
+ "apigateway:DELETE",
185
+ "apigateway:GET",
186
+ "apigateway:PATCH"
187
+ ],
188
+ "Resource": [
189
+ "arn:aws:apigateway:*::/restapis",
190
+ "arn:aws:apigateway:*::/restapis/*",
191
+ "arn:aws:apigateway:*::/domainnames",
192
+ "arn:aws:apigateway:*::/domainnames/*"
193
+ ]
194
+ },
195
+ {
196
+ "Sid": "FriggVPCDeploymentPermissions",
197
+ "Effect": "Allow",
198
+ "Action": [
199
+ "ec2:CreateVpcEndpoint",
200
+ "ec2:DeleteVpcEndpoint",
201
+ "ec2:DescribeVpcEndpoints",
202
+ "ec2:ModifyVpcEndpoint",
203
+ "ec2:CreateNatGateway",
204
+ "ec2:DeleteNatGateway",
205
+ "ec2:DescribeNatGateways",
206
+ "ec2:AllocateAddress",
207
+ "ec2:ReleaseAddress",
208
+ "ec2:DescribeAddresses",
209
+ "ec2:CreateRouteTable",
210
+ "ec2:DeleteRouteTable",
211
+ "ec2:DescribeRouteTables",
212
+ "ec2:CreateRoute",
213
+ "ec2:DeleteRoute",
214
+ "ec2:AssociateRouteTable",
215
+ "ec2:DisassociateRouteTable",
216
+ "ec2:CreateSecurityGroup",
217
+ "ec2:DeleteSecurityGroup",
218
+ "ec2:AuthorizeSecurityGroupEgress",
219
+ "ec2:AuthorizeSecurityGroupIngress",
220
+ "ec2:RevokeSecurityGroupEgress",
221
+ "ec2:RevokeSecurityGroupIngress",
222
+ "ec2:CreateTags",
223
+ "ec2:DeleteTags",
224
+ "ec2:DescribeTags"
225
+ ],
226
+ "Resource": "*",
227
+ "Condition": {
228
+ "StringLike": {
229
+ "aws:RequestTag/Name": "*frigg*"
230
+ }
231
+ }
232
+ },
233
+ {
234
+ "Sid": "FriggKMSEncryptionPermissions",
235
+ "Effect": "Allow",
236
+ "Action": [
237
+ "kms:GenerateDataKey",
238
+ "kms:Decrypt"
239
+ ],
240
+ "Resource": [
241
+ "arn:aws:kms:*:*:key/*"
242
+ ],
243
+ "Condition": {
244
+ "StringEquals": {
245
+ "kms:ViaService": [
246
+ "lambda.*.amazonaws.com",
247
+ "s3.*.amazonaws.com"
248
+ ]
249
+ }
250
+ }
251
+ },
252
+ {
253
+ "Sid": "FriggSSMParameterAccess",
254
+ "Effect": "Allow",
255
+ "Action": [
256
+ "ssm:GetParameter",
257
+ "ssm:GetParameters",
258
+ "ssm:GetParametersByPath"
259
+ ],
260
+ "Resource": [
261
+ "arn:aws:ssm:*:*:parameter/*frigg*",
262
+ "arn:aws:ssm:*:*:parameter/*frigg*/*"
263
+ ]
264
+ }
265
+ ]
266
+ }
@@ -473,8 +473,8 @@ const composeServerlessDefinition = async (AppDefinition) => {
473
473
  if (discoveredResources.defaultVpcId) {
474
474
  console.log(` VPC: ${discoveredResources.defaultVpcId}`);
475
475
  }
476
- if (discoveredResources.privateSubnetIds) {
477
- console.log(` Subnets: ${discoveredResources.privateSubnetIds.join(', ')}`);
476
+ if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
477
+ console.log(` Subnets: ${discoveredResources.privateSubnetId1}, ${discoveredResources.privateSubnetId2}`);
478
478
  }
479
479
  if (discoveredResources.defaultSecurityGroupId) {
480
480
  console.log(` Security Group: ${discoveredResources.defaultSecurityGroupId}`);
@@ -508,8 +508,8 @@ const composeServerlessDefinition = async (AppDefinition) => {
508
508
  // Add discovered resources to environment if available
509
509
  ...(discoveredResources.defaultVpcId && { AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId }),
510
510
  ...(discoveredResources.defaultSecurityGroupId && { AWS_DISCOVERY_SECURITY_GROUP_ID: discoveredResources.defaultSecurityGroupId }),
511
- ...(discoveredResources.privateSubnetIds && discoveredResources.privateSubnetIds[0] && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetIds[0] }),
512
- ...(discoveredResources.privateSubnetIds && discoveredResources.privateSubnetIds[1] && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetIds[1] }),
511
+ ...(discoveredResources.privateSubnetId1 && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetId1 }),
512
+ ...(discoveredResources.privateSubnetId2 && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetId2 }),
513
513
  ...(discoveredResources.publicSubnetId && { AWS_DISCOVERY_PUBLIC_SUBNET_ID: discoveredResources.publicSubnetId }),
514
514
  ...(discoveredResources.defaultRouteTableId && { AWS_DISCOVERY_ROUTE_TABLE_ID: discoveredResources.defaultRouteTableId }),
515
515
  ...(discoveredResources.defaultKmsKeyId && { AWS_DISCOVERY_KMS_KEY_ID: discoveredResources.defaultKmsKeyId }),
@@ -777,52 +777,53 @@ const composeServerlessDefinition = async (AppDefinition) => {
777
777
  // VPC configuration using discovered or explicitly provided resources
778
778
  const vpcConfig = {
779
779
  securityGroupIds: AppDefinition.vpc.securityGroupIds ||
780
- (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}']),
780
+ (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []),
781
781
  subnetIds: AppDefinition.vpc.subnetIds ||
782
- (discoveredResources.privateSubnetIds && discoveredResources.privateSubnetIds.length >= 2 ?
783
- [discoveredResources.privateSubnetIds[0], discoveredResources.privateSubnetIds[1]] :
784
- ['${env:AWS_DISCOVERY_SUBNET_ID_1}', '${env:AWS_DISCOVERY_SUBNET_ID_2}'])
782
+ (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2 ?
783
+ [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2] :
784
+ [])
785
785
  };
786
786
 
787
- // Set VPC config for Lambda functions
788
- definition.provider.vpc = vpcConfig;
789
-
790
- // Add NAT Gateway for Lambda internet access (required for external API calls)
791
- // Even with existing VPC, Lambdas need guaranteed internet access for integrations
792
- definition.resources.Resources.FriggNATGatewayEIP = {
793
- Type: 'AWS::EC2::EIP',
794
- Properties: {
795
- Domain: 'vpc',
796
- Tags: [
797
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
798
- ]
799
- }
800
- };
787
+ // Set VPC config for Lambda functions only if we have valid subnet IDs
788
+ if (vpcConfig.subnetIds.length >= 2 && vpcConfig.securityGroupIds.length > 0) {
789
+ definition.provider.vpc = vpcConfig;
790
+
791
+ // Always create NAT Gateway for Lambda internet access
792
+ // Default VPCs don't have NAT gateways, so we need to create them
793
+ definition.resources.Resources.FriggNATGatewayEIP = {
794
+ Type: 'AWS::EC2::EIP',
795
+ Properties: {
796
+ Domain: 'vpc',
797
+ Tags: [
798
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
799
+ ]
800
+ }
801
+ };
801
802
 
802
- definition.resources.Resources.FriggNATGateway = {
803
- Type: 'AWS::EC2::NatGateway',
804
- Properties: {
805
- AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
806
- SubnetId: discoveredResources.publicSubnetId || '${env:AWS_DISCOVERY_PUBLIC_SUBNET_ID}', // Discovery finds public subnet
807
- Tags: [
808
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
809
- ]
810
- }
811
- };
803
+ definition.resources.Resources.FriggNATGateway = {
804
+ Type: 'AWS::EC2::NatGateway',
805
+ Properties: {
806
+ AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
807
+ SubnetId: discoveredResources.publicSubnetId || discoveredResources.privateSubnetId1, // Use first discovered subnet if no public subnet found
808
+ Tags: [
809
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
810
+ ]
811
+ }
812
+ };
812
813
 
813
- // Create route table for Lambda subnets to use NAT Gateway
814
- definition.resources.Resources.FriggLambdaRouteTable = {
815
- Type: 'AWS::EC2::RouteTable',
816
- Properties: {
817
- VpcId: discoveredResources.defaultVpcId || '${env:AWS_DISCOVERY_VPC_ID}',
818
- Tags: [
819
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
820
- ]
821
- }
822
- };
814
+ // Create route table for Lambda subnets to use NAT Gateway
815
+ definition.resources.Resources.FriggLambdaRouteTable = {
816
+ Type: 'AWS::EC2::RouteTable',
817
+ Properties: {
818
+ VpcId: discoveredResources.defaultVpcId || { Ref: 'FriggVPC' },
819
+ Tags: [
820
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
821
+ ]
822
+ }
823
+ };
823
824
 
824
- definition.resources.Resources.FriggNATRoute = {
825
- Type: 'AWS::EC2::Route',
825
+ definition.resources.Resources.FriggNATRoute = {
826
+ Type: 'AWS::EC2::Route',
826
827
  Properties: {
827
828
  RouteTableId: { Ref: 'FriggLambdaRouteTable' },
828
829
  DestinationCidrBlock: '0.0.0.0/0',
@@ -830,44 +831,45 @@ const composeServerlessDefinition = async (AppDefinition) => {
830
831
  }
831
832
  };
832
833
 
833
- // Associate Lambda subnets with NAT Gateway route table
834
- definition.resources.Resources.FriggSubnet1RouteAssociation = {
835
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
834
+ // Associate Lambda subnets with NAT Gateway route table
835
+ definition.resources.Resources.FriggSubnet1RouteAssociation = {
836
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
836
837
  Properties: {
837
- SubnetId: discoveredResources.privateSubnetIds?.[0] || '${env:AWS_DISCOVERY_SUBNET_ID_1}',
838
+ SubnetId: vpcConfig.subnetIds[0],
838
839
  RouteTableId: { Ref: 'FriggLambdaRouteTable' }
839
840
  }
840
841
  };
841
842
 
842
- definition.resources.Resources.FriggSubnet2RouteAssociation = {
843
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
843
+ definition.resources.Resources.FriggSubnet2RouteAssociation = {
844
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
844
845
  Properties: {
845
- SubnetId: discoveredResources.privateSubnetIds?.[1] || '${env:AWS_DISCOVERY_SUBNET_ID_2}',
846
+ SubnetId: vpcConfig.subnetIds[1],
846
847
  RouteTableId: { Ref: 'FriggLambdaRouteTable' }
847
848
  }
848
849
  };
849
850
 
850
- // Add VPC endpoints for AWS service optimization (optional but recommended)
851
- if (AppDefinition.vpc.enableVPCEndpoints !== false) {
852
- definition.resources.Resources.VPCEndpointS3 = {
851
+ // Add VPC endpoints for AWS service optimization (optional but recommended)
852
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
853
+ definition.resources.Resources.VPCEndpointS3 = {
853
854
  Type: 'AWS::EC2::VPCEndpoint',
854
855
  Properties: {
855
- VpcId: discoveredResources.defaultVpcId || '${env:AWS_DISCOVERY_VPC_ID}',
856
+ VpcId: discoveredResources.defaultVpcId,
856
857
  ServiceName: 'com.amazonaws.${self:provider.region}.s3',
857
858
  VpcEndpointType: 'Gateway',
858
859
  RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
859
860
  }
860
861
  };
861
862
 
862
- definition.resources.Resources.VPCEndpointDynamoDB = {
863
+ definition.resources.Resources.VPCEndpointDynamoDB = {
863
864
  Type: 'AWS::EC2::VPCEndpoint',
864
865
  Properties: {
865
- VpcId: discoveredResources.defaultVpcId || '${env:AWS_DISCOVERY_VPC_ID}',
866
+ VpcId: discoveredResources.defaultVpcId,
866
867
  ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
867
868
  VpcEndpointType: 'Gateway',
868
869
  RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
869
870
  }
870
871
  };
872
+ }
871
873
  }
872
874
  }
873
875
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.398.d5e0e8e.0",
4
+ "version": "2.0.0--canary.398.a314355.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -9,7 +9,7 @@
9
9
  "@babel/eslint-parser": "^7.18.9",
10
10
  "@babel/parser": "^7.25.3",
11
11
  "@babel/traverse": "^7.25.3",
12
- "@friggframework/test": "2.0.0--canary.398.d5e0e8e.0",
12
+ "@friggframework/test": "2.0.0--canary.398.a314355.0",
13
13
  "@hapi/boom": "^10.0.1",
14
14
  "@inquirer/prompts": "^5.3.8",
15
15
  "axios": "^1.7.2",
@@ -31,8 +31,8 @@
31
31
  "serverless-http": "^2.7.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@friggframework/eslint-config": "2.0.0--canary.398.d5e0e8e.0",
35
- "@friggframework/prettier-config": "2.0.0--canary.398.d5e0e8e.0",
34
+ "@friggframework/eslint-config": "2.0.0--canary.398.a314355.0",
35
+ "@friggframework/prettier-config": "2.0.0--canary.398.a314355.0",
36
36
  "prettier": "^2.7.1",
37
37
  "serverless": "3.39.0",
38
38
  "serverless-dotenv-plugin": "^6.0.0",
@@ -64,5 +64,5 @@
64
64
  "publishConfig": {
65
65
  "access": "public"
66
66
  },
67
- "gitHead": "d5e0e8e6ef0dd2b12c5ed4f3fac3ab1037d76673"
67
+ "gitHead": "a31435596361f047f54cfbcc03c9863b129d0d1c"
68
68
  }