| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- """Cross-domain data security and security engineering APIs."""
- from __future__ import annotations
- from datetime import UTC, datetime
- from flask import current_app, g, jsonify, request
- from app import db
- from app.api.system import bp
- from app.core.system.permissions import (
- SECURITY_GOVERNANCE_MANAGE,
- SECURITY_GOVERNANCE_OPERATE,
- SECURITY_GOVERNANCE_READ,
- require_permissions,
- )
- from app.core.system.security_delivery import HttpsWebhookSyslogTransport
- from app.core.system.security_governance import SecurityGovernanceService
- from app.core.system.security_governance_repository import (
- SqlAlchemySecurityGovernanceRepository,
- WorkCenterSecurityApprovalGateway,
- )
- from app.models.result import failed, success
- def _service():
- allowlist = {
- item.strip().lower()
- for item in str(current_app.config.get("SECURITY_SIEM_HOST_ALLOWLIST") or "").split(",")
- if item.strip()
- }
- return SecurityGovernanceService(
- SqlAlchemySecurityGovernanceRepository(db.session),
- approval_gateway=WorkCenterSecurityApprovalGateway(db.session),
- siem_transport=HttpsWebhookSyslogTransport(),
- siem_host_allowlist=allowlist,
- commit=db.session.commit,
- rollback=db.session.rollback,
- )
- def _version():
- value = str(request.headers.get("If-Match") or "").strip().removeprefix("W/").strip('"')
- if not value.isdigit():
- raise ValueError("missing valid If-Match version")
- return int(value)
- def _etag(response, record):
- if record.get("current_version"):
- response.headers["ETag"] = f'"{record["current_version"]}"'
- return response
- def _execute(operation, *, created=False, versioned=False):
- try:
- result = operation()
- response = jsonify(success(result, code=201 if created else 200))
- if versioned and isinstance(result, dict):
- response = _etag(response, result)
- return (response, 201) if created else response
- except Exception as exc:
- db.session.rollback()
- if isinstance(exc, LookupError):
- status = 404
- elif isinstance(exc, PermissionError):
- status = 403
- elif isinstance(exc, RuntimeError):
- status = 409
- else:
- status = 400
- return jsonify(failed(str(exc), code=status)), status
- @bp.route("/security-governance/dashboard", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_dashboard():
- return _execute(_service().dashboard)
- @bp.route("/security-governance/profiles", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_list_profiles():
- return _execute(lambda: _service().list_profiles(business_domain_uid=request.args.get("business_domain_uid")))
- @bp.route("/security-governance/profiles", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_OPERATE)
- def security_create_profile():
- return _execute(
- lambda: _service().create_classification_profile(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]),
- created=True,
- )
- @bp.route("/security-governance/scans", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_list_scans():
- return _execute(lambda: _service().list_classification_scans(business_domain_uid=request.args.get("business_domain_uid")))
- @bp.route("/security-governance/scans", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_OPERATE)
- def security_create_scan():
- return _execute(
- lambda: _service().scan_sensitive_sample(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]),
- created=True,
- )
- @bp.route("/security-governance/findings", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_findings():
- return _execute(lambda: _service().list_classification_findings(status=request.args.get("status")))
- @bp.route("/security-governance/findings/<finding_uid>/review", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_review_finding(finding_uid):
- return _execute(
- lambda: _service().review_classification_finding(
- finding_uid, request.get_json(silent=True) or {}, expected_version=_version(), actor_uid=g.current_user["id"]
- ),
- versioned=True,
- )
- @bp.route("/security-governance/access-policies", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_list_access_policies():
- return _execute(lambda: _service().list_access_policies(business_domain_uid=request.args.get("business_domain_uid"), status=request.args.get("status")))
- @bp.route("/security-governance/access-policies", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_OPERATE)
- def security_create_access_policy():
- return _execute(
- lambda: _service().create_access_policy(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]),
- created=True,
- )
- @bp.route("/security-governance/access-decisions", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_access_decisions():
- return _execute(lambda: _service().list_access_decisions(decision=request.args.get("decision")))
- @bp.route("/security-governance/access/evaluate", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_OPERATE)
- def security_evaluate_access():
- body = request.get_json(silent=True) or {}
- body["user_uid"] = g.current_user["id"]
- body["roles"] = list(g.current_user["roles"])
- return _execute(lambda: _service().evaluate_access(body), created=True)
- @bp.route("/security-governance/egress", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_list_egress():
- return _execute(lambda: _service().list_egress_requests(status=request.args.get("status")))
- @bp.route("/security-governance/egress", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_OPERATE)
- def security_create_egress():
- return _execute(
- lambda: _service().submit_egress_request(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]),
- created=True,
- versioned=True,
- )
- @bp.route("/security-governance/egress/<request_uid>/reconcile", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_reconcile_egress(request_uid):
- return _execute(
- lambda: _service().reconcile_egress_request(request_uid, expected_version=_version(), actor_uid=g.current_user["id"]),
- versioned=True,
- )
- @bp.route("/security-governance/retention", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_list_retention():
- return _execute(_service().list_retention_policies)
- @bp.route("/security-governance/retention", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_create_retention():
- return _execute(
- lambda: _service().create_retention_policy(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]),
- created=True,
- )
- @bp.route("/security-governance/retention/candidates", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_retention_candidates():
- value = request.args.get("as_of") or datetime.now(UTC).isoformat()
- return _execute(lambda: _service().retention_candidates(as_of=value, limit=int(request.args.get("limit", 100))))
- @bp.route("/security-governance/siem/sinks", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_list_siem_sinks():
- return _execute(_service().list_siem_sinks)
- @bp.route("/security-governance/siem/sinks", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_create_siem_sink():
- return _execute(
- lambda: _service().create_siem_sink(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]),
- created=True,
- )
- @bp.route("/security-governance/siem/deliveries", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_siem_deliveries():
- return _execute(lambda: _service().list_siem_deliveries(sink_uid=request.args.get("sink_uid")))
- @bp.route("/security-governance/siem/sinks/<sink_uid>/dispatch", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_siem_dispatch(sink_uid):
- return _execute(lambda: _service().dispatch_siem_events(sink_uid, request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]), created=True)
- @bp.route("/security-governance/sboms", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_list_sboms():
- return _execute(_service().list_sboms)
- @bp.route("/security-governance/sboms", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_register_sbom():
- return _execute(lambda: _service().register_sbom(request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]), created=True)
- @bp.route("/security-governance/sboms/<sbom_uid>/vulnerabilities", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_import_vulnerabilities(sbom_uid):
- return _execute(lambda: _service().ingest_vulnerabilities(sbom_uid, request.get_json(silent=True) or {}, actor_uid=g.current_user["id"]), created=True)
- @bp.route("/security-governance/vulnerabilities", methods=["GET"])
- @require_permissions(SECURITY_GOVERNANCE_READ)
- def security_vulnerabilities():
- return _execute(lambda: _service().list_vulnerabilities(status=request.args.get("status"), severity=request.args.get("severity")))
- def _vulnerability_change(finding_uid, action):
- service = _service()
- method = getattr(service, f"{action}_vulnerability")
- return _execute(
- lambda: method(finding_uid, request.get_json(silent=True) or {}, expected_version=_version(), actor_uid=g.current_user["id"]),
- versioned=True,
- )
- @bp.route("/security-governance/vulnerabilities/<finding_uid>/assign", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_assign_vulnerability(finding_uid):
- return _vulnerability_change(finding_uid, "assign")
- @bp.route("/security-governance/vulnerabilities/<finding_uid>/resolve", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_resolve_vulnerability(finding_uid):
- return _vulnerability_change(finding_uid, "resolve")
- @bp.route("/security-governance/vulnerabilities/<finding_uid>/close", methods=["POST"])
- @require_permissions(SECURITY_GOVERNANCE_MANAGE)
- def security_close_vulnerability(finding_uid):
- return _vulnerability_change(finding_uid, "close")
|