2.3. 管理認証要求
Amazon の S3 サービスは、アクセスキー、要求ヘッダーのハッシュ、および秘密鍵を使用して要求を認証します。これは、認証されたリクエスト (特に SSL オーバーヘッドのない大規模なアップロード) を提供する利点があります。
S3 API のほとんどのユースケースには、Java や Python Boto 用の Amazon SDK の AmazonS3Client などのオープンソースの S3 クライアントを使用します。これらのライブラリーは、Ceph Object Gateway 管理 API をサポートしません。これらのライブラリーをサブクラス化および拡張して、Ceph Admin API をサポートすることができます。一意のゲートウェイクライアントを作成できます。
execute() メソッドの作成
本セクションの CephAdminAPI のサンプルクラスでは、要求パラメーターの取得、リクエストの認証、Ceph 管理 API を呼び出してレスポンスを受け取ることができる execute() メソッドの作成方法を説明します。
CephAdminAPI クラスの例は、商用としてはサポートされず、そのように意図されてもいません。これは説明のみを目的としています。
Ceph Object Gateway の呼び出し
クライアントコード には、CRUD 操作を示すために Ceph Object Gateway への 5 つの呼び出しが含まれます。
- ユーザーの作成
- ユーザーの取得
- ユーザーの変更
- サブユーザーの作成
- ユーザーを削除します。
この例を使用するには、httpcomponents-client-4.5.3 Apache HTTP コンポーネントを取得します。たとえば、http://hc.apache.org/downloads.cgi からダウンロードできます。その後、tar ファイルを展開して lib ディレクトリーに移動し、JAVA_HOME ディレクトリーの /jre/lib/ext ディレクトリーまたはカスタムクラスパスにコピーします。
CephAdminAPI クラスの例を検査する際に、execute() メソッドは HTTP メソッド、リクエストパス、オプションのサブリソース、未指定の場合は null、およびパラメーターのマップを取得することに注意してください。サブリソース (例: subuser、key など) で実行するには、サブリソースを execute() メソッドの引数として指定する必要があります。
方法の例を以下に示します。
- URI をビルドします。
- HTTP ヘッダー文字列をビルドします。
-
HTTP リクエストをインスタンス化します (例:
PUT、POST、GET、DELETE)。 -
Dateヘッダーを HTTP ヘッダー文字列および要求ヘッダーに追加します。 -
Authorizationヘッダーを HTTP リクエストヘッダーに追加します。 - HTTP クライアントをインスタンス化し、インスタンス化された HTTP リクエストを渡します。
- 要求を行います。
- レスポンスを返します。
ヘッダー文字列のビルド
ヘッダー文字列のビルドは、Amazon の S3 認証手順を伴うプロセスの一部です。特に、サンプルメソッドは以下を行います。
-
PUT、POST、GET、DELETEなどのリスエストタイプを追加します。 - 日付を追加します。
- requestPath を追加します。
リクエストタイプは、先頭または最後の空白のない大文字である必要があります。空白を削除しないと、認証は失敗します。日付は GMT で表現される必要があります。さもないと、認証に失敗します。
例示的な方法には、他のヘッダーはありません。Amazon S3 認証手順は、x-amz ヘッダーの辞書式に並べ替えられます。したがって、x-amz ヘッダーを追加する場合は、必ず辞書式で追加する必要があります。
ヘッダー文字列をビルドしたら、次の手順は HTTP リクエストをインスタンス化し、URI を渡すことです。典型的なメソッドは、PUT を使用してユーザーおよびサブユーザーを作成し、GET を使用してユーザーを取得し、POST を使用してユーザーを変更し、DELETE を使用してユーザーを削除します。
リクエストをインスタンス化したら、Date ヘッダーに続けて Authorization ヘッダーを追加します。Amazon の S3 認証は標準の Authorization ヘッダーを使用し、以下の構造を持ちます。
Authorization: AWS ACCESS_KEY:HASH_OF_HEADER_AND_SECRET
CephAdminAPI のサンプルクラスには base64Sha1Hmac() メソッドがあります。これはヘッダー文字列と admin ユーザーの秘密鍵を取得し、SHA1 HMAC を base-64 でエンコードされた文字列として返します。それぞれの execute() 呼び出しは、同じコード行を呼び出して Authorization ヘッダーをビルドします。
httpRequest.addHeader("Authorization", "AWS " + this.getAccessKey() + ":" + base64Sha1Hmac(headerString.toString(), this.getSecretKey()));
以下の CephAdminAPI のサンプルクラスでは、アクセスキー、シークレットキー、およびエンドポイントをコンストラクターに渡す必要があります。クラスは実行時に変更するためのアクセスメソッドを提供します。
例
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.ZoneId;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.Header;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.utils.URIBuilder;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
public class CephAdminAPI {
/*
* Each call must specify an access key, secret key, endpoint and format.
*/
String accessKey;
String secretKey;
String endpoint;
String scheme = "http"; //http only.
int port = 80;
/*
* A constructor that takes an access key, secret key, endpoint and format.
*/
public CephAdminAPI(String accessKey, String secretKey, String endpoint){
this.accessKey = accessKey;
this.secretKey = secretKey;
this.endpoint = endpoint;
}
/*
* Accessor methods for access key, secret key, endpoint and format.
*/
public String getEndpoint(){
return this.endpoint;
}
public void setEndpoint(String endpoint){
this.endpoint = endpoint;
}
public String getAccessKey(){
return this.accessKey;
}
public void setAccessKey(String accessKey){
this.accessKey = accessKey;
}
public String getSecretKey(){
return this.secretKey;
}
public void setSecretKey(String secretKey){
this.secretKey = secretKey;
}
/*
* Takes an HTTP Method, a resource and a map of arguments and
* returns a CloseableHTTPResponse.
*/
public CloseableHttpResponse execute(String HTTPMethod, String resource,
String subresource, Map arguments) {
String httpMethod = HTTPMethod;
String requestPath = resource;
StringBuffer request = new StringBuffer();
StringBuffer headerString = new StringBuffer();
HttpRequestBase httpRequest;
CloseableHttpClient httpclient;
URI uri;
CloseableHttpResponse httpResponse = null;
try {
uri = new URIBuilder()
.setScheme(this.scheme)
.setHost(this.getEndpoint())
.setPath(requestPath)
.setPort(this.port)
.build();
if (subresource != null){
uri = new URIBuilder(uri)
.setCustomQuery(subresource)
.build();
}
for (Iterator iter = arguments.entrySet().iterator();
iter.hasNext();) {
Entry entry = (Entry)iter.next();
uri = new URIBuilder(uri)
.setParameter(entry.getKey().toString(),
entry.getValue().toString())
.build();
}
request.append(uri);
headerString.append(HTTPMethod.toUpperCase().trim() + "\n\n\n");
OffsetDateTime dateTime = OffsetDateTime.now(ZoneId.of("GMT"));
DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME;
String date = dateTime.format(formatter);
headerString.append(date + "\n");
headerString.append(requestPath);
if (HTTPMethod.equalsIgnoreCase("PUT")){
httpRequest = new HttpPut(uri);
} else if (HTTPMethod.equalsIgnoreCase("POST")){
httpRequest = new HttpPost(uri);
} else if (HTTPMethod.equalsIgnoreCase("GET")){
httpRequest = new HttpGet(uri);
} else if (HTTPMethod.equalsIgnoreCase("DELETE")){
httpRequest = new HttpDelete(uri);
} else {
System.err.println("The HTTP Method must be PUT,
POST, GET or DELETE.");
throw new IOException();
}
httpRequest.addHeader("Date", date);
httpRequest.addHeader("Authorization", "AWS " + this.getAccessKey()
+ ":" + base64Sha1Hmac(headerString.toString(),
this.getSecretKey()));
httpclient = HttpClients.createDefault();
httpResponse = httpclient.execute(httpRequest);
} catch (URISyntaxException e){
System.err.println("The URI is not formatted properly.");
e.printStackTrace();
} catch (IOException e){
System.err.println("There was an error making the request.");
e.printStackTrace();
}
return httpResponse;
}
/*
* Takes a uri and a secret key and returns a base64-encoded
* SHA-1 HMAC.
*/
public String base64Sha1Hmac(String uri, String secretKey) {
try {
byte[] keyBytes = secretKey.getBytes("UTF-8");
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(uri.getBytes("UTF-8"));
Encoder base64 = Base64.getEncoder();
return base64.encodeToString(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
後続の CephAdminAPIClient の例は、CephAdminAPI クラスをインスタンス化する方法、リクエストパラメーターのマップをビルドし、execute() メソッドを使用してユーザーを作成、取得、更新、および削除する方法を示しています。
例
import java.io.IOException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;
import java.util.*;
public class CephAdminAPIClient {
public static void main (String[] args){
CephAdminAPI adminApi = new CephAdminAPI ("FFC6ZQ6EMIF64194158N",
"Xac39eCAhlTGcCAUreuwe1ZuH5oVQFa51lbEMVoT",
"ceph-client");
/*
* Create a user
*/
Map requestArgs = new HashMap();
requestArgs.put("access", "usage=read, write; users=read, write");
requestArgs.put("display-name", "New User");
requestArgs.put("email", "new-user@email.com");
requestArgs.put("format", "json");
requestArgs.put("uid", "new-user");
CloseableHttpResponse response =
adminApi.execute("PUT", "/admin/user", null, requestArgs);
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
try {
System.out.println("\nResponse Content is: "
+ EntityUtils.toString(entity, "UTF-8") + "\n");
response.close();
} catch (IOException e){
System.err.println ("Encountered an I/O exception.");
e.printStackTrace();
}
/*
* Get a user
*/
requestArgs = new HashMap();
requestArgs.put("format", "json");
requestArgs.put("uid", "new-user");
response = adminApi.execute("GET", "/admin/user", null, requestArgs);
System.out.println(response.getStatusLine());
entity = response.getEntity();
try {
System.out.println("\nResponse Content is: "
+ EntityUtils.toString(entity, "UTF-8") + "\n");
response.close();
} catch (IOException e){
System.err.println ("Encountered an I/O exception.");
e.printStackTrace();
}
/*
* Modify a user
*/
requestArgs = new HashMap();
requestArgs.put("display-name", "John Doe");
requestArgs.put("email", "johndoe@email.com");
requestArgs.put("format", "json");
requestArgs.put("uid", "new-user");
requestArgs.put("max-buckets", "100");
response = adminApi.execute("POST", "/admin/user", null, requestArgs);
System.out.println(response.getStatusLine());
entity = response.getEntity();
try {
System.out.println("\nResponse Content is: "
+ EntityUtils.toString(entity, "UTF-8") + "\n");
response.close();
} catch (IOException e){
System.err.println ("Encountered an I/O exception.");
e.printStackTrace();
}
/*
* Create a subuser
*/
requestArgs = new HashMap();
requestArgs.put("format", "json");
requestArgs.put("uid", "new-user");
requestArgs.put("subuser", "foobar");
response = adminApi.execute("PUT", "/admin/user", "subuser", requestArgs);
System.out.println(response.getStatusLine());
entity = response.getEntity();
try {
System.out.println("\nResponse Content is: "
+ EntityUtils.toString(entity, "UTF-8") + "\n");
response.close();
} catch (IOException e){
System.err.println ("Encountered an I/O exception.");
e.printStackTrace();
}
/*
* Delete a user
*/
requestArgs = new HashMap();
requestArgs.put("format", "json");
requestArgs.put("uid", "new-user");
response = adminApi.execute("DELETE", "/admin/user", null, requestArgs);
System.out.println(response.getStatusLine());
entity = response.getEntity();
try {
System.out.println("\nResponse Content is: "
+ EntityUtils.toString(entity, "UTF-8") + "\n");
response.close();
} catch (IOException e){
System.err.println ("Encountered an I/O exception.");
e.printStackTrace();
}
}
}
関連情報
- 詳細は、Red Hat Ceph Storage 開発者ガイドの S3 認証 セクションを参照してください。
- Amazon S3 認証手順の詳細は、Amazon Simple Storage Service ドキュメントの Signing and Authenticating REST Requests セクションを参照してください。