Files
claudetools/clients/cascades-tucson/docs/migration/scripts/synology-permission-discovery.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

133 lines
5.1 KiB
PowerShell

# ============================================================================
# Synology Permission Discovery - cascadesDS (192.168.0.120)
# ----------------------------------------------------------------------------
# Runs on CS-SERVER via GuruRMM. Uses plink.exe (PuTTY) to SSH into the
# Synology and dump users, groups, share ACLs, and SMB share configs.
#
# Strictly read-only. No writes, no creates, no config changes.
# Output is captured and printed to stdout for the RMM command to return.
#
# Prepared: 2026-04-22 (Cascades Phase 4 Synology retirement prep)
# ============================================================================
$ErrorActionPreference = 'Continue'
$SynoHost = '192.168.0.120'
$SynoUser = 'admin'
# Password injected via command substitution at execution time.
# Do NOT hardcode here - see the submission wrapper.
$SynoPass = '__SYNO_PASSWORD__'
function Section($n) {
Write-Output ''
Write-Output ('=' * 72)
Write-Output "== $n"
Write-Output ('=' * 72)
}
# ----------------------------------------------------------------------------
Section 'Step 1: Connectivity'
# ----------------------------------------------------------------------------
$reach = Test-NetConnection -ComputerName $SynoHost -Port 22 -InformationLevel Detailed -WarningAction SilentlyContinue
Write-Output "SSH 192.168.0.120:22 reachable: $($reach.TcpTestSucceeded)"
if (-not $reach.TcpTestSucceeded) {
Write-Output 'ABORT: Cannot reach Synology over SSH. Check pfSense rules + Synology service state.'
exit 1
}
# ----------------------------------------------------------------------------
Section 'Step 2: Locate plink.exe'
# ----------------------------------------------------------------------------
$plink = $null
$candidates = @(
'C:\Program Files\PuTTY\plink.exe',
'C:\Program Files (x86)\PuTTY\plink.exe',
'C:\Tools\plink.exe'
)
foreach ($c in $candidates) { if (Test-Path $c) { $plink = $c; break } }
if (-not $plink) {
$plink = (Get-Command plink.exe -ErrorAction SilentlyContinue | Select-Object -First 1).Source
}
if (-not $plink) {
Write-Output 'ABORT: plink.exe not found. Install PuTTY or point at a plink.exe path.'
exit 1
}
Write-Output "plink.exe: $plink"
$plinkVer = & $plink -V 2>&1 | Select-Object -First 1
Write-Output "plink version: $plinkVer"
# ----------------------------------------------------------------------------
Section 'Step 3: Pre-accept SSH host key (first run)'
# ----------------------------------------------------------------------------
# plink with -batch refuses unknown host keys. Pre-accept by piping 'y' into a
# non-batch run that does a harmless probe. Subsequent calls use -batch.
$preaccept = 'y' | & $plink -ssh -pw $SynoPass "$SynoUser@$SynoHost" 'echo preaccept-ok' 2>&1
Write-Output ($preaccept -join "`n")
# ----------------------------------------------------------------------------
Section 'Step 4: Synology probe - identity + shares + ACLs'
# ----------------------------------------------------------------------------
# Single heredoc-style payload: one SSH session, captures everything.
# synogroup/synouser/synoacltool/synoshare often need sudo on DSM 7; admin is
# in administrators group but sudo may still prompt. Use 'sudo -S' with the
# password piped via echo to handle the prompt gracefully.
$probe = @'
PASS="__SUDO_PASSWORD__"
sudo_run() { echo "$PASS" | sudo -S -p '' "$@" 2>&1; }
echo "### date ###"
date
echo
echo "### dsm version ###"
cat /etc.defaults/VERSION 2>&1 || cat /etc/VERSION 2>&1
echo
echo "### synogroup --list ###"
sudo_run synogroup --list
echo
echo "### synogroup --enum global ###"
sudo_run synogroup --enum global
echo
echo "### synouser --enum all ###"
sudo_run synouser --enum all
echo
echo "### /volume1/ directory listing ###"
ls -la /volume1/ 2>&1
echo
echo "### /etc/samba/smb.share.conf ###"
sudo_run cat /etc/samba/smb.share.conf 2>&1 | head -200
echo
echo "### synoshare --enum all ###"
sudo_run synoshare --enum all
echo
for S in homes Management SalesDept Server chat Public Culinary IT Receptionist directoryshare Marketing; do
if [ -d /volume1/$S ]; then
echo "### synoshare --get $S ###"
sudo_run synoshare --get "$S"
echo
echo "### synoacltool -get /volume1/$S ###"
sudo_run synoacltool -get "/volume1/$S"
echo
echo "### ls -la /volume1/$S (first 15 entries) ###"
ls -la "/volume1/$S" 2>&1 | head -15
echo
fi
done
echo "### NET SESSIONS / SMB share access (smbstatus) ###"
sudo_run smbstatus -p 2>&1 | head -20
echo
echo "### EOF ###"
'@
# Splice in the sudo password (same as SSH password for admin account)
$probe = $probe -replace '__SUDO_PASSWORD__', [regex]::Escape($SynoPass) -replace '\\/','/'
# Proper way: use literal replacement without regex escape tricks
$probe = $probe.Replace('__SUDO_PASSWORD__', $SynoPass)
# Execute via plink; batch mode since host key is now cached
$result = $probe | & $plink -ssh -batch -pw $SynoPass "$SynoUser@$SynoHost" 'bash -s' 2>&1
$result | ForEach-Object { Write-Output $_ }
# ----------------------------------------------------------------------------
Section 'Done'
# ----------------------------------------------------------------------------
Write-Output "Completed at $(Get-Date)"