@cerbos/files
Version:
Load Cerbos policies from YAML or JSON files
181 lines • 5.37 kB
JavaScript
;
/**
* Load Cerbos policies from YAML or JSON files.
*
* @packageDocumentation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePolicy = parsePolicy;
exports.readPolicy = readPolicy;
exports.serializePolicy = serializePolicy;
exports.writePolicy = writePolicy;
exports.readSchema = readSchema;
exports.readDirectory = readDirectory;
const promises_1 = require("fs/promises");
const path_1 = require("path");
const yaml_1 = require("yaml");
const core_1 = require("@cerbos/core");
const policy_1 = require("./protobuf/cerbos/policy/v1/policy");
/**
* Parse a policy from a YAML- or JSON-encoded string.
*
* @param contents - the YAML- or JSON-encoded policy definition.
*
* @public
*/
function parsePolicy(contents) {
return (0, core_1._policyFromProtobuf)(policy_1.Policy.fromJSON((0, yaml_1.parse)(contents)));
}
/**
* Read a policy from a YAML or JSON file.
*
* @param path - the path to the policy file.
*
* @public
*/
async function readPolicy(path) {
return parsePolicy(await (0, promises_1.readFile)(path, { encoding: "utf8" }));
}
/**
* Serialize a policy to a JSON-encoded string.
*
* @param policy - the policy definition.
*
* @public
*/
function serializePolicy(policy) {
return `${JSON.stringify(policy_1.Policy.toJSON((0, core_1._policyToProtobuf)(policy)), null, 2)}\n`;
}
/**
* Write a policy to a JSON file.
*
* @param path - the path to the policy file.
* @param policy - the policy definition.
*
* @public
*/
async function writePolicy(path, policy) {
await (0, promises_1.writeFile)(path, serializePolicy(policy), { encoding: "utf8" });
}
/**
* Read a schema from a JSON file.
*
* @param path - the path to the schema file.
* @param options - additional settings.
*
* @public
*/
async function readSchema(path, options = {}) {
return {
id: options.id ?? schemaIdFromPath(path),
definition: await (0, promises_1.readFile)(path, { encoding: "utf8" }),
};
}
function schemaIdFromPath(path) {
const absolutePath = (0, path_1.resolve)(path);
const segments = absolutePath.split(path_1.sep);
while (segments.length > 1) {
const segment = segments.shift();
if (segment === "_schemas") {
return segments.join("/");
}
}
return (0, path_1.basename)(absolutePath);
}
const fileHandlers = {
policies: {
extensions: new Set([".json", ".yaml", ".yml"]),
add(path, { policies }) {
policies.push(readPolicy(path));
},
},
schemas: {
extensions: new Set([".json"]),
add(path, { schemas }) {
schemas.push(readSchema(path));
},
},
};
/**
* Read the policy and schema files in a directory and its subdirectories.
*
* @param path - the path to the directory.
*
* @remarks
* This function looks for policies and schemas stored in the
* {@link https://docs.cerbos.dev/cerbos/latest/policies/best_practices#_policy_repository_layout | standard Cerbos directory layout}.
*
* @public
*/
async function readDirectory(path) {
const pending = {
policies: [],
schemas: [],
};
await walk(path, ".", "policies", pending);
return {
policies: (await Promise.all(pending.policies)).sort(comparePolicies),
schemas: (await Promise.all(pending.schemas)).sort(compareSchemas),
};
}
async function walk(root, subdir, type, pending) {
const subdirs = [];
for await (const entry of await (0, promises_1.opendir)((0, path_1.join)(root, subdir))) {
if (entry.name.startsWith(".")) {
continue;
}
const path = (0, path_1.join)(subdir, entry.name);
if (entry.isDirectory()) {
if (path === "_schemas") {
subdirs.push(walk(root, path, "schemas", pending));
}
else if (!(type === "policies" && entry.name === "testdata")) {
subdirs.push(walk(root, path, type, pending));
}
}
else {
const handler = fileHandlers[type];
if (handler.extensions.has((0, path_1.extname)(entry.name))) {
handler.add((0, path_1.join)(root, path), pending);
}
}
}
await Promise.all(subdirs);
}
function comparePolicies(a, b) {
return compareStrings(policySortKey(a), policySortKey(b));
}
function policySortKey(policy) {
let segments;
if ((0, core_1.policyIsExportVariables)(policy)) {
segments = [0, policy.exportVariables.name];
}
else if ((0, core_1.policyIsDerivedRoles)(policy)) {
segments = [1, policy.derivedRoles.name];
}
else if ((0, core_1.policyIsResourcePolicy)(policy)) {
const { resource, version, scope } = policy.resourcePolicy;
segments = [2, resource, version, scope ?? ""];
}
else if ((0, core_1.policyIsPrincipalPolicy)(policy)) {
const { principal, version, scope } = policy.principalPolicy;
segments = [3, principal, version, scope ?? ""];
}
else {
throw new Error("Unexpected policy type");
}
return segments.join("\0");
}
function compareSchemas(a, b) {
return compareStrings(a.id, b.id);
}
function compareStrings(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
//# sourceMappingURL=index.js.map