feat: integrate AD domain account impersonation for SQL Windows Authentication in SqlService, SettingsView, and SqlDatabaseView

This commit is contained in:
2026-10-21 08:37:11 +09:00
parent 5978de5924
commit 13daed47df
6 changed files with 332 additions and 98 deletions
+14
View File
@@ -1,5 +1,19 @@
# Enterprise Admin Studio - 변경 이력 (CHANGELOG.md) # Enterprise Admin Studio - 변경 이력 (CHANGELOG.md)
## [v1.6.0] - 2026-10-21
### Added
- **MS SQL Server AD 도메인 계정 기반 Windows 통합 인증 위장(Impersonation) 엔진 탑재**:
- `Modules/SqlService.ps1`: Win32 `advapi32.dll`의 `LogonUser` API(Type 9 `LOGON32_LOGON_NEW_CREDENTIALS`, `/netonly`)와 `WindowsIdentity.RunImpersonated`를 결합하여, 사용자가 입력한 AD 도메인 ID/PW를 Windows 보안 토큰으로 위장한 뒤 SQL Server에 `Integrated Security=True`로 인증하는 엔진 구현
- `Test-SqlConnection`: SQL Server가 실제로 누구로 인증했는지 `SELECT SUSER_SNAME()`을 질의하여 반환 메시지에 실시간 출력
- `Get-SqlDatabases`, `Invoke-SqlBackupAction`, `Invoke-SqlQuerySnippet`: 모든 SQL 작업이 설정된 인증 컨텍스트(`Invoke-WithSqlAuthContext`) 하에서 안전하게 실행되도록 통합
- **환경설정 UI (`Views/SettingsView.xaml`) 및 SQL 관리 화면 (`Views/SqlDatabaseView.xaml`) 연동**:
- 인증 방식 선택 옵션 3종 확장:
1) 현재 PC Windows 통합 인증 (Current Session)
2) AD 도메인 계정 위장 Windows 인증 (AD LogonUser /netonly)
3) SQL Server 자체 계정 인증 (User ID / Password)
- `SettingsView.xaml`: `TxtSqlDomain` (AD 도메인 입력칸), `LblSqlDomain`, 동적 설명 힌트(`TxtSqlAuthHint`) 및 선택 변경에 따른 입력 필드 실시간 활성화/비활성화 처리 연동
- `config.json`: `MsSql.Domain` 필드 추가 및 영속적 저장 지원
## [v1.5.0] - 2026-10-20 ## [v1.5.0] - 2026-10-20
### Added ### Added
- **다중 도메인 컨트롤러(6대 DC) 환경 NetApp 활성 DC 감지 및 복제 지연(Replication Lag) 방지 전략 탑재**: - **다중 도메인 컨트롤러(6대 DC) 환경 NetApp 활성 DC 감지 및 복제 지연(Replication Lag) 방지 전략 탑재**:
+147 -20
View File
@@ -1,6 +1,99 @@
# Modules\SqlService.ps1 # Modules\SqlService.ps1
# Microsoft SQL Server Management Service Module (.NET System.Data.SqlClient Live & Fallback) # Microsoft SQL Server Management Service Module (.NET System.Data.SqlClient Live & Fallback)
# P/Invoke helper for Windows Token Impersonation (AD Domain user outbound network authentication)
$winAuthTypeDef = @"
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
public class WinAuthUniversalImpersonator {
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr handle);
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9; // /netonly network outbound impersonation
public const int LOGON32_PROVIDER_DEFAULT = 0;
public static void RunImpersonatedAction(string username, string domain, string password, Action action) {
IntPtr tokenHandle = IntPtr.Zero;
bool success = LogonUser(username, domain, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, out tokenHandle);
if (!success || tokenHandle == IntPtr.Zero) {
int err = Marshal.GetLastWin32Error();
throw new InvalidOperationException("AD 도메인 로그온(LogonUser) 실패: Win32 에러 코드 " + err);
}
try {
using (SafeAccessTokenHandle safeHandle = new SafeAccessTokenHandle(tokenHandle)) {
WindowsIdentity.RunImpersonated(safeHandle, action);
}
}
finally {
// SafeAccessTokenHandle owns and closes tokenHandle
}
}
}
"@
if (-not ([System.Management.Automation.PSTypeName]"WinAuthUniversalImpersonator").Type) {
Add-Type -TypeDefinition $winAuthTypeDef
}
function Invoke-WithSqlAuthContext {
param(
[string]$AuthType = "Integrated",
[string]$Domain = "",
[string]$Username = "",
[string]$Password = "",
[scriptblock]$Action
)
if ($AuthType -eq "AdImpersonated" -and -not [string]::IsNullOrWhiteSpace($Username) -and -not [string]::IsNullOrWhiteSpace($Password)) {
$cleanDomain = $Domain
$cleanUser = $Username
if ($cleanUser -like "*\*") {
$parts = $cleanUser -split "\\", 2
$cleanDomain = $parts[0]
$cleanUser = $parts[1]
} elseif ($cleanUser -like "*@*") {
$parts = $cleanUser -split "@", 2
$cleanUser = $parts[0]
$cleanDomain = $parts[1]
}
if ([string]::IsNullOrWhiteSpace($cleanDomain)) {
$cleanDomain = "."
}
$resHolder = [hashtable]@{ Result = $null; Exception = $null }
[WinAuthUniversalImpersonator]::RunImpersonatedAction($cleanUser, $cleanDomain, $Password, [Action]{
try {
$resHolder.Result = & $Action
} catch {
$resHolder.Exception = $_
}
})
if ($resHolder.Exception) {
throw $resHolder.Exception
}
return $resHolder.Result
} else {
return & $Action
}
}
function Build-SqlConnectionString { function Build-SqlConnectionString {
param( param(
[string]$Server, [string]$Server,
@@ -17,7 +110,8 @@ function Build-SqlConnectionString {
$builder["Connect Timeout"] = $Timeout $builder["Connect Timeout"] = $Timeout
$builder["Application Name"] = "EnterpriseAdminStudio" $builder["Application Name"] = "EnterpriseAdminStudio"
if ($AuthType -eq "Integrated" -or [string]::IsNullOrWhiteSpace($Username)) { # Integrated(현재 세션) 및 AdImpersonated(AD 위장 토큰)는 SQL Server 관점에서 둘 다 Windows 통합 인증(SSPI)
if ($AuthType -eq "Integrated" -or $AuthType -eq "AdImpersonated" -or [string]::IsNullOrWhiteSpace($Username)) {
$builder["Integrated Security"] = $true $builder["Integrated Security"] = $true
} else { } else {
$builder["Integrated Security"] = $false $builder["Integrated Security"] = $false
@@ -32,40 +126,55 @@ function Test-SqlConnection {
param( param(
[string]$Server, [string]$Server,
[string]$AuthType = "Integrated", [string]$AuthType = "Integrated",
[string]$Domain = "",
[string]$Username = "", [string]$Username = "",
[string]$Password = "", [string]$Password = "",
[int]$Timeout = 5 [int]$Timeout = 5
) )
try {
$connStr = Build-SqlConnectionString -Server $Server -AuthType $AuthType -Username $Username -Password $Password -Timeout $Timeout $connStr = Build-SqlConnectionString -Server $Server -AuthType $AuthType -Username $Username -Password $Password -Timeout $Timeout
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
$execResult = Invoke-WithSqlAuthContext -AuthType $AuthType -Domain $Domain -Username $Username -Password $Password -Action {
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
try { try {
$conn.Open() $conn.Open()
$cmd = $conn.CreateCommand() $cmd = $conn.CreateCommand()
$cmd.CommandText = "SELECT @@VERSION AS [Version], SERVERPROPERTY('ProductVersion') AS [ProductVersion], SERVERPROPERTY('Edition') AS [Edition]" $cmd.CommandText = "SELECT SUSER_SNAME() AS [LoginName], @@VERSION AS [Version], SERVERPROPERTY('ProductVersion') AS [ProductVersion], SERVERPROPERTY('Edition') AS [Edition]"
$reader = $cmd.ExecuteReader() $reader = $cmd.ExecuteReader()
if ($reader.Read()) { if ($reader.Read()) {
$loginName = $reader["LoginName"].ToString()
$ver = $reader["ProductVersion"].ToString() $ver = $reader["ProductVersion"].ToString()
$edition = $reader["Edition"].ToString() $edition = $reader["Edition"].ToString()
$authDesc = switch ($AuthType) {
"AdImpersonated" { "AD 계정 위장 Windows 통합 인증 ($loginName)" }
"Integrated" { "현재 세션 Windows 통합 인증 ($loginName)" }
default { "SQL 계정 인증 ($loginName)" }
}
return [PSCustomObject]@{ return [PSCustomObject]@{
Success = $true Success = $true
Message = "SQL Server 연결 성공 - 버전: $ver ($edition)" Message = "SQL Server 연결 성공!`n- 인증 방식: $authDesc`n- 서버 버전: $ver ($edition)"
LoginName = $loginName
Version = "$ver ($edition)" Version = "$ver ($edition)"
} }
} }
return [PSCustomObject]@{ Success = $true; Message = "SQL Server 연결 성공" } return [PSCustomObject]@{ Success = $true; Message = "SQL Server 연결 성공" }
} catch {
return [PSCustomObject]@{
Success = $false
Message = "SQL Server 연결 실패: $($_.Exception.Message)"
}
} finally { } finally {
if ($conn.State -eq [System.Data.ConnectionState]::Open) { if ($conn.State -eq [System.Data.ConnectionState]::Open) {
$conn.Close() $conn.Close()
} }
$conn.Dispose() $conn.Dispose()
} }
}
return $execResult
} catch {
return [PSCustomObject]@{
Success = $false
Message = "SQL Server 연결 실패: $($_.Exception.Message)"
}
}
} }
function Get-SqlDatabases { function Get-SqlDatabases {
@@ -79,13 +188,16 @@ function Get-SqlDatabases {
if ($useLive) { if ($useLive) {
$server = $Config.MsSql.Server $server = $Config.MsSql.Server
$auth = $Config.MsSql.AuthType $auth = $Config.MsSql.AuthType
$domain = if ($Config.MsSql.Domain) { $Config.MsSql.Domain } else { "" }
$user = $Config.MsSql.Username $user = $Config.MsSql.Username
$pass = $Config.MsSql.Password $pass = $Config.MsSql.Password
$timeout = if ($Config.MsSql.ConnectionTimeout) { [int]$Config.MsSql.ConnectionTimeout } else { 3 } $timeout = if ($Config.MsSql.ConnectionTimeout) { [int]$Config.MsSql.ConnectionTimeout } else { 3 }
try {
$connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout $timeout $connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout $timeout
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
$dbList = Invoke-WithSqlAuthContext -AuthType $auth -Domain $domain -Username $user -Password $pass -Action {
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
try { try {
$conn.Open() $conn.Open()
$cmd = $conn.CreateCommand() $cmd = $conn.CreateCommand()
@@ -124,13 +236,16 @@ ORDER BY d.name;
} }
} }
return $list.ToArray() return $list.ToArray()
} catch {
Write-Warning "실제 SQL Server 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
} finally { } finally {
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() } if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
$conn.Dispose() $conn.Dispose()
} }
} }
return $dbList
} catch {
Write-Warning "실제 SQL Server 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
}
}
return Get-SqlSampleDatabases -Filter $Filter return Get-SqlSampleDatabases -Filter $Filter
} }
@@ -147,6 +262,7 @@ function Invoke-SqlBackupAction {
if ($useLive) { if ($useLive) {
$server = $Config.MsSql.Server $server = $Config.MsSql.Server
$auth = $Config.MsSql.AuthType $auth = $Config.MsSql.AuthType
$domain = if ($Config.MsSql.Domain) { $Config.MsSql.Domain } else { "" }
$user = $Config.MsSql.Username $user = $Config.MsSql.Username
$pass = $Config.MsSql.Password $pass = $Config.MsSql.Password
$backupDir = if ($Config.MsSql.BackupDirectory) { $Config.MsSql.BackupDirectory } else { "C:\Backups" } $backupDir = if ($Config.MsSql.BackupDirectory) { $Config.MsSql.BackupDirectory } else { "C:\Backups" }
@@ -159,9 +275,11 @@ function Invoke-SqlBackupAction {
$fileName = "$DatabaseName`_$timeStamp.bak" $fileName = "$DatabaseName`_$timeStamp.bak"
$targetPath = Join-Path $backupDir $fileName $targetPath = Join-Path $backupDir $fileName
try {
$connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout 30 $connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout 30
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
$res = Invoke-WithSqlAuthContext -AuthType $auth -Domain $domain -Username $user -Password $pass -Action {
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
try { try {
$conn.Open() $conn.Open()
$cmd = $conn.CreateCommand() $cmd = $conn.CreateCommand()
@@ -175,15 +293,18 @@ function Invoke-SqlBackupAction {
BackupPath = $targetPath BackupPath = $targetPath
Message = "실제 SQL Server 데이터베이스 [$DatabaseName] 백업이 완료되었습니다.`n저장 경로: $targetPath" Message = "실제 SQL Server 데이터베이스 [$DatabaseName] 백업이 완료되었습니다.`n저장 경로: $targetPath"
} }
} finally {
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
$conn.Dispose()
}
}
return $res
} catch { } catch {
return [PSCustomObject]@{ return [PSCustomObject]@{
Success = $false Success = $false
Database = $DatabaseName Database = $DatabaseName
Message = "SQL 백업 실패: $($_.Exception.Message)" Message = "SQL 백업 실패: $($_.Exception.Message)"
} }
} finally {
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
$conn.Dispose()
} }
} }
@@ -206,12 +327,15 @@ function Invoke-SqlQuerySnippet {
if ($useLive) { if ($useLive) {
$server = $Config.MsSql.Server $server = $Config.MsSql.Server
$auth = $Config.MsSql.AuthType $auth = $Config.MsSql.AuthType
$domain = if ($Config.MsSql.Domain) { $Config.MsSql.Domain } else { "" }
$user = $Config.MsSql.Username $user = $Config.MsSql.Username
$pass = $Config.MsSql.Password $pass = $Config.MsSql.Password
try {
$connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout 15 $connStr = Build-SqlConnectionString -Server $server -AuthType $auth -Username $user -Password $pass -Timeout 15
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
$res = Invoke-WithSqlAuthContext -AuthType $auth -Domain $domain -Username $user -Password $pass -Action {
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
try { try {
$conn.Open() $conn.Open()
$cmd = $conn.CreateCommand() $cmd = $conn.CreateCommand()
@@ -228,15 +352,18 @@ function Invoke-SqlQuerySnippet {
RowCount = $table.Rows.Count RowCount = $table.Rows.Count
Output = "(성공: $($table.Rows.Count)개 행 반환됨 - $((Get-Date).ToString('HH:mm:ss')))`n$($sw.ToString())" Output = "(성공: $($table.Rows.Count)개 행 반환됨 - $((Get-Date).ToString('HH:mm:ss')))`n$($sw.ToString())"
} }
} finally {
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
$conn.Dispose()
}
}
return $res
} catch { } catch {
return [PSCustomObject]@{ return [PSCustomObject]@{
Success = $false Success = $false
RowCount = 0 RowCount = 0
Output = "(오류 발생)`n$($_.Exception.Message)" Output = "(오류 발생)`n$($_.Exception.Message)"
} }
} finally {
if ($conn.State -eq [System.Data.ConnectionState]::Open) { $conn.Close() }
$conn.Dispose()
} }
} }
+93 -7
View File
@@ -228,6 +228,7 @@ $script:loadSettingsToUI = {
$txtSqlServer = $v.FindName("TxtSqlServer") $txtSqlServer = $v.FindName("TxtSqlServer")
$cmbSqlAuth = $v.FindName("CmbSqlAuthType") $cmbSqlAuth = $v.FindName("CmbSqlAuthType")
$txtSqlBackupDir = $v.FindName("TxtSqlBackupDir") $txtSqlBackupDir = $v.FindName("TxtSqlBackupDir")
$txtSqlDomain = $v.FindName("TxtSqlDomain")
$txtSqlUser = $v.FindName("TxtSqlUser") $txtSqlUser = $v.FindName("TxtSqlUser")
$txtSqlPassword = $v.FindName("TxtSqlPassword") $txtSqlPassword = $v.FindName("TxtSqlPassword")
@@ -250,7 +251,14 @@ $script:loadSettingsToUI = {
if ($chkSqlLive) { $chkSqlLive.IsChecked = [bool]$c.MsSql.UseLive } if ($chkSqlLive) { $chkSqlLive.IsChecked = [bool]$c.MsSql.UseLive }
if ($txtSqlServer) { $txtSqlServer.Text = $c.MsSql.Server } if ($txtSqlServer) { $txtSqlServer.Text = $c.MsSql.Server }
if ($cmbSqlAuth) { $cmbSqlAuth.SelectedIndex = if ($c.MsSql.AuthType -eq "Sql") { 1 } else { 0 } } if ($txtSqlDomain) { $txtSqlDomain.Text = if ($c.MsSql.Domain) { $c.MsSql.Domain } else { "corp" } }
if ($cmbSqlAuth) {
$cmbSqlAuth.SelectedIndex = switch ($c.MsSql.AuthType) {
"AdImpersonated" { 1 }
"Sql" { 2 }
default { 0 }
}
}
if ($txtSqlBackupDir) { $txtSqlBackupDir.Text = $c.MsSql.BackupDirectory } if ($txtSqlBackupDir) { $txtSqlBackupDir.Text = $c.MsSql.BackupDirectory }
if ($txtSqlUser) { $txtSqlUser.Text = $c.MsSql.Username } if ($txtSqlUser) { $txtSqlUser.Text = $c.MsSql.Username }
if ($txtSqlPassword) { $txtSqlPassword.Password = $c.MsSql.Password } if ($txtSqlPassword) { $txtSqlPassword.Password = $c.MsSql.Password }
@@ -1152,12 +1160,22 @@ function Navigate-ToPage {
$btnTestConn = $view.FindName("BtnTestSqlConnection") $btnTestConn = $view.FindName("BtnTestSqlConnection")
$btnBackup = $view.FindName("BtnBackupDb") $btnBackup = $view.FindName("BtnBackupDb")
$btnExecQuery = $view.FindName("BtnExecuteQuery") $btnExecQuery = $view.FindName("BtnExecuteQuery")
$cmbSqlAuthInDb = $view.FindName("CmbSqlAuth")
if ($cmbSqlAuthInDb) {
$cmbSqlAuthInDb.SelectedIndex = switch ($global:AppConfig.MsSql.AuthType) {
"AdImpersonated" { 1 }
"Sql" { 2 }
default { 0 }
}
}
if ($btnSqlRefresh) { $btnSqlRefresh.add_Click({ & $script:reloadSql }) } if ($btnSqlRefresh) { $btnSqlRefresh.add_Click({ & $script:reloadSql }) }
if ($btnTestConn) { if ($btnTestConn) {
$btnTestConn.add_Click({ $btnTestConn.add_Click({
$res = Test-SqlConnection -Server $global:AppConfig.MsSql.Server -AuthType $global:AppConfig.MsSql.AuthType -Username $global:AppConfig.MsSql.Username -Password $global:AppConfig.MsSql.Password $cfg = $global:AppConfig.MsSql
$res = Test-SqlConnection -Server $cfg.Server -AuthType $cfg.AuthType -Domain $cfg.Domain -Username $cfg.Username -Password $cfg.Password
$img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Error } $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Error }
[System.Windows.MessageBox]::Show($res.Message, "MS SQL 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img) [System.Windows.MessageBox]::Show($res.Message, "MS SQL 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img)
}) })
@@ -1209,6 +1227,58 @@ function Navigate-ToPage {
& $script:loadSettingsToUI & $script:loadSettingsToUI
$updateSqlAuthUi = {
$v = $ViewCache["Settings"]
if (-not $v) { return }
$cmb = $v.FindName("CmbSqlAuthType")
$txtHint = $v.FindName("TxtSqlAuthHint")
$lblDomain = $v.FindName("LblSqlDomain")
$txtDomain = $v.FindName("TxtSqlDomain")
$lblUser = $v.FindName("LblSqlUser")
$txtUser = $v.FindName("TxtSqlUser")
$lblPass = $v.FindName("LblSqlPassword")
$txtPass = $v.FindName("TxtSqlPassword")
switch ($cmb.SelectedIndex) {
1 {
# AdImpersonated
if ($txtHint) { $txtHint.Text = "입력한 AD 도메인 ID/PW를 Windows 토큰(/netonly)으로 위장하여 SQL Server에 Windows 통합 인증으로 접속합니다." }
if ($lblDomain) { $lblDomain.Text = "AD 도메인 (예: corp 또는 corp.local):" }
if ($lblUser) { $lblUser.Text = "AD 도메인 계정 ID (예: sql_admin):" }
if ($lblPass) { $lblPass.Text = "AD 도메인 계정 암호:" }
if ($txtDomain) { $txtDomain.IsEnabled = $true; $txtDomain.Opacity = 1.0 }
if ($txtUser) { $txtUser.IsEnabled = $true; $txtUser.Opacity = 1.0 }
if ($txtPass) { $txtPass.IsEnabled = $true; $txtPass.Opacity = 1.0 }
}
2 {
# Sql Auth
if ($txtHint) { $txtHint.Text = "SQL Server 자체 계정(sa 또는 DB 전용 계정)과 암호로 접속합니다." }
if ($lblDomain) { $lblDomain.Text = "AD 도메인 (SQL 인증 시 불필요):" }
if ($lblUser) { $lblUser.Text = "SQL Server 계정 ID (예: sa):" }
if ($lblPass) { $lblPass.Text = "SQL Server 계정 암호:" }
if ($txtDomain) { $txtDomain.IsEnabled = $false; $txtDomain.Opacity = 0.5 }
if ($txtUser) { $txtUser.IsEnabled = $true; $txtUser.Opacity = 1.0 }
if ($txtPass) { $txtPass.IsEnabled = $true; $txtPass.Opacity = 1.0 }
}
default {
# Integrated (Current Windows User)
if ($txtHint) { $txtHint.Text = "현재 윈도우에 로그인된 사용자 자격증명(Kerberos/NTLM)으로 SQL Server에 접속합니다." }
if ($lblDomain) { $lblDomain.Text = "AD 도메인 (현재 세션 인증 시 불필요):" }
if ($lblUser) { $lblUser.Text = "로그인 계정 (현재 Windows 로그인 토큰 사용):" }
if ($lblPass) { $lblPass.Text = "비밀번호 (현재 세션 인증 시 불필요):" }
if ($txtDomain) { $txtDomain.IsEnabled = $false; $txtDomain.Opacity = 0.5 }
if ($txtUser) { $txtUser.IsEnabled = $false; $txtUser.Opacity = 0.5 }
if ($txtPass) { $txtPass.IsEnabled = $false; $txtPass.Opacity = 0.5 }
}
}
}
$cmbSqlAuth = $view.FindName("CmbSqlAuthType")
if ($cmbSqlAuth) {
$cmbSqlAuth.add_SelectionChanged({ & $updateSqlAuthUi })
}
& $updateSqlAuthUi
# Test AD # Test AD
if ($btnTestAd) { if ($btnTestAd) {
$btnTestAd.add_Click({ $btnTestAd.add_Click({
@@ -1246,11 +1316,16 @@ function Navigate-ToPage {
$v = $ViewCache["Settings"] $v = $ViewCache["Settings"]
$srv = $v.FindName("TxtSqlServer").Text $srv = $v.FindName("TxtSqlServer").Text
$cmb = $v.FindName("CmbSqlAuthType") $cmb = $v.FindName("CmbSqlAuthType")
$authType = if ($cmb.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } $authType = switch ($cmb.SelectedIndex) {
1 { "AdImpersonated" }
2 { "Sql" }
default { "Integrated" }
}
$domain = $v.FindName("TxtSqlDomain").Text
$user = $v.FindName("TxtSqlUser").Text $user = $v.FindName("TxtSqlUser").Text
$pass = $v.FindName("TxtSqlPassword").Password $pass = $v.FindName("TxtSqlPassword").Password
$res = Test-SqlConnection -Server $srv -AuthType $authType -Username $user -Password $pass $res = Test-SqlConnection -Server $srv -AuthType $authType -Domain $domain -Username $user -Password $pass
$img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Warning } $img = if ($res.Success) { [System.Windows.MessageBoxImage]::Information } else { [System.Windows.MessageBoxImage]::Warning }
[System.Windows.MessageBox]::Show($res.Message, "MS SQL 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img) [System.Windows.MessageBox]::Show($res.Message, "MS SQL 연결 테스트", [System.Windows.MessageBoxButton]::OK, $img)
}) })
@@ -1273,13 +1348,18 @@ function Navigate-ToPage {
$srv = $v.FindName("TxtSqlServer").Text $srv = $v.FindName("TxtSqlServer").Text
$cmb = $v.FindName("CmbSqlAuthType") $cmb = $v.FindName("CmbSqlAuthType")
$authType = if ($cmb.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } $authType = switch ($cmb.SelectedIndex) {
1 { "AdImpersonated" }
2 { "Sql" }
default { "Integrated" }
}
$sDomain = $v.FindName("TxtSqlDomain").Text
$sUser = $v.FindName("TxtSqlUser").Text $sUser = $v.FindName("TxtSqlUser").Text
$sPass = $v.FindName("TxtSqlPassword").Password $sPass = $v.FindName("TxtSqlPassword").Password
$adRes = Test-ADConnection -DomainController $dc -BaseOU $ou -Username $user -Password $pass -UseLdaps $ldaps $adRes = Test-ADConnection -DomainController $dc -BaseOU $ou -Username $user -Password $pass -UseLdaps $ldaps
$netappRes = Test-NetAppConnection -ClusterEndpoint $ep -Username $nUser -Password $nPass -IgnoreSsl $ignoreSsl $netappRes = Test-NetAppConnection -ClusterEndpoint $ep -Username $nUser -Password $nPass -IgnoreSsl $ignoreSsl
$sqlRes = Test-SqlConnection -Server $srv -AuthType $authType -Username $sUser -Password $sPass $sqlRes = Test-SqlConnection -Server $srv -AuthType $authType -Domain $sDomain -Username $sUser -Password $sPass
$report = @" $report = @"
=== 전체 인프라 일괄 진단 결과 === === 전체 인프라 일괄 진단 결과 ===
@@ -1305,7 +1385,11 @@ function Navigate-ToPage {
$btnSave.add_Click({ $btnSave.add_Click({
$v = $ViewCache["Settings"] $v = $ViewCache["Settings"]
$cmb = $v.FindName("CmbSqlAuthType") $cmb = $v.FindName("CmbSqlAuthType")
$authType = if ($cmb.SelectedIndex -eq 1) { "Sql" } else { "Integrated" } $authType = switch ($cmb.SelectedIndex) {
1 { "AdImpersonated" }
2 { "Sql" }
default { "Integrated" }
}
$chkGlobalLive = $v.FindName("ChkGlobalLiveMode") $chkGlobalLive = $v.FindName("ChkGlobalLiveMode")
$chkAdLive = $v.FindName("ChkAdLive") $chkAdLive = $v.FindName("ChkAdLive")
@@ -1340,6 +1424,7 @@ function Navigate-ToPage {
UseLive = [bool]($chkGlobalLive.IsChecked -or $chkSqlLive.IsChecked) UseLive = [bool]($chkGlobalLive.IsChecked -or $chkSqlLive.IsChecked)
Server = $v.FindName("TxtSqlServer").Text Server = $v.FindName("TxtSqlServer").Text
AuthType = $authType AuthType = $authType
Domain = $v.FindName("TxtSqlDomain").Text
Username = $v.FindName("TxtSqlUser").Text Username = $v.FindName("TxtSqlUser").Text
Password = $v.FindName("TxtSqlPassword").Password Password = $v.FindName("TxtSqlPassword").Password
DefaultDatabase = "master" DefaultDatabase = "master"
@@ -1365,6 +1450,7 @@ function Navigate-ToPage {
$btnReload.add_Click({ $btnReload.add_Click({
$global:AppConfig = Get-AppConfig $global:AppConfig = Get-AppConfig
& $script:loadSettingsToUI & $script:loadSettingsToUI
& $updateSqlAuthUi
Update-ModeBadgeUI Update-ModeBadgeUI
[System.Windows.MessageBox]::Show("config.json 설정을 다시 불러왔습니다.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) [System.Windows.MessageBox]::Show("config.json 설정을 다시 불러왔습니다.", "알림", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
}) })
+10 -5
View File
@@ -123,19 +123,24 @@
<TextBox x:Name="TxtSqlServer" Text="localhost" Style="{StaticResource ModernTextBoxStyle}" Margin="0,0,0,12" /> <TextBox x:Name="TxtSqlServer" Text="localhost" Style="{StaticResource ModernTextBoxStyle}" Margin="0,0,0,12" />
<TextBlock Text="인증 방식:" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" /> <TextBlock Text="인증 방식:" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" />
<ComboBox x:Name="CmbSqlAuthType" SelectedIndex="0" Height="32" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,12"> <ComboBox x:Name="CmbSqlAuthType" SelectedIndex="0" Height="32" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,6">
<ComboBoxItem Content="Windows 통합 인증 (Integrated Security)" /> <ComboBoxItem Content="Windows 통합 인증 (현재 Windows 세션 토큰)" />
<ComboBoxItem Content="SQL Server 인증 (User ID / Password)" /> <ComboBoxItem Content="AD 도메인 계정 위장 Windows 인증 (LogonUser /netonly)" />
<ComboBoxItem Content="SQL Server 자체 계정 인증 (User ID / Password)" />
</ComboBox> </ComboBox>
<TextBlock x:Name="TxtSqlAuthHint" Text="현재 윈도우에 로그인된 사용자 자격증명으로 SQL Server에 접속합니다." FontSize="11" Foreground="#60A5FA" Margin="0,0,0,12" TextWrapping="Wrap" />
<TextBlock Text="백업 파일 기본 저장 디렉토리:" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" /> <TextBlock Text="백업 파일 기본 저장 디렉토리:" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" />
<TextBox x:Name="TxtSqlBackupDir" Text="C:\Backups" Style="{StaticResource ModernTextBoxStyle}" /> <TextBox x:Name="TxtSqlBackupDir" Text="C:\Backups" Style="{StaticResource ModernTextBoxStyle}" />
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Margin="12,0,0,0"> <StackPanel Grid.Column="1" Margin="12,0,0,0">
<TextBlock Text="SQL 계정 (SQL 인증 시):" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" /> <TextBlock x:Name="LblSqlDomain" Text="AD 도메인 (AD 위장 인증 시):" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" />
<TextBox x:Name="TxtSqlDomain" Text="corp" Style="{StaticResource ModernTextBoxStyle}" Margin="0,0,0,12" />
<TextBlock x:Name="LblSqlUser" Text="로그인 계정 (AD 사용자명 또는 SQL 계정):" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" />
<TextBox x:Name="TxtSqlUser" Text="sa" Style="{StaticResource ModernTextBoxStyle}" Margin="0,0,0,12" /> <TextBox x:Name="TxtSqlUser" Text="sa" Style="{StaticResource ModernTextBoxStyle}" Margin="0,0,0,12" />
<TextBlock Text="SQL 암호 (SQL 인증 시):" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" /> <TextBlock x:Name="LblSqlPassword" Text="비밀번호:" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,0,0,4" FontSize="12" />
<PasswordBox x:Name="TxtSqlPassword" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" BorderBrush="{StaticResource CardBorderBrush}" Height="34" Margin="0,0,0,12" /> <PasswordBox x:Name="TxtSqlPassword" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" BorderBrush="{StaticResource CardBorderBrush}" Height="34" Margin="0,0,0,12" />
<Button x:Name="BtnTestSql" Content="SQL Server 연결 테스트" Style="{StaticResource SecondaryButtonStyle}" HorizontalAlignment="Right" Padding="14,6" /> <Button x:Name="BtnTestSql" Content="SQL Server 연결 테스트" Style="{StaticResource SecondaryButtonStyle}" HorizontalAlignment="Right" Padding="14,6" />
+4 -3
View File
@@ -23,7 +23,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="220" /> <ColumnDefinition Width="220" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="160" /> <ColumnDefinition Width="210" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -36,8 +36,9 @@
<TextBlock Grid.Column="2" Text="인증 방식:" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center" Margin="0,0,10,0" /> <TextBlock Grid.Column="2" Text="인증 방식:" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center" Margin="0,0,10,0" />
<ComboBox Grid.Column="3" x:Name="CmbSqlAuth" SelectedIndex="0" Height="32" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,16,0"> <ComboBox Grid.Column="3" x:Name="CmbSqlAuth" SelectedIndex="0" Height="32" Background="#0F172A" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,16,0">
<ComboBoxItem Content="Windows 통합 인증" /> <ComboBoxItem Content="Windows 통합 인증 (현재 세션)" />
<ComboBoxItem Content="SQL Server 인증" /> <ComboBoxItem Content="AD 도메인 계정 위장 Windows 인증" />
<ComboBoxItem Content="SQL Server 자체 계정 인증" />
</ComboBox> </ComboBox>
<StackPanel Grid.Column="4" Orientation="Horizontal"> <StackPanel Grid.Column="4" Orientation="Horizontal">
+1
View File
@@ -26,6 +26,7 @@
"UseLive": false, "UseLive": false,
"Server": "localhost", "Server": "localhost",
"AuthType": "Integrated", "AuthType": "Integrated",
"Domain": "corp",
"Username": "sa", "Username": "sa",
"Password": "", "Password": "",
"DefaultDatabase": "master", "DefaultDatabase": "master",