Skip to content

Commit 5be4e75

Browse files
authored
Merge pull request #113 from horike37/apikeys
Apikeys
2 parents b701634 + 4d499ce commit 5be4e75

File tree

9 files changed

+553
-1
lines changed

9 files changed

+553
-1
lines changed

README.md

+54
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,60 @@ You can input an value as json in request body, the value is passed as the input
192192
```
193193
$ curl -XPOST https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/posts/create -d '{"foo":"bar"}'
194194
```
195+
196+
#### Setting API keys for your Rest API
197+
You can specify a list of API keys to be used by your service Rest API by adding an apiKeys array property to the provider object in serverless.yml. You'll also need to explicitly specify which endpoints are private and require one of the api keys to be included in the request by adding a private boolean property to the http event object you want to set as private. API Keys are created globally, so if you want to deploy your service to different stages make sure your API key contains a stage variable as defined below. When using API keys, you can optionally define usage plan quota and throttle, using usagePlan object.
198+
199+
Here's an example configuration for setting API keys for your service Rest API:
200+
201+
```yml
202+
service: my-service
203+
provider:
204+
name: aws
205+
apiKeys:
206+
- myFirstKey
207+
- ${opt:stage}-myFirstKey
208+
- ${env:MY_API_KEY} # you can hide it in a serverless variable
209+
usagePlan:
210+
quota:
211+
limit: 5000
212+
offset: 2
213+
period: MONTH
214+
throttle:
215+
burstLimit: 200
216+
rateLimit: 100
217+
functions:
218+
hello:
219+
handler: handler.hello
220+
221+
stepFunctions:
222+
stateMachines:
223+
statemachine1:
224+
name: ${self:service}-${opt:stage}-statemachine1
225+
events:
226+
- http:
227+
path: /hello
228+
method: post
229+
private: true
230+
definition:
231+
Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function"
232+
StartAt: HelloWorld1
233+
States:
234+
HelloWorld1:
235+
Type: Task
236+
Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${opt:stage}-hello
237+
End: true
238+
239+
240+
plugins:
241+
- serverless-step-functions
242+
- serverless-pseudo-parameters
243+
```
244+
245+
Please note that those are the API keys names, not the actual values. Once you deploy your service, the value of those API keys will be auto generated by AWS and printed on the screen for you to use. The values can be concealed from the output with the --conceal deploy option.
246+
247+
Clients connecting to this Rest API will then need to set any of these API keys values in the x-api-key header of their request. This is only necessary for functions where the private property is set to true.
248+
195249
### Schedule
196250
The following config will attach a schedule event and causes the stateMachine `crawl` to be called every 2 hours. The configuration allows you to attach multiple schedules to the same stateMachine. You can either use the `rate` or `cron` syntax. Take a look at the [AWS schedule syntax documentation](http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html) for more details.
197251

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
6+
module.exports = {
7+
compileApiKeys() {
8+
if (this.serverless.service.provider.apiKeys) {
9+
if (!Array.isArray(this.serverless.service.provider.apiKeys)) {
10+
throw new this.serverless.classes.Error('apiKeys property must be an array');
11+
}
12+
13+
_.forEach(this.serverless.service.provider.apiKeys, (apiKey, i) => {
14+
const apiKeyNumber = i + 1;
15+
16+
if (typeof apiKey !== 'string') {
17+
throw new this.serverless.classes.Error('API Keys must be strings');
18+
}
19+
20+
const apiKeyLogicalId = this.provider.naming
21+
.getApiKeyLogicalId(apiKeyNumber);
22+
23+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
24+
[apiKeyLogicalId]: {
25+
Type: 'AWS::ApiGateway::ApiKey',
26+
Properties: {
27+
Enabled: true,
28+
Name: apiKey,
29+
StageKeys: [{
30+
RestApiId: { Ref: this.apiGatewayRestApiLogicalId },
31+
StageName: this.provider.getStage(),
32+
}],
33+
},
34+
DependsOn: this.apiGatewayDeploymentLogicalId,
35+
},
36+
});
37+
});
38+
}
39+
return BbPromise.resolve();
40+
},
41+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const Serverless = require('serverless/lib/Serverless');
5+
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider');
6+
const ServerlessStepFunctions = require('./../../../index');
7+
8+
describe('#methods()', () => {
9+
let serverless;
10+
let serverlessStepFunctions;
11+
12+
beforeEach(() => {
13+
serverless = new Serverless();
14+
const options = {
15+
stage: 'dev',
16+
region: 'us-east-1',
17+
};
18+
serverless.setProvider('aws', new AwsProvider(serverless));
19+
serverless.service.provider.apiKeys = ['1234567890'];
20+
serverless.service.provider.compiledCloudFormationTemplate = {
21+
Resources: {},
22+
};
23+
serverlessStepFunctions = new ServerlessStepFunctions(serverless, options);
24+
serverlessStepFunctions.serverless.service.stepFunctions = {
25+
stateMachines: {
26+
first: {},
27+
},
28+
};
29+
serverlessStepFunctions.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
30+
serverlessStepFunctions.apiGatewayDeploymentLogicalId = 'ApiGatewayDeploymentTest';
31+
});
32+
33+
it('should compile api key resource', () =>
34+
serverlessStepFunctions.compileApiKeys().then(() => {
35+
expect(
36+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
37+
.Resources[
38+
serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1)
39+
].Type
40+
).to.equal('AWS::ApiGateway::ApiKey');
41+
42+
expect(
43+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
44+
.Resources[
45+
serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1)
46+
].Properties.Enabled
47+
).to.equal(true);
48+
49+
expect(
50+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
51+
.Resources[
52+
serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1)
53+
].Properties.Name
54+
).to.equal('1234567890');
55+
56+
expect(
57+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
58+
.Resources[
59+
serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1)
60+
].Properties.StageKeys[0].RestApiId.Ref
61+
).to.equal('ApiGatewayRestApi');
62+
63+
expect(
64+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
65+
.Resources[
66+
serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1)
67+
].Properties.StageKeys[0].StageName
68+
).to.equal('dev');
69+
70+
expect(
71+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
72+
.Resources[
73+
serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1)
74+
].DependsOn
75+
).to.equal('ApiGatewayDeploymentTest');
76+
})
77+
);
78+
79+
it('throw error if apiKey property is not an array', () => {
80+
serverlessStepFunctions.serverless.service.provider.apiKeys = 2;
81+
expect(() => serverlessStepFunctions.compileApiKeys()).to.throw(Error);
82+
});
83+
84+
it('throw error if an apiKey is not a string', () => {
85+
serverlessStepFunctions.serverless.service.provider.apiKeys = [2];
86+
expect(() => serverlessStepFunctions.compileApiKeys()).to.throw(Error);
87+
});
88+
});
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
6+
module.exports = {
7+
compileUsagePlan() {
8+
if (this.serverless.service.provider.usagePlan || this.serverless.service.provider.apiKeys) {
9+
this.apiGatewayUsagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId();
10+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
11+
[this.apiGatewayUsagePlanLogicalId]: {
12+
Type: 'AWS::ApiGateway::UsagePlan',
13+
DependsOn: this.apiGatewayDeploymentLogicalId,
14+
Properties: {
15+
ApiStages: [
16+
{
17+
ApiId: {
18+
Ref: this.apiGatewayRestApiLogicalId,
19+
},
20+
Stage: this.provider.getStage(),
21+
},
22+
],
23+
Description: `Usage plan for ${this.serverless.service.service} ${
24+
this.provider.getStage()} stage`,
25+
UsagePlanName: `${this.serverless.service.service}-${
26+
this.provider.getStage()}`,
27+
},
28+
},
29+
});
30+
if (_.has(this.serverless.service.provider, 'usagePlan.quota')
31+
&& this.serverless.service.provider.usagePlan.quota !== null) {
32+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
33+
[this.apiGatewayUsagePlanLogicalId]: {
34+
Properties: {
35+
Quota: _.merge(
36+
{ Limit: this.serverless.service.provider.usagePlan.quota.limit },
37+
{ Offset: this.serverless.service.provider.usagePlan.quota.offset },
38+
{ Period: this.serverless.service.provider.usagePlan.quota.period }),
39+
},
40+
},
41+
});
42+
}
43+
if (_.has(this.serverless.service.provider, 'usagePlan.throttle')
44+
&& this.serverless.service.provider.usagePlan.throttle !== null) {
45+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
46+
[this.apiGatewayUsagePlanLogicalId]: {
47+
Properties: {
48+
Throttle: _.merge(
49+
{ BurstLimit: this.serverless.service.provider.usagePlan.throttle.burstLimit },
50+
{ RateLimit: this.serverless.service.provider.usagePlan.throttle.rateLimit }),
51+
},
52+
},
53+
});
54+
}
55+
}
56+
return BbPromise.resolve();
57+
},
58+
};

0 commit comments

Comments
 (0)