Управление доступом в Azure может быстро превратиться в хаос: десятки групп, сотни пользователей, тысячи разрешений. Простая, динамическая визуализация в виде дерева мгновенно показывает структуру, пробелы и потенциальные риски.
В этом посте мы рассмотрим, как построить такую визуализацию прямо внутри .NET Interactive Jupyter Notebooks, используя чистые Vega JSON спецификации — без необходимости писать JavaScript-код.


✨ Почему Vega?

Хотя библиотеки вроде d3.js обеспечивают максимальную гибкость, Vega предоставляет декларативный способ создания мощных, интерактивных графиков с использованием только JSON.
Это делает её идеальной для интеграции в среду с активным скриптингом, например PowerShell-ноутбуки.


📄 Подготовка данных для визуализации

Перед тем как перейти к визуализациям, важно отметить:
Vega требует плоское представление иерархических данных.

Хотя членства в группах Azure или назначение ролей естественно образуют дерево, Vega ожидает представление в формате “id → родитель” в табличной форме.

В противном случае преобразования вроде stratify, tree и treelinks не смогут корректно построить структуру.

Пример ожидаемого формата для Vega:

[
  { "id": "root", "parent": null },
  { "id": "group-A", "parent": "root" },
  { "id": "user-John", "parent": "group-A" },
  { "id": "user-Delenn", "parent": "group-A" }
]

Чтобы преобразовать данные из Azure в этот формат, можно использовать простую рекурсивную функцию PowerShell:

$inputPath = "data/prodScopesAndPermissions.json"
$outputPath = "data/prodScopesAndPermissions_flat.json"

# reding JSON
$tree = Get-Content $inputPath -Raw | ConvertFrom-Json

# init result and ID counter
$flattened = @()
$idCounter = 0

function Flatten-Tree {
    param(
        [Parameter(Mandatory)] $Node,
        [Parameter()] $ParentId,
        [ref] $FlatList,
        [ref] $IdCounter
    )

    # increment ID
    $IdCounter.Value++

    # save current ID
    $currentId = $IdCounter.Value

    $FlatList.Value += [pscustomobject]@{
        id     = $currentId
        name   = $Node.name
        parent = $ParentId
    }

    foreach ($child in $Node.children) {
        Flatten-Tree -Node $child -ParentId $currentId -FlatList $FlatList -IdCounter $IdCounter
    }
}

# Run
Flatten-Tree -Node $tree -FlatList ([ref]$flattened) -IdCounter ([ref]$idCounter)

# Export results
$flattened | ConvertTo-Json -Depth 5 | Out-File $outputPath -Force

Write-Output "Flattened data saved to $outputPath"

🧹 Представление членства в группах

Первая визуализация показывает, какие пользователи входят в какие группы.

Начиная с Azure Active Directory, мы собираем данные о пользователях и группах с помощью PowerShell, преобразуем их в иерархическую структуру и отображаем с помощью Vega.

Вот как отобразить Группы и пользователей:

Vega-спецификация и её вызов в .NET Interactive Jupyter с PowerShell
@"
{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "Groups and Users view.",
  "width": 600,
  "height": 400,
  "padding": 5,
  "signals": [
    {"name": "labels", "value": true, "bind": {"input": "checkbox"}}
  ],
  "data": [
    {
      "name": "tree",
      "url": "permissionsData/userGroupData.json",
      "type": "json",
      "transform": [
        {"type": "stratify", "key": "id", "parentKey": "parent"},
        {
          "type": "tree",
          "method": "tidy",
          "size": [{"signal": "height"}, {"signal": "width - 100"}],
          "separation": "false",
          "as": ["y", "x", "depth", "children"]
        }
      ]
    },
    {
      "name": "links",
      "source": "tree",
      "transform": [
        {"type": "treelinks"},
        {
          "type": "linkpath",
          "orient": "horizontal",
          "shape": "diagonal"
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "color",
      "type": "ordinal",
      "domain": [0, 1, 2],
      "range": ["#5e4fa2", "#3288bd", "#66c2a5"]
    }
  ],
  "marks": [
    {
      "type": "path",
      "from": {"data": "links"},
      "encode": {
        "update": {"path": {"field": "path"}, "stroke": {"value": "#ccc"}}
      }
    },
    {
      "type": "symbol",
      "from": {"data": "tree"},
      "encode": {
        "enter": {"size": {"value": 100}, "stroke": {"value": "#fff"}},
        "update": {
          "x": {"field": "x"},
          "y": {"field": "y"},
          "fill": {"scale": "color", "field": "depth"}
        }
      }
    },
    {
      "type": "text",
      "from": {"data": "tree"},
      "encode": {
        "enter": {
          "text": {"field": "name"},
          "fontSize": {"value": 9},
          "baseline": {"value": "middle"}
        },
        "update": {
          "x": {"field": "x"},
          "y": {"field": "y"},
          "dx": {"signal": "datum.children ? -7 : 7"},
          "align": {"signal": "datum.children ? 'right' : 'left'"},
          "opacity": {"signal": "labels ? 1 : 0"}
        }
      }
    }
  ]
}
"@ | Out-Display -MimeType "application/vnd.vega.v5+json"

🛡️ Представление разрешений

Вторая визуализация показывает, какие пользователи, группы и сервисные принципы имеют какие разрешения на ресурсы Azure.

Vega-спецификация и вызов в ноутбуке для визуализации разрешений
@"
{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "User and Group Permissions.",
  "width": 600,
  "height": 1200,
  "padding": 5,
  "signals": [
    {"name": "labels", "value": true, "bind": {"input": "checkbox"}}
  ],
  "data": [
    {
      "name": "tree",
      "url": "permissionsData/prodScopesAndPermissions.json",
      "type": "json",
      "transform": [
        {"type": "stratify", "key": "id", "parentKey": "parent"},
        {
          "type": "tree",
          "method": "tidy",
          "size": [{"signal": "height"}, {"signal": "width - 100"}],
          "separation": "false",
          "as": ["y", "x", "depth", "children"]
        }
      ]
    },
    {
      "name": "links",
      "source": "tree",
      "transform": [
        {"type": "treelinks"},
        {
          "type": "linkpath",
          "orient": "horizontal",
          "shape": "diagonal"
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "color",
      "type": "ordinal",
      "domain": [0, 1, 2],
      "range": ["#5e4fa2", "#3288bd", "#66c2a5"]
    }
  ],
  "marks": [
    {
      "type": "path",
      "from": {"data": "links"},
      "encode": {
        "update": {"path": {"field": "path"}, "stroke": {"value": "#ccc"}}
      }
    },
    {
      "type": "symbol",
      "from": {"data": "tree"},
      "encode": {
        "enter": {"size": {"value": 100}, "stroke": {"value": "#fff"}},
        "update": {
          "x": {"field": "x"},
          "y": {"field": "y"},
          "fill": {"scale": "color", "field": "depth"}
        }
      }
    },
    {
      "type": "text",
      "from": {"data": "tree"},
      "encode": {
        "enter": {
          "text": {"field": "name"},
          "fontSize": {"value": 9},
          "baseline": {"value": "middle"}
        },
        "update": {
          "x": {"field": "x"},
          "y": {"field": "y"},
          "dx": {"signal": "datum.children ? -7 : 7"},
          "align": {"signal": "datum.children ? 'right' : 'left'"},
          "opacity": {"signal": "labels ? 1 : 0"}
        }
      }
    }
  ]
}
"@ | Out-Display -MimeType "application/vnd.vega.v5+json"

🚀 Результаты

Имея всего два JSON-файла и немного магии Observable или .NET Interactive, вы можете мгновенно визуализировать сложные модели доступа Azure — и наконец-то увидеть, как связаны ваши пользователи, группы, сервисные принципы и назначения ролей.


📋 Что дальше?

Вы можете расширить этот подход, чтобы:


В целом, если вы можете извлечь данные — вы можете их визуализировать.

Как и обычно с ноутбуками, вы можете открыть их в GitHub Codespace и протестировать:

Open in GitHub Codespaces

Удачи! 🚀