Large diffs are not rendered by default.

@@ -20,6 +20,7 @@ import {
getMemoryFlag,
getThreadsFlag,
parseGithubUrl,
getGitHubAuth,
} from "./util";

const program = new Command();
@@ -96,6 +97,7 @@ interface InitArgs {
repository: string;
githubUrl: string;
githubAuth: string;
githubAuthStdin: boolean;
externalRepositoryToken: string | undefined;
debug: boolean;
}
@@ -105,9 +107,13 @@ program
.description("Initializes CodeQL")
.requiredOption("--repository <repository>", "Repository name. (Required)")
.requiredOption("--github-url <url>", "URL of GitHub instance. (Required)")
.requiredOption(
.option(
"--github-auth <auth>",
"GitHub Apps token or personal access token. (Required)"
"GitHub Apps token or personal access token. This option is insecure and deprecated, please use `--github-auth-stdin` instead."
)
.option(
"--github-auth-stdin",
"Read GitHub Apps token or personal access token from stdin."
)
.option(
"--external-repository-token <token>",
@@ -153,8 +159,14 @@ program
fs.rmdirSync(tempDir, { recursive: true });
fs.mkdirSync(tempDir, { recursive: true });

const auth = await getGitHubAuth(
logger,
cmd.githubAuth,
cmd.githubAuthStdin
);

const apiDetails = {
auth: cmd.githubAuth,
auth,
externalRepoAuth: cmd.externalRepositoryToken,
url: parseGithubUrl(cmd.githubUrl),
};
@@ -315,6 +327,7 @@ interface AnalyzeArgs {
ref: string;
githubUrl: string;
githubAuth: string;
githubAuthStdin: boolean;
checkoutPath: string | undefined;
upload: boolean;
outputDir: string | undefined;
@@ -335,9 +348,13 @@ program
)
.requiredOption("--ref <ref>", "Name of ref that was analyzed. (Required)")
.requiredOption("--github-url <url>", "URL of GitHub instance. (Required)")
.requiredOption(
.option(
"--github-auth <auth>",
"GitHub Apps token or personal access token. (Required)"
"GitHub Apps token or personal access token. This option is insecure and deprecated, please use `--github-auth-stdin` instead."
)
.option(
"--github-auth-stdin",
"Read GitHub Apps token or personal access token from stdin."
)
.option(
"--checkout-path <path>",
@@ -379,8 +396,14 @@ program
);
}

const auth = await getGitHubAuth(
logger,
cmd.githubAuth,
cmd.githubAuthStdin
);

const apiDetails = {
auth: cmd.githubAuth,
auth,
url: parseGithubUrl(cmd.githubUrl),
};

@@ -421,6 +444,7 @@ interface UploadArgs {
commit: string;
ref: string;
githubUrl: string;
githubAuthStdin: boolean;
githubAuth: string;
checkoutPath: string | undefined;
debug: boolean;
@@ -442,9 +466,13 @@ program
)
.requiredOption("--ref <ref>", "Name of ref that was analyzed. (Required)")
.requiredOption("--github-url <url>", "URL of GitHub instance. (Required)")
.requiredOption(
.option(
"--github-auth <auth>",
"GitHub Apps token or personal access token. (Required)"
"GitHub Apps token or personal access token. This option is insecure and deprecated, please use `--github-auth-stdin` instead."
)
.option(
"--github-auth-stdin",
"Read GitHub Apps token or personal access token from stdin."
)
.option(
"--checkout-path <path>",
@@ -453,8 +481,13 @@ program
.option("--debug", "Print more verbose output", false)
.action(async (cmd: UploadArgs) => {
const logger = getRunnerLogger(cmd.debug);
const auth = await getGitHubAuth(
logger,
cmd.githubAuth,
cmd.githubAuthStdin
);
const apiDetails = {
auth: cmd.githubAuth,
auth,
url: parseGithubUrl(cmd.githubUrl),
};
try {
@@ -1,12 +1,13 @@
import * as fs from "fs";
import * as os from "os";
import * as stream from "stream";

import * as github from "@actions/github";
import test from "ava";
import test, { ExecutionContext } from "ava";
import sinon from "sinon";

import * as api from "./api-client";
import { getRunnerLogger } from "./logging";
import { getRunnerLogger, Logger } from "./logging";
import { setupTests } from "./testing-utils";
import * as util from "./util";

@@ -244,3 +245,62 @@ test("getGitHubVersion", async (t) => {
});
t.deepEqual({ type: util.GitHubVariant.DOTCOM }, v3);
});

test("getGitHubAuth", async (t) => {
const msgs: string[] = [];
const mockLogger = ({
warning: (msg: string) => msgs.push(msg),
} as unknown) as Logger;

// eslint-disable-next-line @typescript-eslint/no-floating-promises
t.throwsAsync(async () => util.getGitHubAuth(mockLogger, "abc", true));

process.env.GITHUB_TOKEN = "123";
t.is("123", await util.getGitHubAuth(mockLogger, undefined, undefined));
t.is(msgs.length, 0);
t.is("abc", await util.getGitHubAuth(mockLogger, "abc", undefined));
t.is(msgs.length, 1); // warning expected

msgs.length = 0;
await mockStdInForAuth(t, mockLogger, "def", "def");
await mockStdInForAuth(t, mockLogger, "def", "", "def");
await mockStdInForAuth(
t,
mockLogger,
"def",
"def\n some extra garbage",
"ghi"
);
await mockStdInForAuth(t, mockLogger, "defghi", "def", "ghi\n123");

await mockStdInForAuthExpectError(t, mockLogger, "");
await mockStdInForAuthExpectError(t, mockLogger, "", " ", "abc");
await mockStdInForAuthExpectError(
t,
mockLogger,
" def\n some extra garbage",
"ghi"
);
t.is(msgs.length, 0);
});

async function mockStdInForAuth(
t: ExecutionContext<any>,
mockLogger: Logger,
expected: string,
...text: string[]
) {
const stdin = stream.Readable.from(text) as any;
t.is(expected, await util.getGitHubAuth(mockLogger, undefined, true, stdin));
}

async function mockStdInForAuthExpectError(
t: ExecutionContext<unknown>,
mockLogger: Logger,
...text: string[]
) {
const stdin = stream.Readable.from(text) as any;
await t.throwsAsync(async () =>
util.getGitHubAuth(mockLogger, undefined, true, stdin)
);
}
@@ -1,6 +1,7 @@
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { Readable } from "stream";

import * as core from "@actions/core";
import * as semver from "semver";
@@ -311,3 +312,68 @@ export function apiVersionInRange(
}
return undefined;
}

/**
* Retrieves the github auth token for use with the runner. There are
* three possible locations for the token:
*
* 1. from the cli (considered insecure)
* 2. from stdin
* 3. from the GITHUB_TOKEN environment variable
*
* If both 1 & 2 are specified, then an error is thrown.
* If 1 & 3 or 2 & 3 are specified, then the environment variable is ignored.
*
* @param githubAuth a github app token or PAT
* @param fromStdIn read the github app token or PAT from stdin up to, but excluding the first whitespace
* @param readable the readable stream to use for getting the token (defaults to stdin)
*
* @return a promise resolving to the auth token.
*/
export async function getGitHubAuth(
logger: Logger,
githubAuth: string | undefined,
fromStdIn: boolean | undefined,
readable = process.stdin as Readable
): Promise<string> {
if (githubAuth && fromStdIn) {
throw new Error(
"Cannot specify both `--github-auth` and `--github-auth-stdin`. Please use `--github-auth-stdin`, which is more secure."
);
}

if (githubAuth) {
logger.warning(
"Using `--github-auth` via the CLI is insecure. Use `--github-auth-stdin` instead."
);
return githubAuth;
}

if (fromStdIn) {
return new Promise((resolve, reject) => {
let token = "";
readable.on("data", (data) => {
token += data.toString("utf8");
});
readable.on("end", () => {
token = token.split(/\s+/)[0].trim();
if (token) {
resolve(token);
} else {
reject(new Error("Standard input is empty"));
}
});
readable.on("error", (err) => {
reject(err);
});
});
}

if (process.env.GITHUB_TOKEN) {
return process.env.GITHUB_TOKEN;
}

throw new Error(
"No GitHub authentication token was specified. Please provide a token via the GITHUB_TOKEN environment variable, or by adding the `--github-auth-stdin` flag and passing the token via standard input."
);
}