Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api access control #238

Merged
merged 13 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"express": "^5.0.0-beta.3",
"http-errors": "^2.0.0",
"http-status": "^1.5.3",
"jsonwebtoken": "^9.0.1",
"kafka-node": "^5.0.0",
"kafkajs": "^2.2.4",
"kafkajs-snappy": "^1.1.0",
Expand Down Expand Up @@ -65,6 +66,7 @@
"@types/compression": "^1.7.2",
"@types/express": "^4.17.14",
"@types/http-errors": "^2.0.1",
"@types/jsonwebtoken": "^9.0.6",
"@types/kafkajs": "^1.9.0",
"@types/knex": "^0.16.1",
"@types/lodash": "^4.14.190",
Expand Down
4 changes: 3 additions & 1 deletion api-service/src/configs/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,7 @@ export const config = {
"dialect": process.env.dialet || "postgres",
"url": process.env.grafana_url || "http://localhost:8000",
"access_token": process.env.grafana_token || ""
}
},
"user_token_public_key": process.env.user_token_public_key || "",
"is_RBAC_enabled": process.env.is_rbac_enabled || "false",
}
179 changes: 179 additions & 0 deletions api-service/src/middlewares/RBAC_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { ResponseHandler } from "../helpers/ResponseHandler";
import { config } from "../configs/Config";
import _ from "lodash";

enum roles {
Admin = "admin",
DatasetManager = "dataset_manager",
Viewer = "viewer",
DatasetCreator = "dataset_creator",
Ingestor = "ingestor",
}

enum permissions {
DatasetCreate = "api.datasets.create",
DatasetUpdate = "api.datasets.update",
DatasetRead = "api.datasets.read",
DatasetList = "api.datasets.list",
DataIngest = "api.data.in",
DataOut = "api.data.out",
DataExhaust = "api.data.exhaust",
QueryTemplateCreate = "api.query.template.create",
QueryTemplateRead = "api.query.template.read",
QueryTemplateDelete = "api.query.template.delete",
QueryTemplateList = "api.query.template.list",
QueryTemplateUpdate = "api.query.template.update",
QueryTemplate = "api.query.template.query",
SchemaValidator = "api.schema.validator",
GenerateSignedUrl = "api.files.generate-url",
DatasetStatusTransition = "api.datasets.status-transition",
DatasetHealth = "api.dataset.health",
DatasetReset = "api.dataset.reset",
DatasetSchemaGenerator = "api.datasets.dataschema",
DatasetExport = "api.datasets.export",
DatasetCopy = "api.datasets.copy",
ConnectorList = "api.connectors.list",
ConnectorRead = "api.connectors.read",
DatasetImport = "api.datasets.import",
SQLQuery = "api.obsrv.data.sql-query",
}

interface AccessControl {
[key: string]: string[];
}

const accessControl: AccessControl = {
[roles.Ingestor]: [permissions.DataIngest],
[roles.Viewer]: [
permissions.DatasetList,
permissions.DatasetRead,
permissions.DatasetExport,
permissions.ConnectorRead,
permissions.SQLQuery,
permissions.DataOut,
permissions.DataExhaust,
],
[roles.DatasetCreator]: [
permissions.DatasetList,
permissions.DatasetRead,
permissions.DatasetExport,
permissions.ConnectorRead,
permissions.SQLQuery,
permissions.DataOut,
permissions.DataExhaust,
permissions.DatasetImport,
permissions.DatasetCreate,
permissions.DatasetUpdate,
permissions.DatasetCopy,
permissions.QueryTemplateCreate,
permissions.QueryTemplateRead,
permissions.QueryTemplateDelete,
permissions.GenerateSignedUrl,
permissions.SchemaValidator,
permissions.DatasetSchemaGenerator,
],
[roles.DatasetManager]: [
permissions.DatasetList,
permissions.DatasetRead,
permissions.DatasetExport,
permissions.ConnectorRead,
permissions.SQLQuery,
permissions.DataOut,
permissions.DataExhaust,
permissions.DatasetImport,
permissions.DatasetCreate,
permissions.DatasetUpdate,
permissions.DatasetCopy,
permissions.QueryTemplateCreate,
permissions.QueryTemplateRead,
permissions.QueryTemplateDelete,
permissions.GenerateSignedUrl,
permissions.SchemaValidator,
permissions.DatasetSchemaGenerator,
permissions.DatasetReset,
permissions.DatasetStatusTransition,
],
[roles.Admin]: [
permissions.DatasetCreate,
permissions.DatasetList,
permissions.DatasetRead,
permissions.DatasetExport,
permissions.ConnectorRead,
permissions.SQLQuery,
permissions.DataOut,
permissions.DataExhaust,
permissions.DatasetImport,
permissions.DatasetCreate,
permissions.DatasetUpdate,
permissions.DatasetCopy,
permissions.QueryTemplateCreate,
permissions.QueryTemplateRead,
permissions.QueryTemplateDelete,
permissions.GenerateSignedUrl,
permissions.SchemaValidator,
permissions.DatasetSchemaGenerator,
permissions.DatasetReset,
permissions.DatasetStatusTransition,
],
};

export default {
name: "rbac:middleware",
handler: () => (req: Request, res: Response, next: NextFunction) => {
try {
if (_.lowerCase(config.is_RBAC_enabled) === "false") {
next();
} else {
const public_key = config.user_token_public_key;
const token = req.get("x-user-token");
if (!token) {
return ResponseHandler.errorResponse(
{
statusCode: 403,
errCode: "FORBIDDEN",
message: "No token provided",
},
req,
res
);
}
jwt.verify(token as string, public_key, (err, decoded) => {
if (err) {
return ResponseHandler.errorResponse(
{
statusCode: 403,
errCode: "FORBIDDEN",
message: "Token verification failed",
},
req,
res
);
}
if (decoded && _.isObject(decoded)) {
const action = (req as any).id;
const hasAccess = decoded?.roles?.some(
(role: string) =>
accessControl[role] && accessControl[role].includes(action)
);
if (!hasAccess) {
return ResponseHandler.errorResponse(
{
statusCode: 401,
errCode: "Unauthorized access",
message: "Access denied for the user",
},
req,
res
);
}
next();
}
});
}
} catch (error) {
next(error);
}
},
};
54 changes: 28 additions & 26 deletions api-service/src/routes/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,34 +30,36 @@ import DatasetImport from "../controllers/DatasetImport/DatasetImport";
import { OperationType, telemetryAuditStart } from "../services/telemetry";
import telemetryActions from "../telemetry/telemetryActions";
import datasetMetrics from "../controllers/DatasetMetrics/DatasetMetricsController";
import checkRBAC from "../middlewares/RBAC_middleware";

export const router = express.Router();

router.post("/data/in/:datasetId", setDataToRequestObject("api.data.in"), onRequest({ entity: Entity.Data_in }), telemetryAuditStart({ action: telemetryActions.createDataset, operationType: OperationType.CREATE }), dataIn);
router.post("/data/query/:datasetId", setDataToRequestObject("api.data.out"), onRequest({ entity: Entity.Data_out }), dataOut);
router.post("/datasets/create", setDataToRequestObject("api.datasets.create"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.createDataset, operationType: OperationType.CREATE }), DatasetCreate)
router.patch("/datasets/update", setDataToRequestObject("api.datasets.update"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.updateDataset, operationType: OperationType.UPDATE }), DatasetUpdate)
router.get("/datasets/read/:dataset_id", setDataToRequestObject("api.datasets.read"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.readDataset, operationType: OperationType.GET }), DatasetRead)
router.post("/datasets/list", setDataToRequestObject("api.datasets.list"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.listDatasets, operationType: OperationType.LIST }), DatasetList)
router.get("/data/exhaust/:datasetId", setDataToRequestObject("api.data.exhaust"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.datasetExhaust, operationType: OperationType.GET }), dataExhaust);
router.post("/template/create", setDataToRequestObject("api.query.template.create"), createQueryTemplate);
router.get("/template/read/:templateId", setDataToRequestObject("api.query.template.read"), readQueryTemplate);
router.delete("/template/delete/:templateId", setDataToRequestObject("api.query.template.delete"), deleteQueryTemplate);
router.post("/template/list", setDataToRequestObject("api.query.template.list"), listQueryTemplates);
router.patch("/template/update/:templateId", setDataToRequestObject("api.query.template.update"), updateQueryTemplate);
router.post("/schema/validate", setDataToRequestObject("api.schema.validator"), eventValidation);
router.post("/template/query/:templateId", setDataToRequestObject("api.query.template.query"), queryTemplate);
router.post("/files/generate-url", setDataToRequestObject("api.files.generate-url"), onRequest({ entity: Entity.Management }), GenerateSignedURL);
router.post("/datasets/status-transition", setDataToRequestObject("api.datasets.status-transition"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.createTransformation, operationType: OperationType.CREATE }), DatasetStatusTansition);
router.post("/datasets/health", setDataToRequestObject("api.dataset.health"), onRequest({ entity: Entity.Management }), datasetHealth);
router.post("/datasets/reset/:datasetId", setDataToRequestObject("api.dataset.reset"), onRequest({ entity: Entity.Management }), datasetReset);
router.post("/datasets/dataschema", setDataToRequestObject("api.datasets.dataschema"), onRequest({ entity: Entity.Management }), DataSchemaGenerator);
router.get("/datasets/export/:dataset_id", setDataToRequestObject("api.datasets.export"), onRequest({ entity: Entity.Management }), DatasetExport);
router.post("/datasets/copy", setDataToRequestObject("api.datasets.copy"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.copyDataset, operationType: OperationType.CREATE }), DatasetCopy);
router.post("/connectors/list", setDataToRequestObject("api.connectors.list"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.listConnectors, operationType: OperationType.GET }), ConnectorsList);
router.get("/connectors/read/:id", setDataToRequestObject("api.connectors.read"), onRequest({ entity: Entity.Management }), telemetryAuditStart({ action: telemetryActions.readConnectors, operationType: OperationType.GET }), ConnectorsRead);
router.post("/datasets/import", setDataToRequestObject("api.datasets.import"), onRequest({ entity: Entity.Management }), DatasetImport);
router.post("/data/in/:datasetId", setDataToRequestObject("api.data.in"), onRequest({ entity: Entity.Data_in }), telemetryAuditStart({action: telemetryActions.createDataset, operationType: OperationType.CREATE}), checkRBAC.handler(), dataIn);
router.post("/data/query/:datasetId", setDataToRequestObject("api.data.out"), onRequest({ entity: Entity.Data_out }), checkRBAC.handler(), dataOut);
router.post("/datasets/create", setDataToRequestObject("api.datasets.create"), onRequest({ entity: Entity.Management }),telemetryAuditStart({action: telemetryActions.createDataset, operationType: OperationType.CREATE}), checkRBAC.handler(),DatasetCreate)
router.patch("/datasets/update", setDataToRequestObject("api.datasets.update"), onRequest({ entity: Entity.Management }),telemetryAuditStart({action: telemetryActions.updateDataset, operationType: OperationType.UPDATE}), checkRBAC.handler(), DatasetUpdate)
router.get("/datasets/read/:dataset_id", setDataToRequestObject("api.datasets.read"), onRequest({ entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.readDataset, operationType: OperationType.GET}), checkRBAC.handler(), DatasetRead)
router.post("/datasets/list", setDataToRequestObject("api.datasets.list"), onRequest({ entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.listDatasets, operationType: OperationType.LIST}), checkRBAC.handler(), DatasetList)
router.get("/data/exhaust/:datasetId", setDataToRequestObject("api.data.exhaust"), onRequest({ entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.datasetExhaust, operationType: OperationType.GET}), checkRBAC.handler(), dataExhaust);
router.post("/template/create", setDataToRequestObject("api.query.template.create"), checkRBAC.handler(), createQueryTemplate);
router.get("/template/read/:templateId", setDataToRequestObject("api.query.template.read"), checkRBAC.handler(), readQueryTemplate);
router.delete("/template/delete/:templateId", setDataToRequestObject("api.query.template.delete"), checkRBAC.handler(), deleteQueryTemplate);
router.post("/template/list", setDataToRequestObject("api.query.template.list"), checkRBAC.handler(), listQueryTemplates);
router.patch("/template/update/:templateId", setDataToRequestObject("api.query.template.update"), checkRBAC.handler(), updateQueryTemplate);
router.post("/schema/validate", setDataToRequestObject("api.schema.validator"), checkRBAC.handler(), eventValidation);
router.post("/template/query/:templateId", setDataToRequestObject("api.query.template.query"), checkRBAC.handler(), queryTemplate);
router.post("/files/generate-url", setDataToRequestObject("api.files.generate-url"), onRequest({ entity: Entity.Management }), checkRBAC.handler(), GenerateSignedURL);
router.post("/datasets/status-transition", setDataToRequestObject("api.datasets.status-transition"), onRequest({ entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.createTransformation, operationType: OperationType.CREATE}), checkRBAC.handler(), DatasetStatusTansition);
router.post("/datasets/health", setDataToRequestObject("api.dataset.health"), onRequest({ entity: Entity.Management }), checkRBAC.handler(), datasetHealth);
router.post("/datasets/reset/:datasetId", setDataToRequestObject("api.dataset.reset"), onRequest({ entity: Entity.Management }), checkRBAC.handler(), datasetReset);
router.post("/datasets/dataschema", setDataToRequestObject("api.datasets.dataschema"), onRequest({ entity: Entity.Management }), checkRBAC.handler(), DataSchemaGenerator);
router.get("/datasets/export/:dataset_id", setDataToRequestObject("api.datasets.export"), onRequest({ entity: Entity.Management }), checkRBAC.handler(), DatasetExport);
router.post("/datasets/copy", setDataToRequestObject("api.datasets.copy"), onRequest({ entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.copyDataset, operationType: OperationType.CREATE}), checkRBAC.handler(), DatasetCopy);
router.post("/connectors/list", setDataToRequestObject("api.connectors.list"), onRequest({ entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.listConnectors, operationType: OperationType.GET}), checkRBAC.handler(), ConnectorsList);
router.get("/connectors/read/:id", setDataToRequestObject("api.connectors.read"), onRequest({entity: Entity.Management }), telemetryAuditStart({action: telemetryActions.readConnectors, operationType: OperationType.GET}), checkRBAC.handler(), ConnectorsRead);
router.post("/datasets/import", setDataToRequestObject("api.datasets.import"), onRequest({ entity: Entity.Management }), checkRBAC.handler(), DatasetImport);

//Wrapper Service
router.post("/obsrv/data/sql-query", setDataToRequestObject("api.obsrv.data.sql-query"), onRequest({ entity: Entity.Data_out }), sqlQuery);
router.post("/data/metrics", setDataToRequestObject("api.data.metrics"), onRequest({ entity: Entity.Data_out }), datasetMetrics)
router.post("/obsrv/data/sql-query", setDataToRequestObject("api.obsrv.data.sql-query"), onRequest({ entity: Entity.Data_out }), checkRBAC.handler(), sqlQuery);
router.post("/data/metrics", setDataToRequestObject("api.data.metrics"), onRequest({ entity: Entity.Data_out }), datasetMetrics)