mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
feat(template): use environment variables in templates (#19301)
This commit is contained in:
parent
662fe78958
commit
2cd10769f2
5 changed files with 55 additions and 3 deletions
|
@ -363,8 +363,15 @@ If this option is not set, Renovate will fallback to 15 minutes.
|
|||
## exposeAllEnv
|
||||
|
||||
To keep you safe, Renovate only passes a limited set of environment variables to package managers.
|
||||
Confidential data can be leaked if a malicious script enumerates all environment variables.
|
||||
If you must expose all environment variables to package managers, you can set this option to `true`.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! warning
|
||||
Always consider the security implications of using `exposeAllEnv`!
|
||||
Secrets and other confidential information stored in environment variables could be leaked by a malicious script, that enumerates all environment variables.
|
||||
|
||||
Set `exposeAllEnv` to `true` only if you have reviewed, and trust, the repositories which Renovate bot runs against.
|
||||
Alternatively, you can use the [`customEnvVariables`](https://docs.renovatebot.com/self-hosted-configuration/#customenvvariables) config option to handpick a set of variables you need to expose.
|
||||
|
||||
Setting this to `true` also allows for variable substitution in `.npmrc` files.
|
||||
|
||||
|
|
|
@ -95,3 +95,14 @@ In the example above, it will only show a text if `isMajor=true` and `hasRelease
|
|||
Returns `true` if at least one expression is `true`.
|
||||
|
||||
`{{#if (or isPatch isSingleVersion}}Small update, safer to merge and release.{{else}}Check out the changelog for all versions before merging!{{/if}}`
|
||||
|
||||
## Environment variables
|
||||
|
||||
By default, you can only access a handful of basic environment variables like `HOME` or `PATH`.
|
||||
This is for security reasons.
|
||||
|
||||
`HOME is {{env.HOME}}`
|
||||
|
||||
If you're self-hosting Renovate, you can expose additional variables with the [`customEnvVariables`](https://docs.renovatebot.com/self-hosted-configuration/#customenvvariables) config option.
|
||||
|
||||
You can also use the [`exposeAllEnv`](https://docs.renovatebot.com/self-hosted-configuration/#exposeallenv) config option to allow all environment variables in templates, but make sure to consider the security implications of giving the scripts unrestricted access to all variables.
|
||||
|
|
|
@ -21,7 +21,7 @@ import type {
|
|||
RawExecOptions,
|
||||
} from './types';
|
||||
|
||||
function getChildEnv({
|
||||
export function getChildEnv({
|
||||
extraEnv,
|
||||
env: forcedEnv = {},
|
||||
}: ExecOptions): Record<string, string> {
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
import { mocked } from '../../../test/util';
|
||||
import { getOptions } from '../../config/options';
|
||||
import * as _exec from '../exec';
|
||||
import * as template from '.';
|
||||
|
||||
jest.mock('../exec');
|
||||
|
||||
const exec = mocked(_exec);
|
||||
|
||||
describe('util/template/index', () => {
|
||||
beforeEach(() => {
|
||||
exec.getChildEnv.mockReturnValue({
|
||||
CUSTOM_FOO: 'foo',
|
||||
HOME: '/root',
|
||||
});
|
||||
});
|
||||
|
||||
it('has valid exposed config options', () => {
|
||||
const allOptions = getOptions().map((option) => option.name);
|
||||
const missingOptions = template.exposedConfigOptions.filter(
|
||||
|
@ -85,6 +98,18 @@ describe('util/template/index', () => {
|
|||
expect(output).toBe('foo');
|
||||
});
|
||||
|
||||
it('has access to basic environment variables (basicEnvVars)', () => {
|
||||
const userTemplate = 'HOME is {{env.HOME}}';
|
||||
const output = template.compile(userTemplate, {});
|
||||
expect(output).toBe('HOME is /root');
|
||||
});
|
||||
|
||||
it('and has access to custom variables (customEnvVariables)', () => {
|
||||
const userTemplate = 'CUSTOM_FOO is {{env.CUSTOM_FOO}}';
|
||||
const output = template.compile(userTemplate, {});
|
||||
expect(output).toBe('CUSTOM_FOO is foo');
|
||||
});
|
||||
|
||||
describe('proxyCompileInput', () => {
|
||||
const allowedField = 'body';
|
||||
const forbiddenField = 'foobar';
|
||||
|
|
|
@ -2,6 +2,7 @@ import is from '@sindresorhus/is';
|
|||
import handlebars from 'handlebars';
|
||||
import { GlobalConfig } from '../../config/global';
|
||||
import { logger } from '../../logger';
|
||||
import { getChildEnv } from '../exec';
|
||||
|
||||
handlebars.registerHelper('encodeURIComponent', encodeURIComponent);
|
||||
handlebars.registerHelper('decodeURIComponent', decodeURIComponent);
|
||||
|
@ -170,6 +171,10 @@ const allowedTemplateFields = new Set([
|
|||
|
||||
const compileInputProxyHandler: ProxyHandler<CompileInput> = {
|
||||
get(target: CompileInput, prop: keyof CompileInput): unknown {
|
||||
if (prop === 'env') {
|
||||
return target[prop];
|
||||
}
|
||||
|
||||
if (!allowedTemplateFields.has(prop)) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -202,13 +207,17 @@ export function compile(
|
|||
input: CompileInput,
|
||||
filterFields = true
|
||||
): string {
|
||||
const data = { ...GlobalConfig.get(), ...input };
|
||||
const env = getChildEnv({});
|
||||
const data = { ...GlobalConfig.get(), ...input, env };
|
||||
const filteredInput = filterFields ? proxyCompileInput(data) : data;
|
||||
logger.trace({ template, filteredInput }, 'Compiling template');
|
||||
if (filterFields) {
|
||||
const matches = template.matchAll(templateRegex);
|
||||
for (const match of matches) {
|
||||
const varNames = match[1].split('.');
|
||||
if (varNames[0] === 'env') {
|
||||
continue;
|
||||
}
|
||||
for (const varName of varNames) {
|
||||
if (!allowedFieldsList.includes(varName)) {
|
||||
logger.info(
|
||||
|
|
Loading…
Reference in a new issue