tech.guitarrapc.cóm

Technical updates

Windows 資格情報マネージャーをPowerShell DSC で構成管理するリソースを公開しました

PowerShell DSC は PowerShell が目指してきた姿の1つの目標であり、強力な技術基盤です。

PowerShell は DSC を提供にするにあたり、OMI の実装を WMI から クロスプラットフォームな CIMに移してきました。実際 Linux で PowerShell DSC を利用する PowerShell-DSC-for-Linux も公開されています。

github.com

また WMF5.1 からは、PowerShell Core、つまり .NET Core 実装のクロスプラットフォーム PowerShell も提供されます。シェルとして使い心地に課題は残るものの、着実にPowerShell でやると楽な部分、という意味での存在価値は間違いなく存在します。

さて、そんな PowerShell ですが WMF5 からは、DSC の実行ユーザーが BuiltIn/SYSTEM だけでなく任意のユーザー指定が可能になりました。これによりユーザープロファイルに依存する処理も管理可能になりました。

ということで、先日 資格情報マネージャーのリソースを公開したのでご紹介です。*1

目次

資格情報マネージャーの操作

以前 資格情報マネージャーを PowerShell で操作する方法を示しました。

tech.guitarrapc.com

現在はもう少しコードを修正していますが、ようはこれを PowerShell DSC でやれるようにします。

PowerShell Gallery からの導入

WMF5 以降の環境ではワンライナーでDSCリソースを利用できます。

Install-Module -Name GraniResource -Scope AllUsers

幸いにも PowerShell Team が公開している以外のリソースとして上位に位置するダウンロード数のようで嬉しい限りです。

www.powershellgallery.com

実はリポジトリ名を DSCResources から GraniResource へこっそり変えていますが、ソースコードも変わらず公開しています。*2

github.com

利用

Grani_CredentialManager を利用することで、資格情報マネージャーを管理できます。デフォルトは、DSCの実行ユーザー、つまり SYSTEM アカウントが対象です。このアカウントができるということは、SYSTEM で動作するサービスの資格情報を管理できます。

gist.github.com

また、PsRunAsCredential を指定することで任意のアカウントの資格情報も操作できます。

gist.github.com

Grani_CredentialManager について

前回のリソース作成から、PowerShell チームへの今トリビュートを重ねる中で得た知見を使って少し書き方を変えました。

本体とヘルパーの分離

いわゆる *-TargetResource が PowerShell DSC を関数で記述したときの本体です。*3

今回のリソースは、ロジックを C# に閉じ込めていたこともあり本体の処理は非常にシンプルです。が、それでもヘルパーとなる補助関数は *helper.psm1 として分離しました。

ポイントは .psm1、つまりモジュールとしての分離です。本体の Grani_CredentialManager.psm1 で公開していないので内部モジュール扱いなのですが、Pester テストを含めた取り扱いの良さから .ps1 ではなく .psm1 にしています。この手法は、xTimeZone リソースでも行っておりPowerShell におけるプライベートな関数を閉じ込めるにあたりとてもいい手法だと思います。

github.com

呼び出しもシンプルですしね。

Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath "Grani_CredentialManagerHelper.psm1") -Verbose:$false -Force
PowerShell のまずいポイント対策

PowerShell には致命的なポイントがあります。それが、バイナリモジュールを利用する場合 DLL をロックしてしまうということです。つまり、C# で書いてビルドしたDLL を DSC などで利用すると、DSC実行中はファイルロックされます。これはデプロイの観点からみると非常に問題で、Windows 嫌いになります。その対策として、以前 Assembly.Load を利用したダイナミックモジュールによるロック回避も提示しました。

tech.guitarrapc.com

tech.guitarrapc.com

しかし PowerShell DSC に限らず、この手法は正直いやです。人道的とか言いながら逆説的な複雑性を増しています。

そこで今回をはじめとして、PowerShell の Add-Type Cmdlet を利用した C# コードのオンザフライコンパイルによるコード注入を好んで使っています。これならばファイルロックを回避できます。問題になりえるのは、同一AppDomain への 同一名前空間、クラス、シグネチャの多重インポートですが PowerShell DSC は毎回の実行で別AppDomain を起動するので問題ありません。

具体的にはこのC#コード を、ヘルパーとして差し込んでいます。

    $code = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath "CredentialManager.cs") -Raw
    $referenceAssemblies = @("System", "System.Linq", "System.ComponentModel", "System.Management.Automation", "System.Runtime.InteropServices")
    Add-Type -TypeDefinition $code -ReferencedAssemblies $referenceAssemblies -ErrorAction SilentlyContinue;

あとは、PowerShell から扱いやすいように薄くラッパを作って、DSC本体から呼び出すだけです。

PowerShell Team 公式リソースのテスト

PoweShell Team は、DSC リソースに関して、DscResources/CONTRIBUTING.mdにコントリビューションガイドラインを公開しています。

Grani_CredentialManager は、ガイドラインに沿うようにテストを組んであります。今後 DSC リソースを書いて公開する場合のサンプルにでもなれば幸いです。

https://github.com/guitarrapc/GraniResource/tree/master/Test/Grani_CredentialManagergithub.com

特にこの辺りは基礎となります。

$global:dscModuleName = 'GraniResource'

# Import
$modulePath = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)).Replace("Test","DSCResources")
$global:dscResourceName = (Split-Path -Path $modulePath -leaf)
$moduleFileName = $global:dscResourceName + ".psm1"
Import-Module -Name (Join-Path -Path $modulePath -ChildPath $moduleFileName) -Force

# Prerequisite for Initialize-TestEnvironment in Domain Environment
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process

# Initialize
[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
{
    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
}
else
{
    & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull')
}
Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $global:dscModuleName -DSCResourceName $global:dscResourceName -TestType Unit

# Begin Test
try
{
    InModuleScope $global:dscResourceName {
        # テストロジック
    }
}
finaly
{
    Restore-TestEnvironment -TestEnvironment $TestEnvironment
}

コントリビューションガイドの通り、PesterDscResource.Tests を使うのがいいでしょう。もう少し パイプラインベースでテストを組めると書きやすいんですが、まだまだフレームワークもなく事実上 Pester しか確立したテスト手法がないですね。

DscResource.Testsは AppVeyor でのテストを通すことを前提で組んでるのか、ローカルでは妙な挙動をするのでバグ報告もしておきました。とりあえず現状でもふつーに使えるはずです。*4

github.com

github.com

まとめ

これで資格情報マネージャーも PowerShell DSC でハンドルできました。そもそも 資格情報が必要になる処理は RunDeck のように外から管理したところですが、なかなか難しいです。

悩ましいものの、いったんは目指した課題解決はできたので試していただけると幸いです。

*1:資格情報マネージャーは当然 Windows 固有実装なんですけどね。

*2:git からの直接ダウンロードで利用しやするための変更です。ご理解いただければと思います。

*3:ちなみにSet/Get/Test がクラスで書いたときの本体です

*4:ゴミカス残したりするのでアレですが