cloudposse / bastion

đź”’Secure Bastion implemented as Docker Container running Alpine Linux with Google Authenticator & DUO MFA support
https://cloudposse.com/accelerate
Apache License 2.0
640 stars 112 forks source link

Example for AWS ECS #24

Closed dmcd closed 1 year ago

dmcd commented 5 years ago

This bastion host is bloody useful and will save our team an enormous effort, thanks!

I had a little trouble working out how to run this on ECS so I'm posting my cloudformation template here for anyone that is looking to do the same:

AWSTemplateFormatVersion: 2010-09-09
Description: Bastion

Parameters:

  KeyPairName:
    Description: >-
      Enter a Public/private key pair. If you do not have one in this region,
      please create it before continuing
    Type: 'AWS::EC2::KeyPair::KeyName'
  NumBastionHosts:
    AllowedValues:
      - '1'
      - '2'
      - '3'
      - '4'
    Default: '1'
    Description: Enter the number of bastion hosts to create
    Type: String
  NetworkStackName:
    Description: Name of an active CloudFormation stack of networking resources
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

Resources:

  BastionAutoScalingGroup:
    Type: 'AWS::AutoScaling::AutoScalingGroup'
    Properties:
      LaunchConfigurationName: !Ref BastionLaunchConfiguration
      VPCZoneIdentifier:
        - !ImportValue
            Fn::Sub: "${NetworkStackName}-PublicSubnet1ID"
        - !ImportValue
            Fn::Sub: "${NetworkStackName}-PublicSubnet2ID"
        - !ImportValue
            Fn::Sub: "${NetworkStackName}-PublicSubnet3ID"
      MinSize: 0
      MaxSize: 3
      Cooldown: '300'
      DesiredCapacity: !Ref NumBastionHosts
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}
          PropagateAtLaunch: 'true'
    CreationPolicy:
      ResourceSignal:
        Count: !Ref NumBastionHosts
        Timeout: PT30M

  BastionECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${AWS::StackName}

  BastionEC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: [ec2.amazonaws.com]
          Action: ['sts:AssumeRole']
      Path: /
      Policies:
      - PolicyName: ecs-service
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action: [
              'ecs:CreateCluster',
              'ecs:DeregisterContainerInstance',
              'ecs:DiscoverPollEndpoint',
              'ecs:Poll',
              'ecs:RegisterContainerInstance',
              'ecs:StartTelemetrySession',
              'ecs:Submit*',
              "ecr:GetAuthorizationToken",
              "ecr:BatchCheckLayerAvailability",
              "ecr:GetDownloadUrlForLayer",
              "ecr:BatchGetImage",
              'logs:CreateLogStream',
              'logs:PutLogEvents'
            ]
            Resource: '*'

  BastionEC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles: [!Ref 'BastionEC2Role']

  BastionLaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Metadata:
      AWS::CloudFormation::Init:
        config:
          files:
            /usr/bin/github-authorized-keys:
              content: |
                      #!/bin/sh
                      set -ue
                      API_URL="${API_URL:-http://github-authorized-keys:301/user/%s/authorized_keys}"
                      if [ -n "$1" ]; then
                        exec curl --silent --fail $(printf "$API_URL" "$1")
                      else
                        echo "Usage: $0 [github username]"
                      fi
              mode: '000550'
              owner: root
              group: root
            /etc/ssh/cloudposse_sshd_config:
              content: |
                  Port 22
                  AddressFamily any
                  ListenAddress 0.0.0.0
                  ListenAddress ::

                  Protocol 2
                  HostKey /etc/ssh/ssh_host_rsa_key

                  # Lifetime and size of ephemeral version 1 server key
                  #KeyRegenerationInterval 1h
                  #ServerKeyBits 1024

                  # Ciphers and keying
                  #RekeyLimit default none

                  # Logging
                  # obsoletes QuietMode and FascistLogging
                  #SyslogFacility AUTH
                  #LogLevel INFO

                  # Authentication:

                  LoginGraceTime 2m
                  PermitRootLogin yes
                  PermitUserRC no
                  StrictModes no
                  MaxAuthTries 6
                  MaxSessions 10

                  PubkeyAuthentication yes

                  # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
                  # but this is overridden so installations will only check .ssh/authorized_keys
                  AuthorizedKeysFile  .ssh/authorized_keys

                  #AuthorizedPrincipalsFile none

                  # Don't read the user's ~/.rhosts and ~/.shosts files
                  IgnoreRhosts yes

                  # To disable tunneled clear text passwords, change to no here!
                  #PasswordAuthentication yes
                  PermitEmptyPasswords no

                  # Change to no to disable s/key passwords
                  ChallengeResponseAuthentication yes

                  UsePAM yes
                  AuthenticationMethods publickey,keyboard-interactive
                  AllowAgentForwarding yes
                  AllowTcpForwarding no
                  GatewayPorts no
                  X11Forwarding no
                  PermitTTY yes
                  PrintMotd no
                  PrintLastLog yes
                  TCPKeepAlive yes
                  #UseLogin no
                  UsePrivilegeSeparation sandbox
                  PermitUserEnvironment no
                  #Compression delayed
                  ClientAliveInterval 30
                  ClientAliveCountMax 3
                  UseDNS no
                  #PidFile /run/sshd.pid
                  PermitTunnel yes
                  ChrootDirectory none
                  VersionAddendum none

                  # no default banner path
                  Banner none

                  # override default of no subsystems
                  Subsystem  sftp  /usr/lib/ssh/sftp-server -l VERBOSE

                  # the following are HPN related configuration options
                  # tcp receive buffer polling. disable in non autotuning kernels
                  #TcpRcvBufPoll yes

                  # disable hpn performance boosts
                  #HPNDisabled no

                  # buffer size for hpn to non-hpn connections
                  #HPNBufferSize 2048

                  ForceCommand /usr/bin/fc

                  # Example of overriding settings on a per-user basis
                  #Match User anoncvs
                  #  X11Forwarding no
                  #  AllowTcpForwarding no
                  #  PermitTTY no
                  #  ForceCommand cvs server

                  AuthorizedKeysCommand /usr/bin/github-authorized-keys
                  AuthorizedKeysCommandUser root
    Properties:
      ImageId: ami-0092e55c70015d8c3 # ECS AMI
      InstanceType: t2.micro
      IamInstanceProfile:
        Ref: BastionEC2InstanceProfile
      KeyName:
        Ref: KeyPairName
      SecurityGroups:
        - !ImportValue
          Fn::Sub: "${NetworkStackName}-BastionSecurityGroupID"
      AssociatePublicIpAddress: true
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe

          yum install -y aws-cfn-bootstrap

          echo ECS_CLUSTER=${AWS::StackName} >> /etc/ecs/ecs.config

          # Process the default configset from the CloudFormation::Init metadata
          /opt/aws/bin/cfn-init -v \
              --region ${AWS::Region} \
              --stack ${AWS::StackName} \
              --resource BastionLaunchConfiguration \
              --configsets default

          # Signal BastionAutoScalingGroup with the cfn-init exit status
          /opt/aws/bin/cfn-signal -e $? \
              --region ${AWS::Region} \
              --stack ${AWS::StackName} \
              --resource BastionAutoScalingGroup

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Name: 'github-authorized-keys'
          MountPoints:
            - SourceVolume: "host"
              ContainerPath: "/host"
          Image: "cloudposse/github-authorized-keys"
          Cpu: "100"
          Memory: "64"
          Essential: "true"
          Environment:
            - Name: GITHUB_API_TOKEN
              Value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
            - Name: GITHUB_ORGANIZATION
              Value: xxxxxx
            - Name: GITHUB_TEAM
              Value: xxxxx
            - Name: SYNC_USERS_SHELL
              Value: /bin/bash
            - Name: SYNC_USERS_ROOT
              Value: /host
            - Name: SYNC_USERS_INTERVAL
              Value: 300
            - Name: LISTEN
              Value: :301
            - Name: INTEGRATE_SSH
              Value: 'false'
            - Name: LINUX_USER_ADD_TPL
              Value: 'adduser -s {shell} {username}'
            - Name: LINUX_USER_ADD_WITH_GID_TPL
              Value: 'adduser -s {shell} -G {group} {username}'
          PortMappings:
            - ContainerPort: 301
              HostPort: 301
              Protocol: tcp

        - Name: 'bastion'
          MountPoints:
            - SourceVolume: "root"
              ContainerPath: "/root"
            - SourceVolume: "home"
              ContainerPath: "/home"
            - SourceVolume: "etc-shadow"
              ContainerPath: "/etc/shadow"
            - SourceVolume: "etc-passwd"
              ContainerPath: "/etc/passwd"
            - SourceVolume: "etc-group"
              ContainerPath: "/etc/group"
            - SourceVolume: "sshd_config"
              ContainerPath: "/etc/ssh/sshd_config"
            - SourceVolume: "usr-bin-github-authorized-keys"
              ContainerPath: "/usr/bin/github-authorized-keys"
          Image: "cloudposse/bastion"
          Cpu: "100"
          Memory: "128"
          Essential: "true"
          Links: 
            - github-authorized-keys
          Environment:
            - Name: DUO_IKEY
              Value: xxxxxxxxxxxxxxxxxxx
            - Name: DUO_SKEY
              Value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
            - Name: DUO_HOST
              Value: api-xxxxx.duosecurity.com
            - Name: SSH_AUDIT_ENABLED
              Value: 'false'
          PortMappings:
            - ContainerPort: 22
              HostPort: 1234
              Protocol: tcp
      Volumes:
        - Name: "host"
          Host:
            SourcePath: "/"
        - Name: "root"
          Host:
            SourcePath: "/root"
        - Name: "home"
          Host:
            SourcePath: "/home"
        - Name: "etc-shadow"
          Host:
            SourcePath: "/etc/shadow"
        - Name: "etc-passwd"
          Host:
            SourcePath: "/etc/passwd"
        - Name: "etc-group"
          Host:
            SourcePath: "/etc/group"
        - Name: "sshd_config"
          Host:
            SourcePath: "/etc/ssh/cloudposse_sshd_config"
        - Name: "usr-bin-github-authorized-keys"
          Host:
            SourcePath: "/usr/bin/github-authorized-keys"

  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Sub ${AWS::StackName}
      DesiredCount: !Ref NumBastionHosts
      TaskDefinition: !Ref TaskDefinition
      DeploymentConfiguration:
        MinimumHealthyPercent: 0
osterman commented 5 years ago

@dmcd so did you end up getting it all working? =)

dmcd commented 5 years ago

Yes, its working nicely with the above template. I couldn't get the ssh audit logs working but I might revisit that at some point.

osterman commented 5 years ago

Well done @dmcd ! Looks non-trivial.

couldn't get the ssh audit logs working but I might revisit that at some point.

Not sure if this is related to (https://github.com/cloudposse/bastion/issues/25), but it's impossible to log sessions with ProxyCommand because it's an encrypted tunnel. =)

For that, may need to use teleport.

kc1116 commented 5 years ago

Hi I am using this template as a basis for docker compose but I can't get the github-authorized-keys container to find the adduser executable. I am running this on a amazon linux box, with the same exact environment variables. This is the error I keep getting.

{"job":"syncUsers","level":"error","msg":"fork/exec '$(pwd)/usr/sbin/adduser: no such file or directory","subsystem":"jobs","time":"2018-10-01T16:30:17Z"}

osterman commented 5 years ago

this looks like your running something like '$(pwd)/usr/sbin/adduser' vs "$(pwd)/usr/sbin/adduser"; meaning the $(pwd) is not being interpolated.

Also, just want to point out that nearly every linux distro has a different arg format for adduser, which is why we've templatized it. You may need to alter the command templates.

image from README

kc1116 commented 5 years ago

@osterman thanks for the quick reply, turns out that in the .env file I was using I was wrapping the value in single quotes '' just needed to remove them.

osterman commented 5 years ago

wonderful! glad you got it all sorted out.

kc1116 commented 5 years ago

@osterman I feel like I am very close to getting this to work. For some reason I keep getting this error when I start the compose network and try to ssh from a different shell.

PAM: Permission denied

I am also seeing 'error: exit code 2' when the initial users are added. Are there some permissions I might be missing, I am using amazon linux btw

davidvasandani commented 4 years ago

@kc1116 do you mind posting the Docker Compose file?