provisioning-catalog/components/formbricks/cluster/templates/cube-config-configmap.yaml.j2

152 lines
6 KiB
Text
Raw Permalink Normal View History

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ taskserv.name }}-cube-config
namespace: {{ taskserv.namespace }}
labels:
app.kubernetes.io/name: {{ taskserv.name }}-cube
app.kubernetes.io/managed-by: provisioning
data:
cube.js: |
/* eslint-env es2022 */
const TENANT_MEMBERS = ["FeedbackRecords.tenantId"];
const REQUIRED_SCOPE = "xm:cube:query";
function assertRequiredEnvironmentVariable(name) {
const value = process.env[name];
if (typeof value !== "string" || value.trim().length === 0) {
throw new Error(`${name} is required to run Cube`);
}
}
assertRequiredEnvironmentVariable("CUBEJS_API_SECRET");
function getStringClaim(securityContext, claim) {
const value = securityContext?.[claim];
if (typeof value !== "string") return null;
const trimmedValue = value.trim();
return trimmedValue.length > 0 ? trimmedValue : null;
}
function getRequiredStringClaim(securityContext, claim) {
const value = getStringClaim(securityContext, claim);
if (!value) throw new Error(`Cube query rejected: missing ${claim} security context`);
return value;
}
function collectFilterMembers(filters) {
if (!Array.isArray(filters)) return [];
return filters.flatMap((filter) => [
...(typeof filter?.member === "string" ? [filter.member] : []),
...(typeof filter?.dimension === "string" ? [filter.dimension] : []),
...collectFilterMembers(filter?.and),
...collectFilterMembers(filter?.or),
]);
}
function collectOrderMembers(order) {
if (!order) return [];
if (Array.isArray(order)) {
return order.map((e) => (Array.isArray(e) ? e[0] : null)).filter((m) => typeof m === "string");
}
if (typeof order === "object") return Object.keys(order);
return [];
}
function collectTimeDimensionMembers(timeDimensions) {
if (!Array.isArray(timeDimensions)) return [];
return timeDimensions.map((td) => td?.dimension).filter((d) => typeof d === "string");
}
function collectQueryMembers(query) {
const q = query ?? {};
const members = [
...(Array.isArray(q.measures) ? q.measures : []),
...(Array.isArray(q.dimensions) ? q.dimensions : []),
...(Array.isArray(q.segments) ? q.segments : []),
...collectTimeDimensionMembers(q.timeDimensions),
...collectFilterMembers(q.filters),
...collectOrderMembers(q.order),
].filter((m) => typeof m === "string");
return Array.from(new Set(members)).sort((a, b) => a.localeCompare(b));
}
function assertValidSecurityContext(securityContext) {
const tenantId = getRequiredStringClaim(securityContext, "tenantId");
const feedbackDirectoryId = getRequiredStringClaim(securityContext, "feedbackDirectoryId");
const workspaceId = getRequiredStringClaim(securityContext, "workspaceId");
const scope = getRequiredStringClaim(securityContext, "scope");
if (scope !== REQUIRED_SCOPE) throw new Error("Cube query rejected: invalid Cube query scope");
if (tenantId !== feedbackDirectoryId) throw new Error("Cube query rejected: tenantId/feedbackDirectoryId mismatch");
return {
tenantId, feedbackDirectoryId, workspaceId,
organizationId: getRequiredStringClaim(securityContext, "organizationId"),
userId: getRequiredStringClaim(securityContext, "userId"),
requestId: getRequiredStringClaim(securityContext, "jti"),
source: getRequiredStringClaim(securityContext, "source"),
};
}
function assertNoCallerTenantMember(query) {
for (const member of collectQueryMembers(query)) {
if (TENANT_MEMBERS.includes(member)) throw new Error("Cube query rejected: tenant filters are enforced by Cube");
}
}
function logCubeQueryAuditEvent(context, query, { error, status = "success" } = {}) {
const errorName = error instanceof Error ? error.name : undefined;
const errorMessage = error instanceof Error ? error.message : error ? String(error) : undefined;
console.log(JSON.stringify({
type: "audit", event: "cube.query", status,
timestamp: new Date().toISOString(),
tenantId: context.tenantId, feedbackDirectoryId: context.feedbackDirectoryId,
workspaceId: context.workspaceId, organizationId: context.organizationId,
userId: context.userId, requestId: context.requestId, source: context.source,
members: collectQueryMembers(query),
...(errorName ? { errorName } : {}),
...(errorMessage ? { errorMessage } : {}),
}));
}
function logCubeQuerySecurityContextFailure(query, error) {
console.log(JSON.stringify({
type: "audit", event: "cube.query", status: "failure",
timestamp: new Date().toISOString(),
members: collectQueryMembers(query),
errorName: error instanceof Error ? error.name : undefined,
errorMessage: error instanceof Error ? error.message : String(error),
}));
}
function queryRewrite(query, rewriteContext) {
const cubeQuery = query ?? {};
let context;
try {
context = assertValidSecurityContext(rewriteContext?.securityContext);
} catch (error) {
logCubeQuerySecurityContextFailure(cubeQuery, error);
throw error;
}
try {
assertNoCallerTenantMember(cubeQuery);
} catch (error) {
logCubeQueryAuditEvent(context, cubeQuery, { error, status: "failure" });
throw error;
}
const queriedCubePrefixes = new Set(collectQueryMembers(cubeQuery).map((m) => m.split(".")[0]));
const rewrittenQuery = {
...cubeQuery,
filters: [
...(Array.isArray(cubeQuery.filters) ? cubeQuery.filters : []),
...TENANT_MEMBERS.filter((m) => queriedCubePrefixes.has(m.split(".")[0])).map((member) => ({
member, operator: "equals", values: [context.tenantId],
})),
],
};
logCubeQueryAuditEvent(context, rewrittenQuery);
return rewrittenQuery;
}
module.exports = { queryRewrite };