Translate

2012年4月27日金曜日

CloudStackソースをEclipse上から眺める


CloudStackはオープンソースなので(Citrix製品もある)、
ソースコードも取得可能である。

自社でパブリックIaaSを提供する際に
顧客側に使ってもらうセルフサービスポータルが
必要なのだが、
CloudStackそのままでは機能が不足している。

例えば課金情報の参照などの機能は
当然用意されていない。

CloudStackのUIを拡張するか、
CloudStackポータルの外にWebアプリを作るか
しかない。

そこでポータルにリンクを貼るにせよ、
CloudStack上のソースを弄って
機能の一つにしてしまうにせよ、
ソースを読める環境をまず作らなくてはならない。

ソースコードは、

github CloudStack


からダウンロード可能である。

gitを使ってリポジトリをチェックアウトしても良いのだけど
proxy環境な私はcorkscrewやらいろいろ面倒なので
上記サイトのzipボタンを押して圧縮ファイル一括ダウンロードした。

圧縮ファイルを展開すると
いくつかディレクトリがならんでいる。
そこで
Eclipseで上記の展開ディレクトリをワークスペースとして開き、
ファイル>インポート>既存プロジェクトをワークスペースへ>次へ
を選択して、ルートディレクトリの選択の右横の参照ボタンを開いて
以下のディレクトリを1つ1つプロジェクトとして登録していく。

・agent
・agent-simulator
・api
・client
・console-proxy
・core
・deps
・ovm
・patches
・scripts
・server
・setup
・test
・tools
・ui
・usage
・utils
・vmware-base

するとほぼすべてのプロジェクトに
赤いビックリマークが並んでしまう。

setupプロジェクトは
setupプロジェクトを右クリックし
ビルドパス>ビルドパスの構成>ライブラリ
を開いてXML関連の外部Jarを追加する。

面倒な人はxercesのjarを全部選択して追加すれば良い。


次にconsole-proxyプロジェクトも右クリックし
ビルドパス>ビルドパスの構成>プロジェクト
から赤マークの付いているconsoleなどを
除去する。

どうも昔のバージョンではあったみたいだが
CloudStack3.0では存在しないので
削除する。

最後にdepsプロジェクトだけ赤バッテンがのこるが、
この中のソースを見ると、
すべての先頭のほうに



 * follows:
 *   Linking this library statically or dynamically with other modules is
 *   making a combined work based on this library. Thus, the terms and
 *   permission to link this library with independent modules to produce an
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


と書かれていた。


ちなみに

"Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA"を
Google Mapに入れると、
あのEmacsで有名なリチャード・ストールマン率いる
Free Software Foundationを思いっきり指していた。

どうもGPLがらみで1つのワークスペースにできないので
あえてこうしているみたいなので、
ソースの上の箇所をコメントアウトするのはやめておいた。

depsパッケージが不完全でもその他のプロジェクトは大丈夫なので
ほっておいてもよさそうだ。

それに..XenServer関連っぽいし。
Xen使ってないからいいや..



ソースをどこから読むかだけど、
Webアプリをエントリーポイントとして読むには
clientパッケージのWEB-INFにある
web.xmlだろう。

ここにある、各サーブレットになるだろう。

・com.cloud.servlet.CloudStartupServlet
・com.cloud.api.ApiServlet
・com.cloud.servlet.ConsoleProxyServlet
・com.cloud.servlet.RegisterCompleteServlet

これらすべてのServletは
serverプロジェクトに格納されている。


Eclipse上でManagement Serverの
Webアプリを使えるようにしたいけど
そこに持って行くには
agentパッケージとか多分使っていないから
そのあたりを分離してやらないといけないか..

だれかやってないかなあ..
..やってても自社内からださないかな..

というかCloudStack APIをコールする
別アプリとして構築しているのかな。
そのほうがソース読まないから楽だろうし..



2012年4月11日水曜日

OpenLDAPサーバ上のユーザ/パスワードでCloudStack管理サイトへログオンする

CloudStackをつかって商売するには
セルフサービスポータルとして
CloudStack管理サイトをインターネット経由で
利用して貰う必要があるが、
1社以上のユーザを受け入れるために
LDAPサーバでの認証が必要にある可能性が高い。

CloudStack3.0から(?)LDAP連携が有効になったとの
記述がAdnministraion Guideにのっていたので
ためしてみた。

LDAPサーバとして
Active Directorも使用できるらしいが
CloudStackを採用する場合
できるだけ安くケチケチプランのSLAベースになるはず
なので、
ここはOpenLDAPで試して見ることにした。


LDAPサーバ環境は
想定される構成でも別々になるはずなので
以下の図のとおり
Management Serverとは別のサーバにした。




図には出ていないが、
作業のためこのセグメントにWindows PCを
別途用意しておく。

LDAPの構成は
あまり良くわからないのでとりあえず
dc=harahara,dc=co,dc=jp

cn=admin
をつくりパスワードを"password"
さらに組織ユニットou=peopleをつくって
その下にuid=yamada(パスワードを"password")にした。





以下は前提として
・Management Serverは管理サーバがログイン可能な状態まで
・management Server上にはアカウント/ユーザyamadaは存在しない
・OpenLDAPサーバは固定IPでOSセットアップ完了まで
・proxyなしでインターネットに繋がる
という環境から
CloudStack管理サイトへOpenLDAPユーザ/パスワードでログインできるまで
の手順を記述した。


1. OpenLDAP導入
※OpenLDAPをインストールするサーバで作業
・sudo可能なユーザでログイン
・sudo su -
・cd
・export LANG=C
・aptitude update && aptitude -y slapd ldap-utils sysv-rc-conf
・sysv-rc-conf slapd on
・ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/cosine.ldif
・ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/nis.ldif
・ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/inetorgperson.ldif
・vi backend.ldif (新規作成、空行もかならず開ける)
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulepath: /usr/lib/ldap
olcModuleload: back_hdb

dn: olcDatabase=hdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {1}hdb
olcSuffix: dc=harahara,dc=co,dc=jp
olcDbDirectory: /var/lib/ldap
olcRootDN: cn=admin,dc=harahara,dc=co,dc=jp
olcRootPW: password
olcDbConfig: set_cachesize 0 2097152 0
olcDbConfig: set_lk_max_objects 1500
olcDbConfig: set_lk_max_locks 1500
olcDbConfig: set_lk_max_lockers 1500
olcDbIndex: objectClass eq
olcLastMod: TRUE
olcDbCheckpoint: 512 30
olcAccess: to attrs=userPassword by dn="cn=admin,dc=harahara,dc=co,dc=jp" write by anonymous auth by self write by * none
olcAccess: to attrs=shadowLastChange by self write by * read
olcAccess: to dn.base="" by * read
olcAccess: to * by dn="cn=admin,dc=harahara,dc=co,dc=jp" write by * read
・ldapadd -Y EXTERNAL -H ldapi:/// -f backend.ldif
・vi frontend.ldif (新規作成、空行もかならず開ける)
dn: dc=harahara,dc=co,dc=jp
objectClass: top
objectClass: dcObject
objectclass: organization
o: Harahara Server
dc: Server
description: LDAP Server

dn: cn=admin,dc=harahara,dc=co,dc=jp
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword: password

dn: ou=people,dc=harahara,dc=co,dc=jp
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=harahara,dc=co,dc=jp
objectClass: organizationalUnit
ou: groups
・ldapadd -x -D cn=admin,dc=server,dc=world -W -f frontend.ldif
・adduser yamada
※パスワードを"password"にする以外は適当
・grep hori /etc/shadow | cut -d: -f2
※表示される文字列をコピーしておく
・vi user_yamada.ldif(新規作成)
※userPassword: {crypt}の後のxxxxxにコピーした文字列で置換
dn: uid=hori,ou=people,dc=harahara,dc=co,dc=jp
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: yamada
sn: yamada
givenName: yamada
cn: yamada
displayName: taro yamada
uidNumber: 1001
gidNumber: 1001
userPassword: {crypt}xxxxx
gecos: hori
loginShell: /bin/bash
homeDirectory: /home/yamada
shadowExpire: -1
shadowFlag: 0
shadowWarning: 7
shadowMin: 0
shadowMax: 99999
shadowLastChange: 15436
・ldapadd -x -D cn=admin,dc=server,dc=world -W -f ldapuser.ldif

2. OpenLDAP管理用ツール導入(オプション)
・PCへログイン
※Windows PC上にEclipse3.7(日本語対応)をインストール
・Eclipse3.7起動
・ヘルプ>新規ソフトウェアのインストール
・追加ボタン
・名前:Apache Directory Studio
・ロケーション:http://directory.apache.org/studio/update/1.x
・OKボタン
・すべて選択ボタン
・完了ボタン
※内容を確認し、インストーしてもよい場合のみラジオボタンを操作し継続
・ウィンドウ>パースペクティブを開く>その他>LDAP
・画面左下の接続タブ>LDAPアイコン(バルーンで「新規接続...」と表示)
・接続名:harahara LDAP
・ホスト名:192.168.11.99
・ポート:389
・次へ
・認証方法:単純認証
・バインドDNまたはユーザー:cn=admin,dc=harahara,dc=co,dc=jp
・バインド・パスワード:password
・パスワードを保存をチェック
・完了ボタン
・LDAPブラウザーのDIT>Root DSE>dc=harahara,dc=co,dc=jp>ou=people>uid=yamada
・画面中央の"userPassword"をダブルクリック
・Cuttrent Passwordタブ
・Show current password detailsをチェック
・パスワード確認:password
・Show test password detailsをチェック
・検証ボタン※succcessfullyとでたらパスワードがpasswordになっている
※新規パスワードタブからパスワード変更可能


3. LDAPサーバの登録
・以下のコマンドをURL化する
command=ldapConfig&
port=389&
queryfilter=(&(uid=%u))&
response=json&
binddn=cn=admin,dc=harahara,dc=co,dc=jp&
bindpass=password&
searchbase=ou=people,dc=harahara,dc=co,dc=jp&
hostname=192.168.11.99&
apikey=xxxxxx&
sinunature=xxxxxx

作成例)
http://192.168.11.100:8080/client/api?command=ldapConfig&port=389&response=json&bindpass=password&searchbase=ou%3Dpeople%2Cdc%3Dexa-corp%2Cdc%3Dco%2Cdc%3Djp&hostname=192.168.11.99&queryfilter=%28%26%28uid%3D%25u%29%29&binddn=cn%3Dadmin%2Cdc%3Dexa-corp%2Cdc%3Dco%2Cdc%3Djp&apikey=xxxxxx&signature=xxxxxx

※apikeyは管理サイトからadminユーザで生成したもの
※signatureの作成方法含め以下の情報を参照のこと
CloudStack API を呼び出すURL文字列の作り方を調べる
CloudStack API を呼び出すURLを生成するJavaサンプルコード

・Windows PC上でブラウザ起動
・作成したURLをURL欄に入れ実行
※結果以下のようなJSONコードが表示される
{ "ldapconfigresponse" : 
{ "ldapconfig" :
{"hostname":"192.168.11.99",
"port":"false",
"searchbase":"ou=people,dc=harahara,dc=co,dc=jp",
"queryfilter":"(&(uid=%u))",
"binddn":"cn=admin,dc=harahara,dc=co,dc=jp"} }  }
※Administration Guideにはqueryfilterが(&(%uid=%uid))となっていたが
上記のようにuidの前の%がないのが正解
Administoratoion Guideだけ参照しているとはまってしまうという罠が..
※上記表示にならない場合は、失敗している


4. LDAP上の同名アカウント/ユーザをCloudStack上に作成
・以下のコマンドをURL化する
command=createAccount&
accounttype=0&
email=yamada@gmail.com&
firstname=taro&
lastname=yamada&
password=5f4dcc3b5aa765d61d8327deb882cf99&
username=yamada&
response=json&
apikey=xxxxxx&
sinunature=xxxxxx

作成例)
http://192.168.11.100:8080/client/api?command=createAccount&username=yamada&email=yamada%40google.com&lastname=yamada&accounttype=0&firstname=taro&password=5f4dcc3b5aa765d61d8327deb882cf99&apikey=qU1uxi73n6HkyLtWNA7YXho_uljUhEF5KgcOUyi6nMs9zIXcTf7TKZ3S6F-z0Tr-PvcBlH2dWX5zBOKzIZhHhQ&signature=hg%2F%2F75X0sbg2%2BEmEXHtGbiD3nfE%3D

※passwordはpasswordをMD5変換した文字列
Apache Directory Studioからも参照可能


5. CloudStack上のパスワードを変更
・CloudStack管理サイトを開く
・admin/password/(ドメインなし)/日本語
・LogOnボタン
・アカウント>yamada>ビューユーザー>yamada
・鍵マークボタン
・新しいパスワード:yamada
・OKボタン
※LDAP上のuid=yamadaのパスワードはpasswordのまま
・ログアウト

6. yamadaユーザでログオン
・CloudStack管理サイトを開く
・yamada/password/(ドメインなし)/日本語
・LogOnボタン
→ログイン成功したらOK



ログを見ると、
2012-04-11 17:44:35,278 DEBUG [cloud.user.AccountManagerImpl] (catalina-exec-16:null) Attempting to log in user: yamada in domain 1
2012-04-11 17:44:35,278 DEBUG [server.auth.MD5UserAuthenticator] (catalina-exec-16:null) Retrieving user: yamada
2012-04-11 17:44:35,279 DEBUG [server.auth.MD5UserAuthenticator] (catalina-exec-16:null) Password does not match
2012-04-11 17:44:35,279 DEBUG [server.auth.LDAPUserAuthenticator] (catalina-exec-16:null) Retrieving user: yamada
2012-04-11 17:44:35,293 INFO  [server.auth.LDAPUserAuthenticator] (catalina-exec-16:null) DN from LDAP =uid=yamada
2012-04-11 17:44:35,300 DEBUG [cloud.user.AccountManagerImpl] (catalina-exec-16:null) User: yamada in domain 1 has successfully logged in
となっているので、
・CloudStack上のパスワードで認証をかけようとする
→失敗
・LDAP上のqueryfilter以下のuidがyamadaであるアカウントを探す
→発見
・パスワードをチェック
→成功
でログインできるようになっている。



..でも

これじゃ2つのパスワードでログインできるようになっただけじゃん..

CloudStack上のパスワード変えたら
OpenLDAP側も変わっていてほしかった..


あれ?
でもaccounttype=0(非管理者)でログオンした画面で
パスワード変更機能ってなかったような気がする..

とすると設計者の意図は
LDAP側のパスワード管理サイトでユーザにパスワードを
変えさせるように仕向けたいのか...

とするとOpenLDAPを操作する機能を
別途提供しないと..

2012年4月10日火曜日

CloudStack API を呼び出すURLを生成するJavaサンプルコード

CloudStack API を呼び出すURL文字列の作り方を調べる
で変換の仕方と
変換してくれるシェルスクリプトを見つけたところまで紹介しました。

が、やはりJavaプログラムから呼び出したい。

処理も面倒なだけで実装が難しいわけではないので
ちょこっと自分で書いてみました。

コマンド2つ(listAccountsとlistVirtualMachines)だけしか
動作確認していないのでバグがあるかもしれません。

誤りを見つけた方は、
できればコメント欄にて指摘をしてもらえるとありがたいです。

コピーして利用されるのはOKですが、
参考にされる方はAt Your Own Riskでお願いします。

Base64変換は、Java SEの標準ライブラリになさそうだったので
Apache Commonsのcommons-codec-1.6.jarを使用しています。

使い方はとりあえず先頭のコメントに書いておいたので
なんとなく分かると思います。

/*
 * CloudStack API をHTTP GETメソッドで呼び出すためのURLを生成するクラス実装サンプル。
 * 流用はOKですが、くれぐれもAt Your Own Riskでお願いします。
 * 
 * 使用例)
 * CloudStackApiUrlGenerator gen =
 *  new CloudStackApiGenerator(
 *   "qu1uxi73n6hkyltwna7yxho_uljuhef... ", // APIキー
 *   "zixctf7tkz3s6f-z0tr-pvcb... ");       // 秘密キー
 * gen.setParam("command", "listAccounts"); // command名登録
 * // パラメータを設定する場合はsetParam()/setParams()で格納する
 * String url = gen.getApiRequest(); // 呼び出すURL文字列
 * 
 * なお本クラスではBase64エンコード処理に Apache Commons ライブラリを使用している。
 * Apache Commons http://bit.ly/IywKHK よりdownloadし、
 * commons-codec-1.6.jar(1.6の場合) をクラスパスに追加して下さい。
 * 
 * (C)Copyright by Hara-Hara-Kaihatsu, Japan, 2012
 */
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
 * CloudStackApiUrlGeneratorクラス
 * 
 * @author ton / Hara-Hara-Kaihatsu
 */
public class CloudStackApiUrlGenerator {
  /**
   * APIキー
   */
  private String apiKey = null;
  /**
   * 秘密キー
   */
  private String secretKey = null;
  /**
   * GETパラメータを格納するMap
   */
  private Map<String, String> params = new HashMap<String, String>();
  
  /**
   * 唯一のコンストラクタ。必須であるAPIキーと秘密キーはManagement Serverの
   * 管理コンソールのアカウント(ユーザ)から生成し文字列を取得する
   * @param apiKey APIキー文字列
   * @param secretKey 秘密キー文字列
   */
  public CloudStackApiUrlGenerator(String apiKey, String secretKey){
    this.apiKey = apiKey;
    this.secretKey = secretKey;
  }

  /**
   * Command String(?以降のGETパラメータ)を登録する。
   * field1=value1&field2=value2&..&fieldN=valueN 形式の文字列で
   * 複数のパラメータを一括セットすることができる。
   * @param paramPairs field1=value1&field2=value2&..&fieldN=valueN 形式の文字列
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public void setParams(String paramPairs)
      throws UnsupportedEncodingException{
    StringTokenizer st = new StringTokenizer(paramPairs, "&");
    while(st.hasMoreTokens()){
      setParam(st.nextToken());
    }
  }
  
  /**
   * Command String(?以降のGETパラメータ)を1件登録する。
   * field=value 形式の文字列で1セットのみ格納できる。
   * @param paramPair field=value 形式の文字列
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public void setParam(String paramPair)
      throws UnsupportedEncodingException{
    params.put(getField(paramPair), encodeUrl(getValue(paramPair)));
  }
  
  /**
   * Command String(?以降のGETパラメータ)を1件登録する。
   * @param field フィールド名
   * @param value 設定値
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public void setParam(String field, String value)
      throws UnsupportedEncodingException{
    params.put(field, encodeUrl(value));
  }
  
  /**
   * Command Stringを1つの文字列として取得する。
   * @return Command String文字列(signatureなし)
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public String getCommandString() throws UnsupportedEncodingException{
    Map<String, String> comParams = new HashMap<String, String>(params);
    String commandValue = comParams.remove("command");
    String apiKeyValue = comParams.remove("apikey");
    if(apiKeyValue==null || "".equals(apiKeyValue)){
      apiKeyValue = encodeUrl(apiKey);
    }
    comParams.remove("signature");

    StringBuffer commandString = new StringBuffer();
    commandString.append("command=");
    commandString.append(commandValue);
    commandString.append("&");
    Set<String> keyMap = comParams.keySet();
    for(String key: keyMap){
      commandString.append(key);
      commandString.append("=");
      commandString.append(comParams.get(key));
      commandString.append("&");
    }
    commandString.append("apikey=");
    commandString.append(apiKeyValue);
    return commandString.toString();
  }

  /**
   * signatureとして指定する値を生成する。
   * @return signatureとして指定する文字列
   * @throws InvalidKeyException HMACSHA1処理時に発生する例外
   * @throws NoSuchAlgorithmException HMACSHA1処理時に発生する例外
   * @throws UnsupportedEncodingException HMACSHA1処理時に発生する例外
   */
  public String getSigniture()
      throws InvalidKeyException, NoSuchAlgorithmException, 
      UnsupportedEncodingException{
    Map<String, String> sortedMap = new TreeMap<String, String>(params);
    if(apiKey!=null && !params.containsKey("apikey")){
      sortedMap.put("apikey", apiKey);
    }
    sortedMap.remove("signiture");
    int pos = 0;
    StringBuffer sortedParamPairs = new StringBuffer();
    Set<String> keySet = sortedMap.keySet();
    for(String key: keySet){
      sortedParamPairs.append(key.toLowerCase());
      sortedParamPairs.append("=");
      sortedParamPairs.append(sortedMap.get(key).toLowerCase());
      if((++pos)<keySet.size()) sortedParamPairs.append("&");
    }
    String signature = createHash(sortedParamPairs.toString());
    return encodeUrl(signature);
  }

  /**
   * HTTP GEPメソッドを呼び出すためのURLを取得する。
   * @param hostname ホスト名FQDNもしくはIPアドレス
   * @param port ポート番号文字列
   * @return URL文字列
   * @throws InvalidKeyException HMACSHA1処理中に発生する例外
   * @throws NoSuchAlgorithmException HMACSHA1処理中に発生する例外
   * @throws UnsupportedEncodingException HMACSHA1処理中に発生する例外
   */
  public String getApiRequestUrl(String hostname, String port)
      throws InvalidKeyException, NoSuchAlgorithmException, 
      UnsupportedEncodingException{
    StringBuffer request = new StringBuffer();
    request.append("http://");
    if(hostname==null || "".equals(hostname)) request.append("localhost");
    else request.append(hostname);
    request.append(":");
    if(port==null || "".equals(port)) request.append("8080");
    else request.append(port);
    request.append("/client/api?");
    request.append(getCommandString());
    request.append("&signature=");
    request.append(getSigniture());
    return request.toString();
  }
  
  /**
   * field=value 形式文字列からfield部分を切り出す。
   * @param paramPair field=value形式文字列
   * @return field部分の文字列
   */
  private String getField(String paramPair){
    if(paramPair==null || "".equals(paramPair)) return paramPair;
    int pos = paramPair.indexOf("=");
    if(pos < 0) return paramPair;
    if(pos == 0) return "";
    return paramPair.substring(0, pos);
  }
  
  /**
   * field=value 形式文字列からvalue部分を切り出す。
   * @param paramPair field=value形式文字列
   * @return value部分の文字列
   */  
  private String getValue(String paramPair){
    if(paramPair==null || "".equals(paramPair)) return paramPair;
    int pos = paramPair.indexOf("=");
    if(pos < 0) return paramPair;
    if(pos == (paramPair.length() - 1)) return "";
    return paramPair.substring(pos+1);
  }
  
  /**
   * URLエンコード(UTF-8)を実行し、スペース文字を%20に置換する。
   * @param org 処理対象文字列
   * @return 変換後文字列
   * @throws UnsupportedEncodingException URLエンコード処理中に発生する例外
   */
  private String encodeUrl(String org) throws UnsupportedEncodingException{
    if(org==null) return org;
    return URLEncoder.encode(org, "UTF-8").replaceAll("\\+", "%20");
  }
  
  /**
   * HMAC SHA1形式でエンコードする。
   * エンコード時にコンストラクタにて指定した秘密キーを使用する。
   * @param sortedParamPairs エンコード対象文字列
   * @return エンコード後文字列
   * @throws NoSuchAlgorithmException エンコード処理中発生する例外
   * @throws InvalidKeyException エンコード処理中発生する例外
   */
  private String createHash(String sortedParamPairs)
      throws NoSuchAlgorithmException, InvalidKeyException{
    SecretKeySpec secretKeySpec = 
        new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(secretKeySpec);
    byte[] hash = mac.doFinal(sortedParamPairs.getBytes());
    // AppEngineの場合
    //return com.google.appengine.repackaged.com.google.common.util.Base64.encode(hash);
    // Apache Commonsを使う場合
    hash = Base64.encodeBase64(hash);
    return new String(hash);
  }
}

あとは..
Reference APIの日本語訳がほしいなあ..

誰か翻訳してくれないかなあ..
と、つぶやいてみる。

2012年4月9日月曜日

ldapConfig: CloudStack Root Admin API Reference(日本語訳)

CloudStack 3.0のAPIのうちLDAP連携に必要そうなコマンドldapConfigを
翻訳したので載せておきます。
#翻訳が間違っている場合もありますので
#At your own riskで参照下さい。

CloudStack v3.0 Root Admin API Reference

ldapConfig

CloudStackサイトのためのLDAPコンテキストの設定を行う。


リクエストパラメータ一覧

パラメータ名 説明 必須
hostname LDAPサーバのホスト名もしくはIPアドレス
eg) my.ldap.com
はい
queryfilter ユーザやドメイン範囲を限定させるための、LDAPクエリフィルタを指定する。
例) (&(uid=%u))
※訳者注AdminGuideの例ではuidの前に%があるがアレは誤り
はい
searchbase 検索するディレクトリツリーの開始ポイントを定義する。
例) dc=cloud,dc=com
はい
binddn ディレクトリの検索権限を持つユーザの識別名を指定する。
 例) cn=admin,dc=cloud,dc=com
いいえ
bindpass (ディレクトリの検索権限を持つユーザの)パスワードを指定する。
※訳者注:平文で動きました
いいえ
port LDAPポート(デフォルトは389) いいえ
ssl 外部LDAPサーバがLDAP over SSLである場合チェックする。
※訳者注:ssl=falseで送るとなぜかtrueと判断されたSSLを使わない場合は設定しない、が正解らしい
いいえ
truststore 信頼する認証ストアのパスを指定する。 いいえ
truststorepass 信頼するストアのパスワードを指定する。 いいえ


レスポンスタグ一覧
レスポンス名 説明
binddn ディレクトリの検索権限を持つユーザの識別名。
bindpass DN(ディレクトリの検索権限を持つユーザの)パスワード。
hostname LDAPサーバのホスト名もしくはIPアドレス
eg) my.ldap.com
port LDAPポート(デフォルトは389)
port(ssl) 外部LDAPサーバがLDAP over SSLであるかどうか。
※訳者注:原文はportで実際にコマンドを叩くとレスポンスタグがportになっていた
queryfilter ユーザやドメイン範囲を限定させるための、LDAPクエリフィルタ。
searchbase 検索するディレクトリツリーの開始ポイント。
例) dc=cloud,dc=com


実行すると、以下のようなレスポンス(XMLの場合)が返ってくる。


<?xml version="1.0" encoding="UTF-8"?>

<ldapconfigresponse cloud-stack-version="3.0.0.20120228045403">

 <ldapconfig>

  <hostname>192.168.1.1</hostname>

 <port>389</port>

  <port>false</port>

  <searchbase>dc=harahara,dc=co,dc=jp</searchbase>

  <queryfilter>(ou=people)</queryfilter>

 </ldapconfig>

</ldapconfigresponse>


上記のようにエラーが出なかったら成功。
これでLDAP上にあってCloudStackにないユーザでも
ログインできるようになった


..と思ったら
うまくはいれない..


Administration Guideにはこれ以上かかれてないし..

で、色々検索していると
以下の文章をCloudStackのナレッジベースで見つけた。

CloudStack Docs and Knowledge Base
LDAP Authentication


LDAP User Provisioningの章を翻訳してみた。


「LDAPユーザプロビジョニング

 ユーザプロビジョニングするために、
 CloudStackアカウント生成APIを使って
 以下の項目を登録する必要があります。
 ・username
 ・email
 ・lastname
 ・firstname
 ・上記に加え、domain/timezone/accountなど

 アカウント生成中もクエリフィルタ経由で
 ユーザの属性が使用されていることを
 考慮しなくてはいけません。」

ぬー、アカウントをCloudStack側でも作らないといけないとは..
CloudStack機能上大量のユーザを登録することはないとは思うけど..

..めんどくさいなあ..


2012年4月6日金曜日

CloudStack API を呼び出すURL文字列の作り方を調べる

CloudStack APIを呼び出すAPIリクエスト(URL)の作り方



LDAP認証などは
CloudStack Management Serverが提供する
管理コンソール上からは設定することができない。

このようなGUI経由で操作できない
設定などを行いたい場合は、
CloudStackを外部のプログラムから操作できるAPIが
予め提供されているのでこれを使う必要がある。


で、これがかなり厄介、
手順がめんどくさいのだ。


まず認証。
これはAWSでログインする時などと同じ
APIキーと秘密キーを使う。

取得の仕方は、以下の操作を実行し、

1. Management Serverの管理コンソールへログオン
2. アカウント
3. admin(adminアカウントを使う場合)
4. ビューユーザーボタン押下
5. admin(adminユーザを使う場合)
6. 鍵アイコンのとなりのボタン(キーの生成とバルーン表示される)押下

画面に表示される「APIキー」と「秘密キー」を入手しておく。


次に、
ドキュメントとしてJava Docっぽいサイトが
公開されているので、
このなかから使いたいコマンドを探し出す。

CloudStack API Documentation (v3.0)

ここであたりをつけて
コマンド名、
パラメータのフィールド、
および値
が決まったら、
次に HTTP GETメソッドで送信するパラメータを含むURL
(APIリクエスト)を作る必要がある。



文字列のつくりかたは
CloudStack 2.2 -3.0 API Developer's Guideに書かれている。



APIリクエストの形式は、
"Base URL + API Path + Command String + Signature"形式
で構成される。

各構成は以下のとおり


Base URL: CloudStack Management ServerのIPアドレス、ポート
例)http://192.168.11.100:8080

API Path: リクエストを受け付けるAPI Serverのパス
/client/api?

Command String: コマンド、パラメータ群、CloudStack認証に使用するAPIキーを含む問い合わせ文字列
例)command=ldapConfig&hostname=127.0.0.1&searchbase=ou%3Dtesting%2Co%3Dproject&queryfilter=%28%26%28uid%3D%25u%29%29&binddn=cn%3DJohn+Singh%2Cou%3Dtesting%2Co%project&bindpass=secret&port=10389&ssl=true&truststore=C%3A%2Fcompany%2Finfo%2Ftrusted.ks&truststorepass=secret&response=json&apiKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Signature: ユーザの秘密キーとハッシュ化アルゴリズムHMAC SHA-1を組み合わせたBase URLのハッシュ化された署名
例)signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx





Signatureを生成する手順:
1. HTTP GETメソッドで送信するために、Command Stringの各々のフィールドと値のペア(&で分割されたhogehoge=fugafuga形式のこと)に対して、値部分をURLエンコードする

注意:ただし、スペースは"+"の代わりに"%20"でエンコードすること

例)
編集前:command=aaaa&bbb=c c&ddd=(eee)&apikey=XXXXX
編集後:command=aaaa&bbb=c%20c&ddd=%28eee%29&apikey=XXXXX

2. Command String文字列を全部小文字化して、フィールド部分でソートする

例)
編集前:command=aaaa&bbb=c%20c&ddd=%28eee%29&apikey=XXXXX
編集後:apikey=xxxxx&bbb=c%20c&ddd=%28eee%29&command=aaaa


3. ユーザの秘密キーを使って、ソートしたCommand StringをハッシュアルゴリズムHMAC SHA-1(大抵のプログラミング言語はユーティリティメソッドとしてこの機能を保有している)にかける

例)OpenSSLを使ってハッシュ化する場合のコマンド
# echo -n apikey=xxxxx&bbb=c%20c&ddd=%28eee%29&command=aaaa openssl sha1 -binary hmac 秘密キー

ここでハッシュ化する対象を間違えたりしない様に注意する。

HTTP経由で安全に変換できるように、取得した文字列をさらにBase64エンコードしてUTF-8にする。

Base64 エンコード後の文字列がSigniture値(signiture=の後に続く文字列)となる。










..わけわからん..

なにか楽ができないものか..

色々調べてみると、
APIキー/秘密キーやコマンドやパラメータが確定していれば
エンコードやsignitureなどを加工してくれるシェルスクリプトを
公開している人(会社?)を見つけた。
ありがたや、ありがたや。

bash script for CloudStack API  - asako

この記事の最後にあるスクリプトに
自分のAPIキー、秘密キーを書き込み、
最後の行でcurlを使っているのでそこはコメントアウトして
実行すればよい。

./key_api.sh command=listAccounts
と実行すれば
 SEND URL: http://192.168.11.101:8080/client/api?command=listAccounts&apikey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
といったURLを表示してくれる。

command=listAccountsでURLを作って
ブラウザのURL入力欄にいれて実行して
びろびろ長いXMLが出てくれば成功で

errorcodeタグを含む短い文字列の場合は
失敗である。

APIをプログラムで使う前に
開発者はまずこれで
つながることを試す必要がある。

結果がXMLだったがresponse=jsonをつければ
JSON形式にもなるようだ。





..ここまで調べるのにまる1日..


もう、誰か本とかかいてくれないかなあ..



p.s.
Javaでこのシェルスクリプト相当の機能を実装してみました。
CloudStack API を呼び出すURLを生成するJavaサンプルコード

2012年4月2日月曜日

CloudStack3.0.0で独自Webサイト上のISOイメージを取り込むとConnection refusedとなる

CloudStack2.1.7では独自に立ち上げたWebサーバ
上のISOイメージを取り込ませることができたのだが
CloudStack3.0.0ではうまくいかない。

StatusがConnection refused
になって全然ゆうことをきかない..

色々調べてみたところ
Global Settings の
secstorage.allowed.internal.sites
にIPアドレスを入力すれば動作することがわかった。

http://192.168.1.1/hogehoge.iso
を取り込ませたい場合は
secstorage.allowed.internal.sites値を
192.168.1.1
にしてservice cloud-management restartすれば
取り込むことができる。

p.s.
4.1ではCloudStack管理下のIPアドレスだと
はじかれてしまうので、
グローバル設定secstorage.allowed.internal.sitesを設定する。

WebサーバのIPアドレスが192.168.1.1だったら192.168.1.0/24などと記述すれば良い。

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...