TECH PLAY

SCSKクラウドソリューション

SCSKクラウドソリューション の技術ブログ

1226

こんにちは、広野です。 検証で Amazon Linux 2023 のデスクトップ環境 (GUI) が必要になり、最新ソリューションを調べてたところ Amazon DCV を使ってみたくなったので環境をつくってみました。 Amazon DCV について 私が説明するまでもなく AWS 公式サイトに説明があるので、そちらを紹介します。もともと NICE-DCV という名前で開発されていたツールを AWS が買収したものです。 Amazon DCV - Amazon Web Services (AWS) aws.amazon.com   そして、構築の仕方も re:Post にありますので参考にしました。 Install GUI (graphical desktop) on Amazon EC2 instances running Amazon Linux 2023 (AL2023) Steps to install desktop environment, desktop manager and Amazon DCV high performance remote display protocol server on ... repost.aws   作成した環境 パブリックサブネットの Amazon Linux 2023 インスタンスに Amazon DCV をインストールしています。 セキュリティグループで、指定したソース IP アドレスからのみアクセスできるようにします。 ログインユーザー名は ec2-user、初期パスワードは AWS Secrets Manager で生成します。 Amazon EC2 インスタンスには Elastic IP アドレスを割り当て、アクセスする URL が固定になるようにします。 AWS CloudFormation で一発デプロイします。Amazon DCV のインストールは userdata スクリプト内で自動実行します。 既存 VPC を使用する前提です。 ポートを 443 にしたかったんですが、QUIC というプロトコル (UDPを使用、1000番以上のポート使用を推奨) が使えるとレスポンスが向上するようだったので、今回はデフォルトのままにしました。 デプロイすると、AWS CloudFormation スタックの出力欄に URL が表示されます。単純に、Elastic IP に HTTPS 8443 番ポートでアクセスするだけのものです。 ブラウザでアクセスすると、ログイン画面が表示されます。 初期パスワードは AWS Secrets Manager で生成されるので、マネジメントコンソールから参照します。ログイン後、パスワード変更してもらえるとより安心です。 無事ログインできると、Amazon Linux 2023 のデスクトップ画面が表示されます。これで検証できそうです!   AWS CloudFormation テンプレート パラメータとして、既存の VPC、サブネットを選択できるようにしています。 自分の IAM ユーザー名をパラメータとして指定します。AWS Secrets Manager の閲覧権限に使用されます。 インスタンスタイプは Amazon DSV の要件上最低でも medium 以上にする必要があるので t3.medium 一択にしています。 EBS のボリュームサイズ、OS のタイムゾーンをパラメータで指定できるようにしています。 AWSTemplateFormatVersion: "2010-09-09" Description: The CloudFormation template that creates a remote-accessible EC2 instance with Amazon DCV. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 VpcId: Type: AWS::EC2::VPC::Id Description: Choose a existing VPC ID you deploy the EC2 instance in. InstanceSubnet: Type: AWS::EC2::Subnet::Id Description: Choose an existing Public Subnet ID you deploy the EC2 instance in. InstanceType: Type: String Description: EC2 instance type for dcv-server. Default: t3.medium AllowedValues: - t3.medium InstanceVolumeSize: Type: Number Description: The volume size in GB Default: 10 AllowedSubnet: Type: String Description: Allowed source IPv4 subnet and subnet mask. (e.g. xxx.xxx.xxx.xxx/32) Default: xxx.xxx.xxx.xxx/32 MaxLength: 18 MinLength: 9 YourIamUserName: Type: String Description: Your IAM user name. This is used for the permission of your dcv-server credential. Default: youriamusername MaxLength: 30 MinLength: 1 TimeZone: Type: String Description: The specified time zone. Default: Asia/Tokyo MaxLength: 30 MinLength: 1 Resources: # ------------------------------------------------------------# # EC2 Launch template # ------------------------------------------------------------# EC2LaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: !Sub dcv-server-${SystemName}-${SubName} LaunchTemplateData: InstanceType: !Ref InstanceType ImageId: >- {{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}} BlockDeviceMappings: - Ebs: VolumeSize: !Ref InstanceVolumeSize VolumeType: gp3 DeleteOnTermination: true Encrypted: true DeviceName: /dev/xvda NetworkInterfaces: - SubnetId: !Ref InstanceSubnet Groups: - !Ref Ec2SecurityGroup DeviceIndex: 0 AssociatePublicIpAddress: false MetadataOptions: HttpTokens: required Monitoring: Enabled: true TagSpecifications: - ResourceType: volume Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} DependsOn: - Ec2SecurityGroup # ------------------------------------------------------------# # EC2 Security Group # ------------------------------------------------------------# Ec2SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VpcId GroupDescription: Allow dcv-server access via ALB only SecurityGroupIngress: - Description: Allow HTTPS from clients FromPort: 8443 IpProtocol: tcp ToPort: 8443 CidrIp: !Ref AllowedSubnet - Description: Allow QUIC from clients FromPort: 8443 IpProtocol: udp ToPort: 8443 CidrIp: !Ref AllowedSubnet SecurityGroupEgress: - Description: Allow all outbound traffic IpProtocol: -1 CidrIp: 0.0.0.0/0 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub SG-dcv-server-${SystemName}-${SubName} # ------------------------------------------------------------# # EC2 Role / Instance Profile (IAM) # ------------------------------------------------------------# Ec2Role: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: CodeWhisperer requires '*' as a resource, reference https://docs.aws.amazon.com/codewhisperer/latest/userguide/cloud9-setup.html#codewhisperer-IAM-policies Properties: RoleName: !Sub Ec2Role-dcv-server-${SystemName}-${SubName} Description: This role allows EC2 instance to invoke S3 and SSM. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore - arn:aws:iam::aws:policy/AWSCodeCommitPowerUser - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy Policies: - PolicyName: !Sub QDeveloperPolicy-${SystemName}-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Action: - "codewhisperer:GenerateRecommendations" Resource: "*" Effect: Allow - PolicyName: !Sub Ec2SecretsPolicy-${SystemName}-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - secretsmanager:GetResourcePolicy - secretsmanager:GetSecretValue - secretsmanager:DescribeSecret - secretsmanager:ListSecretVersionIds Resource: - !GetAtt SecretDcvServer.Id - PolicyName: !Sub Ec2S3Policy-${SystemName}-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "s3:GetObject" Resource: - !Sub "arn:aws:s3:::dcv-license.${AWS::Region}/*" DependsOn: - SecretDcvServer Ec2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Ref Ec2Role Path: / Roles: - !Ref Ec2Role DependsOn: - Ec2Role # ------------------------------------------------------------# # Secrets Manager # ------------------------------------------------------------# SecretDcvServer: Type: AWS::SecretsManager::Secret Properties: Name: !Sub dcv-server-${SystemName}-${SubName} Description: dcv-server credential GenerateSecretString: PasswordLength: 8 ExcludePunctuation: true IncludeSpace: false RequireEachIncludedType: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} SecretDcvServerResourcePolicy: Type: AWS::SecretsManager::ResourcePolicy Properties: BlockPublicPolicy: true SecretId: !Ref SecretDcvServer ResourcePolicy: Version: "2012-10-17" Statement: - Effect: Deny Principal: "*" Action: secretsmanager:GetSecretValue Resource: "*" Condition: StringNotEquals: aws:PrincipalArn: - !Sub "arn:aws:iam::${AWS::AccountId}:user/${YourIamUserName}" - !GetAtt Ec2Role.Arn # ------------------------------------------------------------# # Elastic IP address for EC2 instance # ------------------------------------------------------------# EipEc2: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub eip-dcv-server-${SystemName}-${SubName} # ------------------------------------------------------------# # EIP Association # ------------------------------------------------------------# EIPAssociation: Type: AWS::EC2::EIPAssociation Properties: AllocationId: !GetAtt EipEc2.AllocationId InstanceId: !Ref Ec2Instance DependsOn: - Ec2Instance - EipEc2 # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# Ec2Instance: Type: AWS::EC2::Instance Properties: IamInstanceProfile: !Ref Ec2InstanceProfile LaunchTemplate: LaunchTemplateId: !Ref EC2LaunchTemplate Version: !GetAtt EC2LaunchTemplate.LatestVersionNumber UserData: Fn::Base64: !Sub | #!/bin/bash set -euxo pipefail timedatectl set-timezone ${TimeZone} # Install packages dnf update -y dnf groupinstall "Desktop" -y # Setup GDM sed -i '/^\[daemon\]/a WaylandEnable=false' /etc/gdm/custom.conf systemctl set-default graphical.target # Get credential from Secrets Manager and set ec2-user password PASSWORD=$(aws secretsmanager get-secret-value --secret-id "${SecretDcvServer.Id}" --region "${AWS::Region}" --query SecretString --output text) echo "ec2-user:${!PASSWORD}" | chpasswd sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config systemctl restart sshd # Install dcv-server cd /tmp rpm --import https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY curl -L -O https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-amzn2023-$(arch).tgz tar -xvzf nice-dcv-amzn2023-$(arch).tgz && cd nice-dcv-*-amzn2023-$(arch) dnf install -y ./nice-dcv-server-*.rpm dnf install -y ./nice-dcv-web-viewer-*.rpm dnf install -y ./nice-xdcv-*.rpm systemctl enable dcvserver sed -i "/^\[session-management\/automatic-console-session/a owner=\"ec2-user\"\nstorage-root=\"%home%\"" /etc/dcv/dcv.conf sed -i "s/^#create-session/create-session/g" /etc/dcv/dcv.conf dnf install -y cups usermod -a -G sys dcv systemctl enable --now cups dnf install -y xorg-x11-drv-dummy tee /etc/X11/xorg.conf > /dev/null << EOF Section "Device" Identifier "DummyDevice" Driver "dummy" Option "UseEDID" "false" VideoRam 512000 EndSection Section "Monitor" Identifier "DummyMonitor" HorizSync 5.0 - 1000.0 VertRefresh 5.0 - 200.0 Option "ReducedBlanking" EndSection Section "Screen" Identifier "DummyScreen" Device "DummyDevice" Monitor "DummyMonitor" DefaultDepth 24 SubSection "Display" Viewport 0 0 Depth 24 Virtual 4096 2160 EndSubSection EndSection EOF if [ -f /usr/bin/nvidia-xconfig ]; then /usr/bin/nvidia-xconfig --preserve-busid --enable-all-gpus dnf install -y vulkan-tools glx-utils fi reboot Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub dcv-server-${SystemName}-${SubName} DependsOn: - Ec2InstanceProfile # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # EIP DcvServerUrl: Value: !Sub "https://${EipEc2.PublicIp}:8443"   まとめ いかがでしたでしょうか? ベタに Amazon EC2 インスタンスをインターネットからアクセスさせる環境になっているのはイケてないので、Amazon DCV がインストールされたプライベートサブネットの EC2 インスタンスに対して Fleet Manager 経由のリモートアクセスをサポートしてくれるような、AWS のアップデートを期待しています。 本記事が皆様のお役に立てれば幸いです。
アバター
本記事は 夏休みクラウド自由研究2025 8/25付の記事です 。 こんにちは、稲葉です。 近年次々と生成AIの技術が登場して、めまぐるしいですね。 クラウド自由研究の記事をきっかけに触ってみようと思いました。 先日AWSからリリースされたKiroは、AIが仕様書やタスクリストを作成しながら実装を行ってくれる「仕様駆動開発」が特徴です。 以下の記事に仕様駆動開発の流れが紹介されています。 Hey Kiro, Spotifyのプレイリストで打線組んで。 AWSが発表した次世代エージェント型IDE「Kiro」を実際に触ってみて、Webアプリケーションを構築してみた体験記となります。 blog.usize-tech.com 2025.08.14 未来を感じたので私もKiroを触ってみようと思います。 本記事では、Kiroで下記4つの内容を試してみようと思います。 Agent Steering機能を試す  Agent Hooks機能を試す 既存のコードを読み取り、仕様書を生成する Terraformのテストコードの作成とテストを実施する また、下記2つのMCPサーバーを利用しています。 awslabs-aws-documentation-mcp-server awslabs-aws-terraform-mcp-server   用意したTerraformコード 下記のようなシンプルな構成のTerraformコードを用意しました。 . ├── main.tf <- new! └── .kiro └── settings └── mcp.json EC2インスタンスのパブリックIPにアクセスすると、hello worldと返ってくるようにしています。 また、セッションマネージャーでインスタンスにアクセスできるようにしています。 Terraformコード(main.tf) terraform { backend"local" { path="kiro-test.tfstate" } required_providers { aws={ source="hashicorp/aws" version="~> 5.57.0" } } } provider "aws" { region="ap-northeast-1" profile="<プロファイル>" } # VPC resource "aws_vpc" "vpc" { cidr_block="10.0.0.0/16" } resource "aws_internet_gateway" "igw" { vpc_id=aws_vpc.vpc.id } resource "aws_subnet" "public_subnet" { vpc_id=aws_vpc.vpc.id cidr_block="10.0.1.0/24" availability_zone="ap-northeast-1a" map_public_ip_on_launch=true } # ルートテーブル resource "aws_route_table" "public_route" { vpc_id=aws_vpc.vpc.id route { cidr_block="0.0.0.0/0" gateway_id=aws_internet_gateway.igw.id } } resource "aws_route_table_association" "public_route_association" { subnet_id=aws_subnet.public_subnet.id route_table_id=aws_route_table.public_route.id } # EC2インスタンス用セキュリティグループ resource "aws_security_group" "web_sg" { name="web-sg" description="Allow HTTP from specific IP" vpc_id=aws_vpc.vpc.id } resource "aws_vpc_security_group_ingress_rule" "allow_http_rule" { security_group_id=aws_security_group.web_sg.id cidr_ipv4="<接続を許可するIPアドレス>" from_port=80 ip_protocol="tcp" to_port=80 } resource "aws_vpc_security_group_egress_rule" "egress_rule" { security_group_id=aws_security_group.web_sg.id cidr_ipv4="0.0.0.0/0" from_port=0 ip_protocol="-1" to_port=0 } # SSM用IAMロール resource "aws_iam_role" "ssm_role" { name="ec2-ssm-role" assume_role_policy=jsonencode({ Version="2012-10-17" Statement= [{ Action="sts:AssumeRole" Effect="Allow" Principal= { Service="ec2.amazonaws.com" } }] }) } resource "aws_iam_role_policy_attachment" "ssm_attach" { role=aws_iam_role.ssm_role.name policy_arn="arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } resource "aws_iam_instance_profile" "ssm_profile" { name="ec2-ssm-profile" role=aws_iam_role.ssm_role.name } # EC2 resource "aws_instance" "web_instance" { ami="ami-0f36dcfcc94112ea1"# Amazon Linux 2 (東京) instance_type="t3.micro" subnet_id=aws_subnet.public_subnet.id vpc_security_group_ids=[aws_security_group.web_sg.id] iam_instance_profile=aws_iam_instance_profile.ssm_profile.name user_data=<<-EOF #!/bin/bash yum update -y amazon-linux-extras install nginx1 -y systemctl enable nginx systemctl start nginx echo "hello world" > /usr/share/nginx/html/index.html EOF } Agent Steeringを試す Agent Steeringはプロジェクトに関する知識をKiroに与えるための機能です。 プロジェクトで作成するアプリケーションについての知識や、プロジェクトで使用するツールについての知識、プロジェクトを進める上でのルールについての知識など、さまざまな知識を与えることができます。 プロジェクトを進める上での2つのルールを設定しようと思います。 やりとりを日本語で行うこと 作成されるリソースにはタグが付いていること やりとりの日本語化 KiroのVibeモードでやりとりを日本語化するSteeringの作成をお願いしました。 Kiroとのやりとりを日本語で行うようにするSteeringファイルを作成してください するとプロジェクトフォルダの.kiroディレクトリ内にSteering用のディレクトリが作成され、その中に作成をお願いしたSteeringファイルができていました。 . ├── main.tf └── .kiro ├── settings │ └── mcp.json └── steering <- new! └── language.md <- new! Steeringファイルはマークダウン形式で書かれていて、人間が呼んでもわかりやすい形式になってました。 日本語化のお願いのほかにも、コミュニケーションのスタイルや日本語化しないことまで設定してくれていて気が利きますね! --- inclusion: always --- # 日本語コミュニケーションガイドライン ## 基本言語設定 - **主要言語**: すべての回答とコミュニケーションは日本語で行う - **コード内コメント**: 日本語でコメントを記述 - **ドキュメント**: README、設計書等も日本語で作成 - **エラーメッセージ**: 可能な限り日本語で説明 ## コミュニケーションスタイル - **敬語**: 丁寧語を基本とし、親しみやすい口調を心がける - **専門用語**: 必要に応じて英語の技術用語も併記(例:コンテナ(Container)) - **説明方法**: 段階的で分かりやすい説明を提供 - **質問対応**: 不明な点は積極的に確認する ## 文書作成ルール - **見出し**: 階層構造を明確にする - **箇条書き**: 情報を整理して提示 - **コード例**: 日本語コメント付きで提供 - **手順書**: ステップバイステップで記述 ## 例外事項 - **コマンド**: CLIコマンドは英語のまま - **設定ファイル**: JSON、YAML等の設定値は英語 - **URL・パス**: ファイルパスやURLは英語 - **ログ出力**: システムログは元の言語のまま ## 対応範囲 - **技術説明**: インフラ、開発、運用すべて日本語 - **トラブルシューティング**: 問題解決も日本語で対応 - **ベストプラクティス**: 推奨事項も日本語で提示 - **レビューコメント**: コードレビューも日本語 ヘッダーの下記記述は、Steeringファイルを読み込むタイミングを設定する箇所です。 --- inclusion: always --- 3つのタイミングを設定できます always(default): 常にこのSteeringファイルを読む fileMatch: 指定したファイルを扱うときにこのSterringファイルを読む manual: 通常このSteeringファイルを読まない。チャットで#<Sterringファイル名>のように指定した時だけ読む Steering - Docs - Kiro Guide Kiro's AI with project-specific context through markdown documents that define your standards, architecture, and c... kiro.dev 作成するリソースへのタグ付け 次にTerraformで作成されるリソースにタグを付与するよう、設定の作成をお願いしました。 このプロジェクトのTerraformから生成される全てのAWSリソースに次のタグを付与するルールを記載してください。 - ManagedBy: Terraform - Project: Kiro-test 先ほどのように.kiro/steeringの下にtag-rules.mdというSteeringファイルができていました。 default_tagsでTerraformが生成するすべてのリソースにManagedByタグとProjectタグを付与する設定をしていますね。 また、各リソースにはdefault_tagsで付与するタグの他にNameタグとEnvironmentタグを付与する設定になっています。 いい感じですね! --- inclusion: always --- # AWS リソースタグ付与ルール ## 必須タグ Kiroで生成・管理する全てのAWSリソースには以下のタグを必ず付与すること: ### 基本タグ(このプロジェクト固有) - **ManagedBy**: `Terraform` - Terraformによって管理されていることを示す - **Project**: `Kiro-test` - このプロジェクト専用の識別子 ## Terraformでの実装例 ### リソース共通タグ ```hcl locals { common_tags = { ManagedBy = "Terraform" Project = "Kiro-test" } } ``` ### プロバイダーレベルでのデフォルトタグ ```hcl provider "aws" { default_tags { tags = local.common_tags } } ``` ## 適用ルール 1. **全リソース必須**: EC2、S3、RDS、Lambda等すべてのAWSリソース 2. **一貫性**: プロジェクト内で統一されたタグ命名規則 3. **自動化**: Terraformのlocalsとdefault_tagsを活用 4. **検証**: タグが正しく付与されているかの確認を含める ## 例外処理 - 一時的なテストリソースでも必須タグ(`ManagedBy: Terraform`, `Project: Kiro-test`)は必須 - サードパーティサービス連携で制限がある場合は代替手段を検討 ## プロジェクト固有設定 このプロジェクト「Kiro-test」では以下の固定値を使用: - **ManagedBy**: 必ず `Terraform` を設定 - **Project**: 必ず `Kiro-test` を設定 ### 実装例 ```hcl# 全リソースに自動適用 provider "aws" { default_tags { tags = { ManagedBy = "Terraform" Project = "Kiro-test" } } } # 個別リソースでの追加タグ例 resource "aws_instance" "example" { # ... その他の設定 ... tags = merge(local.common_tags, { Name = "example-instance" Environment = "dev" }) } ``` コードから仕様書を生成する Kiroではspecsという単位でプロジェクトを管理し、下記3フェーズでシステムを構築します。 requirements : 要件定義 design: 技術設計 tasks: タスクリストの作成、実行 tasksフェーズが終了後、Kiroは仕様書を作成します。 本稿では既存のterraformファイルをKiroに読み込ませ、上記フェーズの実施と仕様書の生成ができるか確かめようと思います。 要件定義フェーズ 次の画像のように、vibeモードでspecsの作成をお願いしました。 specsを作成すると、1番目の要件定義フェーズに入ります。 このフェーズは、プロンプトで指定した内容から要件を定義し、requirements.mdというファイルにまとめるフェーズです。 やりとりを見ていくと、1行目では日本語化するSteeringが効いていませんね。 この行はKiroがプロンプトの内容からSpecモードの方が適切だと判断し、VibeモードからSpecモードに切り替えましょうかという提案をしてきている内容でした。Yesで切り替えました。 1行目以外では日本語でやりとりできています。 タグ付けルールのSteeringは効いていて、必須タグが設定されていないよと指定してくれています。 他にも色々とKiroにアドバイスをいただき、requirements.mdが作られました。 . ├── main.tf └── .kiro ├── settings │ └── mcp.json ├── specs <- new! │ └── terraform-infrastructure-enhancement <- new! │ └── requirements.md <- new! └── steering └── language.md 今回はシンプルな環境で検証したいので、Kiroのアドバイスは無視します。 それでは、作成されたrequirements.md(要件定義書)を確認します。 # 要件定義書 ## はじめに 現在のTerraformインフラ構成にタグ付与ルールを適用するための機能です。ステアリングルールで定義された必須タグ(ManagedBy: Terraform、Project: Kiro-test)をすべてのAWSリソースに自動的に適用します。 ## 要件 ### 要件1: タグ付与ルールの適用 **ユーザーストーリー:** インフラ管理者として、すべてのAWSリソースに統一されたタグが自動的に付与されるようにしたい。これにより、リソースの管理と追跡が容易になる。 #### 受け入れ基準 1. WHEN Terraformリソースが作成される THEN システムは ManagedBy="Terraform" タグを自動的に付与する 2. WHEN Terraformリソースが作成される THEN システムは Project="Kiro-test" タグを自動的に付与する 3. WHEN プロバイダーレベルでdefault_tagsが設定される THEN すべてのリソースに共通タグが適用される 4. WHEN 個別リソースに追加タグが必要な場合 THEN 共通タグとマージして適用される requirements.mdでは、既存のコードに対してKiroが加える要件の内容が記載されています。 既存コードの内容は無いですね。 さらにテスト要件も伝えました。 この内容で次のフェーズ(design.md(技術設計書)の作成) に進もうと思います。 技術設計書には、既存コードの内容も含まれていてほしいです。 技術設計フェーズ design.md(技術設計書)の作成をお願いしました。 design.mdが作られました。 . ├── main.tf └── .kiro ├── settings │ └── mcp.json ├── specs │ └── terraform-infrastructure-enhancement │ ├── requirements.md | └── design.md <- new! └── steering └── language.md 確認してみると、こちらは既存コードの内容が入っていました! アーキテクチャの情報やタグ付け戦略、テスト戦略などが欲しい情報が記載されていて、満足できる出来栄えでした! # 設計文書 ## 概要 現在のTerraformインフラ構成にタグ付与ルールを適用するための設計です。 ステアリングルールで定義された必須タグ(ManagedBy: Terraform、Project: Kiro-test) をすべてのAWSリソースに自動的に適用し、リソース管理の一貫性と追跡可能性を向上させます。 ## アーキテクチャ ### 現在の構成分析 現在のmain.tfファイルには以下のリソースが定義されています: - VPC(aws_vpc.vpc) - インターネットゲートウェイ(aws_internet_gateway.igw) - パブリックサブネット(aws_subnet.public_subnet) - ルートテーブル(aws_route_table.public_route) - ルートテーブル関連付け(aws_route_table_association.public_route_association) - セキュリティグループ(aws_security_group.web_sg) - セキュリティグループルール(aws_vpc_security_group_ingress_rule、aws_vpc_security_group_egress_rule) - IAMロール(aws_iam_role.ssm_role) - IAMロールポリシーアタッチメント(aws_iam_role_policy_attachment.ssm_attach) - IAMインスタンスプロファイル(aws_iam_instance_profile.ssm_profile) - EC2インスタンス(aws_instance.web_instance) ### タグ付与戦略 #### 1. プロバイダーレベルでのデフォルトタグ設定 ```hcl provider "aws" { region = "ap-northeast-1" profile = "inaba" default_tags { tags = { ManagedBy = "Terraform" Project = "Kiro-test" } } } ``` #### 2. ローカル変数での共通タグ定義 ```hcl locals { common_tags = { ManagedBy = "Terraform" Project = "Kiro-test" } } ``` ## コンポーネントと インターフェース ### 1. タグ管理コンポーネント **目的**: 全リソースに統一されたタグを適用する **実装方法**: - プロバイダーレベルでのdefault_tags設定 - localsブロックでの共通タグ定義 - 個別リソースでの追加タグ対応 ### 2. リソース識別コンポーネント **目的**: 各リソースに適切な名前とタグを付与する **実装方法**: - 既存リソースへのNameタグ追加 - リソース種別に応じた追加タグの設定 ### 3. テスト自動化コンポーネント **目的**: インフラの動作とセキュリティを自動的に検証する **実装方法**: - Bashスクリプトによるテスト自動化 - AWS CLIを使用したリソース状態確認 - curlコマンドによるWebサーバー動作確認 - nmapによるポートスキャン - Checkovによるセキュリティスキャン ### 4. セキュリティ検証コンポーネント **目的**: IPアドレス制限とSSM接続を検証する **実装方法**: - 複数IPアドレスからのアクセステスト - セッションマネージャー接続テスト - セキュリティグループルールの動作確認 ## データモデル ### タグデータ構造 ```hcl # 必須タグ(全リソース共通) common_tags = { ManagedBy = "Terraform" # 固定値 Project = "Kiro-test" # 固定値 } # リソース固有タグ(例) resource_specific_tags = { Name = "リソース名" Environment = "dev" Component = "web-server" } # 最終的なタグ(マージ結果) final_tags = merge(local.common_tags, local.resource_specific_tags) ``` ## エラーハンドリング ### 1. タグ適用失敗への対応 **問題**: 一部のAWSリソースでタグ適用がサポートされていない場合 **対応**: - Terraformの警告メッセージを確認 - サポートされていないリソースは文書化 - 代替手段(リソース名での識別など)を検討 ### 2. タグ制限への対応 **問題**: AWSのタグ制限(50個まで、キー/値の長さ制限など) **対応**: - 必須タグを優先 - 追加タグは必要最小限に抑制 - タグ命名規則の統一 ## テスト戦略 ### 1. タグ適用確認テスト **目的**: すべてのリソースに必須タグが適用されているかを確認 **方法**: ```bash # Terraform planでタグ設定を確認 terraform plan # AWS CLIでリソースのタグを確認 aws ec2 describe-instances --query 'Reservations[].Instances[].Tags' aws ec2 describe-vpcs --query 'Vpcs[].Tags' ``` ### 2. インフラ動作テスト **目的**: Terraformで構築したインフラが正しく動作することを確認 **方法**: ```bash # 構文チェック terraform validate # リソース作成確認 terraform plan terraform apply # Webサーバー動作確認 curl -I http://[EC2のパブリックIP] # ネットワーク接続確認 ping [EC2のパブリックIP] # セッションマネージャー接続確認 aws ssm start-session --target [インスタンスID] ``` ### 3. セキュリティテスト **目的**: セキュリティ設定が正しく機能することを確認 **方法**: ```bash # IPアドレス制限テスト(許可されたIP) curl -I http://[EC2のパブリックIP] --interface [許可されたIP] # IPアドレス制限テスト(拒否されるIP) curl -I http://[EC2のパブリックIP] --interface [許可されていないIP] # セキュリティスキャン checkov -f main.tf # ポート開放チェック nmap -p 1-65535 [EC2のパブリックIP] ``` ### 4. タグ一貫性テスト **目的**: プロジェクト全体でタグの一貫性を確認 **方法**: - 全リソースのタグをエクスポート - ManagedBy="Terraform"、Project="Kiro-test"の存在確認 - タグ値の正確性確認 ## 実装詳細 ### Phase 1: プロバイダー設定の更新 - default_tagsをプロバイダーブロックに追加 - 既存の設定を維持しながらタグ機能を追加 ### Phase 2: ローカル変数の定義 - common_tagsローカル変数を定義 - 将来の拡張性を考慮した構造 ### Phase 3: 個別リソースのタグ設定 - 既存リソースにNameタグを追加 - 必要に応じて追加のタグを設定 ### Phase 4: テストスクリプトの作成 - インフラ動作テスト用のスクリプト作成 - セキュリティテスト用のスクリプト作成 - IPアドレス制限テスト用のスクリプト作成 - セッションマネージャー接続テスト用のスクリプト作成 ### Phase 5: 検証とテスト実行 - terraform planでの変更内容確認 - 実際のリソースへのタグ適用確認 - インフラ動作テストの実行 - セキュリティテストの実行 - 既存機能の動作確認 ## 設計上の考慮事項 ### 1. 後方互換性 - 既存のリソース設定を変更せず、タグのみを追加 - インフラの動作に影響を与えない変更 ### 2. 拡張性 - 将来的な追加タグに対応できる構造 - 環境別(dev/staging/prod)への対応準備 ### 3. 保守性 - タグ定義の一元管理 - 変更時の影響範囲を最小化 ### 4. コスト管理 - タグによるリソース使用量の追跡が可能 - プロジェクト別のコスト分析が容易 この設計書でtasks.md(タスクリスト)を作成します。 タスクリストの作成、実行フェーズ tasks.md(タスクリスト)の作成をお願いしました。 tasks.mdが作られました。 . ├── main.tf └── .kiro ├── settings │ └── mcp.json ├── specs │ └── terraform-infrastructure-enhancement │ ├── requirements.md | ├── design.md | └── tasks.md <- new! └── steering └── language.md タスクリストが作成され、各タスク毎に実行できるようになってました。 # 実装計画 - [ ] 1. Terraformタグ設定の実装 - プロバイダーレベルでdefault_tagsを設定し、すべてのリソースに必須タグを自動適用する - localsブロックで共通タグを定義し、保守性を向上させる - 既存リソースにNameタグを追加し、リソース識別を改善する -_要件: 1.1, 1.2, 1.3, 1.4_ - [ ] 2. インフラ動作テストスクリプトの作成 - Terraform構文チェックとリソース作成確認のスクリプトを作成する - Webサーバー動作確認(HTTP応答)のテストスクリプトを作成する - ネットワーク接続確認のテストスクリプトを作成する -_要件: 2.1, 2.2, 2.3, 2.4_ - [ ] 3. セキュリティテストスクリプトの作成 - セキュリティグループのポート制限確認スクリプトを作成する - IPアドレス制限テスト(許可されたIPからのアクセス)スクリプトを作成する - IPアドレス制限テスト(拒否されるIPからのアクセス)スクリプトを作成する -_要件: 2.5, 2.7, 2.8_ - [ ] 4. SSM接続テストスクリプトの作成 - セッションマネージャーでのEC2接続確認スクリプトを作成する - SSMロールとインスタンスプロファイルの動作確認を含める -_要件: 2.6, 2.9_ - [ ] 5. セキュリティスキャン機能の実装 - Checkovを使用したTerraformコードのセキュリティスキャンスクリプトを作成する - 高リスク脆弱性の検出と報告機能を実装する - IAM権限の最小権限原則チェック機能を実装する -_要件: 3.1, 3.2, 3.3_ - [ ] 6. 暗号化設定確認機能の実装 - EBSボリュームの暗号化設定確認スクリプトを作成する - リソースの暗号化状態を検証する機能を実装する -_要件: 3.4_ - [ ] 7. 統合テストスクリプトの作成 - 全テストを順次実行する統合テストスクリプトを作成する - テスト結果の集約と報告機能を実装する - テスト失敗時のエラーハンドリングを実装する -_要件: 2.1-2.9, 3.1-3.4_ - [ ] 8. ドキュメントとREADMEの作成 - テストスクリプトの使用方法を説明するREADMEを作成する - 各テストの目的と期待結果を文書化する - トラブルシューティングガイドを作成する -_要件: 全要件のサポート文書_ これらのタスクをすべて完了すると、下記8つのドキュメントがKiroによって生成されました。 README.md : 仕様書 TROUBLESHOOTING.md: トラブルシューティングガイド TEST_SPECIFICATIONS.md: 各テストの仕様書 TEST_README.md: インフラテスト詳細ガイド INTEGRATED_TEST_README.md: 統合テスト詳細ガイド SECURITY_TEST_README.md: セキュリティテスト詳細ガイド SSM_TEST_README.md: SSMテスト詳細ガイド SECURITY_SCAN_README.md: セキュリティスキャン詳細ガイド 作成された仕様書は長くなってしまうので本稿の最後に記載します。 (その他の仕様書は割愛します。) 他にもテストスクリプトが生成されたので、このスクリプトを使用してテストをしていきます。 テストを実施する Kiroが作成したテストスクリプトを使用してテストします。 Kiroは下記5つのテストスクリプトを生成しました。 test-infrastructure.sh test-security.sh test-ssm.sh test-security-scan.sh test-encryption.sh また、これらのテストスクリプトをまとめて実行できるように、run-all-tests.shも生成されました。 run-all-tests.shを使用して、まとめてテストしようと思います。   まずTerraformでリソースをプロビジョニングします。 // Terraformの初期設定 terraform init // Terraformをdry runする terraform plan // リソースをデプロイする terraform apply EC2の立ち上がりやyunのアップデート、Webサーバーの立ち上がりを待つため、120秒ほど待つ sleep 120 その後テストプログラムを実行します。 export AWS_PROFILE=<プロファイル名> export AWS_REGION=ap-northeast-1 ./run-all-tests.sh 結果が下記になります。 ========================================== Terraform Infrastructure Enhancement 統合テスト開始 ========================================== [INFO] 開始日時: 2025-08-24 00:43:21 ========================================== 前提条件チェック ========================================== [SUCCESS] terraform が利用可能です [SUCCESS] aws が利用可能です [SUCCESS] curl が利用可能です [SUCCESS] jq が利用可能です [SUCCESS] AWS認証情報が設定されています [INFO] アカウントID: xxxxxxx [INFO] ユーザー/ロール: arn:aws:iam::xxxxxxx:user/xxxxx [SUCCESS] Terraformが初期化されています [SUCCESS] test-infrastructure.sh が見つかりました [SUCCESS] test-security.sh が見つかりました [SUCCESS] test-ssm.sh が見つかりました [SUCCESS] test-security-scan.sh が見つかりました [SUCCESS] test-encryption.sh が見つかりました [SUCCESS] すべての前提条件が満たされています ========================================== 1. インフラ動作テスト実行中... ========================================== [SUCCESS] インフラ動作テストが完了しました [SUCCESS] ✅ インフラ動作テスト (実行時間: 25秒) ========================================== 2. セキュリティテスト実行中... ========================================== [SUCCESS] セキュリティテストが完了しました [SUCCESS] ✅ セキュリティテスト (実行時間: 21秒) ========================================== 3. SSM接続テスト実行中... ========================================== [SUCCESS] SSM接続テストが完了しました [SUCCESS] ✅ SSM接続テスト (実行時間: 15秒) ========================================== 4. セキュリティスキャンテスト実行中... ========================================== [WARNING] セキュリティスキャンテストで問題が検出されました ================================== セキュリティスキャン結果サマリー ================================== 総テスト数: 10 成功: 8 失敗: 2 [ERROR] 2 個のテストが失敗しました ⚠️ セキュリティ改善が必要です 詳細なレポートを確認してください: - security_scan_report.txt - security_scan_results.json 推奨アクション: 1. 高リスク脆弱性を優先的に修正 2. セキュリティグループのCIDR制限を見直し 3. EBSボリュームの暗号化を有効化 4. IAM権限の最小権限原則を確認 [SUCCESS] ✅ セキュリティスキャンテスト (実行時間: 13秒) ========================================== 5. 暗号化設定確認テスト実行中... ========================================== [ERROR] 暗号化設定確認テストでエラーが発生しました Terraform設定例: resource "aws_instance" "example" { # ... 他の設定 ... root_block_device { encrypted = true kms_key_id = "alias/aws/ebs" # オプション: カスタムKMSキー } } ========================================== 暗号化設定確認テスト結果 ========================================== 総テスト数: 3 成功: 1 失敗: 2 ✗ 一部の暗号化テストが失敗しました 暗号化設定確認結果をファイルに保存中... ℹ 結果がencryption_check_results.txtに保存されました [ERROR] ❌ 暗号化設定確認テスト (実行時間: 7秒) ========================================== 統合テスト結果レポート生成中... ========================================== [SUCCESS] 統合テスト結果レポートを integrated_test_report.txt に保存しました ========================================== 統合テスト結果サマリー ========================================== [INFO] 実行時間: 83秒 (1分23秒) [INFO] 総テストスイート数: 5 [SUCCESS] 成功: 4 [ERROR] 失敗: 1 [INFO] 詳細結果: ✅ インフラ動作テスト: 成功 (25秒) - Terraform構文チェック、リソース作成確認、ネットワーク接続、Webサーバー動作 ✅ セキュリティテスト: 成功 (21秒) - セキュリティグループ設定、IPアドレス制限、ポートアクセス制限 ✅ SSM接続テスト: 成功 (15秒) - セッションマネージャー接続、SSMロール・インスタンスプロファイル確認 ✅ セキュリティスキャンテスト: 成功 (13秒) - Checkovセキュリティスキャン、IAM権限チェック、脆弱性検出 ❌ 暗号化設定確認テスト: 失敗 (7秒) - EBSボリューム暗号化、アカウントレベル暗号化設定 [ERROR] ⚠️ 1 個のテストスイートで問題が検出されました [INFO] 詳細は integrated_test_report.txt を確認してください 色々と指定されていますね。 EBSボリュームの暗号化が有効化されていないので、色々とエラーが出ているように見えます。 このようにTerraformで作成したリソースをテストすることができました! Agent Hooksを試す Agent HooksはKiroで特定のイベント(ファイルの新規作成、保存、削除)が発生したとき、設定したアクションを実行するトリガーです。 Terraformコードが更新されたとき、セキュリティ的な問題が無いかチェックする設定の作成をお願いしました。 KiroのIDEからAgent Hooksを追加します。 Agent Hooksの設定画面が出るので、Describe a hook using natural languageの欄に下記のプロンプトを記載しました。 するとKiroが動き、Hooksファイルを作成してくれます。 Terraformのファイルの更新時、以下の問題がないかチェックするAgent Hooks作成してください。 - 認証情報のハードコーディング - セキュリティグループのインバウンドルールで0.0.0.0/0を開放 問題があれば、chatを開き問題の場所と修正案を提示してください プロジェクトフォルダの.kiroディレクトリ内にHooks用のディレクトリが作成され、その中にHooksファイルができていました。 . ├── main.tf └── .kiro ├── settings │ └── mcp.json ├── specs │ └── terraform-infrastructure-enhancement │ └── requirements.md ├── steering │ ├── language.md │ └── tag-rules.md └── hooks <- new! └── terraform-security-check.json <- new! こちらはjsonファイルで作成されました。 terraformのファイルが更新されたとき、エージェントがこのプロンプトを入力、実行してくれるみたいですね。 内容はシンプルで分かりやすいですね! { "enabled": true, "name":"Terraform セキュリティチェック", "description":"Terraformファイルの更新時に認証情報のハードコーディングとセキュリティグループの0.0.0.0/0開放をチェックし、問題があればチャットで修正案を提示します", "version":"1", "when":{ "type":"fileEdited", "patterns":[ "*.tf", "*.tfvars", "*.terraform" ] }, "then":{ "type":"askAgent", "prompt": "Terraformファイルが更新されました。以下のセキュリティ問題をチェックしてください:\n\n 1. **認証情報のハードコーディング**\n - AWS Access Key、Secret Key\n - パスワード、APIキー\n - データベース接続文字列\n - その他の機密情報\n\n 2. **セキュリティグループのインバウンドルール**\n - 0.0.0.0/0からのアクセス許可\n - 不適切なポート開放\n - 過度に緩いアクセス制御\n\n 問題を発見した場合は、以下の形式でチャットを開いて報告してください:\n - 問題の場所(ファイル名と行番号)\n - 具体的な問題内容\n - 推奨される修正案\n - セキュリティリスクの説明\n\n 問題がない場合は、「セキュリティチェック完了:問題は検出されませんでした」と報告してください。" } } Terraformコードのセキュリティグループを変更して、インバウンドルールの0.0.0.0/0を開放してみます(下記図の54行目)。   すると、Kiroによってインバウンドルールで0.0.0.0/0が空いていること検出されました! 最後に 初めてKiroを触りましたが、想像以上にドキュメントを作成してくれるのですごいなと思いました。 Terraformのテストの方法で悩んでいたので、checkovなどのツールを知れたのも良かったです。 Specsモードは当然ですが、Agent SteeringやAgent Hooksなどの各機能も便利で、引き続きKiro触って行きたいなと思います。 記事を書いている間にKiroのプレビュー版が終わって慌てましたが、無事書き終えられてよかったです。 作成された仕様書 # Terraform Infrastructure Enhancement プロジェクト ## 概要 このプロジェクトは、Terraformで構築したAWSインフラストラクチャに対して、統一されたタグ付与ルールの適用と包括的なテスト機能を提供します。 ## 主な機能 - **統一タグ付与**: すべてのAWSリソースに必須タグ(ManagedBy: Terraform、Project: Kiro-test)を自動適用 - **インフラ動作テスト**: Terraformで構築したインフラの動作確認 - **セキュリティテスト**: セキュリティグループ設定とIPアドレス制限の検証 - **SSM接続テスト**: セッションマネージャーによるEC2接続確認 - **セキュリティスキャン**: Checkovを使用したセキュリティ脆弱性検出 - **暗号化設定確認**: EBSボリュームなどの暗号化状態検証 - **統合テスト**: 全テストスイートの自動実行と結果集約 ## プロジェクト構成 ``` . ├── README.md # このファイル ├── main.tf # Terraformメイン設定 ├── outputs.tf # Terraform出力定義 ├── run-all-tests.sh # 統合テストスクリプト ├── test-infrastructure.sh # インフラ動作テスト ├── test-security.sh # セキュリティテスト ├── test-ssm.sh # SSM接続テスト ├── test-security-scan.sh # セキュリティスキャンテスト ├── test-encryption.sh # 暗号化設定確認テスト ├── test-network-detailed.sh # 詳細ネットワークテスト ├── .kiro/ │ ├── specs/terraform-infrastructure-enhancement/ │ │ ├── requirements.md # 要件定義書 │ │ ├── design.md # 設計文書 │ │ └── tasks.md # 実装計画 │ └── steering/ │ ├── tag-rules.md # タグ付与ルール │ └── language.md # 日本語コミュニケーションガイド └── 各種READMEファイル/ ├── TEST_README.md # インフラテスト詳細ガイド ├── INTEGRATED_TEST_README.md # 統合テスト詳細ガイド ├── SECURITY_TEST_README.md # セキュリティテスト詳細ガイド ├── SSM_TEST_README.md # SSMテスト詳細ガイド └── SECURITY_SCAN_README.md # セキュリティスキャン詳細ガイド ``` ## 前提条件 ### 必要なツール 以下のツールがインストールされている必要があります: - **Terraform** (>= 1.0) - **AWS CLI** (>= 2.0) - 認証情報設定済み - **jq** - JSON処理用 - **curl** - HTTP通信用 - **nmap** - ポートスキャン用(オプション) - **Checkov** - セキュリティスキャン用 ### インストール方法(macOS) ```bash # Homebrew経由でのインストール brew install terraform awscli jq curl nmap # Checkovのインストール brew install checkov # または pipx install checkov ``` ### AWS認証情報の設定 ```bash # AWS CLIプロファイルの設定 aws configure --profile inaba # または環境変数での設定 export AWS_PROFILE=inaba export AWS_REGION=ap-northeast-1 ``` ## クイックスタート ### 1. インフラのデプロイ ```bash # Terraformの初期化 terraform init # プランの確認 terraform plan # インフラのデプロイ terraform apply ``` ### 2. 統合テストの実行 ```bash # 全テストスイートを実行 ./run-all-tests.sh ``` ### 3. 個別テストの実行 ```bash # インフラ動作テスト ./test-infrastructure.sh # セキュリティテスト ./test-security.sh # SSM接続テスト ./test-ssm.sh # セキュリティスキャ ./test-security-scan.sh # 暗号化設定確認 ./test-encryption.sh ``` ## テストスイート詳細 ### 1. インフラ動作テスト (`test-infrastructure.sh`) **目的**: Terraformで構築したインフラの基本動作を確認 **テスト項目**: - Terraform構文チェック - リソース作成確認 - Webサーバー動作確認(HTTP応答) **期待結果**: すべてのリソースが正常に動作し、Webサーバーが「hello world」を返す ### 2. セキュリティテスト (`test-security.sh`) **目的**: セキュリティグループ設定とアクセス制限を検証 **テスト項目**: - セキュリティグループのポート制限確認 - 許可されたIPアドレスからのアクセステスト - 拒否されるIPアドレスからのアクセステスト - 危険ポートの開放チェック **期待結果**: 適切なアクセス制限が機能し、不要なポートが開放されていない ### 3. SSM接続テスト (`test-ssm.sh`) **目的**: セッションマネージャーによるEC2接続を確認 **テスト項目**: - EC2インスタンスの状態確認 - SSMエージェントの接続状態確認 - IAMロールとポリシーの確認 - セッションマネージャー接続テスト **期待結果**: SSM経由でEC2インスタンスに接続可能 ### 4. セキュリティスキャンテスト (`test-security-scan.sh`) **目的**: Checkovを使用したセキュリティ脆弱性の検出 **テスト項目**: - 基本セキュリティスキャン - 高リスク脆弱性検出 - IAM最小権限原則チェック - セキュリティグループ設定検証 **期待結果**: 高リスクの脆弱性が検出されない ### 5. 暗号化設定確認テスト (`test-encryption.sh`) **目的**: リソースの暗号化状態を検証 **テスト項目**: - EBSボリュームの暗号化設定確認 - アカウントレベルの暗号化設定確認 **期待結果**: 適切な暗号化設定が有効になっている ### 6. 統合テスト (`run-all-tests.sh`) **目的**: 全テストスイートを順次実行し、結果を集約 **機能**: - 前提条件チェック - 全テストスイートの順次実行 - 結果の集約と報告 - エラーハンドリング **期待結果**: すべてのテストスイートが成功し、包括的なレポートが生成される ## 出力ファイル テスト実行後、以下のファイルが生成されます: - `integrated_test_report.txt` - 統合テスト結果の包括的なレポート - `security_scan_results.json` - Checkovセキュリティスキャンの詳細結果 - `security_scan_report.txt` - セキュリティスキャンの要約レポート - `encryption_check_results.txt` - 暗号化設定確認の詳細結果 - `iam_check_results.txt` - IAM権限チェックの結果 - `sg_check_results.txt` - セキュリティグループチェックの結果 ## 要件対応表 | 要件ID | 要件内容 | 対応テストスクリプト | |--------|----------|---------------------| | 1.1-1.4 | タグ付与ルールの適用 | main.tf(実装済み) | | 2.1 | Terraform構文チェック | test-infrastructure.sh | | 2.2 | リソース作成確認 | test-infrastructure.sh | | 2.3 | Webサーバー動作確認 | test-infrastructure.sh | | 2.5 | セキュリティグループポート制限 | test-security.sh | | 2.6 | SSM接続確認 | test-ssm.sh | | 2.7 | 許可IPアドレスアクセス | test-security.sh | | 2.8 | 拒否IPアドレスアクセス | test-security.sh | | 2.9 | セッションマネージャー接続 | test-ssm.sh | | 3.1 | セキュリティスキャン実行 | test-security-scan.sh | | 3.2 | 高リスク脆弱性検出 | test-security-scan.sh | | 3.3 | IAM最小権限原則チェック | test-security-scan.sh | | 3.4 | 暗号化設定確認 | test-encryption.sh | ## トラブルシューティングガイド ### よくある問題と解決方法 #### 1. AWS認証エラー **問題**: ``` Unable to locate credentials. You can configure credentials by running "aws configure" ``` **解決方法**: ```bash # AWS CLIの認証情報を設定 aws configure --profile inaba # または環境変数で設定 export AWS_PROFILE=inaba export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key export AWS_REGION=ap-northeast-1 # 認証情報の確認 aws sts get-caller-identity ``` #### 2. Terraformの初期化エラー **問題**: ``` Error: Backend initialization required ``` **解決方法**: ```bash # Terraformの初期化 terraform init # プロバイダーの更新が必要な場合 terraform init -upgrade ``` #### 3. EC2インスタンスが見つからない **問題**: ``` 実行中のEC2インスタンスが見つかりません ``` **解決方法**: ```bash # Terraformでインフラを構築 terraform plan terraform apply # インスタンスの状態を確認 aws ec2 describe-instances --filters "Name=tag:Name,Values=kiro-test-web-instance" # Terraformの状態確認 terraform show ``` #### 4. HTTPアクセスが失敗する **問題**: ``` HTTPアクセスが失敗しました ``` **原因と解決方法**: **原因1**: EC2インスタンスが起動中 ```bash # インスタンスの状態確認 aws ec2 describe-instances --instance-ids $(terraform output -raw instance_id) # 解決方法: 1-2分待ってから再実行 ``` **原因2**: セキュリティグループの設定問題 ```bash # セキュリティグループの確認 aws ec2 describe-security-groups --group-ids $(terraform output -raw security_group_id) # 解決方法: main.tfのセキュリティグループ設定を確認 ``` **原因3**: Webサーバー(nginx)が起動していない ```bash # SSMでインスタンスに接続してnginx状態確認 aws ssm start-session --target $(terraform output -raw instance_id) sudo systemctl status nginx sudo systemctl start nginx # 必要に応じて起動 ``` #### 5. SSM接続が失敗する **問題**: ``` SSMエージェントがオンラインではありません ``` **解決方法**: ```bash # インスタンスの起動から数分待つ sleep 120 # SSMエージェントの状態確認(インスタンス内で実行) sudo systemctl status amazon-ssm-agent sudo systemctl restart amazon-ssm-agent # IAMロールの確認 aws iam get-role --role-name $(terraform output -raw iam_role_name) aws iam list-attached-role-policies --role-name $(terraform output -raw iam_role_name) ``` #### 6. Checkovのインストールエラー **問題**: ``` externally-managed-environment ``` **解決方法**: ```bash # Homebrewを使用(推奨) brew install checkov # または pipx を使用 brew install pipx pipx install checkov # pipxのパスを追加 echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` #### 7. jqコマンドが見つからない **問題**: ``` jq: command not found ``` **解決方法**: ```bash # macOSの場合 brew install jq # Linuxの場合 sudo apt-get install jq # Ubuntu/Debian sudo yum install jq # CentOS/RHEL ``` #### 8. nmapが利用できない **問題**: ``` nmapが利用できないため、ポートスキャンをスキップします ``` **解決方法**: ```bash # macOSの場合 brew install nmap # Linuxの場合 sudo apt-get install nmap # Ubuntu/Debian sudo yum install nmap # CentOS/RHEL ``` #### 9. パブリックIPアドレスが取得できない **問題**: ``` パブリックIPアドレスを取得できませんでした ``` **解決方法**: ```bash # Terraformのoutput確認 terraform output # outputs.tfファイルの確認 cat outputs.tf # 必要に応じてoutputを追加 terraform apply ``` #### 10. セキュリティスキャンで多数の警告 **問題**: ``` 多数のセキュリティ警告が検出されました ``` **対応方法**: **高リスク問題の優先対応**: - CKV_AWS_260: セキュリティグループの0.0.0.0/0制限 - CKV_AWS_8: EBSボリューム暗号化 - CKV_AWS_79: EC2メタデータサービスv2強制 **設定例**: ```hcl # セキュリティグループの制限 resource "aws_vpc_security_group_ingress_rule" "allow_http_rule" { security_group_id = aws_security_group.web_sg.id cidr_ipv4 = "x.x.x.x/32" # 特定IPのみ from_port = 80 ip_protocol = "tcp" to_port = 80 description = "Allow HTTP from office IP" } # EBSボリューム暗号化 resource "aws_instance" "web_instance" { # ... 他の設定 ... root_block_device { encrypted = true } # メタデータサービスv2強制 metadata_options { http_tokens = "required" } } ``` ### デバッグのためのコマンド #### Terraformの状態確認 ```bash # 現在の状態表示 terraform show # 特定リソースの状態確認 terraform state show aws_instance.web_instance # 出力値の確認 terraform output ``` #### AWSリソースの直接確認 ```bash # EC2インスタンス一覧 aws ec2 describe-instances --filters "Name=tag:Project,Values=Kiro-test" # セキュリティグループ一覧 aws ec2 describe-security-groups --filters "Name=tag:Project,Values=Kiro-test" # VPC一覧 aws ec2 describe-vpcs --filters "Name=tag:Project,Values=Kiro-test" # SSM管理対象インスタンス一覧 aws ssm describe-instance-information ``` #### ログファイルの確認 ```bash # 統合テストレポート cat integrated_test_report.txt # セキュリティスキャン結果 cat security_scan_report.txt # 暗号化チェック結果 cat encryption_check_results.txt ``` ## パフォーマンス最適化 ### テスト実行時間の短縮 1. **並列実行**: 独立したテストは並列で実行可能 2. **キャッシュ活用**: Terraformプランのキャッシュを活用 3. **条件分岐**: 不要なテストをスキップする条件を追加 ### リソース使用量の最適化 1. **インスタンスタイプ**: 開発環境では小さなインスタンスタイプを使用 2. **自動停止**: テスト完了後のリソース自動停止 3. **スポットインスタンス**: コスト削減のためのスポットインスタンス活用 ## セキュリティベストプラクティス ### 1. アクセス制限 - セキュリティグループで必要最小限のポートのみ開放 - 特定IPアドレスからのアクセスに制限 - WAF(Web Application Firewall)の導入検討 ### 2. 暗号化 - 転送時暗号化(HTTPS)の使用 ### 3. 監査とログ - CloudTrailによるAPI呼び出しログ - VPCフローログによるネットワーク監視 - CloudWatchによるメトリクス監視 ### 4. IAM権限 - 最小権限の原則に従った権限設定 - マネージドポリシーの活用 - 定期的な権限レビュー ## CI/CDパイプライン統合 ### GitHub Actions例 ```yaml name: Infrastructure Tests on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 9 * * 1' # 毎週月曜日9時 jobs: terraform-tests: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: 1.5.0 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y jq curl nmap pip install checkov - name: Terraform Init run: terraform init - name: Run integrated tests run: ./run-all-tests.sh - name: Upload test results uses: actions/upload-artifact@v3 if: always() with: name: test-results path: | integrated_test_report.txt security_scan_results.json security_scan_report.txt ``` ## 継続的改善 ### 1. テストカバレッジの拡張 - パフォーマンステストの追加 - 負荷テストの実装 - 災害復旧テストの追加 ### 2. 自動化の強化 - テスト結果の自動通知 - 失敗時の自動ロールバック - 定期的なセキュリティスキャン ### 3. 監視とアラート - CloudWatchアラームの設定 - SNS通知の設定 - ダッシュボードの作成 ## サポートとコミュニティ ### ドキュメント - [Terraform公式ドキュメント](https://www.terraform.io/docs) - [AWS CLI公式ドキュメント](https://docs.aws.amazon.com/cli/) - [Checkov公式ドキュメント](https://www.checkov.io/) ### 問題報告 問題が発生した場合は、以下の情報を含めて報告してください: 1. 実行環境(OS、ツールバージョン) 2. エラーメッセージの全文 3. 実行したコマンド 4. 生成されたログファイル ### 更新履歴 - v1.0.0: 初回リリース - 基本的なタグ付与とテスト機能 - 今後の予定: - パフォーマンステストの追加 - 並列実行サポート - カスタムテストスイートの追加機能 - Terraformモジュール化 --- **注意**: このプロジェクトはAWSリソースを作成するため、AWS料金が発生する可能性があります。テスト完了後は不要なリソースを削除してください(`terraform destroy`)。
アバター
本記事は 夏休みクラウド自由研究2025 8/24付の記事です 。 どうも。8月の夏休みがエンドレスにループしてほしい寺内です。 さて、Amazon S3はオブジェクトストレージです。ファイル操作やディレクトリの概念はブロックストレージにおけるそれとだいぶ違います。 ですが、S3の中のファイルをあたかもファイルシステムの一部のように操作したい、と思うことは稀にあります。またアプリケーションデータをS3に置きたいのだが、アプリケーションコードのファイルアクセス部分をS3用に作り変えることができない、という場合もあるでしょう。 そのようなときに便利なのが、 Mountpoint for Amazon S3 です。 これはS3の機能のひとつとして提供されていますが、実体はLinuxにインストールするファイルアクセスクライアントソフトウェアです。オープンソースであり、 GitHubで公開されて います。この機能自体に料金は発生せず、通常のS3利用料金のみで利用可能です。 Mountpoint for Amazon S3 は、 2023年3月にベータリリース されその後、 2023年8月には一般提供開始 となり本番環境で利用できるレベルになりました。そして 2025年5月には fstab に対応 し、OS起動時の自動マウントが可能となりました。 わたしの周囲では意外と知られていないこの便利機能を紹介していきたいと思います。 Mountpoint for Amazon S3 Mountpoint for Amazon S3 is an open source file client that you can use to mount an S3 bucket on your compute instance a... aws.amazon.com 概略 Linuxのファイルシステムが持つマウント機能を利用し、S3バケットもしくはそれ以下のプレフィクス(ディレクトリ)以下を、ファイルシステムに接続(マウント)します。マウントしたとろに対して、OS内から通常のファイル操作が可能となり、そのファイル操作は即座にS3オブジェクトに反映されます。 これはEC2からでもオンプレミスからでも、S3のAPIにアクセス可能なら利用可能です。 以下は、ドキュメントに沿った説明をしていきます。 Amazon S3 バケットをローカルファイルシステムとしてマウントする - Amazon Simple Storage Service Amazon S3 バケットをローカルファイルシステムにマウントするために、Mountpoint for Amazon S3 をインストールして使用する方法について説明します。 docs.aws.amazon.com インストール Amazon Linux 2023 のEC2からは、以下の dnf コマンドでインストールします。 ubuntuなどのDebian系Linuxでも使えますが、パッケージは異なります。 $ sudo dnf install <https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.rpm> 手動でマウント 手動でマウントするには、 mount-s3 コマンドを使用します。 基本のマウント ホームディレクトリ以下にマウントポイント( ~/mnt ) を作成し、そこにS3バケット( ytera-mountpoint01 )をマウントするには以下のようなコマンドとなります。 mount-s3 <bucketname> <mountpoint> 実行例: バケット名が ytera-mountpoint01 で、ホームディレクトリにある mnt の下にマウントします。 $ cd ~ $ mkdir mnt $ mount-s3 ytera-moutpoint01 ~/mnt bucket ytera-moutpoint01 is mounted at /home/ec2-user/mnt マウントを外すには、通常のアンマウントコマンドを使用します。 $ umount ~/mnt 削除権限を与えるマウント 上記の基本的なマウントでは、S3オブジェクトを読んだり追加することができますが、削除やファイルの書き換えは禁止された状態でマウントされます。 S3オブジェクトの削除を許可する場合は、以下のようにオプションを付けます。 mount-s3 --allow-delete <bucketname> <mountpoint> $ mount-s3 --allow-delete ytera-mountpoint01 ./mnt S3オブジェクトを更新することを許可するには、同じ用に --allow-overwrite オプションを付与します。 バケットの特定プリフィクスでのマウント バケット全体をマウントするのではなく、S3バケット内の特定のディレクトリ(プリフィクス)をマウントするには以下のように --prefix オプションを使います。最後のスラッシュ ( / ) は必須です。 mount-s3 <bucketname> <mountpoint> --prefix <prefix> mount-s3 ytera-mountpoint01 ./mnt/mount-a --prefix mount-a/ fstab での自動マウント OSが起動した時に自動的にsystemdで自動的にマウントするには、 /etc/fstab ファイルに定義します。fstab のオプションの詳細は省略します。 fstabでS3マウントするには、行頭からS3 URIとマウントポイントとなるディレクトリを指定します。マウントポイントとなるディレクトリは、予め作成しておいてください。 s3://ytera-mountpoint01/mount-a/ /mnt/mount-a mount-s3 _netdev,nosuid,nodev,nofail,rw 0 0 s3://ytera-mountpoint01/mount-b/ /mnt/mount-b mount-s3 _netdev,nosuid,nodev,nofail,rw 0 0 なお、末尾の 0 0 は、dumpの対象にするか否か(0は対象にしない)と、ファイルシステムチェック(fsck)の順序(0はチェックしない)という意味です。 性能は Mountpoint for Amazon S3 の仕組み Mountpoint for Amazon S3 は、S3にアクセスするソフトウェアです。アプリケーションレイヤからのマウントポイントへのファイルシステムコールを仮想ファイルシステムが受け取ると、S3マウント領域であればS3への呼び出し(GETやPUT)に変換します。その後、 ~/.aws/credentials の認証情報を使用してS3 APIエンドポイントへリクエストを投げます。 そのため、性能的にはAWS CLIを使う場合とたいして変わらないはずです。いちおうやってみましょう。 実験データの準備 転送速度の実験は、以下2つのランダムデータを用意します。 100KBのファイルを1000個 (合計10MBほど) 1GBのファイルを5個 (合計5GBほど) 以下のように作ります。 mkdir -p file1000-100kb && cd file1000-100kb && for i in {1..1000}; do dd if=/dev/urandom bs=1024 count=100 2>/dev/null > file_${i}.txt; done; mkdir -p file5-1GB && cd file5-1GB && for i in {1..5}; do dd if=/dev/urandom bs=1M count=1024 2>/dev/null > file_${i}.txt; done; これで、ローカルディスクに file1000-100kb と file5-1GB というディレクトリができました。 測定方法 S3マウントしたディレクトリに、 cp -r コマンドでコピーし、その実行時間を測定します。 $ date +"%H:%M:%S";cp -r ./file1000-100kb ./mnt/mount-a;date +"%H:%M:%S" 08:01:49 08:07:58 $ date +"%H:%M:%S";cp -r ./file5-1GB ./mnt/mount-a;date +"%H:%M:%S" 07:59:54 08:00:35 測定結果 転送データ 転送時間 転送速度(計算値) 100KB x 1000 6分9秒(369秒) 2.07 Mbps 1GB x 5 41秒 930.41 Mbps やはり多くのリクエストを出すのは転送効率悪いですね。 これでもmount-s3 コマンドは、ある程度の並列化を行っているようです。 なお、今回の実験はNATゲートウェイ経由のインターネット接続でS3 APIエンドポイントを使っています。VPCエンドポイントを作り同じ実験をしてみましたが、ほぼ同じ結果でした。 感想 S3の中の特定パターンのファイルを大量に消すなど、細かなファイル指定をしたり、複雑なファイル移動をしたりするとき、AWS CLIでは指定が(慣れていないため)面倒です。そういうときに、Linuxコマンドでできるととても簡単にできます。 これほど便利な機能ですが、Windowsには対応していないのが残念です。WSLでubuntuを動かし、ubuntu上のマウントポイントを母艦のWindowsからアクセスすることで、似たようなことができると思います。誰かやってみてほしいです。
アバター
本記事は  夏休みクラウド自由研究2025 8/23付の記事です 。 こんにちは!SCSK猿橋です。 関西万博が盛況のようですが皆さん遊びに行かれましたでしょうか? せっかくの関西開催なので一度行ってみたいと思っているのですが、万博に走る前にAWS CDKに走ってみたいと思います! 今回やりたいこと DevOpsでは一貫したインフラ環境の構築の為にIaC(Infrastracture as Code)の活用が増えていると思います。 IaCではAWS CloudFormation、AWS CDKを活用することが多いと思いますが、最近はリソースからCloudFormationテンプレート、CDKソースを生成する機能もでてきました。 今回はCDKを実践活用したい、というテーマで、よくあるWebアプリケーションで活用するシステム構成をCDK化することにTryしてみました。 cdk migrateコマンドの「from-scan」オプションで既存リソースからCDKソースを生成できる機能がリリースされているので、それを使えば簡単にCDK化できるんじゃないか!という目論見です。 AWS CloudFormation、AWS CDKの関係とCDKソース生成については以下の記事がわかりやすかったので参考にしてみて下さい。 今日から始めるCDK! ~既存リソースをCDK移行する~ - Qiita 2025/01/31追記 本記事ではAWSの既存リソースをCDKに変換する手順について記載していますが、いろいろと触ってみた結果、個人的には「既存リソースをCDKに置き換えるより、1から作り直した方が楽」というように考えるようになりました。... qiita.com cdk migrate コマンドは実験的であり、今後は大幅な変更が行われる場合があるとのことです。 さっそくつまづいた 一つ目のつまづき。以下のハンズオンでエッジサービスを活用したWebアプリケーションを構築し、このシステム構成をさくっとCDKソース化することにTryしました。 AWS Hands-on for Beginners Amazon CloudFrontおよびAWS WAFを用いて エッジサービスの活用方法を学ぼう | AWS Webinar AWS Hands-on for Beginners Amazon CloudFrontおよびAWS WAFを用いて エッジサービスの活用方法を学ぼう | AWS Webinar pages.awscloud.com 各リソースにはTagKey=CdkMigrateTestを設定しています。 TagKeyでフィルタリングして既存リソースからfrom-scanでmigrateしてみることにしました。 cdk migrate --from-scan new --stack-name "cdk-test-edgeservice" --filter tag-key="CdkMigrateTest" XXXStack could not be generated because None is not implemented for ResourceValue::Object 24リソース抽出できているものの、CDK生成でエラーが発生。このCDK生成エラーには今後都度悩まされることになります。 ネット検索やAmazon Qでエラー原因を調査しましたが、この時点では原因掴めず。migrateコマンドの難しさの入り口でした。 リソース別にmigrateしてみる cdk migrate –from-scanコマンドは、 既存リソースのスキャン(filterに合致するリソースの抽出) スキャンしたリソースのCloudFormationテンプレート化 テンプレートからCDKソースを生成 という3ステップを一括で実行できる素敵なコマンドなのですが、途中でエラーが発生するととても原因が掴みづらかったです。 少しでも複雑度を落とすべく、個別のリソース毎にmigrateしてみることにしました。 イメージとして、サーバレスAPIをイメージし、以下のリソースを作成した上で個別にmigrateすることとしました。 No サービス リソース名 tag設定 備考 1 S3 20250815-cdktest Key=CdkMigrateResource Value=S3   2 Lambda 20250815-cdktest-lambda Key=CdkMigrateResource Value=Lambda   3 API Gateway 20250815-cdktest-api Key=CdkMigrateResource Value=ApiGateway Lambda統合のGETメソッドを1つ設定 4 DynamoDB 20250815-cdktest-dynamodb Key=CdkMigrateResource Value=DynamoDB   S3のmigrate cdk migrate --from-scan new --stack-name "cdk-migrate-s3" --filter tag-key="CdkMigrateResource",tag-value="S3" cdkソース生成に成功! 生成されたソース内のmigrate.json(migrate結果が格納されているファイル) 想定したbucketが抽出できている cdkソースはlib配下に生成されている lib/cdk-migrate-s3-stack.ts Lambdaのmigrate cdk migrate --from-scan --stack-name "cdk-migrate-lambda" --filter tag-key="CdkMigrateResource",tag-value="Lambda" XXXStack could not be generated because RecursiveLoop is not a valid property 4リソース抽出できているもののCDKソース生成でエラーが発生。 こちらのissueが関連していそう。まだopenのようなので、このissueが解決しないとLambdaへのmigrateコマンドはエラーが発生しそう。 migrate CLI: Lambda "RecursiveLoop" property not supported · Issue #642 · aws/aws-cdk-cli Describe the bug When calling the cdk migrate CLI call for a CloudFormation template inlcuding the RecursiveLoop propert... github.com Lambdaコンソール上の設定は恐らくこちら テンプレート生成まではRecursiveLoopに追随できているが、テンプレートからのCDKソース生成ライブラリは未対応、ということでしょうか? API Gatewayのmigrate cdk migrate --from-scan --stack-name "cdk-migrate-apigateway" --filter tag-key="CdkMigrateResource",tag-value="ApiGateway" 3リソース抽出でき、CDKソースの生成が成功! migrate.jsonの確認。 Deployment、RestApi、Stageが抽出されている。 CDKソースを確認すると、Lambda統合のGETメソッドが生成されていなさそう。 再度filterでresource-type-prefixでAPI Gatewayを抽出してみたが、GETメソッドソースは生成されず。(一旦置いて、次行ってみよー! DynamoDBのmigrate cdk migrate --from-scan --stack-name "cdk-migrate-dynamodb" --filter tag-key="CdkMigrateResource",tag-value="DynamoDB" XXXStack could not be generated because WarmThroughput is not a valid property 1リソース抽出できていますが、CDKソース生成でエラーが発生。 こちらのissueが関連していそうです (migrate): DynamoUatStack could not be generated because WarmThroughput is not a valid property for resource DynamoDBTableAPI of type AWS::DynamoDB::Table · Issue #525 · aws/aws-cdk-cli Describe the bug The property in question is in the CF documentation: and in the L1 construct https:/... github.com cdk migrate処理の問題 issueを追っていく中で、冒頭の「ResourceValue::Object」エラーもCloudFrontのissueとして登録されていることがわかりました。 Bug: CloudFront distribution generation fails with None is not implemented for ResourceValue::Object · Issue #921 · cdklabs/cdk-from-cfn When trying to use cdk migrate on a CloudFormation template with a CloudFront distribution I get the following error: ❌ ... github.com さらにissueを追っていくと、根本対策としては以下のチケットが登録されていることがわかりました。 Make schema update automatically · Issue #771 · cdklabs/cdk-from-cfn It looks like the schema isn't being updated automatically. It should be. Looks like the files that need updating are he... github.com issue#771にはこれまでハマったエラーに関するissueが見事関連issueとして紐づけられていました。 DynamoDBの issue#525のコメント に投稿されていましたが、migrate処理の裏側で cdk-from-cfnライブラリ が動いており、このバージョンが古いそうです。(2025/08/16時点情報) https://github.com/cdklabs/cdk-from-cfn/blob/main/src/specification/cdk-resources.json で定義されていない新しいプロパティは対応できていないとのこと。 cdk migrateリリース直後は当時の最新プロパティが反映されていたのかと思いますが、少し期間がたって上記issueの通り最新プロパティに反映できていない問題が顕在化してきた過渡期、とも言えるかと思います。タイミングが悪く既存issueを踏みまくってしまいましたが、 isseu#771 が解決すればmigrateエラーも解消するのではないかと期待します。 代替手段(マネジメントコンソール上での段階実行) cdk migrate –from-scanは、既存リソースのスキャン、Cfnテンプレート化、CDKソース生成を一気に実行してくれるコマンドですが、途中でエラーが発生するとほぼ対処のしようがありません。 マネジメントコンソール上ではそれらを別々に実行できるので、途中のテンプレートを修正して進めてみる、という手もありそうです。 以下の記事を参考にマネジメントコンソールで作業を進めてみます。 AWS IaC ジェネレータを使ってみた AWS IaC ジェネレータは、既存のリソースを基にCloudFormationテンプレートを生成できるサービスです。8月にアップデートされた情報と合わせて、使ってみた感想、ポイントをご紹介します。 blog.usize-tech.com 2024.10.25 1.IaCジェネレータでのスキャン 上記の記事通りCloudFormation>IaCジェネレーター>新しいスキャンを開始 でスキャンが実行できます。 cdk migrate –from-scanで実行しているスキャン結果はマネジメントコンソール上でも共有されているので今回は省略します。 2.テンプレートの作成 今回は問題となったLambdaのテンプレートを作成してみます。 IaCジェネレーター>テンプレートを作成 タグを指定してLambdaをテンプレートに追加 関連リソースが推奨されるので、こちらもテンプレートに追加してテンプレートを作成 3.CDKソース生成 テンプレート作成後、AWS CDKタブを表示してCDK処理を進めてみます。 テンプレートをダウンロードし、cdkコマンドをコピーして実行 cdk migrate --stack-name CfnLambdaTemplate-20250815 --from-path ./CfnLambdaTemplate-20250815-1755311593670.yaml --language typescript XXXStack could not be generated because RecursiveLoop is not a valid property 想定通りのエラーが発生。テンプレートから「RecursiveLoop」プロパティを削除して、再度cdk migrateを実行してみる。 無事CDKソースの生成が成功!手間はかかりますが、エラーは回避できそうです。 ちなみにLambdaのCDKソースにAPI GatewayのGETメソッドの設定がありそうでした。(一旦置いておいた問題が解消 余談(苦労話) スタックに含まれているリソースはmigrateでスキャン対象外の模様 当初は以下のサーバレスサービスをベースとしたWebXRアプリケーションをベースにcdk migrateを試そうとしていました。 CDKソースでgithubで提供されており、このCDKソースとcdk migrateで生成したCDKソースを比較したら面白んじゃないかという目論見でした。 GitHub - aws-samples/aws-serverless-example-for-webxr: This repository contains an example solution on how to enhance your WebXR applications using AWS Serverless Services, providing scalable, efficient, and seamless user experiences. This repository contains an example solution on how to enhance your WebXR applications using AWS Serverless Services, pr... github.com 結果、想定していたリソースが何度やってみ抽出されず。当初はfilterオプションの書式に誤りがあるのかと様々な書式を試してみましたが抽出できず。Amazon Qに聞き倒したところ、ようやくスタックに既に含まれているリソースはscan対象外であるという条件がわかりました。 そもそも前提としていた動作確認シナリオからして間違っていた、ということですね(これでどれだけ時間を費やしたか。。。涙 おわりに 今回のcdk migrate動作検証は、まさに「夏休み自由研究」レベルの作業時間となってしまいました(1~2日で終わらせるつもりだったのに。。。 エラーにまみれたときはお先真っ暗な感じでブログアウトプットに辿り着けるのか、、と焦りましたが、なんとかエラー原因も見えてきて形にできました。 今回はタイミング悪くCDKソース生成が難しい状況で、冒頭の参考blogの記載の通り、このツールを使うより1からCDKソースを書いたほうが、エラーまみれの近道よりは前に進める遠回り、という印象でした。 ただし、うまくCDKソースがしゅるしゅるっと生成できた時は感動モノだったので、 issue#771 が解決することを切に願っております!
アバター
こんにちは。 ServiceNow AI Platformは、接続元IPアドレスによってインスタンスへのアクセスを制御することができます。 つい最近、IPアドレス制御の設定を行う機会があったため、その際に学んだ設定手順や実際に設定を行う際の挙動、注意点をまとめていきます。 ※IPアドレス制御は、インバウンド、アウトバウンドの両方が設定できますが、今回は インバウンド に絞って記事にしていきます IPアドレス制御の仕組み ドキュメントには、以下のような記載があります。 つまり、自身のIPアドレスがDenyルールにマッチし、かつ、Allowルールにはマッチしないときに始めて、インスタンスへのアクセスが却下されるということになります。 そのため、Allowルールだけ設定しても、接続を許可するIPアドレスを絞り込むことはできず、Denyルールの設定が必要です。 The system only blocks an IP address if a matching Deny rule exists and no matching Allow rule exists.    設定方法 System Security > IP Address Access Controlのモジュールから設定を行います。 始めに、Allowルールを設定します。 Type: Allow Direction: Inbound Range start/Range end: アクセスを許可するIPアドレス   続いてDenyルールは以下のように設定します。 Type: Deny Direction: Inbound Range start/Range end: アクセスを拒否するIPアドレス ※特定のIPアドレスからのアクセスのみ許可する場合は、DenyルールのIPアドレスに0.0.0.0~255.255.255.255を設定します   設定は以上で終了となります。 IPアドレス制御の設定をした際の挙動を確認 私自身が実務でIPアドレス制御を実施する際、どのような挙動をするか分からず不安だった点をいくつか紹介します。 設定者自身のIPアドレスがAllowルールに含まれない場合 もし、設定するIPアドレスを間違ってしまい、自分自身がインスタンスから締め出されてしまったらどうしようと、個人的に一番の不安ポイントでしたが、全く心配する必要はありませんでした。 Allowルールで自身が含まれないIPアドレスを設定して、その後Denyルールを保存する際、以下画像のようにエラーメッセージが表示されレコードの保存をキャンセルしてくれます。 IPアドレス制御を実施した瞬間、インスタンスにログインしているユーザーが存在する場合 もちろん、許可ルールに合致するIPアドレスを持つユーザーには何も影響はありません。 許可ルールに含まれないユーザーは、どのモジュールを開こうとしても、画像のように403エラーページが表示されます。 当たり前のことですが、設定する際は関係者への周知が必要です。   IPアドレス制御設定後、インスタンスにアクセスした際の挙動 インスタンスへの接続を試みると、ログインページに到達する前に403エラーページが表示され、アクセスが却下されます。 IPアドレス制御を行う際の考慮点 実際にIPアドレス制御を行う際、考慮したことを2点ほど記載します。 インスタンス間の通信 本番環境と非本番環境の複数環境で運用するのが通常ですが、以下の機能を使用する際は、インスタンス間での通信が発生するのでインスタンスのIPアドレスもアクセスを許可する必要があると思いました。 インスタンス間のクローン Remote Instance等の機能を使った、更新セット・アプリケーションの自動移送 他システムとの連携 システム連携している場合、 他システム起点 でServiceNowへのアクセスが発生する場合は、IPアドレスをAllowルールに追加する必要があります。   おわりに 今回の記事では接続元のIPアドレスによるインスタンスへのアクセス制御について、ご紹介しました。 今後、同じような設定を行う際、どなたかの参考となれば幸いです。
アバター
本記事は 夏休みクラウド自由研究2025 8/22付の記事です 。 皆さまこんにちは。お元気ですか? 最近、暑すぎて頭が働かなくなっちゃいますよね。 でも、頭が働かなくてもCloudFormationテンプレートを作成したいことって、往々にしてありますよね。 そういうわけで、Amazon Q Developer CLIを使って頭を使わずにスタックを作成してみようというのが、今回の自由研究のテーマです。 Amazon Q Developer CLIが画像入力をサポート 2025年5月のアップデートにより、Amazon Q Developer CLIに「画像サポート機能」が実装され、 画像ファイルをターミナルから直接入力できるようになりました。JPEG、PNG、WEBP、GIFの形式をサポートしており、また、1回のリクエストで最大10枚の画像をアップロード可能です。 この機能を活用することで、視覚的なアイデアを文書やコードといった成果物に落としこむことが可能になります。 以下にご紹介する公式ブログでは、アーキテクチャ図からのIaC作成、ER図からのDBスキーマ作成、手描き図からの設計書作成、スクリーンショットからUIのモックアップ作成という4つのユースケースが紹介されています。 Amazon Q Developer CLI がターミナルでの画像入力をサポート | Amazon Web Services この記事では、Amazon Q Developer Command Line Interface (CLI) の画像サポート機能が開発プロセスをどのように変革するかをご紹介します。Q Developer CLI は最近 (バージョン 1.1... aws.amazon.com   実際にやってみた そこで今回は、手描きのアーキテクチャ図からYAML形式のCloudFormationテンプレートを出力させ、想定した振る舞いをするかを確かめてみます。なお、以降は読みやすさのため(&親しみを込めて) Amazon Q Developer CLIのことはQdevくんと呼ぶこととします。 準備:手書きのアーキテクチャ図 まずはQdevくんに読み込ませるサービス構成の絵を描きます。今回はこんなのを書いてみました(日本語を正しく読み込むか分からなかったので保険で英語にしました)。処理の流れはざっくり以下の通りです。 S3にファイルをアップロード→イベント通知がEventBridgeへ渡される ファイルがCSV形式ならSNSを介してメールで通知。また、常にLambdaで処理してメタデータ情報をDynamoDBに格納する。 (あくまで構成図通りに一連のサービスを組み立てられるかのテストを目的としているため、上の構成が有用/望ましい構成であるかは一旦置いておきます。) Amazon Q Devにテンプレートを生成させる 準備ができたら「test-diagram.jpeg」として保存し、後はQdevくんにお任せしましょう。 🤖 You are chatting with claude-4-sonnet > test-diagram.jpegを実現するためのCloudFormationテンプレートを作成して。図に載っていない詳細についてはAWSの ベストプラクティスに従うこととします。それでもなお確認が必要であれば、ユーザに質問してください > まず、test-diagram.jpegファイルを確認させていただきます。 🛠️ Using tool: fs_read (trusted) ⋮ ● Reading images: ■■■■/qdev/test-diagram.jpeg ✓ Successfully read image ⋮ ● Completed in 0.0s > 図を確認しました。この「Uploaded data process flow」を実現するCloudFormationテンプレートを作成します。 フローは以下のようになっています: 1. ユーザーがS3にCSVファイルをアップロード 2. S3イベントがEventBridgeをトリガー 3. EventBridgeがSNSに通知 4. SNSからEmailで通知 5. EventBridgeが常にLambda関数もトリガー 6. Lambda関数がメタデータをDynamoDBに保存 CloudFormationテンプレートを作成します: (略) ⋮ ↳ Purpose: データ処理フローのCloudFormationテンプレートを作成 Allow this action? Use 't' to trust (always allow) this tool for the session. [y/n/t]: > y Creating: ■■■■/qdev/data-process-flow.yaml ⋮ ● Completed in 0.2s > CloudFormationテンプレートを作成しました。このテンプレートは図に示されたデータ処理フローを実現します。 ## 主な機能 1. S3バケット: CSVファイルのアップロード先 2. EventBridge: S3イベントをルーティング 3. SNS: CSVファイルアップロード時のEmail通知 4. Lambda関数: 全ファイルのメタデータをDynamoDBに保存 5. DynamoDB: ファイルメタデータの永続化 ## AWSベストプラクティスの適用 • S3バケットの暗号化とバージョニング有効化 • パブリックアクセスブロック設定 • IAMロールの最小権限原則 • DynamoDBのポイントインタイム復旧有効化 • Lambda関数のタイムアウト設定 • リソース名の一意性確保 ## デプロイ方法 bash aws cloudformation create-stack \ --stack-name data-process-flow \ --template-body file://data-process-flow.yaml \ --parameters ParameterKey=NotificationEmail,ParameterValue=your-email@example.com \ --capabilities CAPABILITY_IAM テンプレートをデプロイする前に、NotificationEmailパラメータに実際のメールアドレスを指定してください。 デプロイ後、SNSサブスクリプションの確認メールが送信されます。 何か追加の設定や修正が必要でしたらお知らせください。 作成されたdata-process-flow.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket}/*' # Lambda function for processing metadata MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print(f"Received event: {json.dumps(event)}") try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print(f"Successfully stored metadata for {object_key}") return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print(f"Error processing metadata: {str(e)}") raise e # EventBridge Rule for S3 CSV uploads to SNS S3ToSNSRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-sns' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !Ref DataProcessNotificationTopic Id: SNSTarget RoleArn: !GetAtt EventBridgeToSNSRole.Arn # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus' ほとんど追加の説明を渡さずに依頼を投げたので、テンプレートを作成する前に何回か確認の往復があることを予想していたのですが、意外とあっさり作成してくれました。 - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' (よく見るとLambdaの実行ロールの中で s3:GetObjectMetadata という存在しないIAMアクションを許可してしまっているようですが、その辺はご愛嬌としておきましょう。) Amazon S3 のアクション、リソース、条件キー - サービス認可リファレンス IAM ポリシーで Amazon S3 へのアクセスを管理するために使用できるサービス固有のリソース、アクション、条件キーの一覧を示します。 docs.aws.amazon.com 生成されたテンプレートからスタックを起動してみる テンプレートができたので次はこれをCloudFormationにアップロードします。 通知先のメールアドレス部分がパラメータになっているので、自分のメールアドレスを入力します。あとは「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックを入れること以外はデフォルトの設定のまま作成に進み、しばらく待ちます。 ……スタックの作成を待っていたら、途中でロールバックしてしまいました。見ると、次のようなエラーが出ています。 リソース LambdaExecutionRole は CREATE_FAILED 状態です。 Resource handler returned message: “Resource q-image-stack-20250822-data-upload-111111111111/* must be in ARN format or “*”.(Service: Iam, Status Code: 400, Request ID: ****-****-****-****-************) (SDK Attempt Count: 1) (RequestToken: ****-****-****-****-************, HandlerErrorCode: InvalidRequest) IAMリソースのARN指定が原因のようですが、今回の自由研究はなるべく頭を使わないことが目標なので、もちろんこんなエラーは読みません。全部Qdevくんに丸投げしましょう。 🤖 You are chatting with claude-4-sonnet > 作ってもらったdata-process-flow.yamlを展開したら次のようなエラーが出てCREATE_FAILEDになりました。 Resource handler returned message: "Resource q-image-stack-20250822-data-upload-111111111111/* must be in ARN format or "*".(Service: Iam, Status Code: 400, Request ID: ****-****-****-****-************) (SDK Attempt Count: 1) (RequestToken: ****-****-****-****-************, HandlerErrorCode: InvalidRequest) 適切にテンプレートファイルを修正してくれる? > エラーメッセージを見ると、IAMポリシーでリソースARNの形式が正しくないことが原因です。S3バケットのリソース指定で /* を使う場合は、 完全なARN形式にする必要があります。 まず現在のテンプレートファイルを確認して、適切に修正しましょう。 > 問題を特定しました。LambdaExecutionRoleのS3Accessポリシーで、リソースARNの形式が正しくありません。!Sub '${DataUploadBucket}/*' は正しいARN形式ではありません。S3バケットのARNは arn:aws:s3:::bucket-name/* の形式である必要があります。 テンプレートを修正します: ⋮ ● Completed in 0.0s > 修正が完了しました。主な変更点は以下の通りです: 1. S3リソースARNの修正: !Sub '${DataUploadBucket}/*' を !Sub '${DataUploadBucket.Arn}/*' に変更しました。 これにより、正しいS3バケットのARN形式(arn:aws:s3:::bucket-name/*)が生成されます。 2. ListBucket権限の追加: S3バケット自体への s3:ListBucket 権限も追加しました。これはLambda関数がバケット内の オブジェクトにアクセスする際に必要になる場合があります。 これで、IAMポリシーのリソースARNが正しい形式になり、CloudFormationスタックのデプロイが成功するはずです。 修正されたテンプレートを使って再度デプロイしてみてください。 修正されたdata-process-flow.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' - Effect: Allow Action: - s3:ListBucket Resource: !GetAtt DataUploadBucket.Arn # Lambda function for processing metadata MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print(f"Received event: {json.dumps(event)}") try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print(f"Successfully stored metadata for {object_key}") return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print(f"Error processing metadata: {str(e)}") raise e # EventBridge Rule for S3 CSV uploads to SNS S3ToSNSRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-sns' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !Ref DataProcessNotificationTopic Id: SNSTarget RoleArn: !GetAtt EventBridgeToSNSRole.Arn # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus' 再度アップロードして今度は無事、デプロイまで完了することができました。SNSから登録したアドレスにメールが来るので、承認をしておきます。 テスト:データの作成もAmazon Qに任せよう それでは、ちゃんと動くかテストしてみましょう。ダミーデータが必要になりますが、むろん、これもQdevくんに作ってもらいます。 > data-process-flow.yamlをテストしたい。testフォルダを作って、S3にアップロードするファイル(CSV, HTML, TXT)を作って > data-process-flow.yamlファイルをテストするために、testフォルダを作成してテスト用のファイルを準備しましょう。 (略) > CloudFormationテンプレートの内容を確認しました。このテンプレートはS3にファイルがアップロードされると、EventBridge経由 でLambda関数とSNS(CSVファイルの場合)に通知する仕組みです。 testフォルダを作成して、CSV、HTML、TXTファイルのサンプルを作成しましょう。 (略) > 完璧です!testフォルダに以下のテストファイルを作成しました: こんな感じのファイルを6種類作ってくれました。 作成されたバケットにこれらのファイルをアップロードすると、程なくして以下のようなメールが2通届きました。確かに、CSVファイルのそれぞれについてアップロード通知を送ってきてくれています。 AWS Notification Message From: Data Process Notifications <no-reply@sns.amazonaws.com> {“version”:”0″,”id”:”xxx”,”detail-type”:”Object Created”,”source”:”aws.s3″,”account”:”xxx”,”time”:”2025-08-16T05:34:26Z”,”region”:”ap-northeast-1″,”resources”:[“arn:aws:s3:::q-image-data-flow-data-upload-111111111111”],”detail”:{“version”:”0″,”bucket”:{“name”:”q-image-data-flow-data-upload-111111111111”},”object”:{“key”:”sample_data.csv”,”size”:610,”etag”:”xxx”,”version-id”:”xxx”,”sequencer”:”xxx”},”request-id”:”xxx”,”requester”:”xxx”,”source-ip-address”:”xxx”,”reason”:”PutObject”}}’ また、Dynamoを見ると画像のようにすべてのファイルについてメタデータを保存してくれていました。最初の図で期待していた処理がちゃんとなされていることが確認できます。 おまけ せっかくなのでもう少し遊んでみましょう。 機能の追加 ただ、上のメール通知はJSON形式のメッセージがそのままメールの本文に書かれているだけで、正直可読性は低いです。さらにおねだりをしてフォーマットされたメールが飛ぶようにしてもらいましょう。 > data-process-flow.yamlを更新して、メール送信される通知の本文の可読性を上げてくれる? > data-process-flow.yamlを更新して、SNS通知の本文をより読みやすくするために、Lambda関数を追加してメール通知をフォーマットしましょう。 (略) > 完璧です!data-process-flow.yamlを更新して、メール通知の可読性を大幅に向上させました。 機能を追加したdata-process-flow.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' - Effect: Allow Action: - s3:ListBucket Resource: !GetAtt DataUploadBucket.Arn - PolicyName: SNSAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda function for formatting notifications NotificationFormatterFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-notification-formatter' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: SNS_TOPIC_ARN: !Ref DataProcessNotificationTopic Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse sns = boto3.client('sns') s3 = boto3.client('s3') def lambda_handler(event, context): print("Received event: {}".format(json.dumps(event))) try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Format file size file_size = response['ContentLength'] if file_size < 1024: size_str = "{} bytes".format(file_size) elif file_size < 1024 * 1024: size_str = "{:.1f} KB".format(file_size / 1024) else: size_str = "{:.1f} MB".format(file_size / (1024 * 1024)) # Format timestamp upload_time = response['LastModified'].strftime('%Y-%m-%d %H:%M:%S (UTC)') # Create formatted message subject = "CSV File Uploaded - {}".format(object_key) message_parts = [ "CSV File Upload Notification", "", "File Information", "================", "File Name - {}".format(object_key), "Bucket - {}".format(bucket_name), "File Size - {}".format(size_str), "Content Type - {}".format(response.get('ContentType', 'unknown')), "Upload Time - {}".format(upload_time), "", "Processing Status", "=================", "File upload completed", "Metadata processing in progress", "Data analysis preparation", "", "Next Steps", "==========", "1. File content validation", "2. Data quality check", "3. Processing result notification", "", "This is an automated message from the Data Processing System v2.1" ] message = "\n".join(message_parts) # Send formatted notification sns.publish( TopicArn=os.environ['SNS_TOPIC_ARN'], Subject=subject, Message=message ) print("Successfully sent formatted notification for {}".format(object_key)) return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Notification sent successfully', 'fileKey': object_key }) } except Exception as e: print("Error sending notification: {}".format(str(e))) raise e MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print("Received event: {}".format(json.dumps(event))) try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print("Successfully stored metadata for {}".format(object_key)) return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print("Error processing metadata: {}".format(str(e))) raise e # EventBridge Rule for S3 CSV uploads to notification formatter S3ToNotificationRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-notification' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !GetAtt NotificationFormatterFunction.Arn Id: NotificationFormatterTarget # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge (Notification Formatter) NotificationFormatterInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref NotificationFormatterFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToNotificationRule.Arn # Lambda permission for EventBridge (Metadata Processor) LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' NotificationFormatterFunctionName: Description: Name of the Lambda function for notification formatting Value: !Ref NotificationFormatterFunction Export: Name: !Sub '${AWS::StackName}-NotificationFormatterFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus' フォーマット用のLambda関数を追加してくれました。 更新されたテンプレートでスタックを更新し、再度CSVファイルをアップロードすると今度は以下のように読みやすい形で通知が送られてきました。 CSV File Uploaded – sample_data-2.csv From: Data Process Notifications <no-reply@sns.amazonaws.com> CSV File Upload Notification  File Information ================ File Name – sample_data-2.csv Bucket – q-image-data-flow-data-upload-111111111111 File Size – 610 bytes Content Type – text/csv Upload Time – 2025-08-16 06:03:20 (UTC) Processing Status ================= File upload completed Metadata processing in progress Data analysis preparation アーキテクチャ図を清書してもらう 機能の追加によってLambdaが追加されたので、作成された添付ファイルをもとに改めて構成図を作成させてみます。 実際の指示は履歴が消えてしまったのですが、プロンプトを作成するにあたっては下記記事を参考に最新版のアイコンを参照するなどを指示に含めました。​ 作成された構成図は以下の通りです。かなり自分のイメージ通りに出力してくれた印象です。 おわりに いかがでしたか?そこそこ複雑なテンプレートでもちょっとした対話で簡単に作成することができ、今日の生成AIの威力を身にしみて感じる自由研究となりました。 ChatGPTが出始めの頃、コード生成をさせるにも途中で出力が止まり、「続きを書いて」を繰り返して指示して出力されたコードを何度も修正してやっと動くようになる、というのが遠い昔に感じます。 これで夏休みの宿題は終わりです。皆様くれぐれも暑さには気を付けてお過ごしください。
アバター
本記事は 夏休みクラウド自由研究2025 8/21付の記事です 。 どうもSCSK齋藤です。 今回はTechHarmonyの企画であるクラウド自由研究の一環として投稿させていただきます。 Lambdaのランタイム終了についておさらい Lambdaのランタイムは、下記のブログにある通り、各プログラム言語のバージョンのサポート期間が定められています。 Lambda ランタイム - AWS Lambda Lambda がランタイムでサポートする言語について説明します。 docs.aws.amazon.com サポート終了の際には以下の3段階で流れが進み、徐々にサポートされなくなります。 最初の廃止は、プログラム言語側の廃止なので、AWS上で何かが動かなくなるというものではありません。 AWS Lambdaに直接影響あるのは「関数の作成ブロック」からとなります。 関数の作成ブロックというのは、終了対象のランタイムの関数を新たに作成できなくするというものです。その時点ではすでに作成されたLambdaの更新は可能という状態です。 ですが、「関数の更新ブロック」のフェーズになると、更新も一切できなくなってしまいますので、それまでにランタイムアップデートなり、不要なら廃止する形の対応が必要になります。   投稿経緯 私は普段の業務でAWS上のアプリケーションの保守開発を行っています。 保守業務の一環として、Lambdaのランタイムサポート終了に伴う対応に今取り組んでいます。 その中で、本番環境に展開中の既存Lambdaのランタイムアップデートした後、もし問題があったら以前のランタイムに切り戻しをしたいという要望がありました。 終了対象のランタイムに戻せるのか?という疑問は生まれましたが、実際に検証してみたら終了対象のランタイムに戻すことが可能でした。(マネジメントコンソールからは選択できないので、AWSCLIやSDK経由で戻すことになります。) そうであれば、新規作成した最新ランタイムのLambdaを、AWSCLIやSDK経由で終了対象のランタイムに戻せば、実質的に終了対象のランタイムのLambdaを新規作成したことになるのではないか?という疑問(好奇心)が生まれました。 前述の「Lambdaのランタイム終了についておさらい」で述べたように、関数の作成ブロックに入ってしまうと、終了対象のランタイムのLambdaは作成不可能なはずなので、思わぬ抜け道を見つけたことになるのではないかとワクワクしてしまいましたww 検証 早速検証してみます。 今回は執筆時点のPythonの最新ランタイムPython3.13の関数を作成し、廃止予定のPython3.7に変更してみたいと思います。 1.関数の作成 以下の設定で作成してみます。ちなみに、他の部分の設定は特に変更していません。 2.ランタイム変更 前述したように、終了対象のランタイムへの変更は、AWSCLIかSDK経由でのみの更新となります。 そのため、Cloudshellを起動して、以下のコマンドを入力します。 aws lambda update-function-configuration --function-name RunTimeUpdateTest --runtime python3.7 実行したら以下のようなレスポンスが返ってきます。 赤枠で囲ったところに書いてあるようにPython3.7に変更できましたね! マネジメントコンソールを確認しても、Python3.7になっていますね。 これで当初の仮説通り、終了対象ランタイムのLambdaを新規作成することができてしまいました!   新規作成不可だったパターン 検証していく中で、どうしても作成がうまくいかなかったパターンがありました。 それは、Lambdaのアーキテクチャをarm64にした場合です。 この場合は、前述の検証のように終了対象のランタイムに変更することができませんでした。 AWSCLIで同様のコマンドを入力したら下記のようなエラーとなりました。   しかし、コマンドをPython3.8に変えたところ、、、 Python3.8に変更できました。 Python3.8は、「関数の作成ブロック」が執筆時点よりも1ヶ月ほど先となっています。(2025年8月21日時点) Python3.7は、「関数の作成ブロック」の時期をすでに迎えているため、arm64は対応していないのかもしれません。   まとめ 今回気になっていた、終了対象のランタイムのLambdaが抜け道を使って実質新規作成することが可能かについては、可能といえるのではないかと思いました。 しかし、arm64でPython3.7に対応していなかったりと、必ずしも新規作成がうまくいくとは限らなそうです。 今回の検証が役に立つと思われるユースケースとしては、Lambdaランタイム終了に伴って不要なLambdaを本番環境から削除するような方に役に立つのではないでしょうか。 誤って必要なLambdaを消してしまったが、切り戻しをしたい時などに、もしかしたら役に立ちそうです。 しかし、非公式な方法だと思われるので、検証環境などで動作確認などはしっかり行った上で本番に臨んだ方が良いかと思います。
アバター
こんにちは、石原です。 Insight SQL Testing についてご紹介する内容の第三弾になります。 Insight SQL Testingはインサイトテクノロジー社が提供している製品になります。 Insight SQL Testing - 株式会社インサイトテクノロジー データベース移行及びバージョンアップ向けSQLテストソフトウェア。異種データベース間でSQLのテストができる唯一の製品。 www.insight-tec.com 本内容をご確認いただく前に第一、二弾の内容をご参照ください。 ■第一弾 Insight SQL Testing を触ってみた(第一回) Insight SQL Testingを実際に触った内容を記事にしています。今後データベースのバージョンアップや移行を計画されており、それに伴う工数や懸念をお持ちの方々に是非知ってほしい製品になります。今回はMySQLを対象にして全体的な大まかな設定の流れや結果について概要レベルでまとめたものとなります。 blog.usize-tech.com 2025.04.03 ■第二弾 Insight SQL Testing を触ってみた(第二回) Insight SQL Testingを実際に触った内容を記事にしています。今後データベースのバージョンアップや移行を計画されており、それに伴う工数や懸念をお持ちの方々に是非知ってほしい製品になります。今回はISTの導入についてまとめたものとなります。 blog.usize-tech.com 2025.07.15 今回は以下の2についてまとめてみました。 ①実際にInsight SQL Testing を用いた場合の実行結果 ②実際にInsight SQL Testing を使ってみての注意点 ①実際にInsight SQL Testing を用いた場合の実行結果 Insight SQL Testingは第一弾で触れたように最終的に得られる結果より一括で問題点が洗い出せるため、 そこまでたどり着くと非常に優れた製品です。 簡易的ではありますが最終的なイメージとしてどのような結果が得られるのかご説明します。 Insight SQL Testing の最終結果はアセスメントとしてまとめられ、以下の項目に振り分けられます。 ①ターゲットDBでのみ失敗 ②テスト用ソースDBでのみ失敗 ③両DBで失敗 ④結果が相違 ⑤性能が劣化 ⑥成功 ここに関して以前よりも詳しくまとめていきます。 サンプルデータ MySQLの一般ログとして以下を用います。 Time Id Command  Argument 2024-12-18T05:29:45.214633Z 1627 Connect XXXXX@10.10.90.120 on  using SSL/TLS 2024-12-18T05:30:02.050688Z 1627 Init DB   test 2024-12-18T05:30:18.768317Z 1627 Query insert into test123 values(2,’BOSS’)   2024-12-18T05:30:18.868317Z 1627 Query insert into target1 values(1,’BOSS’) 2024-12-18T05:30:19.768317Z 1627 Query insert into test1   values(1,’BOSS’) 2024-12-18T05:30:20.768317Z 1627 Query insert into test124 values(1,’BOSS’) 2024-12-18T05:31:07.904233Z 1627 Query select * from test.test123 2024-12-18T05:31:17.904233Z 1627 Query select * from test.emp 2024-12-18T05:32:07.904233Z 1627 Query SELECT count(*) FROM users u JOIN orders o ON u.user_id = o.user_id WHERE o.order_amount > 50 or o.order_amount like ‘%11%’ また比較する環境も以下のように設定しておきます。 テーブル名 テスト用ソースDB(現環境のイメージ) ターゲットDB(新環境のイメージ) test123 あり※レコード1件 あり※レコード2件 target1 なし あり test1 あり なし test124 なし なし emp あり※レコードの内容は共に一緒 上記を踏まえてアセスメントの結果を見ていきます。 サマリーの画面になりますが以下のように分類されました。 アセスメントの結果のまとめ 上記の画像でも確認できるようにアセスメント実行時に「エラーメッセージ」として2件確認できます。 最初の「1146」のエラー側を確認すると、test1 の INSERT文でエラーになっています。 これは想定どおり、テスト用ソースDB側にしか存在しない表のため、「ターゲットDB」側でエラーとして計上されています。     また「1136」のエラー側をみると、 test124 でエラーとなっています。 これはテスト用ソースDBおよびターゲットDBのどちらにも存在しない表です。 なお、上記との差としてステータスのマークに違いがみられます。 次に「エラープログラム」側をみてみます。 こちらには2件計上されています。 ここでは例として test123 側に注目します。 test123 のSELECTでエラーとなっていますが、元々この表のデータ量が異なっている状態でした。 もちろん、どのようなデータの差異が生じたのか確認することも可能です。   アセスメントのページの「性能が劣化したSQL」をクリックすると、そのSQLを確認することができます。 この EMP 表はテスト用ソースDBおよびターゲットDBの両方に存在し、データの内容も一致もしたが、実行速度が 「テスト用ソースDB 」より「ターゲットDB」より遅いとこちらに計上されます。 (事前の設定によって容認できる遅延具合を設定できるので、その場合はその条件から外れた場合になります。) ちなみに結果が異なる場合や成功した場合においても、同様に実行時間を確認することができます。 最後にアセスメントのページの「テスト用ソースDBにて失敗」をクリックすると、そのSQLを確認することができます。 ターゲット側で失敗した場合と同様に、2件計上されています。 target1はターゲット側には存在しますが、テスト用ソースDBには存在しないため、こちらに表記されます。 勿論、test124も存在しないのでターゲット側でも表示されたようにこちらにも計上される形となります。   このようにアセスメントの結果より何が問題であるか、うまくSQLが実行できても新しい環境において、 どれだけ遅延を引き起こすのかをまとめて確認することができます。 さて、このアセスメントにたどり着くまでには色々と注意するべき点があります。 それを次にまとめていきます。 ②実際にInsight SQL Testing を使ってみての注意点 ここからはアセスメントにたどり着くまでに検証などを通じで確認できた注意点を記載していきます。 「評価SQLセット」注意点 Insight SQL Testing を使用してみて一番重要と感じたことは「評価SQLセット」のフェーズでした。 今回はMySQLの一般ログから直接Insight SQL Testingに認識させましたが、ここで気を付けなければいけないのが 一般ログに書かれている情報が全部 Insight SQL Testing に登録されたのかという点です。 以下は読み取らせた結果で、特にエラーも出ていないので問題なくすべての情報を読み取ったように見えますが、 実際は内部で認識できずに読み飛ばしている情報があったりします。 そのため、一般ログに書かれたデータ量と読み取られたデータ量が一致するのか確認した方が安全です。 MySQLの一般ログ観点になりますが、検証を行ったうえで Insight SQL Testing に読み込まれる条件についてまとめたので記載します。 「評価SQLセット」Insight SQL Testing に読み込まれる条件 まず条件の前に前提知識について触れます。 以下はMySQLの一般ログの内容の抜粋となりますが、処理のまとまりとしてはプロセスID単位となります。 プロセスIDは以下の中の赤文字の数字になります。 この数字が一致していれば同じセッション内で行われた処理であると考えられます。 一般ログはすべてのセッションの更新情報等を書き込むため、さまざまなプロセスIDのレコードが入り混じっています。 ~ 一般ログの出力例 ~ 2024-12-06T05:16:08.701213Z 1531 Query  show databases 読み込まれる条件(1) 以下を全て満たす必要があります。 ・ Init DB  の出力があること ・ Query  の出力があること 「 Init DB   test」とはコマンドでは「use test」と打った場合に一般ログに表示させる記載内容です。 つまり、MySQL内でデータベースの切り替えを行った場合の出力となります。 ~ 一般ログの出力例 ~ 2024-12-06T05:16:08.700088Z 1531 Init DB test 2024-12-06T05:16:08.701213Z 1531 Query show databases 注意点として、確かにこの出力であれば Insight SQL Testing に読み取られはしますが、 Connect情報が無いとどのユーザーから実行した処理であるか判断付かなくなります。 対処としてアセスメントの前にそのような紐づかないユーザーの処理に対して一律意図したユーザー名に 置き換えることが可能ですが、基本的には一般ログにMySQLに接続される処理を含むようにしてもらうことが重要です。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on  using SSL/TLS 2024-12-18T05:30:25.186372Z 1627 Init DB  test 2024-12-18T05:30:58.220105Z 1627 Query  insert into minori1 values(3,’CCC’) 2024-12-18T05:31:25.186372Z 1628 Init DB  test 2024-12-18T05:32:58.220105Z 1628 Query  insert into minori1 values(4,’DDD’) 読み込まれる条件(2) (1)以外に以下のパターンであっても認識させることができます。 ・ Connect 時に on の次にDB名がある ・ Query  の出力があること Connect は MySQL へ接続した際の出力であり、その際に事前にアクセスするDB名も一緒に指定してあった場合になります。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect ishihara@XXXX.XXX.XXX.XXX on  test using SSL/TLS 2024-12-18T05:30:58.220105Z 1627 Query   show databases こちらも注意点として例えば、接続時にDB名を指定せず行い、「SELECT * FROM TEST.EMP;」などを実行した場合、 これは条件から外れるため無視されてしまいます。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on  using SSL/TLS 2024-12-18T05:30:58.220105Z 1627 Query  select * from test.emp 2024-12-18T05:31:25.186372Z 1627 Init DB  test 2024-12-18T05:32:58.220105Z 1627 Query  insert into minori1 values(4,’DDD’) 「評価SQLセット」Insight SQL Testing は Init DB の情報をみない 「ターゲットDB」の設定時に「データベース名」を一緒に登録します。 ここから分かるように、基本 Insight SQL Testing は一つのデータベースに対して処理を行う場合に用いる製品です。 例えば以下の場合はデータベース名が「test」となっています。 「 Init DB  xxx 」と記載された一般ログを読み込んだ評価SQLセットと、このターゲットDBを使用してアセスメントを行うと、 内部で「 Init DB  xxx 」から「 Init DB  test 」として変換され実行されてしまいます。 裏技として一般ログに対して以下のように変更することで複数DBに対して対応させることができます。 つまり、コマンド単位として Query を選び、そのコマンドとして 「USE <データベース名>」 を記載します。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on test using SSL/TLS 2024-12-18T05:30:25.186372Z 1627 Init DB  exam 2024-12-18T05:31:58.220105Z 1627 Query  insert into emp2 values(3,’BBB’) ↓ ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on test using SSL/TLS 2024-12-18T05:30:25.186372Z 1627 Query  use exam 2024-12-18T05:31:58.220105Z 1627 Query  insert into emp2 values(3,’BBB’) なお、こちらを用いる場合、 「直列実行」 をアセスメント時に選択しておくことをお勧めします。 「評価SQLセット」一般ログのローテーションの問題 上で少し触れましたが、一般ログはローテーションにより別のログに切り替わる影響で Connect 情報が失われることがあります。 その影響により実行ユーザー情報が失われたり、Connect情報にDB名も含む場合はそのセッション情報が全て読み込まれずに 無視されることもあります。 解決策として手動制御とはなりますが、事前にMySQL側で「 show processlist; 」の情報を取得して後で一般ログに反映させる方法です。 例えば、1:00~3:00に動く夜間処理について Insight SQL Testing で解析したい場合、1:00 前に 「 show processlist; 」の情報を取得しておきます。 ~ MySQL の出力例 ~ mysql> show processlist; +------+----------+--------------------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +------+----------+--------------------+------+---------+------+----------+------------------+ | 5 | rdsadmin | localhost | NULL | Sleep | 14 | NULL | NULL | | 2200 | test | XX.XX.XX.XX:51200 | abc | Query | 0 | NULL | NULL | | 2203 | admin | XX.XX.XX.XX:51208 | NULL | Query | 0 | starting | show processlist | +------+----------+--------------------+------+---------+------+----------+------------------+ ※ rdsadmin ユーザーは、AWSが管理する内部ユーザーであり通常のユーザーが意図的に接続することはできないため無視可能 上記の場合、以下のように変換して読み込む一般ログの頭に記載することで Connect 情報を補うことができます。 ~ 一般ログの出力例 ~ ※2024-12-18 01:00 を想定 2024-12-18T01:00:00.000000Z      5 Connect  rdsadmin @localhost on  using Socket 2024-12-18T01:00:00.000000Z   2200 Connect  test@ XX.XX.XX.XX on abc using SSL/TLS 2024-12-18T01:00:00.000000Z   2203 Connect  admin@ XX.XX.XX.XX on  using SSL/TLS 「アセスメント」ロールバックについて アセスメントを実行し、一般ログの内容を「テスト用ソースDB 」と「ターゲットDB」上で実行しますが、 その実施した内容がアセスメント終了時に「コミット」されるのか「ロールバック」されるのかを 選べるようにアセスメントの設定時に「DBへのデータ反映」にて選択することができます。 一度アセスメントしても、またテストをするために前回実行した内容を排除するために 「ロールバック」を選択することが望ましいですが、DBの仕様によってはコミット/ロールバックが効かない場合があります。 例えば以下の一般ログの場合、100のセッションは中でDDL文が実行されています。 このようなセッションの処理はロールバックされません。 なお、200の場合はロールバックされます。このように実行処理によってロールバックの有無がわかれてしまいます。 確実に前回の処理内容を残したくないのであれば、AWSであれば事前にスナップショットを取得し、 アセスメント毎にスナップショットから環境を復旧させて利用すると確実です。 ~ 一般ログの出力例 ~ 2024-12-18T01:00:01.000000Z   100 Init DB test 2024-12-18T01:00:02.000000Z   100 Query drop table emp 2024-12-18T01:00:03.000000Z 100 Query create table emp (no int,name varchar(10)) 2024-12-18T01:00:04.000000Z 100 Query insert into emp values(1,’AAA’) 2024-12-18T01:00:05.000000Z   200 Init DB test 2024-12-18T01:00:06.000000Z 200 Query insert into abc values(1,’AAA’) 「アセスメント」ターゲットDBにアクセス権のないユーザー処理 先にお話ししたようにInsight SQL Testing は「ターゲットDB」で設定したDBに対して一般ログに 記載された内容を実行しようとします。 その動作から場合によっては、ターゲットDBに設定したDBに対して接続できないユーザーも出てくることが考えられます。 では、 「USE <データベース名>」 として置き換えればよいと考えるかもしれませんが、 必ず最初に接続するべきDBはターゲットDBに設定したDBとなってしまう ため、解決になりません。 ちなみにアセスメント時、ユーザー情報より接続テストをすることができますが以下のように失敗します。 この時点で失敗するため、アセスメントを実行しても一連の処理は全て失敗します。 よって、考えられる回避方法は事前にターゲットDBに指定するDBに対して関連ユーザーに権限を与えておくことになります。 その他の注意事項 基本的には上記が躓きやすい箇所になりますが、他の注意事項も以下に記載します。 移行元のDBがAWSの場合、内部で rdsadmin ユーザーの接続や処理内容が思っている以上に存在します。 アセスメントの段階で実行しないユーザーを省くこともできますが、評価SQLセットの段階で余計な 情報を読むことになるため、事前に省いておくことが有効です。 「評価SQLセット作成方法」において「DBのログから作成」以外である「Amazon RDSから作成」もありますが、 上記のように一般ログに対してチューニングすることができるので「DBのログから作成」がお勧めです。 Insight SQL Testing に認識できるログの量は1000万以内に抑えるようにすることが重要です。 検証結果のまとめ 検証結果から以下の方法を用いることがよいと考えられます。 Insight SQL Testing用にDBを用意し、既存ユーザーは全てそのDBに対して接続権限を与える Init DB の処理はすべて Query USE に変更する※アセスメント時に「直列実行」を選択しておく Connect 処理には DB 名もセットで記載する Connect 処理に記載する DB名はターゲットDB名にする 既存のConnectでDB名がある場合、DB名をターゲット名に変更し、その後 Query USE で別途1行追記する rdsadminユーザーの処理は可能であれば、取り込む前に排除する まとめ 今回は Insight SQL Testing の良い点と検証からわかった注意点をまとめてみました。 アセスメントまで辿り着けばそこから得られる情報は非常に有効です。 ただし、意図した処理を全て含むためには今回触れたように手を加える必要があります。 それでもこの製品を使用することで総合的には問題の洗い出しに対して工数を大きく削減することが期待できます。 もし使用する場合は本内容を参考にしていただけると幸いです。
アバター
本記事は 夏休みクラウド自由研究2025 8/20付の記事です 。 こんにちは。梅ヶ谷(うめがたに)です。 今回は2025年の自由研究枠での記事になります。 子どもの頃、夏休みは自由研究の宿題がありましたね。 昆虫を集めたり、植物の成長記録をつけたり、自然と触れ合う研究をしたような記憶があります🌻   今回は大人の自由研究です。それも非常に現実的なテーマです。 ぼくたちの仕事はAIによって楽になるのか?を、 MCP(Model Context Protocol) を活用して検証してみました。   昔はSFの世界として考えられていた、 AIが仕事をする社会 。 2025年、まもなく手の届くところにある気がしますね。 MCPとAIが仕事にもたらす変革を、架空の会社と仕事のシナリオをもとにして、私が試した結果をご紹介します。 なるべく読みやすくまとめてみました。ぜひ、最後までお付き合いください。   はじめに まずはじめに、なぜ自由研究のテーマにAIとMCPを設定取り上げたかについて触れておきます。 新しい要素技術が出ると、実装方法に関する情報や簡単な実験の記事が多く書かれます。 AIの実装記事は多いものの、それが私たちの働き方そのものをどう変えるのか、という問いには明確な答えがありません。 そこで、よりリアルに変化の可能性を体感できるように、架空の会社とシナリオをベースに検証してみました。 みなさんと仕事の内容は違うかもしれませんが、なんとなくイメージがしやすいと思います。   プロローグ 舞台となるのは、創業50年、社員100名ほどの老舗ITコンサルティング会社。 この会社は、長年の信頼と実績で顧客を増やしてきたが、業務は昔ながらのアナログな手法が中心。 顧客へはテクノロジーを活用した効率的な働き方を提案するものの、社内はまだまだアナログ。 膨大なメール、会議、手作業でのタスク管理に社員たちは日々追われていた。 … そんな中、ある日社長が連れてきたのは、一匹の白黒模様の猫だった。 社員たちは、首輪についたタグに 「 MCP」と書かれていたことから、その猫を「エム氏」と呼ぶことにした。 エム氏は最初は、ただデスクの上で心地良さそうに寝ているだけの普通の猫にしか見えなかった。 しかし、社長が社員にこう告げる。 「エム氏を、お前たちの新しい同僚として迎え入れてほしい。面倒な業務は、まず彼に教えてみてくれ」 社員たちは、猫に仕事を教えるという奇妙な命令に戸惑いながらも、半信半疑でエム氏に業務を教え始める。   はじめての仕事 まずは仕事をおぼえる エム氏は、セールス部門の片隅に置かれたふかふかのクッションが敷かれたデスクにちょこんと座った。 「今日から、エム氏の面倒を見てやってくれ」 社長の言葉に同僚たちは半信半疑のまま、エム氏をじっと見つめる。額の「MCP」タグを揺らしながら、エム氏はただじっと同僚たちを見返している。 「猫に仕事を教えるって…冗談でしょ?」 そう呟く同僚たちだったが、社長の命令には逆らえない。 顧客対応の第一歩である問い合わせメールの対応という、手間はかかるが重要なタスクをエム氏へ教えることにした。   こんにちは、仕事の内容をおしえてくれませんか。 仕事の流れを説明するからよく聞いてね。   仕事の内容 1. クライアントから会社のGmail宛に届く問い合わせメールを確認する 2. 問い合わせを行った企業を調査し、アプローチを検討する 3. 問い合わせの内容を確認して、Notionで商材を調査する 4. Googleカレンダーで都合の良いミーティング日時を確認し、仮予定を入れる 5. Notionに新規チケットを作成し、タスクを記載する 6. Gmailでクライアント向けにメールを返信する 7. Slackでチームメンバーへチケットの内容をシェアする    *問い合わせの数だけ処理を行う   一連の手順を話し終え、同僚たちはふとエム氏の顔を覗き込む。 エム氏は、まるでその全てを理解したかのように、まっすぐな瞳で同僚たちを見つめている。   さて、やってみようか…。   お昼休みの会話 昼休み、オフィスは静まり返っていた。 午前中、エム氏がキーボードに前足を置いて動かしていた光景が、同僚たちの脳裏に焼き付いていた。 彼らはエム氏が説明内容を整理しているのだろうと思っていた。 エム氏、ちょっと疲れてない?   同僚の一人が、ふとエム氏のデスクに目をやる。 エム氏はクッションの上で丸くなっているが、同僚がそう声をかけると、ゆっくりと顔を上げた。   大丈夫、準備はできてるよ。 最初は時間がかかるだろうし、ゆっくりでいいんだよ。   同僚はそう声をかけ、再び自席に戻った。   タスクの処理 午後1時。午後の業務が再開した直後だった。 セールス部門のSlackに、多くの通知が連続して飛び込んできた。 「どういうこと?」 同僚たちはエム氏のデスクに駆け寄り、その様子をじっと見つめた。 エム氏は、キーボードに前足を乗せ、画面に表示されたメールのリストを、まるで高速で読んでいるかのように眺めている。 「一体何をやっているの?」 同僚の一人が思わず声を上げると、エム氏はゆっくりと答えた。 「私は、みんなが毎日こなしていた仕事を一瞬で組み立てている。これが、私の仕事。MCPの力だよ」   AIとMCPで自動化された仕事 同僚たちがPCの画面を覗き込むと、午前中に説明した仕事の結果と思われる内容が表示されていた。 どうやら、問い合わせメールに対して必要な処理を自動で行って、その結果が出力されているようだ。   具体的にどのように仕事が自動処理されたのか、順を追って説明しましょう。   AIがやった仕事、人間がやった仕事 さて、エム氏は一体何をやったのでしょうか。 実は午前中に、AIエージェントを使って自動で仕事を処理するための準備をしていたのでした。 また、業務アプリケーションと連携できるよう、MCPを利用してGmailやNotionなどとの連携も設定していました。   結果的に、業務のステップは大幅に効率化されたのです。 仕事の内容(同僚たちのやり方) 1. クライアントから会社のGmail宛に届く問い合わせメールを確認する 2. 問い合わせを行った企業を調査し、アプローチを検討する 3. 問い合わせの内容を確認して、Notionで商材を調査する 4. Googleカレンダーで都合の良いミーティング日時を確認し、仮予定を入れる 5. Notionに新規チケットを作成し、タスクを記載する 6. Gmailでクライアント向けにメールを返信する 7. Slackでチームメンバーへチケットの内容をシェアする    *問い合わせの数だけ処理を行う   仕事の内容(エム氏のやり方) 1. Claude Desktopでプロンプトを入力し、一連の仕事を指示する (MCPサーバと連携したAIエージェントが、一連の仕事を自律的に進める) 2. 各種処理結果を確認する 3. クライアントへのメール返信の下書きを確認し、送信する   実際の画面を見てみましょう。 まずはClaude Desktopでプロンプトを入力する部分です。 細かく書いてありますが、同僚から教えてもらった仕事が羅列してありますね。 ここでAIエージェントに仕事全体のタスク、役割、注意点などをインプットしています。   これ以上の指示はありません。あとはAIエージェントが自律的に処理を進めてくれます。 MCPサーバーの設定手順は、すでに多くの情報が公開されています。 もしご興味があれば、GitHubのREADMEや公式ドキュメントをご参照ください。   Gmailから問い合わせメールを見つける AIエージェントの動きを、一つずつみていきましょう。 まずはGmailの確認です。検証のために以下のようなテストメールを受信させてみました。   MCPを使って Gmail と連携するよう設定した後、以下のプロンプトを与えます。 1. Gmailで問い合わせメールを確認 – 未読メールは必ずチェック – 既読メールは直近1週間でチケットが作成されていない場合は対応する Claude上では、プロンプトを流した後にそれぞれの処理が表示されます。 詳細を開くと、リクエストやレスポンス内容を見ることができます。 先ほどGmailで受信していたメールをしっかり捉えていますね。 ここでは、GmailのMCPサーバを設定しています。 メールの読み取りや送信など、さまざまな操作ができます。 GitHub - GongRzhe/Gmail-MCP-Server: A Model Context Protocol (MCP) server for Gmail integration in Claude Desktop with auto authentication support. This server enables AI assistants to manage Gmail through natural language interactions. A Model Context Protocol (MCP) server for Gmail integration in Claude Desktop with auto authentication support. This ser... github.com   問い合わせ元の企業をWeb調査する 次に、問い合わせを頂いた企業についてWebで調査します。 商談を見据え、どのようなアプローチをするべきかを検討するため、まずは情報を収集します。 会社のホームページや分析のサイト、ニュースなどを頑張って調査してもよいですが、なかなか時間がかかります…。   そこで、この処理を自動で行えるようにします。 現在のClaudeにはWeb検索の機能がありますが、 fetch というMCPサーバと接続することで、WebページのコンテンツをMarkdown形式で取得することができます。 プロンプトは以下です。 2. 顧客企業をWeb調査(web_search + 必要に応じてweb_fetch) – 企業概要300文字程度 – 商談活用ポイント300文字程度 上記のように色々なWebページを探して情報を取得してます。 結果は次の処理で利用しているので後述します。 今回のシナリオは、問い合わせのメールを架空の企業から受信した設定ですが、これではWeb調査ができないため、代わりにSCSKセキュリティ株式会社を検索するようにしています。 MCPサーバfetchは以下のページを参照ください。 servers/src/fetch at main · modelcontextprotocol/servers Model Context Protocol Servers. Contribute to modelcontextprotocol/servers development by creating an account on GitHub. github.com   商談アプローチの戦略を分析する 続いて商談アプローチの戦略分析です。 どのように商談に挑むかを事前に検討しておきます。   ここではMCPサーバの Sequential-Thinking を利用します。 MCPの設定を行い、以下のプロンプトで指示を行います。 3. Sequential-Thinkingで商談アプローチ戦略を分析   Sequential Thinkingは、複雑な問題を段階的に解決できる仕組みです。 問題の理解を深めつつ思考を見直したり、異なる推論パスに分岐、また思考を動的に拡張できます。 LLM単体の思考で難しかった思考のプロセスを実行することができます。 色々な要素が絡みあう複雑な問題に対して効果的です。   実行すると以下のようなリクエストとレスポンスが表示されます。 リクエスト { `thought`: `SCSKセキュリティ株式会社への商談アプローチ戦略を分析します。まず企業の特徴を整理: - SCSK株式会社100%出資の専門会社(2023年8月設立の新しい会社) - セキュリティサービス開発・販売が主事業 - 脆弱性診断、コンサルティング、トレーニングが主力 - \"SECURE YOUR SITE\"をブランドとしている - 最新技術と人的リソース提供による総合支援 問い合わせ内容:営業部署でのAI活用について相談したい`, `thoughtNumber`: 1, `totalThoughts`: 5, `nextThoughtNeeded`: true } レスポンス { "thoughtNumber": 1, "totalThoughts": 5, "nextThoughtNeeded": true, "branches": [], "thoughtHistoryLength": 14 }   他にもいくつかのリクエストが実行されました。 { `thought`: `セキュリティ専門会社の営業部署でのAI活用ニーズを分析: 1. 顧客のセキュリティリスク分析の自動化 2. 脆弱性診断レポートの効率化 3. セキュリティ提案書の作成支援 4. 顧客情報管理とリード管理の最適化 5. マーケティング活動(コンテンツ作成、ターゲティング) 特に、セキュリティ業界では技術的専門性が重要なため、営業担当者がより効率的に顧客のニーズを理解し、適切な提案を行うためのAI活用が求められているはず。`, `thoughtNumber`: 2, `totalThoughts`: 5, `nextThoughtNeeded`: true } { `thought`: `商談での価値提案ポイントを戦略的に整理: 【強み活用ポイント】 - 新設会社なので、最新のAI技術導入に対して柔軟性がある - SCSK本体の豊富なIT基盤とリソースを活用可能 - セキュリティ専門性×AI活用で差別化された営業プロセス構築が可能 【提案すべきソリューション方向性】 - セキュリティ提案の精度向上(顧客環境分析AI) - 営業プロセスの効率化(CRM+AI分析) - 顧客コミュニケーションの質向上(技術説明支援AI) この企業には「競争優位性の確立」と「営業効率化」の両面でアプローチするのが効果的。`, `thoughtNumber`: 3, `totalThoughts`: 5, `nextThoughtNeeded`: true } { `thought`: `具体的な商談アジェンダと進め方を設計: 【第1段階:現状ヒアリング】 - 現在の営業プロセスと課題 - 顧客管理の方法と精度 - 提案書作成にかかる時間・工数 - 競合他社との差別化ポイント 【第2段階:AI活用可能性の提示】 - 業界特化型のAIソリューション事例 - ROI試算とコスト効果 - 段階的導入プラン 【第3段階:次ステップの合意】 - パイロット導入の可能性 - 決裁者との面談設定 - 詳細提案のタイムライン セキュリティ業界の専門性を理解していることを示しつつ、具体的な成果を数値で示すことが重要。`, `thoughtNumber`: 4, `totalThoughts`: 5, `nextThoughtNeeded`: true } { `thought`: `最終的な商談成功戦略をまとめ: 【成功要因】 1. セキュリティ業界への理解度の高さをアピール 2. 新設会社ならではの成長機会とAI導入タイミングの良さを強調 3. SCSK本体との連携可能性も含めた総合提案 4. 短期的な効率化と中長期的な競争優位性の両方を提示 【想定される懸念点への対策】 - コスト面:段階的導入とROI明示で対応 - セキュリティ面:AI利用時のデータ保護対策を事前に準備 - 技術導入負荷:既存システムとの連携を重視した提案 この戦略により、単なるAI導入提案ではなく、SCSKセキュリティの事業成長を支援するパートナーとしてのポジション確立を目指す。`, `thoughtNumber`: 5, `totalThoughts`: 5, `nextThoughtNeeded`: false }   上記の結果は後続の処理でNotionに出力されるようにしていますが、以下のようにまとめられています。 情報収集と整理のベースとして良さそうです。 【企業概要】 SCSKセキュリティ株式会社:2023年8月設立のセキュリティ専門企業。SCSK株式会社の100%子会社(資本金5,000万円)。セキュリティサービス開発・販売、脆弱性診断/評価、トレーニング等を主事業としており、グループ全体で現在200億円のセキュリティ事業を2030年までに1000億円に成長させる目標を掲げている。 【商談活用ポイント】 営業部門でのAI活用を検討中の同社に対し、「セキュアなAI活用」という独自視点でのアプローチが有効。営業活動で扱う顧客情報の安全な管理、CRM・SFAシステムのセキュリティ強化、リモート営業環境のセキュリティ対策などの課題を提起し、当社のAI営業支援コンシェルジュとセキュリティソリューションの組み合わせ提案を行う。 【戦略分析】 差別化ポイント:単なるAI導入支援ではなく「セキュアなAI活用」という独自視点、営業部門特有のセキュリティリスクに特化した専門性、SCSKグループの総合力との連携可能性。アプローチ:セキュリティアセスメントから信頼関係を構築し、営業AI活用の全体設計でコンサルティング契約、その後セキュリティ製品・サービスの継続提供へと発展させる戦略。   Sequential ThinkingのMCPサーバは以下のページになります。 servers/src/sequentialthinking at main · modelcontextprotocol/servers Model Context Protocol Servers. Contribute to modelcontextprotocol/servers development by creating an account on GitHub. github.com   紹介できそうな商材をNotionで検索 商談に備え、社内の商材で紹介できそうなものをあらかじめ調べ、準備しておきます。 会社の中には商材がたくさんあり、すべて把握してきれていない場合も多いのではないでしょうか。 まとめて検索できれば良いのですが、詳しい人や部署に聞くという力技な戦術も耳にします。   この会社はNotionに商材情報をまとめています。 Notion のMCPサーバが提供されているため、設定して情報を取得できるようにします。 プロンプトは以下です。 4. Notionから紹介できそうな商材をピックアップ – 商材は1つあれば十分だが、決められない場合は3個程度ピックアップする 以下のようにNotionを検索しにいき、「AI営業支援コンシェルジュ」という商材がピックアップされました。   権限はページごとにNotion側で設定しておく必要があります。 今回はGmailで営業支援の問い合わせが来ていたので、その情報をもとに営業支援の商材を選定しています。 ツール間の繋がりを持って処理しているのは使い勝手が良いですね。 NotionのMCPサーバです。 情報取得だけでなく、更新やページ作成など様々な操作ができます。 GitHub - makenotion/notion-mcp-server: Official Notion MCP Server Official Notion MCP Server. Contribute to makenotion/notion-mcp-server development by creating an account on GitHub. github.com   商談日程の候補をGoogle Calendarで探して枠を仮設定 さて、色々と情報を揃えたところで、問い合わせ元の企業との商談を設定しましょう。 これまで同僚はGoogle Calendarで空き時間を見つけ、候補をメールで送っていました。 予定がいっぱいだったり参加者が多いと、会議調整だけでも一苦労ですよね…。   Google Calendar のMCPサーバを設定して、自動で候補日時を確認、仮予定で枠を押さえるようにします。 プロンプトは以下です。 5. Google Calendarで空き時間4候補を提示 – 期間は2営業日後から7営業日後までの間 – 開始時間は9:00から17:00まで – 会議時間は1時間 6. 候補日時でGoogle Calendarに仮予定登録 また、注意事項として「担当者のスケジュールがすべて埋まっている場合は、優先度が低そうな予定をいくつかピックアップし、代替案を提案するタスクのみを作成してください」と設定しています。 枠に空きがない場合、AIエージェントは自律的に考えて代替案を提示しそうではありますが、あらかじめ決められたフローがある場合、事前にインプットしておくほうが安全でスムーズです。   まず、空き時間を探していくつか候補の枠を見つけました。 恐ろしいことに土曜も候補に上がっていますね…。 プロンプトに時間帯の指定は設定したのですが、曜日の指定をしていませんでした。 この辺りも丁寧に設定してあげると、綺麗に動きそうです。 どうやら、仮予定が登録されたようです。   実際にGoogle Calendarを見てみると、テストで枠を押さえたところ以外に仮予定が入っています。 これは便利ですね。   今回利用したGoogle CalendarのMCPサーバはこちらです。 GitHub - nspady/google-calendar-mcp: MCP integration for Google Calendar to manage events. MCP integration for Google Calendar to manage events. - nspady/google-calendar-mcp github.com   Notionにチケットを作成 ここまでの情報(問い合わせ内容、顧客の情報、商談アプローチ、打ち合わせ候補日程)をNotionにチケットとして登録しておきましょう。 通常は手動で入力するものですが、件数が多いと非常に手間がかかりますね。 ここでもNotionのMCPを活用して、自動登録を行います。 7. Notionにチケット作成(商談予定ステータス) – 詳細欄に企業概要、商談ポイント、戦略分析を記載 – 商材名には紹介する商材名をリストより選択する API制限でチケット作成が失敗したケースがあったため、「API制限を受けないよう、リクエストの間隔は数秒開けてください」という文言を注意事項としてプロンプトへ記載しています。 チケットの作成処理が動いています。   Notionでチケットリストを開くと、正しくチケットが登録されていました。 商材名も、商材一覧より「AI営業支援コンシェルジュ」が選択されていますね。   詳細欄に情報を記載するようにしたため、ちょっと見づらくなってしまいました。 プロンプトを工夫して、ページ本文や項目欄に記載したほうがよさそうです。     返信のドラフトメールを作成 これで仕事は終わり…ではなく、大事なことを忘れています。顧客へのメール返信です。 メールを作成して、打ち合わせの日程候補を提示し調整します。 ここは 人がチェックをした後にメールを送りたい ので、ドラフト作成までとします。 勝手にメール送信してくれると楽ですが、どんな内容かを見ないで送られるのは怖いですよね。   プロンプトにはドラフト作成(送信禁止)という文言を記載しています。 8. 顧客への返信ドラフト作成(送信禁止) また、注意事項のプロンプトにも念のため「メールは絶対に自動で送信しないでください。必ず下書きを作成するまでに留めてください」の記載を追加しています。 システム機能的に明確に送信を禁止できれば安心なのですが、プロンプトでコントロールするしかない場合の策です。 ちなみにこのMCPサーバは、draft_emailとsend_emailで別々にツールが用意されているため、このツール名をプロンプトに含めるとより安定感があるはずです。 GmailのDraftフォルダを開くと、それっぽい文面と候補日時の提示が書かれたドラフトメールが保存されていました。   Slackでチームに情報共有 いよいよ最後のステップです。 これまでの対応状況をチームのSlackに流して、同僚に情報をシェアしましょう。   Slack のMCPサーバを設定し、どのSlackチャンネルでシェアするかを指定します。 9. #assigned-ticketsチャンネルでチームに状況シェア   Slackを開くと、まとめられた情報が投稿されていました。 特にフォーマットを指定していませんでしたが、見やすく記載されています。 決まったフォーマットに統一したい場合は、プロンプトに指定の構成を入れると良いでしょう。 SlackのMCPサーバはこちら。 Reference ServersのArchivedに存在しています。 servers-archived/src/slack at main · modelcontextprotocol/servers-archived Reference MCP servers that are no longer maintained - modelcontextprotocol/servers-archived github.com   エピローグ 日を追うごとに、エム氏がもたらした魔法のような効果は会社の日常を変えていった。   こんなに仕事の進め方が変わるなんて!   同僚たちは驚きと感謝の言葉を口にした。 しかし、ある日の夕方、彼の姿は消えていた。席には、彼が愛用したクッションだけが残されている。 エム氏がいなくなっても、彼が作り上げたMCPの仕組みは動き続けた。 同僚たちは、彼が残した仕組みを使うだけでなく、AIを仕事に活用するマインドを身につけることができたのだった。 おわりに 本記事では、AIとMCPで仕事が変革される可能性について、架空の会社の業務シナリオを通して見てみました。 実際に仕事に組み込む場合は考慮事項も多く今回のようにシンプルではありませんが、 エポックメイキングな変化を起こす可能性 は十分に感じられました。   MCPの課題 MCPを活用するにあたり、現時点では課題となる点も多くあると考えています。   まずは、プロンプトを通じた不確定なアクションです。 例えば、メールが下書きではなく送信されてしまう可能性や、Notionの異なるページを削除してしまう可能性などがあります。 更新を伴う処理で影響が大きいものについては、不安が残りますね。 また、ツールやコマンドを多く登録していくと、似たような機能や名前が競合してしまい、意図しない動作をするかもしれません。 LLMが曖昧な指示でも受けてくれる一方で、システム的な決まった動きを求める場合には不向きかもしれません。   セキュリティのリスクもあります。 非公式のMCPサーバには、なりすましやインジェクションなどのセキュリティリスクが含まれる可能性があります。 強い権限を持つMCPサーバは便利である反面、悪用された場合の影響も大きいものです。   果たしてMCPは全てのシーンで必要なのでしょうか。 より安全性が高い既存のアーキテクチャ、例えばAPI連携で十分対応できないのでしょうか。 利便性だけではなく、使いどころを見極めて適切に導入する必要があるはずです。   MCPの可能性 これまで単体でしか効率化できなかった仕事が、MCPを通して容易にインテグレーションされ、LLMにより対話的に指示ができるようになりました。 2025年8月の時点で数多くのMCPサーバが公開されています。 様々な会社がサービスをMCPに対応させ始めています。 これまで実現できなかったアイデアが、容易に実現できる時代にシフトしてきています。   一方で、コモディティ化されカジュアルに利用できるようになるには、まだ時間がかかると考えています。 どのようなテクノロジーなのか、何の役に立つのか。正しく理解し体感しながら、可能な範囲で利用していくような向き合い方が良いのではないでしょうか。   この物語でAIとMCPを使って実行した部分は筆者が実際に検証した内容ですが、シナリオはフィクションです。 登場する企業名、人物名、団体名などは架空のものを含んでいます。 本記事では、一部Community ServersのMCPサーバを利用しています。 実際の利用においては、業務への影響やセキュリティなどご自身の責任でお試しいただければと思います。
アバター
SCSK LifeKeeper担当 池田です。 新人エンジニアの皆さんの中には、新人研修も一通り終えて、部署に配属されるかたもいらっしゃるかと思います。 これまでの研修のなかで、「機能要件」や「非機能要件」と言ったキーワードが出てきたかと思いますが、今回は「非機能要件」の「可用性」について考えていきたいと思います。 機能要件と非機能要件について まずその前に、機能要件と非機能要件について簡単におさらいしたいと思います。 機能要件 とは、「システム構築におけるお客様の要求事項」のなかでも業務内容に直結したものです。例えば「営業情報をシステム上で共有し把握したい」、「受発注情報に連動した在庫管理を行いたい」など利用者が自らの言葉で語ることのできるという特徴を持ちます。 一方で、 非機能要件 は、どちらかというと技術要素が強く、利用者からは具体化が進まないと語りにくい内容が多く、例えば、「レスポンスタイムは2秒以内にしてほしい」、「1点で障害が発生してもシステムダウンとならないようにして欲しい」、「将来的に性能を拡張できるようにしてほしい」といったものが該当します。 上記の通り、機能要件は、まさに利用者が「実現したいこと」であるため、利用者側から出てくることが多いですが、 非機能要件については利用者でイメージすることが難しく、利用者側から網羅的に出てくることあまりありません 。 それゆえに要件定義の段階で、エンジニア側からの丁寧な説明と、利用者側との共通認識を持ちながら進めていくことが大切になります。 非機能要件にはどのようなものがあるか 非機能要件には、どのようなものが含まれるかを整理したいと思います。IPA(独立行政法人情報処理推進機構)が公開している非機能要求グレードでは以下の6つが示されています。 ・性能・拡張性:システムの応答時間や処理速度、システムが将来的にどれだけ容易に拡張できるか ・可用性:使いたい時に使えるか。システムがどれだけの時間稼働しているか ・セキュリティ:データの保護やアクセス制御 ・システム環境・エコロジー:法令や条例などの制約、耐震性、エネルギー消費効率など ・運用・保守性:システムの運用や保守のしやすさ ・移行性:他のシステムへの移行の容易さ いずれもシステム開発で重要な要素ですね。 その中でも、今回は 可用性 について掘り下げたいと思います。 可用性とは、(上述した通り)利用者が使いたい時に使えることを意味しますが、逆に可用性を考慮しなかった場合はどのようなことが起こるでしょうか。想像してみましょう。 可用性を考慮しなかった場合を想像する 例えば、ECサイトで想像してみましょう。今やいつでも誰でもどこでも利用できるようになり、頼んだ次の日には届く、配達場所も選べるなど、生活になくてはならない存在となっています。いつでも使い時に使える、これが可用性です。 ここで想像してみましょう。 あなたは、いつもと同じように、ECサイトにアクセスして、明日必要な商品を購入しようと思いました。しかしいつまで経ってもECサイトにアクセスできません。その後何度かアクセスを試してみましたが、やはりダメでした。あなたはきっとこう思うことでしょう。「なんで肝心な時につかえないんだ!!」 このECサイトは、可用性を高める方策を取っていませんでした。これでは、あなたにとっては「買いたい時に買えない」、ECサイト側にとっても「売れるチャンスに売れない」という状況になってしまいます。このようなことが続けば、利用者はこのECサイトから離れていってしまうことでしょう。 だから可用性を考慮することが大切 先述の一例からも、可用性を考慮することはとても大切だということがお解りいただけたのではないでしょうか? 可用性を高めるためのライセンスや保守、運用といった費用はかかってしまいます。しかしひとたび障害が発生してしまうと、機会損失による逸失費用やイメージダウン、信用失墜などに繋がりかねません。 また将来確実に起こるとされている首都直下型地震や南海トラフ地震、富士山噴火などが発生した場合は、数週間から数か月、場合によっては年単位で復旧に時間を要すことが想像されます。 そのように考えると、 可用性を考慮することは、必要不可欠であり、ビジネス継続の柱 ともいえるのではないでしょうか? とは言え、全てのシステムでお金をかけて可用性を向上することは現実的ではありませんよね。 その為、これから構築しようとするシステムが停止してしまった場合の復旧までに発生する損失額を試算し、可用性を高めるための費用を比べ適切に判断することが重要となります。 可用性にお金をかけるべきシステムかどうかをちゃんと考えなきゃ LifeKeeperは可用性レベルとコストのバランスが取れた製品 ここまで可用性の重要性をお伝えしましたが、サーバを始めとする機械は、 必ず壊れるものである 前提で考える必要があります。 世の中には様々な可用性を高める製品がありますが、私がお薦めするのが、サイオステクノロジー社のLifeKeeperとなります。可用性レベルには大きく以下の表にある通り5つほどあります。上位であればあるほど、ダウンタイムが少なくなりますがその分コストが高くなります。 LifeKeeperはフォールトレジリエント と呼ばれる、障害による 年間のダウンタイムが53分 まで許容されるシステムで用いられます。中間レベルの可用性レベル(99.99%)となっており、可用性レベルとコストのバランスが取れた製品となっているのが特徴となります。 興味を持たれたかたは、是非SCSKに見積を依頼していただき、障害発生時の損失(企業イメージのダウンを含む)と比べていただくことをお薦めします。 まとめ 今回は、非機能要件のなかでも「可用性」についてお伝えしてきました。システムの重要度によって、可用性を高める必要を見極める必要があることをご理解いただけたかと思います。 LifeKeeperについて、詳しい内容をお知りになりたいかたは、以下のバナーからSCSK LifeKeeper公式サイトまで
アバター
  SASEプラットフォームのリーダー認定 Cato Networks社が、2025年7月9日に発行された ガートナー(Gartner™)のマジック・クアドラント(Magic Quadrant™)のSASE プラットフォーム部門において2年連続でリーダーに認定されました。 ガートナーのマジック・クアドラントの調査レポートについては、Cato Networks社の以下サイトから無償で閲覧することが可能ですので詳細については、そちらをご覧ください。 Cato Networks named a Leader in the 2025 Gartner® Magic Quadrant™ for SASE Platforms. Again. Cato’s a Leader in the 2025 Gartner® Magic Quadrant™ for SASE Platforms. Want to learn more? Download the full report no... www.catonetworks.com また、Cato Networks社 CEO シュロモ・クレイマーのブログ記事は以下となります。 A Leader Once Again: Cato Networks Recognized in 2025 Gartner® Magic Quadrant™ for SASE Platforms  For the second consecutive year, Cato Networks has been recognized as a Leader in the 2025 Gartner® Magic Quadrant™ for ... www.catonetworks.com Cato Networks社は、昨年(2024年)シングルベンダーSASEのマジック・クアドラントでリーダーに認定されています。 Cato Networks社が 2024年 ガートナーのマジック・クアドラントのシングルベンダー SASE でリーダーに認定 Cato Network社 Catoクラウドが 2024年最新版 ガートナー マジック・クアドラントのシングルベンダー SASE でリーダーに認定されました blog.usize-tech.com 2024.07.22 特に、日本国内においては、ソリューションの検討・採用の際に、ガートナーのマジック・クアドラントが重要視されるケースが多いので、2年連続でSASEのリーダーに選出されて、確固たる地位を確立したと言えます。 ガートナーによると、SASE市場は、2023年から2028年まで年間平均成長率 (CAGR) は 26%で、2025年には150億ドル(約2.2兆円)に達すると予測されています。 また、2028年までに、SASEの新規導入の半分(50%)がシングルベンダーSASEプラットフォームを選択するとされており、2025年の30%から大きく増加しています。ベストオブブリードのマルチベンダーでSASEを構成するよりも、単一(1社)でSASEを提供するシングルベンダーSASEプラットフォームの方が多くの支持を得ると予測されています。 Cato Networks社は、2019年にガートナーが、Secure Access Service Edge(SASE、サッシー)を提唱する以前の2015年に創業しており、Catoクラウドは、創業当初からSASEの概念で、クラウドネイティブなプラットフォームを構築しており「SASEの市場を新たに開発し、牽引した革新的なパイオニア」、「世界初のSASEプラットフォーム提供ベンダー」として評価されています。 他のSASEベンダー、SASEプラットフォームとは異なり、企業買収(M&A)によるセキュリティ機能の統合を行っていないことから、「統一されたプラットフォーム」「単一でシンプルなUI」が高い評価を得ており、ユーザエクスペリエンス(UX)が、他のSASEプラットフォームと比較して高い評価を得ています。 また、ユーザエクスペリエンス(UX)が高いことから、他のSASEプラットフォームと異なり、外部ベンダーへ設定を依頼することなく、お客様自身で設定・運用保守を行うことができるソリューションとしても、Catoクラウドを採用する企業が増えてきています。   Catoクラウドの検討にあたって 日本国内において「SASE(Secure Access Service Edge、サッシー)」、「Cato Networks(ケイトネットワークス)」、「Catoクラウド(正式名称:Cato SASE Cloud Platform)」の認知度は徐々に上がってきていますが、まだ認知度が高いとは言えない状況です。 SCSKでは、2021年からSASEの主要な4ソリューションを一同に紹介を行うオンラインセミナー「SCSK SASE Solution Summit(S4、エスフォー)」を定期的に開催しております。 これまで15回開催し、述べ2,200名以上の方にご参加いただいております。 また、Catoクラウドについては、初心者向けのSASE簡単セミナーから、Catoクラウドの主要機能を管理コンソールを用いて2時間で説明するデモセミナーを開催しており、さらに全国各地で半日のハンズオンセミナーも、年間通じ定期的に開催しておりますので、ご興味ある方は、ぜひご参加ください。 Catoクラウド イベント・セミナー情報 | よくあるご質問 | Cato Cloud ケイトクラウド - SCSK 定期的にCatoクラウドをご紹介するセミナーを開催しております。・2024年9月26日(木)Catoクラウド ハンズオンセミナー【札幌開催分】 ~全国5都市開催(東京/大阪/名古屋/博多/札幌)~ ... cato-scsk.dga.jp 特に、来月2025年9月に九州(博多)、再来月2025年10月に北海道(札幌)にて、ハンズオンセミナーを開催しますので、九州、北海道の方は、ぜひご検討ください。 東京、大阪は多くの参加希望があるのですが、その他の地区(博多、札幌)については比較的参加人数が少ないので、ぜひご参加ください。   次に、Catoクラウドを検討を進めるにあたっては、Catoクラウドのパートナーを探す必要があります。 2025年7月に、Cato Networks社のパートナープログラムがリリースされていますが、Cato社のホームページ上にはパートナーは掲載されていませんので、Web検索などでパートナーを探す必要があります。 Cato パートナープログラムで最高位の「MSP アドバンスドパートナー」を獲得しました! Cato Networksが新たに整備した「チャネルファーストパートナープログラム」において「マネージドサービスプロバイダー(MSP)」の最高位「アドバンスド」を獲得しました。 blog.usize-tech.com 2025.08.06 SCSKは、パートナープログラムの最上位である「 Cato Networks MSP アドバンスドパートナー 」を獲得しているパートーナーです。 さらに、2025年7月に開催されたCato Networks社の日本で3回目となるパートナーサミット「Japan Partner Summit Tokyo 2025」で、 3年連続でアワードを受賞 しています。 今年度は、新規顧客の売り上げに最も貢献したパートナーとして「 Japan Highest New Logo Revenue 2024 」と、最も卓越したサポートを提供し、お客様の満足度向上に貢献したパートナーとして「 Japan Best Technical Support Partner 2024 」の2つのアワードをダブルで受賞しています。 これからCatoクラウドを検討し始めるお客様にちては安心して当社まで お問い合わせ ください。 上記以外に、Catoクラウドの お客様導入事例 の制作、好評をいただいているCatoクラウドの FAQサイト 運営、この TechHarmony(技術ブログ) で、Catoクラウドの日本語での情報提供を積極的に行い、皆様のお役に立ち、Catoクラウドの認知度の向上ができればと考えております。 Catoクラウド エンジニアブログまとめ記事 Catoクラウドのエンジニアブログ(TechHarmony)でのまとめ記事となります。 blog.usize-tech.com 2024.02.27
アバター
みなさん、こんにちは。 SCSK株式会社の津田です。 LifeKeeperについて少しでもご存じの方であれば、 障害発生時には「フェイルオーバー」によって高可用性を確保する、という理解をお持ちではないでしょうか。 ノード障害が発生した際にはその通りですが、IPアドレスやアプリケーション等のリソースで障害が発生した際には、 「フェイルオーバー」の前に自ノードでリソースの復旧を試みる「ローカルリカバリー」という機能があります。 本記事では、その「ローカルリカバリー」についてご紹介します。 !おさらい! ●リソースとは:LifeKeeperで監視や制御を行うアプリケーション、IPアドレス、ボリューム等の単位。 ●フェイルオーバーとは:稼働系ノードでアクティブであったリソースやサーバー、ネットワークにおいて障害や異常終了が              発生したときに待機系ノードへ自動的に切り替えを行う動作。 なぜローカルリカバリ? 実はフェイルオーバーには留意事項があります。 フェイルオーバーが発生すると、稼働系ノードで各リソースが停止し待機系ノードで起動する為、数秒から数分のシステム停止が避けられず、業務への影響を招く可能性があります。 リソース障害でフェイルオーバーを行う場合、依存関係によって正常に稼働していたリソースまで一時停止の影響を受けることになります。このような性質を持つフェイルオーバーを最小限に抑える機能の一つとして、ローカルリカバリーは利用できます。 ローカルリカバリーは、LifeKeeperのリソース監視処理で障害を検知した後で、待機系ノードへのフェイルオーバーを実施する前に自ノード上で障害リソースの再起動を試みる機能です。リソース単位でまず復旧を試みることで、他の正常なリソースの停止やフェイルオーバーによるシステム停止を最小限に抑えることができます。 ローカルリカバリーはリソース単位で有効/無効が設定が可能です。もしローカルリカバリーが失敗した場合でも、フェイルオーバーが実行されますので2段構えで障害復旧が行われます。 <ローカルリカバリー有効時のリソース障害フロー>    <ローカルリカバリ無効時のリソース障害フロー>   ローカルリカバリー設定方法 ローカルリカバリーの有効/無効の設定方法は、LifeKeeper for Linux、LifeKeeper for Windowsで異なりますが、 それぞれリソース毎、ノード毎に設定を行うことが可能です。 LifeKeeper for Linux 対象のリソース作成後、以下コマンドから設定が可能です。 ※デフォルトは有効状態 <有効化する場合> 以下コマンドを実行する。 /opt/LifeKeeper/bin/lkpolicy –set-policy LocalRecovery –on tag=<リソースタグ> ※コマンド実行後は何も出力されません。 ※クラスタ内のノード毎に設定する必要があります。 <無効化する場合> 以下コマンドを実行する。 /opt/LifeKeeper/bin/lkpolicy –set-policy LocalRecovery –off tag=<リソースタグ> ※コマンド実行後は何も出力されません。 ※クラスタ内のノード毎に設定する必要があります。 <設定確認方法> 以下コマンドを実行する。 /opt/LifeKeeper/bin/lkpolicy -gv ・コマンド実行結果例 ※上記赤枠で各リソース毎のローカルリカバリー設定が確認できます。(ON:有効、OFF:無効) ※クラスタ内のノード毎に確認する必要があります。 LifeKeeper for Windows WindowsではIPリソースとそれ以外のリソースで設定方法が異なります。 <IPリソースで有効化/無効化する場合> IPリソース作成時の設定項目[ローカルリカバリー]にて設定が可能です。 プルダウンより、はい/いいえを選択し、そのままリソース作成を進めます。 ※リソース作成後に有効/無効を変更する場合は、IPリソースの再作成が必要となります。   <IPリソース以外のリソースで有効化/無効化する場合> リソース作成後にLifeKeeperGUIから設定が可能です。 ※デフォルトは有効状態 (1)LifeKeeperGUIにて、ローカルリカバリーの設定を行いたいリソース上で右クリックし、「ローカルリカバリ―」を選択します。 (2)ノード名を選択し、[次へ]を選択します。 ※クラスタ内のノード毎に設定する必要があります。 (3)プルダウンより有効/無効を選択し、[変更]を選択します。   <設定確認方法> コマンドプロンプトにて以下コマンドを実行する。 C:\LK\Bin\ins_list.exe -f / ・コマンド実行結果例 ※上記赤枠(”/”区切りで後ろから2つ目のフィールド)で各リソース毎のローカルリカバリー設定が確認できます。(1:有効、0:無効) ※クラスタ内のノード毎に確認する必要があります。   注意事項 ローカルリカバリーを検討している場合、以下の点にご注意ください。 ● LifeKeeper for WindowsのIPリソースではリソース作成後のローカルリカバリー有効/無効が変更出来ません。  変更の必要がある場合は リソースの再作成を行い、作成時の設定の中でローカルリカバリー有効/無効を指定してください。 ● ボリュームリソースでは、ローカルリカバリーの機能は利用できません。 ● ローカルリカバリーは、成功すればフェイルオーバーが不要となりリカバリーに要する時間の短縮となりますが、   失敗した場合は失敗後にフェイルオーバーが実施されるため、障害が発生したリソース(サービス)の停止時間が長くなってしまいます。   そのうえで”ローカルリカバリー、フェイルオーバーの二段構えで障害復旧を行う”、”フェイルオーバー前にローカルリカバリーを行う   ことで業務影響を可能な限り抑える”、”ローカルリカバリーが失敗するケースを考慮して、初めからフェイルオーバーを行うようにする”   等、各システムの状況やリソース構成により適宜有効/無効をご検討頂ければと思います。 さいごに 今回はLifeKeeperの「ローカルリカバリー」機能についてご紹介させて頂きました。 リソース障害が発生した際、フェイルオーバーの前に試すことができる最小限の障害復旧機能として「ローカルリカバリー」があることを ご理解頂けましたら幸いです。 詳しい内容をお知りになりたいかたは、以下のバナーからSCSK Lifekeeper公式サイトまで
アバター
本記事は 夏休みクラウド自由研究2025 8/19付の記事です 。 皆さんこんにちは。UGです。 今年の夏も暑すぎていやになっちゃいますね… 体感でいつも今年はやばいと言っているだけで、データとしてどーなっているのかちゃんと見たことないなと思い調べてみたところ、毎年しっかり暑さが増しているようです…長期的には100年あたり1.31℃の割合で上昇しているとのこと… 気象庁 | 日本の季節平均気温 日本の季節平均気温 www.data.jma.go.jp 熱中症には気を付けましょう。。。 さて本題ですが、皆さんMFAってめんどくさくないですか?自分はめんどくさいと思ってます。 自分は多くのAWSアカウントを利用する業務に携わっているため、サインインするたびにMFAやってたらやる気が失せちゃいます… とは言えMFAを設定しないわけにはいかないので、設定したうえで少しでも楽になる認証アプリないかな~と探していたら、割と最近リリース(2025年7月)されていた「Proton Authenticator」というアプリを見つけたので試してみたブログになります。 「Proton Authenticator」はPC・スマホ両方で利用できますが、本記事はPC版を使ってみての記事となります。 結論 まず先に「Proton Authenticator」についての概要を表にまとめておきます。 機能/項目 内容 対応デバイス モバイル: iOS、Android デスクトップ: Windows、macOS、Linux デバイス間同期 同期可能 ※Appleデバイスの場合はiCloudで可能。Windows、Linux、またはAndroidの場合は、Proton AuthenticatorでProtonアカウントにログインする必要あり 暗号化方式 E2EE(エンドツーエンド暗号化) バックアップ 可能(自動バックアップを設定することで、最後の5日間のデータがバックアップされる) 他の認証アプリからのインポート 対応(一部アプリは不可、現状 Microsoft は不可) 価格 無料(広告・トラッキングなし) 公開状況 オープンソース 背景 自分はMFAの認証アプリとしてMicrosoft Authenticatorをデフォルトで使っていましたが、Microsoft Authenticatorはスマホアプリのため、認証の度にアプリを起動、かつ手入力は面倒この上ないです。 なのでPCで完結させたいと次に手を出したのがWinAuthでした。 WinAuthはPCアプリなので、PCのみでMFA認証を完結できます。さらには番号を表示する際に自動的に番号をコピーしてくれる設定もあるため、手入力の手間も省けます。 AWS スマホを使わずに MFA (多要素認証) を設定する手順 (画像付き) – WinAuth AWS の MFA (多要素認証) を設定してセキュリティ強化する話です。不正アクセス防止に役立つので、MFA デバイスは設定しておいた方が良いでしょう。一般的な方法としてはスマートフォンを MFA デバイスに設定するようですが、スマホを見... se-kowaza.com これで楽ちんだ~とルンルンな時期が自分にもありました。。。 しかし、WinAuthはデバイス間での同期ができないため、別の環境からサインインする場合に一からMFAを設定し直す必要があるのです… 自分は会社のPCやリモート接続先の環境、自宅のPCといった様々な環境から同じアカウントにサインインすることがよくあるので、デバイス間での同期ができない、は致命的でした。。。 それがわかってからはWinAuthも結局使わなくなってしまい、Microsoft Authenticatorで地道にポチポチしている日々でした。 なのでいい加減別のデバイス間の同期ができるアプリを探そう!と思って調べてみました。 WinAuth の GitHub リポジトリはすでに 2019年1月にアーカイブされ「read-only(読み取り専用)」になっており、それ以降の公式アップデートやメンテナンスは止まっている状態です。 MFAアプリのいろいろ 本記事では「 Proton Authenticator」について詳しく書いていますが、もちろん「Proton Authenticator」以外にも多くのMFAアプリがありました。 調査内で出てきたが採用しなかったものの理由を簡単にだけまとめておくので、もし気になるものがあればご自身で試してみてください。 アプリ名/方法 採用しなかった理由 Authy デスクトップ版は2024年にサポート終了していたため Chrome拡張型Authenticator(Authenticator Extension や 2FAS Chrome版など) 「ブラウザ依存」「セキュリティリスク」「バックアップが不安定」などから ※Chromeしか使わないし、手っ取り早くPCのみで完結させたいといった方は良いと思います 1Password 無料じゃなかったため(インストールしてみたら「14日間の無料お試し!」がでてきて、15日目からは自動で有料プランに切り替えられるそうです…) ※BitwardenというのもPC・スマホ両方で使えて、デバイス間同期もできたりで、実際に試せてはいませんが調べた感じだと非常によさそうでした。(試さなかった理由は「Proton Authenticator」の後に試そうと思って、「Proton Authenticator」でええな!ってなったからです。。。) Protonとは? 今回「Proton Authenticator」を試してみたのですが、このアプリ以外にも「Proton Pass」や「Proton Drive」など「Proton」の名の付くものがいっぱいありまして、そもそも「Proton」とは何ぞや?となりました。 なので皆様にも概要を少しばかりご紹介します。 Protonは2014年にCERN(欧州原子核研究機構)の科学者たちによるクラウドファンディングで誕生したスイスの技術企業です。 スイス拠点の運営により、非常に厳格なプライバシー法制のもとにサービスを提供されており、すべてのサービスはエンドツーエンド暗号化(E2EE)かつゼロアクセス設計で、Proton自身もユーザーのデータを読み取れません。 さらに、アプリはオープンソースで、外部セキュリティ専門家による監査も実施済みで信頼性が高いといった特徴があります。 また、今回ご紹介する「Proton Authenticator」以外にも、「Proton Mail」「Proton VPN」「Proton Drive」「Proton Pass」などの包括的なプライバシーエコシステムを提供しています。 Proton: Privacy by default Over 100 million people use Proton to stay private and secure online. Get a free Proton account and take back your priva... proton.me 実際に使ってみる そうしましたらダウンロードからMFA設定までを実際に行い、「Proton Authenticator」の画面や機能についてご紹介いたします。 ダウンロード 以下のページにアクセスし、「Proton Authenticator」をダウンロードします。 Authenticator app download: Get Proton Authenticator | Proton Download Proton Authenticator app for Windows, macOS, Linux, Android, and iOS. Protect your accounts with secure two-fac... proton.me ダウンロードファイルを実行して、画面通りにポチポチしていけばダウンロードが完了します。(特に難しい設定などはありません) ダウンロードが完了しアプリを開くと以下の画面出てきます。 MFAを設定してみる アプリ画面の「+Add」もしくは「Create new code」(何も登録されていないときのみ表示)をクリックし、「New code」という画面からMFAを設定します。 「Title」と「Secret」さえあれば必要最低限で登録が可能です。 設定できると以下のようにコードが表示されるようになり、コードのボックスをクリックするだけでコードがコピーされます。 「Advanced options」も含めた各項目についてを以下にまとめておきます。 項目 内容 Title 好きな名前を設定します Secret Secret keyを設定します Issuer 発行者として任意のものを設定できます Digits コードの桁数を「6~8」で設定できます Time interval コードの切り替わるまでの秒数を「30秒」or「60秒」で設定できます Algorithm ハッシュ関数を「SHA1」or「SHA256」or「SHA512」で設定できます Type パスワードの生成の仕組みを「TOTP」or「STEAM」で設定できます TOTP = RFC準拠の標準方式 STEAM = Steam専用(英数字5桁) 「Issuer」で特定の名前を設定するとアイコンが自動的に変わります。AWSはまだない、がAmazonはあります。。。 その他機能について 「Proton Authenticator」は他の認証アプリからMFAコードをインポートすることが可能です。 (アプリダウンロードしたての何も登録されていないときの画面にあった「import codes」や後述する設定画面の「Manage your data」からインポートが可能です) 画面上では上記画像のアプリが表示されていますが、「Authy」と「Microsoft」に関しては、以下の画面が表示され、現在はサポートされていないようでした。 次に設定画面の項目も以下にまとめておきます。 大項目 小項目 内容 Security Sync between devices 同期の設定ができます(オンにする際、Protonのアカウントにサインインする必要があります) App lock アプリを起動する際にパスワードを設定することができます Hide codes コードを隠す設定ができます Backups Automatic backups バックアップの設定ができます(オンにする際、保存先を指定します)※バックアップは最後の5日間のみです Appearance Theme アプリ画面のカラーモードを「Automatic」or「Dark」or「Light」で設定できます Digit style コードのスタイルを「Plain」(何もなし)or「Boxed」(1文字ずつ四角で囲う)で設定できます Animate code change コードが変更される際にアニメーションが発生するように設定できます Manage your data ー 他の認証アプリからコードをインポート、またコードをjson形式でエクスポートができます Support ー ヘルプページ、問い合わせページにアクセスします ※のはずですが自分はクリックしても何も発生せず… Discover Proton – 割愛 – 他のProtonアプリのサイトにアクセスします   まとめ めちゃくちゃシンプルなのと、同期もProton アカウントにログインするだけですし、かつ特に設定ワンクリックでコードコピーできたりで、非常に使いやすいなと! 知らないアプリだったのでセキュリティ面に最初は不安がありましたが、調べてみると信頼できるものだとわかり安心しました。 1点残念だったのはMicrosoftからコードを移行できなかったことですね…もちろん「Proton Authenticator」が悪いということではないですが、「インポートできる?」「やっほーい!」からの「インポートできないんかーい」だったので悲しさがありました… 皆様も自分と同じようなお悩みがあれば「Proton Authenticator」を使ってみるのもありかもしれませんよ? 最後までお読みいただきありがとうございました!!
アバター
本記事は 夏休みクラウド自由研究2025 8/18付の記事です 。 こんにちは。SCSK渡辺(大)です。 「鬼滅の刃」の映画を見に行きたいのですが、アニメを全て見終えていないため二の足を踏んでいます。 見るなら大きなスクリーンで見たいです。 当記事のAmazon QはAmazon Q Developer CLI、MCPはAWS MCP Serversを意味します。 背景 Amazon Q CLI でゲームを作ろうキャンペーンの報告と最新情報 update & Kiro のご紹介 というイベントにオンラインから参加した後、「AWSを利用することが楽しくなるゲームが欲しい」と感じたことがきっかけです。   ゲームの概要 今回はゲームプレイを目的とするのではなく、AWSを利用することが楽しくなるものにしたかったので、AWSを利用すると必ず生成されるCloudTrailのイベント履歴をインプットにした、放置ベースの街作りゲームにすることにしました。 また、息子が好きなトーマスと自分が好きなファイナルファンタジー9をテーマにすることにしました。   MCPの設定 設定したMCPは以下です。 使われそうなものを設定しました。 awslabs-aws-documentation-mcp-server awslabs.core-mcp-server awslabs.aws-diagram-mcp-server awslabs.nova-canvas-mcp-server awslabs.bedrock-kb-retrieval-mcp-server awslabs.aws-pricing-mcp-server mcp.jsonは以下です。 { "mcpServers": {   "awslabs-aws-documentation-mcp-server": {     "command": "uvx",     "args": [       "awslabs.aws-documentation-mcp-server@latest"     ],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR",       "AWS_DOCUMENTATION_PARTITION": "aws"     },     "disabled": false,     "autoApprove": []   },   "awslabs.core-mcp-server": {     "command": "uvx",     "args": ["awslabs.core-mcp-server@latest"],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR",       "MCP_SETTINGS_PATH": "~/.aws/amazonq/mcp.json"     },     "disabled": false,     "autoApprove": []   },   "awslabs.aws-diagram-mcp-server": {     "command": "uvx",     "args": ["awslabs.aws-diagram-mcp-server"],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR"     },     "disabled": false,     "autoApprove": []     },   "awslabs.nova-canvas-mcp-server": {     "command": "uvx",     "args": ["awslabs.nova-canvas-mcp-server@latest"],     "env": {       "AWS_PROFILE": "default",       "AWS_REGION": "us-east-1",       "FASTMCP_LOG_LEVEL": "ERROR"     },     "disabled": false,     "autoApprove": []   },   "awslabs.bedrock-kb-retrieval-mcp-server": {     "command": "uvx",     "args": ["awslabs.bedrock-kb-retrieval-mcp-server@latest"],     "env": {       "AWS_PROFILE": "default",       "AWS_REGION": "us-east-1",       "FASTMCP_LOG_LEVEL": "ERROR"     },     "disabled": false,     "autoApprove": []   },   "awslabs.aws-pricing-mcp-server": {     "command": "uvx",     "args": ["awslabs.aws-pricing-mcp-server@latest"],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR",       "AWS_PROFILE": "default",       "AWS_REGION": "us-east-1"     },     "disabled": false,     "autoApprove": []   } } }   Amazon Qに渡したプロンプト イベント参加した後、「AIエージェントを利用する時にはVibe-Codingと仕様駆動開発を上手く組み合わせないといけない」と感じましたので、自分はVibe-Coding担当、仕様駆動開発はAmazon Qに担当してもらうことにしました。 Amazon Qには以下のメモ書きレベルのプロンプトを渡しました。 AWS上でゲームを作成したい。 プラットフォームはパソコンのみ。 ブラウザゲーム。 ジャンルは街づくり+コミュニケーション。 視点は見下ろし型の2D。 テーマは機関車とファンタジー。 街の中でユーザーが作成した機関車が走り、ユーザーはその機関車に乗って移動することも出来る。 機関車要素はトーマスを意識してほしいけど著作権は気を付けて。 ファンタジーはファイナルファンタジー9を意識して欲しいけど著作権は気を付けて。 ユーザーのキャラクターは犬。 カスタマー管理のIAMが実行したAPI(つまりcloudtrailイベント履歴)をインプットにして素材が集まるようにしたい。つまり基本的には放置ゲームにしたい。 気が向いた時にアクセスして集まった素材でユーザーが機関車を作ったりカスタマイズしたりできるようにしたい。 コミュニケーション要素はチャット機能を付けてほしいのと掲示板機能をつけてほしい。 掲示板では単純にテキストのやりとりを可能とするが、そのほかの目的として他のユーザーと協力して機関車を作れるようなシステムにしてほしい。 他のユーザーと協力するシステムを作ってほしい。 同時にアクセスする人数は多くても5人。 基本的には放置ゲームであること、および、アクセスする人数が少ないことから、サーバレスでの構成を希望する。 絵柄のイメージは統一してほしい。 必要であればBedrockを利用してほしいけどモデルはNova microにしてほしい。 また、可能であればAmplify Gen2を利用してほしい。 上記をAWS上で構築するための以下資料を作成してほしい。先にフォルダ作成して、資料はそのフォルダにまとめてほしい。 高額なサービスは避けてほしい。 作成してほしい資料 ※資料の作成に必要であればMCPを活用してほしい。(ゲームでmcpを活用してほしいという意味ではありません) ・概要資料(500文字以下) ・企画書(著作権にどのように配慮しているか記載してほしい。想定される月額費用も記載してほしい) ・要件定義書 ・設計書 ・構成図(日本語(ダブルバイト文字)は使わないで) ・ゲーム画面のサンプル(最低10枚。コンセプト画像ではなくて、実際のゲーム画面)   Amazon Qの動きを確認 Amazon Qの動きを抜粋して見ていきます。 まず、以下ではMCPは使ってくれませんでした。 awslabs-aws-documentation-mcp-serverまたはawslabs.bedrock-kb-retrieval-mcp-serverを使用するかと思っていましたが、これくらいの内容であれば確認不要なのですね。 > ## 1. 概要資料の作成 🛠️ Using tool: fs_write (trusted) > ## 2. 企画書の作成 🛠️ Using tool: fs_write (trusted) > ## 3. 要件定義書の作成 🛠️ Using tool: fs_write (trusted) > ## 4. 設計書の作成 設計書が大きくなりすぎるため、複数のファイルに分割して作成します。 🛠️ Using tool: fs_write (trusted)   構成図の作成では awslabs.aws-diagram-mcp-server が使用されました。 > ## 5. 構成図の作成 構成図を作成するために、まず利用可能なアイコンを確認します。 🛠️ Using tool: list_icons (trusted) from mcp server awslabs.aws-diagram-mcp-server ⋮ ● Running list_icons with the param: 🛠️ Using tool: generate_diagram (trusted) from mcp server awslabs.aws-diagram-mcp-server ⋮ ● Running generate_diagram with the param:   構成図の作成では awslabs.aws-diagram-mcp-server が使用されました。 非常に曖昧なプロンプトを渡したにも関わらず、Amazon Qはそのまま横流しすることなく整理した上でNovaにプロンプトを渡してくれました。 > ## 6. ゲーム画面サンプルの生成 ゲーム画面のサンプル画像を生成します。統一された絵柄でファンタジー機関車ゲームの画面を作成します。 🛠️ Using tool: generate_image (trusted) from mcp server awslabs.nova-canvas-mcp-server ⋮ ● Running generate_image with the param: ⋮ "negative_prompt": "people, anatomy, hands, low quality, low resolution, low detail, realistic photo, 3D render", ⋮ "prompt": "fantasy 2D game train creation interface, steampunk locomotive parts selection screen, colorful train components, magical engine parts, passenger cars, cargo cars, dog character mascot, material inventory panel, craft button, game UI style",   コンテキストで会話の履歴を保存するよう依頼していたため、以下のように保存してくれました。 > ## 7. 最終的な会話履歴の保存 🛠️ Using tool: fs_write (trusted) ⋮ ● Path: chat_history.json ⋮ ↳ Purpose: 会話履歴をJSONファイルに保存 Replacing: chat_history.json   10分も掛からずに対応が完了しました。 > ## 対応完了報告 ファンタジー機関車街づくりゲームの開発資料を完全に作成いたしました。 ### 作成した資料一覧 1. 概要資料 (347文字) - ゲームの基本コンセプト 2. 企画書 - 著作権配慮事項と月額費用見積もり($13.20)を含む詳細企画 3. 要件定義書 - 機能要件・非機能要件の詳細定義 4. 設計書 - 基本設計と詳細設計(2ファイルに分割) 5. 構成図 - 3種類のシステム構成図(英語表記) 6. ゲーム画面サンプル - 10枚の実際のゲーム画面イメージ ### 主要な特徴 • **AWS サーバレス構成**:Amplify Gen2をベースとした低コスト運用 • **CloudTrail連携**:API実行履歴による自動素材収集システム • **著作権配慮**:トーマスとFF9の要素を参考にしつつ完全オリジナル • **統一された絵柄**:Bedrock Nova microによる一貫したアートスタイル • **協力システム**:最大5人での協力プレイ対応 ### 想定月額費用 約$13.20(約1,800円)で運用可能な設計となっています。 すべての資料は/■■■■■■■/fantasy-train-city-game/フォルダに整理して保存いたしました。ゲーム開発の次のステップに進む準備が整いました!   成果物 概要資料 注文通り500文字以内でまとめてくれました。 # ファンタジー機関車街づくりゲーム 概要資料 ## ゲーム概要 犬のキャラクターが主人公となり、ファンタジー世界で機関車を中心とした街づくりを行うブラウザゲーム。プレイヤーのAWS CloudTrailイベント履歴を素材収集のトリガーとする放置ゲーム要素を持つ。 ## 主要機能 - 見下ろし型2Dの街づくりシステム - 機関車の作成・カスタマイズ機能 - 機関車での移動システム - リアルタイムチャット機能 - 協力プレイ対応掲示板システム - CloudTrail連携による自動素材収集 ## 技術構成 AWS Amplify Gen2をベースとしたサーバレス構成。Amazon Bedrock Nova microを活用した画像生成により統一された絵柄を実現。同時接続5人以下の小規模運用に最適化。 ## 特徴 著作権に配慮したオリジナルデザインによる機関車とファンタジー要素の融合。放置ゲーム要素により気軽にプレイ可能。協力システムによるコミュニティ形成を促進。   企画書 「3.3 その他の配慮」までの内容を見るとゲームを作るという実感が湧きますね。 こちらから依頼したことは”企画書”と必須事項だけでしたが、スケジュールやリスクまで整理してくれました。 # ファンタジー機関車街づくりゲーム 企画書 ## 1. ゲームコンセプト ### 1.1 基本コンセプト 「犬の冒険者が機関車と共に創る、ファンタジー街づくりゲーム」 ### 1.2 ターゲット - 年齢層:20-40代 - 放置ゲーム愛好者 - 街づくりゲーム愛好者 - AWS利用者(CloudTrail連携機能) ## 2. ゲーム内容 ### 2.1 基本システム - **視点**: 見下ろし型2D - **プラットフォーム**: ブラウザゲーム(PC専用) - **ジャンル**: 街づくり + コミュニケーション + 放置ゲーム ### 2.2 主要機能 1. **街づくりシステム**  - 建物の配置・管理  - 道路・線路の敷設  - 景観の装飾 2. **機関車システム**  - 機関車の設計・作成  - パーツのカスタマイズ  - 機関車での移動・探索 3. **素材収集システム**  - CloudTrail API実行履歴による自動素材獲得  - 放置時間による素材蓄積  - レア素材の確率的獲得 4. **コミュニケーションシステム**  - リアルタイムチャット  - 掲示板での情報交換  - 協力プレイシステム ### 2.3 キャラクター設定 - **主人公**: 犬のキャラクター(種族・色・アクセサリーのカスタマイズ可能) - **NPCキャラクター**: ファンタジー世界の住民(エルフ、ドワーフ等) ## 3. 著作権配慮事項 ### 3.1 機関車要素の著作権配慮 **参考作品**: きかんしゃトーマス **配慮内容**: - 顔のある機関車デザインは避け、無機物としての機関車を採用 - カラーリングは独自の配色パターンを使用 - 形状は現実の蒸気機関車をベースとしたオリジナルデザイン - キャラクター性は犬の主人公に集約 ### 3.2 ファンタジー要素の著作権配慮 **参考作品**: ファイナルファンタジーIX **配慮内容**: - 世界観は中世ヨーロッパ風ファンタジーの一般的要素を採用 - 建築様式は実在する中世建築をベース - 魔法・モンスター等は一般的なファンタジー要素に留める - 独自のストーリー・設定を構築 ### 3.3 その他の配慮 - 使用する音楽・効果音は著作権フリー素材またはオリジナル作成 - フォント・UI要素もオープンソースまたはオリジナル - 第三者の商標・意匠権に抵触しないよう注意 ## 4. 技術仕様 ### 4.1 基盤技術 - **フロントエンド**: React + TypeScript - **バックエンド**: AWS Amplify Gen2 - **データベース**: Amazon DynamoDB - **認証**: Amazon Cognito - **リアルタイム通信**: AWS AppSync (GraphQL Subscriptions) ### 4.2 AWS サービス構成 - **Amplify Gen2**: アプリケーション基盤 - **DynamoDB**: ゲームデータ保存 - **Lambda**: ビジネスロジック処理 - **AppSync**: GraphQL API・リアルタイム通信 - **Cognito**: ユーザー認証・管理 - **CloudTrail**: API履歴取得 - **Bedrock Nova micro**: 画像生成 - **S3**: 静的ファイル保存 - **CloudFront**: CDN ## 5. 想定月額費用 ### 5.1 前提条件 - 同時接続ユーザー数: 最大5人 - 月間アクティブユーザー: 10-20人 - 1日あたりAPI呼び出し: 1,000回程度 - ストレージ使用量: 10GB程度 ### 5.2 サービス別費用見積もり(東京リージョン) | サービス | 使用量 | 月額費用(USD) | |---------|--------|----------------| | Amplify Hosting | 10GB転送 | $1.50 | | DynamoDB | 1GB保存 + 100万RCU/WCU | $2.50 | | Lambda | 10万実行 + 1GB-秒 | $0.20 | | AppSync | 100万リクエスト | $4.00 | | Cognito | 20MAU | $0.00 (無料枠) | | CloudTrail | 基本ログ | $0.00 (無料枠) | | Bedrock Nova micro | 1,000画像生成 | $3.00 | | S3 | 10GB保存 + 転送 | $1.00 | | CloudFront | 10GB転送 | $1.00 | **合計想定月額費用: 約 $13.20 (約1,800円)** ### 5.3 コスト最適化施策 - DynamoDBオンデマンド課金でスケール調整 - Lambda関数の実行時間最適化 - S3 Intelligent-Tieringによる自動コスト最適化 - CloudFrontキャッシュ設定による転送量削減 ## 6. 開発スケジュール ### Phase 1: 基盤構築(4週間) - Amplify Gen2環境構築 - 認証システム実装 - 基本UI/UX実装 ### Phase 2: コア機能実装(6週間) - 街づくりシステム - 機関車システム - CloudTrail連携 ### Phase 3: コミュニケーション機能(4週間) - チャット機能 - 掲示板機能 - 協力システム ### Phase 4: 画像生成・最適化(2週間) - Bedrock Nova micro連携 - 画像統一・最適化 - パフォーマンス調整 **総開発期間: 16週間** ## 7. リスク・課題 ### 7.1 技術的リスク - CloudTrail API制限による素材収集頻度の制約 - リアルタイム通信の安定性 - ブラウザ間の互換性 ### 7.2 運用リスク - 少人数での運用体制 - セキュリティ対策 - データバックアップ・復旧 ### 7.3 対策 - 段階的リリースによるリスク軽減 - 自動化による運用負荷軽減 - AWS Well-Architected Frameworkに基づく設計 要件定義書 長いので抜粋します。 実業務を想定した場合だと、丸ごとそのまま使えるということは無いと思いますが、たたき台としては使えますね。 数分で作ってくれるというのが良いですね。 # ファンタジー機関車街づくりゲーム 要件定義書 ## 1. システム概要 ### 1.1 システム名 ファンタジー機関車街づくりゲーム ### 1.2 システムの目的 AWS CloudTrail連携による放置ゲーム要素を持つ、機関車をテーマとしたファンタジー街づくりブラウザゲームの提供 ### 1.3 システムの範囲 - ゲームクライアント(ブラウザ) - ゲームサーバー(AWS サーバレス構成) - 管理機能 ## 2. 機能要件 ### 2.1 ユーザー管理機能 #### 2.1.1 ユーザー登録・認証 - **機能名**: ユーザー登録 - **概要**: メールアドレスとパスワードによるユーザー登録 - **詳細**: - メールアドレス認証必須 - パスワード強度チェック - 利用規約同意確認 #### 2.1.2 ログイン・ログアウト #### 2.1.3 プロフィール管理 ### 2.2 街づくり機能 #### 2.2.1 マップ表示 #### 2.2.2 建物配置 #### 2.2.3 道路・線路敷設 ### 2.3 機関車システム #### 2.3.1 機関車作成 #### 2.3.2 機関車運行 #### 2.3.3 機関車搭乗 ### 2.4 素材収集システム #### 2.4.1 CloudTrail連携 - **機能名**: CloudTrail連携 - **概要**: AWS CloudTrailイベント履歴による素材獲得 - **詳細**: - API実行履歴の取得 - イベント種別による素材種類決定 - 獲得量の計算 #### 2.4.2 放置収集 #### 2.4.3 素材管理 ### 2.5 コミュニケーション機能 #### 2.5.1 チャット機能 #### 2.5.2 掲示板機能 #### 2.5.3 協力システム ### 2.6 画像生成機能 #### 2.6.1 統一画像生成 - **機能名**: Bedrock画像生成 - **概要**: Amazon Bedrock Nova microによる統一された画像生成 - **詳細**: - 建物・機関車の画像生成 - 統一されたアートスタイル - 動的画像生成 ## 3. 非機能要件 ### 3.1 性能要件 #### 3.1.1 応答時間 - 画面遷移: 3秒以内 - API応答: 1秒以内 - チャット送信: 0.5秒以内 #### 3.1.2 同時接続数 - 最大同時接続: 5ユーザー - 想定アクティブユーザー: 10-20人/月 #### 3.1.3 可用性 - システム稼働率: 99.0%以上 - 計画メンテナンス: 月1回、2時間以内 ### 3.2 セキュリティ要件 #### 3.2.1 認証・認可 - JWT トークンベース認証 - セッション管理 - API アクセス制御 #### 3.2.2 データ保護 - 通信暗号化(HTTPS/WSS) - 個人情報の適切な管理 - CloudTrail データの安全な取り扱い #### 3.2.3 入力検証 - XSS対策 - SQLインジェクション対策 - CSRF対策 ### 3.3 運用要件 #### 3.3.1 監視 - システム稼働監視 - エラーログ監視 - パフォーマンス監視 #### 3.3.2 バックアップ - データベース日次バックアップ - 設定ファイルのバージョン管理 - 災害復旧手順 #### 3.3.3 スケーラビリティ - サーバレス構成による自動スケーリング - データベースの読み取り性能最適化 ### 3.4 互換性要件 #### 3.4.1 ブラウザ対応 - Chrome 90以降 - Firefox 88以降 - Safari 14以降 - Edge 90以降 #### 3.4.2 画面解像度 ## 4. 制約事項 ### 4.1 技術制約 - AWS サーバレス構成必須 - Amplify Gen2 使用必須 - Bedrock Nova micro 使用必須 ### 4.2 コスト制約 ### 4.3 法的制約 ## 5. 前提条件・仮定 ### 5.1 前提条件 - ユーザーはAWSアカウントを保有 - CloudTrail が有効化されている - インターネット接続環境 ### 5.2 仮定 - 同時接続数は5人以下で運用 - ユーザーは基本的なブラウザ操作が可能 - 日本語環境での利用 ## 6. 用語定義 設計書_基本設計 こちらも長いので抜粋。 # ファンタジー機関車街づくりゲーム 設計書(基本設計) ## 1. システム全体設計 ### 1.1 システム構成概要 [ブラウザクライアント]   ↓ HTTPS/WSS [CloudFront]   ↓ [Amplify Gen2 Hosting]   ↓ GraphQL/REST [AppSync API Gateway]   ↓ [Lambda Functions] ← [DynamoDB] ← [S3]   ↓ [Bedrock Nova micro] / [CloudTrail] ### 1.2 アーキテクチャパターン - **フロントエンド**: SPA (Single Page Application) - **バックエンド**: サーバレス + マイクロサービス - **データベース**: NoSQL (DynamoDB) - **通信**: GraphQL + WebSocket ### 1.3 技術スタック #### フロントエンド - **フレームワーク**: React 18 + TypeScript - **状態管理**: Redux Toolkit - **UI ライブラリ**: Material-UI - **2D描画**: Konva.js / Fabric.js - **リアルタイム通信**: GraphQL Subscriptions #### バックエンド - **API**: AWS AppSync (GraphQL) - **認証**: Amazon Cognito - **データベース**: Amazon DynamoDB - **ファイルストレージ**: Amazon S3 - **画像生成**: Amazon Bedrock Nova micro - **ログ取得**: AWS CloudTrail ## 2. データベース設計 ### 2.1 DynamoDB テーブル設計 #### 2.1.1 Users テーブル #### 2.1.2 Cities テーブル #### 2.1.3 Trains テーブル #### 2.1.4 Materials テーブル #### 2.1.5 ChatMessages テーブル #### 2.1.6 ForumPosts テーブル #### 2.1.7 Collaborations テーブル ### 2.2 GSI (Global Secondary Index) 設計 #### 2.2.1 ChatMessages-ByUser-Index #### 2.2.2 ForumPosts-ByCategory-Index ## 3. API設計 ### 3.1 GraphQL スキーマ設計 #### 3.1.1 User関連 #### 3.1.2 City関連 #### 3.1.3 Train関連 ### 3.2 Mutation設計 #### 3.2.1 ユーザー管理 #### 3.2.2 街づくり #### 3.2.3 機関車 ### 3.3 Subscription設計 #### 3.3.1 リアルタイム通信 ## 4. Lambda関数設計 ### 4.1 関数一覧 #### 4.1.1 ユーザー管理関数 #### 4.1.2 CloudTrail連携関数 #### 4.1.3 素材管理関数 #### 4.1.4 画像生成関数 #### 4.1.5 ゲームロジック関数 ### 4.2 関数詳細設計 #### 4.2.1 CloudTrail連携関数 # 疑似コード #### 4.2.2 画像生成関数 # 疑似コード ## 5. セキュリティ設計 ### 5.1 認証・認可 ### 5.2 データアクセス制御 ### 5.3 入力検証 ### 5.4 通信セキュリティ 設計書_詳細設計 こちらも長いので抜粋。 # ファンタジー機関車街づくりゲーム 設計書(詳細設計) ## 6. フロントエンド設計 ### 6.1 コンポーネント設計 #### 6.1.1 画面構成 App ├── AuthWrapper │   ├── LoginPage │   └── RegisterPage └── GameWrapper   ├── Header #### 6.1.2 状態管理設計 ### 6.2 2D描画エンジン設計 ### 6.3 リアルタイム通信設計 ## 7. バックエンド詳細設計 ### 7.1 AppSync Resolver設計 ### 7.2 DynamoDB アクセスパターン ### 7.3 CloudTrail連携詳細設計 ### 7.4 画像生成詳細設計 ## 8. パフォーマンス最適化設計 ### 8.1 フロントエンド最適化 ### 8.2 バックエンド最適化 ## 9. 監視・ログ設計 ### 9.1 CloudWatch メトリクス ### 9.2 構造化ログ ## 10. エラーハンドリング設計 ### 10.1 フロントエンドエラーハンドリング ### 10.2 バックエンドエラーハンドリング 構成図 システム設計、データフロー、CloudTrail統合フローの3つを作ってくれました。 以下はシステム設計です。 ゲーム画面サンプル どれも良い感じに生成してくれたので、小さくして全部貼り付けます。 なぜか人間がいたり、文字がおかしかったりしますが、依頼したテーマに沿っているかと思います。   感想 良かったことは、手元の環境でAmazon QとMCPを設定できたことと、MCPが機能するところを見れたことです。 特にawslabs.nova-canvas-mcp-serverは面白かったです。成果物が非常分かりやすいので。 要件定義書や設計書については、たたき台を数分で作ってくれること、および自分が気づかないことも項目として挙げてくれることが良かったことです。 反対に良くなかったことは、資料作成の後に実際にゲームをAmazon Qに作ってもらったのですが、ログインはできたものの、想定よりもシンプルすぎたことと、不具合が酷すぎたので、当記事では記載の対象外にしたことです。 Vibe-Codingするにしても自分に知識やスキルがないとAmazon Qを上手くハンドリングできないことを実感しました。 納得のいくゲームができたら別記事でご紹介するようにします。
アバター
本記事は 夏休みクラウド自由研究2025 8/17付の記事です 。 「AWSの最新情報を効率的にキャッチアップしたい…」 「毎日Qiitaをチェックするのは大変だけど、重要なトレンドは見逃したくない…」 エンジニアにとって、最新技術の動向を追い続けることは非常に重要! ですが、忙しい日々の中で効率的に情報収集するのは難しい課題ですよね。 この記事では、そんな悩みを解決するために、 AWSのサービスを組み合わせて、Qiitaに投稿されたAWS関連の最新記事から「おすすめトピックス」を自動で抽出し、毎日メールで通知してくれるシステム を構築する方法を、ステップ・バイ・ステップで解説します! サーバー管理は一切不要で、一度作ればあとは自動であなたのために働いてくれる便利なシステムです。AWSの学習にもぴったりのテーマなので、ぜひ一緒に作ってみましょう! この記事で構築するもの 完成するシステムの全体像は以下の通りです。 毎日決まった時刻に … EventBridge がシステムを起動します。 QiitaからAWS記事を自動収集 … Lambda がQiita APIを叩いて最新記事を取得し、 S3 に保存します。 AIが記事を分析 … S3 への保存をトリガーに別の Lambda が起動し、 Amazon Comprehend が記事の内容を分析して、頻出する技術キーワード(トピックス)を抽出します。 結果をメールで通知 …抽出したトピックスと記事リストをまとめて、 SNS があなたのメールアドレスに送信します。   準備するもの 構築を始める前に、以下の3つをご準備忘れずに! AWSアカウント : 無料利用枠の範囲内でほとんど試せます。 IAMユーザー : セキュリティのため、管理者権限を持つIAMユーザーで作業しましょう。 Qiitaのアクセストークン : Qiita APIを利用するために必要です。 こちら の「個人用アクセストークン」から read_qiita スコープで発行してください。発行したトークンは後で使いますので、メモしておきましょう。 準備はOKですか?それでは、さっそく構築を始めましょう!   Step 1. データの保管庫!S3バケットを作成しよう まずは、取得したQiitaの記事データを保存しておくための場所(ストレージ)を用意します。AWSでは、このようなオブジェクトストレージサービスとしてAmazon S3を利用します。 AWSマネジメントコンソールで「 S3 」を検索し、ダッシュボードを開きます。 「 バケットを作成 」をクリック。 バケット名 に世界で一意な名前を付けます(例: qiita-aws-articles-自分の名前-日付 )。 リージョン は、基本的にすべてのサービスで同じ「アジアパシフィック (東京) ap-northeast-1 」に統一しましょう。 あとはデフォルト設定のまま「 バケットを作成 」すれば完了です。ここまでは簡単ですかね…!   Step 3 通知システムの中核!SNSトピックを作成 次に、分析結果をメールで送信するための通知システムをAmazon SNSで構築していきます。 コンソールで「 SNS 」を検索し、ダッシュボードへ。 左メニューの「 トピック 」から「 トピックの作成 」をクリック。 タイプは「 スタンダード 」、名前は「 QiitaAwsTopicsNotifier 」など分かりやすいものを設定します。 トピック作成後、「 サブスクリプションの作成 」ボタンを押します。 プロトコル で「 Eメール 」を選び、 エンドポイント に通知を受け取りたいメールアドレスを入力します。 「サブスクリプションの作成」をクリックすると、入力したアドレスに確認メールが届きます。メール内の「 Confirm subscription 」リンクをクリックして承認を忘れずに行いましょう。   Step 3. IAMロールを作成 AWSのサービス同士が連携するには、「IAMロール」という権限設定が必要です。今回は2つのLambda関数を作るので、それぞれに必要な権限を持ったロールを2つ作成します。 コンソールで「 IAM 」を検索し、ダッシュボードへ。 左メニューの「 ロール 」から「 ロールを作成 」をクリックします。 信頼されたエンティティタイプ は「AWSのサービス」、 ユースケース は「Lambda」を選択します。 記事取得用ロール ( QiitaArticleGetRole ) 許可ポリシー : AWSLambdaBasicExecutionRole (Lambdaの基本的な実行ログを書き込む権限) AmazonS3FullAccess (S3にファイルを書き込む権限) →(慣れてきたら、特定のバケットにのみ書き込めるように権限を絞ると、よりセキュアになります) トピック抽出用ロール ( QiitaTopicExtractRole ) 許可ポリシー : AWSLambdaBasicExecutionRole AmazonS3FullAccess (S3からファイルを読み込む権限) ComprehendReadOnly (Amazon Comprehendでテキスト分析を行う権限) AmazonSNSFullAccess (SNSで通知を送信する権限) それぞれポリシーを選択したら、分かりやすいロール名を付けて作成してください。 Step 4. 収集用Lambda関数の作成 いよいよメイン機能の実装です。まずはQiitaから記事データを取得するLambda関数を作成します。 コンソールで「 Lambda 」を検索し、「 関数の作成 」をクリック。 以下の設定で関数を作成します。 オプション : 「一から作成」 関数名 : getQiitaArticles ランタイム : 「Python 3.13」以降 実行ロール : 「既存のロールを使用する」を選び、先ほど作成した QiitaArticleGetRole を選択。 関数が作成されたら、「 コードソース 」エディタに以下のPythonコードを貼り付けます。        import os import json import urllib.request import urllib.parse from datetime import datetime import boto3 # 環境変数から設定値を取得 QIITA_API_TOKEN = os.environ['QIITA_API_TOKEN'] S3_BUCKET_NAME = os.environ['S3_BUCKET_NAME'] s3 = boto3.client('s3') def lambda_handler(event, context):   print("Fetching articles from Qiita using urllib...")     headers = {'Authorization': f'Bearer {QIITA_API_TOKEN}'}   # "AWS"タグがついており、LGTMが10以上の記事を50件取得 (取得件数を増やして分析精度を向上)   query_params = {'page': '1', 'per_page': '50', 'query': 'tag:AWS stocks:>10'}     # URLエンコード   encoded_params = urllib.parse.urlencode(query_params)   url = f'https://qiita.com/api/v2/items?{encoded_params}'     req = urllib.request.Request(url, headers=headers, method='GET')     try:       with urllib.request.urlopen(req) as res:           # ステータスコードのチェック           if res.status >= 400:               print(f"Error: Received status code {res.status}")               raise urllib.error.HTTPError(res.url, res.status, res.reason, res.headers, res.fp)                     response_body = res.read().decode('utf-8')           articles = json.loads(response_body)       if not articles:           print("No articles found.")           return {'statusCode': 200, 'body': 'No articles found.'}       today = datetime.utcnow().strftime('%Y-%m-%d')       file_name = f'articles/{today}.json'       s3.put_object(           Bucket=S3_BUCKET_NAME,           Key=file_name,           Body=json.dumps(articles, ensure_ascii=False, indent=2),           ContentType='application/json'       )       print(f"Successfully saved {len(articles)} articles to s3://{S3_BUCKET_NAME}/{file_name}")       return {'statusCode': 200, 'body': f'Successfully saved {len(articles)} articles.'}   except urllib.error.HTTPError as e:       print(f"Error fetching from Qiita API: {e}")       error_content = e.read().decode('utf-8')       print(f"Error details: {error_content}")         raise e 次に、「 設定 」タブ -> 「 環境変数 」で、以下の2つの変数を設定しまておきましょう。 キー : QIITA_API_TOKEN , 値 : (準備したQiitaアクセストークン) キー : S3_BUCKET_NAME , 値 : (Step 1で作成したS3バケット名) 最後に、タイムアウト対策として「 設定 」タブ -> 「 一般設定 」でタイムアウトを 1分 に延長しておきましょう。 忘れずに「 Deploy 」ボタンをクリックして、変更を保存します。   Step 5: AIで分析&通知! トピック抽出用Lambda関数の作成 次に、S3に保存された記事データをAIで分析し、結果をSNSで通知する2つ目のLambda関数を作成します。 先ほどと同様に、新しいLambda関数を作成します。 関数名 : extractTopicsAndNotify ランタイム : 「Python 3.13」以降 実行ロール : QiitaTopicExtractRole を選択 「 コードソース 」に以下のコードを貼り付けます。Amazon Comprehendを呼び出してキーフレーズを抽出し、結果を整形してSNSに送信する処理です。 ※頻出ワードに接続詞等を含まないようにするために少々汚いコードを書いています…。改善しますね…。         import os import boto3 import json import re from collections import Counter # 環境変数から設定値を取得 SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN'] s3 = boto3.client('s3') comprehend = boto3.client('comprehend', region_name='ap-northeast-1') sns = boto3.client('sns') # 除外する一般的な単語(ストップワード)のリスト STOP_WORDS = set([   "これ", "それ", "あれ", "この", "その", "あの", "ここ", "そこ", "あそこ", "こちら",   "ため", "よう", "こと", "もの", "とき", "ところ", "うち", "ほう", "わけ", "はず",   "さん", "くん", "ちゃん", "さま", "たち", "など", "ほか", "どう", "なに", "なぜ",   "いつ", "どこ", "だれ", "どれ", "ほう", "以上", "以下", "未満", "以前", "以後",   "今回", "次回", "本日", "明日", "昨日", "今日", "これら", "すべて", "一部", "全体",   "一つ", "二つ", "三つ", "最初", "最後", "記事", "方法", "手順", "設定", "確認",   "問題", "解決", "方法", "注意", "事項", "内容", "部分", "情報", "利用", "作成",   "実行", "結果", "必要", "場合", "影響", "機能", "処理", "自分", "皆さん", "こんにちは" ]) def clean_text_for_summary(text):   """概要用にテキストからMarkdownや改行、URLなどを除去する"""   text = re.sub(r'https?://\S+', '', text)  # URLを除去   text = re.sub(r'!\[.*?\]\(.*?\)', '', text)  # Markdown画像を除去   text = re.sub(r'#+\s?', '', text)  # 見出しを除去   text = re.sub(r'[`\*_]', '', text)  # Markdown装飾を除去   text = text.replace('\n', ' ').replace('\r', '') # 改行をスペースに置換   return text.strip() def is_valid_topic(phrase):   """トピックとして相応しいか判定する"""   if len(phrase) <= 2 and not re.search(r'^[A-Z0-9]+$', phrase): # 2文字以下は基本除外 (S3などの大文字略語は許可)       return False   if phrase in STOP_WORDS: # ストップワードに含まれていたら除外       return False   if re.search(r'[!-/:-@[-`{-~]', phrase): # 記号が多く含まれるものは除外       return False   if phrase.isdigit(): # 数字のみは除外       return False   return True def lambda_handler(event, context):   bucket = event['Records'][0]['s3']['bucket']['name']   key = event['Records'][0]['s3']['object']['key']   print(f"Processing file: s3://{bucket}/{key}")   response = s3.get_object(Bucket=bucket, Key=key)   articles = json.loads(response['Body'].read().decode('utf-8'))   all_phrases = []   for article in articles:       text_to_analyze = (article['title'] + " " + article['body'])[:4900]       try:           comp_response = comprehend.detect_key_phrases(Text=text_to_analyze, LanguageCode='ja')           phrases = [p['Text'] for p in comp_response['KeyPhrases']]           all_phrases.extend(phrases)       except Exception as e:           print(f"Error analyzing article {article['id']}: {e}")           continue     # トピックをフィルタリングして頻度順に並べる   topic_counts = Counter(p for p in all_phrases if is_valid_topic(p))   hot_topics = [topic for topic, count in topic_counts.most_common(20)] # 上位20件   # おすすめ記事をLGTM数でソートして上位3件選出   sorted_articles = sorted(articles, key=lambda x: x.get('likes_count', 0), reverse=True)   recommended_articles = sorted_articles[:3]   # --- メッセージ作成 ---   subject = "【自動通知】本日のQiita AWSおすすめトピックス"     # 1. ホットトピックス   hot_topics_text = "特に注目されたトピックスはありませんでした。"   if hot_topics:       hot_topics_text = ", ".join(hot_topics)   # 2. おすすめ記事   recommended_articles_text = ""   for i, article in enumerate(recommended_articles):       summary = clean_text_for_summary(article['body'])[:120] + "..."       recommended_articles_text += f"{i+1}. {article['title']}\n"       recommended_articles_text += f"概要: {summary}\n"       recommended_articles_text += f"URL: {article['url']}\n\n"   # 3. 全記事リスト   all_articles_text = "\n\n".join([f"- {a['title']}\n  {a['url']}" for a in sorted_articles])   # メッセージを結合   message = (       "本日注目されたAWS関連のトピックスと、おすすめ記事です。\n\n"       "------------------------------------\n"       "▼ 今日のホットトピックス\n"       f"{hot_topics_text}\n\n"       "------------------------------------\n"       "▼ 本日のおすすめ記事\n\n"       f"{recommended_articles_text}"       "------------------------------------\n"       "▼ 新着記事一覧\n"       f"{all_articles_text}"   )     print("Publishing to SNS topic...")   sns.publish(TopicArn=SNS_TOPIC_ARN, Message=message, Subject=subject)     return {'statusCode': 200, 'body': 'Successfully extracted topics and notified.'} このLambda関数は、S3にファイルが置かれたら自動で動いてほしいので、 トリガーを設定 します。 関数デザイナー画面の「 トリガーを追加 」をクリック。 ソースに「 S3 」を選択。 バケット にStep 1で作成したバケットを指定。 プレフィックス に articles/ と入力し、このフォルダ内でのイベントのみを検知するようにします。 警告を確認するチェックを入れて「 追加 」。 こちらも環境変数を設定します。「 設定 」タブ -> 「 環境変数 」で以下を追加します。 キー : SNS_TOPIC_ARN , 値 : (Step 2で作成したSNSトピックのARN) タイムアウトを 3分 程度に延長し、「 Deploy 」で保存します。   Step 6. EventBridgeで定期実行を設定 最後の仕上げです! getQiitaArticles 関数を毎日自動で実行するためのスケジュールを設定します。 コンソールで「 EventBridge 」を検索し、ダッシュボードへ。 「 ルールを作成 」をクリック。 名前 に「 RunQiitaArticleGetterDaily 」などを入力。 ルールタイプ で「 スケジュール 」を選択。 スケジュールパターン で、実行頻度を設定します。例えば、毎日朝9時(JST)に実行したい場合は、Cron式 0 0 * * ? * を入力します (EventBridgeのスケジュールはUTC基準なので、日本時間の9時はUTCの0時です)。 ターゲット として「 Lambda 関数 」を選び、関数に getQiitaArticles を指定します。 あとはデフォルトのまま進み、「 ルールを作成 」すれば完了です!    Step7. 動作確認 お疲れ様でした!これでシステムは完成です。 getQiitaArticles 関数のテスト機能を使って手動で一度実行してみましょう。成功すれば、S3にファイルが作成され、それをトリガーに extractTopicsAndNotify が動き、数分後にあなたの元へ分析結果のメールが届くはずです! メールが届けば大成功!あとは設定した時刻になれば、毎日自動で情報が届きます。  発展:こんな使い方もできるかも…? このシステムは、さらにカスタマイズすることも可能です。 Slackに通知する : SNSの代わりにLambdaから直接Slack APIを叩くように変更。 トピックをDBに保存 : DynamoDBにトピックを保存し、長期的なトレンドを分析する。 Webページで結果を公開 : S3の静的ウェブサイトホスティング機能を使って、結果をWebページとして公開する。 分析精度を向上 : LGTM数や記事の長さを考慮してトピックのスコア付けを行う。 まとめ 今回は、AWSのサーバーレスサービスを組み合わせ、Qiitaの技術トレンドを自動で収集・分析・通知するシステムを構築しました。 このプロジェクトを通して、以下のAWSサービスの基本的な連携方法を学ぶことができたのではないでしょうか…? サーバーレスアーキテクチャは、このような「ちょっとした自動化」や「データ処理パイプライン」の構築に非常に便利です!ぜひ、これを機にあなただけの便利な自動化システム作りに挑戦してみてください!  
アバター
こんにちは、広野です。 生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。 今回 (4回目) は実装編 その3 React アプリ開発編です。 前回の記事 アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] アーキテクチャ概要編 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (初回) はアーキテクチャ概要編です。 blog.usize-tech.com 2025.07.30   バックエンド実装については以下です。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] 実装編1 Amazon Cognito 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (2回目) は Amazon Cognito 実装編です。 blog.usize-tech.com 2025.08.15 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] 実装編2 API作成 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (3回目) は API 作成編です。 blog.usize-tech.com 2025.08.15   今回の説明範囲 本記事では、以下の UI コード (React) を説明します。前回記事の Amazon Bedrock 問い合わせ用 API ができていることが前提となります。   サマリー 重要なポイントは、AWS AppSync Events API をサブスクライブすることです。それによりリアルタイムに Amazon Bedrock からのストリームレスポンスを画面に表示できます。 AWS の公式ドキュメントでは、以下にリファレンスがあります。基本的にはこれに従っています。 Connect to AWS AppSync Events - React - AWS Amplify Gen 2 Documentation Connect to AWS AppSync Events AWS Amplify Documentation docs.amplify.aws   React コード (抜粋)  App.jsx App.jsx など上位のソースファイルに以下のように Amazon Cognito と AWS AppSync Events リソースと連携するための設定を書きます。すみません、私のコードでは環境変数を使っています。 import { Amplify } from 'aws-amplify'; import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react'; //Cognito, AppSync Events 連携設定 Amplify.configure({ Auth: { Cognito: { userPoolId: import.meta.env.VITE_USERPOOLID, userPoolClientId: import.meta.env.VITE_USERPOOLWEBCLIENTID, identityPoolId: import.meta.env.VITE_IDPOOLID } }, API: { Events: { endpoint: import.meta.env.VITE_APPSYNCEVENTSHTTPENDPOINT, region: import.meta.env.VITE_REGION, defaultAuthMode: 'userPool' }, }, }); チャットボット画面  import { useState, useEffect, useRef } from "react"; import { Container, Grid, Box, Paper, Typography, TextField, Button, Avatar } from "@mui/material"; import SendIcon from '@mui/icons-material/Send'; import { blue, grey } from '@mui/material/colors'; import { v4 as uuidv4 } from "uuid"; import { events } from "aws-amplify/data"; import { inquireBedrockSr, Markdown } from "./Functions.jsx"; import Header from "../Header.jsx"; import Menu from "./Menu.jsx"; const BedrockSr = (props) => { //定数定義 const groups = props.groups; const sub = props.sub; const idtoken = props.idtoken; const imgUrl = import.meta.env.VITE_IMG_URL; //変数定義 const sessionidRef = useRef(); const channelRef = useRef(); //state定義 const [prompt, setPrompt] = useState(""); const [response, setResponse] = useState(""); const [conversation, setConversation] = useState([]); //Bedrockへの問い合わせ送信関数 const putBedrockSr = () => { //前のレスポンスがあれば配列にappendしてクリア if (response) { setConversation((prev) => [ ...prev, { role: "ai", text: response, } ]); setResponse(""); } setConversation((prev) => [ ...prev, { role: "user", text: prompt, } ]); inquireBedrockSr(prompt, sessionidRef.current, idtoken); //プロンプト欄をクリア setPrompt(""); }; //サブスクリプション開始関数 const startSubscription = async () => { const sessionid = sessionidRef.current; if (channelRef.current) await channelRef.current.close(); const channel = await events.connect(`bedrock-stream-response/${sub}/${sessionid}`); channel.subscribe({ next: (data) => { setResponse((prev) => prev + data.event.message); }, error: (err) => console.error("Subscription error:", err), complete: () => { setConversation((prev) => [ ...prev, { role: "ai", text: response, }, ]); setResponse(""); } }); channelRef.current = channel; }; //セッションIDのリセット、サブスクリプション再接続関数 const resetSession = async () => { sessionidRef.current = uuidv4(); //表示中のプロンプト、会話履歴、レスポンスをリセット setPrompt(""); setResponse(""); setConversation([]); await startSubscription(); }; //画面表示時 useEffect(() => { //画面表示時に最上部にスクロール window.scrollTo(0, 0); //Bedrockからのレスポンスサブスクライブ関数実行 sessionidRef.current = uuidv4(); startSubscription(); //アンマウント時にチャンネルを閉じる return () => { if (channelRef.current) channelRef.current.close(); }; }, []); //Chatbot UI 会話部分 const renderMessage = (msg, idx) => ( <Box key={idx} sx={{ display: "flex", justifyContent: msg.role === "user" ? "flex-end" : "flex-start", mb: 1, width: "100%", }} > {msg.role === "ai" && ( <Avatar src={`${imgUrl}/images/ai_chat_icon.svg`} alt="AI" sx={{ mr: 2 }} /> )} <Paper elevation={2} sx={{ p: 2, my: 1, maxWidth: "100%", bgcolor: msg.role === "user" ? blue[100] : grey[100], }} > <Markdown>{msg.text}</Markdown> </Paper> {msg.role === "user" && ( <Avatar src={`${imgUrl}/images/human_chat_icon.svg`} alt="User" sx={{ ml: 2 }} /> )} </Box> ); return ( <> {/* Header */} <Header groups={groups} signOut={props.signOut} /> <Container maxWidth="lg" sx={{mt:2}}> <Grid container spacing={4}> {/* Menu Pane */} <Grid size={{xs:12,md:4}} order={{xs:2,md:1}}> {/* Sidebar */} <Menu /> </Grid> {/* Contents Pane IMPORTANT */} <Grid size={{xs:12,md:8}} order={{xs:1,md:2}} my={2}> <main> <Grid container spacing={2}> {/* Heading */} <Grid size={{xs:12}}> <Typography id="bedrocksrtop" variant="h5" component="h1" mb={3} gutterBottom>Amazon Bedrock Stream Response テスト</Typography> </Grid> <Grid size={{xs:12}}> {/* Chatbot */} <Paper sx={{p:2,mb:2,width:"100%"}}> {/* あいさつ文(固定) */} {renderMessage({ role: "ai", text: "こんにちは。何かお困りですか?" }, -1)} {/* 会話部分 */} {conversation.map((msg, idx) => renderMessage(msg, idx))} {/* 直近のレスポンス */} {response && renderMessage({ role: "ai", text: response }, "stream") } </Paper> {/* 入力エリア */} <Box sx={{display:"flex",gap:1}}> <TextField fullWidth multiline value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Type message here..." sx={{ flexGrow: 1 }} /> <Button variant="contained" size="small" onClick={putBedrockSr} disabled={!prompt} startIcon={<SendIcon />} sx={{ whiteSpace: "nowrap", flexShrink: 0 }}>送信</Button> </Box> {/* クリアボタン */} {(response || conversation.length > 0) && ( <Box sx={{ display: "flex", justifyContent: "flex-end", mt: 2 }}> <Button variant="contained" size="small" onClick={resetSession}>問い合わせをクリアする</Button> </Box> )} </Grid> </Grid> </main> </Grid> </Grid> </Container> </> ); }; export default BedrockSr; 解説 ステートは以下を使用しています。 prompt:ユーザー入力中のテキスト (プロンプト) response:Amazon Bedrockから受信中のストリームレスポンス conversation:会話履歴(ユーザーと AI の両方) 以下は useRef を使用しています。問い合わせセッションが変わるたびにリセットされる値なのですが、リセットの度に再レンダーされないようにするためです。 sessionidRef:チャットセッション ID(UUIDで生成) channelRef:サブスクライブ中の AWS AppSync Events チャンネル サブスクリプションの部分は、新しいメッセージを受信すると setResponse で response ステートを更新し、その都度再レンダーがかかる仕様になっています。 Amazon Bedrock への問い合わせは関数化しており、その中身は以下になっています。単純に axios で Amazon API Gateway REST API に POST しているだけです。 //Bedrock問い合わせ関数 import axios from 'axios'; const inquireBedrockSr = async (prompt, sessionid, idtoken) => { const res = await axios.post( import.meta.env.VITE_RESTAPIENDPOINTBEDROCKSR, { prompt: prompt, sessionid: sessionid }, { headers: { "Authorization": idtoken } } ); return res; }; 今回、基盤モデルとして Amazon Nova micro を使用していますが、この場合レスポンスがマークダウン形式で戻ってきます。それを適切に HTML 化して表現したかったので、Markdown 関数を使用して変換をかけています。 import ReactMarkdown from 'markdown-to-jsx'; import { Typography, Link, Box } from '@mui/material'; //Markdown関数 const Markdown = (props) => { const MarkdownListItem = (props) => { return <Box component="li" sx={{ mt: 1, typography: 'body1' }} {...props} />; }; const options = { overrides: { h1: { component: Typography, props: { gutterBottom: true, variant: 'h4', component: 'h1' } }, h2: { component: Typography, props: { gutterBottom: true, variant: 'h5', component: 'h2' } }, h3: { component: Typography, props: { gutterBottom: true, variant: 'h6', component: 'h2' } }, h4: { component: Typography, props: { gutterBottom: true, variant: 'subtitle1', paragraph: true } }, p: { component: Typography, props: { paragraph: true, className: 'txt' } }, a: { component: Link, props: { rel: "noopener", target: "_blank" } }, li: { component: MarkdownListItem } } }; return <ReactMarkdown options={options} {...props} />; };   まとめ いかがでしたでしょうか。 React コードの解説は難しかったので少なくなってしまいました。実際に動いたコードを見て参考にして頂けたらと思います。 本記事が皆様のお役に立てれば幸いです。
アバター
本記事は 夏休みクラウド自由研究2025 8/16付の記事です 。 はじめに Black Hat USA 2025が2025年8月2日~7日にラスベガスのマンダレイ・ベイ・コンベンションセンターで開催され、私は8月6日~7日の期間で参加してきました。本記事では、Black Hatの説明と講演の概要を共有し、Black Hat USA 2024と比較して、今年注目の技術分野を考察したいと思います。 Black Hat USA 2025の Registration Terms & Conditions に則り、本記事ではBlack Hatで発表されたコンテンツを公開することができないため、セッションの詳細やスライドの写真を載せられない旨、ご了承ください。 Black Hat とは Black Hatとは世界中のセキュリティ専門家が集まり、最新技術や研究成果を共有するサイバーセキュリティのカンファレンスです。 開催場所は毎年、アメリカ、アジア、ヨーロッパ、中東&アフリカの4地域で開催され、初心者から上級者まで楽しめるイベントとなっています。今回参加したBlack Hat USAは例年、ラスベガスのマンダレイ・ベイ・コンベンションセンターで開催されるようで、今年も例年通り同じ会場での開催でした。 ラスベガスもBlack Hatで賑わっている様子でタクシーに乗ると、運転手からBlack Hatに参加予定かを聞かれることが数回あったり、ラスベガスの街中やハリー・リード国際空港の中でBlack Hat出店企業が大きな映像広告を流していたりしました。 ハリー・リード国際空港でのSentinelOneの広告   開催されたイベント Black Hatで開催されたイベントは以下の通りです。 私はBlack Hatのメインイベントであるブリーフィングと、スポンサーセッションを中心に講演を聞きました。 トレーニング(8月2日〜5日) 世界中の専門家による実践的な講座が開かれるイベントです。 ブリーフィング(8月6日〜7日) Black Hatの審査を通過した研究発表や最新セキュリティ情報が共有される講演イベントで、これがBlack Hatの メインイベント となります。 サミットデイ(8月4日〜7日) 特定業界や役職に特化したイベントで、その分野のパイオニアの発表を聞くことができます。 CISO Summit、The AI Summit、Financial Services Summit、Innovators & Investors Summitなどが開催されました。 アーセナル(8月6日〜7日) オープンソースツールと製品を紹介するイベントで、デモや実演がメインで参加者はプレゼンターと対話ができます。 ビジネスホール(8月6日〜7日) 企業がブースを開き、自社製品を展示するイベントでベンダーや技術者同士の交流・情報交換ができます。 スポンサーセッション(8月6日〜7日) 企業が研究発表や最新セキュリティ情報を共有したり、自社製品の紹介をする講演イベントです。 ブリーフィングと似ていますが商業的な色合いが強く、発表内容はBlack Hatによって審査されず、スポンサーであれば登壇できる講演となります。   ブリーフィングセッションについて ブリーフィングセッションの内、基調講演の内容を簡単に載せます。 サイバーセキュリティの30年:学んだ教訓と今後の展望 Mikko Hypponen氏の講演で以下内容でした。 特に、エンジニアは努力してもその成果は見えずらいため評価されにくいという話は共感しました。 昔のコンピューターウイルスは遊びで作られたウイルスが主流だったが現在は金銭目的でウイルスが作成されるようになってきた。 最近ではランサムウェアギャングがAIを活用した攻撃事例を確認した。 ユーザがリンクを踏んだことでウイルスなどに感染して被害が出るようなケースはユーザ自身の責任であるとする風潮は良くない。我々エンジニアがそうならないように設計するべきであり、エンジニアに責任がある。 エンジニアはセキュリティインシデントが起きないように努力してもその成果は見えずらく、評価されにくい。   Black Hat USA 2024との比較・考察 比較はブリーフィングセッションをジャンル分けし、ジャンルごとにセッション数を算出し、Black Hat USA 2024と比較したときの各ジャンルのセッション増減数からセキュリティ業界で注目されている技術分野を考察します。なお、ここでのジャンルとはBlack Hatがセッションに割り当てたもので、Black Hat USA 2025、2024の公式サイトで公開されているセッションの一覧から私が数えました。また、1つのセッションが複数ジャンルに割り当てされたセッションもありました。 以下グラフ、表はBlack Hat USA 2025のブリーフィングセッション数が多い順にジャンルを並べました。 セッションの合計数は24年と比べて15セッション減り、全25ジャンルの内15ジャンルでセッション数が減少しました。 その中でもAI, ML & Data Scienceのセッション数が24年と比べて13セッション増え、最もセッション数の多いジャンルとなりました。このジャンルでは、AIモデルやAIエージェントの攻撃・防御技術に関するセッションが多い印象で、AIが普及し、2025年はAI技術そのものの安全性に関して関心が高まっているように思います。 また、Exploit Development & Vulnerability Discoveryのセッション数は24年と比べて6セッション減っています。 このジャンルは24年のBlack Hatで最もセッション数の多いジャンルでしたが、セッション数が減少し、25年は2番目のジャンルとなりました。しかし、未だにセッション数が多いことから、このジャンルが注目されなくなってきているわけではなく、より注目されているAI分野にセッション数が持っていかれただけであり、このジャンルも引き続き注目されていると思います。 ジャンル セッション数(2025年) セッション数(2024年) 前年比 AI, ML & Data Science 27 14 13 Exploit Development & Vulnerability Discovery 17 23 -6 Enterprise Security 16 12 4 Application Security: Offense 13 18 -5 Hardware/Embedded 11 12 -1 Network Security 11 9 2 Keynote/Main Stage 10 7 3 Platform Security 10 8 2 Threat Hunting & Incident Response 10 10 0 Application Security: Defense 9 7 2 Cloud Security 9 11 -2 Malware 9 6 3 Cyber-Physical Systems & IoT 8 7 1 Policy 7 6 1 Cryptography 5 5 0 Defense & Resilience 5 12 -7 Reverse Engineering 5 7 -2 Mobile 4 8 -4 Human Factors 3 7 -4 Privacy 3 4 -1 Leadership(※2025年に新たにジャンル追加) 1 – 1 Community & Career(※2025年ではジャンル無し) – 4 -4 Cyber Insurance(※2025年ではジャンル無し) – 4 -4 Forward Focus(※2025年ではジャンル無し) – 4 -4 Industrial Control Systems(※2025年ではジャンル無し) – 3 -3 合計 193 208 -15 さいごに(宣伝) 当社ではPrisma Cloud利用して、複数クラウド環境の設定状況を自動でチェックし、 設定ミスやコンプライアンス違反、異常行動などのリスクを診断するCSPMソリューションを販売しております。 興味のある方は是非、お気軽にお問い合わせください。 マルチクラウド設定診断サービス with CSPM| SCSK株式会社
アバター
こんにちは、広野です。 生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。 今回 (3回目) は実装編 その2 API 作成編です。 大変恐縮ですが、AWS CloudFormation によるデプロイをしているので YAML テンプレートベースでの説明となります。ご了承ください。 前回の記事 アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] アーキテクチャ概要編 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (初回) はアーキテクチャ概要編です。 blog.usize-tech.com 2025.07.30   今回の説明範囲 アーキテクチャ図中、赤枠の部分を説明いたします。 バックエンド側の根幹となる API 部分の説明になります。主に以下の 3 つのパートに分かれます。 ユーザーからのプロンプト (問い合わせ) を受け付ける Amazon API Gateway REST API Amazon Bedrock にプロンプトを投げて、回答を AWS AppSync Events に返す AWS Lambda 関数 ユーザーに Amazon Bedrock からの回答を細切れに返す AWS AppSync Events API この構成の面白いところは、ユーザーとのやり取りが行きと帰りで異なるところです。 各リソースの説明 若干 アーキテクチャ概要編 の記事と重複する内容がありますが、ご容赦ください。 AWS AppSync まずこちらの説明から始めます。AWS Lambda 関数は AWS AppSync Events API ができていることが前提で動くのと、この部分のセキュリティ設計が肝になるため、これを最初に説明すべきと考えました。 AWS AppSync Events は Pub/Sub 機能を、従来の AWS AppSync GraphQL API で必要だった GraphQL 無しで簡単に作成できるようになりました。その利点を活用すべく、今回は Amazon Bedrock からの細切れの回答 (ストリームレスポンス) を順次アプリに送る目的で、アプリから AWS AppSync Events API をサブスクライブしてもらい、そこに AWS Lambda 関数からストリームレスポンスをパブリッシュする構成にしています。 アプリは複数人が使用しますが、Amazon Bedrock からの回答を受け取れるのは、問い合わせをしたユーザーのみです。いわゆる Pub/Sub は 1対1、1対多、多対多のメッセージ配信ができるわけですが、当然ここでは 1対1 の構成にします。そのセキュリティを、「チャンネル」という概念を使用して設計します。 アーキテクチャ図に書いていますが、今回の構成では以下のチャンネル名にしています。 bedrock-stream-response/<CognitoユーザーID>/<セッションID> チャンネルはスラッシュ (/) 区切りの階層構造を持つことができ、階層構造を使用したセキュリティ設計ができます。仕様上、第一階層は固定文字列になります。この固定名は名前空間と呼ばれます。あらかじめ AWS AppSync Events API で設定をしておきます。それだけしておけば、パブリッシャー、サブスクライバー側で共通認識した階層構造で Pub/Sub することで、メッセージの受け渡しをすることができます。 今回は、自分の問い合わせの回答を自分だけが受信したいので、Amazon Cognito のユーザー ID を第二階層に指定しています。かつ、1件1件の問い合わせごとに生成した一意の ID、セッション ID を第三階層にしています。それにより、そのユーザーのその問い合わせだけを受信するチャンネルをつくることができます。 各 ID の生成、授受の流れを図にすると以下のようになります。 セッション ID は同じ一連の問い合わせかどうかを識別するための ID なので、セキュリティ上の意味は持ちません。 Cognito ユーザー ID はなりすましを防ぐために重要です。まず、ユーザーがアプリにログインした際に自分の Cognito ユーザー ID を取得します。それをバックエンド (Amazon API Gateway, AWS AppSync) に安全に送信する必要があります。 Amazon API Gateway には Cognito ユーザー ID をパラメータとして渡すのではなく、それぞれの Cognito 認証に使用した証明書、つまり検証済みの証明書から取得することで、取得した Cognito ユーザー ID が改ざんされていないことを担保できます。 AWS AppSync 側では、サブスクライブ開始時に AppSync 側で取得した証明書チェック済み Cognito ユーザー ID が、サブスクライバー (アプリ) がサブスクライブしようとしているチャンネル内に書かれている Amazon Cognito ユーザー ID と一致するかチェックし、サブスクライブ可否を判断しています。この部分は、以下の AWS 公式ドキュメントにある onSubscribe handler という機能を使用して実装しています。ご丁寧に Cognito ユーザー ID によるサブスクライブ拒否のサンプルコードが紹介されているので助かりました。 Process real-time events with AWS AppSync event handlers - AWS AppSync Events Learn how to use event handlers in AWS AppSync to process real-time events, filter and transform data, and manage subscr... docs.aws.amazon.com 少々情報です。以下のようにイベントハンドラー欄にコードを書いて onSubscribe handler を設定しているのですが、よく見ると「動作」欄が「無効」になっています。検証したところ、この状態でもイベントハンドラーは機能していましたので気にするのをやめました。画面のバグだと思われます。入れ違いで修正されていたらすみません。   話を戻します。 Amazon Cognito ユーザー ID もセッション ID も UUID を使用しており、容易に推測はしづらいようになっていますが、セキュリティ対策をしていないとなりすましができる状況にはあるので、このような Cognito ユーザー ID 授受設計をしています。 AWS Lambda 関数が AWS AppSync Events API にパブリッシュするときのセキュリティはシンプルな IAM ロールベースの認証にしています。というのは、この Lambda 関数は不特定多数のユーザーが共用するので、パブリッシュ先のチャンネルは第一階層を除いて変動するためです。そのため、チャンネル名の第一階層が一致すればパブリッシュを許可する設計にしています。 AWS Lambda 関数 AWS Lambda 関数は、Amazon API Gateway REST API, Amazon Bedrock, AWS AppSync Events API を仲介する役割になります。 Amazon Bedrock への問い合わせはストリームレスポンス対応の converse_stream API を使用します。ストリームレスポンスを適切に AWS AppSync Events API に流す部分が肝になります。この部分を中心に、コードベースでインラインで説明をします。 import json import boto3 # AppSync Events API への接続には、requests モジュールを使用します。 import requests from requests_aws_sign import AWSV4Sign bedrock = boto3.client('bedrock-runtime') session = boto3.session.Session() # 今回は Amazon Nova micro モデルを使用します。cross region inference 用のモデル ID を指定するので注意が必要です。 model_id = 'apac.amazon.nova-micro-v1:0' # AppSync Events API の HTTPS エンドポイントです。リアルタイムエンドポイントではないので注意。 endpoint = 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/event' headers = {'Content-Type': 'application/json'} def lambda_handler(event, context): try: credentials = session.get_credentials() auth = AWSV4Sign(credentials, 'ap-northeast-1', 'appsync') # API Gateway からのインプットを取得 prompt = event['body']['prompt'] sessionid = event['body']['sessionid'] sub = event['sub'] # Cognito ユーザー ID # Amazon Bedrock への問い合わせフォーマット作成 conversation = [ { "role": "user", "content": [{"text": prompt}] } ] # Amazon Bedrock に問い合わせ stream = bedrock.converse_stream( modelId=model_id, messages=conversation, inferenceConfig={"maxTokens": 10000, "temperature": 0.5, "topP": 0.9} ) # ここで、チャンクと言われる細切れのレスポンス単位で AppSync にパブリッシュしています。 for chunk in stream["stream"]: if "contentBlockDelta" in chunk: text = chunk['contentBlockDelta']['delta']['text'] payload = { "channel": "bedrock-stream-response/" + sub + "/" + sessionid, "events": [ json.dumps({"message": text}) ] } requests.post(endpoint, auth=auth, json=payload, headers=headers).json() except Exception as e: print(str(e)) exit(1) 一連のストリームレスポンスが終了すると、AWS Lambda 関数も自動的に終了します。 AWS Lambda 関数の IAM ロールは後述の AWS CloudFormation テンプレートをご覧ください。そこで定義された権限を、コード内の session.get_credentials() で取得し、証明書を作成、requests モジュールで AWS AppSync Events API に投げています。今時点、AWS AppSync には boto3 で簡単にリクエストを投げられないのが難点です。 requests および requests_aws_sign モジュールは Lambda レイヤーを使用しないと Lambda 関数内で import できないので、あらかじめ作成しておく必要があります。AWS CloudFormation テンプレート内では、Amazon S3 バケットにモジュールの ZIP ファイルを置いてある前提になっています。requests モジュール ZIP の作成方法は以下の記事を参考にしてください。 AWS Lambda (Python 3.12) で使用可能な pandas の Lambda Layer を準備する データ分析や加工でよく使われるライブラリに、pandas があると思います。本記事では、AWS Lambda (Python 3.12) で動作する pandas の Lambda Layer を準備する手順を紹介します。 blog.usize-tech.com 2022.06.07 参考までに、私が実行した requests モジュール作成時のコマンド抜粋を載せておきます。 pip install requests pip install requests_aws_sign cd /home/ec2-user/.pyenv/versions/3.13.1/lib/python3.13/site-packages cp -r certifi /home/ec2-user/environment/python/ cp -r certifi-2024.8.30.dist-info /home/ec2-user/environment/python/ cp -r charset_normalizer /home/ec2-user/environment/python/ cp -r charset_normalizer-3.4.0.dist-info /home/ec2-user/environment/python/ cp -r dateutil /home/ec2-user/environment/python/ cp -r idna /home/ec2-user/environment/python/ cp -r idna-3.10.dist-info /home/ec2-user/environment/python/ cp -r jmespath /home/ec2-user/environment/python/ cp -r jmespath-1.0.1.dist-info /home/ec2-user/environment/python/ cp -r python_dateutil-2.9.0.post0.dist-info /home/ec2-user/environment/python/ cp -r requests /home/ec2-user/environment/python/ cp -r requests-2.32.3.dist-info /home/ec2-user/environment/python/ cp -r requests_aws_sign /home/ec2-user/environment/python/ cp -r requests_aws_sign-0.1.6.dist-info /home/ec2-user/environment/python/ cp -r s3transfer /home/ec2-user/environment/python/ cp -r s3transfer-0.10.4.dist-info /home/ec2-user/environment/python/ cp -r six.py /home/ec2-user/environment/python/ cp -r six-1.17.0.dist-info /home/ec2-user/environment/python/ cp -r urllib3 /home/ec2-user/environment/python/ cp -r urllib3-2.2.3.dist-info /home/ec2-user/environment/python/ cd /home/ec2-user/environment zip -r requests2323.zip python aws s3 cp ./requests2323.zip s3://xxxx-xxxx-sdk-ap-northeast-1/sdk/Python3.13/ Amazon API Gateway REST API さて、本記事最後のトピック、Amazon API Gateway です。この REST API は少々トリッキーで、以下の AWS 公式ドキュメントで紹介されているように、AWS Lamba 関数を非同期呼び出しします。 バックエンド Lambda 関数の非同期呼び出しをセットアップする - Amazon API Gateway Lambda 統合における Lambda 非同期呼び出しについて説明します。 docs.aws.amazon.com   非同期呼出することにより、AWS Lambda 関数を呼び出した後 API Gateway はその応答を待つことなく処理を終了します。API Gateway の呼び出し元であるアプリには、非同期実行をした旨のステータスコード 202 を返すようにしています。 今回の設計では Amazon Bedrock からのレスポンスは AWS AppSync Events 経由でアプリに返すので、Amazon API Gateway が AWS Lambda 関数からの戻りを待つ必要がないためです。また、Amazon Bedrock からのレスポンスが長くなっても Amazon API Gateway のタイムアウトに制限されることがなくなるという利点があります。 気を付けるべきところは、統合リクエストで以下を設定することです。 Lambda プロキシ統合を False にすること ヘッダーに X-Amz-Invocation-Type を登録すること さらに、HTTP ヘッダーの欄に X-Amz-Invocation-Type に ‘Event’ (※ ‘ も入れること!) を登録します。 また、入力パススルーを不可にしているので、マッピングテンプレートを明記する必要があります。今回は body に input すべてを入れて、追加で sub という項目に Amazon Cognito 認証済みの Cognito ユーザー ID を取得して入れるようにしています。これが AWS Lambda 関数からの AWS AppSync Events API パブリッシュ時に使用されます。   AWS CloudFormation テンプレート ここまでの一連の構成を構築するテンプレート例です。細かい設定はこちらをご覧いただけたらと思います。 AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates an API Gateway REST API, an AppSync Events API, a Lambda function, and relevant IAM roles. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 DomainName: Type: String Description: Domain name for URL. xxxxx.xxx (e.g. example.com) Default: example.com MaxLength: 40 MinLength: 5 SubDomainName: Type: String Description: Sub domain name for URL. (e.g. example-prod or example-dev) Default: example-dev MaxLength: 20 MinLength: 1 ModelId: Type: String Description: The Gen AI foundation model ID. (e.g. apac.amazon.nova-micro-v1:0) Default: apac.amazon.nova-micro-v1:0 MaxLength: 100 MinLength: 1 S3BucketNameSdk: Type: String Description: S3 bucket name in which you uploaded sdks for Lambda Layers. (e.g. example-dev-materials-999999999999-ap-northeast-1) Default: example-dev-materials-999999999999-ap-northeast-1 MaxLength: 100 MinLength: 1 S3KeyRequestsSdk: Type: String Description: S3 key of requests.zip. Fill the exact key name if you renamed. (e.g. sdk/Python3.13/requests2323.zip) Default: sdk/Python3.13/requests2323.zip MaxLength: 100 MinLength: 1 Resources: # ------------------------------------------------------------# # AppSync Events # ------------------------------------------------------------# AppSyncApi: Type: AWS::AppSync::Api Properties: Name: !Sub appsync-event-api-${SystemName}-${SubName} EventConfig: AuthProviders: - AuthType: AMAZON_COGNITO_USER_POOLS CognitoConfig: AwsRegion: !Ref AWS::Region UserPoolId: Fn::ImportValue: !Sub CognitoUserPoolId-${SystemName}-${SubName} - AuthType: AWS_IAM ConnectionAuthModes: - AuthType: AMAZON_COGNITO_USER_POOLS - AuthType: AWS_IAM DefaultPublishAuthModes: - AuthType: AWS_IAM DefaultSubscribeAuthModes: - AuthType: AMAZON_COGNITO_USER_POOLS LogConfig: LogLevel: ALL CloudWatchLogsRoleArn: !GetAtt AppSyncCloudWatchLogsPushRole.Arn OwnerContact: !Sub ${SystemName}-${SubName} Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} AppSyncChannelNamespaceBedrockSR: Type: AWS::AppSync::ChannelNamespace Properties: Name: bedrock-stream-response ApiId: !GetAtt AppSyncApi.ApiId CodeHandlers: | import { util } from '@aws-appsync/utils'; export function onSubscribe(ctx) { const requested = ctx.info.channel.path; if (!requested.startsWith(`/bedrock-stream-response/${ctx.identity.sub}`)) { util.unauthorized(); } } Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ------------------------------------------------------------# # AppSync CloudWatch Logs Invocation Role (IAM) # ------------------------------------------------------------# AppSyncCloudWatchLogsPushRole: Type: AWS::IAM::Role Properties: RoleName: !Sub AppSyncCloudWatchLogsPushRole-${SystemName}-${SubName} Description: This role allows AppSync to push logs to CloudWatch Logs. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - appsync.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs # ------------------------------------------------------------# # Lambda Bedrock Invocation Role (IAM) # ------------------------------------------------------------# LambdaBedrockInvocationRole: Type: AWS::IAM::Role Properties: RoleName: !Sub LambdaBedrockSRRole-${SystemName}-${SubName} Description: This role allows Lambda functions to invoke Bedrock and AppSync Events API. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess Policies: - PolicyName: !Sub LambdaBedrockSRPolicy-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "bedrock:InvokeModel" - "bedrock:InvokeModelWithResponseStream" Resource: - !Sub "arn:aws:bedrock:*::foundation-model/*" - !Sub "arn:aws:bedrock:*:${AWS::AccountId}:inference-profile/*" - Effect: Allow Action: - "appsync:connect" Resource: - !GetAtt AppSyncApi.ApiArn - Effect: Allow Action: - "appsync:publish" - "appsync:EventPublish" Resource: - !Sub ${AppSyncApi.ApiArn}/channelNamespace/bedrock-stream-response DependsOn: - AppSyncApi # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaBedrockSR: Type: AWS::Lambda::Function Properties: FunctionName: !Sub bedrock-stream-response-${SystemName}-${SubName} Description: !Sub Lambda Function to invoke Bedrock for ${SystemName}-${SubName} Architectures: - x86_64 Runtime: python3.13 Timeout: 180 MemorySize: 128 Role: !GetAtt LambdaBedrockInvocationRole.Arn Handler: index.lambda_handler Layers: - !Ref LambdaLayerRequests2323 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} Code: ZipFile: !Sub | import json import boto3 import requests from requests_aws_sign import AWSV4Sign bedrock = boto3.client('bedrock-runtime') session = boto3.session.Session() model_id = '${ModelId}' endpoint = 'https://${AppSyncApi.Dns.Http}/event' headers = {'Content-Type': 'application/json'} def lambda_handler(event, context): try: credentials = session.get_credentials() auth = AWSV4Sign(credentials, '${AWS::Region}', 'appsync') # API Gateway からのインプットを取得 prompt = event['body']['prompt'] sessionid = event['body']['sessionid'] sub = event['sub'] # Amazon Bedrock への問い合わせフォーマット作成 conversation = [ { "role": "user", "content": [{"text": prompt}] } ] # Amazon Bedrock に問い合わせ stream = bedrock.converse_stream( modelId=model_id, messages=conversation, inferenceConfig={"maxTokens": 10000, "temperature": 0.5, "topP": 0.9} ) for chunk in stream["stream"]: if "contentBlockDelta" in chunk: text = chunk['contentBlockDelta']['delta']['text'] payload = { "channel": "bedrock-stream-response/" + sub + "/" + sessionid, "events": [ json.dumps({"message": text}) ] } requests.post(endpoint, auth=auth, json=payload, headers=headers).json() except Exception as e: print(str(e)) exit(1) DependsOn: - LambdaBedrockInvocationRole - LambdaLayerRequests2323 LambdaBedrockSREventInvokeConfig: Type: AWS::Lambda::EventInvokeConfig Properties: FunctionName: !GetAtt LambdaBedrockSR.Arn Qualifier: $LATEST MaximumRetryAttempts: 0 MaximumEventAgeInSeconds: 300 # ------------------------------------------------------------# # API Gateway REST API # ------------------------------------------------------------# RestApiBedrockSR: Type: AWS::ApiGateway::RestApi Properties: Name: !Sub bedrock-stream-response-${SystemName}-${SubName} Description: !Sub REST API to call Lambda bedrock-stream-response-${SystemName}-${SubName} EndpointConfiguration: Types: - REGIONAL IpAddressType: dualstack Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} RestApiDeploymentBedrockSR: Type: AWS::ApiGateway::Deployment Properties: RestApiId: !Ref RestApiBedrockSR DependsOn: - RestApiMethodBedrockSRPost - RestApiMethodBedrockSROptions RestApiStageBedrockSR: Type: AWS::ApiGateway::Stage Properties: StageName: prod Description: production stage RestApiId: !Ref RestApiBedrockSR DeploymentId: !Ref RestApiDeploymentBedrockSR MethodSettings: - ResourcePath: "/*" HttpMethod: "*" LoggingLevel: INFO DataTraceEnabled : true TracingEnabled: false AccessLogSetting: DestinationArn: !GetAtt LogGroupRestApiBedrockSR.Arn Format: '{"requestId":"$context.requestId","status":"$context.status","sub":"$context.authorizer.claims.sub","email":"$context.authorizer.claims.email","resourcePath":"$context.resourcePath","requestTime":"$context.requestTime","sourceIp":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent","apigatewayError":"$context.error.message","authorizerError":"$context.authorizer.error","integrationError":"$context.integration.error"}' Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} RestApiAuthorizerBedrockSR: Type: AWS::ApiGateway::Authorizer Properties: Name: !Sub restapi-authorizer-bedrocksr-${SystemName}-${SubName} RestApiId: !Ref RestApiBedrockSR Type: COGNITO_USER_POOLS ProviderARNs: - Fn::ImportValue: !Sub CognitoArn-${SystemName}-${SubName} AuthorizerResultTtlInSeconds: 300 IdentitySource: method.request.header.Authorization RestApiResourceBedrockSR: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref RestApiBedrockSR ParentId: !GetAtt RestApiBedrockSR.RootResourceId PathPart: bedrocksr RestApiMethodBedrockSRPost: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref RestApiBedrockSR ResourceId: !Ref RestApiResourceBedrockSR HttpMethod: POST AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref RestApiAuthorizerBedrockSR Integration: Type: AWS IntegrationHttpMethod: POST Credentials: !GetAtt ApigLambdaInvocationRole.Arn Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaBedrockSR.Arn}/invocations" PassthroughBehavior: NEVER RequestTemplates: application/json: | { "body": $input.json('$'), "sub": "$context.authorizer.claims.sub" } RequestParameters: integration.request.header.X-Amz-Invocation-Type: "'Event'" IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'" method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'" method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'" ResponseTemplates: application/json: '' StatusCode: '202' MethodResponses: - StatusCode: '202' ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Origin: true method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true DependsOn: - ApigLambdaInvocationRole RestApiMethodBedrockSROptions: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref RestApiBedrockSR ResourceId: !Ref RestApiResourceBedrockSR HttpMethod: OPTIONS AuthorizationType: NONE Integration: Type: MOCK Credentials: !GetAtt ApigLambdaInvocationRole.Arn IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'" method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'" method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'" ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' # ------------------------------------------------------------# # API Gateway Lambda Invocation Role (IAM) # ------------------------------------------------------------# ApigLambdaInvocationRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ApigLambdaInvocationRole-${SystemName}-${SubName} Description: This role allows API Gateways to invoke Lambda. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaRole - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess # ------------------------------------------------------------# # API Gateway LogGroup (CloudWatch Logs) # ------------------------------------------------------------# LogGroupRestApiBedrockSR: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/apigateway/${RestApiBedrockSR} RetentionInDays: 365 Tags: - Key: Cost Value: !Sub Hirodemy-${SubName} # ------------------------------------------------------------# # Lambda Layer # ------------------------------------------------------------# LambdaLayerRequests2323: Type: AWS::Lambda::LayerVersion Properties: LayerName: !Sub requests2323-${SystemName}-${SubName} Description: Requests 2.32.3 for Python CompatibleRuntimes: - python3.13 Content: S3Bucket: !Ref S3BucketNameSdk S3Key: !Ref S3KeyRequestsSdk LicenseInfo: Apache-2.0 # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # API Gateway APIGatewayEndpointBedrockSR: Value: !Sub https://${RestApiBedrockSR}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${RestApiStageBedrockSR}/bedrocksr Export: Name: !Sub RestApiEndpointBedrockSR-${SystemName}-${SubName} # AppSync AppSyncEventsApiEndpointHttp: Value: !Sub https://${AppSyncApi.Dns.Http}/event Export: Name: !Sub AppSyncEventsEndpointHttp-${SystemName}-${SubName} AppSyncEventsApiEndpointRealtime: Value: !Sub wss://${AppSyncApi.Dns.Realtime} Export: Name: !Sub AppSyncEventsEndpointRealtime-${SystemName}-${SubName} 続編記事 続編記事が出来次第、この章を更新します。   まとめ いかがでしたでしょうか。 私が AWS AppSync Events と Amazon API Gateway REST API の Lambda 関数非同期呼出を使用するのが初めてだったのと、AWS Lambda 関数内で使用している converse_stream の Python 用 API の情報、それと Amazon Cognito 認証を使用した AWS AppSync Events の使用例が世の中になかったので、動く状態になるまでにとーっても苦労しました。w 本記事が皆様のお役に立てれば幸いです。
アバター
こんにちは、広野です。 生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。 今回 (2回目) は実装編 その1 Amazon Cognito 編です。 大変恐縮ですが、AWS CloudFormation によるデプロイをしているので YAML テンプレートベースでの説明となります。ご了承ください。 前回の記事 アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] アーキテクチャ概要編 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (初回) はアーキテクチャ概要編です。 blog.usize-tech.com 2025.07.30   今回の説明範囲 アーキテクチャ図中、赤枠の部分を説明いたします。 セキュリティの根幹となる認証部分の説明になります。別件で公開している Amazon Cognito の説明記事とほぼ変わりませんが、あらためて掲載します。また、他の用途にも同じ構成で転用しやすいので一度覚えると有用です。ほんと万能です。 実装している仕様 図には Amazon Cognito ユーザープールしか表現していませんが、Amazon Cognito ID プールも作成しています。私が作成するアプリでは ID プールを使用した Amazon Cognito ユーザーまたはグループ単位の AWS リソースアクセス制御をすることが高いからです。ただし、本記事の構成では設定はほぼ何もなく、基本的な設定を入れているだけです。(細かい設定を入れる前の枠だけ作った感じ) Amazon Cognito でユーザーがセルフサインアップできるようにしています。その方がユーザー管理しなくて済み、楽だからです。代わりに、誰でも登録できるのは良くないので、メールアドレスのドメイン名 (本記事では scsk.jp) でセルフサインアップ可否を判断する AWS Lambda 関数を入れています。当然、セルフサインアップの過程でメールの存在確認をするようになっています。 Amazon Cognito は管理者用のメール送信をする都合で、Amazon SES を使用しています。そのためのパラメータを入力する必要があります。Amazon SES は以下の参考記事のようにログ取得用の設定セットが作成されていることが前提となっています。 ユーザーは MFA を強制されます。 Amazon Cognito ユーザーグループは ADMIN と BASIC の 2種類を初期作成しており、後から ADMIN 所属でないとできない機能を作成するときに使用します。バックエンドやアプリ側で、Amazon Cognito ユーザーの属性を取得することで権限制御できます。 Amazon SES の送信ログを取得・通知する [AWS CloudFormation 利用] Amazon SES の送信ログを保存・通知する仕組みを AWS CloudFormation で簡単に作れるようにしました。 blog.usize-tech.com 2024.12.20   AWS CloudFormation テンプレート AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates a Cognito user pool, a Cognito ID pool, Lambda functions and relevant IAM roles. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 DomainName: Type: String Description: Domain name for URL. xxxxx.xxx (e.g. example.com) Default: example.com MaxLength: 40 MinLength: 5 SesId: Type: String Description: Amazon SES ID for sending emails. (email addreess or domain) Default: example.com MaxLength: 100 MinLength: 5 SesConfigurationSet: Type: String Description: Amazon SES configuration set for sending emails. Default: ses-logs-xxxxxxxxxxxx-ap-northeast-1 MaxLength: 100 MinLength: 5 CognitoAdminAlias: Type: String Description: The alias name of Cognito Admin email address. (e.g. admin) Default: admin MaxLength: 100 MinLength: 5 CognitoReplyTo: Type: String Description: Cognito Reply-to email address. (e.g. xxx@xxx.xx) Default: xxxxxx@example.com MaxLength: 100 MinLength: 5 CognitoEmailFrom: Type: String Description: Cognito e-mail from address. (e.g. xxx@xxx.xx) Default: no-reply@mail.example.com MaxLength: 100 MinLength: 5 AllowedUserEmailDomains: Description: Domain list to allow user sign up. Each domains must be comma delimited and double quoted. Type: String Default: '"scsk.jp"' Resources: # ------------------------------------------------------------# # Cognito Idp Roles (IAM) # ------------------------------------------------------------# CognitoIdpAuthRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoIdpAuthRole-${SubName} Description: This role allows Cognito authenticated users to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub ${SystemName}-CognitoIdpAuthRolePolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" CognitoIdpUnauthRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoIdpUnauthRole-${SubName} Description: This role allows Cognito unauthenticated users to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": unauthenticated Policies: - PolicyName: !Sub ${SystemName}-CognitoIdpUnauthRolePolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" CognitoGroupAdminRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoGroupAdminRole-${SubName} Description: This role allows Cognito authenticated users that belong to ADMIN group to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub example-CognitoGroupBasicPolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" CognitoGroupBasicRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoGroupBasicRole-${SubName} Description: This role allows Cognito authenticated users that belong to BASIC group to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub ${SystemName}-CognitoIdpUnauthRolePolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" # ------------------------------------------------------------# # Lambda (triggered from Cognito) Role (IAM) # ------------------------------------------------------------# LambdaTriggeredFromCognitoRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-LambdaTriggeredFromCognitoRole-${SubName} Description: This role grants Lambda functions triggered from Cognito basic priviledges. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess - arn:aws:iam::aws:policy/AmazonCognitoPowerUser # ------------------------------------------------------------# # Cognito Lambda Invocation Permission # ------------------------------------------------------------# CognitoLambdaInvocationPermissionPresignup: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt LambdaCognitoPresignup.Arn Action: lambda:InvokeFunction Principal: cognito-idp.amazonaws.com SourceAccount: !Ref AWS::AccountId SourceArn: !GetAtt UserPool.Arn DependsOn: - LambdaCognitoPresignup - UserPool CognitoLambdaInvocationPermissionPostconfirm: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt LambdaCognitoPostconfirm.Arn Action: lambda:InvokeFunction Principal: cognito-idp.amazonaws.com SourceAccount: !Ref AWS::AccountId SourceArn: !GetAtt UserPool.Arn DependsOn: - LambdaCognitoPostconfirm - UserPool # ------------------------------------------------------------# # Cognito user pool # ------------------------------------------------------------# UserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: !Sub ${SystemName}-${SubName} MfaConfiguration: "ON" EnabledMfas: - SOFTWARE_TOKEN_MFA Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: false TemporaryPasswordValidityDays: 180 AccountRecoverySetting: RecoveryMechanisms: - Name: verified_email Priority: 1 AdminCreateUserConfig: AllowAdminCreateUserOnly: false AutoVerifiedAttributes: - email DeviceConfiguration: ChallengeRequiredOnNewDevice: false DeviceOnlyRememberedOnUserPrompt: false EmailConfiguration: ConfigurationSet: !Ref SesConfigurationSet EmailSendingAccount: DEVELOPER From: !Sub "${SystemName}-${SubName} ${CognitoAdminAlias} <${CognitoEmailFrom}>" ReplyToEmailAddress: !Ref CognitoReplyTo SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/${SesId} EmailVerificationMessage: !Sub "${SystemName}-${SubName} Verification code: {####}" EmailVerificationSubject: !Sub "${SystemName}-${SubName} Verification code" LambdaConfig: PreSignUp: !GetAtt LambdaCognitoPresignup.Arn PostConfirmation: !GetAtt LambdaCognitoPostconfirm.Arn UsernameAttributes: - email UsernameConfiguration: CaseSensitive: false UserPoolAddOns: AdvancedSecurityMode: "OFF" UserPoolTags: Cost: !Sub ${SystemName}-${SubName} UserPoolClient: Type: AWS::Cognito::UserPoolClient Properties: UserPoolId: !Ref UserPool ClientName: !Sub ${SystemName}-${SubName}-appclient GenerateSecret: false RefreshTokenValidity: 3 AccessTokenValidity: 6 IdTokenValidity: 6 ExplicitAuthFlows: - ALLOW_USER_SRP_AUTH - ALLOW_REFRESH_TOKEN_AUTH PreventUserExistenceErrors: ENABLED SupportedIdentityProviders: - COGNITO CallbackURLs: - !Sub https://${DomainName}/index.html LogoutURLs: - !Sub https://${DomainName}/index.html DefaultRedirectURI: !Sub https://${DomainName}/index.html AllowedOAuthFlows: - implicit AllowedOAuthFlowsUserPoolClient: true AllowedOAuthScopes: - email - openid # ------------------------------------------------------------# # Cognito user group # ------------------------------------------------------------# UserPoolGroupAdmin: Type: AWS::Cognito::UserPoolGroup Properties: Description: ${SystemName} User Group which allows users able to access management tools. GroupName: ADMIN Precedence: 1 UserPoolId: !Ref UserPool RoleArn: !GetAtt CognitoGroupAdminRole.Arn UserPoolGroupBasic: Type: AWS::Cognito::UserPoolGroup Properties: Description: ${SystemName} User Group which allows users able to access foundation and associate contents. GroupName: BASIC Precedence: 101 UserPoolId: !Ref UserPool RoleArn: !GetAtt CognitoGroupBasicRole.Arn # ------------------------------------------------------------# # Cognito id pool # ------------------------------------------------------------# IdPool: Type: AWS::Cognito::IdentityPool Properties: IdentityPoolName: !Sub ${SystemName}-${SubName} AllowClassicFlow: false AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref UserPoolClient ProviderName: !GetAtt UserPool.ProviderName ServerSideTokenCheck: true IdentityPoolTags: - Key: Cost Value: !Sub ${SystemName}-${SubName} IdPoolRoleAttachment: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref IdPool Roles: authenticated: !GetAtt CognitoIdpAuthRole.Arn unauthenticated: !GetAtt CognitoIdpUnauthRole.Arn RoleMappings: userpool: IdentityProvider: !Sub cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}:${UserPoolClient} Type: Token AmbiguousRoleResolution: AuthenticatedRole # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaCognitoPresignup: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${SystemName}-CognitoPresignup-${SubName} Description: !Sub Lambda Function triggered from Cognito before user self signup to check the user's email domain for ${SystemName}-${SubName} Runtime: python3.13 Timeout: 3 MemorySize: 128 Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn Handler: index.lambda_handler Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} Code: ZipFile: !Sub | import re def lambda_handler(event, context): try: print(event) triggersource = event['triggerSource'] email = event['request']['userAttributes']['email'] print(email) if triggersource == 'PreSignUp_SignUp': print('via self signup') domain = email.split('@')[1] allowedDomains = [ ${AllowedUserEmailDomains} ] if domain in allowedDomains: if re.fullmatch(r'w[0-9]{5}@scsk\.jp',email): print('prohibited email account') return None else: print('allowed domain and email account') return event else: print('prohibited domain') return None else: print('via admin console') return event except Exception as e: print(str(e)) DependsOn: LambdaTriggeredFromCognitoRole LambdaCognitoPostconfirm: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${SystemName}-CognitoPostconfirm-${SubName} Description: !Sub Lambda Function triggered from Cognito after user confirmation to add the user in BASIC group for ${SystemName}-${SubName} Runtime: python3.13 Timeout: 3 MemorySize: 128 Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn Handler: index.lambda_handler Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} Code: ZipFile: | import boto3 client = boto3.client('cognito-idp') def lambda_handler(event, context): USERPOOLID = event['userPoolId'] USERNAME = event['userName'] try: print(event) res = client.admin_add_user_to_group( UserPoolId=USERPOOLID, Username=USERNAME, GroupName='BASIC' ) return event except Exception as e: print(str(e)) DependsOn: LambdaTriggeredFromCognitoRole # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # Cognito CognitoUserPoolID: Value: !Ref UserPool Export: Name: !Sub CognitoUserPoolId-${SystemName}-${SubName} CognitoArn: Value: !GetAtt UserPool.Arn Export: Name: !Sub CognitoArn-${SystemName}-${SubName} CognitoProviderName: Value: !GetAtt UserPool.ProviderName Export: Name: !Sub CognitoProviderName-${SystemName}-${SubName} CognitoProviderURL: Value: !GetAtt UserPool.ProviderURL Export: Name: !Sub CognitoProviderURL-${SystemName}-${SubName} CognitoAppClientID: Value: !Ref UserPoolClient Export: Name: !Sub CognitoAppClientId-${SystemName}-${SubName} CognitoIdPoolID: Value: !Ref IdPool Export: Name: !Sub CognitoIdPoolId-${SystemName}-${SubName}   続編記事 続編記事が出来次第、この章を更新します。   まとめ いかがでしたでしょうか。 まだ本来の目的を達成するための前段階ですが、別件でも役に立つ構成だと思いますので改めて載せました。 本記事が皆様のお役に立てれば幸いです。
アバター
企業ネットワークの規模が拡大するにつれて、ファイアウォール(FW)やネットワークセキュリティの運用管理はますます複雑化しています。Tufinは、こうした課題を解決するための統合ファイアウォール管理・セキュリティポリシー管理ソリューションです。多種多様なファイアウォールやクラウドセキュリティ製品にまたがるポリシーの一元管理・自動化・可視化を実現します。 Tufin社について Tufinは、2005年にイスラエルで設立されたセキュリティ管理ソリューションのリーディングカンパニーです。世界各国にオフィスを構え、グローバル規模で多くの企業や組織へ製品・サービスを提供しています。同社は、ネットワークセキュリティのポリシー管理分野で数々の賞を受賞しているほか、日々進化するIT技術とセキュリティ要件に応じて製品開発を続けています。 Tufinの主な特徴と機能 1. 複数ベンダー対応・マルチプラットフォーム Tufinは、Palo Alto Networks、Cisco、Fortinet、Check Pointなど、国内外の主要なファイアウォールベンダーに対応しています。オンプレミスだけでなく、AWSやAzureなどのクラウド環境に設置されたセキュリティ機器との連携も可能で、ハイブリッド環境でも柔軟に運用管理ができます。 2. ポリシーの自動分析・可視化 Tufinは、各ファイアウォールの設定やルールを自動収集し、わかりやすい画面でポリシーや通信経路を可視化します。これにより、ルールの重複や非効率な設定、不要な許可などの問題点を容易に発見でき、セキュリティリスクの早期解消に役立ちます。 3. 変更管理・自動化ワークフロー 「誰が」「いつ」「どのような変更を行ったか」といった変更履歴を厳密に管理できるほか、ポリシー変更依頼の申請と承認ワークフロー、変更作業の自動化も実現します。これにより、作業ミスの防止や運用効率の向上を支援します。 4. コンプライアンス・監査機能 PCI DSSやISO 27001など、様々な業界コンプライアンスに対応した設定チェックや監査レポート生成機能が充実しています。監査用証跡の自動記録やレポート作成により、監査対応が従来よりも迅速かつ確実になります。 5. セキュリティポリシーの最適化 Tufinは、最小限のアクセス許可で最大限の業務効率を実現するため、既存ルールを分析して最適化案を提示します。「必要な通信のみ許可」「不要なルールの自動抽出」などを通じて、ネットワーク全体のセキュリティレベルの向上に貢献します。 Tufin導入のメリット 運用負担の大幅削減:自動化と可視化により現場担当者の作業負荷が減ります。 セキュリティリスク低減:設定ミスや抜け漏れを防止し、ポリシーの一貫性を保てます。 迅速な監査対応:充実した記録管理とレポート生成により、監査をスムーズに乗り切ることができます。 ITインフラ拡張にも柔軟対応:クラウド・オンプレミス混在環境でも一元管理が可能です。 まとめ Tufinは、企業の複雑化するネットワーク管理・セキュリティ運用の悩みを解決する強力なツールです。複数ベンダー・マルチクラウドに対応した柔軟な運用、セキュリティポリシーの自動化・可視化、監査対応力など、今後ますます重要性が高まるファイアウォール管理業務を効率よく最適化できる点が大きな魅力です。ネットワークセキュリティの強化と運用負荷の軽減、両方を実現したい方は、ぜひTufinの導入を検討してみてください。
アバター