Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for signing custom events
- Loading branch information
Showing
18 changed files
with
16,127 additions
and
958 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { GraphClientListener } from "@atomist/automation-client/lib/graph/ApolloGraphClient"; | ||
import { HandleEvent } from "@atomist/automation-client/lib/HandleEvent"; | ||
import { metadataFromInstance } from "@atomist/automation-client/lib/internal/metadata/metadataReading"; | ||
import { EventHandlerMetadata } from "@atomist/automation-client/lib/metadata/automationMetadata"; | ||
import { | ||
Maker, | ||
toFactory, | ||
} from "@atomist/automation-client/lib/util/constructionUtils"; | ||
import { logger } from "@atomist/automation-client/lib/util/logger"; | ||
import { MutationOptions } from "@atomist/automation-client/src/lib/spi/graph/GraphClient"; | ||
import * as crypto from "crypto"; | ||
import * as _ from "lodash"; | ||
import { EventSigningConfiguration } from "../../api/machine/SigningKeys"; | ||
import { toArray } from "../util/misc/array"; | ||
|
||
/** | ||
* AutomationEventListener that signs outgoing custom events with a configurable | ||
* JWS signature key. | ||
*/ | ||
export class EventSigningAutomationEventListener implements GraphClientListener<any> { | ||
|
||
constructor(private readonly esc: EventSigningConfiguration) { | ||
this.initVerificationKeys(); | ||
} | ||
|
||
public async onMutation(options: MutationOptions<any>): Promise<MutationOptions<any>> { | ||
|
||
if (eventMatch(options.name, this.esc.events)) { | ||
const privateKey = crypto.createPrivateKey({ | ||
key: this.esc.signingKey.privateKey, | ||
passphrase: this.esc.signingKey.passphrase, | ||
}); | ||
const { default: CompactSign } = require("jose/jws/compact/sign"); | ||
for (const key of Object.getOwnPropertyNames(options.variables || {})) { | ||
const value = options.variables[key]; | ||
const jws = await new CompactSign(Buffer.from(JSON.stringify(value))) | ||
.setProtectedHeader({ alg: "ES512" }) | ||
.sign(privateKey); | ||
value.signature = jws; | ||
logger.debug(`Signed custom event '${options.name}'`); | ||
} | ||
} | ||
|
||
return options; | ||
} | ||
|
||
private initVerificationKeys(): void { | ||
this.esc.verificationKeys = toArray(this.esc.verificationKeys) || []; | ||
|
||
// If signing key is set, also use it to verify | ||
if (!!this.esc.signingKey) { | ||
this.esc.verificationKeys.push(this.esc.signingKey); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Wrap every event handler that is registered and its subscription name matches a configurable set of | ||
* regular expression patterns for event signature verification. | ||
*/ | ||
export function wrapEventHandlersToVerifySignature(handlers: Array<Maker<HandleEvent<any>>>, | ||
options: EventSigningConfiguration): Array<Maker<HandleEvent<any>>> { | ||
const wh: Array<Maker<HandleEvent<any>>> = []; | ||
for (const handler of handlers) { | ||
const instance = toFactory(handler)(); | ||
const md = metadataFromInstance(instance) as EventHandlerMetadata; | ||
if (eventMatch(md.subscriptionName, options.events)) { | ||
wh.push(() => ({ | ||
...md, | ||
handle: async (e, ctx, params) => { | ||
const { default: compactVerify } = require("jose/jws/compact/verify"); | ||
for (const key of Object.getOwnPropertyNames(e.data)) { | ||
const evv = e.data[key][0]; | ||
if (!evv.signature) { | ||
throw new Error("Signature missing on incoming event"); | ||
} | ||
let verified = false; | ||
for (const pkey of toArray(options.verificationKeys)) { | ||
const publicKey = crypto.createPublicKey({ | ||
key: pkey.publicKey, | ||
}); | ||
try { | ||
const { payload } = await compactVerify(evv.signature, publicKey); | ||
e.data[key][0] = _.merge({}, evv, JSON.parse(Buffer.from(payload).toString())); | ||
verified = true; | ||
logger.debug(`Verified signature on custom event '${md.subscriptionName}'`); | ||
break; | ||
} catch (e) { | ||
// return undefined; | ||
} | ||
} | ||
if (!verified) { | ||
throw new Error("Signature verification failed for incoming event"); | ||
} | ||
} | ||
|
||
return instance.handle(e, ctx, params); | ||
}, | ||
})); | ||
} else { | ||
wh.push(handler); | ||
} | ||
} | ||
|
||
return wh; | ||
} | ||
|
||
function eventMatch(event: string, patterns: string[]): boolean { | ||
for (const pattern of patterns) { | ||
if (new RegExp(pattern).test(event)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} |
Oops, something went wrong.