Files
claudetools/clients/cascades-tucson/docs/migration/scripts/synology-discovery-dsm-api.ps1
Howard Enos af4ad0aea3 cascades: CS-SERVER preflight verified + Synology discovery complete
CS-SERVER post-reboot verification: time sync, TLS 1.2 enforcement, and
Windows Server Backup feature all persisted cleanly. dcdiag clean. Ready
for Entra Connect install.

Synology cascadesDS permission inventory captured via DSM API (SSH
disabled by default on Synology). 35 users, 4 groups, 10 shares.
Analysis identifies 7 shared-account role logins (HIPAA violation),
8 departed-employee accounts to clean up, and 4 shares needing
Meredith-side confirmation before migration (pacs most sensitive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:59:38 -07:00

167 lines
7.2 KiB
PowerShell

# ============================================================================
# Synology Permission Discovery via DSM API - cascadesDS (192.168.0.120)
# ----------------------------------------------------------------------------
# Runs on CS-SERVER via GuruRMM. Uses DSM's HTTP API (port 5000) instead of
# SSH since SSH is not enabled on this Synology.
#
# Strictly read-only. Login -> list users/groups/shares -> per-share
# permissions -> logout.
#
# Prepared: 2026-04-22 (Cascades Phase 4 Synology retirement prep)
# ============================================================================
$ErrorActionPreference = 'Continue'
$SynoBase = 'http://192.168.0.120:5000'
$SynoUser = 'admin'
$SynoPass = '__SYNO_PASSWORD__'
function Section($n) {
Write-Output ''
Write-Output ('=' * 72)
Write-Output "== $n"
Write-Output ('=' * 72)
}
function Dump($label, $obj) {
Write-Output ''
Write-Output "--- $label ---"
try {
$obj | ConvertTo-Json -Depth 8 | Write-Output
} catch {
$obj | Format-List * | Out-String | Write-Output
}
}
# TLS changes queued for tonight but not yet effective (reboot @ 18:00). This
# talks HTTP port 5000 so TLS state doesn't matter here.
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
# ----------------------------------------------------------------------------
Section 'Step 0: API version discovery'
# ----------------------------------------------------------------------------
try {
$info = Invoke-RestMethod -Uri "$SynoBase/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth,SYNO.Core.User,SYNO.Core.Group,SYNO.Core.Share,SYNO.Core.Share.Permission,SYNO.Core.Group.Member"
Dump 'SYNO.API.Info' $info
} catch {
Write-Output "API info probe failed: $_"
exit 1
}
# Derive authenticate path + max version
$authPath = $info.data.'SYNO.API.Auth'.path
$authMax = $info.data.'SYNO.API.Auth'.maxVersion
Write-Output "auth path: $authPath, maxVersion: $authMax"
# ----------------------------------------------------------------------------
Section 'Step 1: Login'
# ----------------------------------------------------------------------------
$loginUri = "$SynoBase/webapi/$authPath" +
"?api=SYNO.API.Auth&version=$authMax&method=login" +
"&account=$([uri]::EscapeDataString($SynoUser))" +
"&passwd=$([uri]::EscapeDataString($SynoPass))" +
"&session=FileStation&format=sid"
try {
$loginResp = Invoke-RestMethod -Uri $loginUri
Dump 'login response' $loginResp
if (-not $loginResp.success) {
Write-Output "LOGIN FAILED: $($loginResp | ConvertTo-Json -Depth 5)"
exit 1
}
$sid = $loginResp.data.sid
Write-Output "sid: $sid"
} catch {
Write-Output "Login exception: $_"
exit 1
}
try {
# ------------------------------------------------------------------------
Section 'Step 2: Users'
# ------------------------------------------------------------------------
# SYNO.Core.User list with all additional fields
$userResp = Invoke-RestMethod -Uri ("$SynoBase/webapi/entry.cgi?api=SYNO.Core.User&version=1&method=list" +
"&offset=0&limit=500&type=local" +
"&additional=%5B%22email%22%2C%22description%22%2C%22expired%22%2C%22cannot_chg_passwd%22%2C%22passwd_never_expire%22%2C%22passwd_last_change%22%2C%22passwd_must_change%22%2C%22groups%22%5D" +
"&_sid=$sid")
Dump 'SYNO.Core.User list' $userResp
# ------------------------------------------------------------------------
Section 'Step 3: Groups'
# ------------------------------------------------------------------------
$groupResp = Invoke-RestMethod -Uri ("$SynoBase/webapi/entry.cgi?api=SYNO.Core.Group&version=1&method=list" +
"&offset=0&limit=500&type=local" +
"&additional=%5B%22description%22%2C%22group_type%22%2C%22members%22%5D" +
"&_sid=$sid")
Dump 'SYNO.Core.Group list' $groupResp
# Group members (separate call in some DSM versions)
$groups = $groupResp.data.groups
foreach ($g in $groups) {
try {
$memResp = Invoke-RestMethod -Uri ("$SynoBase/webapi/entry.cgi?api=SYNO.Core.Group.Member&version=1&method=list" +
"&group_name=$([uri]::EscapeDataString($g.name))" +
"&offset=0&limit=500&_sid=$sid")
Dump "members of group '$($g.name)'" $memResp
} catch {
Write-Output "Group member lookup for '$($g.name)' failed: $_"
}
}
# ------------------------------------------------------------------------
Section 'Step 4: Shares'
# ------------------------------------------------------------------------
$shareResp = Invoke-RestMethod -Uri ("$SynoBase/webapi/entry.cgi?api=SYNO.Core.Share&version=1&method=list" +
"&shareType=all&offset=0&limit=200" +
"&additional=%5B%22hidden%22%2C%22encryption%22%2C%22share_quota%22%2C%22recyclebin%22%2C%22enable_share_cow%22%2C%22enable_share_compress%22%2C%22name%22%2C%22vol_path%22%2C%22desc%22%5D" +
"&_sid=$sid")
Dump 'SYNO.Core.Share list' $shareResp
# ------------------------------------------------------------------------
Section 'Step 5: Per-share permissions'
# ------------------------------------------------------------------------
$shares = $shareResp.data.shares
foreach ($sh in $shares) {
try {
# Permissions by user
$permUser = Invoke-RestMethod -Uri ("$SynoBase/webapi/entry.cgi?api=SYNO.Core.Share.Permission&version=1&method=list" +
"&name=$([uri]::EscapeDataString($sh.name))" +
"&user_group_type=local_user&offset=0&limit=500" +
"&_sid=$sid")
Dump "share '$($sh.name)' - local user permissions" $permUser
# Permissions by group
$permGroup = Invoke-RestMethod -Uri ("$SynoBase/webapi/entry.cgi?api=SYNO.Core.Share.Permission&version=1&method=list" +
"&name=$([uri]::EscapeDataString($sh.name))" +
"&user_group_type=local_group&offset=0&limit=500" +
"&_sid=$sid")
Dump "share '$($sh.name)' - local group permissions" $permGroup
} catch {
Write-Output "Permission lookup for '$($sh.name)' failed: $_"
}
}
# ------------------------------------------------------------------------
Section 'Step 6: SMB share config (supplementary)'
# ------------------------------------------------------------------------
# SMB exposure state per share via FileService.SMB.Share or similar
try {
$smb = Invoke-RestMethod -Uri "$SynoBase/webapi/entry.cgi?api=SYNO.Core.FileServ.SMB&version=1&method=get&_sid=$sid"
Dump 'SMB service config' $smb
} catch {
Write-Output "SMB service config lookup failed: $_"
}
} finally {
# ------------------------------------------------------------------------
Section 'Logout'
# ------------------------------------------------------------------------
try {
$logout = Invoke-RestMethod -Uri "$SynoBase/webapi/$authPath?api=SYNO.API.Auth&version=$authMax&method=logout&session=FileStation&_sid=$sid"
Dump 'logout' $logout
} catch {
Write-Output "Logout failed: $_"
}
}
Section 'Done'
Write-Output "Completed at $(Get-Date)"