- 投稿日:2020-09-25T20:21:53+09:00
Java で Amazon Product Advertising API 5.0 (PA-API v5) をコールする
概要
- Java で Amazon Product Advertising API 5.0 (PA-API v5) をコールする
- AWS が提供している公式の SDK を使わず API を直接コールする
- AWS 署名バージョン 4 (AWS Signature Version 4) の処理を Java 標準ライブラリのみで実装する
- 今回の実行環境: macOS Catalina + Java 15 (AdoptOpenJDK 15) + Jackson Databind 2.11.1 + Gradle 6.6.1
サンプルコード
ファイル一覧
├── build.gradle └── src └── main └── java ├── AwsSignature4.java ├── JsonUtil.java ├── MyApp.java └── PaApi5Wrapper.javabuild.gradle
Gradle の設定ファイル。
JSON 操作ライブラリは Java の標準ライブラリにないため Jackson を使用する。plugins { id 'java' id 'application' } repositories { mavenCentral() } dependencies { // Jackson を使う implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1' } application { mainClassName = 'MyApp' } sourceCompatibility = JavaVersion.VERSION_15src/main/java/MyApp.java
import java.util.HashMap; import java.util.Map; /** * PA-API v5 をコールするサンプルクラス。 */ public class MyApp { public static void main(String[] args) throws Exception { // PA-API v5 をコールする searchItems(); getItems(); } private static final String ACCESS_KEY = "<YOUR-ACCESS-KEY-HERE>"; // 取得したアクセスキー private static final String SECRET_KEY = "<YOUR-SECRET-KEY-HERE>"; // 取得したシークレットキー private static final String TRACKING_ID = "<YOUR-PARTNER-TAG-HERE>"; // トラッキングID (例: XXXXX-22) // キーワードから商品を検索 public static void searchItems() throws Exception { String keywords = "シェイクスピア"; // リクエスト情報 Map<String, Object> req = new HashMap<>() { { put("ItemCount", 3); // 検索結果の数 put("PartnerTag", TRACKING_ID); // ストアID or トラッキングID put("PartnerType", "Associates"); // パートナータイプ put("Keywords", keywords); // 検索キーワード put("SearchIndex", "All"); // 検索カテゴリー (All, AmazonVideo, Books, Hobbies, Music などを指定可能) put("Resources", new String[]{ // レスポンスに含む値のタイプ "ItemInfo.Title", "ItemInfo.ByLineInfo", "ItemInfo.ProductInfo", "ItemInfo.ProductInfo", "Images.Primary.Large", "Images.Primary.Medium", "Images.Primary.Small" }); } }; // リクエスト情報を JSON 文字列にする String reqJson = new JsonUtil().objectToJson(req); System.out.println("===== キーワードから商品を検索: リクエスト ====="); System.out.println(reqJson); // PA-API v5 をコールして結果を JSON 文字列で受け取る PaApi5Wrapper api = new PaApi5Wrapper(ACCESS_KEY, SECRET_KEY); String resJson = api.searchItems(reqJson); System.out.println("===== キーワードから商品を検索: レスポンス ====="); System.out.println(new JsonUtil().prettyPrint(resJson)); } // ASIN から商品情報を取得 public static void getItems() throws Exception { String[] asinList = new String[]{"4391641585", "B010EB1HR4", "B0125SPF90", "B07V52KSGT"}; // リクエスト情報 Map<String, Object> req = new HashMap<>() { { put("PartnerTag", TRACKING_ID); // ストアID or トラッキングID put("PartnerType", "Associates"); // パートナータイプ put("ItemIds", asinList); // ASINのリスト put("Resources", new String[]{ // レスポンスに含む値のタイプ "ItemInfo.Title", "ItemInfo.ByLineInfo" }); } }; // リクエスト情報を JSON 文字列にする String reqJson = new JsonUtil().objectToJson(req); System.out.println("===== ASIN から商品情報を取得: リクエスト ====="); System.out.println(reqJson); // PA-API v5 をコールして結果を JSON 文字列で受け取る PaApi5Wrapper api = new PaApi5Wrapper(ACCESS_KEY, SECRET_KEY); String resJson = api.getItems(reqJson); System.out.println("===== ASIN から商品情報を取得: レスポンス ====="); System.out.println(new JsonUtil().prettyPrint(resJson)); } }src/main/java/PaApi5Wrapper.java
import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.util.Map; /** * PA-API v5 ラッパークラス。 */ public class PaApi5Wrapper { private static final String HOST = "webservices.amazon.co.jp"; // Amazon.co.jp の Web API ホスト private static final String REGION = "us-west-2"; // Amazon.co.jp では us-west-2 を指定 private final String accessKey; private final String secretKey; /** * コンストラクタ。 * @param accessKey アクセスキー * @param secretKey シークレットキー */ public PaApi5Wrapper(String accessKey, String secretKey) { this.accessKey = accessKey; this.secretKey = secretKey; } /** * キーワードから商品を検索する。 * @param reqJson リクエスト情報 JSON * @return レスポンス情報 JSON * @throws InterruptedException * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws URISyntaxException */ public String searchItems(String reqJson) throws InterruptedException, IOException, NoSuchAlgorithmException, InvalidKeyException, URISyntaxException { String path = "/paapi5/searchitems"; String target = "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems"; return callApi(reqJson, path, target); } /** * ASIN から商品情報を取得する。 * @param reqJson リクエスト情報 JSON * @return レスポンス情報 JSON * @throws InterruptedException * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws URISyntaxException */ public String getItems(String reqJson) throws InterruptedException, IOException, NoSuchAlgorithmException, InvalidKeyException, URISyntaxException { String path = "/paapi5/getitems"; String target = "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.GetItems"; return callApi(reqJson, path, target); } /** * PA-API v5 をコールする。 * @param reqJson リクエスト情報 JSON * @param path API エントリポイントのパス * @param target リクエストの送信先サービスおよびデータのオペレーション * @return レスポンス情報 JSON * @throws URISyntaxException * @throws InvalidKeyException * @throws NoSuchAlgorithmException * @throws IOException * @throws InterruptedException */ public String callApi(String reqJson, String path, String target) throws URISyntaxException, InvalidKeyException, NoSuchAlgorithmException, IOException, InterruptedException { // Java 11 から正式導入された HTTP Client API を使う // HTTP リクエスト情報を構築 HttpRequest.Builder reqBuilder = HttpRequest.newBuilder() .uri(new URI("https://" + HOST + path)) // API コール用の URL .POST(HttpRequest.BodyPublishers.ofString(reqJson)) // API コールのパラメータをセット .timeout(Duration.ofSeconds(10)); // 署名情報を付加したヘッダ情報を取得 AwsSignature4 awsv4Auth = new AwsSignature4(accessKey, secretKey, path, REGION, HOST, target); Map<String, String> signedHeaders = awsv4Auth.getHeaders(reqJson); // リクエスト情報にヘッダをセット signedHeaders.remove("host"); // Host ヘッダは付加しない (jdk.httpclient.allowRestrictedHeaders) for (Map.Entry<String, String> entrySet : signedHeaders.entrySet()) { reqBuilder.header(entrySet.getKey(), entrySet.getValue()); } // HTTP リクエスト情報を生成 HttpRequest req = reqBuilder.build(); // API をコールして結果を取得 HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .connectTimeout(Duration.ofSeconds(10)) .build(); HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString()); // ステータスコードで成功・失敗を判断 if (res.statusCode() == 200) { return res.body(); } else { throw new RuntimeException(res.body()); } } }src/main/java/AwsSignature4.java
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.TimeZone; import java.util.TreeMap; /** * AWS 署名バージョン 4 (AWS Signature Version 4) */ public class AwsSignature4 { private static final String SERVICE = "ProductAdvertisingAPI"; // PA-API private static final String HMAC_ALGORITHM = "AWS4-HMAC-SHA256"; private static final String AWS_4_REQUEST = "aws4_request"; private final String awsAccessKey; private final String awsSecretKey; private final String path; private final String region; private final String host; private final String target; /** * コンストラクタ。 * @param awsAccessKey アクセスキー * @param awsSecretKey シークレットキー * @param path API エントリポイントのパス * @param region リージョン * @param host API エントリポイントのパス * @param target リクエストの送信先サービスおよびデータのオペレーション */ public AwsSignature4( String awsAccessKey, String awsSecretKey, String path, String region, String host, String target) { this.awsAccessKey = awsAccessKey; this.awsSecretKey = awsSecretKey; this.path = path; this.region = region; this.host = host; this.target = target; } /** * 認証用のヘッダ情報を返す。 * @param payload リクエスト情報 JSON * @return 認証用のヘッダ情報 * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ public Map<String, String> getHeaders(String payload) throws NoSuchAlgorithmException, InvalidKeyException { // ベースになるヘッダ TreeMap<String, String> headers = new TreeMap<>(); headers.put("host", host); headers.put("content-type", "application/json; charset=utf-8"); headers.put("content-encoding", "amz-1.0"); headers.put("x-amz-target", target); // 署名を作成するときに使用されるタイムスタンプ final Date date = new Date(); headers.put("x-amz-date", getXAmzDateString(date)); // 署名付きヘッダー (signed headers) String signedHeaders = createSignedHeaders(headers); // 正規リクエスト (canonical request) String canonicalRequest = createCanonicalRequest(path, headers, signedHeaders, payload); // 署名文字列 (string to sign) String stringToSign = createStringToSign(date, region, canonicalRequest); // 署名 (signature) String signature = calculateSignature(awsSecretKey, date, region, stringToSign); // Authorization ヘッダー値 String authorization = buildAuthorizationString(awsAccessKey, region, signature, signedHeaders, date); headers.put("Authorization", authorization); return headers; } // 署名付きヘッダー (signed headers) private static String createSignedHeaders(TreeMap<String, String> headers) { StringBuilder signedHeaderBuilder = new StringBuilder(); for (String key : headers.keySet()) { signedHeaderBuilder.append(key).append(";"); } return signedHeaderBuilder.substring(0, signedHeaderBuilder.length() - 1); } // 正規リクエスト (canonical request) private static String createCanonicalRequest(String path, TreeMap<String, String> headers, String signedHeaders, String payload) throws NoSuchAlgorithmException { StringBuilder canonicalRequest = new StringBuilder(); canonicalRequest.append("POST").append("\n"); canonicalRequest.append(path).append("\n").append("\n"); for (String key : headers.keySet()) { canonicalRequest.append(key).append(":").append(headers.get(key)).append("\n"); } canonicalRequest.append("\n"); canonicalRequest.append(signedHeaders).append("\n"); canonicalRequest.append(sha256(payload)); return canonicalRequest.toString(); } // 署名文字列 (string to sign) private static String createStringToSign(Date current, String region, String canonicalRequest) throws NoSuchAlgorithmException { return HMAC_ALGORITHM + "\n" + getXAmzDateString(current) + "\n" + getYMDString(current) + "/" + region + "/" + SERVICE + "/" + AWS_4_REQUEST + "\n" + sha256(canonicalRequest); } // 署名 (signature) private static String calculateSignature(String awsSecretKey, Date current, String region, String stringToSign) throws InvalidKeyException, NoSuchAlgorithmException { final String currentDate = getYMDString(current); byte[] signatureKey = getSigningKey(awsSecretKey, currentDate, region); byte[] signature = hmacSha256(signatureKey, stringToSign); return bytesToHex(signature); } // 署名キー (signing key) private static byte[] getSigningKey(String key, String date, String region) throws InvalidKeyException, NoSuchAlgorithmException { // 各ハッシュ関数の結果が次のハッシュ関数の入力になる byte[] kSecret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8); byte[] kDate = hmacSha256(kSecret, date); byte[] kRegion = hmacSha256(kDate, region); byte[] kService = hmacSha256(kRegion, SERVICE); byte[] kSigning = hmacSha256(kService, AWS_4_REQUEST); return kSigning; } // Authorization ヘッダー値 private static String buildAuthorizationString(String awsAccessKey, String region, String signature, String signedHeaders, Date current) { return HMAC_ALGORITHM + " " + "Credential=" + awsAccessKey + "/" + getYMDString(current) + "/" + region + "/" + SERVICE + "/" + AWS_4_REQUEST + "," + "SignedHeaders=" + signedHeaders + "," + "Signature=" + signature; } // ハッシュ関数 SHA-256 private static String sha256(String data) throws NoSuchAlgorithmException { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(data.getBytes(StandardCharsets.UTF_8)); byte[] digest = messageDigest.digest(); return String.format("%064x", new java.math.BigInteger(1, digest)); } // HMAC-SHA256 関数 private static byte[] hmacSha256(byte[] key, String data) throws NoSuchAlgorithmException, InvalidKeyException { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); } // バイナリ値を 16 進数表現に変換 private static String bytesToHex(byte[] data) { final char[] hexCode = "0123456789ABCDEF".toCharArray(); StringBuilder r = new StringBuilder(data.length * 2); for (byte b : data) { r.append(hexCode[(b >> 4) & 0xF]); r.append(hexCode[(b & 0xF)]); } return r.toString().toLowerCase(); } // x-amz-date ヘッダ用日時文字列 (UTC で YYYYMMDD'T'HHMMSS'Z' の ISO 8601 形式) private static String getXAmzDateString(Date date) { DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); // ISO 8601 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat.format(date); } // 日付文字列 yyyyMMdd 形式 private static String getYMDString(Date date) { DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat.format(date); } }src/main/java/JsonUtil.java
// 外部ライブラリの Jackson を使う import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.StringWriter; import java.util.Map; /** * JSON 操作クラス。 */ public class JsonUtil { /** * オブジェクトから JSON 文字列を生成する。 * @param obj オブジェクト * @return JSON 文字列 * @throws IOException */ public String objectToJson(Map<String, Object> obj) throws IOException { StringWriter out = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); // ASCII 文字以外は Unicode escape する mapper.configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), true); mapper.writerWithDefaultPrettyPrinter().writeValue(out, obj); return out.toString(); } /** * JSON を読みやすい形にする。 * @param json JSON 文字列 * @return 読みやすい形になった JSON 文字列 * @throws IOException */ public String prettyPrint(String json) throws IOException { StringWriter out = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.writerWithDefaultPrettyPrinter().writeValue(out, mapper.readValue(json, Map.class)); return out.toString(); } }サンプルコードの実行結果
実行環境: macOS Catalina + Java 15 (AdoptOpenJDK 15) + Gradle 6.6.1
$ gradle run Starting a Gradle Daemon (subsequent builds will be faster) > Task :run ===== キーワードから商品を検索: リクエスト ===== { "PartnerType" : "Associates", "PartnerTag" : "XXXXX-22", "Keywords" : "\u30B7\u30A7\u30A4\u30AF\u30B9\u30D4\u30A2", "SearchIndex" : "All", "ItemCount" : 3, "Resources" : [ "ItemInfo.Title", "ItemInfo.ByLineInfo", "ItemInfo.ProductInfo", "ItemInfo.ProductInfo", "Images.Primary.Large", "Images.Primary.Medium", "Images.Primary.Small" ] } ===== キーワードから商品を検索: レスポンス ===== { "SearchResult" : { "Items" : [ { "ASIN" : "B06ZZH149Y", "DetailPageURL" : "https://www.amazon.co.jp/dp/B06ZZH149Y?tag=XXXXX-22&linkCode=osi&th=1&psc=1", "Images" : { "Primary" : { "Large" : { "Height" : 500, "URL" : "https://m.media-amazon.com/images/I/41Ms+C0NwNL.jpg", "Width" : 311 }, "Medium" : { "Height" : 160, "URL" : "https://m.media-amazon.com/images/I/41Ms+C0NwNL._SL160_.jpg", "Width" : 100 }, "Small" : { "Height" : 75, "URL" : "https://m.media-amazon.com/images/I/41Ms+C0NwNL._SL75_.jpg", "Width" : 47 } } }, "ItemInfo" : { "ByLineInfo" : { "Contributors" : [ { "Locale" : "ja_JP", "Name" : "河合祥一郎", "Role" : "著", "RoleType" : "author" } ], "Manufacturer" : { "DisplayValue" : "祥伝社", "Label" : "Manufacturer", "Locale" : "ja_JP" } }, "ProductInfo" : { "IsAdultProduct" : { "DisplayValue" : false, "Label" : "IsAdultProduct", "Locale" : "en_US" }, "ReleaseDate" : { "DisplayValue" : "2017-04-21T00:00:00.000Z", "Label" : "ReleaseDate", "Locale" : "en_US" } }, "Title" : { "DisplayValue" : "あらすじで読むシェイクスピア全作品 (祥伝社新書)", "Label" : "Title", "Locale" : "ja_JP" } } }, { "ASIN" : "B015BY1Q6Q", "DetailPageURL" : "https://www.amazon.co.jp/dp/B015BY1Q6Q?tag=XXXXX-22&linkCode=osi&th=1&psc=1", "Images" : { "Primary" : { "Large" : { "Height" : 500, "URL" : "https://m.media-amazon.com/images/I/516XD+o35gL.jpg", "Width" : 375 }, "Medium" : { "Height" : 160, "URL" : "https://m.media-amazon.com/images/I/516XD+o35gL._SL160_.jpg", "Width" : 120 }, "Small" : { "Height" : 75, "URL" : "https://m.media-amazon.com/images/I/516XD+o35gL._SL75_.jpg", "Width" : 56 } } }, "ItemInfo" : { "ByLineInfo" : { "Contributors" : [ { "Locale" : "ja_JP", "Name" : "メル・ギブソン", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "グレン・クローズ", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "アラン・ベイツ", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ポール・スコフィールド", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "フランコ・ゼフィレッリ", "Role" : "監督", "RoleType" : "director" }, { "Locale" : "ja_JP", "Name" : "クリストファー・デヴォア", "Role" : "Writer", "RoleType" : "writer" } ] }, "ProductInfo" : { "IsAdultProduct" : { "DisplayValue" : false, "Label" : "IsAdultProduct", "Locale" : "en_US" }, "ReleaseDate" : { "DisplayValue" : "2015-09-16T00:00:00.000Z", "Label" : "ReleaseDate", "Locale" : "en_US" } }, "Title" : { "DisplayValue" : "ハムレット(字幕版)", "Label" : "Title", "Locale" : "ja_JP" } } }, { "ASIN" : "B07WPXRT5W", "DetailPageURL" : "https://www.amazon.co.jp/dp/B07WPXRT5W?tag=XXXXX-22&linkCode=osi&th=1&psc=1", "Images" : { "Primary" : { "Large" : { "Height" : 375, "URL" : "https://m.media-amazon.com/images/I/51CnJBKwu5L.jpg", "Width" : 500 }, "Medium" : { "Height" : 120, "URL" : "https://m.media-amazon.com/images/I/51CnJBKwu5L._SL160_.jpg", "Width" : 160 }, "Small" : { "Height" : 56, "URL" : "https://m.media-amazon.com/images/I/51CnJBKwu5L._SL75_.jpg", "Width" : 75 } } }, "ItemInfo" : { "ByLineInfo" : { "Contributors" : [ { "Locale" : "ja_JP", "Name" : "マーク・ベントン", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ジョー・ジョイナー", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "アンバー・アガ", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "リチャード・サイニー", "Role" : "監督", "RoleType" : "director" }, { "Locale" : "ja_JP", "Name" : "イアン・バーバー", "Role" : "監督", "RoleType" : "director" }, { "Locale" : "ja_JP", "Name" : "ウィル・トロッター", "Role" : "プロデュース", "RoleType" : "producer" } ] }, "ProductInfo" : { "IsAdultProduct" : { "DisplayValue" : false, "Label" : "IsAdultProduct", "Locale" : "en_US" } }, "Title" : { "DisplayValue" : "第1話", "Label" : "Title", "Locale" : "ja_JP" } } } ], "SearchURL" : "https://www.amazon.co.jp/s?k=%E3%82%B7%E3%82%A7%E3%82%A4%E3%82%AF%E3%82%B9%E3%83%94%E3%82%A2&rh=p_n_availability%3A-1&tag=XXXXX-22&linkCode=osi", "TotalResultCount" : 146 } } ===== ASIN から商品情報を取得: リクエスト ===== { "PartnerType" : "Associates", "PartnerTag" : "XXXXX-22", "Resources" : [ "ItemInfo.Title", "ItemInfo.ByLineInfo" ], "ItemIds" : [ "4391641585", "B010EB1HR4", "B0125SPF90", "B07V52KSGT" ] } ===== ASIN から商品情報を取得: レスポンス ===== { "ItemsResult" : { "Items" : [ { "ASIN" : "4391641585", "DetailPageURL" : "https://www.amazon.co.jp/dp/4391641585?tag=XXXXX-22&linkCode=ogi&th=1&psc=1", "ItemInfo" : { "ByLineInfo" : { "Contributors" : [ { "Locale" : "ja_JP", "Name" : "サンエックス", "Role" : "監修", "RoleType" : "consultant_editor" }, { "Locale" : "ja_JP", "Name" : "主婦と生活社", "Role" : "編集", "RoleType" : "editor" } ], "Manufacturer" : { "DisplayValue" : "主婦と生活社", "Label" : "Manufacturer", "Locale" : "ja_JP" } }, "Title" : { "DisplayValue" : "すみっコぐらし検定公式ガイドブック すみっコぐらし大図鑑 (生活シリーズ)", "Label" : "Title", "Locale" : "ja_JP" } } }, { "ASIN" : "B010EB1HR4", "DetailPageURL" : "https://www.amazon.co.jp/dp/B010EB1HR4?tag=XXXXX-22&linkCode=ogi&th=1&psc=1", "ItemInfo" : { "ByLineInfo" : { "Brand" : { "DisplayValue" : "Hostess Entertainmen", "Label" : "Brand", "Locale" : "ja_JP" }, "Contributors" : [ { "Locale" : "ja_JP", "Name" : "ハロウィン", "Role" : "アーティスト", "RoleType" : "artist" } ], "Manufacturer" : { "DisplayValue" : "ホステス", "Label" : "Manufacturer", "Locale" : "ja_JP" } }, "Title" : { "DisplayValue" : "守護神伝 第二章 <エクスパンデッド・エディション>(リマスター)", "Label" : "Title", "Locale" : "ja_JP" } } }, { "ASIN" : "B0125SPF90", "DetailPageURL" : "https://www.amazon.co.jp/dp/B0125SPF90?tag=XXXXX-22&linkCode=ogi&th=1&psc=1", "ItemInfo" : { "ByLineInfo" : { "Brand" : { "DisplayValue" : "ルービーズジャパン(RUBIE'S JAPAN)", "Label" : "Brand", "Locale" : "ja_JP" }, "Manufacturer" : { "DisplayValue" : "ルービーズジャパン(RUBIE'S JAPAN)", "Label" : "Manufacturer", "Locale" : "ja_JP" } }, "Title" : { "DisplayValue" : "ハロウィン ロッキング パンプキン ホームデコレーション用小物 H 130cm", "Label" : "Title", "Locale" : "ja_JP" } } }, { "ASIN" : "B07V52KSGT", "DetailPageURL" : "https://www.amazon.co.jp/dp/B07V52KSGT?tag=XXXXX-22&linkCode=ogi&th=1&psc=1", "ItemInfo" : { "ByLineInfo" : { "Contributors" : [ { "Locale" : "ja_JP", "Name" : "リアン・リース", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ジェイミー・リー・カーティス", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ウィル・パットン", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ジュディ・グリア", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ヴァージニア・ガードナー", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ジェファーソン・ホール", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "アンディ・マティチャック", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ニック・キャッスル", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ジェームス・ジュード・コートニー", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "ハルク・ビルギナー", "Role" : "出演", "RoleType" : "actor" }, { "Locale" : "ja_JP", "Name" : "デヴィッド・ゴードン・グリーン", "Role" : "監督", "RoleType" : "director" }, { "Locale" : "ja_JP", "Name" : "デヴィッド・ゴードン・グリーン", "Role" : "Writer", "RoleType" : "writer" }, { "Locale" : "ja_JP", "Name" : "ダニー・マクブライド", "Role" : "Writer", "RoleType" : "writer" }, { "Locale" : "ja_JP", "Name" : "ジェフ・フラッドリー", "Role" : "Writer", "RoleType" : "writer" }, { "Locale" : "ja_JP", "Name" : "マレク・アカッド", "Role" : "プロデュース", "RoleType" : "producer" }, { "Locale" : "ja_JP", "Name" : "ジェイソン・ブラム", "Role" : "プロデュース", "RoleType" : "producer" }, { "Locale" : "ja_JP", "Name" : "ビル・ブロック", "Role" : "プロデュース", "RoleType" : "producer" } ] }, "Title" : { "DisplayValue" : "ハロウィン (字幕版)", "Label" : "Title", "Locale" : "ja_JP" } } } ] } } BUILD SUCCESSFUL in 13s 2 actionable tasks: 2 executed参考資料
- 投稿日:2020-09-25T19:06:34+09:00
AWS IAMでユーザーを作成しよう
AWSのアカウントを作ろうでアカウント作成するとルートアカウントだけ作成されます。ここでは、作成したAWSアカウントのIAMでユーザーを作成してみましょう。
IAMユーザーをなぜ作成するのか
別な記事にも書きましたが、ルートアカウントは、Administrator権限を有する非常に強力なアカウントであるため、開発や運用時には制限したIAMユーザーアカウントで操作することが推奨されます。
誤操作することを防ぐ目的があり、また、操作する人やプログラムへユーザーやロールを割り当てることで誰が操作をおこなったのか?という証跡を残す目的もあります。
(と書いたけど、少し雑な言い方かもしれない。あとで書き直す)この記事で行うこと
AWSのIAMユーザーを追加する。
事前に決めておくこと
AWSでは、ユーザーへ様々な権限を付与することができます。そのため、ユーザーへ付与する権限を事前に決めておく必要があります。
権限付与でオススメする方法
IAMでグループを作成し、作成したグループに必要な権限を付与しておきます。
本記事の手順で権限を付与する部分がありますが、その時にグループを指定する方法をおすすめします。IAMユーザーを追加する手順
- AWSコンソールへログイン
IAM
へアクセスし、ユーザー追加
をする- ユーザーのIDや詳細情報を登録する
- ユーザーへ権限を設定する
AWSコンソールへログイン
ルートアカウントへログインし、AWSコンソールを表示します
IAM
へアクセスし、ユーザー追加
をするユーザーのIDや詳細情報を登録する
まずはユーザ名を入力します。これは、ログインする際に利用するユーザーIDになります。そして、アクセスの種類を選択します。
・プログラムによるアクセス:
コマンドラインやAPIなどプログラムからアクセスを許す場合にはチェックを付けましょう。
アクセスキーとシークレットアクセスキーが払い出されます。
・AWSマネジメントコンソール:
ルートアカウントと同じく、AWSコンソールを使用する場合にはチェックを付けましょう。
AWSコンソールへアクセスするためのパスワードが払い出されます。2つのチェックボックスは、ユーザーを作成した後でも有効化することが可能です。
ユーザーへ権限を設定する
作成するユーザーへ権限を設定してください。
グループを割り当てるか、それとも既存のポリシーを直接選択することも可能です。タグを追加する
AWSでは作成したリソースへタグをつけて管理することができます。
作成されるユーザーの確認
作成されたユーザーへの認証情報
ユーザー作成が成功した場合、ユーザーへアクセスするための認証情報が表示されます。
パスワードやシークレットアクセスキーは、本画面でしか表示されないため、コピペなどで保持するようにしてください。補足
認証情報を紛失してしまってもルートアカウントにてIAMユーザーの認証情報を再発行することができます。
一覧で作成されたIAMユーザーを確認
IAMユーザーの一覧で、作成されたユーザーを確認できます。
また、この画面ではアクセスキーやパスワードがいつ設定されたのかを確認することができ、それらの認証情報がいつ使われたのかという最後のアクティビティ
も確認することが可能です。AWS公式ドキュメント
- 投稿日:2020-09-25T18:39:48+09:00
Pythonで天気予報botもどきを作ってみた。
Pythonで天気予報botもどきを作ってみた。
タイトル通りPythonで天気予報botもどきを作ってみました(botではないです)。
天気予報を確認することすらめんどくさがってしまう性格で「LINEで送れたらなー」と思っていた所、既に先人の方達がやっていたので、知恵を借りながら(ほぼパクリながら)作ってみました。やったこと
・スクレイピングでYahooの天気情報を取得
・スクレイピングで取得した情報をLINE Notifyで表示準備
必要なライブラリのインストール
$pip install beautifulsoup4 $pip install requestsトークンの取得
LINENotifyでトークンを発行しておきます。
コード
import urllib.request import requests from bs4 import BeautifulSoup line_notify_token = 'xxxxxxxxxxxxxxxxxxxxx'#発行したトークンを使います。 line_notify_api = 'https://notify-api.line.me/api/notify' rssurl = "https://rss-weather.yahoo.co.jp/rss/days/3410.xml"#このコードでは仙台の天気情報を取得します。 URL = "https://weather.yahoo.co.jp/weather/jp/8/3410/8201.html" tenki = [] detail = [] def Parser(rssurl): with urllib.request.urlopen(rssurl) as res: xml = res.read() soup = BeautifulSoup(xml, "html.parser") for item in soup.find_all("item"): title = item.find("title").string description = item.find("description").string if title.find("[ PR ]") == -1: tenki.append(title) detail.append(description) def Otenki(): Parser(rssurl) for i in range(0,2): message = tenki[i] payload = {'message': "\n" + message} headers = {'Authorization': 'Bearer ' + line_notify_token} line_notify = requests.post(line_notify_api, data=payload, headers=headers) Otenki()実行結果
感想
本当はAWS,Herokuを使って自動化までやりたかったですが、知識が何も無い状態で飛び込んで高額請求がきたら対処出来ないので、今回はここまでにしておきました笑。
scheduleライブラリをインストールしてみても良いのかも知れません。
自分で動かしてみると分からないなりにも色々出来て楽しかったです。またスキルを身に付けていきながらこの記事も更新していきたいです。
参考記事
【Yahoo!天気リプレース版】LINE Notify + Pythonで天気情報を取得する方法
Pythonで天気予報をLINE通知する
- 投稿日:2020-09-25T17:53:31+09:00
Pythonを使って天気予報botもどきを作ってみた。
Pythonを使って天気予報botもどきを作ってみた。
タイトル通りPythonを使ってLineで天気予報のお知らせがくるbotもどきを作ってみました。
天気予報を見ることすら面倒なめんどくさがり屋なので、「何とか出来ないかなー」と考えていたら先人の方達が似たようなものを作っていたので99%パクリで作ってみました。行ったこと
・Yahooの天気予報で天気に関する情報をスクレイピングで取得する
・スクレイピングで取得した情報をLine Notifyで表示する準備
必要なライブラリのインストール
$pip install beautifulsoup4 $pip install requestsLineNotifyのトークン発行
LineNotifyでトークンを発行して以下のコードで貼り付けてください。
コード
import urllib.request import requests from bs4 import BeautifulSoup line_notify_token = 'xxxxxxxxxxxxxxxxxxx'#自分で発行したトークンを貼り付けてください. line_notify_api = 'https://notify-api.line.me/api/notify' rssurl = "https://rss-weather.yahoo.co.jp/rss/days/3410.xml"#デフォルトでは仙台市の天気情報になっています. URL = "https://weather.yahoo.co.jp/weather/jp/8/3410/8201.html" tenki = [] detail = [] def Parser(rssurl): with urllib.request.urlopen(rssurl) as res: xml = res.read() soup = BeautifulSoup(xml, "html.parser") for item in soup.find_all("item"): title = item.find("title").string description = item.find("description").string if title.find("[ PR ]") == -1: tenki.append(title) detail.append(description) def Otenki(): Parser(rssurl) for i in range(0,2): message = tenki[i] payload = {'message': "\n" + message} headers = {'Authorization': 'Bearer ' + line_notify_token} line_notify = requests.post(line_notify_api, data=payload, headers=headers) Otenki()実行結果
感想
先人の方達のおかげでほとんど詰まることもなく作成することが出来ました。
本当はAWSやHerokuを使って自動化までしたかったですが、全く知識がない状態で飛び込んで高額請求とか来たら対処出来そうもないので、とりあえずここまでにしておきました。
scheduleライブラリをインストールしてやってみても面白いかも知れません。
初めてアプリもどきを作ってみたのですが、出来は良くないし、完全に自己満足なのですが、Lineに通知が来た時、結構嬉しかったです。
スキルが付いてきたらまた記事も改良していこうと思います。参考記事
【Yahoo!天気リプレース版】LINE Notify + Pythonで天気情報を取得する方法
Pythonで天気予報をLINE通知する
- 投稿日:2020-09-25T17:51:27+09:00
【Athena】既存データをPartition分割する
今使っているデータをPartition分割したい
Athenaで利用しているデータが拡大していき、費用削減・速度向上を狙ってPartitionでデータを分割したくなることがあると思います。
初期からデータを分けていた場合は問題がないのですが、何も考えていなかった場合、1ファイルまたは1ディレクトリ(S3にディレクトリはありませんが便宜上ディレクトリと表現します)に全てのデータが入っていることがあります。
この際、CTAS(CREATE TABLE AS SELECT)クエリで分割、というかデータを再構築します。分割例
少し長いですが以下のようなデータを用意します。
id name team_id year 1 佐藤 2 2019 2 高橋 2 2019 3 近藤 3 2019 4 田中 1 2019 5 山田 2 2019 6 野村 4 2019 7 河合 2 2019 8 五十嵐 1 2019 9 川端 4 2019 10 比屋根 3 2019 11 上田 2 2020 12 村上 4 2020 13 星 5 2020 14 歳内 3 2020 15 西浦 2 2020 16 西田 1 2020 17 中村 4 2020 18 畠山 5 2020 19 真中 3 2020 20 高津 2 2020 このデータを元にAthenaテーブルを作成します。
create.sqlCREATE EXTERNAL TABLE IF NOT EXISTS db_name.partition_test ( `id` int, `name` string, `team_id` int, `year` int ) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe' WITH SERDEPROPERTIES ( 'serialization.format' = ',', 'field.delim' = ',' ) LOCATION 's3://example_bucket/partition_test/original/' TBLPROPERTIES ('has_encrypted_data'='false');これはCSVファイルをただ読み取るだけのcreate文です。
現在はs3://example_bucket/partition_test/original/
ディレクトリに全テータを入れています。このファイルを分割するCTAS文が
ctas.sqlCREATE TABLE db_name.partitioned_test WITH ( format = 'PARQUET', external_location = 's3://example_bucket/partition_test/partitioned/', partitioned_by = ARRAY['team_id', 'year'] ) AS SELECT id, name, team_id, year FROM partition_test;これを実行することでlsコマンドでディレクトリ構造を見てみると…
$ aws s3 ls s3://example_bucket/partition_test/partitioned/ PRE team_id=1/ PRE team_id=2/ PRE team_id=3/ PRE team_id=4/ PRE team_id=5/$ aws s3 ls s3://example_bucket/partition_test/partitioned/team_id=1/ PRE year=2019/ PRE year=2020/このように
team_id
とyear
でデータが分割されています。これでwhere句でteam_id
かyear
を絞るとアクセスするファイルを減らせるため速度・費用の節約を図ることができます。しかし、今回のようにデータ量が少ない場合は、where句で条件を指定しない場合逆にS3へのリクエスト量が増えるため費用は上がってしまいます。
Athenaはどのような読み取り量も最低でも10MBとなる点、S3の費用も考えてデータを構成する必要があります。
ファイルサイズの最適化でも書きましたが、1ファイルサイズは128MB程度になるのが一番オーバーヘッドが少なくなります。もちろんこのような調整は簡単でないことも多いのですが、気に留めておく必要あります。エラー
HIVE_COLUMN_ORDER_MISMATCH: Partition keys must be the last columns in the table and in the same order as the table properties: [team_id, year]. You may need to manually clean the data at locationCTAS文を実行した際にこのようなエラーが出る場合があります。これは読んでそのままなのですが、パーティションキーに指定したものはselect文の末尾に順番通り並べる必要があります。
なので今回の例でいうとSELECT id, name, year, team_id FROM partition_test;だと上記エラーが出ます。エラー文読めば問題なく対応できるのですが、直感的にこのようなエラーが出ることは予想できないので注意が必要です。
- 投稿日:2020-09-25T17:31:30+09:00
AWSのEC2でrootになれなくなった場合の対処方法
ubuntuユーザーのsudoers設定を変更してしまい、スーパーユーザーになれなくなった場合の対処法
- AWSマネジメントコンソールにログインする
- EC2を開く
- インスタンスを選択する
- インスタンスを停止する(停止時に削除されないように注意)
- 「アクションメニュー」の「インスタンスの設定」→「ユーザーデータの表示/変更」を選ぶ
- ユーザーデータに下記を記入
cloud-config
bootcmd:
- echo "ubuntu ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers
7. 「保存」を押した後、インスタンスを起動する
8. ubuntuユーザーで、パスワードなしで「sudo su -」が出来るか確認
9. 出来ることが確認出来たら、インスタンスを停止し、ユーザーデータに記述したものを削除
10. インスタンスを起動以上。
- 投稿日:2020-09-25T17:27:23+09:00
AWS QuickSightに関して
AWS QuickSightとは?
AWS QuickSightとはサーバーレスでフルマネージドなBIサービスになります。S3にあるデータはもちろんのことRDS,Athena,Redshift、オンプレミスにあるJDBC接続(JDBC:javaで書かれたプログラムとデータベースの仲立ちをするもの)可能なデータベースのデータなどと連携して可視化を行うことが出来る。SPICEというインメモリ型の高速データベースが内蔵されており、そこにデータを取り込んでおくとデータソースに負荷をかけることなく高速なクエリが可能になる。
QuickSightを実際に使用してみた。
まずはCSVファイルをアップロードします。新しい分析というところをクリックし「新しいデータセット」→「ファイルのアップロード」を選択し分析したいデータをアップロードします。
そうすると下図のような画面になるので問題がなければ「保存して視覚化」を押します。ここではカラム名を変更したり計算フィールドを使用した変更を行ったりすることが出来ます。
次に可視化を行っていきたいと思います。画面の左側にあるフィールドリストには読み込んだデータのカラムがあります。それを使用して可視化を行っていきます。デフォルトで「AutoGraph」となっているため、カラムを選択するだけで自動的にビジュアルタイプを選択してくれます。左上にある「追加(+)」のところで「ビジュアルを追加」というところを選択すると複数のビジュアルを選択することが可能です。
先ほどは「AutoGraph」による自動的な可視化でしたが、自分自身でビジュアルのタイプを選んで可視化したい場合はどのように行うとよいでしょうか?それはビジュアルタイプを自分で選択すれば可能になります。左下にビジュアルタイプがあるので可視化したいタイプを選択します(今回はピボットテーブル)。その次に使用したいフィールドを選択しフィールドウェルの「行」「列」「値」にそれぞれドラッグします。このように簡単にピボットテーブルを作成することが出来ます。
続いてフィルターを使用してみたいと思います。フィルターを使用する事で表示する範囲を絞れたり、特定のものを抽出したりすることが出来ます。フィルターを使用するには左側にあるフィルターを選択します。今回はオーダー日にフィルターをかけたいのでオーダー日を選択し開始日と終了日の設定を行います。そうすると指定した範囲のみの表示を行うことが出来ます。またパラメータを使用した分析を行いたい場合は「パラメータを使用」にチェックすることで使用できます。パラメータの作成は左側にあるパラメータというところでパラメータを作成することによって使用する事が出来ます。
売上への寄与度の分析を行います。売上への寄与度を分析する際には、棒グラフをクリックし、「売上への寄与度を分析」を選択します。そうすることによって左側に分析結果が表示されます。結果を見てみると大阪府が売り上げの増加に一番貢献したということが分かります。
続いて機械学習による予測を行っていきたいと思います。今回は売り上げの予測をしていきます。売り上げの予測も難しいわけではなくクリックだけで実装することが出来ます。「ビジュアルタイプ」に折れ線グラフを選択し可視化しました。その後「…」を押し「予測の追加」を押します。そうするとオレンジ色の予測結果が可視化されます。見方としては、濃いオレンジの実践を中心に薄いオレンジの幅で売上が推移すると予測しています。
このように可視化をするのであればQuickSightは有用であると思います。しかしながら従量課金制であるため、料金には注意が必要になってくると思います。
<参考にさせていただいたサイト>
JDBCとは
AWSではじめるデータレイク: クラウドによる統合型データリポジトリ構築入門
- 投稿日:2020-09-25T15:56:28+09:00
EBSのファーストタッチペナルティをくらったときに反省したこと
小ネタ。
久々にファーストタッチペナルティをくらって反省したので、
ポストモーテム的なものをまとめます。
出せる部分だけ。発生事象
概要
ファーストタッチペナルティによるレスポンスの遅延及びエラーの発生
ELBの平均レスポンスタイムとパーセンタイルのグラフ。
紫の線がレスポンスタイム。
ほとんどのリクエストのレスポンスタイムが悪化している。詳細
- あるwebサーバのネットワークの帯域が不足し始めたので、スケールアウトすることで対処しようとした。
- ディスクの高IOが必要で、オートスケールは設定していないサーバだった。
- EC2で作成されており、定期的に取得しているAMIから復元しようとしていた。
- EBSに800GB程度のデータを保持していた。
- AMIからインスタンス構築し、追加した台数分ロードバランサの配下に登録した。
- 登録したインスタンスにバランシングされだしたタイミングからレスポンスタイム及びエラー率が上昇した。
- 登録したインスタンスのログからエラー等は未検出だった。
- 調査したところディスクのIOPSが既存環境の1/7程度の性能となっていた。
- ロードバランサから切り離し回復した。
原因
直接原因
- ファーストタッチペナルティにより、ファイル読み込みに遅延、結果レスポンスが遅延した
正常時のCPUの状態
※緑色の部分がiowait、それ以外はsystemやuser、stealなど。
問題発生当時のCPUの状態
※system,userなども本来含まれているが、iowaitが占めていて他の値が見えないほどになっている
対策が必要そうな項目
- ファーストタッチペナルティが起こることを認識できていなかった
- 事前の動作確認でIOの性能の確認は含まれていなかった
- 復旧手順を参考に復旧するも、実際に復旧させたときに発覚するたぐいの問題だった
対応
grep hoge -r /hoge
で、既存ファイル読み込むことでブロックにアクセスした。※公式はddやfioをお勧めしてる- IOPSが正常時動作と同等程度出ることを確認した。
- 再度ロードバランサに接続し問題ないことを確認した。
再発防止策
- AMIからの復旧が必要な場合
- Fast snapshot restoreを利用する
- https://aws.amazon.com/jp/blogs/news/new-amazon-ebs-fast-snapshot-restore-fsr/
- 高IOが必要なディスクに対して
- スナップショットから復元するような運用をやめる
- EBSを作り直しデータを他からダウンロードするなどの仕組みにする
- 作成時にディスクを確保(新規作成)するようにする
見送った再発防止策
- 復旧訓練
- 一回同じことをやったことがあり、その際は運よく問題にならなかった
- 定期的な運用見直しで、定期的にファーストタッチペナルティのことを思い出すよう訓練を行うことは、人員と工数とコストの兼ね合いから難しい
- 高IOの復元だけ必ずFSRを利用するよう自動化する
- こういう自動化やアラートがないとFSRを使うというノウハウが忘れ去られ再発するリスクは免れない
- 同じような条件をどう判定するか、判定にかかる部分の自動化が困難と思われる
感想
再発防止策と書きましたが、防止策というより、再発したときに何をやるのかという内容で、反省としてはあまり良くないかもしれません。
何か良さそうな防止策があれば教えていただきたいところです。そもそもファーストタッチペナルティがどういう時に起きるか知ってても、実際の事象と結び付けられないのが良くないです。
こういうことが起きると気をつけなきゃってなるけど、しばらくしたら忘れると思うのであまり意味がないです。
起きないために気づく方法は現状テストしかないけど、テストの観点にも無ければテストもできない。
テストの観点に含めるためには経験を積まないといけないしでデッドロック。
どうしたらいいんだろ・・・?一律リリース時にディスクパフォーマンスを計測する?
みんなしてるのかなあ。
- 投稿日:2020-09-25T14:00:43+09:00
AWSルートアカウントへMFAを設定する
AWSのアカウントを作ろうで作成したルートアカウントへMFAを設定します。
なぜMFAをおこなうのか
ルートアカウントは、Administrator権限を有する非常に強力なアカウントなので、セキュリティを向上させるためにMFAを設定することが推奨されています。
ここで行うこと
AWSのルートアカウントの多要素認証へ
仮想MFAデバイス
を設定する。MFAを設定する手順
- AWSコンソールへログイン
マイセキュリティ資格情報
へアクセスセキュリティ認証情報
で多要素認証(MFA)を有効化する- 設定するMFAデバイスを選択する
仮想MFAデバイスの設定
をおこなうセキュリティ認証情報
で多要素認証(MFA)が有効化されたことを確認するAWSコンソールへログイン
ルートアカウントへログインし、AWSコンソールを表示します
マイセキュリティ資格情報
へアクセス右上のメニューバーの中にある
マイセキュリティ資格情報
からセキュリティ認証情報
へアクセスします
セキュリティ認証情報
で多要素認証(MFA)を有効化する多要素認証(MFA)のセクションを開き、
MFAの有効化
をクリックします設定するMFAデバイスを選択する
MFAデバイスをここでは
仮想MFAデバイス
を選択します。補足
ハードウェアMFAデバイス
を指定することも可能ですが、AWSがサポートされているMFAデバイスが必要になりますので、購入する際にはご注意ください。
仮想MFAデバイスの設定
をおこなうこの画面へ表示されたQRコードもしくはシークレッキーを
仮想MFAデバイス
となるアプリケーションへ入力してMFAコードを入手しましょう。MFAコードが2つあり、ここには時間差で2つのコードを入力する必要があります。
まずは、コード1へ仮想MFAデバイス
アプリへ表示されいてるコードを入力して、そして、30秒ほど待ち、新しく表示されたコードをコード2へ入力して、MFAの割り当て
をクリックします。
セキュリティ認証情報
で多要素認証(MFA)が有効化されたことを確認する多要素認証(MFA)セクションへデバイスタイプ
仮想
のMFAが登録されていることを確認しましょうAWS公式ドキュメント
- 投稿日:2020-09-25T12:34:07+09:00
AWSって何?
生徒 ジョン
講師 先輩
ここで登場するのは、
プログラミング歴なしのジョン(主人公)。
AWS講師の先輩。
ジョンが一人前のAWSエンジニアになる物語です。
インフラって何??それ美味しいの???
って方に向けて、極力わかりやすくAWSを紹介していきます。
____________________________________________ジョン
「インフラって言葉聞いたことあるんだけど、ネットにおけるインフラってなんっすか?」先輩
「そんなの自分で調べろ。ググったら出てくる」ジョン
「え?わからないんですか?www」先輩
怒。。
「生活や仕事などを営む上で不可欠なものあるだろ(公共の福祉のため整備・提供される施設)。水道とか道路とか」
「ネットでもこの土台部分が整備されてないとコンテンツすら見れないって訳だ」
「だからネットにおけるインフラとは、主にサーバーやストレージやネットワークのことを指すんだ」ジョン
「おお。わかりやすい。」
「じゃあ。クラウドってなんですか?」先輩
「雲だよ!!!!!!!」ジョン
「馬鹿にしないでくださいw」先輩
「馬鹿にしてるようだけど、間違ってもいないんだな」ジョン
・・・・・?????先輩
「そもそもクラウドって定義が曖昧なんだ」
「インフラにおけるクラウドとは。。。。。」
「簡単に説明すると、ジョンがサーバーを持っていなかったとしても、ネット上でサーバーを持つことができるんだ」
「サーバーに限らず、ストレージっていうデータの保管場所やネットワークも構築できる」
「ようは、秋葉や日本橋に行ってサーバーを購入してーセットアップしてーなんてことしたくねーだろ?」ジョン
「そんなことしてる時間があれば、BBQでもしたいっすねw」先輩
「遊ぶことしか頭にねーのか。w」
「時間だけでなく、サーバーを借りることで費用も抑えられる。」
「1台サーバーを用意して何万って払うのと、借りて1ヶ月課金の何百円ならどっち選ぶ?」ジョン
「後者ですねーー」先輩
「もちろんここだけ伝えれば、クラウドのメリットばかりに感じるよな」
「でも自前でインフラを準備する。これをオンプレ(オンプレミスの略)っていうんだが、オンプレにした方が有利な場合もある。」
「ケース×ケースだが、例えば自社で独自のセキュリティーを高めたいとかの場面に有効なんだ」
「しかし、そうなったらセットアップやサーバーを構築・維持する技術者も必要だよな」ジョン
「確かに。」先輩
「固定費を支払っても十分メリットがある場合はオンプレにする場合もあるんだ」
「でも時代はクラウド化してるらしいけどな」ジョン
「どこでそのインフラを借りることができるのですか?」先輩
「いろいろあるけど、今後インフラに興味があるならAWSがいいんじゃなかな?」ジョン
「【AWS】って????」先輩
「みなさんお馴染みのAmazon.com社が提供するクラウドコンピューティングサービスのことだ」
「Amazon Web Serviceの頭文字をとったものだな」
「Amazonって、膨大な情報量(商品や顧客などなど)を処理しなければならないだろ?その技術を一般の人にも提供してる訳」ジョン
「AWSがなぜおすすめかというと」・低価格、初期費用なし、従量課金制
→使用した分しか費用が発生しないし
・継続的な値下げ
→過去10年で70回の値下げ
・サイジングからの解放
→サーバーの台数を瞬時に増減できる。
・ビジネス機会を逃さない
→オンプレだとサーバーを運用するまでに、購入、セットアップなどで半年や数年かかることもあるが、AWSなら瞬時にサーバー構築ができる。
・最先端の技術をいつでも使用可能
→AWS では 165 を超えるサービスを提供(投稿日現在)
・いつでも即時にグローバル展開
→AWSでは世界中にデータセンター(リージョン)があり、アベイラビリティゾーン(複数のデータセンター郡)が存在しているため。グローバルに展開可能。
・開発速度の向上と属人性の排除
・マネージドサービスによる、運用負荷の軽減
・高いセキュリティーを確保
・24時間365日日本語によるサポート詳しくは
https://aws.amazon.com/jp/aws-ten-reasons/ジョン
「訳わからないですけど、とりあえずメリットは沢山あるってことですね」
「ちょっとAWSに興味を持ってきました。今から勉強します!!!」先輩
「質問も従量課金だからな」
- 投稿日:2020-09-25T12:34:07+09:00
AWSを使用する利点
生徒 ジョン
講師 先輩
ここで登場するのは、
プログラミング歴なしのジョン(主人公)。
AWS講師の三郎。
ジョンが一人前のAWSエンジニアになるための物語です。
インフラって何??それ美味しいの???
って方に向けてAWSを使用するメリットを紹介します。
____________________________________________ジョン
「インフラって言葉聞いたことあるんだけど、ネットにおけるインフラってなんっすか?」先輩
「そんなの自分で調べろ。ググったら出てくる」ジョン
「え?わからないんですか?www」先輩
怒。。
「生活や仕事などを営む上で不可欠なものあるだろ(公共の福祉のため整備・提供される施設)。水道とか道路とか」
「ネットでもこの土台部分が整備されてないとコンテンツすら見れないって訳だ」
「ってことはサーバーやストレージやネットワークのことをインフラって呼ぶ訳」ジョン
「おお。わかりやすい。」
「じゃあ。クラウドってなんですか?」先輩
「雲だよ!!!!!!!」ジョン
「馬鹿にしないでくださいw」先輩
「馬鹿にしてるようだけど、間違ってもいないんだな」ジョン
・・・・・?????先輩
「そもそもクラウドって定義が曖昧なんだ」
「インフラにおけるクラウドとは。。。。。」
「簡単に説明すると、ジョンがサーバーを持っていなかったとしても、ネット上でサーバーを持つことができるんだ」
「サーバーに限らず、ストレージっていうデータの保管場所やネットワークも構築できる」
「ようは、秋葉や日本橋に行ってサーバーを購入してーセットアップしてーなんてことしたくねーだろ?」ジョン
「そんなことしてる時間があれば、BBQでもしたいっすねw」先輩
「遊ぶことしか頭にねーのか。w」
「時間だけでなく、サーバーを借りることで費用も抑えられる。」
「1台サーバーを用意して何万って払うのと、借りて1ヶ月課金の何百円ならどっち選ぶ?」ジョン
「後者ですねーー」先輩
「もちろんここだけ伝えれば、クラウドのメリットばかりに感じるよな」
「でも自前でインフラを準備する。これをオンプレ(オンプレミスの略)っていうんだが、オンプレにした方が有利な場合もある。」
「ケース×ケースだが、例えば自社で独自のセキュリティーを高めたいとかの場面に有効なんだ」
「しかし、そうなったらセットアップやサーバーを構築・維持する技術者も必要だよな」ジョン
「確かに。」先輩
「固定費を支払っても十分メリットがある場合はオンプレにする場合もあるんだ」
「でも時代はクラウド化してるらしいけどな」ジョン
「どこでそのインフラを借りることができるのですか?」先輩
「いろいろあるけど、今後インフラに興味があるならAWSがいいんじゃなかな?」ジョン
「【AWS】って????」先輩
「みなさんお馴染みのAmazon.com社が提供するクラウドコンピューティングサービスのことだ」
「Amazon Web Serviceの頭文字をとったものだな」
「Amazonって、膨大な情報量(商品や顧客などなど)を処理しなければならないだろ?その技術を一般の人にも提供してる訳」ジョン
「AWSがなぜおすすめかというと」・低価格、初期費用なし、従量課金制
→使用した分しか費用が発生しないし
・継続的な値下げ
→過去10年で70回の値下げ
・サイジングからの解放
→サーバーの台数を瞬時に増減できる。
・ビジネス機会を逃さない
→オンプレだとサーバーを運用するまでに、購入、セットアップなどで半年や数年かかることもあるが、AWSなら瞬時にサーバー構築ができる。
・最先端の技術をいつでも使用可能
→AWS では 165 を超えるサービスを提供(投稿日現在)
・いつでも即時にグローバル展開
→AWSでは世界中にデータセンター(リージョン)があり、アベイラビリティゾーン(複数のデータセンター郡)が存在しているため。グローバルに展開可能。
・開発速度の向上と属人性の排除
・マネージドサービスによる、運用負荷の軽減
・高いセキュリティーを確保
・24時間365日日本語によるサポート詳しくは
https://aws.amazon.com/jp/aws-ten-reasons/ジョン
「訳わからないですけど、とりあえずメリットは沢山あるってことですね」
「ちょっとAWSに興味を持ってきました。今から勉強します!!!」先輩
「質問も従量課金だからな」
- 投稿日:2020-09-25T11:22:35+09:00
S3バケットをOrganizations内全アカウントにアクセス許可
S3バケットをAWSアカウントを超えて共有したい、Organizatins内の全AWSアカウントに共有できるとなおよいとおもったので、方法を確認してみました。
S3バケットのクロスアカウントアクセス
まず別のAWSアカウントの特定のIAMユーザーにアクセスを許すところから。
ここでは簡単な「別の AWS アカウントのユーザーに、オブジェクトを S3 バケットにアップロードするアクセス権を与える」を試します。このドキュメントは下図のように、S3アクセスを実現するためにIAMポリシーとバケットポリシーを設定する、という内容になっています。
アカウントAでは、バケット名
bucketname
への書込み操作をIAMユーザーに許可するために、IAMポリシーを作成してアタッチします。IAMポリシーの内容は以下のようになります。実施時はbucketname
は実際のバケット名に合わせて置き換えます。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] } ] }これでIAMユーザー
UploadData
はbucketname
に書込みに行けるようになりました。でも実際に書込みに行ってみると書き込む権限がないと断られる状況です。そこで次に、アカウントBでバケット名
bucketname
への書込みをIAMユーザーUploadData
に許すために、バケットポリシーを作成してアタッチします。バケットポリシーの内容は以下のようになります。実施時はbucketname
は実際のバケット名、999999999999
は実際のAWSアカウントAのアカウントID、UploadData
は実際のIAMユーザー名で置き換えます。ドキュメント通りの手順でやるならarn:aws:iam::999999999999:user/UploadData
がまるごとIAMユーザーのARNで置き換えられます。{ "Version": "2012-10-17", "Statement": [ { "Sid": "DelegateS3Access", "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::999999999999:user/UploadData"}, "Action": ["s3:PutObject", "s3:ListBucket"], "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] } ] }これで
UploadData
さんは無事にS3バケットbucketname
にオブジェクトを保存(put)できるようになったはず、ということでこのドキュメントは終了。あとは、他アカウントのS3バケットへのアップロードはWebのAWSマネジメントコンソールからは試せないので、AWS CLIで確認します。aws s3 cp ローカルファイル s3://bucketname/本気で考えるなら「S3 バケット内のオブジェクトへのクロスアカウントアクセスを許可する」で示されている以下の3つのアプローチから、どれがいいか選ぶところから検討することになるのでしょう。上の内容は、このうち最初のものに相当するはず。
- リソースベースのポリシーと IAM ポリシー
- リソースベースのアクセスコントロールリスト (ACL) と IAM ポリシー
- クロスアカウント IAM ロール
他AWSアカウントの全IAMユーザーにアクセス許可
次に別のAWSアカウントのすべてのIAMユーザーにアクセスを許したいと思います。バケットポリシーで読み書きを許可するユーザー名にワイルドカード
*
を指定したいところだけど、これはできません。Principal エレメント内でユーザーを指定する際に、"すべてのユーザー" の意味でワイルドカード (*) を使用することはできません。プリンシパルには、常に特定のユーザー(または複数の特定ユーザー)を指定する必要があります。
(AWS JSON ポリシーの要素: Principal - AWS Identity and Access Management)そこで、AWSアカウントそのものを読み書きの許可対象に指定します。アカウントIDを指定する場合は、以下のどちらの書き方をしてもいいことになっています。
"Principal": { "AWS": "arn:aws:iam::123456789012:root" } "Principal": { "AWS": "123456789012" }バケットポリシーを以下のように書き換えます。ここでは上の1行目の書き方をしましたが、より簡素な2行目の書き方でもOKです。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DelegateS3Access", "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::999999999999:root"}, "Action": ["s3:PutObject", "s3:ListBucket"], "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] } ] }少し待ってからローカルファイルのアップロードを試します。
aws s3 cp ローカルファイル s3://bucketname/Organizations内の全AWSアカウントにアクセス許可
いよいよOrganizations内の全AWSアカウントへのアクセス許可を考えます。Organizations内のAWSアカウントには
arn:${Partition}:organizations::${MasterAccountId}:account/o-${OrganizationId}/${AccountId}
のようなOrganizations IDを含んだARNが付与されるので、これとワイルドカードを組合わせればできそうに思えますが、実際にはこのARNはPrincipalで利用できないようです。以下のリソースタイプは、このサービスによって定義され、IAM アクセス許可ポリシーステートメントの Resource 要素で使用できます。
AWS Organizations のアクション、リソース、および条件キー - AWS Identity and Access ManagementここはPrincipalは完全にワイルドカード
*
にしてしまいます。その代わり、Organizations外からのアクセスができないように、Conditions(条件)を設定します。aws:PrincipalOrgIDが利用できます。{ "Version": "2012-10-17", "Statement": [ { "Sid": "DelegateS3Access", "Effect": "Allow", "Principal": "*", "Action": ["s3:PutObject", "s3:ListBucket"], "Resource": [ "arn:aws:s3:::bucketname", "arn:aws:s3:::bucketname/*" ] "Condition": {"StringEquals": {"aws:PrincipalOrgID":["o-xxxxxxxxxxx"]} } } ] }少し待ってからローカルファイルのアップロードを試してみます。
aws s3 cp ローカルファイル s3://bucketname/これでOrganizations内のどのAWSアカウントのIAMユーザーでも、このバケットへの読み書きが可能になったはずです。できればOrganizations外のAWSアカウントのIAMユーザーでは、バケットへの読み書きができないことも確認しておいた方がよいでしょう。
参考
本稿は以下のドキュメントを参考にまとめました。
- 別の AWS アカウントのユーザーに、オブジェクトを S3 バケットにアップロードするアクセス権を与える
- S3 バケット内のオブジェクトへのクロスアカウントアクセスを許可する
- AWS JSON ポリシーの要素: Principal - AWS Identity and Access Management
- AWS Organizations のアクション、リソース、および条件キー - AWS Identity and Access Management
- aws:PrincipalOrgID
触れていませんが、IAMユーザーとS3バケットにそれぞれポリシーを設定するというのは「アイデンティティベースのポリシーおよびリソースベースのポリシー」に該当します。
- 投稿日:2020-09-25T11:11:12+09:00
VPC エンドポイントへのアクセス許可は SourceIp ではなく VpcSourceIp を利用する
概要
VPC エンドポイントを設定した AWS サービスへの IP アドレスによるアクセス許可は SourceIp ではなく、VpcSourceIp を利用する必要があります。
詳細
前提条件
S3 の特定のバケットに対して VPC エンドポイントを利用していると仮定します。
その S3 に対しては次のようにパブリックなアクセス許可を与えています。
{ "Version": "2012-10-17", "Id": "Policy156583539788", "Statement": [ { "Sid": "Stmt156583536527", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "IpAddress": { "aws:SourceIp": "0.0.0.0/0" } } } ] }この設定ではすべての IP アドレスからの GetObject を許可しています。
VpcSourceIp の指定
この状態で VPC 内のインスタンスなどからこのバケットにアクセスしようとしても Access Denied になります。
VPC エンドポイントへのアクセス許可(およびアクセス制限)に SourceIp は使えないからです。
VPC エンドポイントを介した Amazon S3 へのリクエストに、IAM ポリシーの aws:SourceIp 条件を使用することはできません。これはユーザーとロールの IAM ポリシー、およびバケットポリシーに適用されます。ステートメントに aws:SourceIp 条件が含まれる場合、値は指定した IP アドレスまたは IP アドレス範囲に一致しません。
ref: https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-endpoints-s3.html
その代わり、VpcSourceIp を利用します。
リクエスト実行元が Amazon VPC エンドポイントを使用するホストである場合、aws:SourceIp キーは使用できません。代わりに、aws:VpcSourceIpなどの VPC 固有のキーを使用する必要があります。
ref: https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_condition-keys.html
{ "Version": "2012-10-17", "Id": "Policy1565835397817", "Statement": [ { "Sid": "Stmt1565835396529", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "IpAddress": { "aws:VpcSourceIp": "0.0.0.0/0" } } } ] }これで VPN エンドポイントを経由した VPC 内からのアクセスを許可することができます。
SourceIp と VpcSourceIp の併用
VPC 内に加えて、外部からもアクセスを許可したい場合は次のように SourceIp と VpcSourceIp のアクセス許可を二つ列挙します。
{ "Version": "2012-10-17", "Id": "Policy1565835397817", "Statement": [ { "Sid": "Stmt156583539651", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "IpAddress": { "aws:SourceIp": "0.0.0.0/0" } } }, { "Sid": "Stmt1565835396529", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*", "Condition": { "IpAddress": { "aws:VpcSourceIp": "0.0.0.0/0" } } } ] }
- 投稿日:2020-09-25T10:10:24+09:00
CloudFrontとLambdaでBasic認証設定してみた
こんにちは
「あつまれどうぶつの森」のためにswitchの販売抽選に応募するが当たらず「あつもり」ブームも過ぎていたことに最近気付いたstreampackのrisakoです
そして9月も後半!だんだん涼しくなってきて過ごしやすくなってきましたね!今回のテーマは「Basic認証」です
これまでなかなか触れることがなかったので、初めて設定してみた記念にQiitaに残そうと思います。Basic認証とは?
Web上で利用できる認証システムのことです。
Basic認証が設定されているページにアクセスすると、ブラウザ上にユーザー名とパスワードを入力するポップアップが表示されます。
私はBasic認証を意識する前に数回web上でみたことがあるので、多くの人が見たことがあるのではないでしょうか?事前に用意するもの
- S3 bucketに保存したhtmlなど
Basic認証で保護したいwebページなどをS3bucketに保存しておきます。
まず初めにCloudFront設定
デフォルトの設定から変更が必要な箇所のみ記載します。
Origin Settings
- Origin Domain Name:webページで保存したS3bucketを指定します
Default Cache Behavior Settings
- Viewer Protocol Policy :Redirect HTTP to HTTPS
- Cache and origin request settings:Use legacy cache settings
- Cache Based on Selected Request Headers:Whitelist
- Whitelist Headers:Access-Control-Request-Headers・Access-Control-Request-Method・Origin
- Object Caching:全てのTTLを0にする
Distribution Settings
- Default Root Object:S3に保存しているwebページをしていると、CloudFrontのURLから直で行けるので便利です。例えば、https://CF-URL/test.html と指定しなくてもhttps://CF-URL だけで、test.htmlが表示されます。
次にLambda設定
Lambda@edgeを使用できるリージョンは米国東部(バージニア北部)のみなのでリージョンを移動します。
- 任意の関数名を入力します。
- ランタイム:Node.js 12.x
- 実行ロール:Basic認証用に作成したものとわかるように命名します。仮に(test-basic-role)とします。
- ポリシーテンプレート:「基本的なLambda@Edgeのアクセス権限(CloudFrontトリガーの場合」を選択
Lambda関数が作成できたら、コードを書いていきます
参考記事のコードを使わせてもらっています。
下記の箇所を自分の設定したいユーザー名とパスワードに変更してください。
ここで設定したユーザー名とパスワードがbasic認証で使用するものになります。// Configure authentication
const authUser = 'user';
const authPass = 'pass';'use strict'; exports.handler = (event, context, callback) => { // Get request and request headers const request = event.Records[0].cf.request; const headers = request.headers; // Configure authentication const authUser = 'user'; const authPass = 'pass'; // Construct the Basic Auth string const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64'); // Require Basic authentication if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) { const body = 'Unauthorized'; const response = { status: '401', statusDescription: 'Unauthorized', body: body, headers: { 'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}] }, }; callback(null, response); } // Continue request processing if authentication passed callback(null, request); };コードを保存したら、右上に表示される「アクション」から「Lambda@Edgeへのデプロイ」に進みます。
CloudFront トリガーの設定
- 先ほど作成したCloudFrontを指定します。
- CloudFrontイベント:ビューアーリクエスト
- Lambda@Edge へのデプロイを確認:を入れます
- 「デプロイ」を押下
Cloudfrontとlambdaが連携できているか確認する
作成したCloudFrontのBehaviorから確認します。
Lambda Function Associations
- CloudFront Event:Viewer Request
- Lambda Function ARN:Lambda@EdgeのARN
動作確認
CloudFrontのドメインでアクセスしてみましょう。
Basic認証がちゃんと設定されているようです。
設定したユーザー名とパスワードでログインできればOKです!参考
簡単だった!CloudFront + S3 に BASIC認証を入れる方法
できた!S3 オリジンへの直接アクセス制限と、インデックスドキュメント機能を共存させる方法
- 投稿日:2020-09-25T09:17:13+09:00
EC2上のwebサーバをSSL化対応
今までの準備
- Route53でホストゾーンの作成
- EC2インスタンス作成~ログインまで(画像つき)
- EC2インスタンス(CensOS7)をwebサーバとして起動しドメインで表示させる
- EC2インスタンスのIPアドレスを固定させる
今まではSSL化対応していなかった?
以下の画像を見ていただければわかると思いますが、
鍵のマークに赤い斜線がひかれています。
これは安全ではない通信、いわゆるSSL化対応が出来ていないということになります。SSL化対応しているwebページだと以下のように
鍵のマークが表示され、安全な通信だということが記載されます。
今回は以下のようにSSL化対応していきます。やること
- ACMでSSL証明書を作成
- ターゲットグループの作成
- ターゲットグループにターゲットを紐付ける
- ロードバランサーの作成
- Route53で設定を変更する
- セキュリティグループの変更
ACMでSSL証明書を作成
- ACMを探す
ACMとは
SSL/TML証明書のプロビジョニング、管理及びデプロイ
と書いてあるように、SSL証明書を発行してくれるサービスです。
- 証明書のプロビジョニング
今回は、パブリックな証明書のプロビジョニングを行います。
- 証明書のリクエスト
プライベートではなく、パブリック証明書のリクエストになります。
チェックを入れて証明書のリクエストを行います。
- 検証方法の選択
ざっくりいうと
証明書を発行する際に今申請している人(組織)と発行元のドメインを管理している人(組織)が同一か、というのを見ています。
そのための方法として、以下2つの方法があります。
1. DNS認証
2. Eメール認証今回DNS認証にした理由とは、
Eメール認証だと証明書リクエストを行うドメインへメールが送られ
ます。
メール見るだけやろ?って思う方いるかと思いますが、
今このドメイン(domainname.com
)にはメールを受け取る設定もサーバも立てていません。
また、受け取るユーザも決まっていてadmin
,postmaster
など決まったユーザしか受け取ることが出来ないのです。
メールサーバ立てるのも少し面倒なので、一旦DNS認証にしました。DNS認証にすると、
この値(CNAMEレコード)をDNSに書き込んでね
というのが指定されるのでそれをDNSに書き込みます(今回はRoute53)
またRoute53で管理していると、ワンクリックで自動的にレコード入れてくれるのでとても楽です。ACMのSSL証明書は1年毎の更新ですが、
DNS認証は自動更新になります(例外あり!)
ただし、これから追加するDNSのレコードを消してしまうと更新できないので注意。
メール認証は、毎年メールが送られてきてURLをクリックすると更新されます。
メールなので作業漏れ結構有りなので注意
- タグは今回設定しません
- ドメイン名の追加
ここでは、
どんなドメインのSSL証明書を発行するか
をドメイン名の箇所に記載します。
www.domainname.com
をここでは入れます。
- 最終確認
このドメインでこの認証方法でリクエストするよ、と確認されます。
間違っていたら戻って再度修正してください
- 検証
証明書のリクエストが完了し、検証に入りました。
DNS認証にしたので、ドメイン
の箇所にDNSに入れるべきレコードが記載されています。
Route53でのレコード作成
というボタンがありますが、これが↑で言ってたワンクリックでRoute53にレコードを書き込んでくれるボタンです。
- Route53でのレコードの作成
このレコードをホストゾーン
domainname.com
に入れますか?と記載があるので、作成を行います。
- 検証が成功に変わる
レコードがRoute53に追加されたら一覧に戻ります。
少し待っていると検証状態が検証保留中から成功に変わります。
- 証明書一覧に戻る
証明書一覧に戻ると、作成した証明書が追加されていました。
状況が発行済みになっています。
ここでは証明書の期限など詳細を知ることが出来ますちなみに↑でも書きましたが、
DNS認証だと1年後に自動更新がされます。(※DNSのレコードを消しちゃダメだよ!)
メール認証だと毎年メールが来て、作業が漏れることがあるのでDNS認証のほうが楽なのでは?と思います。ターゲットグループの作成
ターゲットグループとは、後で作るロードバランサーに関係があります。
ロードバランサーとは、ロードバランサーに来たアクセスを紐付いてるサーバに割り振る役割を行います。
その、紐付いてるサーバのグループ
をターゲットグループと呼びます。
ロードバランサー1つに紐づくターゲットグループは1つになります。
- EC2に進む
ターゲットグループはEC2で作成行います。
- ターゲットグループの作成
EC2から左の下の方に
ロードバランシング
というのがあります。
その中にターゲットグループがあり、クリックすると一覧が表示されます(今はなにもないので見えないですが...)
ターゲットグループの作成を行います。
- ターゲットグループの詳細設定
ここではターゲットグループの設定を行います。
ターゲットグループ名 : targetgrouptest ターゲットの種類 : インスタンス プロトコル : HTTP ポート : 80 VPC : EC2インスタンスが存在するVPC ヘルスチェックのプロトコル : HTTP ヘルスチェックのパス : /
- 一覧に戻る
一覧に戻るとターゲットグループが表示されます。
ターゲットグループにターゲットを紐付ける
先程作成したターゲットグループにwebサーバのEC2インスタンスを紐付けます
- 編集
ターゲットグループにチェックを入れ、編集に進みます
- webサーバのインスタンスを追加
- [インスタンス]の項目で追加するwebサーバにチェックを入れ、
- 「登録済みに追加」をすると
- 「登録済みターゲット」にwebサーバのインスタンスが追加されました。
保存を行います。
- 一覧に戻る
保存すると、webサーバが登録済みターゲットに追加されています。
ステータスがunusedなのは、LBにターゲットグループを登録していないのでこの表示になっています。ロードバランサーの作成
いよいよロードバランサー(LB)の追加です。
- ロードバランサーの作成
EC2の左下にロードバランシングがあり、その中にロードバランサーがある
一覧の箇所にロードバランサーの作成があるので、作成を行う
- ロードバランサーの種類の選択
実はロードバランサーには種類があって以下があります。
・ ALB : アプリケーションレイヤー ・ NLB : ネットワークレイヤー ・ CLB : アプリケーションもネットワークもOK。だが古い(クラシック)なので上記2つを推奨今回はHTTPSのアクセスなので、ALBにします。
- ロードバランサーの設定
名前 : loadbalancertest スキーム : インターネット向け ▼リスナー HTTP, HTTPS 2つ追加します ▼アベイラビリティーゾーン VPC : webサーバのあるVPCを選択 アベイラビリティーゾーン : 1つはwebサーバのあるアベイラビリティーゾーン もう1つはどこでもいい(ここの項目は2つ選択しないといけない為。)
- セキュリティ設定の構成
証明書タイプ : ACMから証明書を選択する 証明書の名前 : 先程作成した証明書(www.domainname.com)を選択する
- セキュリティの設定
新しいセキュリティグループ作成
セキュリティグループ名はなんでもよい(今回は「loadbarancertestsecuritygroup」)
許可するポートは、80と443でどこからでもアクセス出来るように、0.0.0.0/0, ::/0 を許可する
- ルーティングの設定
↑で作ったターゲットグループを設定
既存のターゲットグループ と ターゲットグループ名さえ入れれば自動的に入る
- ターゲットの登録
ターゲットグループで指定したEC2インスタンスがここに表示される
- 確認
今まで設定したものが表示される。
意図していないものが表示された場合には、戻って修正を行う。
- ロードバランサーが作成される
- ターゲットの確認
ターゲットグループの一覧からターゲットを確認すると、
LB作成前はunusedになっていたが、
LB作成後はhealthyになり、アクセス出来る状態になった。Route53で設定を変更する
- Route53のdomainname.comのホストゾーンに移動
- レコードの編集
www.domainname.com
のAレコードにチェックを入れ、編集を行う
- 値の変更
ALBとCLBへのエイリアス
にし、その下の段はLBがあるリージョン
を指定。
一番下はLB
を指定します。
- 保存された状態
保存されると、
www.domainname.com
のAレコードはLBに変更されました。セキュリティグループの変更
↑で、実質
www.domainname.com
にアクセスを行うと、
LB
を通り、EC2インスタンス
にアクセスが来るように経路を変更することが出来ました。
しかし、ここで問題となっているのが、
EC2インスタンスのセキュリティグループです。
なぜかというと、EC2インスタンスのセキュリティグループは以下のように
自分のPCからしかアクセスできないようになっています。
要するに、ALBからのアクセスも遮断してしまいます。ですので、ALBからのアクセスのみ許可する変更をします。
- EC2のセキュリティグループを変更
EC2 → セキュリティグループ へ進み、
EC2のセキュリティグループにチェックを入れ、インバウンドを編集します。このときに、LBのセキュリティグループの
グループID
をメモしておいてください
グループIDはsg-xxxxxx
と書かれている、↓で言ったら青の枠の箇所になります。
- 編集
HTTPとHTTPSに書かれていた、自分のIPアドレスを
↑でメモしたグループID(sg-xxxxxx)に書き換えて保存します。
- 一覧に戻る
一覧に戻るとHTTPとHTTPSのソースがセキュリティグループ(sg-xxxxxx)に変更されていました。
webで確認
- webで確認
webの検索で
https://domainname.com/index.html
と検索
すると、以下のように鍵がかかった状態でhello worldが表示されましいた。
- 証明書情報を見る
証明書情報を見ると、Amazonが認証局になり有効期限が来年になっていることが確認できました。
これで、EC2上のwebサーバをSSL化することが出来ました。
LBを利用しているので、アクセス数によって費用がかかります。
費用によっては、きちんと管理いただければと思います。参考
- 投稿日:2020-09-25T06:30:18+09:00
CodeDeployのIn-Place Deploymentでハマったときの対応
はじめに
AWSを使っているとCodeDeployを利用してデプロイすることがあると思います。
CodeDeployのIn-Place Deploymentでデプロイしたときにハマった点がありましたので、ハマった内容と対処方法を紹介します。ハマった内容
In-Place Deploymentは、稼働中サーバーに対して直接新しいアプリケーションをデプロイする方法です。
デプロイ先のサーバーのストレージ容量によりますが、何度かデプロイを行っているとストレージ容量を使い切ってしまい、デプロイを行っても失敗を繰り返しました。原因
CodeDeployエージェントは、/opt/codedeploy-agent/deployment-root/配下にリビジョン、デプロイメント履歴、デプロイメントスクリプトを保存していました。そのため、デプロイを何度も行うと過去のデプロイメント履歴が残りストレージ容量を使い切っていました。
/opt/codedeploy-agent/deployment-root/配下のディレクトリ構成については、Working with the CodeDeploy agent
のFiles installed by the CodeDeploy agentを参考にしてみてください。対処方法
ストレージ容量を使い切ったときに手動で過去のデプロイリソースを削除するでも良いですが、ストレージ容量の確保を自動化しました。
DeploymentGroupID_last_successfull_installファイルに、最後(最新)に正常にデプロイされたアーカイブディレクトリのパスが記述されているので、以下のようにスクリプトを作成し、appspec.ymlでスクリプトを実行し、最後(最新)に正常にデプロイされた場所のアーカイブディレクトリ以外のディレクトリを削除することで、ストレージ容量を確保することに成功しました!#!/bin/bash ###デプロイグループIDを取得 DEPLOYMENT_GROUP_ID=`cat /opt/codedeploy-agent/deployment-root/deployment-instructions/*_last_successful_install | cut -d"/" -f 5` ###デプロイIDを取得 LAST_SUCCESSFUL_DEPLOY_ID=`cat /opt/codedeploy-agent/deployment-root/deployment-instructions/*_last_successful_install | cut -d"/" -f 6` DIR_PATH="/opt/codedeploy-agent/deployment-root/$DEPLOYMENT_GROUP_ID/" ###最後(最新)に正常にデプロイされた場所のアーカイブディレクトリ以外のディレクトリを削除する cd $DIR_PATH && ls | grep -v $LAST_SUCCESSFUL_DEPLOY_ID | xargs rm -rf注意点
次のデプロイを行うときに、最後(最新)に正常にデプロイされたアーカイブディレクトリが必要になるので、アーカイブディレクトリを削除してしまうとデプロイを失敗してしまうので、お気を付け下さい。
- 投稿日:2020-09-25T00:45:33+09:00
ACMのPrivate CAを利用した場合のAmazon API GatewayのmTLS構成を試してみた。
はじめに
先週、こちらのふたつの記事でAmazon API GatewayのmTLS認証について検証・検討しました。
前回、記事が長くなってに分割しましたが、その中でも確認したいことが書けなかったので、その後続の記事として ACM(AWS Certificate Manager)のPrivate CA( Certificate Authority:認証局) を利用したAmazon API GatewayのmTLS構成について書きたいと思います。なお、興味本位でAWS ACM Private CAを今回は利用していますが、opensslコマンドを利用して以下を実施いただいても同じ内容かと思います。
- 仮想のルートCAの秘密鍵、自己証明書(ルートCA証明書)
- 仮想の中間CAの秘密鍵、中間CA証明書(ルートCAによる署名)
- クライアントの秘密鍵、クライアント証明書(中間CAによる署名)
サマリ
- 中間認証局がクライアント証明書発行している場合は、ルート認証局までのチェーンをトラストストアに登録する必要がある(各CAの公開鍵証明書)
- 複数の認証局をサポートしたい場合はその認証局とそのチェーン(ルート認証局までに登場する中間認証局)をトラストストアに登録すればよい
- トラストストア内は重複した認証局の証明書の登録はできないので、既に登録されている認証局の証明書は登録時にエラーとなる。
AWS Certificate ManagerのPrivate CAとは
ACM Private CA は、オンプレミスCA の運用にかかる投資コストや保守コストなしに、ルート CA や下位 CA を含むプライベート認証機関 (CA) 階層を作成できます。リリース当初はルートCAのサポートがありませんでしたが、可能になっています。Private CAを利用するとX.509 証明書を発行できます。
X.509 証明書を発行およびデプロイする AWS サービスは 二つあります。-ACM Private CA
-AWS Certificate Managerどちらを利用するかは、利用ニーズによります。
ACM Private CA
組織内でのプライベートな使用を目的として証明書を利用する場合にACM Private CA を使用すると、独自の認証局、および、その 階層を作成できます。独自の認証局をマネージドで構築することで、運用の負荷を軽減でき、かつ、社内・組織内で利用可能な独自の証明書の発行が可能とンります。この証明を、Public、つまり、インターネット等で利用してもルート認証局が公開され、かつ、認められた認証局ではないため信頼はされない証明書となります。
- 任意のサブジェクト名で証明書を作成可能
- 任意の有効期限で証明書を作成可能
- サポートされている任意のプライベートキーアルゴリズムとキー長を使用可能
- サポートされている任意の署名アルゴリズムを使用可能
AWS Certificate Manager
ACMで発行する証明書は、AWS Elastic Load Balancing、Amazon CloudFront、Amazon API Gateway、およびその他の 統合サービスにデプロイできます。ACM が提供するパブリック証明書 または ACM にインポートされた証明書を上記の統合サービスに適用でき、証明書に指定されたドメイン名でTLS通信を可能にします。
当記事で利用する証明書は3種類
当記事では以下の3州類の証明書を作成・利用します。
- (Private)ACM Private CAで発行するルート認証局ならびに中間認証局の証明書。クライアント証明書の署名検証用
- (Private)ACM Private CAでで発行した中間認証局の証明書で証明されたクライアント証明書。mTLS用
- (Public)ACMで発行されたサーバ証明書。TSL通信用
A.一つ目の中間認証局の環境構築
環境構築は以下の順序で実施します。
- ルート認証局の作成(ACMPrivate CA)
- 中間認証局の作成(ACM Private CA)
- APIの作成
- カスタムドメインの有効化(ACM)
- mTLSの有効化
- クライアント証明書の作成(ACM Private CA)
- 接続
1. ACMのPrivate CAを利用したルート認証局の構築
ルート認証局の作成
ACMを利用することで僅か数分でルート認証局(中間認証局も)が構築可能です。今回は以下の設定でAWS マネジメントコンソールから構築しました。
※ルート認証局を当文書内ではルートCA/中間認証局を中間CAと表記することもあります。ルート認証局に証明書のインストール
前回の記事ではopensslコマンドで
- 秘密キーの作成
- (CSR作成は省略して証明書作成時に入力)
- 自己署名証明書の作成
を実施しましたが今回はAWS マネジメントコンソールから証明書の生成& ルート認証局へのインポートを実施しました。(前回の記事では、自己署名の証明書をつくっただけで、認証局を運用したわけではありません)
個別に秘密鍵の生成、CSRの作成は必要なく、CSRに入力する情報を画面から入力する形でした。有効期限は1か月にしておきました。以上で、Private CAのルート認証局が立ち上がりました。Private CAで構築したルート認証局とAPI Gatewayの組み合わせで設定を確認するだけであれば、ここまでで十分です。ただ、今回は中間認証局(証明書のチェーン)も合わせて試したかったので先ほど構築したルート認証局をルートとした中間認証局も構築します
2. ACMのPrivate CAを利用した中間認証局の構築
中間認証局の作成
こちらは、ルート認証局を構築した手順とほぼ同様の手順ですが、CA TypeがSubordinate(下位)となっている点が違いです。添付の画像を確認してみてください。
続いて、中間認証局の証明書の作成とインポートです。こちらの証明書は先ほどのルート認証局用の証明書とは異なります。どこが異なるかというと、自己署名、つまり自分自身の秘密鍵を使って署名をするのではなく、ルート認証局の秘密鍵を使って証明書に署名する部分がルート認証局の署名時と異なる部分です。つまり、ルート認証局の証明書・秘密鍵が必要になるため、ルートを指定します。以下の画像でParent private CAでルート認証局のIDを選択していることを確認してください。
なお、上位の認証局(今回の場合、ルート認証局)のIDが分からない場合は、以下の画像にある通り、対象の認証局をACMの一覧から表示してStatusをご確認ください。ARN(Amazon Resource Names)の中にIDがあります。
中間認証局の作成2
さて、本来であれば、ここまででクライアント証明書の作成に進むわけですが、ここで気になったのが、中間認証局を複数立ち上げた場合、つまり、クライアント証明書を複数の業者に依頼して発行した場合等はどういう構成になるのか?ということで、別のCAを構築することにしました。こちらのCAの階層としてはという階層構成で、先ほど作った認証局とは、ルート認証局のみが共通しているものとなります
3.APIの作成 & 4. カスタムドメインの有効化(ACM)
APIの作成および、カスタムドメインの有効化設定は先日ご紹介したAmazon API GatewayでmTLSを試してみた。(1/2)の記事の通りです。今回は省略します。前回、無意識のうちにRoute 53へのレコード登録をしていたようですが、今回確認した結果、やはり、カスタムドメインの有効化(ACMでのPublicな証明書の作成&API Gatewayのカスタムドメイン登録)に加えRoute53のレコード登録が必要でした。
5.mTLSの有効化
さて、カスタムドメインの登録まで実施し、https://カスタムドメイン名/ で接続できたことを確認した後で、mTLSの有効化設定をAPI Gatewayのカスタムドメインの設定の中で実施します。
CA証明書の入手
mTLSの登録に必要なものは、認証局の公開鍵証明書です。今回は、ACM Private CAで認証局の証明書を作成したため、ACM Private CAから証明書をダウンロードし登録します。
CA証明書はAWSマネジメントコンソールまたはCLIで抽出可能です。今回はコンソールからリンクをクリックしてbodyおよびchainの両方を取得しました。
CA証明書の登録
入手した中間認証局のCA証明書をS3バケットに格納し、API Gatewayのカスタムドメイン設定を更新しました。すると以下のエラーメッセージが設定後、数分で表示されました。
詳細なエラーは次の通りです。
原因は、中間認証局の証明書のみを登録したからです。
では、中間認証局のCA証明書と、そのチェーンを連結した証明書を登録したいと思います。参考になる方法としてはこちらの手順9をご確認下さい。
ルート認証局までのチェーンを登録することで、以下の画像の通り、正常にMutualTLSが有効化されました。
6. クライアント証明書の作成(ACM Private CA)
サーバサイドの準備が整ったところで、クライアント証明書と秘密鍵を入手したいと思います。
今回は、中間認証局をACM Private CAで構築したので、その中間認証局に証明書の作成を依頼し、そこから、秘密鍵を取り出したいと思います。ACMによるPrivte Certificateの作成
Private Certificateなので、インターネットで正規の証明書として利用することはできないということを改めて述べつつ、解説します。
まずは、ACMの画面から証明書発行のリクエストを行います。そこでは、Private CAを構築していると、以下の画面にあるようにRequest a private certificateを選択することができます。Private certificateを選択します。
その後、認証局を選択します。今回は中間認証局を利用してクライアント証明書を作るので、以下の画像にある通り、Typeはsubordinateとなっておりルート認証局を選択していないことが分かります。
そして、適当にドメイン名を設定することで証明書の作成要求ができます。ACMで証明書発行する場合、ここはドメイン形式で入れる必要があります。任意のフォーマットは使えません。
そして、作成完了。
ACMからクライアント証明書と秘密鍵をExport
ACM上で証明書を作成したら、AWS CLIまたは、AWSマネジメントコンソールを利用してクライアント証明書、その秘密鍵、チェーンをExportすることが可能です。Exportする際には秘密鍵を暗号化するためのパスフレーズの入力が求められます。パスフレーズを入力しExportを実行すると以下のような形で証明書、チェーン、秘密鍵(暗号化されたもの)が表示されますので、保存してください。
7. 接続
接続には前回と同様にcurlコマンドを利用します。以下のような形で --passを指定してあげることで、暗号化された秘密鍵も利用可能です。パスワードは証明書Export時にしていしたものを指定してください。
curl -v --key client1.key --cert client1.pem --pass password https://api.xxx.xxx/hoge/authB.二つ目の中間認証局の環境構築
ここまでは、単体の中間認証局に対する検証でした。
ここからは複数の認証局をサポートするか試したいと思います。ケースとしては、クライアント証明書の発行を複数の認証局が担当するケースを想像してみました。今回の認証局の構成イメージ
先ほどまでは、この図で言うところのintermediateCA 0 が発行したクライアント証明書を利用して接続しました。その際には、intermediateCA 0 とrootCAの証明書をtruststore(S3のオブジェクト)に格納しました。API GatewayのトラストストアはAmazon S3の1つのオブジェクトを指定(オプションでVersionも指定可能)する形です。今回はintermeidiateCA 0をすでに登録してあるので、IntermediateCA 2 が発行したクライアント証明書も利用できるように構成したいと思います。
ちなみにintermediateCA 2 (3階層目)は上図にあるとおりrootCA->intermediateCA 1 ->IintermediateCA 2というチェーンになっています。
Private CAの構築は先ほど構築した手順の繰り返しとなりますので省略します。ここでは、API Gatewayへの登録方法についてご説明します。
二つ目の中間認証局によるクライアント証明書の発行と接続
この場合に必要な手順は次の通りです。
1. 未登録のCA証明書を入手しAPI Gatewayのトラストストアに登録
トラストストアに未登録のintermediateCA1とintermediateCA2の証明書を入手します。今回は、中間認証局intermediateCA2がクライアント証明書を発行するため、intermediateCA2の証明書に加えて、その上位の認証局であるintermediateCA1の証明書が必要となります。さらに上位のrootCAの証明書も本来は必要ですが既にトラストストア内に登録済みのため今回は不要です。入手方法は先ほど「mTLSの有効化手順で記載したCA証明書の入手」をご確認ください。
この時、私は最初、intermediateCA2のチェーン(ルート認証局までのすべてのチェーン)を登録したのですが、エラーメッセージが登録時に表示 され登録できませんでした。重複したCA認証局の証明書は登録できないようです。したがって、rootCAの証明書は今回はすでに追加されているので追加しませんでした。
2. API Gatewayのトラストストア設定を更新
S3のVersioning機能を有効化している場合に上記1で上書きした場合は、そのバージョンIDをAPI Gatewayのカスタムドメイン内のmTLSの設定で指定して更新します。また、別名のオブジェクトをS3バケットに格納した場合はパスを変更して登録してください。
上記を実施することでintermediateCA 2 が発行したクライアント証明書による接続が可能となります。
- 投稿日:2020-09-25T00:15:24+09:00
GCPとAWS どっちがいい
重要なこと
僕は、インフラメインの人間ではないです。
ポエムだと思ってください主観の感想
インフラ専門の方はAWSを使われるイメージが強いです
今までの知見とかが溜まっているとかがあるんでしょうか
自分の周りは会社ではAWSを使っているとよく聞く気がします
でも最近GCP勢が増えてきてる気もします
kubernetesとか機械学習が影響しているんでしょうか個人的にはドキュメントと操作性でGCPのが使いやすく感じました
あとCIとの連携もGCPのがやりやすかったです
慣れの問題だと思いますが、、特に機械学習をやるときにSageServerとTensorflow周りが使いにくかった印象です
料金面の優越は正直分かりませんが、ある程度のアクセス数が無い限りはどっちでもいいのではと思える額でした。
個人開発で使う分ではGCPの永久無料枠と最初の3万円無料は大きかったですGCPのai-platformはかなり安いので、colabとか使ってる人は試しに使ってみてもいいかもしれません
僕は最初AWSだったんですけど、機械学習があってGCPに乗り換えました