renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
148 lines • 6.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.setPrivateKey = setPrivateKey;
exports.writePrivateKey = writePrivateKey;
exports.configSigningKey = configSigningKey;
const tslib_1 = require("tslib");
const node_os_1 = tslib_1.__importDefault(require("node:os"));
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const upath_1 = tslib_1.__importDefault(require("upath"));
const error_messages_1 = require("../../constants/error-messages");
const logger_1 = require("../../logger");
const exec_1 = require("../exec");
const regex_1 = require("../regex");
const sanitize_1 = require("../sanitize");
const string_1 = require("../string");
const sshKeyRegex = (0, regex_1.regEx)(/-----BEGIN ([A-Z ]+ )?PRIVATE KEY-----.*?-----END ([A-Z]+ )?PRIVATE KEY-----/, 's');
let gitPrivateKey;
/**
* Decodes Base64 string if roundtrip encoding matches
*/
function tryBase64(value) {
const decodedValue = (0, string_1.fromBase64)(value);
const encodedValue = (0, string_1.toBase64)(decodedValue);
if (value !== encodedValue) {
return null;
}
return decodedValue;
}
class PrivateKey {
key;
passphrase;
keyId;
constructor(key, passphrase) {
const decodedKey = tryBase64(key);
if (decodedKey) {
this.key = decodedKey;
(0, sanitize_1.addSecretForSanitizing)(key, 'global');
logger_1.logger.debug('gitPrivateKey: decoded key from Base64');
}
else {
this.key = key;
}
(0, sanitize_1.addSecretForSanitizing)(this.key, 'global');
this.passphrase = passphrase;
if (this.passphrase) {
(0, sanitize_1.addSecretForSanitizing)(this.passphrase, 'global');
}
logger_1.logger.debug('gitPrivateKey: successfully set (but not yet written/configured)');
}
async writeKey() {
try {
this.keyId ??= await this.importKey();
logger_1.logger.debug('gitPrivateKey: imported');
}
catch (err) {
logger_1.logger.warn({ err }, 'gitPrivateKey: error importing');
throw new Error(error_messages_1.PLATFORM_GPG_FAILED);
}
}
async configSigningKey(cwd) {
logger_1.logger.debug('gitPrivateKey: configuring commit signing');
// TODO: types (#22198)
await (0, exec_1.exec)(`git config user.signingkey ${this.keyId}`, { cwd });
await (0, exec_1.exec)(`git config commit.gpgsign true`, { cwd });
await (0, exec_1.exec)(`git config gpg.format ${this.gpgFormat}`, { cwd });
}
}
class GPGKey extends PrivateKey {
gpgFormat = 'openpgp';
constructor(key, passphrase) {
super(key.trim(), passphrase);
if (passphrase) {
logger_1.logger.warn('Passphrase is not yet supported for GPG keys, it will be ignored');
}
}
async importKey() {
const keyFileName = upath_1.default.join(node_os_1.default.tmpdir() + '/git-private-gpg.key');
await fs_extra_1.default.outputFile(keyFileName, this.key);
const { stdout, stderr } = await (0, exec_1.exec)(
// --batch --no-tty flags allow Renovate to skip warnings about unsupported algorithms in the key
`gpg --batch --no-tty --import ${keyFileName}`);
logger_1.logger.debug({ stdout, stderr }, 'Private key import result');
await fs_extra_1.default.remove(keyFileName);
return `${stdout}${stderr}`
.split(regex_1.newlineRegex)
.find((line) => line.includes('secret key imported'))
?.replace('gpg: key ', '')
.split(':')
.shift();
}
}
class SSHKey extends PrivateKey {
gpgFormat = 'ssh';
async importKey() {
const keyFileName = upath_1.default.join(node_os_1.default.tmpdir() + '/git-private-ssh.key');
await fs_extra_1.default.outputFile(keyFileName, this.key.replace(/\n?$/, '\n'));
process.on('exit', () => fs_extra_1.default.removeSync(keyFileName));
await fs_extra_1.default.chmod(keyFileName, 0o600);
// If there's a passphrase, decrypt the private key and save without passphrase
if (this.passphrase) {
await (0, exec_1.exec)(
// -p: change passphrase
// -f: key file
// -P: old passphrase
// -N: new passphrase (empty = no passphrase)
`ssh-keygen -p -f ${keyFileName} -P "${this.passphrase}" -N ""`);
}
// HACK: `git` calls `ssh-keygen -Y sign ...` internally for SSH-based
// commit signing. Technically, only the private key is needed for signing,
// but `ssh-keygen` has an implementation quirk which requires also the
// public key file to exist. Therefore, we derive the public key from the
// private key just to satisfy `ssh-keygen` until the problem has been
// resolved.
// https://github.com/renovatebot/renovate/issues/18197#issuecomment-2152333710
const { stdout } = await (0, exec_1.exec)(`ssh-keygen -y -f ${keyFileName}`);
const pubFileName = `${keyFileName}.pub`;
await fs_extra_1.default.outputFile(pubFileName, stdout);
process.on('exit', () => fs_extra_1.default.removeSync(pubFileName));
return keyFileName;
}
}
function getPrivateKeyFormat(key) {
return sshKeyRegex.test(key) ? 'ssh' : 'gpg';
}
function createPrivateKey(key, passphrase) {
switch (getPrivateKeyFormat(key)) {
case 'gpg':
logger_1.logger.debug('gitPrivateKey: GPG key detected');
return new GPGKey(key, passphrase);
case 'ssh':
logger_1.logger.debug('gitPrivateKey: SSH key detected');
return new SSHKey(key, passphrase);
}
}
function setPrivateKey(key, passphrase) {
if (!is_1.default.nonEmptyStringAndNotWhitespace(key)) {
return;
}
gitPrivateKey = createPrivateKey(key, passphrase);
}
async function writePrivateKey() {
await gitPrivateKey?.writeKey();
}
async function configSigningKey(cwd) {
await gitPrivateKey?.configSigningKey(cwd);
}
//# sourceMappingURL=private-key.js.map