Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #45 from github/suite_syntax
introduce new syntax for built-in query suites
Robert authored and GitHub committed Jun 3, 2020

Unverified

No user is associated with the committer email.
2 parents 28a878e + f18fffb commit a0d60d5
Showing 12 changed files with 238 additions and 57 deletions.
7 changes: 7 additions & 0 deletions .github/codeql/codeql-config.yml
@@ -2,5 +2,12 @@ name: "CodeQL config"
queries:
- name: Run custom queries
uses: ./queries
# Run all extra query suites, both because we want to
# and because it'll act as extra testing. This is why
# we include both even though one is a superset of the
# other, because we're testing the parsing logic and
# that the suites exist in the codeql bundle.
- uses: security-extended
- uses: security-and-quality
paths-ignore:
- tests
4 changes: 3 additions & 1 deletion .github/workflows/integration-testing.yml
@@ -23,7 +23,9 @@ jobs:
TEST_MODE: true
- run: |
cd "$CODEQL_ACTION_DATABASE_DIR"
if [ "$(ls | wc -l)" != 6 ] || \
# List all directories as there will be precisely one directory per database
# but there may be other files in this directory such as query suites.
if [ "$(ls -d */ | wc -l)" != 6 ] || \
[[ ! -d cpp ]] || \
[[ ! -d csharp ]] || \
[[ ! -d go ]] || \
2 changes: 1 addition & 1 deletion init/action.yml
@@ -5,7 +5,7 @@ inputs:
tools:
description: URL of CodeQL tools
required: false
default: https://github.com/github/codeql-action/releases/download/codeql-bundle-20200427/codeql-bundle.tar.gz
default: https://github.com/github/codeql-action/releases/download/codeql-bundle-20200601/codeql-bundle.tar.gz
languages:
description: The languages to be analysed
required: false
43 changes: 41 additions & 2 deletions lib/config-utils.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/config-utils.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions lib/config-utils.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/config-utils.test.js.map
71 changes: 50 additions & 21 deletions lib/finalize-db.js
2 changes: 1 addition & 1 deletion lib/finalize-db.js.map
10 changes: 8 additions & 2 deletions src/config-utils.test.ts
@@ -91,6 +91,7 @@ test("load non-empty input", async t => {
name: my config
disable-default-queries: true
queries:
- uses: ./
- uses: ./foo
- uses: foo/bar@dev
paths-ignore:
@@ -103,14 +104,17 @@ test("load non-empty input", async t => {
const expectedConfig = new configUtils.Config();
expectedConfig.name = 'my config';
expectedConfig.disableDefaultQueries = true;
expectedConfig.additionalQueries.push('foo');
expectedConfig.additionalQueries.push(tmpDir);
expectedConfig.additionalQueries.push(path.join(tmpDir, 'foo'));
expectedConfig.externalQueries = [new configUtils.ExternalQuery('foo/bar', 'dev')];
expectedConfig.pathsIgnore = ['a', 'b'];
expectedConfig.paths = ['c/d'];

fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
setInput('config-file', 'input');

fs.mkdirSync(path.join(tmpDir, 'foo'));

const actualConfig = await configUtils.loadConfig();

// Should exactly equal the object we constructed earlier
@@ -222,7 +226,9 @@ const testInputs = {
"foo/bar": configUtils.getQueryUsesIncorrect("foo/bar"),
"foo/bar@v1@v2": configUtils.getQueryUsesIncorrect("foo/bar@v1@v2"),
"foo@master": configUtils.getQueryUsesIncorrect("foo@master"),
"https://github.com/foo/bar@master": configUtils.getQueryUsesIncorrect("https://github.com/foo/bar@master")
"https://github.com/foo/bar@master": configUtils.getQueryUsesIncorrect("https://github.com/foo/bar@master"),
"./foo": configUtils.getLocalPathDoesNotExist("foo"),
"./..": configUtils.getLocalPathOutsideOfRepository(".."),
};

for (const [input, result] of Object.entries(testInputs)) {
49 changes: 47 additions & 2 deletions src/config-utils.ts
@@ -17,11 +17,17 @@ export class ExternalQuery {
}
}

// The set of acceptable values for built-in suites from the codeql bundle
const builtinSuites = ['security-extended', 'security-and-quality'] as const;
// Derive the union type from the array values
type BuiltInSuite = typeof builtinSuites[number];

export class Config {
public name = "";
public disableDefaultQueries = false;
public additionalQueries: string[] = [];
public externalQueries: ExternalQuery[] = [];
public additionalSuites: BuiltInSuite[] = [];
public pathsIgnore: string[] = [];
public paths: string[] = [];

@@ -35,10 +41,37 @@ export class Config {

// Check for the local path case before we start trying to parse the repository name
if (queryUses.startsWith("./")) {
this.additionalQueries.push(queryUses.slice(2));
const localQueryPath = queryUses.slice(2);
// Resolve the local path against the workspace so that when this is
// passed to codeql it resolves to exactly the path we expect it to resolve to.
const workspacePath = util.getRequiredEnvParam('GITHUB_WORKSPACE');
const absoluteQueryPath = path.join(workspacePath, localQueryPath);

// Check the file exists
if (!fs.existsSync(absoluteQueryPath)) {
throw new Error(getLocalPathDoesNotExist(localQueryPath));
}

// Check the local path doesn't jump outside the repo using '..' or symlinks
if (!(fs.realpathSync(absoluteQueryPath) + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getLocalPathOutsideOfRepository(localQueryPath));
}

this.additionalQueries.push(absoluteQueryPath);
return;
}

// Check for one of the builtin suites
if (queryUses.indexOf('/') === -1 && queryUses.indexOf('@') === -1) {
const suite = builtinSuites.find((suite) => suite === queryUses);
if (suite) {
this.additionalSuites.push(suite);
return;
} else {
throw new Error(getQueryUsesIncorrect(queryUses));
}
}

let tok = queryUses.split('@');
if (tok.length !== 2) {
throw new Error(getQueryUsesIncorrect(queryUses));
@@ -74,7 +107,19 @@ export function getQueryUsesBlank(): string {
}

export function getQueryUsesIncorrect(queryUses: string): string {
return '"uses" value for queries must be a path, or owner/repo@ref \n Found: ' + queryUses;
return '"uses" value for queries must be a built-in suite (' + builtinSuites.join(' or ') +
'), a relative path, or of the form owner/repo@ref\n' +
'Found: ' + queryUses;
}

export function getLocalPathOutsideOfRepository(localPath: string): string {
return 'Unable to use queries from local path "' + localPath +
'" as it is outside of the repository';
}

export function getLocalPathDoesNotExist(localPath: string): string {
return 'Unable to use queries from local path "' + localPath +
'" as the path does not exist in the repository';
}

export function getConfigFileOutsideWorkspaceErrorMessage(configFile: string): string {
94 changes: 71 additions & 23 deletions src/finalize-db.ts
@@ -69,32 +69,74 @@ async function finalizeDatabaseCreation(codeqlCmd: string, databaseFolder: strin
}
}

interface ResolveQueriesOutput {
byLanguage: {
[language: string]: {
[queryPath: string]: {}
}
};
noDeclaredLanguage: {
[queryPath: string]: {}
};
multipleDeclaredLanguages: {
[queryPath: string]: {}
};
}

async function runResolveQueries(codeqlCmd: string, queries: string[]): Promise<ResolveQueriesOutput> {
let output = '';
const options = {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
};

await exec.exec(
codeqlCmd, [
'resolve',
'queries',
...queries,
'--format=bylanguage'
],
options);

return JSON.parse(output);
}

async function resolveQueryLanguages(codeqlCmd: string, config: configUtils.Config): Promise<Map<string, string[]>> {
let res = new Map();

if (config.additionalQueries.length !== 0) {
let resolveQueriesOutput = '';
const options = {
listeners: {
stdout: (data: Buffer) => {
resolveQueriesOutput += data.toString();
}
if (!config.disableDefaultQueries || config.additionalSuites.length !== 0) {
const suites: string[] = [];
for (const language of await util.getLanguages()) {
if (!config.disableDefaultQueries) {
suites.push(language + '-code-scanning.qls');
}
for (const additionalSuite of config.additionalSuites) {
suites.push(language + '-' + additionalSuite + '.qls');
}
};
}

await exec.exec(
codeqlCmd, [
'resolve',
'queries',
...config.additionalQueries,
'--format=bylanguage'
],
options);
const resolveQueriesOutputObject = await runResolveQueries(codeqlCmd, suites);

const resolveQueriesOutputObject = JSON.parse(resolveQueriesOutput);
for (const [language, queries] of Object.entries(resolveQueriesOutputObject.byLanguage)) {
if (res[language] === undefined) {
res[language] = [];
}
res[language].push(...Object.keys(<any>queries));
}
}

if (config.additionalQueries.length !== 0) {
const resolveQueriesOutputObject = await runResolveQueries(codeqlCmd, config.additionalQueries);

for (const [language, queries] of Object.entries(resolveQueriesOutputObject.byLanguage)) {
res[language] = Object.keys(<any>queries);
if (res[language] === undefined) {
res[language] = [];
}
res[language].push(...Object.keys(<any>queries));
}

const noDeclaredLanguage = resolveQueriesOutputObject.noDeclaredLanguage;
@@ -120,11 +162,17 @@ async function runQueries(codeqlCmd: string, databaseFolder: string, sarifFolder
for (let database of fs.readdirSync(databaseFolder)) {
core.startGroup('Analyzing ' + database);

const queries: string[] = [];
if (!config.disableDefaultQueries) {
queries.push(database + '-code-scanning.qls');
const queries = queriesPerLanguage[database] || [];
if (queries.length === 0) {
throw new Error('Unable to analyse ' + database + ' as no queries were selected for this language');
}
queries.push(...(queriesPerLanguage[database] || []));

// Pass the queries to codeql using a file instead of using the command
// line to avoid command line length restrictions, particularly on windows.
const querySuite = path.join(databaseFolder, database + '-queries.qls');
const querySuiteContents = queries.map(q => '- query: ' + q).join('\n');
fs.writeFileSync(querySuite, querySuiteContents);
core.debug('Query suite file for ' + database + '...\n' + querySuiteContents);

const sarifFile = path.join(sarifFolder, database + '.sarif');

@@ -136,7 +184,7 @@ async function runQueries(codeqlCmd: string, databaseFolder: string, sarifFolder
'--format=sarif-latest',
'--output=' + sarifFile,
'--no-sarif-add-snippets',
...queries
querySuite
]);

core.debug('SARIF results for database ' + database + ' created at "' + sarifFile + '"');

0 comments on commit a0d60d5

Please sign in to comment.