Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit c003368

Browse files
committed
Automatically secure web actions for HTTP endpoints.
Fixes #107
1 parent afed1bc commit c003368

File tree

4 files changed

+56
-5
lines changed

4 files changed

+56
-5
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,8 @@ Date: Mon, 19 Dec 2016 15:47:53 GMT
711711
}
712712
````
713713

714+
Functions exposed through the API Gateway service are automatically converted into Web Actions during deployment. The framework [secures Web Actions for HTTP endpoints](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md#securing-web-actions) using the `require-whisk-auth` annotation. If the `require-whisk-auth` annotation is manually configured, the existing annotation value is used, otherwise a random token is automatically generated.
715+
714716
## Exporting Web Actions
715717

716718
Functions can be turned into "*web actions*" which return HTTP content without use of an API Gateway. This feature is enabled by setting an annotation (`web-export`) in the configuration file.

compile/apigw/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const BbPromise = require('bluebird');
4+
const crypto = require('crypto');
45

56
class OpenWhiskCompileHttpEvents {
67
constructor(serverless, options) {
@@ -28,6 +29,10 @@ class OpenWhiskCompileHttpEvents {
2829
}
2930
}
3031

32+
generateAuthString() {
33+
return crypto.randomBytes(64).toString('hex')
34+
}
35+
3136
// HTTP events need Web Actions enabled for those functions. Add
3237
// annotation 'web-export' if it is not already present.
3338
addWebAnnotations() {
@@ -38,6 +43,10 @@ class OpenWhiskCompileHttpEvents {
3843
if (httpEvents.length) {
3944
if (!f.annotations) f.annotations = {}
4045
f.annotations['web-export'] = true
46+
47+
if (!f.annotations.hasOwnProperty('require-whisk-auth')) {
48+
f.annotations['require-whisk-auth'] = this.generateAuthString()
49+
}
4150
}
4251
})
4352

@@ -56,6 +65,11 @@ class OpenWhiskCompileHttpEvents {
5665
|| '_';
5766
}
5867

68+
retrieveAuthKey(functionObject) {
69+
const annotations = functionObject.annotations || {}
70+
return annotations['require-whisk-auth']
71+
}
72+
5973
//
6074
// This method takes the rule definitions, parsed from the user's YAML file,
6175
// and turns it into the OpenWhisk Rule resource object.
@@ -69,6 +83,12 @@ class OpenWhiskCompileHttpEvents {
6983
const options = this.parseHttpEvent(http);
7084
options.action = this.calculateFunctionName(funcName, funcObj);
7185
options.basepath = `/${this.serverless.service.service}`;
86+
87+
const secure_key = this.retrieveAuthKey(funcObj)
88+
if (secure_key) {
89+
options.secure_key = secure_key;
90+
}
91+
7292
return options;
7393
}
7494

compile/apigw/tests/index.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const crypto = require('crypto');
34
const BbPromise = require('bluebird');
45
const expect = require('chai').expect;
56
const chaiAsPromised = require('chai-as-promised');
@@ -46,11 +47,29 @@ describe('OpenWhiskCompileHttpEvents', () => {
4647
c: { events: [ { http: true } ], annotations: { 'web-export': false } },
4748
d: { events: [ { http: true } ] }
4849
}
50+
51+
const auth_string = crypto.randomBytes(64).toString('hex');
52+
sandbox.stub(openwhiskCompileHttpEvents, 'generateAuthString', () => auth_string);
53+
54+
return openwhiskCompileHttpEvents.addWebAnnotations().then(() => {
55+
expect(openwhiskCompileHttpEvents.serverless.service.functions.a.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': auth_string })
56+
expect(openwhiskCompileHttpEvents.serverless.service.functions.b.annotations).to.deep.equal({ foo: 'bar', 'web-export': true, 'require-whisk-auth': auth_string })
57+
expect(openwhiskCompileHttpEvents.serverless.service.functions.c.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': auth_string })
58+
expect(openwhiskCompileHttpEvents.serverless.service.functions.d.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': auth_string })
59+
})
60+
});
61+
62+
it('should not add auth annotation when annotation already present', () => {
63+
openwhiskCompileHttpEvents.serverless.service.functions = {
64+
a: { events: [ { http: true } ], annotations: { 'require-whisk-auth': false } },
65+
b: { events: [ { http: true } ], annotations: { 'require-whisk-auth': true } },
66+
c: { events: [ { http: true } ], annotations: { 'require-whisk-auth': 'some string' } }
67+
}
68+
4969
return openwhiskCompileHttpEvents.addWebAnnotations().then(() => {
50-
expect(openwhiskCompileHttpEvents.serverless.service.functions.a.annotations).to.deep.equal({ 'web-export': true })
51-
expect(openwhiskCompileHttpEvents.serverless.service.functions.b.annotations).to.deep.equal({ foo: 'bar', 'web-export': true })
52-
expect(openwhiskCompileHttpEvents.serverless.service.functions.c.annotations).to.deep.equal({ 'web-export': true })
53-
expect(openwhiskCompileHttpEvents.serverless.service.functions.d.annotations).to.deep.equal({ 'web-export': true })
70+
expect(openwhiskCompileHttpEvents.serverless.service.functions.a.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': false })
71+
expect(openwhiskCompileHttpEvents.serverless.service.functions.b.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': true })
72+
expect(openwhiskCompileHttpEvents.serverless.service.functions.c.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': 'some string' })
5473
})
5574
});
5675

@@ -152,6 +171,16 @@ describe('OpenWhiskCompileHttpEvents', () => {
152171
return expect(result).to.deep.equal({basepath: '/my-service', relpath: '/api/foo/bar', operation: 'GET', action: '/sample_ns/my-service_action-name', responsetype: 'json'});
153172
});
154173

174+
it('should add secure auth key if present', () => {
175+
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
176+
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
177+
const http = {path: "/api/foo/bar", method: "GET"}
178+
const result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {
179+
annotations: { 'require-whisk-auth': 'auth-token' }
180+
}, http);
181+
return expect(result).to.deep.equal({basepath: '/my-service', relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token', action: '/sample_ns/my-service_action-name', responsetype: 'json'});
182+
});
183+
155184
it('should define http events with explicit response type', () => {
156185
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
157186
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"jszip": "^3.1.3",
3838
"lodash": "^4.17.4",
3939
"moment": "^2.16.0",
40-
"openwhisk": "^3.4.0"
40+
"openwhisk": "^3.15.0"
4141
},
4242
"devDependencies": {
4343
"chai-as-promised": "^6.0.0",

0 commit comments

Comments
 (0)