Search code examples
amazon-ec2aws-cloudformationaws-security-group

Edit EC2 security group from another AWS account


I have 2 accounts on AWS. On the first account I have created a permanent EC2 instance with a "dbSG" Security Group (which only allow connections by specific port and IP).

When I create an instance in second account using CloudFormation template it should:

  • Add this instance IP to "dbSG" security group and allow connection by specific port.
  • Connect to the first instance by this port.

Can I use AssumeRole in UserData when creating the instance in the second account and modify "dbSG" to allow connections from this instance? If yes, how it can be done step by step?


Solution

  • For EC2-Classic

    The CLI help for ec2 authorize-security-group-ingress has this example:

    To add a rule that allows inbound HTTP traffic from a security group in another account

    This example enables inbound traffic on TCP port 80 from a source security group (otheraccountgroup) in a different AWS account (123456789012). If the command succeeds, no output is returned.

    Command:

    aws ec2 authorize-security-group-ingress --group-name MySecurityGroup --protocol tcp --port 80 --source- group otheraccountgroup --group-owner 123456789012

    So, provided that you know the Security Group ID of the "appSG", with credentials from the "db" account:

    aws ec2 authorize-security-group-ingress --group-name
    dbSG --protocol tcp --port 1234 --source-group
    appSG --group-owner XXX-APP-ACCOUNT-ID
    

    Via CloudFormation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-rule.html#cfn-ec2-security-group-rule-sourcesecuritygroupownerid

    Unfortunately, this seems not to work with Instances in a VPC, but only EC2-Classic.


    For EC2-VPC: The user-data way

    In the "db" account, add a Role to your CF template, specifying a Trust Policy that allows such role to be assumed by a specific role in another AWS account:

    (replace XXX-... with your own values)

    'RoleForOtherAccount': {
      'Type': 'AWS::IAM::Role',
      'Properties': {
        'AssumeRolePolicyDocument': {
          'Version': '2012-10-17',
          'Statement': [{
            'Effect': 'Allow',
            'Principal': {
              'AWS': "arn:aws:iam::XXX-OTHER-AWS-ACCOUNT-ID:role/XXX-ROLE-NAME-GIVEN-TO-APP-INSTANCES"
            },
            'Action': ['sts:AssumeRole']
          }]
        },
        'Path': '/',
        'Policies': [{
          'PolicyName': 'manage-sg',
          'PolicyDocument': {
            'Version': '2012-10-17',
            'Statement': [
              {
                'Effect': 'Allow',
                'Action': [
                  'ec2: AuthorizeSecurityGroupIngress'
                ],
                'Resource': '*'
              }
            ]
          }
        }]
      }
    }
    

    Then, on the "app" instance you can add the following User-data script (via CloudFormation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-userdata)

    #!/bin/bash
    
    # get current public IP address via EC2 meta-data
    MY_IP=$(wget -qO- http://instance-data/latest/meta-data/public-ipv4)
    
    # assume the "db" account role and get the credentials
    CREDENTIALS_JSON=$(aws sts assume-role --role-arn XXX-ARN-OF-ROLE-IN-DB-ACCOUNT --role-session-name "AppSessionForSGIngress" --query '{"AWS_ACCESS_KEY_ID": Credentials.AccessKeyId, "AWS_SECRET_ACCESS_KEY": Credentials.SecretAccessKey, "AWS_SESSION_TOKEN": Credentials.SessionToken }')
    
    # here you should find a way to extract AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN from the above $CREDENTIALS_JSON, then export them or pass as values replacing YYY below
    
    # authorize the IP
    aws --region XXX-DB-REGION --access-key-id YYY --secret-access-key YYY --session-token YYY ec2 authorize-security-group-ingress --group-id sg-XXX --protocol tcp --port 1234 --cidr $MY_IP/32
    

    The IAM Role of the "app" instance must allow calls sts:AssumeRole.

    Caveat: if you stop and restart the instance, its public IP will change (unless you've assigned an ElasticIP). Since User-data scripts are executed only during the first Launch, your dbSG wouldn't get updated.


    via Lambda

    You could also use a Lambda function triggered by a CloudTrail or Config, altough this is a bit tricky: Run AWS Lambda code when creating a new AWS EC2 instance

    This way, you can also track calls to StopInstance and StartInstance and update (revoke/authorize) the dbSG rules in a more robust way.


    References: