CSEncryptで$が入力できなかったり

ふとWindows Azure SDK 1.5に付属するCSEncryptコマンドを使って暗号化されたパスワードな文字列を生成して、RDPのユーザーパスワードに設定して配置後、接続しようとするとどうも認証ではじかれる。

管理ポータルから再設定したらちゃんと入れるし、Visual Studioを使用してCSCFGファイル作れば問題なし=CSEncryptが何かおかしい。という状況に陥りました。

打ち間違いもなく、何ならコピペでやってるから絶対同じなのにおかしいなぁ~と思ってよくよく、ゆっくりと入力してみると…

あああ! $ が入力されなイイイイイイ!!

タイプしても、ペーストしてもスルー!!!/(^o^)\

というわけで原因はCSEncryptで(というかPowerShellで?)$が入力できない、ということでした。ちゃんちゃん。

 

いやいやいや、入力できないと困ります。

ということでどうするかといいますと。リダイレクトすればいいわけですねー。

こんなファイルを用意しまして、

という感じで標準入力にリダイレクトしてあげるといいようです。

ちなみにちゃんと$込で暗号化されてるのか不安ですよね。ちょっと復号してみてみましょう。

[Reflection.Assembly]::LoadWithPartialName("System.Security")
$source = "<Base64な暗号化された文字列>"
$thumbprint = "<証明書の拇印>"
$store = new-object System.Security.Cryptography.X509Certificates.X509Store My,CurrentUser
$store.Open("ReadOnly")
$cert = $store.Certificates.Find(0,$thumbprint,0)
$env = new-object Security.Cryptography.Pkcs.EnvelopedCms
$env.Decode([Convert]::FromBase64String($source))
$env.Decrypt($cert)
[Text.Encoding]::UTF8.GetString($env.ContentInfo.Content)

こんな感じのコマンドをPowerShell上で実行してあげると復号して元文字列を取り出せます。
結果は同じ、$もOKですね!

※ちなみに暗号化には公開鍵を、復号には秘密鍵を利用しているので暗号化する際に使用した秘密鍵付証明書が個人用証明書ストアに保存されてないとダメです。逆に言えば秘密鍵付証明書(とパスワード)があれば簡単に復号できちゃいますということで。

今日はこの挙動に悩まされた一日でした…とほほ。

Windows AzureのCTPやらの利用許諾はどうなってるのか

Windows Azureで提供されるCTPやBeta機能のTerms of Use(利用規約)はどうなってるのか?よくわからなかったので中の人に聞いてみました。(2011年10月28日時点の内容です)

image

というわけで、 Windows Azure Platform の法的情報 に準ずるということで、つまるところSLAはないですが利用にあたって商用利用やら本番環境での利用は問題なさそうですね。

image

ベータやらCTPはSLAないよってのは このへん に書いてますと。
ドッグフードもぐもぐしてる方ならよくご存知かと思いますが、当然ながら互換性はないし機能がそのまま使えるかもわからない、サポートやバグ修正の基準もGA(General Availability)レベルじゃないので期待しないでねとのこと。

自己責任で面倒見れない場合はお勧めしませんということです。

image

ただし、今時点でベータ扱いのVM Roleだけは例外。SLAもあります。

あと分かりにくいですけど、SQL Azure Reportingみたいに、CTP利用時に個別にライセンスが表示されるやつはそちらに準拠すると思うので注意ください。

それから、Windows Azure Connect Endpoint Software(クライアント側のアプリ)みたいなのはそれぞれEULA持ってるみたいなので注意。

最後になりましたが、お答えくださいました中の人、さとうなおきさんありがとうございました。

Ruby on Rails を Windows Azure で使用する

Ruby on Rails を Windows Azrue上で使用するためのホワイトペーパーが公開されています。

作者は日本Rubyの会のartonさんです。この間のThe Microsoft Conference 2011 (MSC)のセッションでもartonさんがスピーカーでこのあたりのお話をしていましたので、ご存知の方もいらっしゃるかも。

目次をみてもわかる通り、単にNougakuDoやNougakuDo Companionだけの話に留まらず、技術的にかなり踏み込んでWindows+RoRを解説してるので勉強になるかと。

http.sysで動くEnnou、ステキですね!

また荒井省三さんが裏話というか、背景含めていろいろ書かれるようなのでこちらも期待です。

Twitterでも見かけましたが、ぜひ英訳して欲しいですね!

In-place UpdateがUpdateされました

唐突なアナウンスですがWindows AzureのIn-place Update機能が更新されたようです。まぁWindows Azure側の話なので、こちらで何か修正等があるわけではありませんし、既に展開されてるのでみんな恩恵にあずかれます。

元ネタ:Announcing Improved In-place Updates 、 Overview of Updating a Windows Azure Service

変更内容ですが、簡単にいうと以下のケースにおいて今までIn-place Updateが出来なかったのですが、それができるようになりました
( ゚Д゚ノノ"☆パチパチパチパチ

  • 仮想マシンのサイズ変更(スケールアップ/スケールダウン) ※たとえばSサイズからLサイズ、SサイズからXSとか
  • ローカルストレージの増加
  • デプロイされてるロールの追加・削除
  • エンドポイントの数や種類の変更

今まで再デプロイとかVIPスワップで対応しないと行けなかったのがIn-place Updateでもいけるのは嬉しいですね!

※ちなみにIn-place UpgradeなのかIn-place Updateなのかははっきりしてほしいですがw(管理ポータルとか見る限りUpgradeだとは思いますけど元ネタのBlogに合わせてます。どっちでもいいんじゃないですかね)

変更できる項目が元ネタのところにまとまってるのでそのまま持ってこようと思います。

変更内容 In-place Update VIP Swap 再デプロイ
Guest OSのバージョン OK OK OK
.NET Trustレベル OK OK OK
仮想マシンのサイズ OK
注意:仮想マシンのサイズを変更するとローカルデータが破棄されます。
Management APIを使用してこの変更を行う場合は強制フラグが必要です
メモ:Azure SDK 1.5以上が必要
OK OK
ローカルストレージの設定 OK(ただし増加のみ)
メモ:Azure SDK 1.5以上が必要
OK OK
ロールの追加または削除 OK OK OK
特定ロールのインスタンス数 OK OK OK
サービスエンドポイントの数や種類の変更 OK
メモ:Azure SDK 1.5以上が必要
エンドポイントが更新されるので、一時的に可用性が損なわれる(接続できなくなる瞬間がある)
NG OK
ConfigurationSettingsの名前と値 OK OK OK
ConfigurationSettingsの値 OK OK OK
証明書の追加 OK OK OK
既存の証明書の変更 OK OK OK
新しいコードのデプロイ OK OK OK

再デプロイは基本的にまっさらな状態でデプロイになるので、まぁこの辺の制約はないわけで。
VIP SwapもIPアドレス付け替えと考えると、サービスエンドポイントが変わらなければOK(つまりそこだけNG)なので納得ですね。

In-place Updateで仮想マシン(インスタンス)のサイズやロール数を変更する場合は、アップグレードダイアログの「VMサイズまたはロール数の更新を許可する」にチェックを付けましょう。付けずに更新しようとするとエラーになります。(Management APIでやる場合も同様に強制フラグをOnにして実行しましょう)

さて他にも幾つか追加された機能がありまして。

In-place Updateをキャンセル/ロールバックなど細かくコントロールすることができます。
アップグレードモードが自動の場合はこれらはよしなにしてくれますが、マニュアルモードの場合、管理ポータルやManagement APIを使って細かく制御できます。

このへん詳しく載っていますので参照ください↓

 

まとめ

ということで、アップグレード手法をまとめるとこんな感じですかね(コピペですけど)

更新方法 説明 メリット デメリット
In-place 新しいパッケージを今稼働してるインスタンスやサービス上に適用する方法 1つのデプロイで済む
各ロールで2インスタンス以上あれば可用性を維持したまま更新できる
更新してる間、更新対象のロールはダウンするのでサービス提供能力が落ちます。
全インスタンスが更新されるまでの間、新旧2つのバージョンのサービスコードが稼働するので動作に差がでてしまいます
VIP Swap 新しいパッケージを別の領域(ステージング)に展開し、IPアドレスを付け替える方法(サービスをスワップする) サービスダウンタイムや提供機能のロスが無い 少なくとも2つデプロイ(プロビジョニングとステージング)が実行されてる必要がある(スワップするのに必要)
再デプロイ 稼働してるサービスを削除して新しいパッケージをデプロイする方法 1つのデプロイで済む サービス削除するので、提供していたサービスはダウンする。デプロイが変わるとIPアドレスも変わるので、DNSの伝達に時間がかかるケースもある

CSEncrypt コマンドでパスワードの暗号化

Windows Azure SDK 1.5でリモートデスクトップ接続時に使用するパスワードやらを暗号化するのに使える、CSEncryptコマンドラインツールが追加されました。

Azure SDK 1.5が出る以前はどうだったかというと…

にある通り、パスワード文字列をバイト配列にして証明書読み込んで読み込んだ証明書を使って暗号化して、できあがったものをBase64でエンコードするという超絶めんどい作業をする必要がありました。

[Reflection.Assembly]::LoadWithPartialName("System.Security")
$pass = [Text.Encoding]::UTF8.GetBytes("<Password>")
$content = new-object Security.Cryptography.Pkcs.ContentInfo ?argumentList (,$pass)
$env = new-object Security.Cryptography.Pkcs.EnvelopedCms $content
$env.Encrypt((new-object System.Security.Cryptography.Pkcs.CmsRecipient(gi cert:\CurrentUser\My\<Thumbprint>)))
[Convert]::ToBase64String($env.Encode())

こんな感じですね。

さてこれがAzure SDK 1.5に付属するCSEncryptコマンドだとどうなるかというと。

csencrypt Get-PasswordEncryptionCertificate
コマンドを実行して証明書一覧を表示して拇印をコピーして…

csencrypt Encrypt-Password -CopyToClipboard -Thumbprint "<さっきの拇印>"
コマンドを実行してパスワードを入力するだけ!

あとは出力された文字列を(改行除いて)コピーすればOK!ほら簡単でしょ?(

ちなみに -CopyToClipboard オプションを付けておけば勝手に結果をクリップボードにコピーしてくれます。
※Outputオプションでファイルに出力することもできます。(やっぱり改行はされてますけど)
そのほかにもNew-PasswordEncryptionCertificateオプションで証明書の生成もできるのでお手軽ですね。

ちなみにパスワードを書いたテキストファイルを標準入力として読み込ませれば手打ちしなくてもイイです。

オプションや内容についてはこちらを参照。

というわけでコマンドでやるにも便利な感じになりましたとさ。

Windows Developer Preview をVHDブートさせる

Windows Developer PreviewをVHDブートさせる方法はいろいろありますが、基となるVHDをWindows Server 2008 R2のHyper-V 2.0とかで作ってしまうとそのままじゃブートしません。

※ブートメニューでWindows Developer Previewを選ぶと 0xC0000248 な署名が検証できない云々で⊂ミ⊃^ω^)アウアウ

解決方法は一応ありますが自己責任で、ということで載せておきます。
簡単にいうと、ブートローダ(Winload)がWindows 7のままだとエラーになるわけで、Windows Developer Previewのブートローダ(Winload)になるように修復してあげればいいだけです。

  1. Hyper-V 2.0上でWindows Developer Previewをインストール(※ディスクサイズはVHDファイルを置くWindows 7等の空きディスク容量より少なくなるようにしましょう)
  2. VHDファイルをコピーして Windows OSをVHDブートさせる2つの方法 (@IT) あたりを参考にBCDを弄ります(このままだと上記のエラーになる)
  3. Visual BCD Editor for Windows 7/Vista をダウンロードしてインストールします
  4. ディスクの管理でコピーしたVHDをマウントします
  5. Visual BCD Editorを立ち上げ、「Repair」-「Repair BCD」メニューを選択します
    image
  6. Bcd DriveはSystemのままでWin7Folderを先ほどマウントしたVHDのWindowsフォルダ(Windows Developer PreviewのWindowsフォルダ)を選択して修復します。

以上でブートローダがWindows Developer PreviewのものにかわってちゃんとVHDブートできるようになると思います。

WP_000003

まーWindows Developer PreviewのISOをブートできたらこんなことせずに修復できると思うのですが手元にそういったものはなかったのでこんな方法になりました。

Windows Azure AppFabric ACS でカスタムのサインイン画面を作る

Windows Azure AppFabric ACS使ってフェデレーション認証するのはいいけど、味気ないサインイン画面で辟易してる方も多いんじゃないでしょうか。

↓味気ないサインイン画面

というわけで今日はこいつのカスタマイズをしたいと思います。

ゴールとして今回はASP.NET MVC3のログイン画面内で上記のサインイン画面(というかセレクタ画面)を埋め込んでアプリケーションでちゃんと(?)制御できるようにしたいと思います。

続きを読む →

ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (4)

性懲りもなく4回目。

@kazuk さんが「無駄な処理をしない事と、重い処理をバックグラウンドにする事」ということで、
削除処理を毎回するんじゃなくてある程度間隔あけて+別スレッドでやるべきだろJKとおっしゃられました。
というわけで @takepara さんのHighPerformanceSessionStateProviderに追加してみました。

↓結果

少しパフォーマンスよくなりました。ただMAXも2秒とかなので、平均して落ち着いた感。エラーもなくなってますね。
クライアントは↓

多少ばらつきありますがネットワーク的に遠いところ(日本)なのでまぁこんな感じですかね。ゴミセッションの削除がはしったと思われるタイミングでもそんなに重くなってないのが特徴です。

とまぁこんな感じでかなりまともに使えるセッションプロバイダになった気がします。
ロールやメンバシップの部分はASP.NET Univarsal Providersのが良しなに利用できると思うので組み合わせるといいんではないでしょうか。

最終的なソースはこちら↓

// create: 2011.08.23
// author: @takepara (http://tinyurl.com/3kebwlu) , @kazuk (http://tinyurl.com/3r5vjkq)

using System;
using System.Configuration;
using System.Data.Common;
using System.Reflection;
using System.Web;
using System.Web.Providers;
using System.Web.Providers.Entities;
using System.Threading;

namespace UniversalProviders
{
	public class HighPerformanceSessionStateProvider : DefaultSessionStateProvider
	{
		private string _connectionStringName;
		private bool _initialized = false;
		private DateTime _lastSessionExpireInvoked = DateTime.MinValue;
		private object _lockSessionExpire = new object();
		private bool _working = false; 

		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			_connectionStringName = config["connectionStringName"];

			base.Initialize(name, config);
		}

		private void ExecuteSql(ConnectionStringSettings connectionStringSettings, Action<DbProviderFactory, DbCommand> functor)
		{
			var providerName = connectionStringSettings.ProviderName;
			var factory = DbProviderFactories.GetFactory(providerName);
			using (var connection = factory.CreateConnection())
			{
				connection.ConnectionString = connectionStringSettings.ConnectionString;
				connection.Open();

				var command = connection.CreateCommand();

				functor(factory, command);
				command.ExecuteNonQuery();
				connection.Close();
			}
		}

		private void CreateSessionIndex(ConnectionStringSettings connectionStringSettings)
		{
			ExecuteSql(connectionStringSettings, (factory, command) =>
			{
				command.CommandText = @"IF not EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Sessions]') AND name = N'IX_Sessions')
begin
CREATE NONCLUSTERED INDEX [IX_Sessions] ON [dbo].[Sessions] 
(
	[Expires] ASC
)
end
";
			});
		}

		private void RemoveExpiredSessions( 
			ConnectionStringSettings connectionStringSettings) 
		{ 
			if( _working ) return; // やってる人が居るならやらんでいいでしょ 
			_working = true; 
			try { 
				ThreadPool.QueueUserWorkItem( (object state)=> 
				{  
					try 
					{ 
						ExecuteSql(connectionStringSettings, (factory, command) => 
						{  
							command.CommandText = "delete Sessions where Expires < @0"; 
							var parameter = factory.CreateParameter(); 
							parameter.ParameterName = "@0"; 
							parameter.Value = DateTime.UtcNow; 
							command.Parameters.Add(parameter); 
						}); 
					} 
					finally { _working = false; } 
				}); 
			} catch { _working = false; throw; } 
		}

		private MethodInfo GetCreateSessionEntities()
		{
			var modelHelper =
				Assembly.GetAssembly(typeof(Session)).GetType("System.Web.Providers.Entities.ModelHelper");
			return modelHelper.GetMethod("CreateSessionEntities", 
				BindingFlags.NonPublic | BindingFlags.Static);
		}

		public override void InitializeRequest(HttpContext context)
		{
			var connectionStringSettings = ConfigurationManager.ConnectionStrings[_connectionStringName];
			if (!_initialized)
			{
				var initializer = GetCreateSessionEntities();
				initializer.Invoke(null, new object[] { connectionStringSettings });
				CreateSessionIndex(connectionStringSettings);
				_initialized = true;
			}

			if( _lastSessionExpireInvoked < DateTime.UtcNow - TimeSpan.FromSeconds(30) ) 
			{ 
				if( Monitor.TryEnter( _lockSessionExpire ) ) { 
					// ロックに入れたスレッドでだけ処理する、入れなかったスレッドでは処理しない 
					DateTime old = _lastSessionExpireInvoked;
					_lastSessionExpireInvoked = DateTime.UtcNow; 
					try 
					{ 
						try 
						{ 
							RemoveExpiredSessions(connectionStringSettings); 
						} 
						catch 
						{ 
							// 失敗した時はタイマ値を戻すことで別のスレッドにやってもらう 
							_lastSessionExpireInvoked = old; 
							throw; 
						} 
					} 
					finally 
					{ 
						// 絶対にMonitorを出る 
						Monitor.Exit( _lockSessionExpire ); 
					} 
			   } 
			}
		}
	}
}

Windows Azure AppFabric Caching を使ってみる

ASP.NET Universal ProvidersおよびカスタムのHighPerformanceSessionStateProviderを見てきたわけですが、本命(?)のWindows Azure AppFabric Cachingだと、どんなもんなんでしょうか。

構成等は今までと同じで、Windows Azure AppFabric Cachingも同じエリア(東アジア)に作って試してみました。

※Azure AppFabric Cachingの使い方等は適当に検索で(
※アセンブリ追加してWeb.configにもろもろ追加+セッションプロバイダの変更をしただけです。(ちなみに今回はSSLなエンドポイントを使用)

↓結果

えらい早い ( ゚д゚) 12万セッションだだだっと作るのに3分ぐらい。

作りながら同じセッションで継続アクセスした場合はこんな感じ

削除処理とかはAzure AppFabric Caching側が面倒みるのでいつかわかりませんが、きちんと破棄されたりしているようです。

とまぁこんな感じでした。

ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (3)

第1回、第2回に続いて第3回です。

前回はそのまんまASP.NET MVC3ロールに含まれてるASP.NET Univarsal Providersを使って軽く負荷テストしたりしてみました。
結果は想定通りだったわけですが、なんと、たけぱらさんにこんなコメントをつけて頂き、しかもHighPerformanceSessionStateProvider という実装までしてくださいました。

こりゃー再度テストしてみないといけないな!ということで。

条件は前回と同じくAzure上にSサイズで5インスタンスほど、たけぱらさん作HighPerformanceSessionStateProviderを使ったMVC3ロールを展開。クライアントも同様にSサイズなWorkerロールを20インスタンスほど作って並行で6000セッションづつ作りまくる感じにしました。

HighPerformanceSessionStateProvider はたけぱらさんのBlogからコピペ。ただSQL Azureで利用する際に1点だけ修正が必要なので注意です。

CreateSessionIndex メソッド内

		private void CreateSessionIndex(ConnectionStringSettings connectionStringSettings)
		{
			ExecuteSql(connectionStringSettings, (factory, command) =>
			{
				command.CommandText = @"IF not EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Sessions]') AND name = N'IX_Sessions')
begin
CREATE NONCLUSTERED INDEX [IX_Sessions] ON [dbo].[Sessions] 
(
	[Expires] ASC
)
end
";
			});
		}

SQL Azure上で利用できないT-SQLの構文があるので、上記10行目に書かれてた「ON [PRIMARY]」を削除します。

Web.configも書き換えたら準備完了です。

で、実際に見てみると…だいぶ早い感じ。セッションタイムアウトは10分にしてたのですが、6000セッションx20作成中に生存してるセッションが2万弱とかだったのに対し4万弱ほど作れてたりします。

ログみると

タイムアウトになったりなNGが5000ちょいほどありますが…概ね良好な感じですね。
※タイムアウトなるのはコードの問題な気がしないでもない…例外まで補足してなかったので詳細不明(ひょっとしたらSQL Azure側?うーん)

で、前回まともに動作しなかった同じセッションIDで更新したり、ごみセッションを削除する処理ですが

こんな感じでゴリゴリセッション作成してる間でも500msとかでアクセスできてますね。(=セッションテーブルの更新もOK)

また最後の15分経過後のアクセスも5秒かかっていますが問題なくできてます。
ちなみにこの時のごみセッション数は32000弱。

ちょっとしたひと手間でまともに使える感じがします!

HighPerformanceSessionStateProvider イイネ!