Validate secrets passed in to reusable workflow job

This commit is contained in:
Beth Brennan
2023-02-15 14:46:19 -05:00
parent ed9199aa93
commit 9e1bb89fa3
8 changed files with 67 additions and 4 deletions
@@ -21,6 +21,8 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
let steps: Step[] = [];
let workflowJobRef: StringToken | undefined;
let workflowJobInputs: MappingToken | undefined;
let inheritSecrets = false;
let workflowJobSecrets: MappingToken | undefined;
for (const item of token) {
const propertyName = item.key.assertString("job property name");
@@ -95,6 +97,12 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
case "with":
workflowJobInputs = item.value.assertMapping("uses-with value");
break;
case "secrets":
if (isString(item.value) && item.value.value === "inherit") {
inheritSecrets = true;
} else {
workflowJobSecrets = item.value.assertMapping("uses-secrets value");
}
}
}
@@ -108,6 +116,9 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
ref: workflowJobRef,
"input-definitions": undefined,
"input-values": workflowJobInputs,
"secret-definitions": undefined,
"secret-values": workflowJobSecrets,
"inherit-secrets": inheritSecrets || undefined,
outputs: undefined,
concurrency,
strategy
@@ -39,7 +39,7 @@ export function convertWorkflowJobInputs(context: TemplateContext, job: Reusable
}
}
function createTokenMap(mapping: MappingToken | undefined, description: string): TokenMap | undefined {
export function createTokenMap(mapping: MappingToken | undefined, description: string): TokenMap | undefined {
if (!mapping) {
return undefined;
}
@@ -0,0 +1,43 @@
import {TemplateContext} from "../../../templates/template-context";
import {NullToken} from "../../../templates/tokens";
import {ReusableWorkflowJob} from "../../workflow-template";
import {createTokenMap} from "./inputs";
export function convertWorkflowJobSecrets(context: TemplateContext, job: ReusableWorkflowJob) {
// No validation if job passes all secrets
if (!!job["inherit-secrets"]) {
return;
}
const secretDefinitions = createTokenMap(
job["secret-definitions"]?.assertMapping("workflow job secret definitions"),
"secrets"
);
const secretValues = createTokenMap(job["secret-values"]?.assertMapping("workflow job secret values"), "secrets");
if (secretDefinitions !== undefined) {
for (const [_, [name, value]] of secretDefinitions) {
if (value instanceof NullToken) {
continue;
}
const secretSpec = createTokenMap(value.assertMapping(`secret ${name}`), `secret ${name} key`)!;
const required = secretSpec.get("required")?.[1].assertBoolean(`secret ${name} required`).value;
if (required) {
if (secretValues == undefined || !secretValues.has(name.toLowerCase())) {
context.error(job.ref, `Secret ${name} is required, but not provided while calling.`);
}
}
}
}
if (secretValues !== undefined) {
for (const [_, [name, value]] of secretValues) {
if (!secretDefinitions?.has(name.toLowerCase())) {
context.error(value, `Invalid secret, ${name} is not defined in the referenced workflow.`);
}
}
}
}
@@ -4,6 +4,7 @@ import {TokenType} from "../../templates/tokens/types";
import {ReusableWorkflowJob} from "../workflow-template";
import {handleTemplateTokenErrors} from "./handle-errors";
import {convertWorkflowJobInputs} from "./job/inputs";
import {convertWorkflowJobSecrets} from "./job/secrets";
import {convertJobs} from "./jobs";
export function convertReferencedWorkflow(
@@ -41,6 +42,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
const event = on.assertString("Reference workflow on value").value;
if (event === "workflow_call") {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobSecrets(context, job));
return;
}
break;
@@ -52,6 +54,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
const event = eventToken.assertString(`Reference workflow on value ${eventToken}`).value;
if (event === "workflow_call") {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobSecrets(context, job));
return;
}
}
@@ -69,6 +72,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
if (pair.value.templateTokenType === TokenType.Null) {
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobSecrets(context, job));
return;
}
@@ -83,10 +87,15 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
case "outputs":
job.outputs = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
break;
case "secrets":
job["secret-definitions"] = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
break;
}
}
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobInputs(context, job));
handleTemplateTokenErrors(tokenForErrors, context, undefined, () => convertWorkflowJobSecrets(context, job));
return;
}
break;
@@ -60,6 +60,9 @@ export type ReusableWorkflowJob = BaseJob & {
ref: StringToken;
"input-definitions"?: MappingToken;
"input-values"?: MappingToken;
"secret-definitions"?: MappingToken;
"secret-values"?: MappingToken;
"inherit-secrets"?: boolean;
jobs?: WorkflowJob[];
};
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs: