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も公開されています。

https://github.com/Microsoft/PowerShell-DSC-for-Linux

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

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

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

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

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

https://tech.guitarrapc.com/entry/2014/03/13/062713

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

PowerShell Gallery からの導入

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

Install-Module -Name GraniResource -Scope AllUsers

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

https://www.powershellgallery.com/packages/GraniResource

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

https://github.com/guitarrapc/GraniResource

利用

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

https://gist.github.com/guitarrapc/93713a4a2577abd6b9312677b641d307

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

https://gist.github.com/guitarrapc/453d573ca2bf67e8d2a13c24cc9cf166

Grani_CredentialManager について

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

本体とヘルパーの分離

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

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

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

https://github.com/PowerShell/xTimeZone

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

Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath "Grani_CredentialManagerHelper.psm1") -Verbose:$false -Force

PowerShell のまずいポイント対策

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

https://tech.guitarrapc.com/entry/2015/12/25/233000

https://tech.guitarrapc.com/entry/2013/12/03/014013

しかし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_CredentialManager

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

$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

https://github.com/PowerShell/DscResource.Tests/issues/68

https://github.com/PowerShell/DscResource.Tests/issues/70

まとめ

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

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

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

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

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

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