├── version.txt ├── pytest.ini ├── terraform-build-user ├── .terraform-docs.yml ├── caller_identity.tf ├── main.tf ├── versions.tf ├── backend.tf ├── variables.tf ├── .terraform.lock.hcl ├── remote_states.tf ├── README.md └── providers.tf ├── terraform-post-packer ├── .terraform-docs.yml ├── versions.tf ├── outputs.tf ├── providers.tf ├── backend.tf ├── .terraform.lock.hcl ├── variables.tf ├── README.md └── main.tf ├── requirements-test.txt ├── locals.pkr.hcl ├── .github ├── lineage.yml ├── CODEOWNERS ├── dependabot.yml ├── labeler.yml ├── labels.yml └── workflows │ ├── label-prs.yml │ ├── sync-labels.yml │ ├── dependency-review.yml │ ├── codeql-analysis.yml │ ├── prerelease.yml │ ├── release.yml │ └── build.yml ├── requirements-dev.txt ├── .prettierignore ├── .lgtm.yml ├── ansible ├── upgrade.yml ├── playbook.yml ├── python.yml ├── aws.yml ├── install-prerequisites-for-netplan-configuration-fix.yml ├── base.yml ├── requirements.yml └── guacamole.yml ├── .terraform-docs.yml ├── .isort.cfg ├── .gitignore ├── versions.pkr.hcl ├── .bandit.yml ├── base_amis.pkr.hcl ├── tests ├── test_version.py └── conftest.py ├── requirements.txt ├── .ansible-lint ├── .flake8 ├── .mdl_config.yaml ├── ami_arm64.pkr.hcl ├── ami_x86_64.pkr.hcl ├── .yamllint ├── variables.pkr.hcl ├── bump-version ├── LICENSE ├── CONTRIBUTING.md ├── .pre-commit-config.yaml ├── setup-env └── README.md /version.txt: -------------------------------------------------------------------------------- 1 | 2.0.1 2 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v -ra 3 | -------------------------------------------------------------------------------- /terraform-build-user/.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ../.terraform-docs.yml -------------------------------------------------------------------------------- /terraform-post-packer/.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ../.terraform-docs.yml -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | --requirement requirements.txt 2 | pre-commit 3 | pytest 4 | -------------------------------------------------------------------------------- /locals.pkr.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | timestamp = regex_replace(timestamp(), "[- TZ:]", "") 3 | } 4 | -------------------------------------------------------------------------------- /.github/lineage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | lineage: 3 | skeleton: 4 | remote-url: https://github.com/cisagov/skeleton-packer.git 5 | version: "1" 6 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | --requirement requirements-test.txt 2 | ipython 3 | # The bump-version script requires at least version 3 of semver. 4 | semver>=3 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Already being linted by pretty-format-json 2 | *.json 3 | # Already being linted by mdl 4 | *.md 5 | # Already being linted by yamllint 6 | *.yaml 7 | *.yml 8 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extraction: 3 | python: 4 | python_setup: 5 | version: 3 6 | requirements_files: 7 | - requirements-test.txt 8 | setup_py: false 9 | -------------------------------------------------------------------------------- /ansible/upgrade.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Upgrade base image 3 | hosts: all 4 | become: true 5 | become_method: ansible.builtin.sudo 6 | tasks: 7 | - name: Upgrade all packages 8 | ansible.builtin.include_role: 9 | name: upgrade 10 | -------------------------------------------------------------------------------- /terraform-build-user/caller_identity.tf: -------------------------------------------------------------------------------- 1 | # Retrieve the effective Account ID, User ID, and ARN in which Terraform is 2 | # authorized. This is used to calculate the session names for assumed roles. 3 | data "aws_caller_identity" "terraform_backend" { 4 | provider = aws.cool-terraform-backend 5 | } 6 | -------------------------------------------------------------------------------- /.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | formatter: markdown table 3 | output: 4 | file: README.md 5 | mode: inject 6 | template: |- 7 | 8 | {{ .Content }} 9 | 10 | settings: 11 | anchor: false 12 | atx-closed: true 13 | html: false 14 | lockfile: false 15 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | combine_star=true 3 | force_sort_within_sections=true 4 | 5 | import_heading_stdlib=Standard Python Libraries 6 | import_heading_thirdparty=Third-Party Libraries 7 | import_heading_firstparty=cisagov Libraries 8 | 9 | # Run isort under the black profile to align with our other Python linting 10 | profile=black 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file specifies intentionally untracked files that Git should ignore. 2 | # Files already tracked by Git are not affected. 3 | # See: https://git-scm.com/docs/gitignore 4 | 5 | ## Packer ## 6 | packer_cache 7 | 8 | ## Python ## 9 | __pycache__ 10 | .coverage 11 | .mypy_cache 12 | .pytest_cache 13 | .python-version 14 | *.egg-info 15 | dist 16 | 17 | ## Terraform ## 18 | .terraform 19 | terraform.tfstate 20 | terraform.tfstate.backup 21 | *.tfconfig 22 | *.tfvars 23 | -------------------------------------------------------------------------------- /versions.pkr.hcl: -------------------------------------------------------------------------------- 1 | packer { 2 | required_plugins { 3 | amazon = { 4 | source = "github.com/hashicorp/amazon" 5 | version = "~> 1.2" 6 | } 7 | ansible = { 8 | source = "github.com/hashicorp/ansible" 9 | version = "~> 1.1" 10 | } 11 | } 12 | # The required_plugins section is only supported in Packer 1.7.0 and 13 | # later. We also want to avoid jumping to Packer v2 until we are 14 | # ready. 15 | required_version = "~> 1.7" 16 | } 17 | -------------------------------------------------------------------------------- /.bandit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configuration file for the Bandit python security scanner 3 | # https://bandit.readthedocs.io/en/latest/config.html 4 | # This config is applied to bandit when scanning the "tests" tree 5 | 6 | # Tests are first included by `tests`, and then excluded by `skips`. 7 | # If `tests` is empty, all tests are considered included. 8 | 9 | tests: 10 | # - B101 11 | # - B102 12 | 13 | skips: 14 | - B101 # skip "assert used" check since assertions are required in pytests 15 | -------------------------------------------------------------------------------- /ansible/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Import base image playbook 3 | ansible.builtin.import_playbook: base.yml 4 | 5 | # TODO: Remove this when and if that becomes possible. See 6 | # cisagov/skeleton-packer#301. 7 | - name: >- 8 | Import playbook to install prerequisites for the Netplan 9 | configuration fix 10 | ansible.builtin.import_playbook: install-prerequisites-for-netplan-configuration-fix.yml 11 | 12 | - name: Import guacamole playbook 13 | ansible.builtin.import_playbook: guacamole.yml 14 | 15 | - name: Import AWS playbook 16 | ansible.builtin.import_playbook: aws.yml 17 | -------------------------------------------------------------------------------- /terraform-build-user/main.tf: -------------------------------------------------------------------------------- 1 | module "iam_user" { 2 | source = "github.com/cisagov/ami-build-iam-user-tf-module" 3 | 4 | providers = { 5 | aws = aws 6 | aws.images-ami = aws.images-ami 7 | aws.images-ssm = aws.images-ssm 8 | } 9 | 10 | ssm_parameters = [ 11 | "/guacamole/postgres_username", 12 | "/guacamole/postgres_password", 13 | "/rdp/username", 14 | "/rdp/password", 15 | "/vnc/ssh/ed25519_private_key", 16 | "/vnc/username", 17 | "/vnc/password", 18 | "/vnc/sftp/windows_base_directory", 19 | ] 20 | user_name = "build-guacamole-packer" 21 | } 22 | -------------------------------------------------------------------------------- /terraform-build-user/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # Version 1.1 of Terraform is the first version to support the 3 | # nullable key in variable definitions. 4 | required_version = "~> 1.1" 5 | 6 | # If you use any other providers you should also pin them to the 7 | # major version currently being used. This practice will help us 8 | # avoid unwelcome surprises. 9 | required_providers { 10 | # We have verified that our code works with version 6.7 of this 11 | # Terraform provider. 12 | aws = { 13 | source = "hashicorp/aws" 14 | version = "~> 6.7" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /terraform-post-packer/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # Version 1.1 of Terraform is the first version to support the 3 | # nullable key in variable definitions. 4 | required_version = "~> 1.1" 5 | 6 | # If you use any other providers you should also pin them to the 7 | # major version currently being used. This practice will help us 8 | # avoid unwelcome surprises. 9 | required_providers { 10 | # We have verified that our code works with version 6.7 of this 11 | # Terraform provider. 12 | aws = { 13 | source = "hashicorp/aws" 14 | version = "~> 6.7" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /terraform-post-packer/outputs.tf: -------------------------------------------------------------------------------- 1 | # cisagov/ansible-role-guacamole cannot currently support ARM64 2 | # because the official Guacamole Docker images do not. 3 | # output "launch_permissions_arm64" { 4 | # value = module.ami_launch_permission_arm64 5 | # description = "The cisagov/ami-launch-permission-tf-module for each ARM64 AMI to which launch permission is being granted." 6 | # } 7 | 8 | output "launch_permissions_x86_64" { 9 | value = module.ami_launch_permission_x86_64 10 | description = "The cisagov/ami-launch-permission-tf-module for each x86_64 AMI to which launch permission is being granted." 11 | } 12 | -------------------------------------------------------------------------------- /terraform-post-packer/providers.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | tags = { 3 | Application = "guacamole-packer" 4 | Team = "CISA - Development" 5 | } 6 | } 7 | 8 | # Default AWS provider (EC2AMICreate role in the Images account) 9 | provider "aws" { 10 | default_tags { 11 | tags = local.tags 12 | } 13 | profile = "cool-images-ec2amicreate" 14 | region = "us-east-1" 15 | } 16 | 17 | # AWS provider for the Master account (OrganizationsReadOnly role) 18 | provider "aws" { 19 | alias = "master" 20 | default_tags { 21 | tags = local.tags 22 | } 23 | profile = "cool-master-organizationsreadonly" 24 | region = "us-east-1" 25 | } 26 | -------------------------------------------------------------------------------- /terraform-build-user/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | # Use a partial configuration to avoid hardcoding the bucket name. This 4 | # allows the bucket name to be set on a per-environment basis via the 5 | # -backend-config command line option or other methods. For details, see: 6 | # https://developer.hashicorp.com/terraform/language/backend#partial-configuration 7 | bucket = "" 8 | dynamodb_table = "terraform-state-lock" 9 | encrypt = true 10 | key = "guacamole-packer/terraform-build-user.tfstate" 11 | profile = "cool-terraform-backend" 12 | region = "us-east-1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /terraform-post-packer/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | # Use a partial configuration to avoid hardcoding the bucket name. This 4 | # allows the bucket name to be set on a per-environment basis via the 5 | # -backend-config command line option or other methods. For details, see: 6 | # https://developer.hashicorp.com/terraform/language/backend#partial-configuration 7 | bucket = "" 8 | dynamodb_table = "terraform-state-lock" 9 | encrypt = true 10 | key = "guacamole-packer/terraform-post-packer.tfstate" 11 | profile = "cool-terraform-backend" 12 | region = "us-east-1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /base_amis.pkr.hcl: -------------------------------------------------------------------------------- 1 | data "amazon-ami" "debian_trixie_arm64" { 2 | filters = { 3 | architecture = "arm64" 4 | name = "debian-13-arm64-*" 5 | root-device-type = "ebs" 6 | virtualization-type = "hvm" 7 | } 8 | most_recent = true 9 | owners = ["136693071363"] 10 | region = var.build_region 11 | } 12 | 13 | data "amazon-ami" "debian_trixie_x86_64" { 14 | filters = { 15 | architecture = "x86_64" 16 | name = "debian-13-amd64-*" 17 | root-device-type = "ebs" 18 | virtualization-type = "hvm" 19 | } 20 | most_recent = true 21 | owners = ["136693071363"] 22 | region = var.build_region 23 | } 24 | -------------------------------------------------------------------------------- /ansible/python.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install pip3/python3 and remove pip2/python2 3 | hosts: all 4 | become: true 5 | become_method: ansible.builtin.sudo 6 | tasks: 7 | # If pip were to be installed first, then the OS _could_ pull 8 | # different Python packages than what would be installed via the 9 | # cisagov/ansible-role-python role; hence, the ordering below is 10 | # more controlled. 11 | - name: Install Python 3 12 | ansible.builtin.include_role: 13 | name: python 14 | - name: Install pip3 15 | ansible.builtin.include_role: 16 | name: pip 17 | - name: Uninstall Python 2 18 | ansible.builtin.include_role: 19 | name: remove_python2 20 | -------------------------------------------------------------------------------- /terraform-build-user/variables.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Required parameters 3 | # 4 | # You must provide a value for each of these parameters. 5 | # ------------------------------------------------------------------------------ 6 | 7 | variable "terraform_state_bucket" { 8 | description = "The name of the S3 bucket where Terraform state is stored." 9 | nullable = false 10 | type = string 11 | } 12 | 13 | # ------------------------------------------------------------------------------ 14 | # Optional parameters 15 | # 16 | # These parameters have reasonable defaults. 17 | # ------------------------------------------------------------------------------ 18 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | """Version tests for packer skeleton project.""" 2 | 3 | # Standard Python Libraries 4 | import os 5 | 6 | # Third-Party Libraries 7 | import pytest 8 | 9 | GITHUB_RELEASE_TAG = os.getenv("GITHUB_RELEASE_TAG") 10 | VERSION_FILE = "version.txt" 11 | 12 | 13 | @pytest.mark.skipif( 14 | GITHUB_RELEASE_TAG in [None, ""], 15 | reason="this is not a release (GITHUB_RELEASE_TAG not set)", 16 | ) 17 | def test_release_version(): 18 | """Verify that release tag version agrees with the module version.""" 19 | with open(VERSION_FILE) as f: 20 | project_version = f.read().strip() 21 | assert ( 22 | GITHUB_RELEASE_TAG == f"v{project_version}" 23 | ), "GITHUB_RELEASE_TAG does not match the project version" 24 | -------------------------------------------------------------------------------- /ansible/aws.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: AWS-specific roles 3 | hosts: all 4 | become: true 5 | become_method: ansible.builtin.sudo 6 | tasks: 7 | - name: Install Amazon SSM Agent 8 | ansible.builtin.include_role: 9 | name: amazon_ssm_agent 10 | - name: Install chrony and configure it for use within AWS 11 | ansible.builtin.include_role: 12 | name: chrony_aws 13 | - name: Install and configure Amazon CloudWatch Agent 14 | ansible.builtin.include_role: 15 | name: cloudwatch_agent 16 | # The instance types used for almost all the instances expose EBS 17 | # volumes as NVMe block devices, so that's why we need nvme here. 18 | - name: Install prerequisites for working with NVMe block devices 19 | ansible.builtin.include_role: 20 | name: nvme 21 | -------------------------------------------------------------------------------- /ansible/install-prerequisites-for-netplan-configuration-fix.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install the prerequisites for the user script that fixes the Netplan 3 | # configuration generated by cloud-init from the instance metadata. 4 | # 5 | # See these issues for more details: 6 | # - cisagov/skeleton-packer#300 7 | # - canonical/cloud-init#4764 8 | # 9 | # TODO: Remove this playbook when and if that becomes possible. See 10 | # cisagov/skeleton-packer#301 for more details. 11 | - name: >- 12 | Install prerequisites for the script that fixes the Netplan 13 | configuration generated by cloud-init from the instance metadata 14 | hosts: all 15 | become: true 16 | become_method: ansible.builtin.sudo 17 | tasks: 18 | - name: Install python3-pyyaml 19 | ansible.builtin.package: 20 | name: 21 | - python3-yaml 22 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """pytest plugin configuration. 2 | 3 | https://docs.pytest.org/en/latest/writing_plugins.html#conftest-py-plugins 4 | """ 5 | 6 | # Third-Party Libraries 7 | import pytest 8 | 9 | 10 | def pytest_addoption(parser): 11 | """Add new commandline options to pytest.""" 12 | parser.addoption( 13 | "--runslow", action="store_true", default=False, help="run slow tests" 14 | ) 15 | 16 | 17 | def pytest_collection_modifyitems(config, items): 18 | """Modify collected tests based on custom marks and commandline options.""" 19 | if config.getoption("--runslow"): 20 | # --runslow given in cli: do not skip slow tests 21 | return 22 | skip_slow = pytest.mark.skip(reason="need --runslow option to run") 23 | for item in items: 24 | if "slow" in item.keywords: 25 | item.add_marker(skip_slow) 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Version 10 is required because the pip-audit pre-commit hook 2 | # identifies a vulnerability in ansible-core 2.16.13, but all versions 3 | # of ansible 9 have a dependency on ~=2.16.X. 4 | # 5 | # We have tested against version 10. We want to avoid automatically 6 | # jumping to another major version without testing, since there are 7 | # often breaking changes across major versions. This is the reason 8 | # for the upper bound. 9 | ansible>=10,<11 10 | # ansible-core<2.17.7 suffers from GHSA-99w6-3xph-cx78. 11 | # 12 | # Note that any changes made to this dependency must also be made in 13 | # requirements-test.txt in cisagov/skeleton-ansible-role and 14 | # .pre-commit-config.yaml in cisagov/skeleton-generic. 15 | ansible-core>=2.17.7 16 | boto3 17 | docopt 18 | # The bump-version script requires at least version 3 of semver. 19 | semver>=3 20 | setuptools 21 | wheel 22 | -------------------------------------------------------------------------------- /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | # See https://ansible-lint.readthedocs.io/configuring/ for a list of 3 | # the configuration elements that can exist in this file. 4 | enable_list: 5 | # Useful checks that one must opt-into. See here for more details: 6 | # https://ansible-lint.readthedocs.io/rules/ 7 | - fcqn-builtins 8 | - no-log-password 9 | - no-same-owner 10 | exclude_paths: 11 | # This exclusion is implicit, unless exclude_paths is defined 12 | - .cache 13 | # Seems wise to ignore this too 14 | - .github 15 | kinds: 16 | # This will force our systemd specific molecule configurations to be treated 17 | # as plain yaml files by ansible-lint. This mirrors the default kind 18 | # configuration in ansible-lint for molecule configurations: 19 | # yaml: "**/molecule/*/{base,molecule}.{yaml,yml}" 20 | - yaml: "**/molecule/*/molecule-{no,with}-systemd.yml" 21 | use_default_rules: true 22 | -------------------------------------------------------------------------------- /ansible/base.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Setup base image 3 | hosts: all 4 | become: true 5 | become_method: ansible.builtin.sudo 6 | tasks: 7 | - name: Install and configure automated security updates 8 | ansible.builtin.include_role: 9 | name: automated_security_updates 10 | - name: Install and configure login banner 11 | ansible.builtin.include_role: 12 | name: banner 13 | - name: Install and configure ClamAV 14 | ansible.builtin.include_role: 15 | name: clamav 16 | - name: Create the devops user 17 | ansible.builtin.include_role: 18 | name: devops 19 | - name: Install and configure FreeIPA client 20 | ansible.builtin.include_role: 21 | name: freeipa_client 22 | - name: Install and configure htop 23 | ansible.builtin.include_role: 24 | name: htop 25 | - name: Create the joiner user 26 | ansible.builtin.include_role: 27 | name: joiner 28 | - name: Configure JournalD to preserve logs across reboots 29 | ansible.builtin.include_role: 30 | name: persist_journald 31 | - name: Install and configure systemd-resolved 32 | ansible.builtin.include_role: 33 | name: systemd_resolved 34 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Each line is a file pattern followed by one or more owners. 2 | 3 | # These owners will be the default owners for everything in the 4 | # repo. Unless a later match takes precedence, these owners will be 5 | # requested for review when someone opens a pull request. 6 | * @dav3r @felddy @jsf9k @mcdonnnj 7 | 8 | # These folks own any files in the .github directory at the root of 9 | # the repository and any of its subdirectories. 10 | /.github/ @dav3r @felddy @jsf9k @mcdonnnj 11 | 12 | # Let jsf9k own the sometimes-touchy AWS and Python playbooks, as well 13 | # as the Packer template. 14 | /*.pkr.hcl @jsf9k 15 | /ansible/aws.yml @jsf9k 16 | /ansible/python.yml @jsf9k 17 | 18 | # These folks own all linting configuration files. 19 | /.ansible-lint @dav3r @felddy @jsf9k @mcdonnnj 20 | /.bandit.yml @dav3r @felddy @jsf9k @mcdonnnj 21 | /.flake8 @dav3r @felddy @jsf9k @mcdonnnj 22 | /.isort.cfg @dav3r @felddy @jsf9k @mcdonnnj 23 | /.mdl_config.yaml @dav3r @felddy @jsf9k @mcdonnnj 24 | /.pre-commit-config.yaml @dav3r @felddy @jsf9k @mcdonnnj 25 | /.prettierignore @dav3r @felddy @jsf9k @mcdonnnj 26 | /.yamllint @dav3r @felddy @jsf9k @mcdonnnj 27 | /requirements.txt @dav3r @felddy @jsf9k @mcdonnnj 28 | /requirements-dev.txt @dav3r @felddy @jsf9k @mcdonnnj 29 | /requirements-test.txt @dav3r @felddy @jsf9k @mcdonnnj 30 | /setup-env @dav3r @felddy @jsf9k @mcdonnnj 31 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | # Select (turn on) 4 | # * Complexity violations reported by mccabe (C) - 5 | # http://flake8.pycqa.org/en/latest/user/error-codes.html#error-violation-codes 6 | # * Documentation conventions compliance reported by pydocstyle (D) - 7 | # http://www.pydocstyle.org/en/stable/error_codes.html 8 | # * Default errors and warnings reported by pycodestyle (E and W) - 9 | # https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes 10 | # * Default errors reported by pyflakes (F) - 11 | # http://flake8.pycqa.org/en/latest/glossary.html#term-pyflakes 12 | # * Default warnings reported by flake8-bugbear (B) - 13 | # https://github.com/PyCQA/flake8-bugbear#list-of-warnings 14 | # * The B950 flake8-bugbear opinionated warning - 15 | # https://github.com/PyCQA/flake8-bugbear#opinionated-warnings 16 | select = C,D,E,F,W,B,B950 17 | # Ignore flake8's default warning about maximum line length, which has 18 | # a hard stop at the configured value. Instead we use 19 | # flake8-bugbear's B950, which allows up to 10% overage. 20 | # 21 | # Also ignore flake8's warning about line breaks before binary 22 | # operators. It no longer agrees with PEP8. See, for example, here: 23 | # https://github.com/ambv/black/issues/21. Guido agrees here: 24 | # https://github.com/python/peps/commit/c59c4376ad233a62ca4b3a6060c81368bd21e85b. 25 | ignore = E501,W503 26 | -------------------------------------------------------------------------------- /terraform-build-user/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "6.25.0" 6 | constraints = ">= 4.9.0, ~> 6.7" 7 | hashes = [ 8 | "h1:0XEc9eHELD/BtPNybqkzzaS3bYp2HSv9LwAfaGyCpOU=", 9 | "zh:0f9621f719ec2051eabb94ca59aa4f13574487fbc1517b183293431c9d388e38", 10 | "zh:2ffbedb2e3afcd82da8bfc540bd74e9611527bdafd00d6d1885f62e7d13bac74", 11 | "zh:30fb4ab8b4af19da7b9ce95cb41fa9399f81383e1adc91801b770e7eeab651c3", 12 | "zh:377cbaffe3ec8aa5bb594071df0e91f17ac9292a325ed73cebd69fe78c51f7ec", 13 | "zh:3b65f5c98e03f1bfc5b71fa69521e785552ff9656860b25e211287910874037d", 14 | "zh:4478fab7b111c40a9a2a9db6ec5331618cc8e5a8b591f651095c77b87e9f22b1", 15 | "zh:4fdaa559c57aed5d24fa3d5cb59fed59e1e689c21d038fd336a3ba93b258803f", 16 | "zh:7a751ecd0f2654746dd4041d0f6d894c3a1876a152ba4bb7805ec2c715259065", 17 | "zh:866725b83f8d5587dab0559ac208ee6c181746871faa99ce551b535e19c7bb6a", 18 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 19 | "zh:b16e3e2a8ccba4ceeeee961c708ef572c4a65e0001eaf09d08fa14cef01ab179", 20 | "zh:dc897b2037bbb7f8d6456a4aa1ed82cbd4daddb173a184efdfe8c03a57557771", 21 | "zh:de2344f23c980093a46dda3185f9052cda950d1b8ca9cf3c6e16b8c45fa23779", 22 | "zh:ef538ec8a917715a1804c6735d44b756c32972d4fab71e15df87a59eb75dd57c", 23 | "zh:f25cdfdac6798e7de4a1d3dd577a97c1ca200a12317a1fd5a4b9ea54cb05e868", 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /terraform-post-packer/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "6.25.0" 6 | constraints = ">= 4.9.0, ~> 6.7" 7 | hashes = [ 8 | "h1:0XEc9eHELD/BtPNybqkzzaS3bYp2HSv9LwAfaGyCpOU=", 9 | "zh:0f9621f719ec2051eabb94ca59aa4f13574487fbc1517b183293431c9d388e38", 10 | "zh:2ffbedb2e3afcd82da8bfc540bd74e9611527bdafd00d6d1885f62e7d13bac74", 11 | "zh:30fb4ab8b4af19da7b9ce95cb41fa9399f81383e1adc91801b770e7eeab651c3", 12 | "zh:377cbaffe3ec8aa5bb594071df0e91f17ac9292a325ed73cebd69fe78c51f7ec", 13 | "zh:3b65f5c98e03f1bfc5b71fa69521e785552ff9656860b25e211287910874037d", 14 | "zh:4478fab7b111c40a9a2a9db6ec5331618cc8e5a8b591f651095c77b87e9f22b1", 15 | "zh:4fdaa559c57aed5d24fa3d5cb59fed59e1e689c21d038fd336a3ba93b258803f", 16 | "zh:7a751ecd0f2654746dd4041d0f6d894c3a1876a152ba4bb7805ec2c715259065", 17 | "zh:866725b83f8d5587dab0559ac208ee6c181746871faa99ce551b535e19c7bb6a", 18 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 19 | "zh:b16e3e2a8ccba4ceeeee961c708ef572c4a65e0001eaf09d08fa14cef01ab179", 20 | "zh:dc897b2037bbb7f8d6456a4aa1ed82cbd4daddb173a184efdfe8c03a57557771", 21 | "zh:de2344f23c980093a46dda3185f9052cda950d1b8ca9cf3c6e16b8c45fa23779", 22 | "zh:ef538ec8a917715a1804c6735d44b756c32972d4fab71e15df87a59eb75dd57c", 23 | "zh:f25cdfdac6798e7de4a1d3dd577a97c1ca200a12317a1fd5a4b9ea54cb05e868", 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /terraform-post-packer/variables.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # OPTIONAL PARAMETERS 3 | # 4 | # These parameters have reasonable defaults. 5 | # ------------------------------------------------------------------------------ 6 | 7 | variable "ami_share_account_name_regex" { 8 | default = "^env[[:digit:]]+" 9 | description = "A regular expression that matches the names of AWS accounts with which to share the AMIs created by this repository. This variable is used to share the AMIs with accounts that are members of the same AWS Organization as the account that owns the AMIs." 10 | nullable = false 11 | type = string 12 | } 13 | 14 | variable "extraorg_account_ids" { 15 | default = [] 16 | description = "A list of AWS account IDs corresponding to \"extra\" accounts with which you want to share this AMI (e.g. [\"123456789012\"]). Normally this variable is used to share an AMI with accounts that are not a member of the same AWS Organization as the account that owns the AMI." 17 | nullable = false 18 | type = list(string) 19 | } 20 | 21 | variable "recent_ami_count" { 22 | default = 12 23 | description = "The number of most-recent AMIs (per architecture) for which to grant launch permission (e.g. \"3\"). If this variable is set to three, for example, then accounts will be granted permission to launch the three most recent AMIs (or all most recent AMIs, if there are only one or two of them in existence)." 24 | nullable = false 25 | type = number 26 | } 27 | -------------------------------------------------------------------------------- /ansible/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: amazon_ssm_agent 3 | src: https://github.com/cisagov/ansible-role-amazon-ssm-agent 4 | - name: automated_security_updates 5 | src: https://github.com/cisagov/ansible-role-automated-security-updates 6 | - name: banner 7 | src: https://github.com/cisagov/ansible-role-banner 8 | - name: chrony_aws 9 | src: https://github.com/cisagov/ansible-role-chrony-aws 10 | - name: clamav 11 | src: https://github.com/cisagov/ansible-role-clamav 12 | - name: cloudwatch_agent 13 | src: https://github.com/cisagov/ansible-role-cloudwatch-agent 14 | - name: devops 15 | src: https://github.com/cisagov/ansible-role-devops-user 16 | - name: freeipa_client 17 | src: https://github.com/cisagov/ansible-role-freeipa-client 18 | - name: guacamole 19 | src: https://github.com/cisagov/ansible-role-guacamole 20 | - name: htop 21 | src: https://github.com/cisagov/ansible-role-htop 22 | - name: joiner 23 | src: https://github.com/cisagov/ansible-role-joiner-user 24 | - name: nvme 25 | src: https://github.com/cisagov/ansible-role-nvme 26 | - name: persist_journald 27 | src: https://github.com/cisagov/ansible-role-persist-journald 28 | - name: pip 29 | src: https://github.com/cisagov/ansible-role-pip 30 | - name: python 31 | src: https://github.com/cisagov/ansible-role-python 32 | - name: remove_python2 33 | src: https://github.com/cisagov/ansible-role-remove-python2 34 | - name: systemd_resolved 35 | src: https://github.com/cisagov/ansible-role-systemd-resolved 36 | - name: upgrade 37 | src: https://github.com/cisagov/ansible-role-upgrade 38 | -------------------------------------------------------------------------------- /.mdl_config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Default state for all rules 4 | default: true 5 | 6 | # MD003/heading-style/header-style - Heading style 7 | MD003: 8 | # Enforce the ATX-closed style of header 9 | style: atx_closed 10 | 11 | # MD004/ul-style - Unordered list style 12 | MD004: 13 | # Enforce dashes for unordered lists 14 | style: dash 15 | 16 | # MD013/line-length - Line length 17 | MD013: 18 | # Do not enforce for code blocks 19 | code_blocks: false 20 | # Do not enforce for tables 21 | tables: false 22 | 23 | # MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the 24 | # same content 25 | MD024: 26 | # Allow headers with the same content as long as they are not in the same 27 | # parent heading 28 | allow_different_nesting: true 29 | 30 | # MD029/ol-prefix - Ordered list item prefix 31 | MD029: 32 | # Enforce the `1.` style for ordered lists 33 | style: one 34 | 35 | # MD033/no-inline-html - Inline HTML 36 | MD033: 37 | # The h1 and img elements are allowed to permit header images 38 | allowed_elements: 39 | - h1 40 | - img 41 | 42 | # MD035/hr-style - Horizontal rule style 43 | MD035: 44 | # Enforce dashes for horizontal rules 45 | style: --- 46 | 47 | # MD046/code-block-style - Code block style 48 | MD046: 49 | # Enforce the fenced style for code blocks 50 | style: fenced 51 | 52 | # MD049/emphasis-style - Emphasis style should be consistent 53 | MD049: 54 | # Enforce asterisks as the style to use for emphasis 55 | style: asterisk 56 | 57 | # MD050/strong-style - Strong style should be consistent 58 | MD050: 59 | # Enforce asterisks as the style to use for strong 60 | style: asterisk 61 | -------------------------------------------------------------------------------- /terraform-build-user/remote_states.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Retrieves state data from Terraform backends. This allows use of the 3 | # root-level outputs of one or more Terraform configurations as input data 4 | # for this configuration. 5 | # ------------------------------------------------------------------------------ 6 | 7 | data "terraform_remote_state" "images_parameterstore" { 8 | backend = "s3" 9 | 10 | config = { 11 | bucket = var.terraform_state_bucket 12 | dynamodb_table = "terraform-state-lock" 13 | encrypt = true 14 | key = "cool-images-parameterstore/terraform.tfstate" 15 | profile = "cool-terraform-backend" 16 | region = "us-east-1" 17 | } 18 | 19 | workspace = terraform.workspace 20 | } 21 | 22 | data "terraform_remote_state" "images" { 23 | backend = "s3" 24 | 25 | config = { 26 | bucket = var.terraform_state_bucket 27 | dynamodb_table = "terraform-state-lock" 28 | encrypt = true 29 | key = "cool-accounts/images.tfstate" 30 | profile = "cool-terraform-backend" 31 | region = "us-east-1" 32 | } 33 | 34 | workspace = terraform.workspace 35 | } 36 | 37 | data "terraform_remote_state" "users" { 38 | backend = "s3" 39 | 40 | config = { 41 | bucket = var.terraform_state_bucket 42 | dynamodb_table = "terraform-state-lock" 43 | encrypt = true 44 | key = "cool-accounts/users.tfstate" 45 | profile = "cool-terraform-backend" 46 | region = "us-east-1" 47 | } 48 | 49 | workspace = terraform.workspace 50 | } 51 | -------------------------------------------------------------------------------- /ami_arm64.pkr.hcl: -------------------------------------------------------------------------------- 1 | source "amazon-ebs" "arm64" { 2 | ami_name = "guacamole-hvm-${local.timestamp}-arm64-ebs" 3 | ami_regions = var.ami_regions 4 | associate_public_ip_address = true 5 | encrypt_boot = true 6 | instance_type = "t4g.small" 7 | kms_key_id = var.build_region_kms 8 | launch_block_device_mappings { 9 | delete_on_termination = true 10 | device_name = "/dev/xvda" 11 | encrypted = true 12 | volume_size = 8 13 | volume_type = "gp3" 14 | } 15 | region = var.build_region 16 | region_kms_key_ids = var.region_kms_keys 17 | skip_create_ami = var.skip_create_ami 18 | source_ami = data.amazon-ami.debian_trixie_arm64.id 19 | ssh_username = "admin" 20 | subnet_filter { 21 | filters = { 22 | "tag:Name" = "AMI Build" 23 | } 24 | } 25 | tags = { 26 | Application = "Guacamole" 27 | Architecture = "arm64" 28 | Base_AMI_Name = data.amazon-ami.debian_trixie_arm64.name 29 | GitHub_Ref_Name = var.github_ref_name 30 | GitHub_Release_URL = var.release_url 31 | GitHub_SHA = var.github_sha 32 | OS_Version = "Debian Trixie" 33 | Pre_Release = var.is_prerelease 34 | Release = var.release_tag 35 | Team = "VM Fusion - Development" 36 | } 37 | # Many Linux distributions are now disallowing the use of RSA keys, 38 | # so it makes sense to use an ED25519 key instead. 39 | temporary_key_pair_type = "ed25519" 40 | vpc_filter { 41 | filters = { 42 | "tag:Name" = "AMI Build" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ami_x86_64.pkr.hcl: -------------------------------------------------------------------------------- 1 | source "amazon-ebs" "x86_64" { 2 | ami_name = "guacamole-hvm-${local.timestamp}-x86_64-ebs" 3 | ami_regions = var.ami_regions 4 | associate_public_ip_address = true 5 | encrypt_boot = true 6 | instance_type = "t3.small" 7 | kms_key_id = var.build_region_kms 8 | launch_block_device_mappings { 9 | delete_on_termination = true 10 | device_name = "/dev/xvda" 11 | encrypted = true 12 | volume_size = 8 13 | volume_type = "gp3" 14 | } 15 | region = var.build_region 16 | region_kms_key_ids = var.region_kms_keys 17 | skip_create_ami = var.skip_create_ami 18 | source_ami = data.amazon-ami.debian_trixie_x86_64.id 19 | ssh_username = "admin" 20 | subnet_filter { 21 | filters = { 22 | "tag:Name" = "AMI Build" 23 | } 24 | } 25 | tags = { 26 | Application = "Guacamole" 27 | Architecture = "x86_64" 28 | Base_AMI_Name = data.amazon-ami.debian_trixie_x86_64.name 29 | GitHub_Ref_Name = var.github_ref_name 30 | GitHub_Release_URL = var.release_url 31 | GitHub_SHA = var.github_sha 32 | OS_Version = "Debian Trixie" 33 | Pre_Release = var.is_prerelease 34 | Release = var.release_tag 35 | Team = "VM Fusion - Development" 36 | } 37 | # Many Linux distributions are now disallowing the use of RSA keys, 38 | # so it makes sense to use an ED25519 key instead. 39 | temporary_key_pair_type = "ed25519" 40 | vpc_filter { 41 | filters = { 42 | "tag:Name" = "AMI Build" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ansible/guacamole.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Guacamole Docker composition 3 | hosts: all 4 | become: true 5 | become_method: ansible.builtin.sudo 6 | tasks: 7 | - name: Install Guacamole Docker composition 8 | ansible.builtin.include_role: 9 | name: guacamole 10 | vars: 11 | # The ">-" combination is critical here. Without the minus 12 | # character these values would have a line return appended 13 | # onto the end of them. 14 | # 15 | # The postgres database username and password for guacamole 16 | guacamole_postgres_username: >- 17 | {{ lookup('aws_ssm', '/guacamole/postgres_username') }} 18 | guacamole_postgres_password: >- 19 | {{ lookup('aws_ssm', '/guacamole/postgres_password') }} 20 | guacamole_private_ssh_key: >- 21 | {{ lookup('aws_ssm', '/vnc/ssh/ed25519_private_key') }} 22 | guacamole_rdp_username: >- 23 | {{ lookup('aws_ssm', '/rdp/username') }} 24 | guacamole_rdp_password: >- 25 | {{ lookup('aws_ssm', '/rdp/password') }} 26 | guacamole_vnc_username: >- 27 | {{ lookup('aws_ssm', '/vnc/username') }} 28 | guacamole_vnc_password: >- 29 | {{ lookup('aws_ssm', '/vnc/password') }} 30 | guacamole_windows_sftp_base: >- 31 | {{ lookup('aws_ssm', '/vnc/sftp/windows_base_directory') }} 32 | # boto3 is needed during deployment to fetch SSL certificates. 33 | # pystache may be used during deployment as part of the process 34 | # that automatically defines Guacamole connections to other 35 | # instances. 36 | - name: Install python3-boto3 and python3-pystache 37 | ansible.builtin.package: 38 | name: 39 | - python3-boto3 40 | - python3-pystache 41 | -------------------------------------------------------------------------------- /terraform-build-user/README.md: -------------------------------------------------------------------------------- 1 | # Build User # 2 | 3 | This directory consists of [Terraform](https://www.terraform.io/) code 4 | that is used to create a build user. This build user is in turn used 5 | by our CI/CD pipeline to create AMIs via [Packer](https://packer.io) 6 | for our various COOL environments. 7 | 8 | See [the overall project documentation](../README.md) for a detailed 9 | description of how this code is intended to be used. 10 | 11 | 12 | ## Requirements ## 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | terraform | ~> 1.1 | 17 | | aws | ~> 6.7 | 18 | 19 | ## Providers ## 20 | 21 | | Name | Version | 22 | |------|---------| 23 | | aws.cool-terraform-backend | ~> 6.7 | 24 | | terraform | n/a | 25 | 26 | ## Modules ## 27 | 28 | | Name | Source | Version | 29 | |------|--------|---------| 30 | | iam\_user | github.com/cisagov/ami-build-iam-user-tf-module | n/a | 31 | 32 | ## Resources ## 33 | 34 | | Name | Type | 35 | |------|------| 36 | | [aws_caller_identity.terraform_backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 37 | | [terraform_remote_state.images](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | 38 | | [terraform_remote_state.images_parameterstore](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | 39 | | [terraform_remote_state.users](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state) | data source | 40 | 41 | ## Inputs ## 42 | 43 | | Name | Description | Type | Default | Required | 44 | |------|-------------|------|---------|:--------:| 45 | | terraform\_state\_bucket | The name of the S3 bucket where Terraform state is stored. | `string` | n/a | yes | 46 | 47 | ## Outputs ## 48 | 49 | No outputs. 50 | 51 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Any ignore directives should be uncommented in downstream projects to disable 4 | # Dependabot updates for the given dependency. Downstream projects will get 5 | # these updates when the pull request(s) in the appropriate skeleton are merged 6 | # and Lineage processes these changes. 7 | 8 | updates: 9 | - directory: / 10 | ignore: 11 | # Managed by cisagov/skeleton-generic 12 | - dependency-name: actions/cache 13 | - dependency-name: actions/checkout 14 | - dependency-name: actions/dependency-review-action 15 | - dependency-name: actions/labeler 16 | - dependency-name: actions/setup-go 17 | - dependency-name: actions/setup-python 18 | - dependency-name: cisagov/action-job-preamble 19 | - dependency-name: cisagov/setup-env-github-action 20 | - dependency-name: crazy-max/ghaction-github-labeler 21 | - dependency-name: github/codeql-action 22 | - dependency-name: hashicorp/setup-packer 23 | - dependency-name: hashicorp/setup-terraform 24 | - dependency-name: mxschmitt/action-tmate 25 | # Managed by cisagov/skeleton-packer 26 | - dependency-name: aws-actions/configure-aws-credentials 27 | labels: 28 | # dependabot default we need to replicate 29 | - dependencies 30 | # This matches our label definition in .github/labels.yml as opposed to 31 | # dependabot's default of `github_actions`. 32 | - github-actions 33 | package-ecosystem: github-actions 34 | schedule: 35 | interval: weekly 36 | 37 | - directory: / 38 | ignore: 39 | # Managed by cisagov/skeleton-packer 40 | - dependency-name: ansible 41 | - dependency-name: ansible-core 42 | package-ecosystem: pip 43 | schedule: 44 | interval: weekly 45 | 46 | - directory: /terraform-build-user 47 | ignore: 48 | # Managed by cisagov/skeleton-packer 49 | - dependency-name: hashicorp/aws 50 | package-ecosystem: terraform 51 | schedule: 52 | interval: weekly 53 | 54 | - directory: /terraform-post-packer 55 | ignore: 56 | # Managed by cisagov/skeleton-packer 57 | - dependency-name: hashicorp/aws 58 | package-ecosystem: terraform 59 | schedule: 60 | interval: weekly 61 | version: 2 62 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | braces: 6 | # Do not allow non-empty flow mappings 7 | forbid: non-empty 8 | # Allow up to one space inside braces. This is required for Ansible compatibility. 9 | max-spaces-inside: 1 10 | 11 | brackets: 12 | # Do not allow non-empty flow sequences 13 | forbid: non-empty 14 | 15 | comments: 16 | # Ensure that inline comments have at least one space before the preceding content. 17 | # This is required for Ansible compatibility. 18 | min-spaces-from-content: 1 19 | 20 | # yamllint does not like it when you comment out different parts of 21 | # dictionaries in a list. You can see 22 | # https://github.com/adrienverge/yamllint/issues/384 for some examples of 23 | # this behavior. 24 | comments-indentation: disable 25 | 26 | indentation: 27 | # Ensure that block sequences inside of a mapping are indented 28 | indent-sequences: true 29 | # Enforce a specific number of spaces 30 | spaces: 2 31 | 32 | # yamllint does not allow inline mappings that exceed the line length by 33 | # default. There are many scenarios where the inline mapping may be a key, 34 | # hash, or other long value that would exceed the line length but cannot 35 | # reasonably be broken across lines. 36 | line-length: 37 | # This rule implies the allow-non-breakable-words rule 38 | allow-non-breakable-inline-mappings: true 39 | # Allows a 10% overage from the default limit of 80 40 | max: 88 41 | 42 | # Using anything other than strings to express octal values can lead to unexpected 43 | # and potentially unsafe behavior. Ansible strongly recommends against such practices 44 | # and these rules are needed for Ansible compatibility. Please see the following for 45 | # more information: 46 | # https://ansible.readthedocs.io/projects/lint/rules/risky-octal/ 47 | octal-values: 48 | # Do not allow explicit octal values (those beginning with a leading 0o). 49 | forbid-explicit-octal: true 50 | # Do not allow implicit octal values (those beginning with a leading 0). 51 | forbid-implicit-octal: true 52 | 53 | quoted-strings: 54 | # Allow disallowed quotes (single quotes) for strings that contain allowed quotes 55 | # (double quotes). 56 | allow-quoted-quotes: true 57 | # Apply these rules to keys in mappings as well 58 | check-keys: true 59 | # We prefer double quotes for strings when they are needed 60 | quote-type: double 61 | # Only require quotes when they are necessary for proper processing 62 | required: only-when-needed 63 | -------------------------------------------------------------------------------- /terraform-post-packer/README.md: -------------------------------------------------------------------------------- 1 | # Post-Packer # 2 | 3 | This directory consists of [Terraform](https://www.terraform.io/) code 4 | that is used to share AMIs with the appropriate AWS accounts. This 5 | code is intended to be run after the CI/CD pipeline has created a new 6 | AMI for a COOL environment. 7 | 8 | See [the overall project documentation](../README.md) for more 9 | details. 10 | 11 | 12 | ## Requirements ## 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | terraform | ~> 1.1 | 17 | | aws | ~> 6.7 | 18 | 19 | ## Providers ## 20 | 21 | | Name | Version | 22 | |------|---------| 23 | | aws | ~> 6.7 | 24 | 25 | ## Modules ## 26 | 27 | | Name | Source | Version | 28 | |------|--------|---------| 29 | | ami\_launch\_permission\_x86\_64 | github.com/cisagov/ami-launch-permission-tf-module | n/a | 30 | 31 | ## Resources ## 32 | 33 | | Name | Type | 34 | |------|------| 35 | | [aws_ami_ids.historical_amis_x86_64](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami_ids) | data source | 36 | | [aws_caller_identity.images](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 37 | 38 | ## Inputs ## 39 | 40 | | Name | Description | Type | Default | Required | 41 | |------|-------------|------|---------|:--------:| 42 | | ami\_share\_account\_name\_regex | A regular expression that matches the names of AWS accounts with which to share the AMIs created by this repository. This variable is used to share the AMIs with accounts that are members of the same AWS Organization as the account that owns the AMIs. | `string` | `"^env[[:digit:]]+"` | no | 43 | | extraorg\_account\_ids | A list of AWS account IDs corresponding to "extra" accounts with which you want to share this AMI (e.g. ["123456789012"]). Normally this variable is used to share an AMI with accounts that are not a member of the same AWS Organization as the account that owns the AMI. | `list(string)` | `[]` | no | 44 | | recent\_ami\_count | The number of most-recent AMIs (per architecture) for which to grant launch permission (e.g. "3"). If this variable is set to three, for example, then accounts will be granted permission to launch the three most recent AMIs (or all most recent AMIs, if there are only one or two of them in existence). | `number` | `12` | no | 45 | 46 | ## Outputs ## 47 | 48 | | Name | Description | 49 | |------|-------------| 50 | | launch\_permissions\_x86\_64 | The cisagov/ami-launch-permission-tf-module for each x86\_64 AMI to which launch permission is being granted. | 51 | 52 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Each entry in this file is a label that will be applied to pull requests 3 | # if there is a match based on the matching rules for the entry. Please see 4 | # the actions/labeler documentation for more information: 5 | # https://github.com/actions/labeler#match-object 6 | # 7 | # Note: Verify that the label you want to use is defined in the 8 | # crazy-max/ghaction-github-labeler configuration file located at 9 | # .github/labels.yml. 10 | 11 | ansible: 12 | - changed-files: 13 | - any-glob-to-any-file: 14 | - ansible/**/*.yml 15 | dependencies: 16 | - changed-files: 17 | - any-glob-to-any-file: 18 | # Add any dependency files used. 19 | - .pre-commit-config.yaml 20 | - "**/.terraform.lock.hcl" 21 | - "**/versions.tf" 22 | - ansible/requirements.yml 23 | - requirements*.txt 24 | - versions.pkr.hcl 25 | docker: 26 | - changed-files: 27 | - any-glob-to-any-file: 28 | - "**/compose*.yml" 29 | - "**/docker-compose*.yml" 30 | - "**/Dockerfile*" 31 | documentation: 32 | - changed-files: 33 | - any-glob-to-any-file: 34 | - "**/*.md" 35 | github-actions: 36 | - changed-files: 37 | - any-glob-to-any-file: 38 | - .github/workflows/** 39 | javascript: 40 | - changed-files: 41 | - any-glob-to-any-file: 42 | - "**/*.js" 43 | packer: 44 | - changed-files: 45 | - any-glob-to-any-file: 46 | - "**/*.pkr.hcl" 47 | python: 48 | - changed-files: 49 | - any-glob-to-any-file: 50 | - "**/*.py" 51 | shell script: 52 | - changed-files: 53 | - any-glob-to-any-file: 54 | # If this project has any shell scripts that do not end in the ".sh" 55 | # extension, add them below. 56 | - "**/*.sh" 57 | - bump-version 58 | - setup-env 59 | terraform: 60 | - changed-files: 61 | - any-glob-to-any-file: 62 | - "**/*.tf" 63 | test: 64 | - changed-files: 65 | - any-glob-to-any-file: 66 | # Add any test-related files or paths. 67 | - .ansible-lint 68 | - .bandit.yml 69 | - .flake8 70 | - .isort.cfg 71 | - .mdl_config.yaml 72 | - .yamllint 73 | - tests/** 74 | typescript: 75 | - changed-files: 76 | - any-glob-to-any-file: 77 | - "**/*.ts" 78 | upstream update: 79 | - head-branch: 80 | # Any Lineage pull requests should use this branch. 81 | - lineage/skeleton 82 | version bump: 83 | - changed-files: 84 | - any-glob-to-any-file: 85 | # Ensure this matches your version tracking file(s). 86 | - version.txt 87 | -------------------------------------------------------------------------------- /terraform-build-user/providers.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # Extract the user name of the caller for use in assumed role session names. 3 | # When using the value of `user_id` we were unable to assume roles using 4 | # `caller_user_name` as their `session_name`. The error message returned by AWS 5 | # did not indicate that the `session_name` was the issue, however when the 6 | # semicolon was removed there were no issues. Please see the documentation at 7 | # https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html 8 | # for information about acceptable characters for the session name. 9 | caller_user_name = replace(data.aws_caller_identity.terraform_backend.user_id, ":", ".") 10 | 11 | tags = { 12 | Application = "guacamole-packer" 13 | Team = "CISA - Development" 14 | } 15 | } 16 | 17 | # Provider that is only used for obtaining the caller identity. 18 | # Note that we cannot use a provider that assumes a role via an ARN from a 19 | # Terraform remote state for this purpose (like we do for all of the other 20 | # providers below). This is because we derive the session name (in the 21 | # assume_role block within the provider) from the caller identity of this 22 | # provider; if we try to do that, it results in a Terraform "Cycle" error. 23 | # Hence, for our caller identity, we use a provider based on a profile that 24 | # must exist for the Terraform backend to work ("cool-terraform-backend"). 25 | provider "aws" { 26 | alias = "cool-terraform-backend" 27 | default_tags { 28 | tags = local.tags 29 | } 30 | profile = "cool-terraform-backend" 31 | region = "us-east-1" 32 | } 33 | 34 | # Default AWS provider (ProvisionAccount for the Users account) 35 | provider "aws" { 36 | assume_role { 37 | role_arn = data.terraform_remote_state.users.outputs.provisionaccount_role.arn 38 | session_name = local.caller_user_name 39 | } 40 | default_tags { 41 | tags = local.tags 42 | } 43 | region = "us-east-1" 44 | } 45 | 46 | # ProvisionEC2AMICreateRoles AWS provider for the Images account 47 | provider "aws" { 48 | alias = "images-ami" 49 | assume_role { 50 | role_arn = data.terraform_remote_state.images.outputs.provisionec2amicreateroles_role.arn 51 | session_name = local.caller_user_name 52 | } 53 | default_tags { 54 | tags = local.tags 55 | } 56 | region = "us-east-1" 57 | } 58 | 59 | # ProvisionParameterStoreReadRoles AWS provider for the Images account 60 | provider "aws" { 61 | alias = "images-ssm" 62 | assume_role { 63 | role_arn = data.terraform_remote_state.images_parameterstore.outputs.provisionparameterstorereadroles_role.arn 64 | session_name = local.caller_user_name 65 | } 66 | default_tags { 67 | tags = local.tags 68 | } 69 | region = "us-east-1" 70 | } 71 | -------------------------------------------------------------------------------- /variables.pkr.hcl: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Required parameters 3 | # 4 | # You must provide a value for each of these parameters. 5 | # ------------------------------------------------------------------------------ 6 | 7 | # ------------------------------------------------------------------------------ 8 | # Optional parameters 9 | # 10 | # These parameters have reasonable defaults. 11 | # ------------------------------------------------------------------------------ 12 | 13 | variable "ami_regions" { 14 | default = [] 15 | description = "The list of AWS regions to copy the AMI to once it has been created. Example: [\"us-east-1\"]" 16 | type = list(string) 17 | } 18 | 19 | variable "build_region" { 20 | default = "us-east-1" 21 | description = "The region in which to retrieve the base AMI from and build the new AMI." 22 | type = string 23 | } 24 | 25 | variable "build_region_kms" { 26 | default = "alias/cool-amis" 27 | description = "The ID or ARN of the KMS key to use for AMI encryption." 28 | type = string 29 | } 30 | 31 | variable "force_install_ansible_requirements" { 32 | default = false 33 | description = "Indicate if the Ansible requirements should be force installed." 34 | type = bool 35 | } 36 | 37 | variable "force_install_ansible_requirements_with_dependencies" { 38 | default = false 39 | description = "Indicate if the Ansible requirements *and* their dependencies should be force installed." 40 | type = bool 41 | } 42 | 43 | variable "github_ref_name" { 44 | default = "" 45 | description = "The GitHub short ref name to use for the tags applied to the created AMI." 46 | type = string 47 | } 48 | 49 | variable "github_sha" { 50 | default = "" 51 | description = "The GitHub commit SHA to use for the tags applied to the created AMI." 52 | type = string 53 | } 54 | variable "is_prerelease" { 55 | default = false 56 | description = "The pre-release status to use for the tags applied to the created AMI." 57 | type = bool 58 | } 59 | 60 | variable "region_kms_keys" { 61 | default = {} 62 | description = "A map of regions to copy the created AMI to and the KMS keys to use for encryption in that region. The keys for this map must match the values provided to the aws_regions variable. Example: {\"us-east-1\": \"alias/example-kms\"}" 63 | type = map(string) 64 | } 65 | 66 | variable "release_tag" { 67 | default = "" 68 | description = "The GitHub release tag to use for the tags applied to the created AMI." 69 | type = string 70 | } 71 | 72 | variable "release_url" { 73 | default = "" 74 | description = "The GitHub release URL to use for the tags applied to the created AMI." 75 | type = string 76 | } 77 | 78 | variable "skip_create_ami" { 79 | default = false 80 | description = "Indicate if Packer should not create the AMI." 81 | type = bool 82 | } 83 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Rather than breaking up descriptions into multiline strings we disable that 3 | # specific rule in yamllint for this file. 4 | # yamllint disable rule:line-length 5 | - color: ff5850 6 | description: Pull requests that update Ansible code 7 | name: ansible 8 | - color: eb6420 9 | description: This issue or pull request is awaiting the outcome of another issue or pull request 10 | name: blocked 11 | - color: "000000" 12 | description: This issue or pull request involves changes to existing functionality 13 | name: breaking change 14 | - color: d73a4a 15 | description: This issue or pull request addresses broken functionality 16 | name: bug 17 | - color: 07648d 18 | description: This issue will be advertised on code.gov's Open Tasks page (https://code.gov/open-tasks) 19 | name: code.gov 20 | - color: 0366d6 21 | description: Pull requests that update a dependency file 22 | name: dependencies 23 | - color: 1d63ed 24 | description: Pull requests that update Docker code 25 | name: docker 26 | - color: 5319e7 27 | description: This issue or pull request improves or adds to documentation 28 | name: documentation 29 | - color: cfd3d7 30 | description: This issue or pull request already exists or is covered in another issue or pull request 31 | name: duplicate 32 | - color: b005bc 33 | description: A high-level objective issue encompassing multiple issues instead of a specific unit of work 34 | name: epic 35 | - color: "000000" 36 | description: Pull requests that update GitHub Actions code 37 | name: github-actions 38 | - color: 0e8a16 39 | description: This issue or pull request is well-defined and good for newcomers 40 | name: good first issue 41 | - color: ff7518 42 | description: Pull request that should count toward Hacktoberfest participation 43 | name: hacktoberfest-accepted 44 | - color: a2eeef 45 | description: This issue or pull request will add or improve functionality, maintainability, or ease of use 46 | name: improvement 47 | - color: fef2c0 48 | description: This issue or pull request is not applicable, incorrect, or obsolete 49 | name: invalid 50 | - color: f0db4f 51 | description: Pull requests that update JavaScript code 52 | name: javascript 53 | - color: ce099a 54 | description: This pull request is ready to merge during the next Lineage Kraken release 55 | name: kraken 🐙 56 | - color: a4fc5d 57 | description: This issue or pull request requires further information 58 | name: need info 59 | - color: fcdb45 60 | description: This pull request is awaiting an action or decision to move forward 61 | name: on hold 62 | - color: 02a8ef 63 | description: Pull requests that update Packer code 64 | name: packer 65 | - color: 3776ab 66 | description: Pull requests that update Python code 67 | name: python 68 | - color: ef476c 69 | description: This issue is a request for information or needs discussion 70 | name: question 71 | - color: d73a4a 72 | description: This issue or pull request addresses a security issue 73 | name: security 74 | - color: 4eaa25 75 | description: Pull requests that update shell scripts 76 | name: shell script 77 | - color: 7b42bc 78 | description: Pull requests that update Terraform code 79 | name: terraform 80 | - color: 00008b 81 | description: This issue or pull request adds or otherwise modifies test code 82 | name: test 83 | - color: 2678c5 84 | description: Pull requests that update TypeScript code 85 | name: typescript 86 | - color: 1d76db 87 | description: This issue or pull request pulls in upstream updates 88 | name: upstream update 89 | - color: d4c5f9 90 | description: This issue or pull request increments the version number 91 | name: version bump 92 | - color: ffffff 93 | description: This issue will not be incorporated 94 | name: wontfix 95 | -------------------------------------------------------------------------------- /.github/workflows/label-prs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Label pull requests 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: 6 | types: 7 | - edited 8 | - opened 9 | - synchronize 10 | 11 | # Set a default shell for any run steps. The `-Eueo pipefail` sets errtrace, 12 | # nounset, errexit, and pipefail. The `-x` will print all commands as they are 13 | # run. Please see the GitHub Actions documentation for more information: 14 | # https://docs.github.com/en/actions/using-jobs/setting-default-values-for-jobs 15 | defaults: 16 | run: 17 | shell: bash -Eueo pipefail -x {0} 18 | 19 | jobs: 20 | diagnostics: 21 | name: Run diagnostics 22 | # This job does not need any permissions 23 | permissions: {} 24 | runs-on: ubuntu-latest 25 | steps: 26 | # Note that a duplicate of this step must be added at the top of 27 | # each job. 28 | - name: Apply standard cisagov job preamble 29 | uses: cisagov/action-job-preamble@v1 30 | with: 31 | check_github_status: "true" 32 | # This functionality is poorly implemented and has been 33 | # causing problems due to the MITM implementation hogging or 34 | # leaking memory. As a result we disable it by default. If 35 | # you want to temporarily enable it, simply set 36 | # monitor_permissions equal to "true". 37 | # 38 | # TODO: Re-enable this functionality when practical. See 39 | # cisagov/skeleton-generic#207 for more details. 40 | monitor_permissions: "false" 41 | output_workflow_context: "true" 42 | # Use a variable to specify the permissions monitoring 43 | # configuration. By default this will yield the 44 | # configuration stored in the cisagov organization-level 45 | # variable, but if you want to use a different configuration 46 | # then simply: 47 | # 1. Create a repository-level variable with the name 48 | # ACTIONS_PERMISSIONS_CONFIG. 49 | # 2. Set this new variable's value to the configuration you 50 | # want to use for this repository. 51 | # 52 | # Note in particular that changing the permissions 53 | # monitoring configuration *does not* require you to modify 54 | # this workflow. 55 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 56 | label: 57 | needs: 58 | - diagnostics 59 | permissions: 60 | # Permissions required by actions/labeler 61 | contents: read 62 | pull-requests: write 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Apply standard cisagov job preamble 66 | uses: cisagov/action-job-preamble@v1 67 | with: 68 | # This functionality is poorly implemented and has been 69 | # causing problems due to the MITM implementation hogging or 70 | # leaking memory. As a result we disable it by default. If 71 | # you want to temporarily enable it, simply set 72 | # monitor_permissions equal to "true". 73 | # 74 | # TODO: Re-enable this functionality when practical. See 75 | # cisagov/skeleton-generic#207 for more details. 76 | monitor_permissions: "false" 77 | # Use a variable to specify the permissions monitoring 78 | # configuration. By default this will yield the 79 | # configuration stored in the cisagov organization-level 80 | # variable, but if you want to use a different configuration 81 | # then simply: 82 | # 1. Create a repository-level variable with the name 83 | # ACTIONS_PERMISSIONS_CONFIG. 84 | # 2. Set this new variable's value to the configuration you 85 | # want to use for this repository. 86 | # 87 | # Note in particular that changing the permissions 88 | # monitoring configuration *does not* require you to modify 89 | # this workflow. 90 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 91 | - name: Apply suitable labels to a pull request 92 | uses: actions/labeler@v6 93 | -------------------------------------------------------------------------------- /terraform-post-packer/main.tf: -------------------------------------------------------------------------------- 1 | # Use aws_caller_identity with the default provider (Images account) 2 | # so we can provide the Images account ID below 3 | data "aws_caller_identity" "images" { 4 | } 5 | 6 | # cisagov/ansible-role-guacamole cannot currently support ARM64 7 | # because the official Guacamole Docker images do not. 8 | # The IDs of all ARM64 cisagov/guacamole-packer AMIs 9 | # data "aws_ami_ids" "historical_amis_arm64" { 10 | # owners = [data.aws_caller_identity.images.account_id] 11 | # 12 | # filter { 13 | # name = "architecture" 14 | # values = ["arm64"] 15 | # } 16 | # 17 | # filter { 18 | # name = "name" 19 | # values = ["guacamole-hvm-*-arm64-ebs"] 20 | # } 21 | # 22 | # filter { 23 | # name = "root-device-type" 24 | # values = ["ebs"] 25 | # } 26 | # 27 | # filter { 28 | # name = "virtualization-type" 29 | # values = ["hvm"] 30 | # } 31 | # } 32 | 33 | # cisagov/ansible-role-guacamole cannot currently support ARM64 34 | # because the official Guacamole Docker images do not. 35 | # Assign launch permissions to the ARM64 AMIs 36 | # module "ami_launch_permission_arm64" { 37 | # # Really we only want the var.recent_ami_count most recent AMIs, but 38 | # # we have to cover the case where there are fewer than that many 39 | # # AMIs in existence. Hence the min()/length() tomfoolery. 40 | # for_each = toset(slice(data.aws_ami_ids.historical_amis_arm64.ids, 0, min(var.recent_ami_count, length(data.aws_ami_ids.historical_amis_arm64.ids)))) 41 | # 42 | # source = "github.com/cisagov/ami-launch-permission-tf-module" 43 | # 44 | # providers = { 45 | # aws = aws 46 | # aws.master = aws.master 47 | # } 48 | # 49 | # account_name_regex = var.ami_share_account_name_regex 50 | # ami_id = each.value 51 | # extraorg_account_ids = var.extraorg_account_ids 52 | # } 53 | 54 | # The IDs of all x86-64 cisagov/guacamole-packer AMIs 55 | data "aws_ami_ids" "historical_amis_x86_64" { 56 | owners = [data.aws_caller_identity.images.account_id] 57 | 58 | filter { 59 | name = "architecture" 60 | values = ["x86_64"] 61 | } 62 | 63 | filter { 64 | name = "name" 65 | values = ["guacamole-hvm-*-x86_64-ebs"] 66 | } 67 | 68 | filter { 69 | name = "root-device-type" 70 | values = ["ebs"] 71 | } 72 | 73 | filter { 74 | name = "virtualization-type" 75 | values = ["hvm"] 76 | } 77 | } 78 | 79 | # This moved block allows us to rename the resources at 80 | # aws_ami_ids.historical_amis to aws_ami_ids.historical_amis_x86_64 81 | # instead of destroying and recreating them with a new name. 82 | # 83 | # TODO: Consider removing this moved block when it is no longer 84 | # needed. See cisagov/skeleton-packer#369 for more details. 85 | moved { 86 | from = aws_ami_ids.historical_amis 87 | to = aws_ami_ids.historical_amis_x86_64 88 | } 89 | 90 | # Assign launch permissions to the x86-64 AMIs 91 | module "ami_launch_permission_x86_64" { 92 | # Really we only want the var.recent_ami_count most recent AMIs, but 93 | # we have to cover the case where there are fewer than that many 94 | # AMIs in existence. Hence the min()/length() tomfoolery. 95 | for_each = toset(slice(data.aws_ami_ids.historical_amis_x86_64.ids, 0, min(var.recent_ami_count, length(data.aws_ami_ids.historical_amis_x86_64.ids)))) 96 | 97 | source = "github.com/cisagov/ami-launch-permission-tf-module" 98 | 99 | providers = { 100 | aws = aws 101 | aws.master = aws.master 102 | } 103 | 104 | account_name_regex = var.ami_share_account_name_regex 105 | ami_id = each.value 106 | extraorg_account_ids = var.extraorg_account_ids 107 | } 108 | 109 | # This moved block allows us to rename the resources at 110 | # module.ami_launch_permission to module.ami_launch_permission_x86_64 111 | # instead of destroying and recreating them with a new name. 112 | # 113 | # TODO: Consider removing this moved block when it is no longer 114 | # needed. See cisagov/skeleton-packer#369 for more details. 115 | moved { 116 | from = module.ami_launch_permission 117 | to = module.ami_launch_permission_x86_64 118 | } 119 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: sync-labels 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | paths: 7 | - .github/labels.yml 8 | - .github/workflows/sync-labels.yml 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | diagnostics: 16 | name: Run diagnostics 17 | # This job does not need any permissions 18 | permissions: {} 19 | runs-on: ubuntu-latest 20 | steps: 21 | # Note that a duplicate of this step must be added at the top of 22 | # each job. 23 | - name: Apply standard cisagov job preamble 24 | uses: cisagov/action-job-preamble@v1 25 | with: 26 | check_github_status: "true" 27 | # This functionality is poorly implemented and has been 28 | # causing problems due to the MITM implementation hogging or 29 | # leaking memory. As a result we disable it by default. If 30 | # you want to temporarily enable it, simply set 31 | # monitor_permissions equal to "true". 32 | # 33 | # TODO: Re-enable this functionality when practical. See 34 | # cisagov/skeleton-generic#207 for more details. 35 | monitor_permissions: "false" 36 | output_workflow_context: "true" 37 | # Use a variable to specify the permissions monitoring 38 | # configuration. By default this will yield the 39 | # configuration stored in the cisagov organization-level 40 | # variable, but if you want to use a different configuration 41 | # then simply: 42 | # 1. Create a repository-level variable with the name 43 | # ACTIONS_PERMISSIONS_CONFIG. 44 | # 2. Set this new variable's value to the configuration you 45 | # want to use for this repository. 46 | # 47 | # Note in particular that changing the permissions 48 | # monitoring configuration *does not* require you to modify 49 | # this workflow. 50 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 51 | labeler: 52 | needs: 53 | - diagnostics 54 | permissions: 55 | # actions/checkout needs this to fetch code 56 | contents: read 57 | # crazy-max/ghaction-github-labeler needs this to manage repository labels 58 | issues: write 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Apply standard cisagov job preamble 62 | uses: cisagov/action-job-preamble@v1 63 | with: 64 | # This functionality is poorly implemented and has been 65 | # causing problems due to the MITM implementation hogging or 66 | # leaking memory. As a result we disable it by default. If 67 | # you want to temporarily enable it, simply set 68 | # monitor_permissions equal to "true". 69 | # 70 | # TODO: Re-enable this functionality when practical. See 71 | # cisagov/skeleton-generic#207 for more details. 72 | monitor_permissions: "false" 73 | # Use a variable to specify the permissions monitoring 74 | # configuration. By default this will yield the 75 | # configuration stored in the cisagov organization-level 76 | # variable, but if you want to use a different configuration 77 | # then simply: 78 | # 1. Create a repository-level variable with the name 79 | # ACTIONS_PERMISSIONS_CONFIG. 80 | # 2. Set this new variable's value to the configuration you 81 | # want to use for this repository. 82 | # 83 | # Note in particular that changing the permissions 84 | # monitoring configuration *does not* require you to modify 85 | # this workflow. 86 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 87 | - uses: actions/checkout@v6 88 | - name: Sync repository labels 89 | if: success() 90 | uses: crazy-max/ghaction-github-labeler@v5 91 | with: 92 | # This is a hideous ternary equivalent so we only do a dry run unless 93 | # this workflow is triggered by the develop branch. 94 | dry-run: ${{ github.ref_name == 'develop' && 'false' || 'true' }} 95 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dependency review 3 | 4 | on: # yamllint disable-line rule:truthy 5 | merge_group: 6 | types: 7 | - checks_requested 8 | pull_request: 9 | 10 | # Set a default shell for any run steps. The `-Eueo pipefail` sets errtrace, 11 | # nounset, errexit, and pipefail. The `-x` will print all commands as they are 12 | # run. Please see the GitHub Actions documentation for more information: 13 | # https://docs.github.com/en/actions/using-jobs/setting-default-values-for-jobs 14 | defaults: 15 | run: 16 | shell: bash -Eueo pipefail -x {0} 17 | 18 | jobs: 19 | diagnostics: 20 | name: Run diagnostics 21 | # This job does not need any permissions 22 | permissions: {} 23 | runs-on: ubuntu-latest 24 | steps: 25 | # Note that a duplicate of this step must be added at the top of 26 | # each job. 27 | - name: Apply standard cisagov job preamble 28 | uses: cisagov/action-job-preamble@v1 29 | with: 30 | check_github_status: "true" 31 | # This functionality is poorly implemented and has been 32 | # causing problems due to the MITM implementation hogging or 33 | # leaking memory. As a result we disable it by default. If 34 | # you want to temporarily enable it, simply set 35 | # monitor_permissions equal to "true". 36 | # 37 | # TODO: Re-enable this functionality when practical. See 38 | # cisagov/skeleton-generic#207 for more details. 39 | monitor_permissions: "false" 40 | output_workflow_context: "true" 41 | # Use a variable to specify the permissions monitoring 42 | # configuration. By default this will yield the 43 | # configuration stored in the cisagov organization-level 44 | # variable, but if you want to use a different configuration 45 | # then simply: 46 | # 1. Create a repository-level variable with the name 47 | # ACTIONS_PERMISSIONS_CONFIG. 48 | # 2. Set this new variable's value to the configuration you 49 | # want to use for this repository. 50 | # 51 | # Note in particular that changing the permissions 52 | # monitoring configuration *does not* require you to modify 53 | # this workflow. 54 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 55 | dependency-review: 56 | name: Dependency review 57 | needs: 58 | - diagnostics 59 | permissions: 60 | # actions/checkout needs this to fetch code 61 | contents: read 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Apply standard cisagov job preamble 65 | uses: cisagov/action-job-preamble@v1 66 | with: 67 | # This functionality is poorly implemented and has been 68 | # causing problems due to the MITM implementation hogging or 69 | # leaking memory. As a result we disable it by default. If 70 | # you want to temporarily enable it, simply set 71 | # monitor_permissions equal to "true". 72 | # 73 | # TODO: Re-enable this functionality when practical. See 74 | # cisagov/skeleton-generic#207 for more details. 75 | monitor_permissions: "false" 76 | # Use a variable to specify the permissions monitoring 77 | # configuration. By default this will yield the 78 | # configuration stored in the cisagov organization-level 79 | # variable, but if you want to use a different configuration 80 | # then simply: 81 | # 1. Create a repository-level variable with the name 82 | # ACTIONS_PERMISSIONS_CONFIG. 83 | # 2. Set this new variable's value to the configuration you 84 | # want to use for this repository. 85 | # 86 | # Note in particular that changing the permissions 87 | # monitoring configuration *does not* require you to modify 88 | # this workflow. 89 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 90 | - id: checkout-repo 91 | name: Checkout the repository 92 | uses: actions/checkout@v6 93 | - id: dependency-review 94 | name: Review dependency changes for vulnerabilities and license changes 95 | uses: actions/dependency-review-action@v4 96 | -------------------------------------------------------------------------------- /bump-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # bump-version [--push] [--label LABEL] (major | minor | patch | prerelease | build | finalize | show) 4 | # bump-version --list-files 5 | 6 | set -o nounset 7 | set -o errexit 8 | set -o pipefail 9 | 10 | # Stores the canonical version for the project. 11 | VERSION_FILE=version.txt 12 | # Files that should be updated with the new version. 13 | VERSION_FILES=("$VERSION_FILE") 14 | 15 | USAGE=$( 16 | cat << END_OF_LINE 17 | Update the version of the project. 18 | 19 | Usage: 20 | ${0##*/} [--push] [--label LABEL] (major | minor | patch | prerelease | build | finalize | show) 21 | ${0##*/} --list-files 22 | ${0##*/} (-h | --help) 23 | 24 | Options: 25 | -h | --help Show this message. 26 | --push Perform a \`git push\` after updating the version. 27 | --label LABEL Specify the label to use when updating the build or prerelease version. 28 | --list-files List the files that will be updated when the version is bumped. 29 | END_OF_LINE 30 | ) 31 | 32 | old_version=$(< "$VERSION_FILE") 33 | # Comment out periods so they are interpreted as periods and don't 34 | # just match any character 35 | old_version_regex=${old_version//\./\\\.} 36 | new_version="$old_version" 37 | 38 | bump_part="" 39 | label="" 40 | commit_prefix="Bump" 41 | with_push=false 42 | commands_with_label=("build" "prerelease") 43 | commands_with_prerelease=("major" "minor" "patch") 44 | with_prerelease=false 45 | 46 | ####################################### 47 | # Display an error message, the help information, and exit with a non-zero status. 48 | # Arguments: 49 | # Error message. 50 | ####################################### 51 | function invalid_option() { 52 | echo "$1" 53 | echo "$USAGE" 54 | exit 1 55 | } 56 | 57 | ####################################### 58 | # Bump the version using the provided command. 59 | # Arguments: 60 | # The version to bump. 61 | # The command to bump the version. 62 | # Returns: 63 | # The new version. 64 | ####################################### 65 | function bump_version() { 66 | local temp_version 67 | temp_version=$(python -c "import semver; print(semver.parse_version_info('$1').${2})") 68 | echo "$temp_version" 69 | } 70 | 71 | if [ $# -eq 0 ]; then 72 | echo "$USAGE" 73 | exit 1 74 | else 75 | while [ $# -gt 0 ]; do 76 | case $1 in 77 | --push) 78 | if [ "$with_push" = true ]; then 79 | invalid_option "Push has already been set." 80 | fi 81 | 82 | with_push=true 83 | shift 84 | ;; 85 | --label) 86 | if [ -n "$label" ]; then 87 | invalid_option "Label has already been set." 88 | fi 89 | 90 | label="$2" 91 | shift 2 92 | ;; 93 | build | finalize | major | minor | patch) 94 | if [ -n "$bump_part" ]; then 95 | invalid_option "Only one version part should be bumped at a time." 96 | fi 97 | 98 | bump_part="$1" 99 | shift 100 | ;; 101 | prerelease) 102 | with_prerelease=true 103 | shift 104 | ;; 105 | show) 106 | echo "$old_version" 107 | exit 0 108 | ;; 109 | -h | --help) 110 | echo "$USAGE" 111 | exit 0 112 | ;; 113 | --list-files) 114 | printf '%s\n' "${VERSION_FILES[@]}" 115 | exit 0 116 | ;; 117 | *) 118 | invalid_option "Invalid option: $1" 119 | ;; 120 | esac 121 | done 122 | fi 123 | 124 | if [ -n "$label" ] && [ "$with_prerelease" = false ] && [[ ! " ${commands_with_label[*]} " =~ [[:space:]]${bump_part}[[:space:]] ]]; then 125 | invalid_option "Setting the label is only allowed for the following commands: ${commands_with_label[*]}" 126 | fi 127 | 128 | if [ "$with_prerelease" = true ] && [ -n "$bump_part" ] && [[ ! " ${commands_with_prerelease[*]} " =~ [[:space:]]${bump_part}[[:space:]] ]]; then 129 | invalid_option "Changing the prerelease is only allowed in conjunction with the following commands: ${commands_with_prerelease[*]}" 130 | fi 131 | 132 | label_option="" 133 | if [ -n "$label" ]; then 134 | label_option="token='$label'" 135 | fi 136 | 137 | if [ -n "$bump_part" ]; then 138 | if [ "$bump_part" = "finalize" ]; then 139 | commit_prefix="Finalize" 140 | bump_command="finalize_version()" 141 | elif [ "$bump_part" = "build" ]; then 142 | bump_command="bump_${bump_part}($label_option)" 143 | else 144 | bump_command="bump_${bump_part}()" 145 | fi 146 | new_version=$(bump_version "$old_version" "$bump_command") 147 | echo Changing version from "$old_version" to "$new_version" 148 | fi 149 | 150 | if [ "$with_prerelease" = true ]; then 151 | bump_command="bump_prerelease($label_option)" 152 | temp_version=$(bump_version "$new_version" "$bump_command") 153 | echo Changing version from "$new_version" to "$temp_version" 154 | new_version="$temp_version" 155 | fi 156 | 157 | tmp_file=/tmp/version.$$ 158 | for version_file in "${VERSION_FILES[@]}"; do 159 | if [ ! -f "$version_file" ]; then 160 | echo Missing expected file: "$version_file" 161 | exit 1 162 | fi 163 | sed "s/$old_version_regex/$new_version/" "$version_file" > $tmp_file 164 | mv $tmp_file "$version_file" 165 | done 166 | 167 | git add "${VERSION_FILES[@]}" 168 | git commit --message "$commit_prefix version from $old_version to $new_version" 169 | 170 | if [ "$with_push" = true ]; then 171 | git push 172 | fi 173 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # For most projects, this workflow file will not need changing; you simply need 3 | # to commit it to your repository. 4 | # 5 | # You may wish to alter this file to override the set of languages analyzed, 6 | # or to provide custom queries or build logic. 7 | name: CodeQL 8 | 9 | # The use of on here as a key is part of the GitHub actions syntax. 10 | # yamllint disable-line rule:truthy 11 | on: 12 | merge_group: 13 | types: 14 | - checks_requested 15 | pull_request: 16 | # The branches here must be a subset of the ones in the push key 17 | branches: 18 | - develop 19 | push: 20 | # Dependabot-triggered push events have read-only access, but uploading code 21 | # scanning requires write access. 22 | branches-ignore: 23 | - dependabot/** 24 | schedule: 25 | - cron: 0 2 * * 6 26 | 27 | jobs: 28 | diagnostics: 29 | name: Run diagnostics 30 | # This job does not need any permissions 31 | permissions: {} 32 | runs-on: ubuntu-latest 33 | steps: 34 | # Note that a duplicate of this step must be added at the top of 35 | # each job. 36 | - name: Apply standard cisagov job preamble 37 | uses: cisagov/action-job-preamble@v1 38 | with: 39 | check_github_status: "true" 40 | # This functionality is poorly implemented and has been 41 | # causing problems due to the MITM implementation hogging or 42 | # leaking memory. As a result we disable it by default. If 43 | # you want to temporarily enable it, simply set 44 | # monitor_permissions equal to "true". 45 | # 46 | # TODO: Re-enable this functionality when practical. See 47 | # cisagov/skeleton-generic#207 for more details. 48 | monitor_permissions: "false" 49 | output_workflow_context: "true" 50 | # Use a variable to specify the permissions monitoring 51 | # configuration. By default this will yield the 52 | # configuration stored in the cisagov organization-level 53 | # variable, but if you want to use a different configuration 54 | # then simply: 55 | # 1. Create a repository-level variable with the name 56 | # ACTIONS_PERMISSIONS_CONFIG. 57 | # 2. Set this new variable's value to the configuration you 58 | # want to use for this repository. 59 | # 60 | # Note in particular that changing the permissions 61 | # monitoring configuration *does not* require you to modify 62 | # this workflow. 63 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 64 | analyze: 65 | name: Analyze 66 | needs: 67 | - diagnostics 68 | runs-on: ubuntu-latest 69 | permissions: 70 | # actions/checkout needs this to fetch code 71 | contents: read 72 | # required for all workflows 73 | security-events: write 74 | strategy: 75 | fail-fast: false 76 | matrix: 77 | # Override automatic language detection by changing the below 78 | # list 79 | # 80 | # Supported options are actions, c-cpp, csharp, go, 81 | # java-kotlin, javascript-typescript, python, ruby, and swift. 82 | language: 83 | - actions 84 | # Learn more... 85 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 86 | 87 | steps: 88 | - name: Apply standard cisagov job preamble 89 | uses: cisagov/action-job-preamble@v1 90 | with: 91 | # This functionality is poorly implemented and has been 92 | # causing problems due to the MITM implementation hogging or 93 | # leaking memory. As a result we disable it by default. If 94 | # you want to temporarily enable it, simply set 95 | # monitor_permissions equal to "true". 96 | # 97 | # TODO: Re-enable this functionality when practical. See 98 | # cisagov/skeleton-generic#207 for more details. 99 | monitor_permissions: "false" 100 | # Use a variable to specify the permissions monitoring 101 | # configuration. By default this will yield the 102 | # configuration stored in the cisagov organization-level 103 | # variable, but if you want to use a different configuration 104 | # then simply: 105 | # 1. Create a repository-level variable with the name 106 | # ACTIONS_PERMISSIONS_CONFIG. 107 | # 2. Set this new variable's value to the configuration you 108 | # want to use for this repository. 109 | # 110 | # Note in particular that changing the permissions 111 | # monitoring configuration *does not* require you to modify 112 | # this workflow. 113 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 114 | 115 | - name: Checkout repository 116 | uses: actions/checkout@v6 117 | 118 | # Initializes the CodeQL tools for scanning. 119 | - name: Initialize CodeQL 120 | uses: github/codeql-action/init@v4 121 | with: 122 | languages: ${{ matrix.language }} 123 | 124 | # Autobuild attempts to build any compiled languages (C/C++, C#, or 125 | # Java). If this step fails, then you should remove it and run the build 126 | # manually (see below). 127 | - name: Autobuild 128 | uses: github/codeql-action/autobuild@v4 129 | 130 | # ℹ️ Command-line programs to run using the OS shell. 131 | # 📚 https://git.io/JvXDl 132 | 133 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 134 | # three lines and modify them (or add more) to build your code if your 135 | # project uses a compiled language 136 | 137 | # - run: | 138 | # make bootstrap 139 | # make release 140 | 141 | - name: Perform CodeQL Analysis 142 | uses: github/codeql-action/analyze@v4 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome # 2 | 3 | We're so glad you're thinking about contributing to this open source 4 | project! If you're unsure or afraid of anything, just ask or submit 5 | the issue or pull request anyway. The worst that can happen is that 6 | you'll be politely asked to change something. We appreciate any sort 7 | of contribution, and don't want a wall of rules to get in the way of 8 | that. 9 | 10 | Before contributing, we encourage you to read our CONTRIBUTING policy 11 | (you are here), our [LICENSE](LICENSE), and our [README](README.md), 12 | all of which should be in this repository. 13 | 14 | ## Issues ## 15 | 16 | If you want to report a bug or request a new feature, the most direct 17 | method is to [create an 18 | issue](https://github.com/cisagov/guacamole-packer/issues) in this 19 | repository. We recommend that you first search through existing 20 | issues (both open and closed) to check if your particular issue has 21 | already been reported. If it has then you might want to add a comment 22 | to the existing issue. If it hasn't then feel free to create a new 23 | one. 24 | 25 | ## Pull requests ## 26 | 27 | If you choose to [submit a pull 28 | request](https://github.com/cisagov/guacamole-packer/pulls), you will 29 | notice that our continuous integration (CI) system runs a fairly 30 | extensive set of linters and syntax checkers. Your pull request may 31 | fail these checks, and that's OK. If you want you can stop there and 32 | wait for us to make the necessary corrections to ensure your code 33 | passes the CI checks. 34 | 35 | If you want to make the changes yourself, or if you want to become a 36 | regular contributor, then you will want to set up 37 | [pre-commit](https://pre-commit.com/) on your local machine. Once you 38 | do that, the CI checks will run locally before you even write your 39 | commit message. This speeds up your development cycle considerably. 40 | 41 | ### Setting up pre-commit ### 42 | 43 | There are a few ways to do this, but we prefer to use 44 | [`pyenv`](https://github.com/pyenv/pyenv) and 45 | [`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv) to 46 | create and manage a Python virtual environment specific to this 47 | project. 48 | 49 | We recommend using the `setup-env` script located in this repository, 50 | as it automates the entire environment configuration process. The 51 | dependencies required to run this script are 52 | [GNU `getopt`](https://github.com/util-linux/util-linux/blob/master/misc-utils/getopt.1.adoc), 53 | [`pyenv`](https://github.com/pyenv/pyenv), and [`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv). 54 | If these tools are already configured on your system, you can simply run the 55 | following command: 56 | 57 | ```console 58 | ./setup-env 59 | ``` 60 | 61 | Otherwise, follow the steps below to manually configure your 62 | environment. 63 | 64 | #### Installing and using GNU `getopt`, `pyenv`, and `pyenv-virtualenv` #### 65 | 66 | On macOS, we recommend installing [brew](https://brew.sh/). Then 67 | installation is as simple as `brew install gnu-getopt pyenv pyenv-virtualenv` and 68 | adding this to your profile: 69 | 70 | ```bash 71 | # GNU getopt must be explicitly added to the path since it is 72 | # keg-only (https://docs.brew.sh/FAQ#what-does-keg-only-mean) 73 | export PATH="$(brew --prefix)/opt/gnu-getopt/bin:$PATH" 74 | 75 | # Setup pyenv 76 | export PYENV_ROOT="$HOME/.pyenv" 77 | export PATH="$PYENV_ROOT/bin:$PATH" 78 | eval "$(pyenv init --path)" 79 | eval "$(pyenv init -)" 80 | eval "$(pyenv virtualenv-init -)" 81 | ``` 82 | 83 | For Linux, Windows Subsystem for Linux (WSL), or macOS (if you 84 | don't want to use `brew`) you can use 85 | [pyenv/pyenv-installer](https://github.com/pyenv/pyenv-installer) to 86 | install the necessary tools. Before running this ensure that you have 87 | installed the prerequisites for your platform according to the 88 | [`pyenv` wiki 89 | page](https://github.com/pyenv/pyenv/wiki/common-build-problems). 90 | GNU `getopt` is included in most Linux distributions as part of the 91 | [`util-linux`](https://github.com/util-linux/util-linux) package. 92 | 93 | On WSL you should treat your platform as whatever Linux distribution 94 | you've chosen to install. 95 | 96 | Once you have installed `pyenv` you will need to add the following 97 | lines to your `.bash_profile` (or `.profile`): 98 | 99 | ```bash 100 | export PYENV_ROOT="$HOME/.pyenv" 101 | export PATH="$PYENV_ROOT/bin:$PATH" 102 | eval "$(pyenv init --path)" 103 | ``` 104 | 105 | and then add the following lines to your `.bashrc`: 106 | 107 | ```bash 108 | eval "$(pyenv init -)" 109 | eval "$(pyenv virtualenv-init -)" 110 | ``` 111 | 112 | If you want more information about setting up `pyenv` once installed, please run 113 | 114 | ```console 115 | pyenv init 116 | ``` 117 | 118 | and 119 | 120 | ```console 121 | pyenv virtualenv-init 122 | ``` 123 | 124 | for the current configuration instructions. 125 | 126 | If you are using a shell other than `bash` you should follow the 127 | instructions that the `pyenv-installer` script outputs. 128 | 129 | You will need to reload your shell for these changes to take effect so 130 | you can begin to use `pyenv`. 131 | 132 | For a list of Python versions that are already installed and ready to 133 | use with `pyenv`, use the command `pyenv versions`. To see a list of 134 | the Python versions available to be installed and used with `pyenv` 135 | use the command `pyenv install --list`. You can read more about 136 | the [many things that `pyenv` can do](https://github.com/pyenv/pyenv/blob/master/COMMANDS.md). 137 | See the [usage information](https://github.com/pyenv/pyenv-virtualenv#usage) 138 | for the additional capabilities that pyenv-virtualenv adds to the `pyenv` 139 | command. 140 | 141 | #### Creating the Python virtual environment #### 142 | 143 | Once `pyenv` and `pyenv-virtualenv` are installed on your system, you 144 | can create and configure the Python virtual environment with these 145 | commands: 146 | 147 | ```console 148 | cd guacamole-packer 149 | pyenv virtualenv guacamole-packer 150 | pyenv local guacamole-packer 151 | pip install --requirement requirements-dev.txt 152 | ``` 153 | 154 | #### Installing the pre-commit hook #### 155 | 156 | Now setting up pre-commit is as simple as: 157 | 158 | ```console 159 | pre-commit install 160 | ``` 161 | 162 | At this point the pre-commit checks will run against any files that 163 | you attempt to commit. If you want to run the checks against the 164 | entire repo, just execute `pre-commit run --all-files`. 165 | 166 | ### Running unit and system tests ### 167 | 168 | In addition to the pre-commit checks the CI system will run the suite 169 | of unit and system tests that are included with this project. To run 170 | these tests locally execute `pytest` from the root of the project. 171 | 172 | We encourage any updates to these tests to improve the overall code 173 | coverage. If your pull request adds new functionality we would 174 | appreciate it if you extend existing test cases, or add new ones to 175 | exercise the newly added code. 176 | 177 | ## Public domain ## 178 | 179 | This project is in the public domain within the United States, and 180 | copyright and related rights in the work worldwide are waived through 181 | the [CC0 1.0 Universal public domain 182 | dedication](https://creativecommons.org/publicdomain/zero/1.0/). 183 | 184 | All contributions to this project will be released under the CC0 185 | dedication. By submitting a pull request, you are agreeing to comply 186 | with this waiver of copyright interest. 187 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: prerelease 3 | 4 | on: # yamllint disable-line rule:truthy 5 | release: 6 | types: 7 | - prereleased 8 | 9 | env: 10 | AWS_DEFAULT_REGION: us-east-1 11 | # We have seen some failures of packer init in GitHub Actions due to 12 | # rate limiting when pulling Packer plugins from GitHub. Having 13 | # Packer use a GitHub API token should reduce this. The rate 14 | # limiting for unauthenticated requests is 60 requests/hour while 15 | # the rate limiting for authenticated requests is 5000 16 | # requests/hour. 17 | PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | PIP_CACHE_DIR: ~/.cache/pip 19 | RUN_TMATE: ${{ secrets.RUN_TMATE }} 20 | 21 | jobs: 22 | diagnostics: 23 | name: Run diagnostics 24 | # This job does not need any permissions 25 | permissions: {} 26 | runs-on: ubuntu-latest 27 | steps: 28 | # Note that a duplicate of this step must be added at the top of 29 | # each job. 30 | - name: Apply standard cisagov job preamble 31 | uses: cisagov/action-job-preamble@v1 32 | with: 33 | check_github_status: "true" 34 | # This functionality is poorly implemented and has been 35 | # causing problems due to the MITM implementation hogging or 36 | # leaking memory. As a result we disable it by default. If 37 | # you want to temporarily enable it, simply set 38 | # monitor_permissions equal to "true". 39 | # 40 | # TODO: Re-enable this functionality when practical. See 41 | # cisagov/skeleton-packer#411 for more details. 42 | monitor_permissions: "false" 43 | output_workflow_context: "true" 44 | # Use a variable to specify the permissions monitoring 45 | # configuration. By default this will yield the 46 | # configuration stored in the cisagov organization-level 47 | # variable, but if you want to use a different configuration 48 | # then simply: 49 | # 1. Create a repository-level variable with the name 50 | # ACTIONS_PERMISSIONS_CONFIG. 51 | # 2. Set this new variable's value to the configuration you 52 | # want to use for this repository. 53 | # 54 | # Note in particular that changing the permissions 55 | # monitoring configuration *does not* require you to modify 56 | # this workflow. 57 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 58 | prerelease: 59 | environment: staging-a 60 | needs: 61 | - diagnostics 62 | permissions: 63 | # actions/checkout needs this to fetch code 64 | contents: read 65 | runs-on: ubuntu-latest 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | architecture: 70 | # cisagov/ansible-role-guacamole cannot currently support 71 | # ARM64 because the official Guacamole Docker images do not. 72 | # - arm64 73 | - x86_64 74 | steps: 75 | - name: Apply standard cisagov job preamble 76 | uses: cisagov/action-job-preamble@v1 77 | with: 78 | # This functionality is poorly implemented and has been 79 | # causing problems due to the MITM implementation hogging or 80 | # leaking memory. As a result we disable it by default. If 81 | # you want to temporarily enable it, simply set 82 | # monitor_permissions equal to "true". 83 | # 84 | # TODO: Re-enable this functionality when practical. See 85 | # cisagov/skeleton-packer#411 for more details. 86 | monitor_permissions: "false" 87 | # Use a variable to specify the permissions monitoring 88 | # configuration. By default this will yield the 89 | # configuration stored in the cisagov organization-level 90 | # variable, but if you want to use a different configuration 91 | # then simply: 92 | # 1. Create a repository-level variable with the name 93 | # ACTIONS_PERMISSIONS_CONFIG. 94 | # 2. Set this new variable's value to the configuration you 95 | # want to use for this repository. 96 | # 97 | # Note in particular that changing the permissions 98 | # monitoring configuration *does not* require you to modify 99 | # this workflow. 100 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 101 | - id: setup-env 102 | uses: cisagov/setup-env-github-action@v1 103 | - uses: actions/checkout@v6 104 | - id: setup-python 105 | uses: actions/setup-python@v6 106 | with: 107 | python-version: ${{ steps.setup-env.outputs.python-version }} 108 | - uses: actions/cache@v4 109 | env: 110 | BASE_CACHE_KEY: ${{ github.job }}-${{ runner.os }}-\ 111 | py${{ steps.setup-python.outputs.python-version }}-\ 112 | packer${{ steps.setup-env.outputs.packer-version }}-\ 113 | tf-${{ steps.setup-env.outputs.terraform-version }}- 114 | with: 115 | path: | 116 | ${{ env.PIP_CACHE_DIR }} 117 | key: ${{ env.BASE_CACHE_KEY }}\ 118 | ${{ hashFiles('**/requirements.txt') }} 119 | restore-keys: | 120 | ${{ env.BASE_CACHE_KEY }} 121 | - uses: hashicorp/setup-packer@v3 122 | with: 123 | version: ${{ steps.setup-env.outputs.packer-version }} 124 | - uses: hashicorp/setup-terraform@v3 125 | with: 126 | terraform_version: ${{ steps.setup-env.outputs.terraform-version }} 127 | - name: Install dependencies 128 | run: | 129 | python -m pip install --upgrade pip 130 | pip install --upgrade \ 131 | --requirement requirements.txt 132 | - name: Assume AWS build role 133 | uses: aws-actions/configure-aws-credentials@v5 134 | with: 135 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 136 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 137 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 138 | role-to-assume: ${{ secrets.BUILD_ROLE_TO_ASSUME }} 139 | role-duration-seconds: 3600 140 | # When called by Packer, Ansible will find /usr/bin/python3 and 141 | # use it; therefore, we must ensure that /usr/bin/python3 points 142 | # to the version of Python that we installed in the 143 | # actions/setup-python step above. This can hose other tasks 144 | # that are expecting to find the system Python at that location, 145 | # though, so we undo this change after running Packer. 146 | - name: Create a /usr/bin/python3 symlink to the installed python 147 | run: | 148 | sudo mv /usr/bin/python3 /usr/bin/python3-default 149 | sudo ln -s ${{ env.pythonLocation }}/bin/python3 \ 150 | /usr/bin/python3 151 | - name: Install Packer plugins 152 | run: packer init . 153 | - name: Create machine image 154 | run: | 155 | packer build -only amazon-ebs.${{ matrix.architecture }} \ 156 | -timestamp-ui \ 157 | -var github_ref_name=${{ github.ref_name }} \ 158 | -var github_sha=${{ github.sha }} \ 159 | -var is_prerelease=${{ github.event.release.prerelease }} \ 160 | -var release_tag=${{ github.event.release.tag_name }} \ 161 | -var release_url=${{ github.event.release.html_url }} \ 162 | . 163 | - name: Remove /usr/bin/python3 symlink to the installed python 164 | run: | 165 | sudo mv /usr/bin/python3-default /usr/bin/python3 166 | - name: Setup tmate debug session 167 | uses: mxschmitt/action-tmate@v3 168 | if: env.RUN_TMATE 169 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ci: 3 | # Do not commit changes from running pre-commit for pull requests. 4 | autofix_prs: false 5 | # Autoupdate hooks weekly (this is the default). 6 | autoupdate_schedule: weekly 7 | 8 | default_language_version: 9 | # force all unspecified python hooks to run python3 10 | python: python3 11 | 12 | repos: 13 | # Check the pre-commit configuration 14 | - repo: meta 15 | hooks: 16 | - id: check-useless-excludes 17 | 18 | - repo: https://github.com/pre-commit/pre-commit-hooks 19 | rev: v6.0.0 20 | hooks: 21 | - id: check-case-conflict 22 | - id: check-executables-have-shebangs 23 | - id: check-json 24 | - id: check-merge-conflict 25 | - id: check-shebang-scripts-are-executable 26 | - id: check-symlinks 27 | - id: check-toml 28 | - id: check-vcs-permalinks 29 | - id: check-xml 30 | - id: debug-statements 31 | - id: destroyed-symlinks 32 | - id: detect-aws-credentials 33 | args: 34 | - --allow-missing-credentials 35 | - id: detect-private-key 36 | - id: end-of-file-fixer 37 | - id: mixed-line-ending 38 | args: 39 | - --fix=lf 40 | - id: pretty-format-json 41 | args: 42 | - --autofix 43 | - id: requirements-txt-fixer 44 | - id: trailing-whitespace 45 | 46 | # Text file hooks 47 | - repo: https://github.com/igorshubovych/markdownlint-cli 48 | rev: v0.45.0 49 | hooks: 50 | - id: markdownlint 51 | args: 52 | - --config=.mdl_config.yaml 53 | - repo: https://github.com/rbubley/mirrors-prettier 54 | rev: v3.6.2 55 | hooks: 56 | - id: prettier 57 | - repo: https://github.com/adrienverge/yamllint 58 | rev: v1.37.1 59 | hooks: 60 | - id: yamllint 61 | args: 62 | - --strict 63 | 64 | # GitHub Actions hooks 65 | - repo: https://github.com/python-jsonschema/check-jsonschema 66 | rev: 0.35.0 67 | hooks: 68 | - id: check-github-actions 69 | - id: check-github-workflows 70 | 71 | # pre-commit hooks 72 | - repo: https://github.com/pre-commit/pre-commit 73 | rev: v4.4.0 74 | hooks: 75 | - id: validate_manifest 76 | 77 | # Go hooks 78 | - repo: https://github.com/TekWizely/pre-commit-golang 79 | rev: v1.0.0-rc.4 80 | hooks: 81 | # Go Build 82 | - id: go-build-repo-mod 83 | # Style Checkers 84 | - id: go-critic 85 | # goimports 86 | - id: go-imports-repo 87 | args: 88 | # Write changes to files 89 | - -w 90 | # Go Mod Tidy 91 | - id: go-mod-tidy-repo 92 | # GoSec 93 | - id: go-sec-repo-mod 94 | # StaticCheck 95 | - id: go-staticcheck-repo-mod 96 | # Go Test 97 | - id: go-test-repo-mod 98 | # Go Vet 99 | - id: go-vet-repo-mod 100 | # Nix hooks 101 | - repo: https://github.com/nix-community/nixpkgs-fmt 102 | rev: v1.3.0 103 | hooks: 104 | - id: nixpkgs-fmt 105 | 106 | # Shell script hooks 107 | - repo: https://github.com/scop/pre-commit-shfmt 108 | rev: v3.12.0-2 109 | hooks: 110 | - id: shfmt 111 | args: 112 | # List files that will be formatted 113 | - --list 114 | # Write result to file instead of stdout 115 | - --write 116 | # Indent by two spaces 117 | - --indent 118 | - "2" 119 | # Binary operators may start a line 120 | - --binary-next-line 121 | # Switch cases are indented 122 | - --case-indent 123 | # Redirect operators are followed by a space 124 | - --space-redirects 125 | - repo: https://github.com/shellcheck-py/shellcheck-py 126 | rev: v0.11.0.1 127 | hooks: 128 | - id: shellcheck 129 | 130 | # Python hooks 131 | # Run bandit on the "tests" tree with a configuration 132 | - repo: https://github.com/PyCQA/bandit 133 | rev: 1.9.1 134 | hooks: 135 | - id: bandit 136 | name: bandit (tests tree) 137 | files: tests 138 | args: 139 | - --config=.bandit.yml 140 | # Run bandit on everything except the "tests" tree 141 | - repo: https://github.com/PyCQA/bandit 142 | rev: 1.9.1 143 | hooks: 144 | - id: bandit 145 | name: bandit (everything else) 146 | exclude: tests 147 | - repo: https://github.com/psf/black-pre-commit-mirror 148 | rev: 25.11.0 149 | hooks: 150 | - id: black 151 | - repo: https://github.com/PyCQA/flake8 152 | rev: 7.3.0 153 | hooks: 154 | - id: flake8 155 | additional_dependencies: 156 | - flake8-docstrings==1.7.0 157 | - repo: https://github.com/PyCQA/isort 158 | rev: 7.0.0 159 | hooks: 160 | - id: isort 161 | - repo: https://github.com/pre-commit/mirrors-mypy 162 | rev: v1.18.2 163 | hooks: 164 | - id: mypy 165 | - repo: https://github.com/pypa/pip-audit 166 | rev: v2.9.0 167 | hooks: 168 | - id: pip-audit 169 | args: 170 | # We have to ignore this vulnerability since we need to pin 171 | # to ansible 10 for now to support our CyHy code that must 172 | # still run on Debian Buster. This vulnerability is fixed 173 | # in ansible>=12. 174 | # 175 | # This isn't a big deal since the vulnerability only impacts 176 | # users of the Keycloak modules in 177 | # ansible.community.general, and we don't use these modules. 178 | # 179 | # TODO: Remove this when it becomes possible. See 180 | # cisagov/skeleton-packer#486 for more details. 181 | - --ignore-vuln 182 | - GHSA-8ggh-xwr9-3373 183 | # Add any pip requirements files to scan 184 | - --requirement 185 | - requirements-dev.txt 186 | - --requirement 187 | - requirements-test.txt 188 | - --requirement 189 | - requirements.txt 190 | - repo: https://github.com/asottile/pyupgrade 191 | rev: v3.21.1 192 | hooks: 193 | - id: pyupgrade 194 | args: 195 | # Python 3.10 is currently the oldest non-EOL version of 196 | # Python, so we want to apply all rules that apply to this 197 | # version or later. See here for more details: 198 | # https://www.gyford.com/phil/writing/2025/08/26/how-to-use-pyupgrade/ 199 | - --py310-plus 200 | 201 | # Ansible hooks 202 | - repo: https://github.com/ansible/ansible-lint 203 | rev: v25.11.1 204 | hooks: 205 | - id: ansible-lint 206 | additional_dependencies: 207 | # On its own ansible-lint does not pull in ansible, only 208 | # ansible-core. Therefore, if an Ansible module lives in 209 | # ansible instead of ansible-core, the linter will complain 210 | # that the module is unknown. In these cases it is 211 | # necessary to add the ansible package itself as an 212 | # additional dependency, with the same pinning as is done in 213 | # requirements-test.txt of cisagov/skeleton-ansible-role. 214 | # 215 | # Version 10 is required because the pip-audit pre-commit 216 | # hook identifies a vulnerability in ansible-core 2.16.13, 217 | # but all versions of ansible 9 have a dependency on 218 | # ~=2.16.X. 219 | # - ansible>=10,<11 220 | # ansible-core<2.17.7 suffers from GHSA-99w6-3xph-cx78. 221 | # 222 | # Note that any changes made to this dependency must also be 223 | # made in requirements.txt in cisagov/skeleton-packer and 224 | # requirements-test.txt in cisagov/skeleton-ansible-role. 225 | - ansible-core>=2.17.7 226 | 227 | # Terraform hooks 228 | - repo: https://github.com/antonbabenko/pre-commit-terraform 229 | rev: v1.103.0 230 | hooks: 231 | - id: terraform_fmt 232 | - id: terraform_validate 233 | 234 | # Docker hooks 235 | - repo: https://github.com/IamTheFij/docker-pre-commit 236 | rev: v3.0.1 237 | hooks: 238 | - id: docker-compose-check 239 | 240 | # Packer hooks 241 | - repo: https://github.com/cisagov/pre-commit-packer 242 | rev: v0.3.1 243 | hooks: 244 | - id: packer_fmt 245 | - id: packer_validate 246 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release 3 | 4 | on: # yamllint disable-line rule:truthy 5 | release: 6 | types: 7 | - released 8 | 9 | env: 10 | AWS_DEFAULT_REGION: us-east-1 11 | # We have seen some failures of packer init in GitHub Actions due to 12 | # rate limiting when pulling Packer plugins from GitHub. Having 13 | # Packer use a GitHub API token should reduce this. The rate 14 | # limiting for unauthenticated requests is 60 requests/hour while 15 | # the rate limiting for authenticated requests is 5000 16 | # requests/hour. 17 | PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | # Do not copy the AMI to other regions until we have figured out a 19 | # workable mechanism for creating and managing AMI KMS keys in other 20 | # regions. 21 | # See https://github.com/cisagov/cool-system/issues/18 for details. 22 | # COPY_REGIONS_KMS_MAP: "us-east-2:alias/cool-amis, 23 | # us-west-1:alias/cool-amis, 24 | # us-west-2:alias/cool-amis" 25 | PIP_CACHE_DIR: ~/.cache/pip 26 | RUN_TMATE: ${{ secrets.RUN_TMATE }} 27 | 28 | jobs: 29 | diagnostics: 30 | name: Run diagnostics 31 | # This job does not need any permissions 32 | permissions: {} 33 | runs-on: ubuntu-latest 34 | steps: 35 | # Note that a duplicate of this step must be added at the top of 36 | # each job. 37 | - name: Apply standard cisagov job preamble 38 | uses: cisagov/action-job-preamble@v1 39 | with: 40 | check_github_status: "true" 41 | # This functionality is poorly implemented and has been 42 | # causing problems due to the MITM implementation hogging or 43 | # leaking memory. As a result we disable it by default. If 44 | # you want to temporarily enable it, simply set 45 | # monitor_permissions equal to "true". 46 | # 47 | # TODO: Re-enable this functionality when practical. See 48 | # cisagov/skeleton-packer#411 for more details. 49 | monitor_permissions: "false" 50 | output_workflow_context: "true" 51 | # Use a variable to specify the permissions monitoring 52 | # configuration. By default this will yield the 53 | # configuration stored in the cisagov organization-level 54 | # variable, but if you want to use a different configuration 55 | # then simply: 56 | # 1. Create a repository-level variable with the name 57 | # ACTIONS_PERMISSIONS_CONFIG. 58 | # 2. Set this new variable's value to the configuration you 59 | # want to use for this repository. 60 | # 61 | # Note in particular that changing the permissions 62 | # monitoring configuration *does not* require you to modify 63 | # this workflow. 64 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 65 | release: 66 | environment: production 67 | needs: 68 | - diagnostics 69 | permissions: 70 | # actions/checkout needs this to fetch code 71 | contents: read 72 | runs-on: ubuntu-latest 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | architecture: 77 | # cisagov/ansible-role-guacamole cannot currently support 78 | # ARM64 because the official Guacamole Docker images do not. 79 | # - arm64 80 | - x86_64 81 | steps: 82 | - name: Apply standard cisagov job preamble 83 | uses: cisagov/action-job-preamble@v1 84 | with: 85 | # This functionality is poorly implemented and has been 86 | # causing problems due to the MITM implementation hogging or 87 | # leaking memory. As a result we disable it by default. If 88 | # you want to temporarily enable it, simply set 89 | # monitor_permissions equal to "true". 90 | # 91 | # TODO: Re-enable this functionality when practical. See 92 | # cisagov/skeleton-packer#411 for more details. 93 | monitor_permissions: "false" 94 | # Use a variable to specify the permissions monitoring 95 | # configuration. By default this will yield the 96 | # configuration stored in the cisagov organization-level 97 | # variable, but if you want to use a different configuration 98 | # then simply: 99 | # 1. Create a repository-level variable with the name 100 | # ACTIONS_PERMISSIONS_CONFIG. 101 | # 2. Set this new variable's value to the configuration you 102 | # want to use for this repository. 103 | # 104 | # Note in particular that changing the permissions 105 | # monitoring configuration *does not* require you to modify 106 | # this workflow. 107 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 108 | - id: setup-env 109 | uses: cisagov/setup-env-github-action@v1 110 | - uses: actions/checkout@v6 111 | - id: setup-python 112 | uses: actions/setup-python@v6 113 | with: 114 | python-version: ${{ steps.setup-env.outputs.python-version }} 115 | - uses: actions/cache@v4 116 | env: 117 | BASE_CACHE_KEY: ${{ github.job }}-${{ runner.os }}-\ 118 | py${{ steps.setup-python.outputs.python-version }}-\ 119 | packer${{ steps.setup-env.outputs.packer-version }}-\ 120 | tf-${{ steps.setup-env.outputs.terraform-version }}- 121 | with: 122 | path: | 123 | ${{ env.PIP_CACHE_DIR }} 124 | key: ${{ env.BASE_CACHE_KEY }}\ 125 | ${{ hashFiles('**/requirements.txt') }} 126 | restore-keys: | 127 | ${{ env.BASE_CACHE_KEY }} 128 | - uses: hashicorp/setup-packer@v3 129 | with: 130 | version: ${{ steps.setup-env.outputs.packer-version }} 131 | - uses: hashicorp/setup-terraform@v3 132 | with: 133 | terraform_version: ${{ steps.setup-env.outputs.terraform-version }} 134 | - name: Install dependencies 135 | run: | 136 | python -m pip install --upgrade pip 137 | pip install --upgrade \ 138 | --requirement requirements.txt 139 | # Do not copy the AMI to other regions until we have figured out a 140 | # workable mechanism for creating and managing AMI KMS keys in other 141 | # regions. 142 | # See https://github.com/cisagov/cool-system/issues/18 for details. 143 | # - name: Add copy regions to packer configuration 144 | # run: | 145 | # echo $COPY_REGIONS_KMS_MAP | \ 146 | # ./patch_packer_config.py variables.pkr.hcl 147 | - name: Assume AWS build role 148 | uses: aws-actions/configure-aws-credentials@v5 149 | with: 150 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 151 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 152 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 153 | role-to-assume: ${{ secrets.BUILD_ROLE_TO_ASSUME }} 154 | role-duration-seconds: 3600 155 | # When called by Packer, Ansible will find /usr/bin/python3 and 156 | # use it; therefore, we must ensure that /usr/bin/python3 points 157 | # to the version of Python that we installed in the 158 | # actions/setup-python step above. This can hose other tasks 159 | # that are expecting to find the system Python at that location, 160 | # though, so we undo this change after running Packer. 161 | - name: Create a /usr/bin/python3 symlink to the installed python 162 | run: | 163 | sudo mv /usr/bin/python3 /usr/bin/python3-default 164 | sudo ln -s ${{ env.pythonLocation }}/bin/python3 \ 165 | /usr/bin/python3 166 | - name: Install Packer plugins 167 | run: packer init . 168 | - name: Create machine image 169 | run: | 170 | packer build -only amazon-ebs.${{ matrix.architecture }} \ 171 | -timestamp-ui \ 172 | -var github_ref_name=${{ github.ref_name }} \ 173 | -var github_sha=${{ github.sha }} \ 174 | -var is_prerelease=${{ github.event.release.prerelease }} \ 175 | -var release_tag=${{ github.event.release.tag_name }} \ 176 | -var release_url=${{ github.event.release.html_url }} \ 177 | . 178 | - name: Remove /usr/bin/python3 symlink to the installed python 179 | run: | 180 | sudo mv /usr/bin/python3-default /usr/bin/python3 181 | - name: Setup tmate debug session 182 | uses: mxschmitt/action-tmate@v3 183 | if: env.RUN_TMATE 184 | -------------------------------------------------------------------------------- /setup-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o nounset 4 | set -o errexit 5 | set -o pipefail 6 | 7 | USAGE=$( 8 | cat << 'END_OF_LINE' 9 | Configure a development environment for this repository. 10 | 11 | It does the following: 12 | - Allows the user to specify the Python version to use for the virtual environment. 13 | - Allows the user to specify a name for the virtual environment. 14 | - Verifies pyenv and pyenv-virtualenv are installed. 15 | - Creates the Python virtual environment. 16 | - Configures the activation of the virtual enviroment for the repo directory. 17 | - Installs the requirements needed for development. 18 | - Installs git pre-commit hooks. 19 | - Configures git remotes for upstream "lineage" repositories. 20 | 21 | Usage: 22 | setup-env [--venv-name venv_name] [--python-version python_version] 23 | setup-env (-h | --help) 24 | 25 | Options: 26 | -f | --force Delete virtual enviroment if it already exists. 27 | -h | --help Show this message. 28 | -i | --install-hooks Install hook environments for all environments in the 29 | pre-commit config file. 30 | -l | --list-versions List available Python versions and select one interactively. 31 | -v | --venv-name Specify the name of the virtual environment. 32 | -p | --python-version Specify the Python version for the virtual environment. 33 | 34 | END_OF_LINE 35 | ) 36 | 37 | # Display pyenv's installed Python versions 38 | python_versions() { 39 | pyenv versions --bare --skip-aliases --skip-envs 40 | } 41 | 42 | check_python_version() { 43 | local version=$1 44 | 45 | # This is a valid regex for semantically correct Python version strings. 46 | # For more information see here: 47 | # https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string 48 | # Break down the regex into readable parts major.minor.patch 49 | local major="0|[1-9]\d*" 50 | local minor="0|[1-9]\d*" 51 | local patch="0|[1-9]\d*" 52 | 53 | # Splitting the prerelease part for readability 54 | # Start of the prerelease 55 | local prerelease="(?:-" 56 | # Numeric or alphanumeric identifiers 57 | local prerelease+="(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)" 58 | # Additional dot-separated identifiers 59 | local prerelease+="(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*" 60 | # End of the prerelease, making it optional 61 | local prerelease+=")?" 62 | # Optional build metadata 63 | local build="(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?" 64 | 65 | # Final regex composed of parts 66 | local regex="^($major)\.($minor)\.($patch)$prerelease$build$" 67 | 68 | # This checks if the Python version does not match the regex pattern specified in $regex, 69 | # using Perl for regex matching. If the pattern is not found, then prompt the user with 70 | # the invalid version message. 71 | if ! echo "$version" | perl -ne "exit(!/$regex/)"; then 72 | echo "Invalid version of Python: Python follows semantic versioning," \ 73 | "so any version string that is not a valid semantic version is an" \ 74 | "invalid version of Python." 75 | exit 1 76 | # Else if the Python version isn't installed then notify the user. 77 | # grep -E is used for searching through text lines that match the 78 | # specific version. 79 | elif ! python_versions | grep -E "^${version}$" > /dev/null; then 80 | echo "Error: Python version $version is not installed." 81 | echo "Installed Python versions are:" 82 | python_versions 83 | exit 1 84 | else 85 | echo "Using Python version $version" 86 | fi 87 | } 88 | 89 | # Flag to force deletion and creation of virtual environment 90 | FORCE=0 91 | 92 | # Initialize the other flags 93 | INSTALL_HOOKS=0 94 | LIST_VERSIONS=0 95 | PYTHON_VERSION="" 96 | VENV_NAME="" 97 | 98 | # Define long options 99 | LONGOPTS="force,help,install-hooks,list-versions,python-version:,venv-name:" 100 | 101 | # Define short options for getopt 102 | SHORTOPTS="fhilp:v:" 103 | 104 | # Check for GNU getopt by testing for long option support. GNU getopt supports 105 | # the "--test" option and will return exit code 4 while POSIX/BSD getopt does 106 | # not and will return exit code 0. 107 | if getopt --test > /dev/null 2>&1; then 108 | cat << 'END_OF_LINE' 109 | 110 | Please note, this script requires GNU getopt due to its enhanced 111 | functionality and compatibility with certain script features that 112 | are not supported by the POSIX getopt found in some systems, particularly 113 | those with a non-GNU version of getopt. This distinction is crucial 114 | as a system might have a non-GNU version of getopt installed by default, 115 | which could lead to unexpected behavior. 116 | 117 | On macOS, we recommend installing brew (https://brew.sh/). Then installation 118 | is as simple as `brew install gnu-getopt` and adding this to your 119 | profile: 120 | 121 | export PATH="$(brew --prefix)/opt/gnu-getopt/bin:$PATH" 122 | 123 | GNU getopt must be explicitly added to the PATH since it 124 | is keg-only (https://docs.brew.sh/FAQ#what-does-keg-only-mean). 125 | 126 | END_OF_LINE 127 | exit 1 128 | fi 129 | 130 | # Check to see if pyenv is installed 131 | if [ -z "$(command -v pyenv)" ] || { [ -z "$(command -v pyenv-virtualenv)" ] && [ ! -f "$(pyenv root)/plugins/pyenv-virtualenv/bin/pyenv-virtualenv" ]; }; then 132 | echo "pyenv and pyenv-virtualenv are required." 133 | if [[ "$OSTYPE" == "darwin"* ]]; then 134 | cat << 'END_OF_LINE' 135 | 136 | On macOS, we recommend installing brew, https://brew.sh/. Then installation 137 | is as simple as `brew install pyenv pyenv-virtualenv` and adding this to your 138 | profile: 139 | 140 | eval "$(pyenv init -)" 141 | eval "$(pyenv virtualenv-init -)" 142 | 143 | END_OF_LINE 144 | 145 | fi 146 | cat << 'END_OF_LINE' 147 | For Linux, Windows Subsystem for Linux (WSL), or macOS (if you don't want 148 | to use "brew") you can use https://github.com/pyenv/pyenv-installer to install 149 | the necessary tools. Before running this ensure that you have installed the 150 | prerequisites for your platform according to the pyenv wiki page, 151 | https://github.com/pyenv/pyenv/wiki/common-build-problems. 152 | 153 | On WSL you should treat your platform as whatever Linux distribution you've 154 | chosen to install. 155 | 156 | Once you have installed "pyenv" you will need to add the following lines to 157 | your ".bashrc": 158 | 159 | export PATH="$PATH:$HOME/.pyenv/bin" 160 | eval "$(pyenv init -)" 161 | eval "$(pyenv virtualenv-init -)" 162 | END_OF_LINE 163 | exit 1 164 | fi 165 | 166 | # Use GNU getopt to parse options 167 | if ! PARSED=$(getopt --options $SHORTOPTS --longoptions $LONGOPTS --name "$0" -- "$@"); then 168 | echo "Error parsing options" 169 | exit 1 170 | fi 171 | eval set -- "$PARSED" 172 | 173 | while true; do 174 | case "$1" in 175 | -f | --force) 176 | FORCE=1 177 | shift 178 | ;; 179 | -h | --help) 180 | echo "$USAGE" 181 | exit 0 182 | ;; 183 | -i | --install-hooks) 184 | INSTALL_HOOKS=1 185 | shift 186 | ;; 187 | -l | --list-versions) 188 | LIST_VERSIONS=1 189 | shift 190 | ;; 191 | -p | --python-version) 192 | PYTHON_VERSION="$2" 193 | shift 2 194 | # Check the Python version being passed in. 195 | check_python_version "$PYTHON_VERSION" 196 | ;; 197 | -v | --venv-name) 198 | VENV_NAME="$2" 199 | shift 2 200 | ;; 201 | --) 202 | shift 203 | break 204 | ;; 205 | *) 206 | # Unreachable due to GNU getopt handling all options 207 | echo "Programming error" 208 | exit 64 209 | ;; 210 | esac 211 | done 212 | 213 | # Determine the virtual environment name 214 | if [ -n "$VENV_NAME" ]; then 215 | # Use the user-provided environment name 216 | env_name="$VENV_NAME" 217 | else 218 | # Set the environment name to the last part of the working directory. 219 | env_name=${PWD##*/} 220 | fi 221 | 222 | # List Python versions and select one interactively. 223 | if [ $LIST_VERSIONS -ne 0 ]; then 224 | echo Available Python versions: 225 | python_versions 226 | # Read the user's desired Python version. 227 | # -r: treat backslashes as literal, -p: display prompt before input. 228 | read -r -p "Enter the desired Python version: " PYTHON_VERSION 229 | # Check the Python version being passed in. 230 | check_python_version "$PYTHON_VERSION" 231 | fi 232 | 233 | # Remove any lingering local configuration. 234 | if [ $FORCE -ne 0 ]; then 235 | rm -f .python-version 236 | pyenv virtualenv-delete --force "${env_name}" || true 237 | elif [[ -f .python-version ]]; then 238 | cat << 'END_OF_LINE' 239 | An existing .python-version file was found. Either remove this file yourself 240 | or re-run with the --force option to have it deleted along with the associated 241 | virtual environment. 242 | 243 | rm .python-version 244 | 245 | END_OF_LINE 246 | exit 1 247 | fi 248 | 249 | # Create a new virtual environment for this project 250 | # 251 | # If $PYTHON_VERSION is undefined then the current pyenv Python version will be used. 252 | # 253 | # We can't quote ${PYTHON_VERSION:=} below since if the variable is 254 | # undefined then we want nothing to appear; this is the reason for the 255 | # "shellcheck disable" line below. 256 | # 257 | # shellcheck disable=SC2086 258 | if ! pyenv virtualenv ${PYTHON_VERSION:=} "${env_name}"; then 259 | cat << END_OF_LINE 260 | An existing virtual environment named $env_name was found. Either delete this 261 | environment yourself or re-run with the --force option to have it deleted. 262 | 263 | pyenv virtualenv-delete ${env_name} 264 | 265 | END_OF_LINE 266 | exit 1 267 | fi 268 | 269 | # Set the local application-specific Python version(s) by writing the 270 | # version name to a file named `.python-version'. 271 | pyenv local "${env_name}" 272 | 273 | # Upgrade pip and friends 274 | python3 -m pip install --upgrade pip setuptools wheel 275 | 276 | # Find a requirements file (if possible) and install 277 | for req_file in "requirements-dev.txt" "requirements-test.txt" "requirements.txt"; do 278 | if [[ -f $req_file ]]; then 279 | pip install --requirement $req_file 280 | break 281 | fi 282 | done 283 | 284 | # Install Packer plugin dependencies 285 | packer init -upgrade . 286 | 287 | # Install git pre-commit hooks now or later. 288 | pre-commit install ${INSTALL_HOOKS:+"--install-hooks"} 289 | 290 | # Setup git remotes from lineage configuration 291 | # This could fail if the remotes are already setup, but that is ok. 292 | set +o errexit 293 | 294 | eval "$( 295 | python3 << 'END_OF_LINE' 296 | from pathlib import Path 297 | import yaml 298 | import sys 299 | 300 | LINEAGE_CONFIG = Path(".github/lineage.yml") 301 | 302 | if not LINEAGE_CONFIG.exists(): 303 | print("No lineage configuration found.", file=sys.stderr) 304 | sys.exit(0) 305 | 306 | with LINEAGE_CONFIG.open("r") as f: 307 | lineage = yaml.safe_load(stream=f) 308 | 309 | if lineage["version"] == "1": 310 | for parent_name, v in lineage["lineage"].items(): 311 | remote_url = v["remote-url"] 312 | print(f"git remote add {parent_name} {remote_url};") 313 | print(f"git remote set-url --push {parent_name} no_push;") 314 | else: 315 | print(f'Unsupported lineage version: {lineage["version"]}', file=sys.stderr) 316 | END_OF_LINE 317 | )" 318 | 319 | # Qapla' 320 | echo "Success!" 321 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # guacamole-packer 🥑📦 # 2 | 3 | [![GitHub Build Status](https://github.com/cisagov/guacamole-packer/workflows/build/badge.svg)](https://github.com/cisagov/guacamole-packer/actions) 4 | [![License](https://img.shields.io/github/license/cisagov/guacamole-packer)](https://spdx.org/licenses/) 5 | [![CodeQL](https://github.com/cisagov/guacamole-packer/workflows/CodeQL/badge.svg)](https://github.com/cisagov/guacamole-packer/actions/workflows/codeql-analysis.yml) 6 | 7 | This project can be used to create machine images that include 8 | [Apache Guacamole](https://guacamole.apache.org/), a clientless 9 | remote desktop gateway. 10 | 11 | ## Pre-requisites ## 12 | 13 | This project requires a build user to exist in AWS. The accompanying Terraform 14 | code will create the user with the appropriate name and permissions. This only 15 | needs to be run once per project, per AWS account. This user will also be used 16 | by GitHub Actions. 17 | 18 | Before the build user can be created, the following profile must exist in 19 | your AWS credentials file: 20 | 21 | - `cool-terraform-backend` 22 | 23 | The easiest way to set up that profile is to use our 24 | [`aws-profile-sync`](https://github.com/cisagov/aws-profile-sync) utility. 25 | Follow the usage instructions in that repository before continuing with the 26 | next steps. Note that you will need to know where your team stores their 27 | remote profile data in order to use 28 | [`aws-profile-sync`](https://github.com/cisagov/aws-profile-sync). 29 | 30 | ### Creating a build user ### 31 | 32 | You will need to create a build user for each environment that you use. The 33 | following steps show how to create a build user for an environment named "dev". 34 | You will need to repeat this process for any additional environments. 35 | 36 | 1. Change into the `terraform-build-user` directory: 37 | 38 | ```console 39 | cd terraform-build-user 40 | ``` 41 | 42 | 1. Create a backend configuration file named `dev.tfconfig` containing the 43 | name of the bucket where "dev" environment Terraform state is stored - this file 44 | is required to initialize the Terraform backend in each environment: 45 | 46 | ```hcl 47 | bucket = "my-dev-terraform-state-bucket" 48 | ``` 49 | 50 | 1. Initialize the Terraform backend for the "dev" environment using your backend 51 | configuration file: 52 | 53 | ```console 54 | terraform init -backend-config=dev.tfconfig 55 | ``` 56 | 57 | > [!NOTE] 58 | > When performing this step for additional environments (i.e. not your first 59 | > environment), use the `-reconfigure` flag: 60 | > 61 | > ```console 62 | > terraform init -backend-config=other-env.tfconfig -reconfigure 63 | > ``` 64 | 65 | 1. Create a Terraform variables file named `dev.tfvars` containing all 66 | required variables (currently only `terraform_state_bucket`): 67 | 68 | ```hcl 69 | terraform_state_bucket = "my-dev-terraform-state-bucket" 70 | ``` 71 | 72 | 1. Create a Terraform workspace for the "dev" environment: 73 | 74 | ```console 75 | terraform workspace new dev 76 | ``` 77 | 78 | 1. Initialize and upgrade the Terraform workspace, then apply the configuration 79 | to create the build user in the "dev" environment: 80 | 81 | ```console 82 | terraform init -upgrade=true 83 | terraform apply -var-file=dev.tfvars 84 | ``` 85 | 86 | Once the build user is created you will need to update the 87 | [repository's secrets](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets) 88 | with the new encrypted environment variables. This should be done using the 89 | [`terraform-to-secrets`](https://github.com/cisagov/development-guide/tree/develop/project_setup#terraform-iam-credentials-to-github-secrets-) 90 | tool available in the 91 | [development guide](https://github.com/cisagov/development-guide). Instructions 92 | for how to use this tool can be found in the 93 | ["Terraform IAM Credentials to GitHub Secrets" section](https://github.com/cisagov/development-guide/tree/develop/project_setup#terraform-iam-credentials-to-github-secrets-). 94 | of the Project Setup README. 95 | 96 | If you have appropriate permissions for the repository you can view existing 97 | secrets on the 98 | [appropriate page](https://github.com/cisagov/guacamole-packer/settings/secrets) 99 | in the repository's settings. 100 | 101 | This project also requires the following data to exist in your [AWS Systems 102 | Manager parameter store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html): 103 | 104 | - `/guacamole/postgres_username`: The non-admin postgres username used by 105 | Guacamole 106 | - `/guacamole/postgres_password`: The non-admin postgres password used by 107 | Guacamole 108 | - `/rdp/username`: The username for Guacamole to use when connecting to an 109 | instance via RDP 110 | - `/rdp/password`: The password for Guacamole to use when connecting to an 111 | instance via RDP 112 | - `/vnc/ssh/ed25519_private_key`: The private SSH key to use for SFTP 113 | file transfer in Guacamole 114 | - `/vnc/username`: The username for Guacamole to use when connecting to an 115 | instance via VNC 116 | - `/vnc/password`: The password for Guacamole to use when connecting to an 117 | instance via VNC 118 | - `/vnc/sftp/windows_base_directory`: The base path for the SFTP directories 119 | that Guacamole will use when connecting to a Windows instance via VNC 120 | 121 | IMPORTANT: The account where your images will be built must have a VPC and 122 | a public subnet both tagged with the name "AMI Build", otherwise `packer` 123 | will not be able to build images. 124 | 125 | ## Building the image ## 126 | 127 | ### Using GitHub Actions ### 128 | 129 | 1. Create a [new release](https://help.github.com/en/articles/creating-releases) 130 | in GitHub. 131 | 1. There is no step 2! 132 | 133 | GitHub Actions can build this project in three different modes depending on 134 | how the build was triggered from GitHub. 135 | 136 | 1. **Development release**: After a normal commit and also on a pull request, 137 | GitHub Actions will run tests and validation on the Packer template, and then 138 | build the project. An image will be built and deployed using the 139 | [`build`](.github/workflows/build.yml) workflow. This should be configured 140 | to deploy the image to a single region using a development account. 141 | 1. **Pre-release**: Publish a GitHub release with the "This is a pre-release" 142 | checkbox checked. An image will be built and deployed using the 143 | [`prerelease`](.github/workflows/prerelease.yml) workflow. This should be 144 | configured to deploy the image to a single region using a non-production 145 | account (e.g. "staging"). 146 | 1. **Production release**: Publish a GitHub release with the "This is a 147 | pre-release" checkbox unchecked. An image will be built and deployed using 148 | the [`release`](.github/workflows/release.yml) workflow. This should be 149 | configured to deploy the image to multiple regions using a production 150 | account. 151 | 152 | ### Using your local environment ### 153 | 154 | Packer will use your 155 | [standard AWS environment](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) 156 | to build the image, however you will need to set up one profile for the 157 | previously-created build user and another profile to assume the associated 158 | `EC2AMICreate` role. You will need the `aws_access_key_id` and 159 | `aws_secret_access_key` that you set as GitHub secrets earlier. 160 | 161 | Add the following blocks to your AWS credentials file (be sure to replace the 162 | dummy account ID in the `role_arn` with your own): 163 | 164 | ```console 165 | [build-guacamole-packer] 166 | aws_access_key_id = AKIAXXXXXXXXXXXXXXXX 167 | aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 168 | 169 | [cool-images-ec2amicreate-guacamole-packer] 170 | role_arn = arn:aws:iam::111111111111:role/EC2AMICreate-build-guacamole-packer 171 | source_profile = build-guacamole-packer 172 | role_session_name = example 173 | ``` 174 | 175 | This Packer template defines a number of variables whose defaults can be changed 176 | through a `.pkrvars.hcl` file: 177 | 178 | ```hcl 179 | build_region = "us-east-2" 180 | build_region_kms = "alias/example-kms" 181 | is_prerelease = "true" 182 | ``` 183 | 184 | Here is an example of how to kick off a pre-release build: 185 | 186 | ```console 187 | pip install --requirement requirements-dev.txt 188 | ansible-galaxy install --force --force-with-deps --role-file ansible/requirements.yml 189 | AWS_PROFILE=cool-images-ec2amicreate-guacamole-packer packer build --timestamp-ui -var release_tag=$(./bump-version show) -var is_prerelease=true . 190 | ``` 191 | 192 | If you are satisfied with your pre-release image, you can easily create a release 193 | that deploys to all regions by adding additional regions to the Packer template. 194 | This can be done by using a `.pkrvars.hcl` for example with `release.pkrvars.hcl`: 195 | 196 | ```hcl 197 | ami_regions = ["us-east-2", "us-west-1", "us-west-2"] 198 | region_kms_keys = { 199 | "us-east-2": "alias/cool-amis", 200 | "us-west-1": "alias/cool-amis", 201 | "us-west-2": "alias/cool-amis", 202 | } 203 | ``` 204 | 205 | ```console 206 | AWS_PROFILE=cool-images-ec2amicreate-guacamole-packer packer build --timestamp-ui -var-file release.pkrvars.hcl . 207 | ``` 208 | 209 | ### Giving other AWS accounts permission to launch the image ### 210 | 211 | After the AMI has been successfully created, you may want to allow other 212 | accounts in your AWS organization permission to launch it. The following steps 213 | show how to do this for an environment named "dev". You will need to repeat this 214 | process for any additional environments. 215 | 216 | > [!NOTE] 217 | > Refer to the `ami_share_account_name_regex` variable if you want to customize 218 | > which accounts in your AWS organization to share your AMI with. 219 | 220 | 1. Change into the `terraform-post-packer` directory: 221 | 222 | ```console 223 | cd terraform-post-packer 224 | ``` 225 | 226 | 1. Create a backend configuration file named `dev.tfconfig` containing the 227 | name of the bucket where "dev" environment Terraform state is stored - this file 228 | is required to initialize the Terraform backend in each environment: 229 | 230 | ```hcl 231 | bucket = "my-dev-terraform-state-bucket" 232 | ``` 233 | 234 | 1. Initialize the Terraform backend for the "dev" environment using your backend 235 | configuration file: 236 | 237 | ```console 238 | terraform init -backend-config=dev.tfconfig 239 | ``` 240 | 241 | > [!NOTE] 242 | > When performing this step for additional environments (i.e. not your first 243 | > environment), use the `-reconfigure` flag: 244 | > 245 | > ```console 246 | > terraform init -backend-config=other-env.tfconfig -reconfigure 247 | > ``` 248 | 249 | 1. If not already created, create a Terraform workspace for the "dev" environment: 250 | 251 | ```console 252 | terraform workspace new dev 253 | ``` 254 | 255 | Otherwise, switch to the existing "dev" workspace: 256 | 257 | ```console 258 | terraform workspace select dev 259 | ``` 260 | 261 | 1. Initialize and upgrade the Terraform workspace, then apply the configuration 262 | to share the AMI with accounts in the "dev" environment: 263 | 264 | ```console 265 | terraform init -upgrade=true 266 | terraform apply 267 | ``` 268 | 269 | 270 | ## Requirements ## 271 | 272 | No requirements. 273 | 274 | ## Providers ## 275 | 276 | | Name | Version | 277 | |------|---------| 278 | | amazon-ami | n/a | 279 | 280 | ## Modules ## 281 | 282 | No modules. 283 | 284 | ## Resources ## 285 | 286 | | Name | Type | 287 | |------|------| 288 | | [amazon-ami_amazon-ami.debian_trixie_arm64](https://registry.terraform.io/providers/hashicorp/amazon-ami/latest/docs/data-sources/amazon-ami) | data source | 289 | | [amazon-ami_amazon-ami.debian_trixie_x86_64](https://registry.terraform.io/providers/hashicorp/amazon-ami/latest/docs/data-sources/amazon-ami) | data source | 290 | 291 | ## Inputs ## 292 | 293 | | Name | Description | Type | Default | Required | 294 | |------|-------------|------|---------|:--------:| 295 | | ami\_regions | The list of AWS regions to copy the AMI to once it has been created. Example: ["us-east-1"] | `list(string)` | `[]` | no | 296 | | build\_region | The region in which to retrieve the base AMI from and build the new AMI. | `string` | `"us-east-1"` | no | 297 | | build\_region\_kms | The ID or ARN of the KMS key to use for AMI encryption. | `string` | `"alias/cool-amis"` | no | 298 | | force\_install\_ansible\_requirements | Indicate if the Ansible requirements should be force installed. | `bool` | `false` | no | 299 | | force\_install\_ansible\_requirements\_with\_dependencies | Indicate if the Ansible requirements *and* their dependencies should be force installed. | `bool` | `false` | no | 300 | | github\_ref\_name | The GitHub short ref name to use for the tags applied to the created AMI. | `string` | `""` | no | 301 | | github\_sha | The GitHub commit SHA to use for the tags applied to the created AMI. | `string` | `""` | no | 302 | | is\_prerelease | The pre-release status to use for the tags applied to the created AMI. | `bool` | `false` | no | 303 | | region\_kms\_keys | A map of regions to copy the created AMI to and the KMS keys to use for encryption in that region. The keys for this map must match the values provided to the aws\_regions variable. Example: {"us-east-1": "alias/example-kms"} | `map(string)` | `{}` | no | 304 | | release\_tag | The GitHub release tag to use for the tags applied to the created AMI. | `string` | `""` | no | 305 | | release\_url | The GitHub release URL to use for the tags applied to the created AMI. | `string` | `""` | no | 306 | | skip\_create\_ami | Indicate if Packer should not create the AMI. | `bool` | `false` | no | 307 | 308 | ## Outputs ## 309 | 310 | No outputs. 311 | 312 | 313 | ## Contributing ## 314 | 315 | We welcome contributions! Please see [`CONTRIBUTING.md`](CONTRIBUTING.md) for 316 | details. 317 | 318 | ## License ## 319 | 320 | This project is in the worldwide [public domain](LICENSE). 321 | 322 | This project is in the public domain within the United States, and 323 | copyright and related rights in the work worldwide are waived through 324 | the [CC0 1.0 Universal public domain 325 | dedication](https://creativecommons.org/publicdomain/zero/1.0/). 326 | 327 | All contributions to this project will be released under the CC0 328 | dedication. By submitting a pull request, you are agreeing to comply 329 | with this waiver of copyright interest. 330 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build 3 | 4 | on: # yamllint disable-line rule:truthy 5 | merge_group: 6 | types: 7 | - checks_requested 8 | pull_request: 9 | push: 10 | repository_dispatch: 11 | types: 12 | - apb 13 | 14 | # Set a default shell for any run steps. The `-Eueo pipefail` sets errtrace, 15 | # nounset, errexit, and pipefail. The `-x` will print all commands as they are 16 | # run. Please see the GitHub Actions documentation for more information: 17 | # https://docs.github.com/en/actions/using-jobs/setting-default-values-for-jobs 18 | defaults: 19 | run: 20 | shell: bash -Eueo pipefail -x {0} 21 | 22 | env: 23 | AWS_DEFAULT_REGION: us-east-1 24 | # We have seen some failures of packer init in GitHub Actions due to 25 | # rate limiting when pulling Packer plugins from GitHub. Having 26 | # Packer use a GitHub API token should reduce this. The rate 27 | # limiting for unauthenticated requests is 60 requests/hour while 28 | # the rate limiting for authenticated requests is 5000 29 | # requests/hour. 30 | PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | PIP_CACHE_DIR: ~/.cache/pip 32 | PRE_COMMIT_CACHE_DIR: ~/.cache/pre-commit 33 | RUN_TMATE: ${{ secrets.RUN_TMATE }} 34 | TERRAFORM_DOCS_REPO_BRANCH_NAME: improvement/support_atx_closed_markdown_headers 35 | TERRAFORM_DOCS_REPO_DEPTH: 1 36 | TERRAFORM_DOCS_REPO_URL: https://github.com/mcdonnnj/terraform-docs.git 37 | 38 | jobs: 39 | diagnostics: 40 | name: Run diagnostics 41 | # This job does not need any permissions 42 | permissions: {} 43 | runs-on: ubuntu-latest 44 | steps: 45 | # Note that a duplicate of this step must be added at the top of 46 | # each job. 47 | - name: Apply standard cisagov job preamble 48 | uses: cisagov/action-job-preamble@v1 49 | with: 50 | check_github_status: "true" 51 | # This functionality is poorly implemented and has been 52 | # causing problems due to the MITM implementation hogging or 53 | # leaking memory. As a result we disable it by default. If 54 | # you want to temporarily enable it, simply set 55 | # monitor_permissions equal to "true". 56 | # 57 | # TODO: Re-enable this functionality when practical. See 58 | # cisagov/skeleton-generic#207 for more details. 59 | monitor_permissions: "false" 60 | output_workflow_context: "true" 61 | # Use a variable to specify the permissions monitoring 62 | # configuration. By default this will yield the 63 | # configuration stored in the cisagov organization-level 64 | # variable, but if you want to use a different configuration 65 | # then simply: 66 | # 1. Create a repository-level variable with the name 67 | # ACTIONS_PERMISSIONS_CONFIG. 68 | # 2. Set this new variable's value to the configuration you 69 | # want to use for this repository. 70 | # 71 | # Note in particular that changing the permissions 72 | # monitoring configuration *does not* require you to modify 73 | # this workflow. 74 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 75 | lint: 76 | needs: 77 | - diagnostics 78 | permissions: 79 | # actions/checkout needs this to fetch code 80 | contents: read 81 | runs-on: ubuntu-latest 82 | steps: 83 | - name: Apply standard cisagov job preamble 84 | uses: cisagov/action-job-preamble@v1 85 | with: 86 | # This functionality is poorly implemented and has been 87 | # causing problems due to the MITM implementation hogging or 88 | # leaking memory. As a result we disable it by default. If 89 | # you want to temporarily enable it, simply set 90 | # monitor_permissions equal to "true". 91 | # 92 | # TODO: Re-enable this functionality when practical. See 93 | # cisagov/skeleton-generic#207 for more details. 94 | monitor_permissions: "false" 95 | # Use a variable to specify the permissions monitoring 96 | # configuration. By default this will yield the 97 | # configuration stored in the cisagov organization-level 98 | # variable, but if you want to use a different configuration 99 | # then simply: 100 | # 1. Create a repository-level variable with the name 101 | # ACTIONS_PERMISSIONS_CONFIG. 102 | # 2. Set this new variable's value to the configuration you 103 | # want to use for this repository. 104 | # 105 | # Note in particular that changing the permissions 106 | # monitoring configuration *does not* require you to modify 107 | # this workflow. 108 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 109 | - id: setup-env 110 | uses: cisagov/setup-env-github-action@v1 111 | - uses: actions/checkout@v6 112 | - id: setup-python 113 | uses: actions/setup-python@v6 114 | with: 115 | python-version: ${{ steps.setup-env.outputs.python-version }} 116 | # We need the Go version and Go cache location for the actions/cache step, 117 | # so the Go installation must happen before that. 118 | - id: setup-go 119 | uses: actions/setup-go@v6 120 | with: 121 | # There is no expectation for actual Go code so we disable caching as 122 | # it relies on the existence of a go.sum file. 123 | cache: false 124 | go-version: ${{ steps.setup-env.outputs.go-version }} 125 | - id: go-cache 126 | name: Lookup Go cache directory 127 | run: | 128 | echo "dir=$(go env GOCACHE)" >> $GITHUB_OUTPUT 129 | - uses: actions/cache@v4 130 | env: 131 | BASE_CACHE_KEY: ${{ github.job }}-${{ runner.os }}-\ 132 | py${{ steps.setup-python.outputs.python-version }}-\ 133 | go${{ steps.setup-go.outputs.go-version }}-\ 134 | packer${{ steps.setup-env.outputs.packer-version }}-\ 135 | tf${{ steps.setup-env.outputs.terraform-version }}- 136 | with: 137 | key: ${{ env.BASE_CACHE_KEY }}\ 138 | ${{ hashFiles('**/requirements-test.txt') }}-\ 139 | ${{ hashFiles('**/requirements.txt') }}-\ 140 | ${{ hashFiles('**/.pre-commit-config.yaml') }} 141 | # Note that the .terraform directory IS NOT included in the 142 | # cache because if we were caching, then we would need to use 143 | # the `-upgrade=true` option. This option blindly pulls down the 144 | # latest modules and providers instead of checking to see if an 145 | # update is required. That behavior defeats the benefits of caching. 146 | # so there is no point in doing it for the .terraform directory. 147 | path: | 148 | ${{ env.PIP_CACHE_DIR }} 149 | ${{ env.PRE_COMMIT_CACHE_DIR }} 150 | ${{ steps.go-cache.outputs.dir }} 151 | restore-keys: | 152 | ${{ env.BASE_CACHE_KEY }} 153 | - uses: hashicorp/setup-packer@v3 154 | with: 155 | version: ${{ steps.setup-env.outputs.packer-version }} 156 | - uses: hashicorp/setup-terraform@v3 157 | with: 158 | terraform_version: ${{ steps.setup-env.outputs.terraform-version }} 159 | - name: Install go-critic 160 | env: 161 | PACKAGE_URL: github.com/go-critic/go-critic/cmd/gocritic 162 | PACKAGE_VERSION: ${{ steps.setup-env.outputs.go-critic-version }} 163 | run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} 164 | - name: Install goimports 165 | env: 166 | PACKAGE_URL: golang.org/x/tools/cmd/goimports 167 | PACKAGE_VERSION: ${{ steps.setup-env.outputs.goimports-version }} 168 | run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} 169 | - name: Install gosec 170 | env: 171 | PACKAGE_URL: github.com/securego/gosec/v2/cmd/gosec 172 | PACKAGE_VERSION: ${{ steps.setup-env.outputs.gosec-version }} 173 | run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} 174 | - name: Install staticcheck 175 | env: 176 | PACKAGE_URL: honnef.co/go/tools/cmd/staticcheck 177 | PACKAGE_VERSION: ${{ steps.setup-env.outputs.staticcheck-version }} 178 | run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} 179 | # TODO: https://github.com/cisagov/skeleton-generic/issues/165 180 | # We are temporarily using @mcdonnnj's forked branch of terraform-docs 181 | # until his PR: https://github.com/terraform-docs/terraform-docs/pull/745 182 | # is approved. This temporary fix will allow for ATX header support when 183 | # terraform-docs is run during linting. 184 | - name: Clone ATX headers branch from terraform-docs fork 185 | run: | 186 | git clone \ 187 | --branch $TERRAFORM_DOCS_REPO_BRANCH_NAME \ 188 | --depth $TERRAFORM_DOCS_REPO_DEPTH \ 189 | --single-branch \ 190 | $TERRAFORM_DOCS_REPO_URL /tmp/terraform-docs 191 | - name: Build and install terraform-docs binary 192 | run: | 193 | go build \ 194 | -C /tmp/terraform-docs \ 195 | -o $(go env GOPATH)/bin/terraform-docs 196 | - name: Install dependencies 197 | run: | 198 | python -m pip install --upgrade pip setuptools wheel 199 | pip install --upgrade --requirement requirements-test.txt 200 | - name: Install Ansible roles 201 | run: ansible-galaxy install --force --role-file ansible/requirements.yml 202 | # This must happen before pre-commit is run or the Packer format 203 | # linter will throw an error. 204 | - name: Install Packer plugins 205 | run: packer init . 206 | - name: Set up pre-commit hook environments 207 | run: pre-commit install-hooks 208 | - name: Run pre-commit on all files 209 | run: pre-commit run --all-files 210 | - name: Setup tmate debug session 211 | uses: mxschmitt/action-tmate@v3 212 | if: env.RUN_TMATE 213 | test: 214 | needs: 215 | - diagnostics 216 | permissions: 217 | # actions/checkout needs this to fetch code 218 | contents: read 219 | runs-on: ubuntu-latest 220 | steps: 221 | - name: Apply standard cisagov job preamble 222 | uses: cisagov/action-job-preamble@v1 223 | with: 224 | # This functionality is poorly implemented and has been 225 | # causing problems due to the MITM implementation hogging or 226 | # leaking memory. As a result we disable it by default. If 227 | # you want to temporarily enable it, simply set 228 | # monitor_permissions equal to "true". 229 | # 230 | # TODO: Re-enable this functionality when practical. See 231 | # cisagov/skeleton-packer#411 for more details. 232 | monitor_permissions: "false" 233 | # Use a variable to specify the permissions monitoring 234 | # configuration. By default this will yield the 235 | # configuration stored in the cisagov organization-level 236 | # variable, but if you want to use a different configuration 237 | # then simply: 238 | # 1. Create a repository-level variable with the name 239 | # ACTIONS_PERMISSIONS_CONFIG. 240 | # 2. Set this new variable's value to the configuration you 241 | # want to use for this repository. 242 | # 243 | # Note in particular that changing the permissions 244 | # monitoring configuration *does not* require you to modify 245 | # this workflow. 246 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 247 | - id: setup-env 248 | uses: cisagov/setup-env-github-action@v1 249 | - uses: actions/checkout@v6 250 | - id: setup-python 251 | uses: actions/setup-python@v6 252 | with: 253 | python-version: ${{ steps.setup-env.outputs.python-version }} 254 | - uses: actions/cache@v4 255 | env: 256 | BASE_CACHE_KEY: ${{ github.job }}-${{ runner.os }}-\ 257 | py${{ steps.setup-python.outputs.python-version }}-\ 258 | packer${{ steps.setup-env.outputs.packer-version }}- 259 | with: 260 | path: | 261 | ${{ env.PIP_CACHE_DIR }} 262 | key: ${{ env.BASE_CACHE_KEY }}\ 263 | ${{ hashFiles('**/requirements-test.txt') }}-\ 264 | ${{ hashFiles('**/requirements.txt') }} 265 | restore-keys: | 266 | ${{ env.BASE_CACHE_KEY }} 267 | - uses: hashicorp/setup-packer@v3 268 | with: 269 | version: ${{ steps.setup-env.outputs.packer-version }} 270 | - name: Install dependencies 271 | run: | 272 | python -m pip install --upgrade pip 273 | pip install --upgrade --requirement requirements-test.txt 274 | - name: Run tests 275 | env: 276 | GITHUB_RELEASE_TAG: ${{ github.event.release.tag_name }} 277 | run: pytest 278 | - name: Setup tmate debug session 279 | uses: mxschmitt/action-tmate@v3 280 | if: env.RUN_TMATE 281 | build: 282 | environment: dev-a 283 | # The AMI build process is an expensive test (in terms of time) so 284 | # let's not run it unless the other jobs succeed. 285 | needs: 286 | - lint 287 | - test 288 | permissions: 289 | # actions/checkout needs this to fetch code 290 | contents: read 291 | runs-on: ubuntu-latest 292 | strategy: 293 | fail-fast: false 294 | matrix: 295 | architecture: 296 | # cisagov/ansible-role-guacamole cannot currently support 297 | # ARM64 because the official Guacamole Docker images do not. 298 | # - arm64 299 | - x86_64 300 | steps: 301 | - name: Apply standard cisagov job preamble 302 | uses: cisagov/action-job-preamble@v1 303 | with: 304 | # This functionality is poorly implemented and has been 305 | # causing problems due to the MITM implementation hogging or 306 | # leaking memory. As a result we disable it by default. If 307 | # you want to temporarily enable it, simply set 308 | # monitor_permissions equal to "true". 309 | # 310 | # TODO: Re-enable this functionality when practical. See 311 | # cisagov/skeleton-packer#411 for more details. 312 | monitor_permissions: "false" 313 | # Use a variable to specify the permissions monitoring 314 | # configuration. By default this will yield the 315 | # configuration stored in the cisagov organization-level 316 | # variable, but if you want to use a different configuration 317 | # then simply: 318 | # 1. Create a repository-level variable with the name 319 | # ACTIONS_PERMISSIONS_CONFIG. 320 | # 2. Set this new variable's value to the configuration you 321 | # want to use for this repository. 322 | # 323 | # Note in particular that changing the permissions 324 | # monitoring configuration *does not* require you to modify 325 | # this workflow. 326 | permissions_monitoring_config: ${{ vars.ACTIONS_PERMISSIONS_CONFIG }} 327 | - id: setup-env 328 | uses: cisagov/setup-env-github-action@v1 329 | - uses: actions/checkout@v6 330 | - id: setup-python 331 | uses: actions/setup-python@v6 332 | with: 333 | python-version: ${{ steps.setup-env.outputs.python-version }} 334 | - uses: actions/cache@v4 335 | env: 336 | BASE_CACHE_KEY: ${{ github.job }}-${{ runner.os }}-\ 337 | py${{ steps.setup-python.outputs.python-version }}-\ 338 | packer${{ steps.setup-env.outputs.packer-version }}-\ 339 | tf-${{ steps.setup-env.outputs.terraform-version }}- 340 | with: 341 | path: | 342 | ${{ env.PIP_CACHE_DIR }} 343 | key: ${{ env.BASE_CACHE_KEY }}\ 344 | ${{ hashFiles('**/requirements.txt') }} 345 | restore-keys: | 346 | ${{ env.BASE_CACHE_KEY }} 347 | - uses: hashicorp/setup-packer@v3 348 | with: 349 | version: ${{ steps.setup-env.outputs.packer-version }} 350 | - uses: hashicorp/setup-terraform@v3 351 | with: 352 | terraform_version: ${{ steps.setup-env.outputs.terraform-version }} 353 | - name: Install dependencies 354 | run: | 355 | python -m pip install --upgrade pip 356 | pip install --upgrade \ 357 | --requirement requirements.txt 358 | - name: Assume AWS build role 359 | uses: aws-actions/configure-aws-credentials@v5 360 | with: 361 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 362 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 363 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 364 | role-to-assume: ${{ secrets.BUILD_ROLE_TO_ASSUME }} 365 | role-duration-seconds: 3600 366 | # When called by Packer, Ansible will find /usr/bin/python3 and 367 | # use it; therefore, we must ensure that /usr/bin/python3 points 368 | # to the version of Python that we installed in the 369 | # actions/setup-python step above. This can hose other tasks 370 | # that are expecting to find the system Python at that location, 371 | # though, so we undo this change after running Packer. 372 | - name: Create a /usr/bin/python3 symlink to the installed Python 373 | run: | 374 | sudo mv /usr/bin/python3 /usr/bin/python3-default 375 | sudo ln -s ${{ env.pythonLocation }}/bin/python3 \ 376 | /usr/bin/python3 377 | - name: Install Packer plugins 378 | run: packer init . 379 | - name: Create machine image 380 | run: | 381 | packer build -only amazon-ebs.${{ matrix.architecture }} \ 382 | -timestamp-ui \ 383 | -var github_ref_name=${{ github.ref_name }} \ 384 | -var github_sha=${{ github.sha }} \ 385 | . 386 | - name: Remove /usr/bin/python3 symlink to the installed Python 387 | run: | 388 | sudo mv /usr/bin/python3-default /usr/bin/python3 389 | - name: Setup tmate debug session 390 | uses: mxschmitt/action-tmate@v3 391 | if: env.RUN_TMATE 392 | --------------------------------------------------------------------------------