You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过AWS CloudFormation动态创建密钥对并复制PEM到EC2 Linux实例?

通过AWS CloudFormation动态创建密钥对并关联EC2实例

你完全可以实现这个需求——用CloudFormation动态生成密钥对,同时把对应的PEM私钥文件放到EC2 Linux实例里。不过要注意:CloudFormation原生的AWS::EC2::KeyPair资源不会自动把私钥传递给实例,所以需要借助一些额外的手段来完成私钥的注入。下面我给你两种可行的方案:

方案一:自定义资源生成密钥对+SSM存储私钥注入实例

这个方案通过Lambda自定义资源生成密钥对,把私钥加密存储到SSM参数仓库,再让EC2实例通过用户数据脚本拉取私钥并保存。

完整CloudFormation模板示例

AWSTemplateFormatVersion: '2010-09-09'
Description: 动态创建密钥对并注入EC2实例

Resources:
  # Lambda执行角色:允许创建密钥对、操作SSM、响应CloudFormation
  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: KeyPairAndSSMAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: [ec2:CreateKeyPair, ec2:DeleteKeyPair]
                Resource: "*"
              - Effect: Allow
                Action: [ssm:PutParameter, ssm:DeleteParameter]
                Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/MyDynamicKeyPairPrivateKey"

  # 自定义Lambda:生成密钥对并存储私钥到SSM
  KeyPairCreatorLambda:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.11
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os

          ec2 = boto3.client('ec2')
          ssm = boto3.client('ssm')
          PARAM_NAME = os.environ['PARAM_NAME']

          def lambda_handler(event, context):
              response_data = {}
              try:
                  key_name = event['ResourceProperties']['KeyName']
                  if event['RequestType'] == 'Create':
                      # 创建密钥对并获取私钥
                      key_pair = ec2.create_key_pair(KeyName=key_name)
                      private_key = key_pair['KeyMaterial']
                      # 加密存储私钥到SSM
                      ssm.put_parameter(
                          Name=PARAM_NAME,
                          Value=private_key,
                          Type='SecureString',
                          Overwrite=True
                      )
                      response_data['KeyName'] = key_name
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                  elif event['RequestType'] == 'Delete':
                      # 清理资源:删除密钥对和SSM参数
                      try: ec2.delete_key_pair(KeyName=key_name)
                      except Exception as e: print(f"删密钥对出错: {e}")
                      try: ssm.delete_parameter(Name=PARAM_NAME)
                      except Exception as e: print(f"删SSM参数出错: {e}")
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                  else:
                      # 更新操作不处理(密钥对无法更新)
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              except Exception as e:
                  print(f"整体出错: {e}")
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
      Environment:
        Variables:
          PARAM_NAME: MyDynamicKeyPairPrivateKey

  # 自定义资源:触发Lambda生成密钥对
  DynamicKeyPair:
    Type: Custom::KeyPairCreator
    Properties:
      ServiceToken: !GetAtt KeyPairCreatorLambda.Arn
      KeyName: MyDynamicKeyPair

  # EC2实例角色:允许读取SSM中的私钥参数
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SSMParameterAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: ssm:GetParameter
                Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/MyDynamicKeyPairPrivateKey"

  # 实例配置文件:关联EC2角色
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles: [!Ref EC2InstanceRole]

  # 目标EC2实例:拉取私钥并保存
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0c55b159cbfafe1f0 # 替换为你所在区域的Amazon Linux 2 AMI
      KeyName: !GetAtt DynamicKeyPair.KeyName
      IamInstanceProfile: !GetAtt EC2InstanceProfile.Arn
      UserData:
        Fn::Base64: |
          #!/bin/bash
          # 从SSM拉取解密后的私钥并保存
          aws ssm get-parameter --name MyDynamicKeyPairPrivateKey --with-decryption --query Parameter.Value --output text > /home/ec2-user/my-key.pem
          # 设置PEM文件的安全权限(必须400才能用于SSH)
          chmod 400 /home/ec2-user/my-key.pem
          chown ec2-user:ec2-user /home/ec2-user/my-key.pem

方案说明

  1. 自定义资源Lambda:负责生成密钥对,把私钥加密存储到SSM参数仓库,同时处理栈删除时的资源清理。
  2. EC2实例角色:赋予实例读取SSM参数的权限,确保能安全拉取私钥。
  3. 用户数据脚本:实例启动后自动执行,从SSM获取私钥并保存到指定路径,同时设置正确的文件权限。

方案二:EC2实例本地生成密钥对并注册到AWS

如果不想用Lambda和SSM,也可以让EC2实例自己生成密钥对,再把公钥上传到AWS创建正式的密钥对。这样私钥直接保存在实例本地,流程更简洁。

模板示例

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2实例本地生成密钥对并注册到AWS

Resources:
  # EC2实例角色:允许创建密钥对
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: EC2KeyPairAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: ec2:CreateKeyPair
                Resource: "*"

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles: [!Ref EC2InstanceRole]

  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0c55b159cbfafe1f0 # 替换为你所在区域的Amazon Linux 2 AMI
      IamInstanceProfile: !GetAtt EC2InstanceProfile.Arn
      UserData:
        Fn::Base64: |
          #!/bin/bash
          # 定义密钥对名称(可以改成固定值)
          KEY_NAME="EC2-Generated-Key-$(hostname)"
          # 在实例本地生成RSA密钥对(无密码)
          ssh-keygen -t rsa -b 2048 -f /home/ec2-user/${KEY_NAME}.pem -N ""
          # 设置私钥权限
          chmod 400 /home/ec2-user/${KEY_NAME}.pem
          chown ec2-user:ec2-user /home/ec2-user/${KEY_NAME}.pem
          # 读取公钥内容并上传到AWS创建密钥对
          PUBLIC_KEY=$(cat /home/ec2-user/${KEY_NAME}.pem.pub)
          aws ec2 create-key-pair --key-name ${KEY_NAME} --public-key-material "${PUBLIC_KEY}"

方案说明

  1. 实例初始化脚本:启动后自动生成本地密钥对,然后用AWS CLI把公钥上传到AWS,创建对应的密钥对。
  2. 优势:不需要额外的Lambda资源,流程简单;私钥直接保存在实例的/home/ec2-user/目录下。
  3. 注意:如果实例终止,私钥会丢失,建议生成后备份到加密的S3桶等安全位置。

关键注意事项

  • 私钥安全:不管用哪种方案,私钥都是敏感数据,一定要确保存储和传输过程加密。方案一中的SSM参数用SecureString加密,方案二中要限制实例的访问权限。
  • CloudFormation原生限制:原生的AWS::EC2::KeyPair资源只会在栈创建时返回私钥(控制台或CLI输出),无法直接在模板内传递给实例,所以必须用上述两种方案来实现私钥注入。

内容的提问来源于stack exchange,提问作者Amit Chandra

火山引擎 最新活动