diff --git a/.github/workflows/build-gem.yml b/.github/workflows/build-gem.yml new file mode 100644 index 00000000..75036357 --- /dev/null +++ b/.github/workflows/build-gem.yml @@ -0,0 +1,26 @@ +name: test and build gem +on: + push: + branches: + - master + - develop + - feature/* + +jobs: + build: + name: test + build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up ruby 2.7 + uses: actions/setup-ruby@v1 + with: + ruby-version: 2.7.x + - name: rspec + run: | + gem install rspec + rspec + - name: build gem + run: | + gem build ciinabox-ecs.gemspec \ No newline at end of file diff --git a/.github/workflows/release-gem.yml b/.github/workflows/release-gem.yml new file mode 100644 index 00000000..b9fac216 --- /dev/null +++ b/.github/workflows/release-gem.yml @@ -0,0 +1,34 @@ +name: release gem + +on: + release: + types: [published] + +jobs: + build: + name: Build + Publish Gem + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Set up Ruby 2.7 + uses: actions/setup-ruby@v1 + with: + ruby-version: 2.7.x + + - name: rspec + run: | + gem install rspec + rspec + + - name: build gem + run: | + gem build ciinabox-ecs.gemspec + + - name: Publish gem + uses: dawidd6/action-publish-gem@v1 + with: + api_key: ${{secrets.RUBYGEMS_API_KEY}} + github_token: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml new file mode 100644 index 00000000..461b44b2 --- /dev/null +++ b/.github/workflows/release-image.yml @@ -0,0 +1,33 @@ +name: release docker image + +on: + release: + types: [published] + +jobs: + build: + name: Build + Publish Container Image + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Repository + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Container Image to GitHub Container Repository + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + push: true + tags: ghcr.io/${{ github.repository_owner }}/ciinabox-ecs:${{ github.event.release.tag_name }} + build-args: CIINABOX_ECS_VERSION=${{ github.event.release.tag_name }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b0db5836..00000000 --- a/.travis.yml +++ /dev/null @@ -1,46 +0,0 @@ -sudo: required -dist: trusty -rvm: - - 2.3 -python: - - 3.6 -#before_install: -# - | -# sudo apt-get update && \ -# sudo apt-get install software-properties-common -y && \ -# sudo add-apt-repository ppa:deadsnakes/ppa -y && \ -# sudo apt-add-repository ppa:brightbox/ruby-ng -y && \ -# sudo apt-get update && sudo apt-get install python3.6 python3-pip -y && \ -# sudo apt-get install ruby2.3 -y -script: - - gem build ciinabox-ecs.gemspec - - gem install ciinabox-ecs-*.gem - - which ciinabox-ecs && ciinabox-ecs help - - cfndsl -u 9.0.0 - - | - git clone https://github.com/base2services/ciinabox-ecs-examples - cd ciinabox-ecs-examples - which pip - git checkout master - set -x - for ciinabox in demo_* ; do - printf "\n\nTesting ${ciinabox}\n\n" - # avoid validation in PRs as aws creds are not available - set +e - if [[ "$TRAVIS_EVENT_TYPE" =~ ^push|api$ ]]; then - ciinabox-ecs generate validate ${ciinabox} - else - ciinabox-ecs generate ${ciinabox} - fi - if [ $? -ne 0 ]; then - printf "\n\nCIINABOX test configuration ${ciinabox} failed\n\n" - exit 2 - fi - done - - cd .. -deploy: - provider: rubygems - api_key: "${RUBYGEMS_API_KEY}" - on: - all_branches: true - condition: $TRAVIS_BRANCH =~ ^develop|master && $TRAVIS_EVENT_TYPE =~ ^push|api$ && $TRAVIS_REPO_SLUG == "base2Services/ciinabox-ecs" diff --git a/Dockerfile b/Dockerfile index 948b30c0..e288b0bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,15 @@ FROM ruby:2.5-alpine +LABEL org.opencontainers.image.source = https://github.com/base2Services/ciinabox-ecs + ARG CFNDSL_SPEC_VERSION=${CFNDSL_SPEC_VERSION:-9.0.0} +ARG CIINABOX_ECS_VERSION='*' COPY . /src WORKDIR /src -RUN rm ciinabox-ecs-*.gem ; \ - gem build ciinabox-ecs.gemspec && \ - gem install ciinabox-ecs-*.gem && \ +RUN gem build ciinabox-ecs.gemspec && \ + gem install ciinabox-ecs-${CIINABOX_ECS_VERSION}.gem && \ rm -rf /src RUN adduser -u 1000 -D ciinabox && \ diff --git a/README.md b/README.md index 938a6e86..44a6c89d 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,23 @@ A common update would be to lock down ip access to your ciinabox environment .... ``` + or using AWS IP Prefix Lists + + ```yaml + .... + #Environment Access + #add list of public IP addresses you want to access the environment from + #default to public access probably best to change this + opsIpPrefixLists: + - pl-12345 + - pl-abcde + #add list of public IP addresses for your developers to access the environment + #default to public access probably best to change this + devIpPrefixLists: + - pl-11111 + .... + ``` + 2. update your ciinabox ```bash $ ciinabox-ecs generate deploy update [ciinabox_name] diff --git a/ciinabox-ecs.gemspec b/ciinabox-ecs.gemspec index 320bd06f..bbb32b20 100644 --- a/ciinabox-ecs.gemspec +++ b/ciinabox-ecs.gemspec @@ -3,7 +3,7 @@ require 'date' Gem::Specification.new do |s| s.name = 'ciinabox-ecs' - s.version = '0.3.2' + s.version = '0.4.0' s.version = "#{s.version}.alpha.#{Time.now.getutc.to_i}" if ENV['TRAVIS'] and ENV['TRAVIS_BRANCH'] != 'master' s.date = Date.today.to_s s.summary = 'Manage ciinabox on Aws Ecs' diff --git a/templates/ecs-services.rb b/templates/ecs-services.rb index 0a34cb0e..9cc0d529 100644 --- a/templates/ecs-services.rb +++ b/templates/ecs-services.rb @@ -137,20 +137,23 @@ ]) } - if defined? webHooks - rules = [] - webHooks.each do |ip| - rules << { IpProtocol: 'tcp', FromPort: '443', ToPort: '443', CidrIp: ip } - end - else - rules = [{ IpProtocol: 'tcp', FromPort: '443', ToPort: '443', CidrIp: '192.168.1.1/32' }] + webHooks = webHooks || [] + webHooksIpPrefixLists = webHooksIpPrefixLists || [] + + rules = [] + webHooks.each do |ip| + rules << { IpProtocol: 'tcp', FromPort: '443', ToPort: '443', CidrIp: ip } + end + + webHooksIpPrefixLists.each do |list| + rules << { IpProtocol: 'tcp', FromPort: '443', ToPort: '443', SourcePrefixListId: list } end Resource("SecurityGroupWebHooks") { Type 'AWS::EC2::SecurityGroup' Property('VpcId', Ref('VPC')) Property('GroupDescription', 'WebHooks like github') - Property('SecurityGroupIngress', rules) + Property('SecurityGroupIngress', rules) if rules.any? } Resource('ToolsSSLCertificate') { @@ -261,6 +264,14 @@ end end + log_group_retention = log_group_retention || 90 + + Resource("LogGroup") { + Type "AWS::Logs::LogGroup" + Property("LogGroupName", "/ciinabox/#{ciinabox_name}/proxy") + Property("RetentionInDays", log_group_retention) + } + volumes = [] mount_points = [] @@ -290,6 +301,14 @@ HostPort: 8080, ContainerPort: 80 }], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group' => Ref("LogGroup"), + "awslogs-region" => Ref("AWS::Region"), + "awslogs-stream-prefix" => "proxy" + } + }, Essential: true, MountPoints: mount_points } diff --git a/templates/services/jenkins.rb b/templates/services/jenkins.rb index f8a9d749..8812c802 100644 --- a/templates/services/jenkins.rb +++ b/templates/services/jenkins.rb @@ -1,189 +1,219 @@ require 'cfndsl' require_relative '../../ext/helper' -if !defined? timezone - timezone = 'GMT' -end - -if !defined? internal_elb - internal_elb = nil -end - -if !defined? volatile_jenkins_slave - volatile_jenkins_slave = false -end - -# Prefixing application images allows us to 'vendorize' ciinabox into client's account by setting -# ciinabox_repo to ${account_no}.dkr.ecr.${region}.amazonaws.com -if not defined? ciinabox_repo - ciinabox_repo = 'ghcr.io/base2services' -end -image = "#{ciinabox_repo}/ciinabox-jenkins:lts" - -jenkins_java_opts = '' -memory = 2048 -slave_memory = 2048 -cpu = 300 -container_port = 0 -service = lookup_service('jenkins', services) -virtual_host = "jenkins.#{dns_domain}" -if defined? internal_elb and internal_elb - virtual_host = "#{virtual_host},internal-jenkins.#{dns_domain}" -end -port_mappings = [] - -if defined? service - service = {} if service.nil? - jenkins_java_opts = service['JAVA_OPTS'] || '' - image = service['ContainerImage'] || image - memory = service['ContainerMemory'] || 2048 - slave_memory = service['SlaveContainerMemory'] || 2048 - cpu = service['ContainerCPU'] || 300 - - if service['InstancePort'] - port_mappings << { - HostPort: service['InstancePort'], - ContainerPort: service['InstancePort'] - } - container_port = service['InstancePort'] - virtual_host = "jenkins.#{dns_domain},internal-jenkins.#{dns_domain}" +CloudFormation { + AWSTemplateFormatVersion "2010-09-09" + Description "ciinabox - ECS Service Jenkins v#{ciinabox_version}" + + Parameter("ECSCluster") {Type 'String'} + Parameter("ECSRole") {Type 'String'} + Parameter("ServiceELB") {Type 'String'} + Parameter('InternalELB') {Type 'String'} if internal_elb + + if !defined? timezone + timezone = 'GMT' end -end + if !defined? internal_elb + internal_elb = nil + end -# container volumes and container definitions depending on feature flags -volumes = [ - { - Name: 'timezone', - Host: { - SourcePath: '/etc/localtime' - } - }, - { - Name: 'jenkins_data', - Host: { - SourcePath: '/data/jenkins' - } - }] - -container_definitions = [ - { - Name: 'jenkins', - Links: [], - Memory: memory, - Cpu: cpu, - Image: image, - PortMappings: port_mappings, - Environment: [ - { - Name: 'JAVA_OPTS', - Value: "#{jenkins_java_opts} -Duser.timezone=#{timezone}" - }, - { - Name: 'VIRTUAL_HOST', - Value: virtual_host - }, - { - Name: 'VIRTUAL_PORT', - Value: '8080' - } - ], - Essential: true, + if !defined? volatile_jenkins_slave + volatile_jenkins_slave = false + end + + # Prefixing application images allows us to 'vendorize' ciinabox into client's account by setting + # ciinabox_repo to ${account_no}.dkr.ecr.${region}.amazonaws.com + if not defined? ciinabox_repo + ciinabox_repo = 'ghcr.io/base2services' + end + image = "#{ciinabox_repo}/ciinabox-jenkins:lts" + + jenkins_java_opts = '' + memory = 2048 + slave_memory = 2048 + cpu = 300 + container_port = 0 + service = lookup_service('jenkins', services) + virtual_host = "jenkins.#{dns_domain}" + if defined? internal_elb and internal_elb + virtual_host = "#{virtual_host},internal-jenkins.#{dns_domain}" + end + port_mappings = [] + + if defined? service + service = {} if service.nil? + jenkins_java_opts = service['JAVA_OPTS'] || '' + image = service['ContainerImage'] || image + memory = service['ContainerMemory'] || 2048 + slave_memory = service['SlaveContainerMemory'] || 2048 + cpu = service['ContainerCPU'] || 300 + + if service['InstancePort'] + port_mappings << { + HostPort: service['InstancePort'], + ContainerPort: service['InstancePort'] + } + container_port = service['InstancePort'] + virtual_host = "jenkins.#{dns_domain},internal-jenkins.#{dns_domain}" + end + + end + + # container volumes and container definitions depending on feature flags + volumes = [ + { + Name: 'timezone', + Host: { + SourcePath: '/etc/localtime' + } + }, + { + Name: 'jenkins_data', + Host: { + SourcePath: '/data/jenkins' + } + }] + + container_definitions = [ + { + Name: 'jenkins', + Links: [], + Memory: memory, + Cpu: cpu, + Image: image, + PortMappings: port_mappings, + Environment: [ + { + Name: 'JAVA_OPTS', + Value: "#{jenkins_java_opts} -Duser.timezone=#{timezone}" + }, + { + Name: 'VIRTUAL_HOST', + Value: virtual_host + }, + { + Name: 'VIRTUAL_PORT', + Value: '8080' + } + ], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group' => Ref("LogGroup"), + "awslogs-region" => Ref("AWS::Region"), + "awslogs-stream-prefix" => "jenkins" + } + }, + Essential: true, + MountPoints: [ + { + ContainerPath: '/etc/localtime', + SourceVolume: 'timezone', + ReadOnly: true + }, + { + ContainerPath: '/var/jenkins_home', + SourceVolume: 'jenkins_data', + ReadOnly: false + } + ] + } + ] + + # If docker in docker slave is enabled + if defined? include_diind_slave and include_diind_slave + container_definitions[0][:Links] << 'jenkins-docker-dind-slave' + dind_definition = { + Name: 'jenkins-docker-dind-slave', + Memory: slave_memory, + Image: "#{ciinabox_repo}/ciinabox-docker-slave:#{docker_slave_version}", + Environment: [{Name: 'RUN_DOCKER_IN_DOCKER', Value: 1}], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group' => Ref("LogGroup"), + "awslogs-region" => Ref("AWS::Region"), + "awslogs-stream-prefix" => "jenkins-docker-dind-slave" + } + }, + Essential: false, + Privileged: true + } + dind_definition[:Environment] << { Name: 'USE_ECR_CREDENTIAL_HELPER', Value: 1 } if docker_slave_enable_ecr_credentials_helper + if not volatile_jenkins_slave + dind_definition[:MountPoints] = [ + { + ContainerPath: '/var/lib/docker', + SourceVolume: 'jenkins_dind_data', + ReadOnly: false + } + ] + volumes << { + Name: 'jenkins_dind_data', + Host: { + SourcePath: '/data/jenkins-diind' + } + } + end + container_definitions << dind_definition + + end + + # If docker outside of docker slave is enabled + if defined? include_dood_slave and include_dood_slave + container_definitions[0][:Links] << 'jenkins-docker-dood-slave' + dood_definition = { + Name: 'jenkins-docker-dood-slave', + Memory: slave_memory, + Image: "#{ciinabox_repo}/ciinabox-docker-slave:#{docker_slave_version}", + Environment: [{Name: 'RUN_DOCKER_IN_DOCKER', Value: 0}], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group' => Ref("LogGroup"), + "awslogs-region" => Ref("AWS::Region"), + "awslogs-stream-prefix" => "jenkins-docker-dood-slave" + } + }, MountPoints: [ { - ContainerPath: '/etc/localtime', - SourceVolume: 'timezone', - ReadOnly: true + ContainerPath: '/var/run/docker.sock', + SourceVolume: 'docker_socket', + ReadOnly: false }, { - ContainerPath: '/var/jenkins_home', - SourceVolume: 'jenkins_data', + ContainerPath: '/data/jenkins-dood', + SourceVolume: 'jenkins_dood_data', ReadOnly: false } - ] + ], + Essential: false, + Privileged: false } -] - -# If docker in docker slave is enabled -if defined? include_diind_slave and include_diind_slave - container_definitions[0][:Links] << 'jenkins-docker-dind-slave' - dind_definition = { - Name: 'jenkins-docker-dind-slave', - Memory: slave_memory, - Image: "#{ciinabox_repo}/ciinabox-docker-slave:#{docker_slave_version}", - Environment: [{Name: 'RUN_DOCKER_IN_DOCKER', Value: 1}], - Essential: false, - Privileged: true - } - dind_definition[:Environment] << { Name: 'USE_ECR_CREDENTIAL_HELPER', Value: 1 } if docker_slave_enable_ecr_credentials_helper - if not volatile_jenkins_slave - dind_definition[:MountPoints] = [ - { - ContainerPath: '/var/lib/docker', - SourceVolume: 'jenkins_dind_data', - ReadOnly: false + dood_definition[:Environment] << { Name: 'USE_ECR_CREDENTIAL_HELPER', Value: 1 } if docker_slave_enable_ecr_credentials_helper + container_definitions << dood_definition + volumes << { + Name: 'jenkins_dood_data', + Host: { + SourcePath: '/data/jenkins-dood' } - ] + } volumes << { - Name: 'jenkins_dind_data', + Name: 'docker_socket', Host: { - SourcePath: '/data/jenkins-diind' + SourcePath: '/var/run/docker.sock' } } end - container_definitions << dind_definition - -end - -# If docker outside of docker slave is enabled -if defined? include_dood_slave and include_dood_slave - container_definitions[0][:Links] << 'jenkins-docker-dood-slave' - dood_definition = { - Name: 'jenkins-docker-dood-slave', - Memory: slave_memory, - Image: "#{ciinabox_repo}/ciinabox-docker-slave:#{docker_slave_version}", - Environment: [{Name: 'RUN_DOCKER_IN_DOCKER', Value: 0}], - MountPoints: [ - { - ContainerPath: '/var/run/docker.sock', - SourceVolume: 'docker_socket', - ReadOnly: false - }, - { - ContainerPath: '/data/jenkins-dood', - SourceVolume: 'jenkins_dood_data', - ReadOnly: false - } - ], - Essential: false, - Privileged: false - } - dood_definition[:Environment] << { Name: 'USE_ECR_CREDENTIAL_HELPER', Value: 1 } if docker_slave_enable_ecr_credentials_helper - container_definitions << dood_definition - volumes << { - Name: 'jenkins_dood_data', - Host: { - SourcePath: '/data/jenkins-dood' - } - } - volumes << { - Name: 'docker_socket', - Host: { - SourcePath: '/var/run/docker.sock' - } - } -end - -CloudFormation { + log_group_retention = log_group_retention || 90 - AWSTemplateFormatVersion "2010-09-09" - Description "ciinabox - ECS Service Jenkins v#{ciinabox_version}" - - Parameter("ECSCluster") {Type 'String'} - Parameter("ECSRole") {Type 'String'} - Parameter("ServiceELB") {Type 'String'} - Parameter('InternalELB') {Type 'String'} if internal_elb + Resource("LogGroup") { + Type "AWS::Logs::LogGroup" + Property("LogGroupName", "/ciinabox/#{ciinabox_name}/jenkins") + Property("RetentionInDays", log_group_retention) + } Resource('JenkinsTask') { Type "AWS::ECS::TaskDefinition" diff --git a/templates/vpc.rb b/templates/vpc.rb index fb0838a4..59a1f753 100644 --- a/templates/vpc.rb +++ b/templates/vpc.rb @@ -167,6 +167,13 @@ rules << { IpProtocol: 'tcp', FromPort: '50000', ToPort: '50000', CidrIp: ip } end + opsIpPrefixLists = opsIpPrefixLists || [] + + opsIpPrefixLists.each do |list| + rules << { IpProtocol: 'tcp', FromPort: '80', ToPort: '80', SourcePrefixListId: list } + rules << { IpProtocol: 'tcp', FromPort: '443', ToPort: '443', SourcePrefixListId: list } + end + Resource("SecurityGroupOps") { Type 'AWS::EC2::SecurityGroup' Property('VpcId', Ref('VPC')) @@ -184,6 +191,13 @@ rules << { IpProtocol: 'tcp', FromPort: '50000', ToPort: '50000', CidrIp: ip } end + devIpPrefixLists = devIpPrefixLists || [] + + devIpPrefixLists.each do |list| + rules << { IpProtocol: 'tcp', FromPort: '80', ToPort: '80', SourcePrefixListId: list } + rules << { IpProtocol: 'tcp', FromPort: '443', ToPort: '443', SourcePrefixListId: list } + end + Resource("SecurityGroupDev") { Type 'AWS::EC2::SecurityGroup' Property('VpcId', Ref('VPC'))