@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
157 lines (156 loc) • 5.79 kB
JavaScript
;
/**
* RBAC Enforcement Module (PRD #392 Milestone 1)
*
* Wraps Kubernetes SubjectAccessReview to check tool-level permissions
* for OAuth-authenticated users. Token users bypass RBAC entirely.
*
* Uses the virtual API group "dot-ai.devopstoolkit.ai" — no CRDs needed.
* Kubernetes evaluates RBAC rules as pure string matching on the group,
* resource, resourceName, and verb fields.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.isRbacEnabled = isRbacEnabled;
exports.checkToolAccess = checkToolAccess;
exports.filterAuthorizedTools = filterAuthorizedTools;
exports.resetAuthzApi = resetAuthzApi;
const k8s = __importStar(require("@kubernetes/client-node"));
const audit_logger_1 = require("./audit-logger");
const RBAC_API_GROUP = 'dot-ai.devopstoolkit.ai';
const RBAC_VERB = 'execute';
/**
* Whether RBAC enforcement is enabled.
* When disabled (default), all authenticated users have full access.
* Set DOT_AI_RBAC_ENABLED=true to enforce tool-level RBAC via SubjectAccessReview.
*/
function isRbacEnabled() {
return process.env.DOT_AI_RBAC_ENABLED === 'true';
}
let authzApi;
function getAuthzApi() {
if (!authzApi) {
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
authzApi = kc.makeApiClient(k8s.AuthorizationV1Api);
}
return authzApi;
}
/**
* Check whether the given identity is authorized to use the specified tool.
*
* - Token users (`source: 'token'`) always bypass RBAC.
* - OAuth users are checked via SubjectAccessReview against the virtual
* API group `dot-ai.devopstoolkit.ai`.
*/
async function checkToolAccess(identity, params) {
// No identity — deny
if (!identity) {
const result = { allowed: false, reason: 'No identity available' };
(0, audit_logger_1.logToolAccessDecision)(identity, params, result);
return result;
}
// Token users bypass RBAC (backward-compatible)
if (identity.source === 'token') {
const result = { allowed: true };
(0, audit_logger_1.logToolAccessDecision)(identity, params, result);
return result;
}
// RBAC disabled — all authenticated users have full access
if (!isRbacEnabled()) {
return { allowed: true };
}
const resource = params.resource || 'tools';
const verb = params.verb || RBAC_VERB;
try {
const api = getAuthzApi();
const review = await api.createSubjectAccessReview({
body: {
apiVersion: 'authorization.k8s.io/v1',
kind: 'SubjectAccessReview',
spec: {
user: identity.email,
groups: identity.groups,
resourceAttributes: {
group: RBAC_API_GROUP,
resource,
name: params.toolName,
verb,
...(params.namespace ? { namespace: params.namespace } : {}),
},
},
},
});
const status = review.status;
const result = {
allowed: status?.allowed ?? false,
reason: status?.reason ||
(status?.allowed ? undefined : 'Access denied by RBAC policy'),
};
(0, audit_logger_1.logToolAccessDecision)(identity, params, result);
return result;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
const result = {
allowed: false,
reason: 'RBAC evaluation failed',
evaluationError: message,
};
(0, audit_logger_1.logToolAccessDecision)(identity, params, result);
return result;
}
}
/**
* Check which tools from a list the identity is authorized for.
* Runs checks in parallel for efficiency.
*/
async function filterAuthorizedTools(identity, tools) {
// No identity, token user, or RBAC disabled — return all tools
if (!identity || identity.source === 'token' || !isRbacEnabled()) {
return tools;
}
const checks = await Promise.all(tools.map(async (tool) => ({
tool,
result: await checkToolAccess(identity, { toolName: tool.name }),
})));
return checks.filter(c => c.result.allowed).map(c => c.tool);
}
/**
* Reset the cached API client (for testing).
*/
function resetAuthzApi() {
authzApi = undefined;
}