Skip to content

Commit

Permalink
New CONTRACT_ALLOWLIST option to limit device types/contracts
Browse files Browse the repository at this point in the history
Resolves: balena-io#1433
Change-type: minor
  • Loading branch information
shaunco committed Oct 6, 2023
1 parent e8dfa1c commit 0fd8048
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 2 deletions.
1 change: 1 addition & 0 deletions config/confd/conf.d/env.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ keys = [
"IMAGE_STORAGE_FORCE_PATH_STYLE",
"IMAGE_STORAGE_PREFIX",
"IMAGE_STORAGE_SECRET_KEY",
"CONTRACT_ALLOWLIST",
"JSON_WEB_TOKEN_EXPIRY_MINUTES",
"JSON_WEB_TOKEN_SECRET",
"MIXPANEL_TOKEN",
Expand Down
1 change: 1 addition & 0 deletions config/confd/templates/env.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ COOKIE_SESSION_SECRET={{getenv "COOKIE_SESSION_SECRET"}}
{{if getenv "CONTRACTS_PRIVATE_REPO_NAME"}}CONTRACTS_PRIVATE_REPO_NAME={{getenv "CONTRACTS_PRIVATE_REPO_NAME"}}{{end}}
{{if getenv "CONTRACTS_PRIVATE_REPO_BRANCH"}}CONTRACTS_PRIVATE_REPO_BRANCH"CONTRACTS_PRIVATE_REPO_BRANCH"}}{{end}}
{{if getenv "CONTRACTS_PRIVATE_REPO_TOKEN"}}CONTRACTS_PRIVATE_REPO_TOKEN={{getenv "CONTRACTS_PRIVATE_REPO_TOKEN"}}{{end}}
{{if getenv "CONTRACT_ALLOWLIST"}}CONTRACT_ALLOWLIST={{getenv "CONTRACT_ALLOWLIST"}}{{end}}
DATABASE_URL=postgres://{{getenv "DB_USER"}}:{{getenv "DB_PASSWORD"}}@{{getenv "DB_HOST"}}:{{getenv "DB_PORT"}}/{{getenv "DB_NAME" "resin"}}
DB_HOST={{getenv "DB_HOST"}}
DB_PASSWORD={{getenv "DB_PASSWORD"}}
Expand Down
24 changes: 23 additions & 1 deletion src/features/contracts/contracts-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import validator from 'validator';
import type { RepositoryInfo, Contract } from './index';
import { getBase64DataUri } from '../../lib/utils';
import { captureException } from '../../infra/error-handling';
import { CONTRACT_ALLOWLIST } from '../../lib/config';

const pipeline = util.promisify(stream.pipeline);
const exists = util.promisify(fs.exists);
Expand Down Expand Up @@ -177,13 +178,34 @@ export const getContracts = async (type: string): Promise<Contract[]> => {
return [];
}

const contractFiles = await glob(
let contractFiles = await glob(
`${CONTRACTS_BASE_DIR}/**/contracts/${type}/**/*.json`,
);
if (!contractFiles.length) {
return [];
}

// If there are explicit includes, then everything else is excluded so we need to
// filter the contractFiles list to include only contracts that are in the CONTRACT_ALLOWLIST map
if (CONTRACT_ALLOWLIST.size > 0) {
const slugRegex = new RegExp(`/contracts/(${_.escapeRegExp(type)}/[^/]+)/`);
const before = contractFiles.length;
contractFiles = contractFiles.filter((file) => {
// Get the contract slug from the file path
const deviceTypeSlug = file.match(slugRegex)?.[1];
if (!deviceTypeSlug) {
return false;
}

// Check if this slug is included in the map
return CONTRACT_ALLOWLIST.has(deviceTypeSlug);
});

console.log(
`CONTRACT_ALLOWLIST reduced ${type} contract slugs from ${before} to ${contractFiles.length}`,
);
}

const contracts = await Promise.all(
contractFiles.map(async (file) => {
let contract;
Expand Down
16 changes: 15 additions & 1 deletion src/features/device-types/device-types-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { multiCacheMemoizee } from '../../infra/cache';
import {
DEVICE_TYPES_CACHE_LOCAL_TIMEOUT,
DEVICE_TYPES_CACHE_TIMEOUT,
CONTRACT_ALLOWLIST,
} from '../../lib/config';

export interface DeviceTypeInfo {
Expand Down Expand Up @@ -58,7 +59,20 @@ const getFirstValidBuild = async (
export const getDeviceTypes = multiCacheMemoizee(
async (): Promise<Dictionary<DeviceTypeInfo>> => {
const result: Dictionary<DeviceTypeInfo> = {};
const slugs = await listFolders(IMAGE_STORAGE_PREFIX);
let slugs = await listFolders(IMAGE_STORAGE_PREFIX);

// If there are explicit includes, then everything else is excluded so we need to
// filter the slugs list to include only contracts that are in the CONTRACT_ALLOWLIST map
if (CONTRACT_ALLOWLIST.size > 0) {
const before = slugs.length;
slugs = slugs.filter((slug) =>
CONTRACT_ALLOWLIST.has(`hw.device-type/${slug}`),
);
console.log(
`CONTRACT_ALLOWLIST reduced device type slugs from ${before} to ${slugs.length}`,
);
}

await Promise.all(
slugs.map(async (slug) => {
try {
Expand Down
17 changes: 17 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ export const AUTH_RESINOS_REGISTRY_CODE = optionalVar(
'AUTH_RESINOS_REGISTRY_CODE',
);
export const COOKIE_SESSION_SECRET = requiredVar('COOKIE_SESSION_SECRET');

/**
* null: include all device type and device contract slugs
* "x;y;z": include only the specified device type and contract slugs - note that you MUST list
* all dependent slugs as well so for hw.device-type/asus-tinker-board-s you would need:
* `arch.sw/armv7hf;hw.device-manufacturer/asus;hw.device-family/tinkerboard;hw.device-type/asus-tinker-board-s`
* For something like hw.device-type/iot-gate-imx8 you would need:
* `arch.sw/aarch64;hw.device-type/iot-gate-imx8`
* (the order of the slugs in this variable does not matter)
*/
export const CONTRACT_ALLOWLIST = new Set(
optionalVar('CONTRACT_ALLOWLIST', '')
.split(';')
.map((slug) => slug.trim())
.filter((slug) => slug.length > 0),
);

export const CONTRACTS_PUBLIC_REPO_OWNER = optionalVar(
'CONTRACTS_PUBLIC_REPO_OWNER',
'balena-io',
Expand Down

0 comments on commit 0fd8048

Please sign in to comment.