SE の雑記

SQL Server の情報をメインに Microsoft 製品の勉強内容を日々投稿

ワークグループ環境でミラーアカウントを使用しない共有フォルダーへのバックアップの取得

leave a comment

ワークグループ環境の SQL Server で、共有フォルダーに対してバックアップを取得する場合、SQL Server のサービスアカウントと同様のユーザー名/パスワードのユーザーをを共有フォルダーをホストする環境に作成し、ミラーアカウントでアクセスさせる方法があるかと思います。
共有フォルダーをホストする環境に個別のユーザーを作成しづらい場合などには、資格情報マネジャーを使用する方法をとることができるかと思いますのでこの方法を。
本投稿のターゲットは Windows Server 2012 以降としています。
SQL Server のサービスを起動するユーザーですが、「NT SERVICE\MSSQLSERVER」ではうまく設定することができなかったため、ローカルユーザーかビルトインアカウントで動作させる必要がありました。
今回は、「NT AUTHORITY\NETWORK SERVICE」(NetworkService) を使用するため、以下のスクリプトで SQL Server のサービスアカウントを変更します。

[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
$mc = new-object Microsoft.SQLServer.Management.SMO.WMI.ManagedComputer localhost
$service = $mc.Services["MSSQLSERVER"]
$service.SetServiceAccount("NT AUTHORITY\NetworkService", "")
$service.Alter()

 
NetworkService の資格情報マネジャーを操作するための準備として、以下の 2 つの作業を実施する必要があるかと。

  1. NetworkService で資格情報マネージャーを操作するためのプロセスを起動する
  2. 資格情報マネージャーに資格情報を登録

NetworkService で資格情報マネージャーを操作するための方法としては、

  • タスクスケジューラー
  • PsExec

等を実行する方法があるかと思います。
今回は、PsExec を使用しています。
次に資格情報マネージャーに資格情報を登録ですが、

  1. CredMan.ps1
  2. CredentialManager
    Package Management が使用できない環境で使いたい場合は,使える環境で「Save-Package CredentialManager -Path c:\temp\cred」のような形で一度ダウンロードしてから、コピーすれば利用できるかと。
  3. CmdKey

を使用する方法があるかと思いますので、これらの方法を組み合わせます。
CredMan も CredentialManager も 基本的な処理は CredentialManager.cs のような内容なのでしょうね。
最初に以下のような処理を実行するためのスクリプトを準備しておきます。

# CredMan.ps1 を使用する場合
C:\temp\CredMan.ps1 -AddCred -Target "<取得先>" -User "<ユーザー名>" -Pass "<パスワード>" -CredPersist ENTERPRISE -CredType DOMAIN_PASSWORD
C:\temp\CredMan.ps1 -GetCred -Target "<取得先>"-CredPersist ENTERPRISE -CredType DOMAIN_PASSWORD
 # CredentialManager を使用する場合
Import-Module C:\temp\CredentialManager\2.0\CredentialManager.dll
New-StoredCredential -Target "<取得先>" -UserName "<ユーザー名>" -Password "<パスワード>" -Persist Enterprise -Type DomainPassword
Get-StoredCredential -Type DomainPassword -AsCredentialObject

 
次に PsExec を使用して、このスクリプトを NetworkService で実行します。

c:\temp\PsExec.exe -accepteula -u "NT AUTHORITY\NETWORK SERVICE" cmd.exe /C "powershell.exe -file C:\temp\New-Credential.ps1"

 
これで、NetworkService に資格情報が登録され、共有フォルダーに対しての接続が可能となりバックアップの取得ができるようになります。
この方法ですが、共有フォルダーを使用したクォーラムを使用する場合や、Azure Files にバックアップを取得する場合などにも利用することができるかと。
# 共有フォルダーを使用したクォーラムで使用する場合は、LocalSystem で実行する必要があります。
本来は、タスクスケジューラーや PsExec を使わずにユーザーの偽装を使用して実施できればよかったのですが、偽装をした後の資格情報の登録がうまくできずにとん挫しています…。
# LogonUser → LoadUserProfile で Administrator に偽装した場合は想定の動作となっていたのですが、Administrators グループのユーザーや、NetworkService で実現する方法がいまいちわからず。
ユーザー情報の偽装については、advapi32.dll の LogonUser を使用する方法が一般的かと。
公開されているスクリプトとしては、以下のようなものがあるかと思います。

ユーザー偽装については、以下の情報が参考になります。

この辺の偽装は、AlwaysOn のクイックスタートテンプレートでも使用されており、sqlvm-alwayson-cluster からも処理を切り出すことが可能です。
該当箇所を切り出したものが以下になります。

function Get-ImpersonateLib
{
    if ($script:ImpersonateLib)
    {
        return $script:ImpersonateLib
    }
    $sig = @'
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
    [DllImport("kernel32.dll")]
    public static extern Boolean CloseHandle(IntPtr hObject);
'@
    $script:ImpersonateLib = Add-Type -PassThru -Namespace 'Lib.Impersonation' -Name ImpersonationLib -MemberDefinition $sig
    return $script:ImpersonateLib
}
function Get-UserProfileLib
{
$sig = @"
    [DllImport("userenv.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
    [DllImport("userenv.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern bool UnLoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
    [StructLayout(LayoutKind.Sequential)]
    public struct PROFILEINFO {
        public int dwSize;
        public int dwFlags;
        public String lpUserName;
        public String lpProfilePath;
        public String lpDefaultPath;
        public String lpServerName;
        public String lpPolicyPath;
        public IntPtr hProfile;
    }
"@
    # $script:UserProfileLib = Add-Type -PassThru -Name "UserProfileLib" -Namespace "Lib.UserProfile" -UsingNamespace "System.Security.Principal" -MemberDefinition $sig
    $script:UserProfileLib = Add-Type -PassThru -Name "UserProfileLib" -Namespace "Lib.UserProfile" -MemberDefinition $sig
    return $script:UserProfileLib
    # Add-Type -TypeDefinition $sig -Language CSharp
}
#function ImpersonateAs([PSCredential] $cred)
function ImpersonateAs($UserName, $Domain, $Password)
{
    [IntPtr] $userToken = [Security.Principal.WindowsIdentity]::GetCurrent().Token
    $userToken
    $ImpersonateLib = Get-ImpersonateLib
    $LOGON32_LOGON_INTERACTIVE = 2
    $LOGON32_LOGON_NETWORK = 3
    $LOGON32_LOGON_BATCH = 4
    $LOGON32_LOGON_SERVICE = 5
    $LOGON32_LOGON_UNLOCK = 7
    $LOGON32_LOGON_NETWORK_CLEARTEXT = 8
    $LOGON32_LOGON_NEW_CREDENTIALS = 9
    $LOGON32_PROVIDER_DEFAULT = 0
    $LOGON32_PROVIDER_WINNT35 = 1
    $LOGON32_PROVIDER_WINNT40 = 2
    $LOGON32_PROVIDER_WINNT50 = 3
    # インタラクティブに、その資格情報でアクセスさせる (ユーザー名 /パスワードが違うとエラーとなる)
    # $bLogin = $ImpersonateLib::LogonUser($UserName, $Domain, $Password,  $LOGON32_LOGON_INTERACTIVE, $LOGON32_PROVIDER_DEFAULT, [ref]$userToken)
    # 新しい資格情報の作成のため、そのユーザーが存在していなくてもエラーとならない (トークンは生成できる)
    # ワークグループ環境からドメインの資格情報を使用する場合などに使用する
    $bLogin = $ImpersonateLib::LogonUser("NetworkService", "NT AUTHORITY", "", $LOGON32_LOGON_NEW_CREDENTIALS, $LOGON32_PROVIDER_DEFAULT, [ref]$userToken)
    if ($bLogin)
    {
        $Identity = New-Object Security.Principal.WindowsIdentity $userToken
        $context = $Identity.Impersonate()
    }
    else
    {
        # throw "Can't log on as user '$($cred.GetNetworkCredential().UserName)'."
        throw "Can't log on as user $UserName."
    }
    $context, $userToken
}
function CloseUserToken([IntPtr] $token)
{
    $ImpersonateLib = Get-ImpersonateLib
    $bLogin = $ImpersonateLib::CloseHandle($token)
    if (!$bLogin)
    {
        throw "Can't close token."
    }
}
$UserName = "Administrator"
$Domain = $ENV:COMPUTERNAME
$Password = "M@sterEr0s"
($oldToken, $context, $newToken)  = ImpersonateAs -UserName $UserName -Domain $Domain -Password $Password
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$context.Undo()
$context.Dispose()
CloseUserToken($newToken)

 
ユーザーの偽装が終わった後に、資格情報の登録をするのですが、ユーザープロファイルをロードしていない状態では、資格情報の登録である CredWrite を実行した際に 1312 のエラー (ERROR_NO_SUCH_LOGON_SESSION)? が発生してしまい、資格情報の登録を実施することができませんでした。
ユーザープロファイルのロードに関しては、偽装後のトークンで、userenv.dll の LoadUserProfile を実行することになるかと思いますが、偽装後に Administrator 以外のアカウントで、LoadUserProfile の実行をしてもユーザープロファイルのロードがうまくできなくて今のところ止まっています。
# 偽装後のユーザーの権限のような気はするのですが。
LoadUserProfile については、以下が参考になります。

Share

Written by Masayuki.Ozawa

9月 12th, 2016 at 8:43 pm

Posted in SQL Server

Tagged with

Leave a Reply