対象読者: AWSをこれから触る人。EC2という名前しか知らなくて構いません。 AWS勉強シリーズの5本目です。全体の地図は索引記事にあります。前半でEC2そのものを説明して、後半は権限に絞ります。サーバーを1台立てるとき、誰が入れて、そのサーバー自身は何を触れて、誰が起動ボタンを押せるのか。EC2で初心者が最初に踏むのは、だいたいこの2つです。
- サーバーを作ったのに、SSHで入れない
- EC2の上のプログラムから、S3のファイルが読めない
見た目はどちらも「繋がらない」ですが、原因は別物です。前者は入口の設定、後者はサーバー自身の権限。ポートを開けても後者は直りません。この記事を読み終わると、この2つを切り分けられて、セキュリティグループに何を書けばいいかも決められるようになります。
EC2とは何か
Amazon Elastic Compute Cloud(EC2)は、AWSの中にコンピューターを1台借りて、自分のプログラムを動かし続けるサービスです。借りた1台をインスタンスと呼びます。
自分のノートPCとの違いは、実物が手元に無いことだけです。OS(LinuxやWindows)が入っていて、SSHでログインでき、好きなソフトを入れて、プログラムを常時動かせます。違うのは、数分で作れて、要らなくなったら消せて、動かした時間だけお金を払う点です。
「Elastic(伸縮する)」の名前どおり、性能はあとから変えられます。最初は一番小さいサイズで作り、足りなければ止めて大きいサイズに変えて起動し直せます。物理サーバーを買うのと違って、選択を間違えても後で直せます。
何に使うか
- Webサイト・Webアプリを動かす: 一番多い用途です。Apache/Nginxを入れてブラウザからアクセスさせる
- バッチ処理・定期実行: 毎晩CSVを集計する、データを変換するといった、常駐や長時間の処理
- 開発用・検証用のサーバー: 「自分のPCでは動くのに本番で動かない」を確かめる場所
- 他のサービスの操作元: EC2の上のプログラムからS3にファイルを置く、SQSからメッセージを取る
裏を返すと、短時間で終わる処理ならLambda、コンテナ前提ならECSやFargateの方が向きます。EC2を選ぶ基準は「OSまで自分で触りたい」「プロセスを常駐させたい」の2つです。
EC2とS3・SQSの違い
このシリーズで触ってきたサービスと比べると、EC2だけ役割が違います。
| 何をするものか | あなたが書いたコードは | |
|---|---|---|
| S3 | ファイルの置き場 | 動かない(置くだけ) |
| SQS / SNS | メッセージの受け渡し | 動かない(渡すだけ) |
| EC2 | コンピューターそのもの | ここで動く |
S3もSQSも「あなたのコードが外から呼ぶ相手」でした。EC2はあなたのコードが住む場所で、そこから他のサービスを呼ぶ側になります。だから権限の話が急に増えます。「誰がこのサーバーに入れるか」「このサーバーは何を触っていいか」という、置き場サービスには無かった問いが出てくるからです。
今AWSの地図のどこにいるか
このシリーズで触ってきたものと、今回の位置です。
IAM(誰が何をしていいか)を1本目にやったのは、この回のためでもあります。EC2の権限は、そのIAMの知識を3方向に使い分ける話になります。
今はこれだけ分かればOK
EC2の記事を読むとVPC、サブネット、AMI、EBS、ENI、ELB…と用語が雪崩れてきます。権限の話をするなら、覚えるのは3つだけです。
| 用語 | 意味 | ひとことで |
|---|---|---|
| セキュリティグループ(SG) | インスタンスに付ける通信の許可リスト | 外から誰が入れるか |
| キーペア | SSHでログインするための鍵。公開鍵がサーバーに入る | 入るときの鍵 |
| IAMロール(インスタンスプロファイル) | インスタンス自身に持たせる権限 | サーバーが何を触れるか |
後回しでいい語: VPC・サブネット(SGの外側のネットワーク設定)、EBS(ディスク)、AMI(OSの型)、ENI(仮想LANカード)。最初の1台を立てるだけなら既定のままで動きます。
権限は3方向ある
EC2の権限が分かりにくいのは、種類の違う3つが同時に出てくるからです。冒頭の絵がその3つで、オフィスの部屋に例えるとこうなります。
- 部屋の前に立つ警備員が、誰を通すかのリストを持っている = セキュリティグループ
- 部屋の鍵を持っている人だけが中に入れる = キーペア
- 部屋の中の人が首から下げる社員証があると、資料室に入れる = IAMロール
- そもそも部屋を借りていいかを決めるのがビルの管理人 = IAMポリシー
この4つを、方向で整理し直します。
- 外 → サーバー: 誰がこのサーバーに接続できるか。担当はセキュリティグループとキーペア
- サーバー → AWS: このサーバーの上のプログラムがS3やSQSを触れるか。担当はIAMロール
- 人 → AWS: そもそも誰がこのインスタンスを起動・停止・削除できるか。担当はIAMポリシー(前回のIAM記事の話)
初心者がハマるのはたいてい1と2の取り違えです。「EC2からS3が読めない」を1(SG)の問題だと思って全ポートを開ける、という事故が起きます。実際は2(ロール)の問題です。
使い方。3つを付けてサーバーを立てる
やることは4つです。部屋を借りて、警備員を立てて、鍵を作って、社員証を渡す。この順番で組み立てます。
上の4コマがそのままコードの順番になります。Python(boto3)で書くと次のとおりです。motoでAWSアカウント無しに実行しています。
import json
import os
import boto3
ec2 = boto3.client("ec2", region_name="ap-northeast-1")
iam = boto3.client("iam")
# 既定VPCを名指しで取る(先頭を取ると別のVPCを掴むことがある)
vpc_id = ec2.describe_vpcs(Filters=[{"Name": "isDefault", "Values": ["true"]}]
)["Vpcs"][0]["VpcId"]
ami_id = ec2.describe_images(Owners=["amazon"])["Images"][0]["ImageId"] # OSの型
# ① 外→サーバー: セキュリティグループ
sg = ec2.create_security_group(GroupName="web-sg", Description="web server",
VpcId=vpc_id)["GroupId"]
ec2.authorize_security_group_ingress(GroupId=sg, IpPermissions=[
# HTTP(80番)は全世界に開ける
{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80,
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]},
# SSH(22番)は自分のIPだけ。/32 は「このIP1つだけ」の意味
{"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22,
"IpRanges": [{"CidrIp": "203.0.113.10/32"}]},
])
# ② 入るときの鍵: キーペア。秘密鍵はこの1回しか受け取れない
kp = ec2.create_key_pair(KeyName="my-key")
with open("my-key.pem", "w") as f:
f.write(kp["KeyMaterial"])
os.chmod("my-key.pem", 0o400) # 権限が緩いとsshが鍵を拒否する
サーバー自身の権限は、IAMロールを作ってインスタンスプロファイルという入れ物に入れてから渡します。EC2にロールを直接は渡せず、この入れ物が要る、というのがEC2特有の一手間です。
# ③ サーバー→AWS: EC2が引き受けられるロールを作る
trust = {"Version": "2012-10-17", "Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"}, # 引き受けていいのはEC2
"Action": "sts:AssumeRole"}]}
iam.create_role(RoleName="ec2-s3-reader",
AssumeRolePolicyDocument=json.dumps(trust))
# 対象は絞る。ListBucketはバケット、GetObjectはその中身と、指定先が違う
pol = {"Version": "2012-10-17", "Statement": [
{"Effect": "Allow", "Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket"},
{"Effect": "Allow", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"}]}
arn = iam.create_policy(PolicyName="S3ReadOnly",
PolicyDocument=json.dumps(pol))["Policy"]["Arn"]
iam.attach_role_policy(RoleName="ec2-s3-reader", PolicyArn=arn)
# ロールをインスタンスプロファイルに入れる(EC2はこの形でしか受け取れない)
iam.create_instance_profile(InstanceProfileName="ec2-s3-reader")
iam.add_role_to_instance_profile(InstanceProfileName="ec2-s3-reader",
RoleName="ec2-s3-reader")
# ④ 3つを渡して起動
r = ec2.run_instances(
ImageId=ami_id, InstanceType="t3.micro", MinCount=1, MaxCount=1,
KeyName="my-key", # 鍵
SecurityGroupIds=[sg], # 入口
IamInstanceProfile={"Name": "ec2-s3-reader"}, # サーバーの権限
MetadataOptions={"HttpTokens": "required"}, # IMDSv2を必須にする(後述)
)
実行結果です。
SG rules: [(80, '0.0.0.0/0'), (22, '203.0.113.10/32')]
keypair: my-key 秘密鍵の先頭: -----BEGIN RSA PRIVATE KEY-
instance: i-1801bd63d60fa4836 running t3.micro
key: my-key | sg: ['web-sg'] | profile: ec2-s3-reader | IMDSv2: required
今あなたが作ったもの
インスタンスが running になりました。手元のPCと比べると、こういう状態です。
| 自分のPC | 今作ったEC2 | |
|---|---|---|
| 電源 | 自分で入れる |
run_instances で入った。止めるまで課金が続く |
| ログイン | パスワード |
my-key.pem(鍵ファイル)。無くすとSSHでは入れない(SSM Session Manager等の別経路はある) |
| 外から入れるか | 基本入れない | SGで許可した80番と22番だけ入れる |
| 他のサービスを触る権限 | 自分のアカウント | インスタンスプロファイル経由。アクセスキーは置いていない |
アクセスキーをサーバーに置かないのが今回いちばん大事な点です。ロールを付けたインスタンスの中では、AWS SDKが自動で一時的な認証情報を取ってきます。コードに鍵を書く必要も、~/.aws/credentials を置く必要もありません。
その一時認証情報の取得口が**インスタンスメタデータ(IMDS)**です。ここで MetadataOptions={"HttpTokens": "required"} を指定しました。これはIMDSv2必須という設定です。v1が使える状態だと、アプリの脆弱性を踏んだときに外部から認証情報を抜かれる経路が残ります。新しいAMIやアカウント設定で既定がv2必須になっていることもありますが、起動時に明示しておくのが確実です。
1か所だけ変えてみる
SSHの許可範囲を 203.0.113.10/32(自分のIPだけ)から 0.0.0.0/0(全世界)に変えると、設定はこう見えます。
ec2.authorize_security_group_ingress(GroupId=sg, IpPermissions=[
{"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22,
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]}])
port 80 from 0.0.0.0/0
port 22 from 203.0.113.10/32
port 22 from 0.0.0.0/0 <- 危険
古い許可は消えず、両方が残ります。 セキュリティグループは「許可の足し算」なので、広い方が実質の設定になります。範囲を狭めたいときは追加ではなく revoke_security_group_ingress で古い行を消してください。22番を全世界に開けると総当たりログインが来ます。気になるならサーバーの /var/log/secure で Failed password の行を数えてみてください。
作った直後のグループの中身も見ておきます。
作った直後のingress(内向き): []
作った直後のegress(外向き): [('-1', ['0.0.0.0/0'])]
内向きは空(誰も入れない)、外向きは全許可です。ここでいう「作ったばかり」は自分で create_security_group したSGの話で、VPCに最初からある default という名前のSGは別物です。あちらは「同じdefault SGを付けたリソース同士は通す」という内向きルールを最初から1本持っています。「作ったばかりのサーバーに繋がらない」は、たいていSGの内向きが空なだけです。逆に外向きは既定で開いているので、サーバーからのapt/yumやAPI呼び出しは最初から通ります(既定VPCのようにインターネットへの出口がある場合の話です)。
踏み台サーバー経由にしたいときは、IPではなくグループを指定できます。
ec2.authorize_security_group_ingress(GroupId=sg, IpPermissions=[
{"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22,
"UserIdGroupPairs": [{"GroupId": bastion_sg}]}]) # 踏み台SGからだけ許可
踏み台のIPが変わっても直す必要がなくなります。
セキュリティグループには結局、何を書くのか
ここが一番迷います。作った直後のセキュリティグループが何を持っているか、実際に見てみます。
sg = ec2.create_security_group(GroupName="web-sg", Description="web", VpcId=vpc)["GroupId"]
d = ec2.describe_security_groups(GroupIds=[sg])["SecurityGroups"][0]
print("入る方向:", d["IpPermissions"])
print("出る方向:", d["IpPermissionsEgress"])
入る方向: []
出る方向: [{'IpProtocol': '-1', 'IpRanges': [{'CidrIp': '0.0.0.0/0'}], ...}]
入る方向は空っぽ、出る方向は最初から全開放です。つまり作りたてのサーバーは「誰も入れないが、中からはどこへでも出られる」状態から始まります。
だから普段書くのは入る方向だけです。出る方向は既定のまま触りません(サーバーがOSの更新やAWSのAPIを呼ぶのに使うので、閉じると逆に困ります)。
戻りの通信の設定は要らない
初心者が最初に混乱するのがここです。「80番を開けたけど、サーバーが返事を送り返す分の許可も要るのでは?」と考えてしまいます。
要りません。セキュリティグループは行きを許可すれば、その戻りは自動で通ります(ステートフル、という性質です)。80番を1行書けば、閲覧者からのリクエストも、サーバーからの応答も両方通ります。
何番を開けるか
用途ごとに決まっています。自分がやりたいことの行だけ開ければ十分です。
| やりたいこと | ポート | ソース(誰から) |
|---|---|---|
| Webサイトを公開する | 80 |
0.0.0.0/0(全世界) |
| WebサイトをHTTPSで公開する | 443 |
0.0.0.0/0(全世界) |
| SSHでログインして作業する | 22 | 自分のIPだけ |
| WindowsにRDPで入る | 3389 | 自分のIPだけ |
| 別のサーバーからMySQLを使う | 3306 | 相手のセキュリティグループ |
| 別のサーバーからPostgreSQLを使う | 5432 | 相手のセキュリティグループ |
太字の行を 0.0.0.0/0 にしないでください。22番を全世界に開けると、数分で世界中からログイン試行が来ます。
ソースの3つの書き方
「誰から」の欄には3種類の書き方があります。
| 書き方 | 意味 | 使う場面 |
|---|---|---|
0.0.0.0/0 |
全世界のどこからでも | 公開するWebサイトの80番・443番 |
203.0.113.10/32 |
このIPアドレス1つだけ | 自分の家や会社からのSSH |
sg-0abc123... |
このセキュリティグループが付いた機器から | Webサーバー → DBサーバー |
3つ目が実務で一番使います。相手のIPアドレスを調べる必要がなく、サーバーを増やしても設定を直さなくて済むからです。
そのまま使える3パターン
① 公開するWebサーバー
ec2.authorize_security_group_ingress(GroupId=web_sg, IpPermissions=[
{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80,
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]},
{"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443,
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]},
{"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22,
"IpRanges": [{"CidrIp": "203.0.113.10/32"}]}, # 自分のIPに置き換える
])
② 自分しか使わない検証用サーバー
ec2.authorize_security_group_ingress(GroupId=dev_sg, IpPermissions=[
{"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22,
"IpRanges": [{"CidrIp": "203.0.113.10/32"}]},
])
22番を自分のIPだけ。これで足ります。
③ Webサーバーとデータベースの2台構成
データベース側には、IPアドレスではなくWebサーバーのセキュリティグループを書きます。
ec2.authorize_security_group_ingress(GroupId=db_sg, IpPermissions=[
{"IpProtocol": "tcp", "FromPort": 3306, "ToPort": 3306,
"UserIdGroupPairs": [{"GroupId": web_sg}]}, # IPではなくSGを指定
])
実行して中身を見ると、IPの欄は空で、参照先のSGだけが入っています。
3306番 ← SG ['sg-ef89609e7367de7c6'] IP指定=[]
こう書いておくと、Webサーバーを2台3台と増やしても、そのサーバーに web_sg を付けるだけでDBに繋がります。DB側の設定は変えません。
セキュリティグループで詰まるところ
実際に叩いて確かめた挙動です。
同じルールをもう一度追加
→ InvalidPermission.Duplicate - The specified rule already exists
「このIPは拒否」と書きたい
→ 書けない。APIに拒否の指定が無く、許可だけを並べる仕組み
IpProtocol="-1" を指定
→ 全プロトコル・全ポートを開ける意味になる。範囲を絞らずに使わない
1台のサーバーに2つのSGを付ける
→ 付いたSG: ['web-sg', 'db-sg'] 複数付けられ、許可は合算される
拒否が書けないのが、前の節で見た「許可の足し算」の理由です。狭めたいときは追加ではなく削除(revoke_security_group_ingress)を使います。
なお、ここで扱ったのはインスタンスに付ける通信の許可だけです。サブネット単位で拒否も書ける仕組み(ネットワークACL)や、そもそもネットワークをどう区切るかは、次回のVPCで扱います。
実際に叩くと止まるところ
権限まわりで返るエラーをmotoで再現しました。エラーコードは本物のEC2と同じです。
無いSGを指定して起動 -> InvalidGroup.NotFound
同名のSGをもう一度作る -> InvalidGroup.Duplicate
同じ許可を二重に追加 -> InvalidPermission.Duplicate
インスタンスを停止 -> stopped
インスタンスを終了 -> terminated
stopped と terminated は別物です。停止は電源を切っただけで、ディスク(EBS)は残り、また起動できます。終了は削除で、戻せません。EBSの料金は停止中もかかり続けるので、「止めたのに請求が来る」はここが理由です。terminateしても、後から追加したボリュームは既定で残ります。
この実測の限界を3つ。1つ目、存在しないキーペア名を指定してもmotoは起動を通しました。本物のEC2は InvalidKeyPair.NotFound で弾きます。2つ目、motoはIAMポリシーの評価をしません(simulate_principal_policy は未実装で例外になりました)。つまり「権限が足りずに起動できない」の再現はローカルではできません。ここは実際のアカウントで確かめるしかない部分です。3つ目として、インスタンスプロファイルを作った直後に起動すると、実アカウントでは反映待ちで Invalid IAM Instance Profile name が返ることがあります。motoでは一瞬で通るので、この待ちもローカルでは体験できません。
人側の権限。誰が起動していいか
3方向の3つ目です。EC2を操作する人(またはCI)には、IAMポリシーでこう与えます。
{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": ["ec2:Describe*"], "Resource": "*"},
{"Effect": "Allow",
"Action": ["ec2:RunInstances", "ec2:StartInstances", "ec2:StopInstances"],
"Resource": "*",
"Condition": {"StringEquals": {"aws:RequestedRegion": "ap-northeast-1"}}},
{"Effect": "Allow", "Action": "iam:PassRole", "Resource":
"arn:aws:iam::123456789012:role/ec2-s3-reader"}
]
}
3つ目の iam:PassRole を忘れると「ロール付きのインスタンスだけ起動できない」という分かりにくい詰まり方をします。インスタンスにロールを渡す行為そのものに権限が要るためです。渡していいロールを限定して書くのが定石で、Resource: "*" にすると管理者権限のロールを付けたインスタンスを誰でも作れてしまいます。
TerminateInstances を外してあるのも意図的です。開発者に起動と停止は許し、削除は許さない、という線引きができます。
料金の考え方
-
稼働時間: インスタンスが
runningの間だけ課金。Linuxは1分以上なら秒単位、Windowsなどは時間単位。停止中は本体の課金はゼロ -
EBS(ディスク): 停止中も課金が続く。terminateで自動的に消えるのはルートボリュームだけで、後から追加したボリュームは既定で残ります。
describe_volumesで状態がavailableのまま溜まっていないか見てください - 通信: インターネットへの送信に課金。受信は無料
- パブリックIPv4アドレス: 2024年2月から、使用中かどうかに関係なく1アドレスあたり0.005 USD/時間(月約3.6 USD)。EC2に自動で付くパブリックIPも、Elastic IPも対象です。使わないElastic IPは解放してください
権限とは直接関係ありませんが、「terminateせず止めただけ」のインスタンスと、取り残されたボリュームは、気付かないうちに積み上がります。
よくある注意点
-
22番を
0.0.0.0/0に開けない。 自分のIP(/32)に限定するか、Session Manager(SSM)を使って22番を閉じたまま入る方法もあります -
秘密鍵は作成時にしか受け取れない。
create_key_pairの戻り値を保存し忘れると、そのインスタンスにSSHでは入れません(SSM Session Managerを有効にしていれば別経路で入れます) - アクセスキーをEC2に置かない。 ロールを使えばキーは不要です。置いたキーは漏えいしたら取り消すまで有効なままです
-
IMDSv2を必須にする。 新規作成時に
HttpTokens: requiredを付けます -
SGの許可は足し算。 狭めたいときは
revoke_security_group_ingressで古い行を消します。追加しただけだと両方残ります -
iam:PassRoleを忘れない。 ロール付き起動だけ失敗する原因の筆頭です
まとめ
EC2は「AWSの中にコンピューターを1台借りて、自分のプログラムを動かし続ける」サービスです。借りた1台がインスタンスで、OSまで自分で触れます。
そして権限は方向で3つに分けると迷わなくなります。外から入る権限がセキュリティグループとキーペア、サーバー自身が持つ権限がIAMロール、操作する人の権限がIAMポリシー。
冒頭の2つの症状は、これで切り分けられます。
- SSHで入れない → 入口の問題。警備員のリスト(SGの22番)か、鍵(キーペア)のどちらか
- S3が読めない → サーバー自身の問題。社員証(IAMロール)が無い。ポートをいくら開けても直らない
次に手を動かすなら、SGの22番を自分のIPに限定した状態から 0.0.0.0/0 を追加して、describe_security_groups で両方の行が残るのを見てください。許可が足し算だと体で分かれば、EC2の設定で事故る場面はかなり減ります。
次回はVPC(ネットワーク)を予定しています。
参考
- Amazon EC2 公式ドキュメント
- EC2のIAMロール(インスタンスプロファイル)
- インスタンスメタデータとIMDSv2
- シリーズ索引: AWSとは何か
- 前回: S3入門 / 権限の基礎: IAM入門



