├── .gitattributes ├── .gitignore ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.md ├── Vagrantfile ├── ansible ├── app-AfterInstall.yml ├── app-StartServer.yml ├── bakery.yml ├── cloudinit.yml ├── codedeploy.yml ├── gauntlt-results.txt ├── newrelic-infrastructure.yml ├── requirements.yml ├── roles │ ├── app-AfterInstall │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── my_httpd_t.te │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── templates │ │ │ ├── app.conf.j2 │ │ │ ├── emperor.ini.j2 │ │ │ ├── emperor.service.j2 │ │ │ └── infra-demo.ini.j2 │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ ├── app-StartServer │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ ├── cloudwatch-agent │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── config.json │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ ├── extra-cis-remediation │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ ├── prepare-codedeploy │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ ├── prepare-web-content │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ ├── scan-gauntlt │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ └── tasks │ │ │ └── main.yml │ └── scan-openscap │ │ ├── README.md │ │ ├── defaults │ │ └── main.yml │ │ ├── handlers │ │ └── main.yml │ │ ├── meta │ │ └── main.yml │ │ ├── tasks │ │ └── main.yml │ │ ├── tests │ │ ├── inventory │ │ └── test.yml │ │ └── vars │ │ └── main.yml ├── scan-gauntlt.yml └── scan-openscap.yml ├── application ├── 50x.html ├── Dockerfile ├── LICENSE.txt ├── README.txt ├── assets │ ├── css │ │ ├── font-awesome.min.css │ │ ├── ie9.css │ │ ├── main.css │ │ └── noscript.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── js │ │ ├── jquery.min.js │ │ ├── main.js │ │ ├── skel.min.js │ │ └── util.js │ └── sass │ │ ├── base │ │ ├── _page.scss │ │ └── _typography.scss │ │ ├── components │ │ ├── _box.scss │ │ ├── _button.scss │ │ ├── _form.scss │ │ ├── _icon.scss │ │ ├── _image.scss │ │ ├── _list.scss │ │ └── _table.scss │ │ ├── ie9.scss │ │ ├── layout │ │ ├── _bg.scss │ │ ├── _footer.scss │ │ ├── _header.scss │ │ ├── _main.scss │ │ └── _wrapper.scss │ │ ├── libs │ │ ├── _functions.scss │ │ ├── _mixins.scss │ │ ├── _skel.scss │ │ └── _vars.scss │ │ ├── main.scss │ │ └── noscript.scss ├── images │ ├── bg.jpg │ ├── bg1.jpg │ ├── bg2.jpg │ ├── overlay.png │ ├── pic01.jpg │ ├── pic02.jpg │ └── pic03.jpg └── index.html ├── bin ├── activate-rvm.sh ├── ansible.sh ├── build-codedeploy.sh ├── clean-workspace.sh ├── codedeploy │ ├── AfterInstall.sh │ ├── ApplicationStart.sh │ ├── ApplicationStop.sh │ ├── BeforeInstall.sh │ └── ValidateService.sh ├── common-terraform.sh ├── common.sh ├── deploy-codedeploy.sh ├── install-ansible.sh ├── install-gauntlt.sh ├── jmeter.sh ├── pack.sh ├── prep.sh ├── rotate-asg.sh ├── scan.sh ├── set-newrelic-license-key.sh ├── spin-3.sh ├── spin-7.sh ├── spin.sh ├── terraform.sh └── validate.sh ├── codedeploy └── appspec.yml ├── env.sh.sample ├── gauntlt ├── nmap-invariant.attack ├── nmap-running.attack ├── nmap-stopped.attack ├── nmap.attack └── readme.md ├── jmeter └── api-spin.jmx ├── packer └── machines │ └── web-server.json ├── src ├── Dockerfile ├── requirements.txt ├── run.py ├── spin.py ├── startit.sh ├── stopit.sh └── wsgi.py └── terraform ├── assume-role-policy-codedeploy.json ├── assume-role-policy-ec2.json ├── aws.tf ├── cloud-config.yml ├── codedeploy.tf ├── elb.tf ├── google.tf ├── infra-demo-role-policy.json ├── instances.tf ├── newrelic.tf ├── route53.tf ├── secrets.tf ├── security-groups.tf ├── terraform.tf ├── variables.tf └── vpc.tf /.gitattributes: -------------------------------------------------------------------------------- 1 | application/assets/**/* linguist-vendored 2 | application/*.html linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | *.pyc 3 | *.retry 4 | *.tmp 5 | *~ 6 | .DS_Store 7 | .vagrant 8 | /env.sh 9 | /terraform/.terraform* 10 | /terraform/terraform.tfstate* 11 | /terraform/tf.plan 12 | __pycache__ 13 | build/ 14 | tmp/ 15 | jmeter.log 16 | scan-xccdf-results.html 17 | scan-xccdf-results.xml 18 | venv/ 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM centos:latest AS infra-demo 3 | 4 | # setup rpm repos, install base packages and create virtual env in a single step 5 | RUN yum install -y https://centos7.iuscommunity.org/ius-release.rpm \ 6 | && yum update -y \ 7 | && yum install -y \ 8 | python36u python36u-libs python36u-devel \ 9 | python36u-pip uwsgi-plugin-python36u uwsgi \ 10 | gcc make glibc-devel kernel-headers \ 11 | pcre pcre-devel pcre2 pcre2-devel \ 12 | postgresql-devel \ 13 | && yum clean all \ 14 | && mkdir /app \ 15 | && python3.6 -m venv --copies --clear /app/venv 16 | 17 | # Copy in your requirements file 18 | ADD src/requirements.txt /app/requirements.txt 19 | 20 | # setup python packages 21 | RUN /app/venv/bin/pip install -U pip \ 22 | && /bin/sh -c "/app/venv/bin/pip install --no-cache-dir -r /app/requirements.txt" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, Modus Create, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "bento/centos-7.5" 3 | config.vm.synced_folder ".", "/app" 4 | config.vm.provision "shell", inline: "/app/bin/install-gauntlt.sh", upload_path: "/home/vagrant/install-gauntlt.sh", privileged: false 5 | config.vm.provision "shell", inline: "/app/bin/install-ansible.sh", upload_path: "/home/vagrant/install-ansible.sh", privileged: false 6 | config.vm.provision "shell", inline: "/app/bin/ansible.sh bakery.yml scan-openscap.yml scan-gauntlt.yml app-AfterInstall.yml app-StartServer.yml ", upload_path: "/home/vagrant/ansible.sh", privileged: false 7 | config.vm.network "forwarded_port", guest: 80, host: 6080, auto_correct: true 8 | end 9 | -------------------------------------------------------------------------------- /ansible/app-AfterInstall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use ansible to install the codedeploy agent at boot time through cloudinit 3 | 4 | # Because AWS updates the CodeDeploy agent somewhat frequently, baking it into 5 | # the image is an antipattern. It can cause instances to fail to register with 6 | # a CodeDeploy deployment group if the version of CodeDeploy is too old. 7 | 8 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 9 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 10 | - name: Perform CodeDeploy AfterInstall hook 11 | hosts: 127.0.0.1 12 | connection: local 13 | become: yes 14 | roles: 15 | - app-AfterInstall 16 | -------------------------------------------------------------------------------- /ansible/app-StartServer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 4 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 5 | - name: Perform CodeDeploy StartServer hook 6 | hosts: 127.0.0.1 7 | connection: local 8 | become: yes 9 | roles: 10 | - app-StartServer 11 | -------------------------------------------------------------------------------- /ansible/bakery.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use ansible to provision hosts 3 | 4 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 5 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 6 | 7 | - name: Install CloudWatch Agent 8 | hosts: 127.0.0.1 9 | connection: local 10 | become: yes 11 | roles: 12 | - cloudwatch-agent 13 | 14 | - name: Install New Relic Infrastructure 15 | hosts: 127.0.0.1 16 | connection: local 17 | become: yes 18 | roles: 19 | - newrelic.newrelic-infra 20 | vars: 21 | nrinfragent_os_name: CentOS 22 | nrinfragent_os_version: 7 23 | nrinfragent_config: 24 | license_key: ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 25 | log_file: /var/log/newrelic-infra/nr-infra.log 26 | log_to_stdout: false 27 | 28 | - name: Install Web Application 29 | hosts: 127.0.0.1 30 | connection: local 31 | become: yes 32 | roles: 33 | - nginxinc.nginx 34 | - prepare-web-content 35 | - prepare-codedeploy 36 | vars: 37 | nginx_start: false 38 | 39 | 40 | - name: Harden Server 41 | hosts: 127.0.0.1 42 | connection: local 43 | become: yes 44 | roles: 45 | - extra-cis-remediation 46 | #- MindPointGroup.RHEL7-CIS 47 | -------------------------------------------------------------------------------- /ansible/cloudinit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use ansible to install the codedeploy agent at boot time through cloudinit 3 | 4 | # Because AWS updates the CodeDeploy agent somewhat frequently, baking it into 5 | # the image is an antipattern. It can cause instances to fail to register with 6 | # a CodeDeploy deployment group if the version of CodeDeploy is too old. 7 | 8 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 9 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 10 | - name: Install CodeDeploy agent 11 | hosts: 127.0.0.1 12 | connection: local 13 | become: yes 14 | roles: 15 | - ansible-aws-codedeploy-agent 16 | -------------------------------------------------------------------------------- /ansible/codedeploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use ansible to install the codedeploy agent at boot time through cloudinit 3 | 4 | # Because AWS updates the CodeDeploy agent somewhat frequently, baking it into 5 | # the image is an antipattern. It can cause instances to fail to register with 6 | # a CodeDeploy deployment group if the version of CodeDeploy is too old. 7 | 8 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 9 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 10 | - name: Install CodeDeploy agent 11 | hosts: 127.0.0.1 12 | connection: local 13 | become: yes 14 | roles: 15 | - ansible-aws-codedeploy-agent 16 | -------------------------------------------------------------------------------- /ansible/newrelic-infrastructure.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use ansible to install the newrelic infra agent. 3 | 4 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 5 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 6 | 7 | - name: Set the newrelic license key 8 | hosts: 127.0.0.1 9 | connection: local 10 | become: yes 11 | tasks: 12 | - command: bash /app/bin/set-newrelic-license-key.sh 13 | 14 | - name: Restart newrelic-infra 15 | hosts: 127.0.0.1 16 | connection: local 17 | become: yes 18 | service: 19 | name: newerelic-infra 20 | state: restarted 21 | 22 | -------------------------------------------------------------------------------- /ansible/requirements.yml: -------------------------------------------------------------------------------- 1 | # Install roles from Ansible Galaxy 2 | 3 | # CIS hardening 4 | - src: MindPointGroup.RHEL7-CIS 5 | 6 | # NGINX web server 7 | - src: nginxinc.nginx 8 | 9 | # AWS CodeDeploy agent 10 | # The original version of this role in Ansible Galaxy 11 | # is telus/ansible-aws-codedeploy-agent 12 | # 13 | # It has not been updated for Ansible 2.7 :( 14 | # However CareerBuilder's fork of it works with 2.7 :) 15 | # And the ModusCreateOrg fork has been fixed to avoid running stuff from /tmp 16 | - src: https://github.com/ModusCreateOrg/ansible-aws-codedeploy-agent 17 | 18 | # New Relic Infrastructure 19 | - src: newrelic.newrelic-infra 20 | 21 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/README.md: -------------------------------------------------------------------------------- 1 | App AfterInstall Role 2 | ===================== 3 | 4 | This is intended to prepare an application that requires some setup after installation for running. It will modify SELinux labels. This is intended for use with AWS CodeDeploy as an AfterInstall hook. 5 | 6 | Requirements 7 | ------------ 8 | 9 | None 10 | 11 | Role Variables 12 | -------------- 13 | 14 | None 15 | 16 | Dependencies 17 | ------------ 18 | 19 | SELinux and a Red Hat Linux family OS 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Please see the example below: 25 | 26 | - name: Perform AfterInstall hook 27 | hosts: 127.0.0.1 28 | connection: local 29 | become: yes 30 | roles: 31 | - app-AfterInstall 32 | 33 | 34 | License 35 | ------- 36 | 37 | MIT 38 | 39 | Author Information 40 | ------------------ 41 | 42 | Richard Bullington-McGuire 43 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for app-AfterInstall 3 | ec2: false 4 | virtualbox: false 5 | app_dir: /app/application 6 | server_name: localhost 7 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/files/my_httpd_t.te: -------------------------------------------------------------------------------- 1 | # Credit to https://stackoverflow.com/a/45339087 2 | module my_httpd_t 1.0; 3 | 4 | require { 5 | type httpd_t; 6 | type vmblock_t; 7 | class dir read; 8 | class file { read getattr open }; 9 | } 10 | 11 | #============= httpd_t ============== 12 | allow httpd_t vmblock_t:dir read; 13 | allow httpd_t vmblock_t:file { getattr open read }; 14 | 15 | # Generated by audit2allow 16 | 17 | # To apply this policy: 18 | ## sudo su - 19 | ## checkmodule -M -m -o my_httpd_t.mod my_httpd_t.te 20 | ## semodule_package -o my_httpd_t.pp -m my_httpd_t.mod 21 | ## semodule -i my_httpd_t.pp 22 | ## systemctl restart httpd 23 | ## # or 24 | ## systemctl restart nginx 25 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for app-selinux -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for prepare-web-content 3 | 4 | - name: Ensure python-virtualenv is present 5 | package: name={{item}} state=present 6 | with_items: 7 | - python-virtualenv 8 | 9 | - name: Define VirtualBox variable 10 | set_fact: 11 | virtualbox: true 12 | when: "'VirtualBox' in ansible_bios_version" 13 | 14 | - name: Debug ansible_bios_version 15 | debug: 16 | msg: "{{ ansible_bios_version }}" 17 | 18 | - name: Define ec2 variable 19 | set_fact: 20 | ec2: true 21 | when: "'amazon' in ansible_bios_version or 'Amazon EC2' in ansible_system_vendor" 22 | 23 | - name: Ensure selinux modules are present 24 | package: name={{item}} state=present 25 | with_items: 26 | - checkpolicy 27 | - policycoreutils-python 28 | - policycoreutils 29 | 30 | - name: Create temp directory 31 | tempfile: 32 | state: directory 33 | suffix: selinux 34 | register: tmpdir 35 | when: virtualbox 36 | 37 | - name: Copy SELinux policy to temp directory 38 | copy: 39 | src: my_httpd_t.te 40 | dest: "{{ tmpdir.path }}" 41 | when: virtualbox 42 | 43 | - name: Set up SELinux rules for Virtualbox 44 | shell: | 45 | cd "{{ tmpdir.path }}" 46 | checkmodule -M -m -o my_httpd_t.mod my_httpd_t.te 47 | semodule_package -o my_httpd_t.pp -m my_httpd_t.mod 48 | semodule -i my_httpd_t.pp 49 | when: virtualbox 50 | 51 | - name: Set up SELinux rules for Amazon EC2 52 | shell: | 53 | setsebool -P httpd_can_network_connect 1 54 | setsebool -P httpd_can_network_relay 1 55 | semanage fcontext -a -t httpd_sys_content_t "{{ app_dir }}(/.*)?" 56 | restorecon -R "{{ app_dir }}" 57 | when: ec2 58 | 59 | - name: Ensure default web server config is removed 60 | file: 61 | path: /etc/nginx/conf.d/default.conf 62 | state: absent 63 | 64 | - name: Copy web server config into place 65 | template: 66 | src: app.conf.j2 67 | dest: /etc/nginx/conf.d/app.conf 68 | mode: 0640 69 | 70 | - name: Install python dependencies for app 71 | pip: 72 | requirements: /app/src/requirements.txt 73 | virtualenv: /app/venv 74 | 75 | - name: nginx owns /app/socket 76 | file: 77 | path: /app/socket 78 | state: directory 79 | owner: nginx 80 | group: nginx 81 | 82 | - name: Emperor systemd config 83 | template: 84 | src: emperor.service.j2 85 | dest: /etc/systemd/system/emperor.service 86 | owner: root 87 | group: root 88 | mode: 0640 89 | 90 | - name: emperor.ini 91 | template: 92 | src: emperor.ini.j2 93 | dest: /app/emperor.ini 94 | owner: root 95 | group: root 96 | mode: 0644 97 | 98 | - name: uwsgi app config infra-demo.ini 99 | template: 100 | src: infra-demo.ini.j2 101 | dest: /app/src/infra-demo.ini 102 | owner: root 103 | group: root 104 | mode: 0644 -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/templates/app.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name {{ server_name }}; 4 | 5 | location / { 6 | root {{ app_dir }}; 7 | index index.html index.htm; 8 | } 9 | 10 | location /api/spin { 11 | uwsgi_pass 127.0.0.1:8008; 12 | include /etc/nginx/uwsgi_params; 13 | uwsgi_param UWSGI_SCRIPT /app/src/wsgi.py; 14 | # following config allow us to map /api/spin to /spin on uwsgi: 15 | uwsgi_param SCRIPT_NAME /api; # set SCRIPT_NAME to match subpath 16 | uwsgi_modifier1 30; # strips SCRIPT_NAME from PATH_INFO 17 | } 18 | 19 | # redirect server error pages to the static page /50x.html 20 | # 21 | error_page 500 502 503 504 /50x.html; 22 | location = /50x.html { 23 | root /usr/share/nginx/html; 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/templates/emperor.ini.j2: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | venv = /app/venv 3 | emperor = /app/src 4 | uid = nginx 5 | gid = nginx 6 | 7 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/templates/emperor.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=uWSGI Emperor 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/app/venv/bin/uwsgi --ini /app/emperor.ini 7 | # Requires systemd version 211 or newer 8 | RuntimeDirectory=uwsgi 9 | Restart=always 10 | KillSignal=SIGQUIT 11 | Type=notify 12 | StandardError=syslog 13 | NotifyAccess=all 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/templates/infra-demo.ini.j2: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | venv = /app/venv 3 | wsgi-file = /app/src/wsgi.py 4 | chdir = /app/src 5 | master = 1 6 | workers = 64 7 | threads = 1 8 | lazy-apps = 1 9 | wsgi-env-behaviour = holy 10 | enable-threads = 0 11 | http-auto-chunked = 1 12 | http-keepalive = 1 13 | uwsgi-socket = 127.0.0.1:8008 14 | harakiri = 120 15 | harakiri-verbose 16 | max-requests = 2048 17 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - app-selinux -------------------------------------------------------------------------------- /ansible/roles/app-AfterInstall/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for app-selinux -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/README.md: -------------------------------------------------------------------------------- 1 | App StartServer Role 2 | ===================== 3 | 4 | This is intended to prepare an application that requires some setup after installation for running. This is intended for use with AWS CodeDeploy as a StartServer hook. 5 | 6 | Requirements 7 | ------------ 8 | 9 | None 10 | 11 | Role Variables 12 | -------------- 13 | 14 | None 15 | 16 | Dependencies 17 | ------------ 18 | 19 | nginx 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Please see the example below: 25 | 26 | - name: Perform StartServer hook 27 | hosts: 127.0.0.1 28 | connection: local 29 | become: yes 30 | roles: 31 | - app-StartServer 32 | 33 | 34 | License 35 | ------- 36 | 37 | MIT 38 | 39 | Author Information 40 | ------------------ 41 | 42 | Richard Bullington-McGuire 43 | -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for app-StartServer 3 | -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for app-selinux -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for prepare-web-content 3 | 4 | - name: Start and enable emperor, as it should start off disabled 5 | service: 6 | name: emperor 7 | enabled: yes 8 | state: restarted 9 | 10 | - name: Start and enable nginx, as it should start off disabled 11 | service: 12 | name: nginx 13 | enabled: yes 14 | state: restarted 15 | 16 | -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - app-selinux -------------------------------------------------------------------------------- /ansible/roles/app-StartServer/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for app-selinux -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | Set up the AWS CloudWatch agent and capture some basic log files. 5 | 6 | Requirements 7 | ------------ 8 | 9 | A working AWS account. The config file included is suitable for CentOS 7 and an instance running on EC2. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | N/A 15 | 16 | Dependencies 17 | ------------ 18 | 19 | This role stands alone and has no dependencies. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Here is an example playbook: 25 | 26 | - hosts: servers 27 | become: yes 28 | roles: 29 | - cloudwatch-agent 30 | 31 | License 32 | ------- 33 | 34 | MIT 35 | 36 | Author Information 37 | ------------------ 38 | 39 | Copyright © 2019 Modus Create, Inc. 40 | 41 | * Madalin Borodi (@MadalinBorodi) 42 | * Richard Bullington-McGuire (@obscurerichard) 43 | 44 | 45 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for cloudwatch-agent 3 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "agent": { 3 | "metrics_collection_interval": 60, 4 | "run_as_user": "root" 5 | }, 6 | "logs": { 7 | "logs_collected": { 8 | "files": { 9 | "collect_list": [ 10 | { 11 | "file_path": "/var/log/boot.log", 12 | "log_group_name": "syslog", 13 | "log_stream_name": "{instance_id}" 14 | }, 15 | { 16 | "file_path": "/var/log/cron", 17 | "log_group_name": "syslog", 18 | "log_stream_name": "{instance_id}" 19 | }, 20 | { 21 | "file_path": "/var/log/messages", 22 | "log_group_name": "syslog", 23 | "log_stream_name": "{instance_id}" 24 | }, 25 | { 26 | "file_path": "/var/log/dmesg", 27 | "log_group_name": "syslog", 28 | "log_stream_name": "{instance_id}" 29 | }, 30 | { 31 | "file_path": "/var/log/secure", 32 | "log_group_name": "syslog", 33 | "log_stream_name": "{instance_id}" 34 | }, 35 | { 36 | "file_path": "/var/log/spooler", 37 | "log_group_name": "syslog", 38 | "log_stream_name": "{instance_id}" 39 | }, 40 | { 41 | "file_path": "/var/log/maillog", 42 | "log_group_name": "syslog", 43 | "log_stream_name": "{instance_id}" 44 | }, 45 | { 46 | "file_path": "/var/log/yum.log", 47 | "log_group_name": "package-updates", 48 | "log_stream_name": "{instance_id}" 49 | }, 50 | { 51 | "file_path": "/var/log/audit/audit.log", 52 | "log_group_name": "selinux", 53 | "log_stream_name": "{instance_id}" 54 | }, 55 | { 56 | "file_path": "/var/log/nginx/access.log", 57 | "log_group_name": "nginx-access", 58 | "log_stream_name": "{instance_id}" 59 | }, 60 | { 61 | "file_path": "/var/log/nginx/error.log", 62 | "log_group_name": "nginx-error", 63 | "log_stream_name": "{instance_id}" 64 | }, 65 | { 66 | "file_path": "/opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log", 67 | "log_group_name": "codedeploy", 68 | "log_stream_name": "{instance_id}" 69 | } 70 | ] 71 | } 72 | } 73 | }, 74 | "metrics": { 75 | "append_dimensions": { 76 | "AutoScalingGroupName": "${aws:AutoScalingGroupName}", 77 | "ImageId": "${aws:ImageId}", 78 | "InstanceId": "${aws:InstanceId}", 79 | "InstanceType": "${aws:InstanceType}" 80 | }, 81 | "metrics_collected": { 82 | "mem": { 83 | "measurement": [ 84 | "mem_used_percent" 85 | ], 86 | "metrics_collection_interval": 60 87 | }, 88 | "statsd": { 89 | "metrics_aggregation_interval": 60, 90 | "metrics_collection_interval": 10, 91 | "service_address": ":8125" 92 | }, 93 | "swap": { 94 | "measurement": [ 95 | "swap_used_percent" 96 | ], 97 | "metrics_collection_interval": 60 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for cloudwatch-agent 3 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Richard Bullington-McGuire (@obscurerichard) 3 | description: CloudWatch agent role 4 | company: Modus Create, Inc. 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Choose a valid license ID from https://spdx.org - some suggested licenses: 11 | # - BSD-3-Clause (default) 12 | # - MIT 13 | # - GPL-2.0-or-later 14 | # - GPL-3.0-only 15 | # - Apache-2.0 16 | # - CC-BY-4.0 17 | license: MIT 18 | 19 | min_ansible_version: 2.4 20 | 21 | platforms: 22 | - name: CentOs 23 | versions: 24 | - 7 25 | - name: RHEL 26 | versions: 27 | - 7 28 | 29 | galaxy_tags: [] 30 | # List tags for your role here, one per line. A tag is a keyword that describes 31 | # and categorizes the role. Users find roles by searching for tags. Be sure to 32 | # remove the '[]' above, if you add tags to this list. 33 | # 34 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 35 | # Maximum 20 tags per role. 36 | 37 | dependencies: [] 38 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 39 | # if you add dependencies to this list. 40 | 41 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for cloudwatch-agent 3 | 4 | - name: Ensure cloudwatch agent is present 5 | package: name={{item}} state=present 6 | with_items: 7 | - https://s3.amazonaws.com/amazoncloudwatch-agent/centos/amd64/latest/amazon-cloudwatch-agent.rpm 8 | tags: cloudwatch 9 | 10 | - name: Copy cloudwatch config file 11 | copy: 12 | src: config.json 13 | dest: /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json 14 | tags: cloudwatch 15 | 16 | - name: Enable cloudwatch-agent, as it should start off disabled 17 | service: 18 | name: amazon-cloudwatch-agent 19 | enabled: yes 20 | state: stopped 21 | tags: cloudwatch 22 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - cloudwatch-agent 6 | -------------------------------------------------------------------------------- /ansible/roles/cloudwatch-agent/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for cloudwatch-agent 3 | -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/README.md: -------------------------------------------------------------------------------- 1 | Extra CIS Remediation 2 | ===================== 3 | 4 | This takes care of remediating CIS issues that the Mindpoint role does not. 5 | 6 | Requirements 7 | ------------ 8 | 9 | None 10 | 11 | Role Variables 12 | -------------- 13 | 14 | None 15 | 16 | Dependencies 17 | ------------ 18 | 19 | This is intended for use with the MindPointGroup.RHEL7-CIS role, but it doesn't depend on it strictly. Use it before that role runs. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Please see the example below: 25 | 26 | - name: Harden Server 27 | hosts: 127.0.0.1 28 | connection: local 29 | become: yes 30 | roles: 31 | - extra-cis-remediation 32 | - MindPointGroup.RHEL7-CIS 33 | 34 | License 35 | ------- 36 | 37 | MIT 38 | 39 | Author Information 40 | ------------------ 41 | 42 | Richard Bullington-McGuire 43 | -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for extra-cis-remediation -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for extra-cis-remediation -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for extra-cis-remediation 3 | # Thanks Stack Overflow https://stackoverflow.com/a/34929776 4 | - name: 5.5 Ensure root login is restricted to system console (go further and prohibit all direct root logins) 5 | copy: 6 | content: "" 7 | dest: /etc/securetty 8 | force: yes 9 | group: root 10 | owner: root 11 | mode: 0600 12 | tags: 13 | - no_direct_root_logins 14 | - medium_severity 15 | - restrict_strategy 16 | - low_complexity 17 | - low_disruption 18 | - CCE-27294-8 19 | - NIST-800-53-IA-2(1) 20 | - NIST-800-171-3.1.1 21 | - NIST-800-171-3.1.6 22 | - level1 23 | - level2 24 | - patch 25 | - rule_5.5 26 | - 27 | 28 | -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - extra-cis-remediation -------------------------------------------------------------------------------- /ansible/roles/extra-cis-remediation/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for extra-cis-remediation -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/README.md: -------------------------------------------------------------------------------- 1 | Prepare CodeDeploy 2 | ================== 3 | 4 | Prepare an image for having AWS CodeDeploy installed on it with a minimum of runtime downloads. 5 | 6 | Requirements 7 | ------------ 8 | 9 | This is tested only on CentOS 7 currently. It might work on other distributions. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | None 15 | 16 | Dependencies 17 | ------------ 18 | 19 | None 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: prepare-codedeploy } 29 | 30 | License 31 | ------- 32 | 33 | MIT 34 | 35 | Author Information 36 | ------------------ 37 | 38 | Richard Bullington-McGuire 39 | -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for prepare-codedeploy -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for prepare-codedeploy -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 2.4 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # Provide a list of supported platforms, and for each platform a list of versions. 34 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 35 | # To view available platforms and versions (or releases), visit: 36 | # https://galaxy.ansible.com/api/v1/platforms/ 37 | # 38 | # platforms: 39 | # - name: Fedora 40 | # versions: 41 | # - all 42 | # - 25 43 | # - name: SomePlatform 44 | # versions: 45 | # - all 46 | # - 1.0 47 | # - 7 48 | # - 99.99 49 | 50 | galaxy_tags: [] 51 | # List tags for your role here, one per line. A tag is a keyword that describes 52 | # and categorizes the role. Users find roles by searching for tags. Be sure to 53 | # remove the '[]' above, if you add tags to this list. 54 | # 55 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 56 | # Maximum 20 tags per role. 57 | 58 | dependencies: [] 59 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 60 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for prepare-codedeploy 3 | 4 | 5 | - name: Ensure packages used by CodeDeploy installer are present 6 | package: name={{item}} state=present 7 | with_items: 8 | - python2-pip 9 | - ruby 10 | - git 11 | 12 | - name: Ensure awscli is installed 13 | pip: name=awscli state=present 14 | -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - prepare-codedeploy -------------------------------------------------------------------------------- /ansible/roles/prepare-codedeploy/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for prepare-codedeploy -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/README.md: -------------------------------------------------------------------------------- 1 | Prepare Web Content 2 | =================== 3 | 4 | This is intended to prepare a system running NGINX to host a web application. 5 | 6 | Requirements 7 | ------------ 8 | 9 | None 10 | 11 | Role Variables 12 | -------------- 13 | 14 | None 15 | 16 | Dependencies 17 | ------------ 18 | 19 | This depends on nginxinc.nginx. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Please see the example below: 25 | 26 | - name: Install Web Application 27 | hosts: 127.0.0.1 28 | connection: local 29 | become: yes 30 | roles: 31 | - nginxinc.nginx 32 | - prepare-web-content 33 | 34 | 35 | License 36 | ------- 37 | 38 | MIT 39 | 40 | Author Information 41 | ------------------ 42 | 43 | Richard Bullington-McGuire 44 | -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for prepare-web-content 3 | -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for prepare-web-content -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for prepare-web-content 3 | 4 | - name: install the 'Development tools' package group 5 | yum: 6 | name: "@Development tools" 7 | state: present 8 | 9 | # Strictly speaking, we don't need the selinux modules at this early stage, 10 | # but having them will help the app-AfterInstall playbook avoid delays. 11 | - name: Ensure firewall and selinux modules are present 12 | package: name={{item}} state=present 13 | with_items: 14 | - checkpolicy 15 | - firewalld 16 | - policycoreutils-python 17 | - policycoreutils 18 | - python-firewall 19 | 20 | - name: Open firewalld port for http 21 | firewalld: 22 | service: http 23 | zone: drop 24 | permanent: true 25 | state: enabled 26 | 27 | - name: Open firewalld port for http in public zone 28 | firewalld: 29 | service: http 30 | zone: public 31 | permanent: true 32 | state: enabled 33 | 34 | - name: Reload service firewalld 35 | service: 36 | name: firewalld 37 | state: reloaded 38 | 39 | - name: Stop and disable nginx, as it should start off disabled 40 | service: 41 | name: nginx 42 | enabled: no 43 | state: stopped 44 | 45 | -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - prepare-web-content -------------------------------------------------------------------------------- /ansible/roles/prepare-web-content/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for prepare-web-content -------------------------------------------------------------------------------- /ansible/roles/scan-gauntlt/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/ansible/roles/scan-gauntlt/README.md -------------------------------------------------------------------------------- /ansible/roles/scan-gauntlt/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | gauntlt_version: "1.0.8" 4 | build_dir: /app/build/ 5 | gauntlt_attacks: /app/gauntlt/*.attack 6 | output_file_html: /app/build/gauntlt-results.html 7 | -------------------------------------------------------------------------------- /ansible/roles/scan-gauntlt/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | galaxy_info: 5 | author: "rpigu-i" 6 | description: "Gauntlt installation role for EC2" 7 | company: "Modus Create" 8 | license: "license (MIT)" 9 | min_ansible_version: 1.2 10 | galaxy_tags: 11 | - security 12 | - gauntlt 13 | - DevSecOps 14 | - InfoSec 15 | -------------------------------------------------------------------------------- /ansible/roles/scan-gauntlt/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for scan-gauntlt 3 | 4 | 5 | - name: Ensure build directory exists 6 | file: 7 | path: "{{ build_dir }}" 8 | state: directory 9 | mode: 0775 10 | 11 | - name: Scan using gauntlt 12 | command: gauntlt -f html -o "{{ output_file_html }}" "{{ gauntlt_attacks }}" 13 | -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/README.md: -------------------------------------------------------------------------------- 1 | Scan OpenSCAP 2 | ========== 3 | 4 | Scan a machine with OpenSCAP and an XCCDF profile. 5 | 6 | Requirements 7 | ------------ 8 | 9 | This works on Red Hat-family systems that have openscap available. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | Example Playbook 20 | ---------------- 21 | 22 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 23 | 24 | - hosts: servers 25 | roles: 26 | - scan-openscap 27 | 28 | License 29 | ------- 30 | 31 | MIT 32 | 33 | Author Information 34 | ------------------ 35 | 36 | Richard Bullington-McGuire 37 | https://moduscreate.com/ 38 | -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for scan-oscap 3 | build_dir: /app/build 4 | output_file_html: /app/build/scan-xccdf-results.html 5 | output_file_xml: /app/build/scan-xccdf-results.xml 6 | # Oh no! The old C2S profile is no longer available! 7 | profile: standard 8 | xccdf_file: /usr/share/xml/scap/ssg/content/ssg-centos7-xccdf.xml 9 | -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for scan-oscap -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for scan-oscap 3 | - name: Install openscap packages 4 | package: name={{item}} state=present 5 | with_items: 6 | - openscap-scanner 7 | - scap-security-guide 8 | 9 | - name: Ensure build directory exists 10 | file: 11 | path: "{{ build_dir }}" 12 | state: directory 13 | mode: 0775 14 | 15 | - name: Scan with OpenSCAP 16 | shell: | 17 | set -euo pipefail 18 | cd {{ build_dir }} 19 | # This will have a non-zero exit if any of the scans fail, so do not fail immediately on that 20 | set +e 21 | oscap xccdf eval --profile {{ profile }} --results {{ output_file_xml }} {{ xccdf_file }} 22 | set -e 23 | oscap xccdf generate report {{ output_file_xml }} > {{ output_file_html }} 24 | args: 25 | executable: /bin/bash 26 | 27 | -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - scan-oscap -------------------------------------------------------------------------------- /ansible/roles/scan-openscap/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for extra-cis-remediation 3 | -------------------------------------------------------------------------------- /ansible/scan-gauntlt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 3 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 4 | 5 | - name: Scan Server with Gauntlt 6 | hosts: 127.0.0.1 7 | connection: local 8 | roles: 9 | - scan-gauntlt 10 | -------------------------------------------------------------------------------- /ansible/scan-openscap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Thanks https://www.tricksofthetrades.net/2017/10/02/ansible-local-playbooks/ for 3 | # the trick on installing locally using "hosts: 127.0.0.1" and "connection:local" 4 | 5 | - name: Scan Server with OpenSCAP 6 | hosts: 127.0.0.1 7 | connection: local 8 | become: yes 9 | roles: 10 | - scan-openscap 11 | -------------------------------------------------------------------------------- /application/50x.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 5 | 12 | 13 | 14 |

An error occurred.

15 |

Sorry, the page you are looking for is currently unavailable.
16 | Please try again later.

17 |

If you are the system administrator of this resource then you should check 18 | the error log for details.

19 |

Faithfully yours, nginx.

20 | 21 | 22 | -------------------------------------------------------------------------------- /application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | 3 | ADD . /usr/share/nginx/html/ 4 | -------------------------------------------------------------------------------- /application/README.txt: -------------------------------------------------------------------------------- 1 | Dimension by HTML5 UP 2 | html5up.net | @ajlkn 3 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 4 | 5 | 6 | This is Dimension, a fun little one-pager with modal-ized (is that a word?) "pages" 7 | and a cool depth effect (click on a menu item to see what I mean). Simple, fully 8 | responsive, and kitted out with all the usual pre-styled elements you'd expect. 9 | Hope you dig it :) 10 | 11 | Demo images* courtesy of Unsplash, a radtastic collection of CC0 (public domain) images 12 | you can use for pretty much whatever. 13 | 14 | (* = not included) 15 | 16 | AJ 17 | aj@lkn.io | @ajlkn 18 | 19 | 20 | Credits: 21 | 22 | Demo Images: 23 | Unsplash (unsplash.com) 24 | 25 | Icons: 26 | Font Awesome (fortawesome.github.com/Font-Awesome) 27 | 28 | Other: 29 | jQuery (jquery.com) 30 | Misc. Sass functions (@HugoGiraudel) 31 | Skel (skel.io) -------------------------------------------------------------------------------- /application/assets/css/ie9.css: -------------------------------------------------------------------------------- 1 | /* 2 | Dimension by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* BG */ 8 | 9 | #bg:before { 10 | background: rgba(19, 21, 25, 0.5); 11 | } 12 | 13 | /* Header */ 14 | 15 | #header .logo { 16 | margin: 0 auto; 17 | } 18 | 19 | #header .content { 20 | display: inline-block; 21 | } 22 | 23 | #header nav ul { 24 | display: inline-block; 25 | } 26 | 27 | #header nav ul li { 28 | display: inline-block; 29 | } 30 | 31 | /* Main */ 32 | 33 | #main article { 34 | margin: 0 auto; 35 | } -------------------------------------------------------------------------------- /application/assets/css/noscript.css: -------------------------------------------------------------------------------- 1 | /* 2 | Dimension by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Main */ 8 | 9 | #main article { 10 | opacity: 1; 11 | margin: 4rem 0 0 0; 12 | } -------------------------------------------------------------------------------- /application/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /application/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /application/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /application/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /application/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /application/assets/js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Dimension by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | (function($) { 8 | 9 | skel.breakpoints({ 10 | xlarge: '(max-width: 1680px)', 11 | large: '(max-width: 1280px)', 12 | medium: '(max-width: 980px)', 13 | small: '(max-width: 736px)', 14 | xsmall: '(max-width: 480px)', 15 | xxsmall: '(max-width: 360px)' 16 | }); 17 | 18 | $(function() { 19 | 20 | var $window = $(window), 21 | $body = $('body'), 22 | $wrapper = $('#wrapper'), 23 | $header = $('#header'), 24 | $footer = $('#footer'), 25 | $main = $('#main'), 26 | $main_articles = $main.children('article'); 27 | 28 | // Disable animations/transitions until the page has loaded. 29 | $body.addClass('is-loading'); 30 | 31 | $window.on('load', function() { 32 | window.setTimeout(function() { 33 | $body.removeClass('is-loading'); 34 | }, 100); 35 | }); 36 | 37 | // Fix: Placeholder polyfill. 38 | $('form').placeholder(); 39 | 40 | // Fix: Flexbox min-height bug on IE. 41 | if (skel.vars.IEVersion < 12) { 42 | 43 | var flexboxFixTimeoutId; 44 | 45 | $window.on('resize.flexbox-fix', function() { 46 | 47 | clearTimeout(flexboxFixTimeoutId); 48 | 49 | flexboxFixTimeoutId = setTimeout(function() { 50 | 51 | if ($wrapper.prop('scrollHeight') > $window.height()) 52 | $wrapper.css('height', 'auto'); 53 | else 54 | $wrapper.css('height', '100vh'); 55 | 56 | }, 250); 57 | 58 | }).triggerHandler('resize.flexbox-fix'); 59 | 60 | } 61 | 62 | // Nav. 63 | var $nav = $header.children('nav'), 64 | $nav_li = $nav.find('li'); 65 | 66 | // Add "middle" alignment classes if we're dealing with an even number of items. 67 | if ($nav_li.length % 2 == 0) { 68 | 69 | $nav.addClass('use-middle'); 70 | $nav_li.eq( ($nav_li.length / 2) ).addClass('is-middle'); 71 | 72 | } 73 | 74 | // Main. 75 | var delay = 325, 76 | locked = false; 77 | 78 | // Methods. 79 | $main._show = function(id, initial) { 80 | 81 | var $article = $main_articles.filter('#' + id); 82 | 83 | // No such article? Bail. 84 | if ($article.length == 0) 85 | return; 86 | 87 | // Handle lock. 88 | 89 | // Already locked? Speed through "show" steps w/o delays. 90 | if (locked || (typeof initial != 'undefined' && initial === true)) { 91 | 92 | // Mark as switching. 93 | $body.addClass('is-switching'); 94 | 95 | // Mark as visible. 96 | $body.addClass('is-article-visible'); 97 | 98 | // Deactivate all articles (just in case one's already active). 99 | $main_articles.removeClass('active'); 100 | 101 | // Hide header, footer. 102 | $header.hide(); 103 | $footer.hide(); 104 | 105 | // Show main, article. 106 | $main.show(); 107 | $article.show(); 108 | 109 | // Activate article. 110 | $article.addClass('active'); 111 | 112 | // Unlock. 113 | locked = false; 114 | 115 | // Unmark as switching. 116 | setTimeout(function() { 117 | $body.removeClass('is-switching'); 118 | }, (initial ? 1000 : 0)); 119 | 120 | return; 121 | 122 | } 123 | 124 | // Lock. 125 | locked = true; 126 | 127 | // Article already visible? Just swap articles. 128 | if ($body.hasClass('is-article-visible')) { 129 | 130 | // Deactivate current article. 131 | var $currentArticle = $main_articles.filter('.active'); 132 | 133 | $currentArticle.removeClass('active'); 134 | 135 | // Show article. 136 | setTimeout(function() { 137 | 138 | // Hide current article. 139 | $currentArticle.hide(); 140 | 141 | // Show article. 142 | $article.show(); 143 | 144 | // Activate article. 145 | setTimeout(function() { 146 | 147 | $article.addClass('active'); 148 | 149 | // Window stuff. 150 | $window 151 | .scrollTop(0) 152 | .triggerHandler('resize.flexbox-fix'); 153 | 154 | // Unlock. 155 | setTimeout(function() { 156 | locked = false; 157 | }, delay); 158 | 159 | }, 25); 160 | 161 | }, delay); 162 | 163 | } 164 | 165 | // Otherwise, handle as normal. 166 | else { 167 | 168 | // Mark as visible. 169 | $body 170 | .addClass('is-article-visible'); 171 | 172 | // Show article. 173 | setTimeout(function() { 174 | 175 | // Hide header, footer. 176 | $header.hide(); 177 | $footer.hide(); 178 | 179 | // Show main, article. 180 | $main.show(); 181 | $article.show(); 182 | 183 | // Activate article. 184 | setTimeout(function() { 185 | 186 | $article.addClass('active'); 187 | 188 | // Window stuff. 189 | $window 190 | .scrollTop(0) 191 | .triggerHandler('resize.flexbox-fix'); 192 | 193 | // Unlock. 194 | setTimeout(function() { 195 | locked = false; 196 | }, delay); 197 | 198 | }, 25); 199 | 200 | }, delay); 201 | 202 | } 203 | 204 | }; 205 | 206 | $main._hide = function(addState) { 207 | 208 | var $article = $main_articles.filter('.active'); 209 | 210 | // Article not visible? Bail. 211 | if (!$body.hasClass('is-article-visible')) 212 | return; 213 | 214 | // Add state? 215 | if (typeof addState != 'undefined' 216 | && addState === true) 217 | history.pushState(null, null, '#'); 218 | 219 | // Handle lock. 220 | 221 | // Already locked? Speed through "hide" steps w/o delays. 222 | if (locked) { 223 | 224 | // Mark as switching. 225 | $body.addClass('is-switching'); 226 | 227 | // Deactivate article. 228 | $article.removeClass('active'); 229 | 230 | // Hide article, main. 231 | $article.hide(); 232 | $main.hide(); 233 | 234 | // Show footer, header. 235 | $footer.show(); 236 | $header.show(); 237 | 238 | // Unmark as visible. 239 | $body.removeClass('is-article-visible'); 240 | 241 | // Unlock. 242 | locked = false; 243 | 244 | // Unmark as switching. 245 | $body.removeClass('is-switching'); 246 | 247 | // Window stuff. 248 | $window 249 | .scrollTop(0) 250 | .triggerHandler('resize.flexbox-fix'); 251 | 252 | return; 253 | 254 | } 255 | 256 | // Lock. 257 | locked = true; 258 | 259 | // Deactivate article. 260 | $article.removeClass('active'); 261 | 262 | // Hide article. 263 | setTimeout(function() { 264 | 265 | // Hide article, main. 266 | $article.hide(); 267 | $main.hide(); 268 | 269 | // Show footer, header. 270 | $footer.show(); 271 | $header.show(); 272 | 273 | // Unmark as visible. 274 | setTimeout(function() { 275 | 276 | $body.removeClass('is-article-visible'); 277 | 278 | // Window stuff. 279 | $window 280 | .scrollTop(0) 281 | .triggerHandler('resize.flexbox-fix'); 282 | 283 | // Unlock. 284 | setTimeout(function() { 285 | locked = false; 286 | }, delay); 287 | 288 | }, 25); 289 | 290 | }, delay); 291 | 292 | 293 | }; 294 | 295 | // Articles. 296 | $main_articles.each(function() { 297 | 298 | var $this = $(this); 299 | 300 | // Close. 301 | $('
Close
') 302 | .appendTo($this) 303 | .on('click', function() { 304 | location.hash = ''; 305 | }); 306 | 307 | // Prevent clicks from inside article from bubbling. 308 | $this.on('click', function(event) { 309 | event.stopPropagation(); 310 | }); 311 | 312 | }); 313 | 314 | // Events. 315 | $body.on('click', function(event) { 316 | 317 | // Article visible? Hide. 318 | if ($body.hasClass('is-article-visible')) 319 | $main._hide(true); 320 | 321 | }); 322 | 323 | $window.on('keyup', function(event) { 324 | 325 | switch (event.keyCode) { 326 | 327 | case 27: 328 | 329 | // Article visible? Hide. 330 | if ($body.hasClass('is-article-visible')) 331 | $main._hide(true); 332 | 333 | break; 334 | 335 | default: 336 | break; 337 | 338 | } 339 | 340 | }); 341 | 342 | $window.on('hashchange', function(event) { 343 | 344 | // Empty hash? 345 | if (location.hash == '' 346 | || location.hash == '#') { 347 | 348 | // Prevent default. 349 | event.preventDefault(); 350 | event.stopPropagation(); 351 | 352 | // Hide. 353 | $main._hide(); 354 | 355 | } 356 | 357 | // Otherwise, check for a matching article. 358 | else if ($main_articles.filter(location.hash).length > 0) { 359 | 360 | // Prevent default. 361 | event.preventDefault(); 362 | event.stopPropagation(); 363 | 364 | // Show article. 365 | $main._show(location.hash.substr(1)); 366 | 367 | } 368 | 369 | }); 370 | 371 | // Scroll restoration. 372 | // This prevents the page from scrolling back to the top on a hashchange. 373 | if ('scrollRestoration' in history) 374 | history.scrollRestoration = 'manual'; 375 | else { 376 | 377 | var oldScrollPos = 0, 378 | scrollPos = 0, 379 | $htmlbody = $('html,body'); 380 | 381 | $window 382 | .on('scroll', function() { 383 | 384 | oldScrollPos = scrollPos; 385 | scrollPos = $htmlbody.scrollTop(); 386 | 387 | }) 388 | .on('hashchange', function() { 389 | $window.scrollTop(oldScrollPos); 390 | }); 391 | 392 | } 393 | 394 | // Initialize. 395 | 396 | // Hide main, articles. 397 | $main.hide(); 398 | $main_articles.hide(); 399 | 400 | // Initial article. 401 | if (location.hash != '' 402 | && location.hash != '#') 403 | $window.on('load', function() { 404 | $main._show(location.hash.substr(1), true); 405 | }); 406 | 407 | }); 408 | 409 | })(jQuery); -------------------------------------------------------------------------------- /application/assets/js/skel.min.js: -------------------------------------------------------------------------------- 1 | /* skel.js v3.0.1 | (c) skel.io | MIT licensed */ 2 | var skel=function(){"use strict";var t={breakpointIds:null,events:{},isInit:!1,obj:{attachments:{},breakpoints:{},head:null,states:{}},sd:"/",state:null,stateHandlers:{},stateId:"",vars:{},DOMReady:null,indexOf:null,isArray:null,iterate:null,matchesMedia:null,extend:function(e,n){t.iterate(n,function(i){t.isArray(n[i])?(t.isArray(e[i])||(e[i]=[]),t.extend(e[i],n[i])):"object"==typeof n[i]?("object"!=typeof e[i]&&(e[i]={}),t.extend(e[i],n[i])):e[i]=n[i]})},newStyle:function(t){var e=document.createElement("style");return e.type="text/css",e.innerHTML=t,e},_canUse:null,canUse:function(e){t._canUse||(t._canUse=document.createElement("div"));var n=t._canUse.style,i=e.charAt(0).toUpperCase()+e.slice(1);return e in n||"Moz"+i in n||"Webkit"+i in n||"O"+i in n||"ms"+i in n},on:function(e,n){var i=e.split(/[\s]+/);return t.iterate(i,function(e){var a=i[e];if(t.isInit){if("init"==a)return void n();if("change"==a)n();else{var r=a.charAt(0);if("+"==r||"!"==r){var o=a.substring(1);if(o in t.obj.breakpoints)if("+"==r&&t.obj.breakpoints[o].active)n();else if("!"==r&&!t.obj.breakpoints[o].active)return void n()}}}t.events[a]||(t.events[a]=[]),t.events[a].push(n)}),t},trigger:function(e){return t.events[e]&&0!=t.events[e].length?(t.iterate(t.events[e],function(n){t.events[e][n]()}),t):void 0},breakpoint:function(e){return t.obj.breakpoints[e]},breakpoints:function(e){function n(t,e){this.name=this.id=t,this.media=e,this.active=!1,this.wasActive=!1}return n.prototype.matches=function(){return t.matchesMedia(this.media)},n.prototype.sync=function(){this.wasActive=this.active,this.active=this.matches()},t.iterate(e,function(i){t.obj.breakpoints[i]=new n(i,e[i])}),window.setTimeout(function(){t.poll()},0),t},addStateHandler:function(e,n){t.stateHandlers[e]=n},callStateHandler:function(e){var n=t.stateHandlers[e]();t.iterate(n,function(e){t.state.attachments.push(n[e])})},changeState:function(e){t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].sync()}),t.vars.lastStateId=t.stateId,t.stateId=e,t.breakpointIds=t.stateId===t.sd?[]:t.stateId.substring(1).split(t.sd),t.obj.states[t.stateId]?t.state=t.obj.states[t.stateId]:(t.obj.states[t.stateId]={attachments:[]},t.state=t.obj.states[t.stateId],t.iterate(t.stateHandlers,t.callStateHandler)),t.detachAll(t.state.attachments),t.attachAll(t.state.attachments),t.vars.stateId=t.stateId,t.vars.state=t.state,t.trigger("change"),t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].active?t.obj.breakpoints[e].wasActive||t.trigger("+"+e):t.obj.breakpoints[e].wasActive&&t.trigger("-"+e)})},generateStateConfig:function(e,n){var i={};return t.extend(i,e),t.iterate(t.breakpointIds,function(e){t.extend(i,n[t.breakpointIds[e]])}),i},getStateId:function(){var e="";return t.iterate(t.obj.breakpoints,function(n){var i=t.obj.breakpoints[n];i.matches()&&(e+=t.sd+i.id)}),e},poll:function(){var e="";e=t.getStateId(),""===e&&(e=t.sd),e!==t.stateId&&t.changeState(e)},_attach:null,attach:function(e){var n=t.obj.head,i=e.element;return i.parentNode&&i.parentNode.tagName?!1:(t._attach||(t._attach=n.firstChild),n.insertBefore(i,t._attach.nextSibling),e.permanent&&(t._attach=i),!0)},attachAll:function(e){var n=[];t.iterate(e,function(t){n[e[t].priority]||(n[e[t].priority]=[]),n[e[t].priority].push(e[t])}),n.reverse(),t.iterate(n,function(e){t.iterate(n[e],function(i){t.attach(n[e][i])})})},detach:function(t){var e=t.element;return t.permanent||!e.parentNode||e.parentNode&&!e.parentNode.tagName?!1:(e.parentNode.removeChild(e),!0)},detachAll:function(e){var n={};t.iterate(e,function(t){n[e[t].id]=!0}),t.iterate(t.obj.attachments,function(e){e in n||t.detach(t.obj.attachments[e])})},attachment:function(e){return e in t.obj.attachments?t.obj.attachments[e]:null},newAttachment:function(e,n,i,a){return t.obj.attachments[e]={id:e,element:n,priority:i,permanent:a}},init:function(){t.initMethods(),t.initVars(),t.initEvents(),t.obj.head=document.getElementsByTagName("head")[0],t.isInit=!0,t.trigger("init")},initEvents:function(){t.on("resize",function(){t.poll()}),t.on("orientationChange",function(){t.poll()}),t.DOMReady(function(){t.trigger("ready")}),window.onload&&t.on("load",window.onload),window.onload=function(){t.trigger("load")},window.onresize&&t.on("resize",window.onresize),window.onresize=function(){t.trigger("resize")},window.onorientationchange&&t.on("orientationChange",window.onorientationchange),window.onorientationchange=function(){t.trigger("orientationChange")}},initMethods:function(){document.addEventListener?!function(e,n){t.DOMReady=n()}("domready",function(){function t(t){for(r=1;t=n.shift();)t()}var e,n=[],i=document,a="DOMContentLoaded",r=/^loaded|^c/.test(i.readyState);return i.addEventListener(a,e=function(){i.removeEventListener(a,e),t()}),function(t){r?t():n.push(t)}}):!function(e,n){t.DOMReady=n()}("domready",function(t){function e(t){for(h=1;t=i.shift();)t()}var n,i=[],a=!1,r=document,o=r.documentElement,s=o.doScroll,c="DOMContentLoaded",d="addEventListener",u="onreadystatechange",l="readyState",f=s?/^loaded|^c/:/^loaded|c/,h=f.test(r[l]);return r[d]&&r[d](c,n=function(){r.removeEventListener(c,n,a),e()},a),s&&r.attachEvent(u,n=function(){/^c/.test(r[l])&&(r.detachEvent(u,n),e())}),t=s?function(e){self!=top?h?e():i.push(e):function(){try{o.doScroll("left")}catch(n){return setTimeout(function(){t(e)},50)}e()}()}:function(t){h?t():i.push(t)}}),Array.prototype.indexOf?t.indexOf=function(t,e){return t.indexOf(e)}:t.indexOf=function(t,e){if("string"==typeof t)return t.indexOf(e);var n,i,a=e?e:0;if(!this)throw new TypeError;if(i=this.length,0===i||a>=i)return-1;for(0>a&&(a=i-Math.abs(a)),n=a;i>n;n++)if(this[n]===t)return n;return-1},Array.isArray?t.isArray=function(t){return Array.isArray(t)}:t.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)},Object.keys?t.iterate=function(t,e){if(!t)return[];var n,i=Object.keys(t);for(n=0;i[n]&&e(i[n],t[i[n]])!==!1;n++);}:t.iterate=function(t,e){if(!t)return[];var n;for(n in t)if(Object.prototype.hasOwnProperty.call(t,n)&&e(n,t[n])===!1)break},window.matchMedia?t.matchesMedia=function(t){return""==t?!0:window.matchMedia(t).matches}:window.styleMedia||window.media?t.matchesMedia=function(t){if(""==t)return!0;var e=window.styleMedia||window.media;return e.matchMedium(t||"all")}:window.getComputedStyle?t.matchesMedia=function(t){if(""==t)return!0;var e=document.createElement("style"),n=document.getElementsByTagName("script")[0],i=null;e.type="text/css",e.id="matchmediajs-test",n.parentNode.insertBefore(e,n),i="getComputedStyle"in window&&window.getComputedStyle(e,null)||e.currentStyle;var a="@media "+t+"{ #matchmediajs-test { width: 1px; } }";return e.styleSheet?e.styleSheet.cssText=a:e.textContent=a,"1px"===i.width}:t.matchesMedia=function(t){if(""==t)return!0;var e,n,i,a,r={"min-width":null,"max-width":null},o=!1;for(i=t.split(/\s+and\s+/),e=0;er["max-width"]||null!==r["min-height"]&&cr["max-height"]?!1:!0},navigator.userAgent.match(/MSIE ([0-9]+)/)&&RegExp.$1<9&&(t.newStyle=function(t){var e=document.createElement("span");return e.innerHTML=' ",e})},initVars:function(){var e,n,i,a=navigator.userAgent;e="other",n=0,i=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(RegExp.$1),!1):void 0}),t.vars.browser=e,t.vars.browserVersion=n,e="other",n=0,i=[["ios",/([0-9_]+) like Mac OS X/,function(t){return t.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(t){return 0}],["wp",/Windows Phone ([0-9\.]+)/,null],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(t){return t.replace("_",".").replace("_","")}],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(i[2]?i[2](RegExp.$1):RegExp.$1),!1):void 0}),t.vars.os=e,t.vars.osVersion=n,t.vars.IEVersion="ie"==t.vars.browser?t.vars.browserVersion:99,t.vars.touch="wp"==t.vars.os?navigator.msMaxTouchPoints>0:!!("ontouchstart"in window),t.vars.mobile="wp"==t.vars.os||"android"==t.vars.os||"ios"==t.vars.os||"bb"==t.vars.os}};return t.init(),t}();!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.skel=e()}(this,function(){return skel}); 3 | -------------------------------------------------------------------------------- /application/assets/sass/base/_page.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Basic */ 8 | 9 | // MSIE: Required for IEMobile. 10 | @-ms-viewport { 11 | width: device-width; 12 | } 13 | 14 | // Ensures page width is always >=320px. 15 | @include breakpoint(xsmall) { 16 | html, body { 17 | min-width: 320px; 18 | } 19 | } 20 | 21 | body { 22 | background: _palette(bg); 23 | 24 | // Prevents animation/transition "flicker" on page load. 25 | // Automatically added/removed by js/main.js. 26 | &.is-loading, 27 | &.is-switching { 28 | *, *:before, *:after { 29 | @include vendor('animation', 'none !important'); 30 | @include vendor('transition', 'none !important'); 31 | @include vendor('transition-delay', 'none !important'); 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /application/assets/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Type */ 8 | 9 | html { 10 | font-size: 16pt; 11 | 12 | @include breakpoint(xlarge) { 13 | font-size: 12pt; 14 | } 15 | 16 | @include breakpoint(small) { 17 | font-size: 11pt; 18 | } 19 | 20 | @include breakpoint(xxsmall) { 21 | font-size: 10pt; 22 | } 23 | } 24 | 25 | body, input, select, textarea { 26 | color: _palette(fg); 27 | font-family: _font(family); 28 | font-weight: _font(weight); 29 | font-size: 1rem; 30 | line-height: 1.65; 31 | } 32 | 33 | a { 34 | @include vendor('transition', ( 35 | 'color #{_duration(transition)} ease-in-out', 36 | 'background-color #{_duration(transition)} ease-in-out', 37 | 'border-bottom-color #{_duration(transition)} ease-in-out' 38 | )); 39 | border-bottom: dotted 1px _palette(fg-light); 40 | text-decoration: none; 41 | color: inherit; 42 | 43 | &:hover { 44 | border-bottom-color: transparent; 45 | } 46 | } 47 | 48 | strong, b { 49 | color: _palette(fg-bold); 50 | font-weight: _font(weight-bold); 51 | } 52 | 53 | em, i { 54 | font-style: italic; 55 | } 56 | 57 | p { 58 | margin: 0 0 _size(element-margin) 0; 59 | } 60 | 61 | h1, h2, h3, h4, h5, h6 { 62 | color: _palette(fg-bold); 63 | font-weight: _font(weight-bold); 64 | line-height: 1.5; 65 | margin: 0 0 (_size(element-margin) * 0.5) 0; 66 | text-transform: uppercase; 67 | letter-spacing: _font(letter-spacing); 68 | 69 | a { 70 | color: inherit; 71 | text-decoration: none; 72 | } 73 | 74 | &.major { 75 | border-bottom: solid _size(border-width) _palette(border); 76 | width: -moz-max-content; 77 | width: -webkit-max-content; 78 | width: -ms-max-content; 79 | width: max-content; 80 | padding-bottom: 0.5rem; 81 | margin: 0 0 (_size(element-margin) * 1) 0; 82 | } 83 | } 84 | 85 | h1 { 86 | font-size: 2.25rem; 87 | line-height: 1.3; 88 | letter-spacing: _font(letter-spacing-heading); 89 | } 90 | 91 | h2 { 92 | font-size: 1.5rem; 93 | line-height: 1.4; 94 | letter-spacing: _font(letter-spacing-heading); 95 | } 96 | 97 | h3 { 98 | font-size: 1rem; 99 | } 100 | 101 | h4 { 102 | font-size: 0.8rem; 103 | } 104 | 105 | h5 { 106 | font-size: 0.7rem; 107 | } 108 | 109 | h6 { 110 | font-size: 0.6rem; 111 | } 112 | 113 | @include breakpoint(small) { 114 | h1 { 115 | font-size: 1.75rem; 116 | line-height: 1.4; 117 | } 118 | 119 | h2 { 120 | font-size: 1.25em; 121 | line-height: 1.5; 122 | } 123 | } 124 | 125 | sub { 126 | font-size: 0.8rem; 127 | position: relative; 128 | top: 0.5rem; 129 | } 130 | 131 | sup { 132 | font-size: 0.8rem; 133 | position: relative; 134 | top: -0.5rem; 135 | } 136 | 137 | blockquote { 138 | border-left: solid (_size(border-width) * 4) _palette(border); 139 | font-style: italic; 140 | margin: 0 0 _size(element-margin) 0; 141 | padding: (_size(element-margin) / 4) 0 (_size(element-margin) / 4) _size(element-margin); 142 | } 143 | 144 | code { 145 | background: _palette(border-bg); 146 | border-radius: _size(border-radius); 147 | font-family: _font(family-fixed); 148 | font-size: 0.9rem; 149 | margin: 0 0.25rem; 150 | padding: 0.25rem 0.65rem; 151 | } 152 | 153 | pre { 154 | -webkit-overflow-scrolling: touch; 155 | font-family: _font(family-fixed); 156 | font-size: 0.9rem; 157 | margin: 0 0 _size(element-margin) 0; 158 | 159 | code { 160 | display: block; 161 | line-height: 1.75; 162 | padding: 1rem 1.5rem; 163 | overflow-x: auto; 164 | } 165 | } 166 | 167 | hr { 168 | border: 0; 169 | border-bottom: solid _size(border-width) _palette(border); 170 | margin: (_size(element-margin) * 1.375) 0; 171 | } 172 | 173 | .align-left { 174 | text-align: left; 175 | } 176 | 177 | .align-center { 178 | text-align: center; 179 | } 180 | 181 | .align-right { 182 | text-align: right; 183 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_box.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Box */ 8 | 9 | .box { 10 | border-radius: _size(border-radius); 11 | border: solid _size(border-width) _palette(border); 12 | margin-bottom: _size(element-margin); 13 | padding: 1.5em; 14 | 15 | > :last-child, 16 | > :last-child > :last-child, 17 | > :last-child > :last-child > :last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | &.alt { 22 | border: 0; 23 | border-radius: 0; 24 | padding: 0; 25 | } 26 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_button.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Button */ 8 | 9 | input[type="submit"], 10 | input[type="reset"], 11 | input[type="button"], 12 | button, 13 | .button { 14 | @include vendor('appearance', 'none'); 15 | @include vendor('transition', 'background-color #{_duration(transition)} ease-in-out, color #{_duration(transition)} ease-in-out'); 16 | background-color: transparent; 17 | border-radius: _size(border-radius); 18 | border: 0; 19 | box-shadow: inset 0 0 0 _size(border-width) _palette(border); 20 | color: _palette(fg-bold) !important; 21 | cursor: pointer; 22 | display: inline-block; 23 | font-size: 0.8rem; 24 | font-weight: _font(weight); 25 | height: _size(element-height); 26 | letter-spacing: _font(letter-spacing); 27 | line-height: _size(element-height); 28 | outline: 0; 29 | padding: 0 1.25rem 0 (1.25rem + (_font(letter-spacing) * 0.5)); 30 | text-align: center; 31 | text-decoration: none; 32 | text-transform: uppercase; 33 | white-space: nowrap; 34 | 35 | &:hover { 36 | background-color: _palette(border-bg); 37 | } 38 | 39 | &:active { 40 | background-color: _palette(border-bg-alt); 41 | } 42 | 43 | &.icon { 44 | &:before { 45 | margin-right: 0.5em; 46 | } 47 | } 48 | 49 | &.fit { 50 | display: block; 51 | margin: 0 0 (_size(element-margin) * 0.5) 0; 52 | width: 100%; 53 | } 54 | 55 | &.special { 56 | background-color: _palette(fg-bold); 57 | color: _palette(bg) !important; 58 | font-weight: _font(weight-bold); 59 | 60 | &:hover { 61 | } 62 | 63 | &:active { 64 | } 65 | } 66 | 67 | &.disabled, 68 | &:disabled { 69 | @include vendor('pointer-events', 'none'); 70 | cursor: default; 71 | opacity: 0.25; 72 | } 73 | } 74 | 75 | input[type="submit"], 76 | input[type="reset"], 77 | input[type="button"], 78 | button { 79 | line-height: calc(#{_size(element-height)} - 2px); 80 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_form.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Form */ 8 | 9 | form { 10 | $gutter: _size(element-margin) * 0.75; 11 | 12 | margin: 0 0 (_size(element-margin) * 1.25) 0; 13 | 14 | .field { 15 | margin: 0 0 ($gutter * 1) 0; 16 | 17 | &.half { 18 | width: 50%; 19 | float: left; 20 | padding: 0 0 0 ($gutter * 1 * 0.5); 21 | 22 | &.first { 23 | padding: 0 ($gutter * 1 * 0.5) 0 0; 24 | } 25 | } 26 | } 27 | 28 | > .actions { 29 | margin: ($gutter * 1.25) 0 0 0 !important; 30 | } 31 | 32 | @include breakpoint(small) { 33 | .field { 34 | margin: 0 0 ($gutter * 0.75) 0; 35 | 36 | &.half { 37 | padding: 0 0 0 ($gutter * 0.75 * 0.5); 38 | 39 | &.first { 40 | padding: 0 ($gutter * 0.75 * 0.5) 0 0; 41 | } 42 | } 43 | } 44 | 45 | > .actions { 46 | margin: ($gutter * 1) 0 0 0 !important; 47 | } 48 | } 49 | 50 | @include breakpoint(xsmall) { 51 | .field { 52 | &.half { 53 | width: 100%; 54 | float: none; 55 | padding: 0; 56 | 57 | &.first { 58 | padding: 0; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | label { 66 | color: _palette(fg-bold); 67 | display: block; 68 | font-size: 0.8rem; 69 | font-weight: _font(weight); 70 | letter-spacing: _font(letter-spacing); 71 | line-height: 1.5; 72 | margin: 0 0 (_size(element-margin) * 0.5) 0; 73 | text-transform: uppercase; 74 | } 75 | 76 | input[type="text"], 77 | input[type="password"], 78 | input[type="email"], 79 | input[type="tel"], 80 | select, 81 | textarea { 82 | @include vendor('appearance', 'none'); 83 | @include vendor('transition', ( 84 | 'border-color #{_duration(transition)} ease-in-out', 85 | 'box-shadow #{_duration(transition)} ease-in-out', 86 | 'background-color #{_duration(transition)} ease-in-out' 87 | )); 88 | background: transparent; 89 | border-radius: _size(border-radius); 90 | border: solid _size(border-width) _palette(border); 91 | color: inherit; 92 | display: block; 93 | outline: 0; 94 | padding: 0 1rem; 95 | text-decoration: none; 96 | width: 100%; 97 | 98 | &:invalid { 99 | box-shadow: none; 100 | } 101 | 102 | &:focus { 103 | background: _palette(border-bg); 104 | border-color: _palette(fg-bold); 105 | box-shadow: 0 0 0 _size(border-width) _palette(fg-bold); 106 | } 107 | } 108 | 109 | select { 110 | option { 111 | background: _palette(bg); 112 | color: _palette(fg); 113 | } 114 | } 115 | 116 | .select-wrapper { 117 | @include icon; 118 | display: block; 119 | position: relative; 120 | 121 | &:before { 122 | color: _palette(border); 123 | content: '\f107'; 124 | display: block; 125 | height: _size(element-height); 126 | //line-height: _size(element-height); 127 | line-height: calc(#{_size(element-height)} + 0em); 128 | pointer-events: none; 129 | position: absolute; 130 | right: 0; 131 | text-align: center; 132 | top: 0; 133 | width: _size(element-height); 134 | } 135 | 136 | select::-ms-expand { 137 | display: none; 138 | } 139 | } 140 | 141 | input[type="text"], 142 | input[type="password"], 143 | input[type="email"], 144 | select { 145 | height: _size(element-height); 146 | } 147 | 148 | textarea { 149 | padding: 0.75rem 1rem; 150 | } 151 | 152 | input[type="checkbox"], 153 | input[type="radio"], { 154 | @include vendor('appearance', 'none'); 155 | display: block; 156 | float: left; 157 | margin-right: -2rem; 158 | opacity: 0; 159 | width: 1rem; 160 | z-index: -1; 161 | 162 | & + label { 163 | @include icon; 164 | @include vendor('user-select', 'none'); 165 | color: _palette(fg); 166 | cursor: pointer; 167 | display: inline-block; 168 | font-size: 0.8rem; 169 | font-weight: _font(weight); 170 | margin: 0 0 (_size(element-margin) * 0.25) 0; 171 | padding-left: (_size(element-height) * 0.6) + 1rem; 172 | padding-right: 0.75rem; 173 | position: relative; 174 | 175 | &:before { 176 | @include vendor('transition', ( 177 | 'border-color #{_duration(transition)} ease-in-out', 178 | 'box-shadow #{_duration(transition)} ease-in-out', 179 | 'background-color #{_duration(transition)} ease-in-out' 180 | )); 181 | border-radius: _size(border-radius); 182 | border: solid _size(border-width) _palette(border); 183 | content: ''; 184 | display: inline-block; 185 | height: (_size(element-height) * 0.6); 186 | left: 0; 187 | //line-height: (_size(element-height) * 0.575); 188 | line-height: calc(#{_size(element-height) * 0.575} + 0em); 189 | position: absolute; 190 | text-align: center; 191 | top: -0.125rem; 192 | width: (_size(element-height) * 0.6); 193 | } 194 | } 195 | 196 | &:checked + label { 197 | &:before { 198 | background: _palette(fg-bold) !important; 199 | border-color: _palette(fg-bold) !important; 200 | color: _palette(bg); 201 | content: '\f00c'; 202 | } 203 | } 204 | 205 | &:focus + label { 206 | &:before { 207 | background: _palette(border-bg); 208 | border-color: _palette(fg-bold); 209 | box-shadow: 0 0 0 _size(border-width) _palette(fg-bold); 210 | } 211 | } 212 | } 213 | 214 | input[type="checkbox"] { 215 | & + label { 216 | &:before { 217 | border-radius: _size(border-radius); 218 | } 219 | } 220 | } 221 | 222 | input[type="radio"] { 223 | & + label { 224 | &:before { 225 | border-radius: 100%; 226 | } 227 | } 228 | } 229 | 230 | ::-webkit-input-placeholder { 231 | color: _palette(fg-light) !important; 232 | opacity: 1.0; 233 | } 234 | 235 | :-moz-placeholder { 236 | color: _palette(fg-light) !important; 237 | opacity: 1.0; 238 | } 239 | 240 | ::-moz-placeholder { 241 | color: _palette(fg-light) !important; 242 | opacity: 1.0; 243 | } 244 | 245 | :-ms-input-placeholder { 246 | color: _palette(fg-light) !important; 247 | opacity: 1.0; 248 | } 249 | 250 | .formerize-placeholder { 251 | color: _palette(fg-light) !important; 252 | opacity: 1.0; 253 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_icon.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Icon */ 8 | 9 | .icon { 10 | @include icon; 11 | border-bottom: none; 12 | position: relative; 13 | 14 | > .label { 15 | display: none; 16 | } 17 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_image.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Image */ 8 | 9 | .image { 10 | border-radius: _size(border-radius); 11 | border: 0; 12 | display: inline-block; 13 | position: relative; 14 | 15 | &:before { 16 | @include vendor('pointer-events', 'none'); 17 | background-image: url('../../images/overlay.png'); 18 | background-color: _palette(bg-overlay); 19 | border-radius: _size(border-radius); 20 | content: ''; 21 | display: block; 22 | height: 100%; 23 | left: 0; 24 | opacity: 0.5; 25 | position: absolute; 26 | top: 0; 27 | width: 100%; 28 | } 29 | 30 | img { 31 | border-radius: _size(border-radius); 32 | display: block; 33 | } 34 | 35 | &.left, 36 | &.right { 37 | max-width: 40%; 38 | 39 | img { 40 | width: 100%; 41 | } 42 | } 43 | 44 | &.left { 45 | float: left; 46 | padding: 0 1.5em 1em 0; 47 | top: 0.25em; 48 | } 49 | 50 | &.right { 51 | float: right; 52 | padding: 0 0 1em 1.5em; 53 | top: 0.25em; 54 | } 55 | 56 | &.fit { 57 | display: block; 58 | margin: 0 0 _size(element-margin) 0; 59 | width: 100%; 60 | 61 | img { 62 | width: 100%; 63 | } 64 | } 65 | 66 | &.main { 67 | display: block; 68 | margin: (_size(element-margin) * 1.25) 0; 69 | width: 100%; 70 | 71 | img { 72 | width: 100%; 73 | } 74 | } 75 | 76 | @include breakpoint(small) { 77 | &.main { 78 | margin: (_size(element-margin) * 1) 0; 79 | } 80 | } 81 | 82 | @include breakpoint(xsmall) { 83 | &.main { 84 | margin: (_size(element-margin) * 0.75) 0; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_list.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* List */ 8 | 9 | ol { 10 | list-style: decimal; 11 | margin: 0 0 _size(element-margin) 0; 12 | padding-left: 1.25em; 13 | 14 | li { 15 | padding-left: 0.25em; 16 | } 17 | } 18 | 19 | ul { 20 | list-style: disc; 21 | margin: 0 0 _size(element-margin) 0; 22 | padding-left: 1em; 23 | 24 | li { 25 | padding-left: 0.5em; 26 | } 27 | 28 | &.alt { 29 | list-style: none; 30 | padding-left: 0; 31 | 32 | li { 33 | border-top: solid _size(border-width) _palette(border); 34 | padding: 0.5em 0; 35 | 36 | &:first-child { 37 | border-top: 0; 38 | padding-top: 0; 39 | } 40 | } 41 | } 42 | 43 | &.icons { 44 | cursor: default; 45 | list-style: none; 46 | padding-left: 0; 47 | 48 | li { 49 | display: inline-block; 50 | padding: 0 0.75em 0 0; 51 | 52 | &:last-child { 53 | padding-right: 0; 54 | } 55 | 56 | a { 57 | border-radius: 100%; 58 | box-shadow: inset 0 0 0 _size(border-width) _palette(border); 59 | display: inline-block; 60 | height: 2.25rem; 61 | line-height: 2.25rem; 62 | text-align: center; 63 | width: 2.25rem; 64 | 65 | &:hover { 66 | background-color: _palette(border-bg); 67 | } 68 | 69 | &:active { 70 | background-color: _palette(border-bg-alt); 71 | } 72 | } 73 | } 74 | } 75 | 76 | &.actions { 77 | cursor: default; 78 | list-style: none; 79 | padding-left: 0; 80 | 81 | li { 82 | display: inline-block; 83 | padding: 0 (_size(element-margin) * 0.5) 0 0; 84 | vertical-align: middle; 85 | 86 | &:last-child { 87 | padding-right: 0; 88 | } 89 | } 90 | 91 | &.small { 92 | li { 93 | padding: 0 (_size(element-margin) * 0.25) 0 0; 94 | } 95 | } 96 | 97 | &.vertical { 98 | li { 99 | display: block; 100 | padding: (_size(element-margin) * 0.5) 0 0 0; 101 | 102 | &:first-child { 103 | padding-top: 0; 104 | } 105 | 106 | > * { 107 | margin-bottom: 0; 108 | } 109 | } 110 | 111 | &.small { 112 | li { 113 | padding: (_size(element-margin) * 0.25) 0 0 0; 114 | 115 | &:first-child { 116 | padding-top: 0; 117 | } 118 | } 119 | } 120 | } 121 | 122 | &.fit { 123 | display: table; 124 | margin-left: (_size(element-margin) * -0.5); 125 | padding: 0; 126 | table-layout: fixed; 127 | width: calc(100% + #{(_size(element-margin) * 0.5)}); 128 | 129 | li { 130 | display: table-cell; 131 | padding: 0 0 0 (_size(element-margin) * 0.5); 132 | 133 | > * { 134 | margin-bottom: 0; 135 | } 136 | } 137 | 138 | &.small { 139 | margin-left: (_size(element-margin) * -0.25); 140 | width: calc(100% + #{(_size(element-margin) * 0.25)}); 141 | 142 | li { 143 | padding: 0 0 0 (_size(element-margin) * 0.25); 144 | } 145 | } 146 | } 147 | 148 | @include breakpoint(xsmall) { 149 | margin: 0 0 _size(element-margin) 0; 150 | 151 | li { 152 | padding: (_size(element-margin) * 0.5) 0 0 0; 153 | display: block; 154 | text-align: center; 155 | width: 100%; 156 | 157 | &:first-child { 158 | padding-top: 0; 159 | } 160 | 161 | > * { 162 | width: 100%; 163 | margin: 0 !important; 164 | 165 | &.icon { 166 | &:before { 167 | margin-left: -2em; 168 | } 169 | } 170 | } 171 | } 172 | 173 | &.small { 174 | li { 175 | padding: (_size(element-margin) * 0.25) 0 0 0; 176 | 177 | &:first-child { 178 | padding-top: 0; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | dl { 187 | margin: 0 0 _size(element-margin) 0; 188 | 189 | dt { 190 | display: block; 191 | font-weight: _font(weight-bold); 192 | margin: 0 0 (_size(element-margin) * 0.5) 0; 193 | } 194 | 195 | dd { 196 | margin-left: _size(element-margin); 197 | } 198 | } -------------------------------------------------------------------------------- /application/assets/sass/components/_table.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Table */ 8 | 9 | .table-wrapper { 10 | -webkit-overflow-scrolling: touch; 11 | overflow-x: auto; 12 | } 13 | 14 | table { 15 | margin: 0 0 _size(element-margin) 0; 16 | width: 100%; 17 | 18 | tbody { 19 | tr { 20 | border: solid _size(border-width) _palette(border); 21 | border-left: 0; 22 | border-right: 0; 23 | 24 | &:nth-child(2n + 1) { 25 | background-color: _palette(border-bg); 26 | } 27 | } 28 | } 29 | 30 | td { 31 | padding: 0.75em 0.75em; 32 | } 33 | 34 | th { 35 | color: _palette(fg-bold); 36 | font-size: 0.9em; 37 | font-weight: _font(weight-bold); 38 | padding: 0 0.75em 0.75em 0.75em; 39 | text-align: left; 40 | } 41 | 42 | thead { 43 | border-bottom: solid (_size(border-width) * 2) _palette(border); 44 | } 45 | 46 | tfoot { 47 | border-top: solid (_size(border-width) * 2) _palette(border); 48 | } 49 | 50 | &.alt { 51 | border-collapse: separate; 52 | 53 | tbody { 54 | tr { 55 | td { 56 | border: solid _size(border-width) _palette(border); 57 | border-left-width: 0; 58 | border-top-width: 0; 59 | 60 | &:first-child { 61 | border-left-width: _size(border-width); 62 | } 63 | } 64 | 65 | &:first-child { 66 | td { 67 | border-top-width: _size(border-width); 68 | } 69 | } 70 | } 71 | } 72 | 73 | thead { 74 | border-bottom: 0; 75 | } 76 | 77 | tfoot { 78 | border-top: 0; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /application/assets/sass/ie9.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/skel'; 5 | 6 | /* 7 | Dimension by HTML5 UP 8 | html5up.net | @ajlkn 9 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 10 | */ 11 | 12 | /* BG */ 13 | 14 | #bg { 15 | &:before { 16 | background: _palette(bg-overlay); 17 | } 18 | } 19 | 20 | /* Header */ 21 | 22 | #header { 23 | .logo { 24 | margin: 0 auto; 25 | } 26 | 27 | .content { 28 | display: inline-block; 29 | } 30 | 31 | nav { 32 | ul { 33 | display: inline-block; 34 | 35 | li { 36 | display: inline-block; 37 | } 38 | } 39 | } 40 | } 41 | 42 | /* Main */ 43 | 44 | #main { 45 | article { 46 | margin: 0 auto; 47 | } 48 | } -------------------------------------------------------------------------------- /application/assets/sass/layout/_bg.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* BG */ 8 | 9 | #bg { 10 | @include vendor('transform', 'scale(1.0)'); 11 | -webkit-backface-visibility: hidden; 12 | position: fixed; 13 | top: 0; 14 | left: 0; 15 | width: 100%; 16 | height: 100vh; 17 | z-index: 1; 18 | 19 | &:before, &:after { 20 | content: ''; 21 | display: block; 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | width: 100%; 26 | height: 100%; 27 | } 28 | 29 | &:before { 30 | @include vendor('transition', 'background-color #{_duration(bg)} ease-in-out'); 31 | @include vendor('transition-delay', '#{_duration(intro)}'); 32 | background-image: linear-gradient(to top, #{_palette(bg-overlay)}, #{_palette(bg-overlay)}), 33 | url('../../images/overlay.png'); 34 | background-size: auto, 35 | 256px 256px; 36 | background-position: center, 37 | center; 38 | background-repeat: no-repeat, 39 | repeat; 40 | z-index: 2; 41 | } 42 | 43 | &:after { 44 | @include vendor('transform', 'scale(1.125)'); 45 | @include vendor('transition', ( 46 | 'transform #{_duration(article)} ease-in-out', 47 | 'filter #{_duration(article)} ease-in-out' 48 | )); 49 | background-image: url('../../images/bg.jpg'); 50 | background-position: center; 51 | background-size: cover; 52 | background-repeat: no-repeat; 53 | z-index: 1; 54 | } 55 | 56 | body.is-article-visible & { 57 | &:after { 58 | @include vendor('transform', 'scale(1.0825)'); 59 | @include vendor('filter', 'blur(0.2rem)'); 60 | } 61 | } 62 | 63 | body.is-loading & { 64 | &:before { 65 | background-color: _palette(bg-alt); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /application/assets/sass/layout/_footer.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Footer */ 8 | 9 | #footer { 10 | @include vendor('transition', ( 11 | 'transform #{_duration(article)} ease-in-out', 12 | 'filter #{_duration(article)} ease-in-out', 13 | 'opacity #{_duration(article)} ease-in-out', 14 | )); 15 | width: 100%; 16 | max-width: 100%; 17 | margin-top: 2rem; 18 | text-align: center; 19 | 20 | .copyright { 21 | letter-spacing: _font(letter-spacing); 22 | font-size: 0.6rem; 23 | opacity: 0.75; 24 | margin-bottom: 0; 25 | text-transform: uppercase; 26 | } 27 | 28 | body.is-article-visible & { 29 | @include vendor('transform', 'scale(0.95)'); 30 | @include vendor('filter', 'blur(0.1rem)'); 31 | opacity: 0; 32 | } 33 | 34 | body.is-loading & { 35 | opacity: 0; 36 | } 37 | } -------------------------------------------------------------------------------- /application/assets/sass/layout/_header.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Header */ 8 | 9 | #header { 10 | @include vendor('display', 'flex'); 11 | @include vendor('flex-direction', 'column'); 12 | @include vendor('align-items', 'center'); 13 | @include vendor('transition', ( 14 | 'transform #{_duration(article)} ease-in-out', 15 | 'filter #{_duration(article)} ease-in-out', 16 | 'opacity #{_duration(article)} ease-in-out', 17 | )); 18 | background-image: -moz-radial-gradient(rgba(0,0,0,0.25) 25%, rgba(0,0,0,0) 55%); 19 | background-image: -webkit-radial-gradient(rgba(0,0,0,0.25) 25%, rgba(0,0,0,0) 55%); 20 | background-image: -ms-radial-gradient(rgba(0,0,0,0.25) 25%, rgba(0,0,0,0) 55%); 21 | background-image: radial-gradient(rgba(0,0,0,0.25) 25%, rgba(0,0,0,0) 55%); 22 | max-width: 100%; 23 | text-align: center; 24 | 25 | > * { 26 | @include vendor('transition', 'opacity #{_duration(article)} ease-in-out'); 27 | position: relative; 28 | margin-top: 3.5rem; 29 | 30 | &:before { 31 | content: ''; 32 | display: block; 33 | position: absolute; 34 | top: calc(-3.5rem - 1px); 35 | left: calc(50% - #{_size(border-width) * 1}); 36 | width: _size(border-width); 37 | height: calc(3.5rem + 1px); 38 | background: _palette(border); 39 | } 40 | } 41 | 42 | > :first-child { 43 | margin-top: 0; 44 | 45 | &:before { 46 | display: none; 47 | } 48 | } 49 | 50 | .logo { 51 | width: 5.5rem; 52 | height: 5.5rem; 53 | line-height: 5.5rem; 54 | border: solid _size(border-width) _palette(border); 55 | border-radius: 100%; 56 | 57 | .icon { 58 | &:before { 59 | font-size: 2rem; 60 | } 61 | } 62 | } 63 | 64 | .content { 65 | border-style: solid; 66 | border-color: _palette(border); 67 | border-top-width: _size(border-width); 68 | border-bottom-width: _size(border-width); 69 | max-width: 100%; 70 | 71 | .inner { 72 | @include vendor('transition', ( 73 | 'max-height #{_duration(intro)} ease', 74 | 'padding #{_duration(intro)} ease', 75 | 'opacity #{_duration(article)} ease-in-out' 76 | )); 77 | @include vendor('transition-delay', '0.25s'); 78 | padding: 3rem 2rem; 79 | max-height: 40rem; 80 | overflow: hidden; 81 | 82 | > :last-child { 83 | margin-bottom: 0; 84 | } 85 | } 86 | 87 | p { 88 | text-transform: uppercase; 89 | letter-spacing: _font(letter-spacing); 90 | font-size: 0.8rem; 91 | line-height: 2; 92 | } 93 | } 94 | 95 | nav { 96 | ul { 97 | @include vendor('display', 'flex'); 98 | margin-bottom: 0; 99 | list-style: none; 100 | padding-left: 0; 101 | border: solid _size(border-width) _palette(border); 102 | border-radius: _size(border-radius); 103 | 104 | li { 105 | padding-left: 0; 106 | border-left: solid _size(border-width) _palette(border); 107 | 108 | &:first-child { 109 | border-left: 0; 110 | } 111 | 112 | a { 113 | display: block; 114 | min-width: 7.5rem; 115 | height: 2.75rem; 116 | line-height: 2.75rem; 117 | padding: 0 1.25rem 0 (1.25rem + _font(letter-spacing)); 118 | text-transform: uppercase; 119 | letter-spacing: _font(letter-spacing); 120 | font-size: 0.8rem; 121 | border-bottom: 0; 122 | 123 | &:hover { 124 | background-color: _palette(border-bg); 125 | } 126 | 127 | &:active { 128 | background-color: _palette(border-bg-alt); 129 | } 130 | } 131 | } 132 | } 133 | 134 | &.use-middle { 135 | &:after { 136 | content: ''; 137 | display: block; 138 | position: absolute; 139 | top: 0; 140 | left: calc(50% - #{_size(border-width) * 1}); 141 | width: _size(border-width); 142 | height: 100%; 143 | background: _palette(border); 144 | } 145 | 146 | ul { 147 | li { 148 | &.is-middle { 149 | border-left: 0; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | body.is-article-visible & { 157 | @include vendor('transform', 'scale(0.95)'); 158 | @include vendor('filter', 'blur(0.1rem)'); 159 | opacity: 0; 160 | } 161 | 162 | body.is-loading & { 163 | > * { 164 | opacity: 0; 165 | } 166 | 167 | @include vendor('filter', 'blur(0.125rem)'); 168 | 169 | .content { 170 | .inner { 171 | max-height: 0; 172 | padding-top: 0; 173 | padding-bottom: 0; 174 | opacity: 0; 175 | } 176 | } 177 | } 178 | 179 | @include breakpoint(medium) { 180 | .content { 181 | p { 182 | br { 183 | display: none; 184 | } 185 | } 186 | } 187 | } 188 | 189 | @include breakpoint(small) { 190 | > * { 191 | margin-top: 2rem; 192 | 193 | &:before { 194 | top: calc(-2rem - 1px); 195 | height: calc(2rem + 1px); 196 | } 197 | } 198 | 199 | .logo { 200 | width: 4.75rem; 201 | height: 4.75rem; 202 | line-height: 4.75rem; 203 | 204 | .icon { 205 | &:before { 206 | font-size: 1.75rem; 207 | } 208 | } 209 | } 210 | 211 | .content { 212 | .inner { 213 | padding: 2.5rem 1rem; 214 | } 215 | 216 | p { 217 | line-height: 1.875; 218 | } 219 | } 220 | } 221 | 222 | @include breakpoint(xsmall) { 223 | padding: 1.5rem 0; 224 | 225 | .content { 226 | .inner { 227 | padding: 2.5rem 0; 228 | } 229 | } 230 | 231 | nav { 232 | ul { 233 | @include vendor('flex-direction', 'column'); 234 | min-width: 10rem; 235 | max-width: 100%; 236 | 237 | li { 238 | border-left: 0; 239 | border-top: solid _size(border-width) _palette(border); 240 | 241 | &:first-child { 242 | border-top: 0; 243 | } 244 | 245 | a { 246 | height: 3rem; 247 | line-height: 3rem; 248 | min-width: 0; 249 | width: 100%; 250 | } 251 | } 252 | } 253 | 254 | &.use-middle { 255 | &:after { 256 | display: none; 257 | } 258 | } 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /application/assets/sass/layout/_main.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Main */ 8 | 9 | #main { 10 | @include vendor('flex-grow', '1'); 11 | @include vendor('flex-shrink', '1'); 12 | @include vendor('display', 'flex'); 13 | @include vendor('align-items', 'center'); 14 | @include vendor('justify-content', 'center'); 15 | @include vendor('flex-direction', 'column'); 16 | position: relative; 17 | max-width: 100%; 18 | z-index: 3; 19 | 20 | article { 21 | @include vendor('transform', 'translateY(0.25rem)'); 22 | @include vendor('transition', ( 23 | 'opacity #{_duration(article)} ease-in-out', 24 | 'transform #{_duration(article)} ease-in-out' 25 | )); 26 | @include padding(2.5rem, 2.5rem, (2rem, 0, 1rem, 0)); 27 | position: relative; 28 | width: 40rem; 29 | max-width: 100%; 30 | background-color: transparentize(_palette(bg), 0.15); 31 | border-radius: _size(border-radius); 32 | opacity: 0; 33 | 34 | &.active { 35 | @include vendor('transform', 'translateY(0)'); 36 | opacity: 1; 37 | } 38 | 39 | .close { 40 | display: block; 41 | position: absolute; 42 | top: 0; 43 | right: 0; 44 | width: 4rem; 45 | height: 4rem; 46 | cursor: pointer; 47 | text-indent: 4rem; 48 | overflow: hidden; 49 | white-space: nowrap; 50 | 51 | &:before { 52 | @include vendor('transition', 'background-color #{_duration(transition)} ease-in-out'); 53 | content: ''; 54 | display: block; 55 | position: absolute; 56 | top: 0.75rem; 57 | left: 0.75rem; 58 | width: 2.5rem; 59 | height: 2.5rem; 60 | border-radius: 100%; 61 | background-position: center; 62 | background-image: svg-url(''); 63 | background-size: 20px 20px; 64 | background-repeat: no-repeat; 65 | } 66 | 67 | &:hover { 68 | &:before { 69 | background-color: _palette(border-bg); 70 | } 71 | } 72 | 73 | &:active { 74 | &:before { 75 | background-color: _palette(border-bg-alt); 76 | } 77 | } 78 | } 79 | } 80 | 81 | @include breakpoint(small) { 82 | article { 83 | @include padding(2rem, 2rem, (1.5rem, 0, 0.5rem, 0)); 84 | 85 | .close { 86 | &:before { 87 | top: 0.875rem; 88 | left: 0.875rem; 89 | width: 2.25rem; 90 | height: 2.25rem; 91 | background-size: 14px 14px; 92 | } 93 | } 94 | } 95 | } 96 | 97 | @include breakpoint(xsmall) { 98 | article { 99 | @include padding(2rem, 1.5rem, (1rem, 0, 0.5rem, 0)); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /application/assets/sass/layout/_wrapper.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Dimension by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Wrapper */ 8 | 9 | #wrapper { 10 | @include vendor('display', 'flex'); 11 | @include vendor('flex-direction', 'column'); 12 | @include vendor('align-items', 'center'); 13 | @include vendor('justify-content', 'space-between'); 14 | position: relative; 15 | min-height: 100vh; 16 | width: 100%; 17 | padding: 4rem 2rem; 18 | z-index: 3; 19 | 20 | &:before { 21 | content: ''; 22 | display: block; 23 | } 24 | 25 | @include breakpoint(xlarge) { 26 | padding: 3rem 2rem; 27 | } 28 | 29 | @include breakpoint(small) { 30 | padding: 2rem 1rem; 31 | } 32 | 33 | @include breakpoint(xsmall) { 34 | padding: 1rem; 35 | } 36 | } -------------------------------------------------------------------------------- /application/assets/sass/libs/_functions.scss: -------------------------------------------------------------------------------- 1 | /// Gets a duration value. 2 | /// @param {string} $keys Key(s). 3 | /// @return {string} Value. 4 | @function _duration($keys...) { 5 | @return val($duration, $keys...); 6 | } 7 | 8 | /// Gets a font value. 9 | /// @param {string} $keys Key(s). 10 | /// @return {string} Value. 11 | @function _font($keys...) { 12 | @return val($font, $keys...); 13 | } 14 | 15 | /// Gets a misc value. 16 | /// @param {string} $keys Key(s). 17 | /// @return {string} Value. 18 | @function _misc($keys...) { 19 | @return val($misc, $keys...); 20 | } 21 | 22 | /// Gets a palette value. 23 | /// @param {string} $keys Key(s). 24 | /// @return {string} Value. 25 | @function _palette($keys...) { 26 | @return val($palette, $keys...); 27 | } 28 | 29 | /// Gets a size value. 30 | /// @param {string} $keys Key(s). 31 | /// @return {string} Value. 32 | @function _size($keys...) { 33 | @return val($size, $keys...); 34 | } -------------------------------------------------------------------------------- /application/assets/sass/libs/_mixins.scss: -------------------------------------------------------------------------------- 1 | /// Makes an element's :before pseudoelement a FontAwesome icon. 2 | /// @param {string} $content Optional content value to use. 3 | /// @param {string} $where Optional pseudoelement to target (before or after). 4 | @mixin icon($content: false, $where: before) { 5 | 6 | text-decoration: none; 7 | 8 | &:#{$where} { 9 | 10 | @if $content { 11 | content: $content; 12 | } 13 | 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-font-smoothing: antialiased; 16 | font-family: FontAwesome; 17 | font-style: normal; 18 | font-weight: normal; 19 | text-transform: none !important; 20 | 21 | } 22 | 23 | } 24 | 25 | /// Applies padding to an element, taking the current element-margin value into account. 26 | /// @param {mixed} $tb Top/bottom padding. 27 | /// @param {mixed} $lr Left/right padding. 28 | /// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left) 29 | /// @param {bool} $important If true, adds !important. 30 | @mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) { 31 | 32 | @if $important { 33 | $important: '!important'; 34 | } 35 | 36 | $x: 0.1em; 37 | 38 | @if unit(_size(element-margin)) == 'rem' { 39 | $x: 0.1rem; 40 | } 41 | 42 | padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max($x, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important}; 43 | 44 | } 45 | 46 | /// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp). 47 | /// @param {string} $svg SVG data URL. 48 | /// @return {string} Encoded SVG data URL. 49 | @function svg-url($svg) { 50 | 51 | $svg: str-replace($svg, '"', '\''); 52 | $svg: str-replace($svg, '<', '%3C'); 53 | $svg: str-replace($svg, '>', '%3E'); 54 | $svg: str-replace($svg, '&', '%26'); 55 | $svg: str-replace($svg, '#', '%23'); 56 | $svg: str-replace($svg, '{', '%7B'); 57 | $svg: str-replace($svg, '}', '%7D'); 58 | $svg: str-replace($svg, ';', '%3B'); 59 | 60 | @return url("data:image/svg+xml;charset=utf8,#{$svg}"); 61 | 62 | } -------------------------------------------------------------------------------- /application/assets/sass/libs/_vars.scss: -------------------------------------------------------------------------------- 1 | // Misc. 2 | $misc: ( 3 | z-index-base: 10000 4 | ); 5 | 6 | // Duration. 7 | $duration: ( 8 | transition: 0.2s, 9 | bg: 2.5s, 10 | intro: 0.75s, 11 | article: 0.325s 12 | ); 13 | 14 | // Size. 15 | $size: ( 16 | border-radius: 4px, 17 | border-width: 1px, 18 | element-height: 2.75rem, 19 | element-margin: 2rem 20 | ); 21 | 22 | // Font. 23 | $font: ( 24 | family: ('Source Sans Pro', sans-serif), 25 | family-fixed: ('Courier New', monospace), 26 | weight: 300, 27 | weight-bold: 600, 28 | letter-spacing: 0.2rem, 29 | letter-spacing-heading: 0.5rem 30 | ); 31 | 32 | // Palette. 33 | $palette: ( 34 | bg: #1b1f22, 35 | bg-alt: #000000, 36 | bg-overlay: rgba(19,21,25,0.5), 37 | fg: #ffffff, 38 | fg-bold: #ffffff, 39 | fg-light: rgba(255,255,255,0.5), 40 | border: #ffffff, 41 | border-bg: rgba(255,255,255,0.075), 42 | border-bg-alt: rgba(255,255,255,0.175) 43 | ); -------------------------------------------------------------------------------- /application/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/skel'; 5 | @import 'font-awesome.min.css'; 6 | @import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300italic,600italic,300,600'); 7 | 8 | /* 9 | Dimension by HTML5 UP 10 | html5up.net | @ajlkn 11 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 12 | */ 13 | 14 | @include skel-breakpoints(( 15 | xlarge: '(max-width: 1680px)', 16 | large: '(max-width: 1280px)', 17 | medium: '(max-width: 980px)', 18 | small: '(max-width: 736px)', 19 | xsmall: '(max-width: 480px)', 20 | xxsmall: '(max-width: 360px)' 21 | )); 22 | 23 | @include skel-layout(( 24 | reset: 'full', 25 | boxModel: 'border' 26 | )); 27 | 28 | // Base. 29 | 30 | @import 'base/page'; 31 | @import 'base/typography'; 32 | 33 | // Component. 34 | 35 | @import 'components/form'; 36 | @import 'components/box'; 37 | @import 'components/icon'; 38 | @import 'components/image'; 39 | @import 'components/list'; 40 | @import 'components/table'; 41 | @import 'components/button'; 42 | 43 | // Layout. 44 | 45 | @import 'layout/bg'; 46 | @import 'layout/wrapper'; 47 | @import 'layout/header'; 48 | @import 'layout/main'; 49 | @import 'layout/footer'; -------------------------------------------------------------------------------- /application/assets/sass/noscript.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/skel'; 5 | 6 | /* 7 | Dimension by HTML5 UP 8 | html5up.net | @ajlkn 9 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 10 | */ 11 | 12 | /* Main */ 13 | 14 | #main { 15 | article { 16 | opacity: 1; 17 | margin: (_size(element-margin) * 2) 0 0 0; 18 | } 19 | } -------------------------------------------------------------------------------- /application/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/bg.jpg -------------------------------------------------------------------------------- /application/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/bg1.jpg -------------------------------------------------------------------------------- /application/images/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/bg2.jpg -------------------------------------------------------------------------------- /application/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/overlay.png -------------------------------------------------------------------------------- /application/images/pic01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/pic01.jpg -------------------------------------------------------------------------------- /application/images/pic02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/pic02.jpg -------------------------------------------------------------------------------- /application/images/pic03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/devops-infra-demo/ea197a8147c83dec3f396bf96c07fabd54a30b05/application/images/pic03.jpg -------------------------------------------------------------------------------- /bin/activate-rvm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Activate rvm 3 | # Source this to activate RVM 4 | 5 | # CodeDeploy has no HOME variable defined! 6 | HOME=${HOME:-/centos} 7 | RVM_SH=${RVM_SH:-$HOME/.rvm/scripts/rvm} 8 | RUBY_VERSION=${RUBY_VERSION:-2.6.3} 9 | 10 | # rvm hates the bash options -eu 11 | 12 | echo -n "Activating RVM. HOME=$HOME id:" 13 | id -a 14 | 15 | if [[ ! -f "$RVM_SH" ]]; then 16 | echo "Error: $0: RVM_SH $RVM_SH not found" 17 | exit 1 18 | fi 19 | set +eu 20 | #shellcheck disable=SC1091,SC1090 21 | . "$RVM_SH" 22 | rvm reload 23 | rvm install "$RUBY_VERSION" 24 | rvm alias create default ruby-"$RUBY_VERSION" 25 | rvm list 26 | rvm use "$RUBY_VERSION" --default 27 | # We don't reactivate -u because even doing a "cd" will invoke an rvm 28 | # function in .rvm/scripts/cd that bombs with: 29 | # .rvm/scripts/functions/environment: line 267: rvm_bash_nounset: unbound variable 30 | 31 | set -e 32 | ruby --version 33 | 34 | -------------------------------------------------------------------------------- /bin/ansible.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Run ansible 3 | # 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | 16 | # shellcheck disable=SC1090 17 | . "$DIR/common.sh" 18 | # shellcheck disable=SC1090 19 | . "$DIR/activate-rvm.sh" 20 | 21 | ensure_not_root 22 | 23 | cd "$DIR/../ansible" 24 | ansible-playbook -l localhost "$@" 25 | -------------------------------------------------------------------------------- /bin/build-codedeploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | 6 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 7 | ${DEBUG:-false} && set -vx 8 | # Credit to https://stackoverflow.com/a/17805088 9 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 10 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 11 | 12 | # Credit to http://stackoverflow.com/a/246128/424301 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | BASE_DIR="$DIR/.." 15 | BUILD_DIR="$BASE_DIR/build" 16 | ANSIBLE_DIR="$BASE_DIR/ansible" 17 | APPLICTION_DIR="$BASE_DIR/application" 18 | SRC_DIR="$BASE_DIR/src" 19 | GAUNTLT_DIR="$BASE_DIR/gauntlt" 20 | 21 | # Credit to http://stackoverflow.com/a/246128/424301 22 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 23 | BASE_DIR="$DIR/.." 24 | 25 | # shellcheck disable=SC1090 26 | . "$DIR/common.sh" 27 | #shellcheck disable=SC1090 28 | . "$BASE_DIR/env.sh" 29 | 30 | 31 | GIT_REV="$(git rev-parse --short HEAD)" 32 | BUILD_NUMBER=${BUILD_NUMBER:-0} 33 | BRANCH_PREFIX=${1:-master} 34 | ARCHIVE="codedeploy-$BRANCH_PREFIX-$BUILD_NUMBER-$GIT_REV.zip" 35 | CONTAINERNAME=infra-demo 36 | # Thanks https://stackoverflow.com/questions/33791069/quick-way-to-get-aws-account-number-from-the-cli-tools 37 | AWS_ACCOUNT_ID=$(get_aws_account_id) 38 | BUCKET="codedeploy-$AWS_ACCOUNT_ID" 39 | S3_URL="s3://$BUCKET/$ARCHIVE" 40 | 41 | echo "GIT_REV=$GIT_REV" 42 | echo "BRANCH_PREFIX=$BRANCH_PREFIX" 43 | echo "BUILD_NUMBER=$BUILD_NUMBER" 44 | echo "ARCHIVE=$ARCHIVE" 45 | echo "S3_URL=$S3_URL" 46 | 47 | 48 | if [[ -d "$BUILD_DIR" ]]; then 49 | rm -rf "$BUILD_DIR" 50 | fi 51 | mkdir -p "$BUILD_DIR/socket" 52 | 53 | echo Build docker container $CONTAINERNAME 54 | docker build -f=Dockerfile -t "$CONTAINERNAME" "$BASE_DIR" 55 | 56 | echo Create python virtual environment 57 | docker run \ 58 | --rm \ 59 | -v "$BASE_DIR:/src" \ 60 | "$CONTAINERNAME" \ 61 | /bin/bash -c \ 62 | "mkdir -p /src/build/venv ; \ 63 | cp -fa /app/venv/* /src/build/venv" 64 | 65 | SOURCES="$BASE_DIR/bin 66 | $ANSIBLE_DIR 67 | $APPLICTION_DIR 68 | $SRC_DIR 69 | $GAUNTLT_DIR 70 | $BASE_DIR/codedeploy/appspec.yml" 71 | echo "Copying sources into place" 72 | for src in $SOURCES; do 73 | echo cp -a "$src" "$BUILD_DIR" 74 | cp -a "$src" "$BUILD_DIR" 75 | done 76 | 77 | ( 78 | cd "$BUILD_DIR" 79 | zip -q -r "$ARCHIVE" \ 80 | appspec.yml \ 81 | bin \ 82 | ansible \ 83 | application \ 84 | gauntlt \ 85 | src \ 86 | venv \ 87 | socket 88 | ) 89 | 90 | echo Remove docker generated files 91 | docker run --rm -v "$BASE_DIR:/src" "$CONTAINERNAME" /bin/bash -c \ 92 | "rm -rf /src/venv" 93 | 94 | cd "$BUILD_DIR" 95 | aws s3 cp "$ARCHIVE" "$S3_URL" --quiet 96 | echo "CodeDeploy archive uploaded OK: $S3_URL" 97 | aws s3 ls "$S3_URL" 98 | -------------------------------------------------------------------------------- /bin/clean-workspace.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # clean workspace 3 | # 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$DIR/.." 16 | BUILD_DIR="$BASE_DIR/build" 17 | export BASE_DIR 18 | 19 | # shellcheck disable=SC1090 20 | . "$DIR/common.sh" 21 | 22 | clean_root_owned_docker_files 23 | cp "$BASE_DIR/env.sh.sample" "$BASE_DIR/env.sh" 24 | rm -rf "$BUILD_DIR" "$BASE_DIR/terraform/.terraform" 25 | mkdir "$BUILD_DIR" 26 | -------------------------------------------------------------------------------- /bin/codedeploy/AfterInstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # AfterInstall.sh 4 | # 5 | # AWS CodeDeploy After Install hook script 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | # Credit to http://stackoverflow.com/a/246128/424301 18 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | BASE_DIR="$DIR/../.." 20 | ANSIBLE_DIR="$BASE_DIR/ansible" 21 | 22 | # Invoke Ansible for final set up 23 | ansible-playbook -l localhost "$ANSIBLE_DIR/app-AfterInstall.yml" 24 | 25 | # Configure New Relic 26 | # TODO: move into Ansible playbook app-AfterInstall.yml 27 | NEWRELIC_CONFIG_DIR=/app 28 | VENV_DIR=/app/venv 29 | # Thanks Stack Overflow https://stackoverflow.com/a/9735663/424301 30 | EC2_AVAIL_ZONE=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) 31 | EC2_REGION="$(sed 's/[a-z]$//' <<<"$EC2_AVAIL_ZONE")" 32 | 33 | NEWRELIC_LICENSE_KEY=$(aws secretsmanager get-secret-value \ 34 | --region="$EC2_REGION" \ 35 | --secret-id newrelic_license \ 36 | --output text \ 37 | --query '[SecretString]') 38 | set +u 39 | #shellcheck disable=SC1090 40 | source ${VENV_DIR}/bin/activate 41 | set -u 42 | newrelic-admin generate-config "${NEWRELIC_LICENSE_KEY}" "${NEWRELIC_CONFIG_DIR}/newrelic.ini.orig" 43 | sed 's/^app_name =.*$/app_name = Spin/' "${NEWRELIC_CONFIG_DIR}/newrelic.ini.orig" > "${NEWRELIC_CONFIG_DIR}/newrelic.ini" 44 | 45 | echo /app directory: 46 | ls -laZ /app 47 | -------------------------------------------------------------------------------- /bin/codedeploy/ApplicationStart.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # ApplicationStart.sh 4 | # 5 | # AWS CodeDeploy Application Start hook script 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | # Credit to http://stackoverflow.com/a/246128/424301 18 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | BASE_DIR="$DIR/../.." 20 | ANSIBLE_DIR="$BASE_DIR/ansible" 21 | 22 | # Invoke Ansible for final set up 23 | ansible-playbook -l localhost "$ANSIBLE_DIR/app-StartServer.yml" 24 | -------------------------------------------------------------------------------- /bin/codedeploy/ApplicationStop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # ApplicationStop.sh 4 | # 5 | # AWS CodeDeploy Application Stop hook script 6 | # 7 | # Useful when running in Jenkins CI or other contexts where you have Docker 8 | # available. 9 | 10 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 11 | set -euo pipefail 12 | IFS=$'\n\t' 13 | 14 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 15 | ${DEBUG:-false} && set -vx 16 | # Credit to https://stackoverflow.com/a/17805088 17 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 18 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 19 | 20 | systemctl stop emperor 21 | systemctl disable emperor 22 | systemctl stop nginx 23 | systemctl disable nginx 24 | -------------------------------------------------------------------------------- /bin/codedeploy/BeforeInstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # BeforeInstall.sh 4 | # 5 | # AWS CodeDeploy Before Install hook script 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | rm -rf /app 18 | -------------------------------------------------------------------------------- /bin/codedeploy/ValidateService.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # ValidateService.sh 4 | # 5 | # AWS CodeDeploy Validate Service hook script 6 | 7 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 8 | set -euo pipefail 9 | IFS=$'\n\t' 10 | 11 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 12 | ${DEBUG:-false} && set -vx 13 | # Credit to https://stackoverflow.com/a/17805088 14 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 15 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 16 | 17 | GAUNTLT_RESULTS=/app/build/gauntlt-results.html 18 | # TODO: save this to S3 instead 19 | GAUNTLT_RESULTS_SAVE="/home/centos/$DEPLOYMENT_ID-gauntlt-results.html" 20 | 21 | check_every() { 22 | local delay=${1:-} 23 | local host="http://localhost/" 24 | # shellcheck disable=SC2048 25 | while ! curl -s -o /dev/null $host 26 | do 27 | sleep "$delay" 28 | echo "Sleeping $delay, $host was not reachable" 29 | done 30 | } 31 | 32 | echo "Checking web server availability" 33 | check_every 2 34 | 35 | echo "Scanning with openscap and gauntlt" 36 | mkdir -p /app/build /app/ansible/tmp 37 | cat < /dev/null > "$GAUNTLT_RESULTS" 38 | chown -R centos:centos "$GAUNTLT_RESULTS" /app/build /app/ansible/tmp 39 | chmod 755 "$GAUNTLT_RESULTS" /app/build /app/ansible/tmp 40 | 41 | set +e 42 | sudo -u centos HOME=/home/centos /app/bin/ansible.sh scan-openscap.yml scan-gauntlt.yml 43 | RETCODE=$? 44 | set -e 45 | cp "$GAUNTLT_RESULTS" "$GAUNTLT_RESULTS_SAVE" 46 | rm -rf /app/ansible/tmp /app/build 47 | exit "$RETCODE" 48 | -------------------------------------------------------------------------------- /bin/common-terraform.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # common-terraform.sh 3 | # 4 | # Functions and variables related to Terraform 5 | 6 | 7 | TF_VERSION=0.11.7 8 | # TF_DIR is from the perspective of the Terraform docker container 9 | TF_DIR="/app/terraform" 10 | 11 | TF_PLAN="$TF_DIR/tf.plan" 12 | ENV_FILE=$(get_env_tmpfile) 13 | 14 | function get_var_tmpfile() { 15 | # Emit a Terraform variables tempfile 16 | local TMPFILE 17 | local EXTRA=${1:-# no extras} 18 | mkdir -p "$BUILD_DIR" 19 | TMPFILE="$BUILD_DIR/extra.tfvars" 20 | echo "$EXTRA" > "$TMPFILE" 21 | echo "$TMPFILE" 22 | } 23 | 24 | VAR_FILE="$(get_var_tmpfile "${Extra_Variables:-}")" 25 | export VAR_FILE ENV_FILE TF_PLAN TF_VERSION 26 | 27 | function get_targets() { 28 | for target in ${Terraform_Targets:-}; do 29 | echo -n "-target=$target " 30 | done 31 | } 32 | 33 | function get_docker_landscape() { 34 | echo "docker run -i --rm alpine/landscape" 35 | } 36 | 37 | function get_docker_terraform { 38 | echo "docker run -i --rm 39 | ${USE_TTY} 40 | --env-file $ENV_FILE 41 | --mount type=bind,source=${BASE_DIR}/terraform,target=${TF_DIR} 42 | --mount type=bind,source=${BASE_DIR}/application,target=/app/application 43 | --mount type=bind,source=${BUILD_DIR},target=/app/build 44 | --mount type=bind,source=${HOME}/.aws,target=/root/.aws 45 | --mount type=bind,source=${HOME}/.ssh,target=/root/.ssh 46 | -w ${TF_DIR} 47 | hashicorp/terraform:${TF_VERSION}" 48 | } 49 | 50 | function init_terraform() { 51 | #shellcheck disable=SC2086,SC2046 52 | $DOCKER_TERRAFORM init \ 53 | -input="$INPUT_ENABLED" \ 54 | -backend-config bucket=tf-state.${PROJECT_NAME}.${AWS_DEFAULT_REGION}.$(get_aws_account_id) \ 55 | -backend-config dynamodb_table=TerraformStatelock-${PROJECT_NAME} \ 56 | -backend-config region=${AWS_DEFAULT_REGION} \ 57 | -backend-config encrypt=true 58 | # Generate an SSH keypair if none exists yet 59 | if [[ ! -f ~/.ssh/id_rsa.pub ]]; then 60 | #shellcheck disable=SC2174 61 | mkdir -p -m 0700 ~/.ssh 62 | ssh-keygen -t rsa -b 2048 -P '' -f ~/.ssh/id_rsa 63 | fi 64 | } 65 | -------------------------------------------------------------------------------- /bin/common.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # common.sh 3 | 4 | # Only use TTY for Docker if we detect one, otherwise 5 | # this will balk when run in Jenkins 6 | # Thanks https://stackoverflow.com/a/48230089 7 | declare USE_TTY 8 | test -t 1 && USE_TTY="-t" || USE_TTY="" 9 | 10 | declare INPUT_ENABLED 11 | test -t 1 && INPUT_ENABLED="true" || INPUT_ENABLED="false" 12 | 13 | export INPUT_ENABLED USE_TTY 14 | 15 | # https://gist.github.com/samkeen/4255e1c8620be643d692 16 | # Thanks to GitHub user samkeen 17 | function is_ec2() { 18 | set +e 19 | INTERFACE=$(curl --silent http://169.254.169.254/latest/meta-data/network/interfaces/macs/ --connect-timeout 1) 20 | set -e 21 | if [[ -n "$INTERFACE" ]]; then # we are running on EC2 22 | return 0 23 | fi 24 | return 1 25 | } 26 | 27 | function get_env_tmpfile() { 28 | # Clean up the env file for use in packer & terraform 29 | local TMPFILE 30 | TMPFILE="$(mktemp)" 31 | grep ^export "$DIR/../env.sh" | cut -c8- > "$TMPFILE" 32 | echo "$TMPFILE" 33 | } 34 | 35 | function get_aws_account_id() { 36 | aws sts get-caller-identity \ 37 | --query Arn \ 38 | --output text \ 39 | | cut -d: -f5 40 | } 41 | 42 | function clean_root_owned_docker_files { 43 | # Fix file permissions on Jenkins / Linux as 44 | # Docker makes a bunch of root-owned files as it works 45 | local TF_DIR=/app 46 | local BASE_DIR 47 | BASE_DIR="$(pwd)" 48 | if is_ec2; then 49 | docker run -i \ 50 | --mount type=bind,source="${BASE_DIR}",target="${TF_DIR}" \ 51 | -w "${TF_DIR}" \ 52 | --entrypoint /bin/sh \ 53 | busybox \ 54 | <<< "chown -R $(id -u):$(id -g) ${TF_DIR}" 55 | fi 56 | } 57 | 58 | function get_docker_packer { 59 | # This is going to leak a new tempfile every time 60 | # it is run, maybe we should chain the exit traps to 61 | # avoid this. https://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal 62 | local TMPFILE 63 | TMPFILE=$(get_env_tmpfile) 64 | if is_ec2; then 65 | PACKER_AWS_SUBNET_ID="$(curl --silent http://169.254.169.254/latest/meta-data/network/interfaces/macs/"$INTERFACE"/subnet-id)" 66 | PACKER_AWS_VPC_ID="$(curl --silent http://169.254.169.254/latest/meta-data/network/interfaces/macs/"$INTERFACE"/vpc-id)" 67 | fi 68 | 69 | echo "docker run -i 70 | ${USE_TTY} 71 | --env-file $TMPFILE 72 | -e PACKER_AWS_SUBNET_ID=$PACKER_AWS_SUBNET_ID 73 | -e PACKER_AWS_VPC_ID=$PACKER_AWS_VPC_ID 74 | --mount type=bind,source=${BASE_DIR},target=/app 75 | hashicorp/packer:light" 76 | } 77 | 78 | function get_docker_landscape() { 79 | echo "docker run -i --rm alpine/landscape" 80 | } 81 | 82 | function get_docker_shellcheck() { 83 | # See https://hub.docker.com/r/nlknguyen/alpine-shellcheck/ 84 | # and https://github.com/koalaman/shellcheck/issues/727 85 | echo "docker run --rm -i ${USE_TTY} -v $(pwd):/mnt koalaman/shellcheck" 86 | } 87 | 88 | function ensure_root () { 89 | # Thanks Unix Stack Exchange https://unix.stackexchange.com/a/389407 90 | if ((EUID != 0)); then 91 | echo >&2 "Error: script not running as root or with sudo! Exiting..." 92 | exit 1 93 | fi 94 | } 95 | 96 | function ensure_not_root () { 97 | # Thanks Unix Stack Exchange https://unix.stackexchange.com/a/389407 98 | if ((EUID == 0)); then 99 | echo >&2 "Error: do not run script as root or with sudo! Exiting..." 100 | exit 1 101 | fi 102 | } 103 | 104 | 105 | function quick_yum_install() { 106 | declare package 107 | package=${1?"You must specify a package to install"} 108 | local sudo_maybe='' 109 | ((EUID != 0)) && sudo_maybe='sudo' 110 | if ! rpm -q "$package" > /dev/null; then 111 | $sudo_maybe yum -y -q install "$package" 112 | else 113 | echo "$package already installed, skipping" >&2 114 | fi 115 | } 116 | 117 | function get_local_ipv4_addresses () { 118 | ip addr | \ 119 | grep 'inet ' | \ 120 | sed 's/ */ /g;s/\/[0-9]* / /g' | \ 121 | cut -d\ -f 3 122 | } 123 | 124 | function get_local_ipv6_addresses () { 125 | ip addr | \ 126 | grep 'inet6' | \ 127 | sed 's/ */ /g;s/\/[0-9]* / /g' | \ 128 | cut -d\ -f 3 129 | } 130 | -------------------------------------------------------------------------------- /bin/deploy-codedeploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | 6 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 7 | ${DEBUG:-false} && set -vx 8 | # Credit to https://stackoverflow.com/a/17805088 9 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 10 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 11 | 12 | # Credit to http://stackoverflow.com/a/246128/424301 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | BASE_DIR="$DIR/.." 15 | 16 | # shellcheck disable=SC1090 17 | . "$DIR/common.sh" 18 | #shellcheck disable=SC1090 19 | . "$BASE_DIR/env.sh" 20 | 21 | BUILD_NUMBER=${BUILD_NUMBER:-0} 22 | BUCKET="codedeploy-$(get_aws_account_id)" 23 | PARAM=${1:-} 24 | BRANCH_PREFIX=${2:-master} 25 | APP_NAME=${3:-tf-infra-demo-app} 26 | DEPLOYMENT_GROUP_NAME=${4:-dev} 27 | 28 | GIT_REV="$(git rev-parse --short HEAD)" 29 | ARCHIVE="codedeploy-$BRANCH_PREFIX-$BUILD_NUMBER-$GIT_REV.zip" 30 | # Thanks https://stackoverflow.com/questions/33791069/quick-way-to-get-aws-account-number-from-the-cli-tools 31 | S3_URL="s3://$BUCKET/$ARCHIVE" 32 | 33 | # Thanks https://stackoverflow.com/questions/33791069/quick-way-to-get-aws-account-number-from-the-cli-tools 34 | AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account') 35 | BUCKET="codedeploy-$AWS_ACCOUNT_ID" 36 | 37 | case $PARAM in 38 | s3[:]//[a-z0-9]*) 39 | S3_URL="$PARAM" 40 | ARCHIVE=$(cut -d/ -f 3- <<<"$S3_URL") 41 | BUCKET=$(cut -d/ -f 2- <<<"$S3_URL") 42 | ;; 43 | current) 44 | echo "Using current CodeDeploy build: $S3_URL" 45 | ;; 46 | *) 47 | echo "ERROR: Unknown format for $PARAM, exiting" 48 | exit 1 49 | ;; 50 | esac 51 | 52 | 53 | S3_SHORTHAND="bundleType=zip,bucket=$BUCKET,key=$ARCHIVE" 54 | 55 | echo "BRANCH_PREFIX=$BRANCH_PREFIX" 56 | echo "BUILD_NUMBER=$BUILD_NUMBER" 57 | echo "ARCHIVE=$ARCHIVE" 58 | echo "S3_URL=$S3_URL" 59 | echo "S3_SHORTHAND=$S3_SHORTHAND" 60 | 61 | 62 | DEPLOYMENT_ID=$(aws deploy create-deployment \ 63 | --region "$AWS_DEFAULT_REGION" \ 64 | --output text \ 65 | --query '[deploymentId]' \ 66 | --application-name "$APP_NAME" \ 67 | --deployment-group-name "$DEPLOYMENT_GROUP_NAME" \ 68 | --description "deployment initiated by deploy-codedeploy.sh" \ 69 | --no-ignore-application-stop-failures \ 70 | --s3-location "$S3_SHORTHAND") 71 | echo "CodeDeploy: deployment started $DEPLOYMENT_ID" 72 | echo "CodeDeploy: see https://console.aws.amazon.com/codesuite/codedeploy/deployments/$DEPLOYMENT_ID" 73 | echo "CodeDeploy: waiting for deployment $DEPLOYMENT_ID to complete..." 74 | aws deploy wait deployment-successful --deployment-id "$DEPLOYMENT_ID" 75 | -------------------------------------------------------------------------------- /bin/install-ansible.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Install Ansible 3 | 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | IFS=$'\n\t' 7 | 8 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 9 | ${DEBUG:-false} && set -vx 10 | # Credit to https://stackoverflow.com/a/17805088 11 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 12 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 13 | 14 | # Credit to http://stackoverflow.com/a/246128/424301 15 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 16 | #shellcheck disable=SC1090 17 | . "$DIR/common.sh" 18 | 19 | ensure_not_root 20 | 21 | quick_yum_install epel-release 22 | quick_yum_install ansible 23 | quick_yum_install git 24 | ansible-galaxy install -r /app/ansible/requirements.yml 25 | -------------------------------------------------------------------------------- /bin/install-gauntlt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Install gauntlt using rvm 3 | 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | IFS=$'\n\t' 7 | 8 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 9 | ${DEBUG:-false} && set -vx 10 | # Credit to https://stackoverflow.com/a/17805088 11 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 12 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 13 | 14 | # Credit to http://stackoverflow.com/a/246128/424301 15 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 16 | #shellcheck disable=SC1090 17 | . "$DIR/common.sh" 18 | 19 | ensure_not_root 20 | 21 | RUBY_VERSION=2.6.3 22 | export RUBY_VERSION 23 | RVM_SH="$HOME/.rvm/scripts/rvm" 24 | 25 | PACKAGES='nmap 26 | ruby-devel 27 | autoconf 28 | automake 29 | bison 30 | gcc-c++ 31 | libffi-devel 32 | libtool 33 | patch 34 | readline-devel 35 | sqlite-devel 36 | zlib-devel 37 | glibc-headers 38 | glibc-devel 39 | openssl-devel 40 | requirements_centos_libs_install 41 | patch 42 | autoconf 43 | automake 44 | bison 45 | gcc-c++ 46 | libffi-devel 47 | libtool 48 | patch 49 | readline-devel 50 | sqlite-devel 51 | zlib-devel 52 | glibc-headers 53 | glibc-devel 54 | openssl-devel' 55 | 56 | #shellcheck disable=SC2086 57 | sudo yum -q install -y $PACKAGES 58 | 59 | if [[ ! -f "$RVM_SH" ]]; then 60 | curl -sSL https://rvm.io/mpapis.asc | gpg2 --import - 61 | curl -sSL https://rvm.io/pkuczynski.asc | gpg2 --import - 62 | curl -L get.rvm.io | bash -s stable 63 | # rvm hates the bash options -eu 64 | set +eu 65 | #shellcheck disable=SC1091,SC1090 66 | . "$RVM_SH" 67 | rvm reload 68 | rvm requirements run 69 | set -eu 70 | else 71 | echo "rvm already installed" >&2 72 | fi 73 | 74 | #shellcheck disable=SC1090 75 | . "$DIR/activate-rvm.sh" 76 | 77 | if ! (gem list gauntlt | grep gauntlt > /dev/null); then 78 | echo 'gem: --no-rdoc --no-ri' > ~/.gemrc 79 | gem install gauntlt syntax 80 | fi 81 | -------------------------------------------------------------------------------- /bin/jmeter.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | 6 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 7 | ${DEBUG:-false} && set -vx 8 | # Credit to https://stackoverflow.com/a/17805088 9 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 10 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 11 | 12 | # Credit to http://stackoverflow.com/a/246128/424301 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | BASE_DIR="$DIR/.." 15 | BUILD_DIR="$BASE_DIR/build" 16 | export BUILD_DIR 17 | 18 | mkdir -p "$BUILD_DIR" 19 | docker run -t -v "$BASE_DIR:/repo" justb4/jmeter -n -t /repo/jmeter/api-spin.jmx -l /repo/build/jmeter.jtl "$@" 20 | -------------------------------------------------------------------------------- /bin/pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Run packer to create machine imaages 3 | # 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$DIR/.." 16 | export BASE_DIR 17 | 18 | # shellcheck disable=SC1090 19 | . "$DIR/common.sh" 20 | 21 | function finish() { 22 | pwd 23 | ls -l "$BASE_DIR" "$BASE_DIR/build" build 24 | } 25 | 26 | trap finish EXIT 27 | 28 | DOCKER_PACKER=$(get_docker_packer) 29 | $DOCKER_PACKER validate app/packer/machines/web-server.json 30 | $DOCKER_PACKER build app/packer/machines/web-server.json 31 | find "$BASE_DIR" -name '*.html' | grep -E 'gauntlt|scan' 32 | -------------------------------------------------------------------------------- /bin/prep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Prepare a clean environment 3 | 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$DIR/.." 16 | 17 | # shellcheck disable=SC1090 18 | . "$DIR/common.sh" 19 | 20 | cd "$BASE_DIR" 21 | clean_root_owned_docker_files 22 | git clean -fdx 23 | cp env.sh.sample env.sh 24 | rm -rf build 25 | mkdir -p build 26 | -------------------------------------------------------------------------------- /bin/rotate-asg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | IFS=$'\n\t' 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$DIR/.." 16 | 17 | # shellcheck disable=SC1090 18 | . "$DIR/common.sh" 19 | #shellcheck disable=SC1090 20 | . "$BASE_DIR/env.sh" 21 | 22 | asg_name=${1:-} 23 | 24 | if [[ -z "$asg_name" ]]; then 25 | echo "You must specify an Auto Scaling Group name. Existing ASGs are:" 26 | #shellcheck disable=SC2016 27 | aws autoscaling describe-auto-scaling-groups \ 28 | --query 'AutoScalingGroups[].Instances[?contains(LifecycleState,`InService`)].InstanceId' \ 29 | --query "AutoScalingGroups[].AutoScalingGroupName" \ 30 | --output table 31 | exit 1 32 | fi 33 | 34 | 35 | # Count the number of instances in service 36 | function num_in_service() { 37 | local inservice 38 | local count 39 | inservice=${1?You must specify a tab-separated list of instance ids for num_inservice} 40 | count="$(echo "$inservice" | sed -e 's/ */ /g' | tr ' ' '\n' | wc -l)" 41 | echo "$count" 42 | } 43 | 44 | # Wait for new instances to become healthy 45 | function num_in_service_less_than() { 46 | local inservice 47 | local target 48 | local count 49 | inservice=${1?You must specify a tab-separated list of instance ids for num_inservice} 50 | target=${2?You must specify a target for num_in_service_less_than} 51 | #shellcheck disable=SC2016 52 | count="$(num_in_service "$inservice")" 53 | if [[ "$count" -lt "$target" ]]; then 54 | return 0 55 | else 56 | return 1 57 | fi 58 | } 59 | function get_asg_instances() { 60 | local asg_name 61 | asg_name=${1?You must specify an asg_name} 62 | #shellcheck disable=SC2016 63 | aws autoscaling describe-auto-scaling-groups \ 64 | --auto-scaling-group-name "$asg_name" \ 65 | --query 'AutoScalingGroups[].Instances[?contains(LifecycleState,`InService`)].InstanceId' \ 66 | --output text 67 | } 68 | 69 | function finish() { 70 | aws autoscaling resume-processes \ 71 | --auto-scaling-group-name "$asg_name" \ 72 | --scaling-processes AlarmNotification ReplaceUnhealthy HealthCheck AZRebalance ScheduledActions 73 | } 74 | 75 | trap finish EXIT 76 | 77 | original_asg_instances="$(get_asg_instances "$asg_name")" 78 | 79 | count="$(num_in_service "$original_asg_instances")" 80 | echo "$count instances running - $original_asg_instances" 81 | 82 | asg_DesiredCapacity="$(aws autoscaling describe-auto-scaling-groups \ 83 | --auto-scaling-group-name "$asg_name" \ 84 | --query 'AutoScalingGroups[].DesiredCapacity' \ 85 | --output text)" 86 | 87 | asg_MaxSize=$(aws autoscaling describe-auto-scaling-groups \ 88 | --auto-scaling-group-name "$asg_name" \ 89 | --query 'AutoScalingGroups[].MaxSize' \ 90 | --output text) 91 | 92 | asg_NewCapacity=$((asg_DesiredCapacity * 2)) 93 | 94 | echo "Current desired capacity: $asg_DesiredCapacity" 95 | echo "Current max size: $asg_MaxSize" 96 | echo "New capacity: $asg_NewCapacity" 97 | 98 | aws autoscaling suspend-processes \ 99 | --auto-scaling-group-name "$asg_name" \ 100 | --scaling-processes AlarmNotification ReplaceUnhealthy HealthCheck AZRebalance ScheduledActions 101 | 102 | aws autoscaling update-auto-scaling-group \ 103 | --auto-scaling-group-name "$asg_name" \ 104 | --max-size "$asg_NewCapacity" \ 105 | --desired-capacity "$asg_NewCapacity" \ 106 | --output table 107 | 108 | # Wait until new instances spin up 109 | echo -n "Waiting until new instances are in service" 110 | current_asg_instances="$(get_asg_instances "$asg_name")" 111 | while num_in_service_less_than "$current_asg_instances" "$asg_NewCapacity"; do 112 | sleep 5 113 | echo -n "." 114 | current_asg_instances="$(get_asg_instances "$asg_name")" 115 | done 116 | echo "" 117 | 118 | # Terminate old instances explicitly 119 | echo "Terminating old instances $original_asg_instances" 120 | for instance in $original_asg_instances; do 121 | aws autoscaling terminate-instance-in-auto-scaling-group \ 122 | --instance-id "$instance" \ 123 | --should-decrement-desired-capacity \ 124 | --output table 125 | done 126 | 127 | aws autoscaling update-auto-scaling-group \ 128 | --auto-scaling-group-name "$asg_name" \ 129 | --max-size "$asg_MaxSize" \ 130 | --output table 131 | 132 | # Wait for old instances to complete termination 133 | for instance in $original_asg_instances; do 134 | echo "" 135 | echo -n "Waiting for $instance to terminate" 136 | while aws ec2 describe-instances \ 137 | --instance-id "$instance" \ 138 | --query 'Reservations[*].Instances[*].State.Name' \ 139 | --output text \ 140 | | grep -v '^terminated$' >/dev/null 141 | do 142 | echo -n "." 143 | sleep 5 144 | done 145 | done 146 | echo "" 147 | 148 | echo "Rotate complete. New ASG instances: $(get_asg_instances "$asg_name")" 149 | -------------------------------------------------------------------------------- /bin/scan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Install openscap and run a security scan 3 | 4 | set -euo pipefail 5 | 6 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 7 | ${DEBUG:-false} && set -vx 8 | # Credit to https://stackoverflow.com/a/17805088 9 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 10 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 11 | 12 | sudo yum -q install -y openscap-scanner scap-security-guide 13 | 14 | mkdir -p build 15 | cd build 16 | # This will have a non-zero exit if any of the scans fail, so do not fail immediately on that 17 | set +e 18 | sudo oscap xccdf eval --profile C2S --results scan-xccdf-results.xml --fetch-remote-resources /usr/share/xml/scap/ssg/content/ssg-centos7-xccdf.xml 19 | set -e 20 | 21 | oscap xccdf generate report scan-xccdf-results.xml > scan-xccdf-results.html 22 | -------------------------------------------------------------------------------- /bin/set-newrelic-license-key.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Set the newrelic license key fron AWS credentials 3 | # 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$DIR/.." 16 | export BASE_DIR 17 | 18 | NEWRELIC_CONFIG_FILE="/etc/newrelic-infra.yml" 19 | # Thanks Stack Overflow https://stackoverflow.com/a/9735663/424301 20 | EC2_AVAIL_ZONE=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) 21 | EC2_REGION="$(sed 's/[a-z]$//' <<<"$EC2_AVAIL_ZONE")" 22 | 23 | NEWRELIC_LICENSE_KEY=$(aws secretsmanager get-secret-value \ 24 | --region="$EC2_REGION" \ 25 | --secret-id newrelic_license \ 26 | --output text \ 27 | --query '[SecretString]') 28 | 29 | cp -a "${NEWRELIC_CONFIG_FILE}" "${NEWRELIC_CONFIG_FILE}.orig" 30 | sed "s/ZZZZ*ZZZZ/${NEWRELIC_LICENSE_KEY}/" "${NEWRELIC_CONFIG_FILE}.orig" > "${NEWRELIC_CONFIG_FILE}" 31 | -------------------------------------------------------------------------------- /bin/spin-3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ab -c 2 -t 900 http://devops-infra-demo.modus.app/api/spin 4 | -------------------------------------------------------------------------------- /bin/spin-7.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ab -c 4 -t 900 http://devops-infra-demo.modus.app/api/spin 4 | -------------------------------------------------------------------------------- /bin/spin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ab -c 8 -t 900 http://devops-infra-demo.modus.app/api/spin 4 | -------------------------------------------------------------------------------- /bin/terraform.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # terraform.sh 4 | # 5 | # Wrapper script for running Terraform through Docker 6 | # 7 | # Useful when running in Jenkins CI or other contexts where you have Docker 8 | # available. 9 | 10 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 11 | set -euo pipefail 12 | #IFS=$'\n\t' 13 | 14 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 15 | ${DEBUG:-false} && set -vx 16 | # Credit to https://stackoverflow.com/a/17805088 17 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 18 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 19 | 20 | # Credit to http://stackoverflow.com/a/246128/424301 21 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 22 | BASE_DIR="$DIR/.." 23 | BUILD_DIR="$BASE_DIR/build" 24 | #shellcheck disable=SC1090 25 | . "$DIR/common.sh" 26 | #shellcheck disable=SC1090 27 | . "$BASE_DIR/env.sh" 28 | #shellcheck disable=SC1090 29 | . "$DIR/common-terraform.sh" 30 | 31 | DOCKER_TERRAFORM=$(get_docker_terraform) 32 | DOCKER_LANDSCAPE=$(get_docker_landscape) 33 | 34 | HELP_TEXT="You must specify a verb: apply, plan, plan-destroy, import, output or show" 35 | verb=${1:?$HELP_TEXT} 36 | 37 | # Inject Google application credentials into env file for docker 38 | GOOGLE_APPLICATION_CREDENTIALS_OVERRIDE=${GOOGLE_APPLICATION_CREDENTIALS_OVERRIDE:-} 39 | if [[ -n "$GOOGLE_APPLICATION_CREDENTIALS_OVERRIDE" ]]; then 40 | echo "Overriding Google Application Credentials" 1>&2 41 | GOOGLE_APPLICATION_CREDENTIALS="$GOOGLE_APPLICATION_CREDENTIALS_OVERRIDE" 42 | fi 43 | NEWRELIC_LICENSE_KEY_OVERRIDE=${NEWRELIC_LICENSE_KEY_OVERRIDE:-} 44 | if [[ -n "$NEWRELIC_LICENSE_KEY_OVERRIDE" ]]; then 45 | echo "Overriding New Relic License Key" 1>&2 46 | NEWRELIC_LICENSE_KEY="$NEWRELIC_LICENSE_KEY_OVERRIDE" 47 | fi 48 | NEWRELIC_API_KEY_OVERRIDE=${NEWRELIC_API_KEY_OVERRIDE:-} 49 | if [[ -n "$NEWRELIC_API_KEY_OVERRIDE" ]]; then 50 | echo "Overriding New Relic API Key" 1>&2 51 | NEWRELIC_API_KEY="$NEWRELIC_API_KEY_OVERRIDE" 52 | fi 53 | NEWRELIC_ALERT_EMAIL_OVERRIDE=${NEWRELIC_ALERT_EMAIL_OVERRIDE:-} 54 | if [[ -n "$NEWRELIC_ALERT_EMAIL_OVERRIDE" ]]; then 55 | echo "Overriding New Relic Alert Email" 1>&2 56 | NEWRELIC_ALERT_EMAIL="$NEWRELIC_ALERT_EMAIL_OVERRIDE" 57 | fi 58 | 59 | # Set up Google creds in build dir for docker terraform 60 | mkdir -p "$BUILD_DIR" 61 | cp "$GOOGLE_APPLICATION_CREDENTIALS" "$BUILD_DIR/google.json" 62 | # Ugh. Jenkins was failing to extract the stash containing this file 63 | # because google.json had a umask of 0400 (read only to user): 64 | # java.io.IOException: Failed to extract plan.tar.gz 65 | # This is similar to the problem listed here: 66 | # https://issues.jenkins-ci.org/browse/JENKINS-33126 67 | chmod u+w "$BUILD_DIR/google.json" 68 | sed -i.bak '/GOOGLE_APPLICATION_CREDENTIALS/d' "$ENV_FILE" 69 | #shellcheck disable=SC2086 70 | GOOGLE_PROJECT_OVERRIDE=$(awk 'BEGIN { FS = "\"" } /project_id/{print $4}' <$GOOGLE_APPLICATION_CREDENTIALS) 71 | cat <>"$ENV_FILE" 72 | GOOGLE_APPLICATION_CREDENTIALS=/app/build/google.json 73 | GOOGLE_PROJECT=$GOOGLE_PROJECT_OVERRIDE 74 | NEWRELIC_LICENSE_KEY=$NEWRELIC_LICENSE_KEY 75 | NEWRELIC_API_KEY=$NEWRELIC_API_KEY 76 | NEWRELIC_ALERT_EMAIL=$NEWRELIC_ALERT_EMAIL 77 | EOF 78 | 79 | # http://redsymbol.net/articles/bash-exit-traps/ 80 | trap clean_root_owned_docker_files EXIT 81 | 82 | function import() { 83 | local -i retcode 84 | #shellcheck disable=SC2086 85 | $DOCKER_TERRAFORM import "$@" 86 | } 87 | 88 | function show() { 89 | local -i retcode 90 | #shellcheck disable=SC2086 91 | $DOCKER_TERRAFORM show 92 | } 93 | 94 | function output () { 95 | local -i retcode 96 | local extra 97 | #shellcheck disable=SC2086 98 | $DOCKER_TERRAFORM output "$@" 99 | } 100 | 101 | function plan() { 102 | local extra 103 | local nr_app_id 104 | local output 105 | local -i retcode 106 | local targets 107 | extra="$*" 108 | output="$(mktemp)" 109 | targets=$(get_targets) 110 | 111 | set +e 112 | 113 | nr_app_id=$(curl \ 114 | -s \ 115 | -X GET \ 116 | 'https://api.newrelic.com/v2/applications.json' \ 117 | -H "X-Api-Key:${NEWRELIC_API_KEY}" \ 118 | -d "filter[name]=${NEWRELIC_APP_NAME}" \ 119 | | jq '.applications[].id') 120 | 121 | #shellcheck disable=SC2086 122 | $DOCKER_TERRAFORM plan \ 123 | $extra \ 124 | $targets \ 125 | -lock=true \ 126 | -input="$INPUT_ENABLED" \ 127 | -var project_name="$PROJECT_NAME" \ 128 | -var newrelic_license_key="$NEWRELIC_LICENSE_KEY" \ 129 | -var newrelic_api_key="$NEWRELIC_API_KEY" \ 130 | -var newrelic_alert_email="$NEWRELIC_ALERT_EMAIL" \ 131 | -var newrelic_apm_entities="[$nr_app_id]" \ 132 | -var-file="/app/build/extra.tfvars" \ 133 | -out="$TF_PLAN" \ 134 | "$TF_DIR" \ 135 | > "$output" 136 | retcode="$?" 137 | set -e 138 | if [[ "$retcode" -eq 0 ]]; then 139 | $DOCKER_LANDSCAPE - < "$output" 140 | else 141 | cat "$output" 142 | fi 143 | rm -f "$output" 144 | return "$retcode" 145 | } 146 | 147 | function plan-destroy() { 148 | cat 1>&2 <&2 189 | exit 1 190 | ;; 191 | esac 192 | shift 193 | echo "$Message" 1>&2 194 | init_terraform 1>&2 195 | "$verb" "$@" 196 | 197 | -------------------------------------------------------------------------------- /bin/validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | 6 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 7 | ${DEBUG:-false} && set -vx 8 | # Credit to https://stackoverflow.com/a/17805088 9 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 10 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 11 | 12 | # Credit to http://stackoverflow.com/a/246128/424301 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | BASE_DIR="$DIR/.." 15 | BUILD_DIR="$BASE_DIR/build" 16 | export BUILD_DIR 17 | 18 | # shellcheck disable=SC1090 19 | . "$DIR/common.sh" 20 | # shellcheck disable=SC1090 21 | . "$BASE_DIR/env.sh" 22 | # shellcheck disable=SC1090 23 | . "$DIR/common-terraform.sh" 24 | 25 | DOCKER_PACKER=$(get_docker_packer "$BASE_DIR") 26 | echo "Linting packer files" 27 | $DOCKER_PACKER validate app/packer/machines/web-server.json 28 | 29 | # Ensure that `terraform fmt` comes up clean 30 | if [[ "$SKIP_TERRAFORM" == "false" ]]; then 31 | echo "Linting terraform files for correctness" 32 | DOCKER_TERRAFORM=$(get_docker_terraform) 33 | init_terraform 34 | $DOCKER_TERRAFORM validate \ 35 | -var 'newrelic_license_key=ZZZZ' \ 36 | -var 'newrelic_api_key=ZZZZ' \ 37 | -var 'newrelic_alert_email=ferd.berferd@example.com' \ 38 | echo "Linting terraform files for formatting" 39 | fmt=$($DOCKER_TERRAFORM fmt) 40 | if [[ -n "$fmt" ]]; then 41 | echo 'ERROR: these files are not formatted correctly. Run "terraform fmt"' 42 | echo "$fmt" 43 | git diff 44 | exit 1 45 | fi 46 | fi 47 | 48 | echo "Linting shell scripts" 49 | DOCKER_SHELLCHECK=$(get_docker_shellcheck) 50 | # shellcheck disable=SC2046 51 | $DOCKER_SHELLCHECK $(find . -name '*.sh') env.sh.sample 52 | -------------------------------------------------------------------------------- /codedeploy/appspec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 0.0 3 | os: linux 4 | files: 5 | - source: application 6 | destination: /app/application 7 | - source: src 8 | destination: /app/src 9 | - source: gauntlt 10 | destination: /app/gauntlt 11 | - source: bin 12 | destination: /app/bin 13 | - source: ansible 14 | destination: /app/ansible 15 | hooks: 16 | ApplicationStop: 17 | - location: bin/codedeploy/ApplicationStop.sh 18 | timeout: 600 19 | BeforeInstall: 20 | - location: bin/codedeploy/BeforeInstall.sh 21 | timeout: 30 22 | AfterInstall: 23 | - location: bin/codedeploy/AfterInstall.sh 24 | timeout: 300 25 | ApplicationStart: 26 | - location: bin/codedeploy/ApplicationStart.sh 27 | timeout: 120 28 | ValidateService: 29 | - location: bin/codedeploy/ValidateService.sh 30 | timeout: 300 31 | -------------------------------------------------------------------------------- /env.sh.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copy this sample to env.sh and customize it, then source it with: 3 | # 4 | # source env.sh 5 | # 6 | 7 | export PROJECT_NAME=devops-infra-demo 8 | # Replace this with the AWS profile from the AWS cli you want to use 9 | export AWS_PROFILE=default 10 | export AWS_DEFAULT_PROFILE=default 11 | export AWS_DEFAULT_REGION=us-east-1 12 | export AWS_SOURCE_AMI=ami-9887c6e7 13 | #export AWS_DEFAULT_REGION=us-east-2 14 | #export AWS_SOURCE_AMI=ami-3daa8f58 15 | 16 | # Fill this in with either the default VPC from your AWS region 17 | # or with a VPC and subnet ID that have automatic assignment of 18 | # IPv4 addresses enabled. 19 | #export PACKER_AWS_VPC_ID=vpc-XXXXXXXX 20 | #export PACKER_AWS_SUBNET_ID=subnet-YYYYYYYY 21 | # credentials for IAM user - needed for Docker packer when run locally 22 | #export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX 23 | #export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY 24 | 25 | # Fill this in with details related to your Google account 26 | export GOOGLE_APPLICATION_CREDENTIALS=~/terraform-demo.json 27 | declare GOOGLE_CLOUD_KEYFILE_JSON 28 | if [ -f "$GOOGLE_APPLICATION_CREDENTIALS" ]; then 29 | GOOGLE_CLOUD_KEYFILE_JSON="$(cat "$GOOGLE_APPLICATION_CREDENTIALS")" 30 | fi 31 | export GOOGLE_CLOUD_KEYFILE_JSON 32 | #export GOOGLE_PROJECT=example-terraform-demo-999999 33 | export GOOGLE_REGION=us-east1 34 | 35 | # Fill in your New Relic license key and API key 36 | export NEWRELIC_LICENSE_KEY=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 37 | export NEWRELIC_API_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 38 | export NEWRELIC_ALERT_EMAIL=nobody@example.com 39 | export NEWRELIC_APP_NAME=Spin 40 | -------------------------------------------------------------------------------- /gauntlt/nmap-invariant.attack: -------------------------------------------------------------------------------- 1 | @slow 2 | # Adapted from https://github.com/gauntlt/gauntlt/blob/master/examples/nmap/nmap.attack 3 | 4 | Feature: nmap attacks for localhost 5 | Background: 6 | Given "nmap" is installed 7 | And the following profile: 8 | | name | value | 9 | | hostname | localhost | 10 | | host | localhost | 11 | | tcp_ping_ports | 22,25 | 12 | 13 | Scenario: Verify server is open on expected set of ports using the nmap-fast attack step 14 | When I launch a "nmap-fast" attack 15 | Then the output should match /22.tcp\s+open/ 16 | And the output should not match /25.tcp\s+open/ 17 | 18 | Scenario: Verify server is open on expected set of ports using the nmap fast flag 19 | When I launch an "nmap" attack with: 20 | """ 21 | nmap -F 22 | """ 23 | Then the output should match: 24 | """ 25 | 22/tcp\s+open 26 | """ 27 | 28 | Scenario: Verify server is open on expected set of ports using the nmap fast flag 29 | When I launch an "nmap" attack with: 30 | """ 31 | nmap -F 32 | """ 33 | Then the output should not contain: 34 | """ 35 | 25/tcp\s+open 36 | """ 37 | 38 | Scenario: Output to XML 39 | When I launch an "nmap" attack with: 40 | """ 41 | nmap -p- -oX /app/build/nmap-results.xml 42 | """ 43 | And the file "/app/build/nmap-results.xml" should contain XML: 44 | | css | 45 | | ports port[protocol="tcp"][portid="22"] state[state="open"] | 46 | And the file "/app/build/nmap-results.xml" should not contain XML: 47 | | ports port[protocol="tcp"][portid="25"] state[state="open"] | 48 | 49 | -------------------------------------------------------------------------------- /gauntlt/nmap-running.attack: -------------------------------------------------------------------------------- 1 | @slow 2 | 3 | Feature: nmap attacks for localhost and to use this for your tests, change the value in the profile 4 | Background: 5 | Given "nmap" is installed 6 | And the following profile: 7 | | name | value | 8 | | hostname | localhost | 9 | | host | localhost | 10 | | tcp_ping_ports | 22,80 | 11 | 12 | Scenario: Verify server is open on expected set of ports using the nmap-fast attack step 13 | When I launch a "nmap-fast" attack 14 | Then the output should match /22.tcp\s+open/ 15 | And the output should match /80.tcp\s+open/ 16 | And the output should not match /25.tcp\s+open/ 17 | 18 | -------------------------------------------------------------------------------- /gauntlt/nmap-stopped.attack: -------------------------------------------------------------------------------- 1 | @slow 2 | 3 | Feature: nmap attacks for localhost and to use this for your tests, change the value in the profile 4 | Background: 5 | Given "nmap" is installed 6 | And the following profile: 7 | | name | value | 8 | | hostname | localhost | 9 | | host | localhost | 10 | | tcp_ping_ports | 22,25,80,443 | 11 | 12 | Scenario: Verify server is open on expected set of ports using the nmap-fast attack step 13 | When I launch a "nmap-fast" attack 14 | Then the output should match /22\.tcp\s+open/ 15 | Then the output should not contain /(25|80|443)\.tcp\s+open/ 16 | 17 | -------------------------------------------------------------------------------- /gauntlt/nmap.attack: -------------------------------------------------------------------------------- 1 | @slow 2 | 3 | Feature: nmap attacks for localhost and to use this for your tests, change the value in the profile 4 | Background: 5 | Given "nmap" is installed 6 | And the following profile: 7 | | name | value | 8 | | hostname | localhost | 9 | | host | localhost | 10 | | tcp_ping_ports | 22,25,80,443 | 11 | 12 | Scenario: Verify server is open on expected set of ports using the nmap-fast attack step 13 | When I launch a "nmap-fast" attack 14 | Then the output should match /22.tcp\s+open/ 15 | 16 | Scenario: Verify server is open on expected set of ports using the nmap fast flag 17 | When I launch an "nmap" attack with: 18 | """ 19 | nmap -F 20 | """ 21 | Then the output should match: 22 | """ 23 | 22/tcp\s+open 24 | """ 25 | 26 | Scenario: Verify that there are no unexpected ports open 27 | When I launch an "nmap" attack with: 28 | """ 29 | nmap -F 30 | """ 31 | Then the output should not contain: 32 | """ 33 | 443/tcp 34 | """ 35 | 36 | Scenario: Output to XML 37 | When I launch an "nmap" attack with: 38 | """ 39 | nmap -p 22,80,443 -oX /app/build/nmap-results.xml 40 | """ 41 | And the file "/app/build/nmap-results.xml" should contain XML: 42 | | css | 43 | | ports port[protocol="tcp"][portid="22"] state[state="open"] | 44 | And the file "/app/build/nmap-results.xml" should not contain XML: 45 | | css | 46 | | ports port[protocol="tcp"][portid="80"] state[state="open"] | 47 | | ports port[protocol="tcp"][portid="443"] state[state="open"] | 48 | 49 | -------------------------------------------------------------------------------- /gauntlt/readme.md: -------------------------------------------------------------------------------- 1 | # Running gauntlt 2 | 3 | ## Vagrant 4 | You can run gauntlt through Vagrant: 5 | 6 | vagrant up --provision 7 | vagrant ssh -c "cd /app/gauntlt && gauntlt" 8 | 9 | ##Attacks 10 | 11 | NMAP attacks taken from here: 12 | 13 | https://github.com/gauntlt/gauntlt/tree/master/examples/nmap 14 | -------------------------------------------------------------------------------- /jmeter/api-spin.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | parametrized to accept command line parameters for time, ramp up, and number of threads. 17 | continue 18 | 19 | false 20 | -1 21 | 22 | ${__P(num_threads,2)} 23 | ${__P(ramp_time,90)} 24 | true 25 | ${__P(duration,180)} 26 | 27 | 1548631349000 28 | 1548631349000 29 | 30 | 31 | 32 | 33 | 34 | 35 | ${__P(domain,devops-infra-demo.moduscreate.com)} 36 | 80 37 | 38 | 39 | http 40 | 41 | ${__P(path,/api/spin)} 42 | GET 43 | true 44 | false 45 | true 46 | false 47 | false 48 | 49 | 50 | 51 | 52 | 53 | spun 54 | 55 | Assertion.response_data 56 | false 57 | 2 58 | 59 | 60 | 61 | 62 | 200 63 | 64 | Assertion.response_code 65 | false 66 | 1 67 | 68 | 69 | 70 | 71 | 72 | 73 | false 74 | 75 | saveConfig 76 | 77 | 78 | true 79 | true 80 | true 81 | 82 | true 83 | true 84 | true 85 | true 86 | false 87 | true 88 | true 89 | false 90 | false 91 | false 92 | true 93 | false 94 | false 95 | false 96 | true 97 | 0 98 | true 99 | true 100 | true 101 | true 102 | true 103 | 104 | 105 | 106 | 107 | 108 | 109 | false 110 | 111 | saveConfig 112 | 113 | 114 | true 115 | true 116 | true 117 | 118 | true 119 | true 120 | true 121 | true 122 | false 123 | true 124 | true 125 | false 126 | false 127 | false 128 | true 129 | false 130 | false 131 | false 132 | true 133 | 0 134 | true 135 | true 136 | true 137 | true 138 | true 139 | 140 | 141 | 142 | 143 | 144 | 145 | true 146 | 147 | saveConfig 148 | 149 | 150 | true 151 | true 152 | true 153 | 154 | true 155 | true 156 | true 157 | true 158 | true 159 | true 160 | true 161 | true 162 | true 163 | true 164 | false 165 | false 166 | true 167 | false 168 | true 169 | 0 170 | true 171 | true 172 | true 173 | true 174 | true 175 | true 176 | 177 | 178 | /repo/build/jmeter-results.xml 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /packer/machines/web-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "aws_access_key":"{{env `AWS_ACCESS_KEY_ID`}}", 4 | "aws_default_region":"{{env `AWS_DEFAULT_REGION`}}", 5 | "aws_source_ami":"{{env `AWS_SOURCE_AMI`}}", 6 | "aws_secret_key":"{{env `AWS_SECRET_ACCESS_KEY`}}", 7 | "aws_vpc_id": "{{env `PACKER_AWS_VPC_ID`}}", 8 | "aws_subnet_id": "{{env `PACKER_AWS_SUBNET_ID`}}" 9 | }, 10 | "builders": [{ 11 | "type": "amazon-ebs", 12 | "access_key": "{{user `aws_access_key`}}", 13 | "secret_key": "{{user `aws_secret_key`}}", 14 | "region": "{{user `aws_default_region`}}", 15 | "source_ami" : "{{user `aws_source_ami`}}", 16 | "instance_type": "t2.large", 17 | "subnet_id": "{{user `aws_subnet_id`}}", 18 | "vpc_id": "{{user `aws_vpc_id`}}", 19 | "ssh_username": "centos", 20 | "ami_name": "devops-infra-demo-centos-7-{{timestamp}}", 21 | "ami_description": "DevOps Infrastructure Demo CentOS 7 - CIS hardened" 22 | }], 23 | "provisioners": [ 24 | { 25 | "type": "shell", 26 | "inline": [ 27 | "sudo mkdir -p /app/build", 28 | "sudo chown -R centos:centos /app" 29 | ] 30 | }, 31 | { 32 | "type": "file", 33 | "source": "/app/ansible", 34 | "destination": "/app" 35 | }, 36 | { 37 | "type": "file", 38 | "source": "/app/application", 39 | "destination": "/app" 40 | }, 41 | { 42 | "type": "file", 43 | "source": "/app/bin", 44 | "destination": "/app" 45 | }, 46 | { 47 | "type": "file", 48 | "source": "/app/gauntlt", 49 | "destination": "/app" 50 | }, 51 | { 52 | "type": "shell", 53 | "inline": [ 54 | "bash /app/bin/install-gauntlt.sh", 55 | "bash /app/bin/install-ansible.sh", 56 | "bash /app/bin/ansible.sh bakery.yml scan-openscap.yml scan-gauntlt.yml", 57 | "find / -name '*.html' 2>/dev/null | grep -E 'gauntlt|scan'", 58 | "ls -l /app /app/build" 59 | ] 60 | }, 61 | { 62 | "type": "file", 63 | "source": "/app/build/", 64 | "direction": "download", 65 | "destination": "/app" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | # Adapted from https://www.caktusgroup.com/blog/2017/03/14/production-ready-dockerfile-your-python-django-app/ 2 | # which states: 3 | # 4 | # Without further ado, here’s a production-ready Dockerfile you can use as a starting point for your project 5 | # 6 | FROM python:3.6-alpine 7 | 8 | # Copy in your requirements file 9 | ADD requirements.txt /requirements.txt 10 | 11 | # Install build deps, then run `pip install`, then remove unneeded build deps all in a single step. Correct the path to your production requirements file, if needed. 12 | RUN set -ex \ 13 | && apk add --no-cache --virtual .build-deps \ 14 | gcc \ 15 | make \ 16 | libc-dev \ 17 | musl-dev \ 18 | linux-headers \ 19 | pcre-dev \ 20 | postgresql-dev \ 21 | && pyvenv /venv \ 22 | && /venv/bin/pip install -U pip \ 23 | && LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "/venv/bin/pip install --no-cache-dir -r /requirements.txt" \ 24 | && runDeps="$( \ 25 | scanelf --needed --nobanner --recursive /venv \ 26 | | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ 27 | | sort -u \ 28 | | xargs -r apk info --installed \ 29 | | sort -u \ 30 | )" \ 31 | && apk add --virtual .python-rundeps $runDeps \ 32 | && apk del .build-deps 33 | 34 | # Copy your application code to the container (make sure you create a .dockerignore file if any large files or directories should be excluded) 35 | RUN mkdir /code/ 36 | WORKDIR /code/ 37 | ADD . /code/ 38 | 39 | # uWSGI will listen on this port 40 | EXPOSE 8000 41 | 42 | # uWSGI configuration (customize as needed): 43 | ENV UWSGI_VIRTUALENV=/venv UWSGI_WSGI_FILE=wsgi.py UWSGI_HTTP=:8000 UWSGI_MASTER=1 UWSGI_WORKERS=2 UWSGI_THREADS=8 UWSGI_UID=1000 UWSGI_GID=2000 UWSGI_LAZY_APPS=1 UWSGI_WSGI_ENV_BEHAVIOR=holy 44 | 45 | # Start uWSGI 46 | CMD ["/venv/bin/uwsgi", "--http-auto-chunked", "--http-keepalive"] 47 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | bottle==0.12.13 2 | uwsgi 3 | newrelic 4 | 5 | -------------------------------------------------------------------------------- /src/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """x""" 3 | 4 | if __name__ == "__main__": 5 | from spin import spin 6 | from bottle import run, default_app 7 | 8 | run(host="localhost", port=8080, debug=True) 9 | -------------------------------------------------------------------------------- /src/spin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This module spins the CPU.""" 3 | import os 4 | import time 5 | import random 6 | import socket 7 | import urllib2 8 | import newrelic.agent 9 | from bottle import route, default_app, response, HTTPError 10 | 11 | NEWRELIC_INI = "../newrelic.ini" 12 | if os.path.isfile(NEWRELIC_INI): 13 | newrelic.agent.initialize(NEWRELIC_INI) 14 | 15 | 16 | @route("/spin") 17 | def spin(delay=0.05, max_duration=10.0, simulate_congestion=True): 18 | """Spin the CPU, return the process id at the end""" 19 | spin.invocations += 1 20 | child_pid = os.getpid() 21 | upper_max = 100000000000000000000000000000000 22 | 23 | # Use a pareto distribution to give additional 24 | # variation to the delay 25 | # See https://en.wikipedia.org/wiki/Pareto_distribution 26 | alpha = 2 27 | pareto_factor = random.paretovariate(alpha) 28 | start_time = time.time() 29 | 30 | current_time = start_time 31 | scratch = 42 + int(current_time) 32 | congestion_slowdown = 0.0 33 | if simulate_congestion: 34 | congestion_slowdown = delay * 2 / (current_time - spin.last_time) 35 | end_time = start_time + (delay + congestion_slowdown) * pareto_factor 36 | time_limit = start_time + (max_duration) 37 | calcs = 0 38 | while current_time < end_time: 39 | calcs += 1 40 | scratch = (scratch * scratch) % upper_max 41 | current_time = time.time() 42 | interval = current_time - start_time 43 | if current_time > time_limit: 44 | raise HTTPError( 45 | 500, 46 | "Allowed transaction time exceeded ({} ms elapsed)".format(interval), 47 | ) 48 | spin.last_time = current_time 49 | rate = calcs / interval 50 | response.set_header("Content-Type", "text/plain") 51 | return "node {0} pid {1} spun {2} times over {3}s (rate {4} invoked {5} times. Congestion slowdown {6}s)\n".format( 52 | spin.node, child_pid, calcs, interval, rate, spin.invocations, congestion_slowdown 53 | ) 54 | 55 | 56 | spin.invocations = 0 57 | spin.last_time = time.time() - 10 58 | spin.slowdown = 0 59 | try: 60 | # Thanks stack overflow https://stackoverflow.com/a/43816449/424301 61 | spin.node = urllib2.urlopen( 62 | "http://169.254.169.254/latest/meta-data/instance-id", timeout=1 63 | ).read() 64 | # Thanks stack overflow https://stackoverflow.com/questions/2712524/handling-urllib2s-timeout-python 65 | except urllib2.URLError: 66 | # Thanks stack overflow: https://stackoverflow.com/a/4271755/424301 67 | spin.node = socket.gethostname() 68 | 69 | application = default_app() 70 | -------------------------------------------------------------------------------- /src/startit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # current directory needs where this script is located 4 | cd "$(dirname "$0")" || exit 5 | 6 | UWSGI_VIRTUALENV=/app/venv \ 7 | UWSGI_WSGI_FILE=/app/src/wsgi.py \ 8 | UWSGI_MASTER=1 \ 9 | UWSGI_WORKERS=2 \ 10 | UWSGI_THREADS=8 \ 11 | UWSGI_UID=nobody \ 12 | UWSGI_GID=nobody \ 13 | UWSGI_LAZY_APPS=1 \ 14 | UWSGI_WSGI_ENV_BEHAVIOR=holy \ 15 | /app/venv/bin/uwsgi \ 16 | --enable-threads \ 17 | --http-auto-chunked \ 18 | --http-keepalive \ 19 | --socket=/app/socket/uwsgi.sock & 20 | 21 | -------------------------------------------------------------------------------- /src/stopit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pkill --signal INT -f uwsgi || true 4 | 5 | -------------------------------------------------------------------------------- /src/wsgi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This exports a WSGI application""" 3 | from spin import spin 4 | from bottle import route, default_app 5 | 6 | application = default_app() 7 | -------------------------------------------------------------------------------- /terraform/assume-role-policy-codedeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": [ 9 | "codedeploy.amazonaws.com" 10 | ] 11 | }, 12 | "Action": "sts:AssumeRole" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /terraform/assume-role-policy-ec2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": [ 9 | "ec2.amazonaws.com" 10 | ] 11 | }, 12 | "Action": "sts:AssumeRole" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /terraform/aws.tf: -------------------------------------------------------------------------------- 1 | # Thanks @marcosdiez for the suggestion 2 | # This makes it super-clear which AWS account, arn, and user_id are in use 3 | # in a way that can be conveniently tracked in the output of CI tools 4 | provider "aws" { 5 | region = "${var.aws_region}" 6 | version = "~> 1.57" 7 | } 8 | 9 | data "aws_caller_identity" "current" {} 10 | 11 | output "account_id" { 12 | value = "${data.aws_caller_identity.current.account_id}" 13 | } 14 | 15 | output "arn" { 16 | value = "${data.aws_caller_identity.current.arn}" 17 | } 18 | 19 | output "user_id" { 20 | value = "${data.aws_caller_identity.current.user_id}" 21 | } 22 | -------------------------------------------------------------------------------- /terraform/cloud-config.yml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | runcmd: 3 | - sudo -u centos ansible-playbook -l localhost /app/ansible/codedeploy.yml /app/ansible/newrelic-infrastructure.yml 4 | -------------------------------------------------------------------------------- /terraform/codedeploy.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "infra-demo" { 2 | name = "tf-infra-demo-role" 3 | 4 | assume_role_policy = <