Skip to content

Commit 94e836b

Browse files
committed
feat: validate aws cn region not supported config
feat: patch aws cn lambda permission principal suffix
1 parent d951769 commit 94e836b

9 files changed

+3355
-0
lines changed

.travis.yml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
language: node_js
2+
node_js:
3+
- node
4+
- 8
5+
- 6
6+
- 4
7+
8+
branches:
9+
only:
10+
- master
11+
- /^greenkeeper/.*$/
12+
13+
cache: yarn
14+
15+
before_install:
16+
- which npx || npm i -g npx
17+
18+
after_success:
19+
- npx codecov
20+
21+
notifications:
22+
email: false

README.md

+106
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,108 @@
11
# serverless-aws-cn
22
Serverless plugin compatible with aws cn
3+
4+
[![License][ico-license]][link-license]
5+
[![NPM][ico-npm]][link-npm]
6+
[![Build Status][ico-build]][link-build]
7+
[![Coverage Status][ico-codecov]][link-codecov]
8+
9+
## Example:
10+
11+
```yml
12+
service:
13+
name: demo
14+
plugins:
15+
- serverless-aws-cn
16+
provider:
17+
name: aws
18+
region: cn-north-1
19+
endpointType: REGIONAL
20+
functions:
21+
hello:
22+
handler: handler.hello
23+
events:
24+
- http:
25+
method: get
26+
path: hello
27+
```
28+
29+
## Some tips about AWS China:
30+
31+
1. Lambda supported in Beijing `cn-north-1` region only. [Ningxia](https://www.amazonaws.cn/about-aws/regional-product-services/) `cn-northwest-1` region is not supported yet.
32+
33+
2. If you have a function named `hello` with http event. You need patch Cloud Formation API Gateway Principal like this:
34+
35+
```yml
36+
functions:
37+
hello:
38+
handler: handler.hello
39+
events:
40+
- http: GET hello
41+
42+
resources:
43+
Resources:
44+
HelloLambdaPermissionApiGateway:
45+
Properties:
46+
Principal: apigateway.amazonaws.com
47+
```
48+
49+
3. You cannot open your endpoint without [ICP Recordal](https://www.amazonaws.cn/en/about-aws/china/faqs/#new%20step). It always return `403 {"Message": null}`. Except your function authorize by IAM:
50+
51+
```yml
52+
functions:
53+
hello:
54+
handler: handler.hello
55+
events:
56+
- http:
57+
method: get
58+
path: hello
59+
authorizer: aws_iam
60+
```
61+
62+
Consider try [postman](http://getpostman.com) for test your endpoint with AWS4 Authorization header.
63+
64+
4. Don't set environment in your provider or functions. It's not supported in `cn-north-1` region.
65+
66+
```yml
67+
provider:
68+
name: aws
69+
region: cn-north-1
70+
endpointType: REGIONAL
71+
runtime: nodejs6.10
72+
# Lambda environment is not supported yet!
73+
# environment:
74+
# DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
75+
functions:
76+
hello:
77+
# environment:
78+
# NODE_ENV: production
79+
```
80+
81+
5. Don't waste time on Cognito User Pool (trigger or auth). Only [Federate Identities](http://docs.amazonaws.cn/en_us/aws/latest/userguide/cognito.html) available now.
82+
```yml
83+
functions:
84+
preSignUp:
85+
handler: preSignUp.handler
86+
events:
87+
- http:
88+
path: posts/create
89+
method: post
90+
# This ARN is not exists.
91+
# authorizer: arn:aws-cn:cognito-idp:cn-north-1:xxx:userpool/cn-north-1_ZZZ
92+
# This event trigger not work!
93+
# - cognitoUserPool:
94+
# pool: MyUserPool
95+
# trigger: PreSignUp
96+
```
97+
98+
6. The builtin `aws-sdk` version is `2.190.0`. [Doc](https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/current-supported-versions.html) expired.
99+
100+
[ico-license]: https://img.shields.io/github/license/vitarn/serverless-aws-cn.svg
101+
[ico-npm]: https://img.shields.io/npm/v/serverless-aws-cn.svg
102+
[ico-build]: https://travis-ci.org/vitarn/serverless-aws-cn.svg?branch=master
103+
[ico-codecov]: https://codecov.io/gh/vitarn/serverless-aws-cn/branch/master/graph/badge.svg
104+
105+
[link-license]: ./blob/master/LICENSE
106+
[link-npm]: https://www.npmjs.com/package/serverless-aws-cn
107+
[link-build]: https://travis-ci.org/vitarn/serverless-aws-cn
108+
[link-codecov]: https://codecov.io/gh/vitarn/serverless-aws-cn

lib/index.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict'
2+
3+
const chalk = require('chalk')
4+
5+
/**
6+
* Validate and tune AWS CN region config
7+
*
8+
* * Assert provider.endpointType is regional
9+
* * Prevent provider.environment
10+
* * Prevent function.environment
11+
* * Prevent function.awsKmsKeyArn
12+
* * Replace YourFuncLambdaPermissionApiGateway.Properties.Principal with `apigateway.amazonaws.com`
13+
*
14+
* @see https://github.com/serverless/serverless/pull/4665#issuecomment-365843810
15+
*/
16+
module.exports = function serverlessAWSCN(serverless, options) {
17+
const service = serverless.service
18+
const provider = service.provider
19+
const name = provider.name
20+
const region = options.region || provider.region || ''
21+
22+
const isAWSCN = name === 'aws' && region.substr(0, 3) === 'cn-'
23+
24+
if (!isAWSCN) return
25+
26+
const warn = (function (message) {
27+
this.cli.consoleLog('Serverless: ' + chalk.redBright('FAILURE: ' + message))
28+
return message
29+
}).bind(serverless)
30+
31+
function fail(message) {
32+
warn(message)
33+
throw new Error(message)
34+
}
35+
36+
this.hooks = {
37+
'before:package:compileFunctions': function () {
38+
if (provider.endpointType && provider.endpointType.toLowerCase() !== 'regional') {
39+
fail(`AWS CN provider endpointType must be 'regional'!
40+
provider.endpointType: ${provider.endpointType}`)
41+
}
42+
43+
if (provider.environment) {
44+
fail(`AWS CN provider environment is not supported!
45+
provider.environment: ${provider.environment}`)
46+
}
47+
48+
service.getAllFunctions().forEach(function (functionName) {
49+
const functionObject = service.getFunction(functionName)
50+
51+
if (functionObject.environment) {
52+
fail(`AWS CN lambda function environment is not supported!
53+
functions.${functionName}.environment: ${functionObject.environment}`)
54+
}
55+
56+
if (functionObject.awsKmsKeyArn) {
57+
fail(`AWS CN KMS service is not supported!
58+
functions.${functionName}.awsKmsKeyArn: ${functionObject.awsKmsKeyArn}`)
59+
}
60+
})
61+
},
62+
63+
'before:aws:package:finalize:saveServiceState': function () {
64+
const template = provider.compiledCloudFormationTemplate
65+
66+
Object.keys(template.Resources)
67+
.forEach(key => {
68+
const res = template.Resources[key]
69+
70+
if (res.Type === 'AWS::Lambda::Permission') {
71+
serverless.cli.log(`Replace ${key} Principal 'AWS::URLSuffix' to 'amazonaws.com'`)
72+
res.Properties.Principal['Fn::Join'][1][1] = 'amazonaws.com'
73+
}
74+
})
75+
},
76+
}
77+
}

package.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "serverless-aws-cn",
3+
"version": "0.0.1",
4+
"description": "Serverless plugin compatible with aws cn",
5+
"main": "lib",
6+
"repository": "https://github.com/vitarn/serverless-aws-cn.git",
7+
"author": "colder <[email protected]>",
8+
"license": "MIT",
9+
"scripts": {
10+
"test": "tap --cov test"
11+
},
12+
"dependencies": {
13+
"chalk": "^2.0.0",
14+
"lodash": "^4.13.1"
15+
},
16+
"devDependencies": {
17+
"standard-version": "^4.3.0",
18+
"tap": "^11.1.3"
19+
},
20+
"files": [
21+
"lib"
22+
]
23+
}

test/basic.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const tap = require('tap')
2+
const Plugin = require('../lib')
3+
4+
const test = tap.test
5+
6+
test('do nothing if non aws cn region', t => {
7+
let plugin = new Plugin({
8+
service: {
9+
provider: {}
10+
}
11+
}, {})
12+
13+
t.is(plugin.hooks, undefined)
14+
t.end()
15+
})
16+
17+
test('create plugin if in aws cn region', t => {
18+
let plugin = new Plugin({
19+
service: {
20+
provider: {
21+
name: 'aws'
22+
}
23+
}
24+
}, { region: 'cn-north-1' })
25+
26+
t.ok(plugin.hooks)
27+
t.end()
28+
})

test/compileFunctionsHook.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const tap = require('tap')
2+
const Plugin = require('../lib')
3+
const template = require('./template.json')
4+
5+
const test = tap.test
6+
7+
test('before:package:compileFunctions', t => {
8+
let serverless = {
9+
service: {
10+
provider: {
11+
name: 'aws'
12+
},
13+
getAllFunctions() {
14+
return ['test']
15+
},
16+
getFunction(name) {
17+
return {}
18+
}
19+
},
20+
cli: {
21+
consoleLog: () => { }
22+
}
23+
}
24+
let options = { region: 'cn-north-1' }
25+
let plugin = new Plugin(serverless, options)
26+
let hook = plugin.hooks['before:package:compileFunctions']
27+
28+
t.notThrow(hook)
29+
t.end()
30+
})
31+
32+
test('provider endpointType', t => {
33+
let serverless = {
34+
service: {
35+
provider: {
36+
name: 'aws',
37+
endpointType: 'edge'
38+
}
39+
},
40+
cli: {
41+
consoleLog: () => { }
42+
}
43+
}
44+
let options = { region: 'cn-north-1' }
45+
let plugin = new Plugin(serverless, options)
46+
let hook = plugin.hooks['before:package:compileFunctions']
47+
48+
t.throws(hook, `provider endpointType must be 'regional'`)
49+
t.end()
50+
})
51+
52+
test('provider environment', t => {
53+
let serverless = {
54+
service: {
55+
provider: {
56+
name: 'aws',
57+
environment: {}
58+
}
59+
},
60+
cli: {
61+
consoleLog: () => { }
62+
}
63+
}
64+
let options = { region: 'cn-north-1' }
65+
let plugin = new Plugin(serverless, options)
66+
let hook = plugin.hooks['before:package:compileFunctions']
67+
68+
t.throws(hook, 'provider environment is not supported')
69+
t.end()
70+
})
71+
72+
test('function environment', t => {
73+
let serverless = {
74+
service: {
75+
provider: {
76+
name: 'aws'
77+
},
78+
getAllFunctions() {
79+
return ['test']
80+
},
81+
getFunction(name) {
82+
return {
83+
environment: {}
84+
}
85+
}
86+
},
87+
cli: {
88+
consoleLog: () => { }
89+
}
90+
}
91+
let options = { region: 'cn-north-1' }
92+
let plugin = new Plugin(serverless, options)
93+
let hook = plugin.hooks['before:package:compileFunctions']
94+
95+
t.throws(hook, 'function environment is not supported')
96+
t.end()
97+
})
98+
99+
test('function environment', t => {
100+
let serverless = {
101+
service: {
102+
provider: {
103+
name: 'aws'
104+
},
105+
getAllFunctions() {
106+
return ['test']
107+
},
108+
getFunction(name) {
109+
return {
110+
awsKmsKeyArn: 'arn:'
111+
}
112+
}
113+
},
114+
cli: {
115+
consoleLog: () => { }
116+
}
117+
}
118+
let options = { region: 'cn-north-1' }
119+
let plugin = new Plugin(serverless, options)
120+
let hook = plugin.hooks['before:package:compileFunctions']
121+
122+
t.throws(hook, 'KMS service is not supported')
123+
t.end()
124+
})

0 commit comments

Comments
 (0)