UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

148 lines 6.01 kB
"use strict"; /* * Copyright © 2020 Atomist, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.signGoal = exports.verifyGoal = exports.GoalSigningAutomationEventListener = exports.DefaultGoalSigningAlgorithm = void 0; const logger_1 = require("@atomist/automation-client/lib/util/logger"); const fs = require("fs-extra"); const path = require("path"); const storeGoals_1 = require("../../api-helper/goal/storeGoals"); const SigningKeys_1 = require("../../api/machine/SigningKeys"); const types_1 = require("../../typings/types"); const array_1 = require("../util/misc/array"); const jwtGoalSigning_1 = require("./jwtGoalSigning"); const rsaGoalSigning_1 = require("./rsaGoalSigning"); exports.DefaultGoalSigningAlgorithm = rsaGoalSigning_1.RsaGoalSigningAlgorithm; /** * AutomationEventListener that verifies incoming SDM goals against a set of configurable * verification public keys. * * Optionally a private key can be specified to sign outgoing goals. Setting this is strongly * recommended to prevent executing untrusted and/or tampered SDM goals. */ class GoalSigningAutomationEventListener { constructor(gsc) { this.gsc = gsc; this.initVerificationKeys(); } async onMutation(options) { var _a; if (options.name === "UpdateSdmGoal") { const goal = (_a = options.variables) === null || _a === void 0 ? void 0 : _a.goal; if (!!goal) { options.variables.goal = await signGoal(goal, this.gsc); } } return options; } initVerificationKeys() { this.gsc.verificationKeys = array_1.toArray(this.gsc.verificationKeys) || []; // If signing key is set, also use it to verify if (!!this.gsc.signingKey) { this.gsc.verificationKeys.push(this.gsc.signingKey); } // Load the Atomist public key const publicKey = fs.readFileSync(path.join(__dirname, "atomist-public.pem")).toString(); this.gsc.verificationKeys.push({ publicKey, name: "atomist.com/sdm" }); } } exports.GoalSigningAutomationEventListener = GoalSigningAutomationEventListener; /** * Verify a goal signature against the public keys configured in provided Configuration. * If signature can't be verified, the goal will be marked as failed and an Error will be thrown. * @param goal goal to verify * @param gsc signing configuration * @param ctx */ async function verifyGoal(goal, gsc, ctx) { if (!!gsc && gsc.enabled === true && !!goal && isInScope(gsc.scope, ctx) && !isGoalRejected(goal)) { if (!!goal.signature) { let verifiedWith; let verifiedGoal; for (const key of array_1.toArray(gsc.verificationKeys)) { const algorithm = findAlgorithm(key, gsc); verifiedGoal = await algorithm.verify(goal, goal.signature, key); if (verifiedGoal) { verifiedWith = key; break; } } if (!!verifiedWith) { logger_1.logger.debug(`Verified signature for incoming goal '${goal.uniqueName}' of '${goal.goalSetId}' with key '${verifiedWith.name}' and algorithm '${verifiedWith.algorithm || exports.DefaultGoalSigningAlgorithm.name}'`); return verifiedGoal; } else { await rejectGoal("signature invalid", goal, ctx); throw new Error("SDM goal signature invalid. Rejecting goal!"); } } else { await rejectGoal("signature missing", goal, ctx); throw new Error("SDM goal signature is missing. Rejecting goal!"); } } else { return goal; } } exports.verifyGoal = verifyGoal; /** * Add a signature to a goal * @param goal * @param gsc */ async function signGoal(goal, gsc) { if (!!gsc && gsc.enabled === true && !!gsc.signingKey) { goal.signature = await findAlgorithm(gsc.signingKey, gsc).sign(goal, gsc.signingKey); logger_1.logger.debug(`Signed goal '${goal.uniqueName}' of '${goal.goalSetId}'`); return goal; } else { return goal; } } exports.signGoal = signGoal; async function rejectGoal(reason, sdmGoal, ctx) { await storeGoals_1.updateGoal(ctx, sdmGoal, { state: types_1.SdmGoalState.failure, description: `Rejected: ${sdmGoal.name}`, phase: reason, }); } function findAlgorithm(key, gsc) { const algorithm = [...array_1.toArray(gsc.algorithms || []), exports.DefaultGoalSigningAlgorithm, jwtGoalSigning_1.JwtGoalSigningAlgorithm] .find(a => a.name.toLowerCase() === (key.algorithm || exports.DefaultGoalSigningAlgorithm.name).toLowerCase()); if (!algorithm) { throw new Error(`Goal signing or verification key '${key.name}' requested algorithm '${key.algorithm}' which isn't configured`); } return algorithm; } function isInScope(scope, ctx) { if (scope === SigningKeys_1.GoalSigningScope.All) { return true; } else if (scope === SigningKeys_1.GoalSigningScope.Fulfillment && ctx.context.operation === "FulfillGoalOnRequested") { return true; } else { return false; } } function isGoalRejected(sdmGoal) { return sdmGoal.state === types_1.SdmGoalState.failure && sdmGoal.description === `Rejected: ${sdmGoal.name}`; } //# sourceMappingURL=goalSigning.js.map