Skip to content
This repository has been archived by the owner on May 1, 2023. It is now read-only.

Feat/zap ecs container #77

Merged
merged 7 commits into from
Aug 17, 2021
Merged

Feat/zap ecs container #77

merged 7 commits into from
Aug 17, 2021

Conversation

mohdnr
Copy link
Contributor

@mohdnr mohdnr commented Aug 16, 2021

ECS container that will initiate the zap scan and save the report to S3 for future processing.

ECS is being used instead of a lambda due to vulnerability scans typically exceeding the lambda maximum execution time of 15 minutes.

@mohdnr mohdnr marked this pull request as ready for review August 16, 2021 20:57
Copy link
Member

@CalvinRodo CalvinRodo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌮:cat

Copy link
Member

@patheard patheard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a comment about the entrypoint.sh while loop waiting for ZAP proxy init.

runners/owasp-zap/entrypoint.sh Outdated Show resolved Hide resolved
runners/owasp-zap/entrypoint.sh Outdated Show resolved Hide resolved
runners/owasp-zap/entrypoint.sh Show resolved Hide resolved
runners/owasp-zap/entrypoint.sh Show resolved Hide resolved
@patheard patheard self-requested a review August 17, 2021 14:23
Copy link
Member

@patheard patheard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment about French logout path exclusion.

@mohdnr mohdnr requested review from dj2 and patheard August 17, 2021 14:32
# Convert URL into a valid filename for the report
FILENAME=$(echo "$SCAN_URL" | sed -e 's/[^A-Za-z0-9._-]/_/g')-$fDate

zap-cli --port "${ZAP_PORT}" --zap-url "http://$HOST_IP" exclude "^.*(logout|log-out|signout|sign-out|deconnecter).*$"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a little lax. It would match '/blog/how-we-implemented-logout' or '/blog/our-sign-out-system', etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tighten with slashes as a first step?

^.*/(logout|log-out|signout|sign-out|deconnecter)/.*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe ^.*/(logout|log-out|signout|sign-out|deconnecter)/?$ so the trailing slash is optional and logout is end of string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100%. This is why we'll always need to spend time manually testing since automation has it's limits.

When testing manually you scope exclusions to full URL paths, but with automation we have to use a heavier hand since the impact of accidentally crawling logout has a greater impact than not scanning URL's that contain the same keywords.

I'll add an issue to look into providing URL exclusions during API invocation for scanning. Once we have user input on what not to scan we can remove this exclusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested regex implemented and created #79

@mohdnr mohdnr requested a review from dj2 August 17, 2021 15:52
@github-actions
Copy link

Plan for scanners/owasp-zap

✅   Terraform Format: success
✅   Terraform Plan: success

⚠️   WARNING: resources will be destroyed by this change!

Plan: 3 to add, 3 to change, 2 to destroy
Show plan
Resource actions are indicated with the following symbols:
  + create
  ~ update in-place
  - destroy
-/+ destroy and then create replacement
 <= read (data resources)

Terraform will perform the following actions:

  # data.aws_iam_policy_document.zap_runner_container_policies will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "zap_runner_container_policies"  {
      ~ id      = "4022618487" -> (known after apply)
      ~ json    = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = [
                          - "ecr:GetDownloadUrlForLayer",
                          - "ecr:GetAuthorizationToken",
                          - "ecr:BatchGetImage",
                          - "ecr:BatchCheckLayerAvailability",
                        ]
                      - Effect   = "Allow"
                      - Resource = "*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = "lambda:InvokeFunction"
                      - Effect   = "Allow"
                      - Resource = "arn:aws:lambda:ca-central-1:507252742351:function:scan-websites*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "logs:PutLogEvents",
                          - "logs:CreateLogStream",
                          - "logs:CreateLogGroup",
                        ]
                      - Effect   = "Allow"
                      - Resource = [
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs:log-stream:*",
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs",
                        ]
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "ec2:DeleteNetworkInterface",
                          - "ec2:CreateNetworkInterface",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:ec2:ca-central-1:507252742351:network-interface/*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = "ec2:DescribeNetworkInterfaces"
                      - Effect   = "Allow"
                      - Resource = "*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "s3:PutObjectAcl",
                          - "s3:PutObject",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:s3:::scan-websites-owasp-zap-report-data"
                      - Sid      = ""
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> (known after apply)
      - version = "2012-10-17" -> null

      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
          ~ resources     = [
              - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs",
              - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs:log-stream:*",
              ~ (known after apply),
              ~ (known after apply),
            ]
            # (2 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
    }

  # data.aws_iam_policy_document.zap_runner_policies will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "zap_runner_policies"  {
      ~ id      = "2401258957" -> (known after apply)
      ~ json    = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = [
                          - "logs:PutLogEvents",
                          - "logs:CreateLogStream",
                          - "logs:CreateLogGroup",
                        ]
                      - Effect   = "Allow"
                      - Resource = [
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs:log-stream:*",
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs",
                        ]
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "ecr:GetDownloadUrlForlayer",
                          - "ecr:BatchGetImage",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:ecr:ca-central-1:507252742351:repository/scan-websites/scanners/owasp-zap"
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "ecs:RunTask",
                          - "ecs:DescribeTaskDefinition",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:ecs:ca-central-1:507252742351:task-definition/zap_runner:1"
                      - Sid      = ""
                    },
                  - {
                      - Action   = "iam:PassRole"
                      - Effect   = "Allow"
                      - Resource = [
                          - "arn:aws:iam::507252742351:role/security_tools_execution_role",
                          - "arn:aws:iam::507252742351:role/container_execution_role",
                        ]
                      - Sid      = ""
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> (known after apply)
      - version = "2012-10-17" -> null

      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
          ~ resources     = [
              - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs",
              - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs:log-stream:*",
              ~ (known after apply),
              ~ (known after apply),
            ]
            # (2 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
          ~ resources     = [
              - "arn:aws:ecs:ca-central-1:507252742351:task-definition/zap_runner:1",
              ~ (known after apply),
            ]
            # (2 unchanged attributes hidden)
        }
      ~ statement {
          - not_actions   = [] -> null
          - not_resources = [] -> null
            # (3 unchanged attributes hidden)
        }
    }

  # data.template_file.scanning_tools will be read during apply
  # (config refers to values not yet known)
 <= data "template_file" "scanning_tools"  {
      ~ id       = "6a7792385bd0a9b617fc3cce2bff5f5c9e09fd8b0e1de39d462743585abbb93d" -> (known after apply)
      ~ rendered = jsonencode(
            [
              - {
                  - cpu              = 0
                  - environment      = [
                      - {
                          - name  = "S3_BUCKET"
                          - value = "scan-websites-owasp-zap-report-data"
                        },
                      - {
                          - name  = "ZAP_PORT"
                          - value = "8080"
                        },
                    ]
                  - essential        = true
                  - image            = "507252742351.dkr.ecr.ca-central-1.amazonaws.com/scan-websites/scanners/owasp-zap:latest"
                  - linuxParameters  = {
                      - capabilities = {
                          - add  = [
                              - "SYS_PTRACE",
                            ]
                          - drop = null
                        }
                    }
                  - logConfiguration = {
                      - logDriver = "awslogs"
                      - options   = {
                          - awslogs-group         = "/aws/ecs/zap_runner_ecs"
                          - awslogs-region        = "ca-central-1"
                          - awslogs-stream-prefix = "ecs-zap-runner"
                        }
                    }
                  - mountPoints      = []
                  - name             = "zap_runner"
                  - portMappings     = [
                      - {
                          - containerPort = 8001
                          - hostPort      = 8001
                          - protocol      = "tcp"
                        },
                    ]
                  - volumesFrom      = []
                },
              - {
                  - command          = [
                      - "zap.sh",
                      - "-daemon",
                      - "-host",
                      - "0.0.0.0",
                      - "-port",
                      - "8080",
                      - "-config",
                      - "api.disablekey=true",
                      - "-config",
                      - "api.addrs.addr.name=.*",
                      - "-config",
                      - "api.addrs.addr.regex=true",
                    ]
                  - cpu              = 1024
                  - environment      = []
                  - essential        = true
                  - image            = "owasp/zap2docker-stable"
                  - linuxParameters  = {
                      - capabilities = {
                          - drop = [
                              - "ALL",
                            ]
                        }
                    }
                  - logConfiguration = {
                      - logDriver = "awslogs"
                      - options   = {
                          - awslogs-group         = "/aws/ecs/zap_runner_ecs"
                          - awslogs-region        = "ca-central-1"
                          - awslogs-stream-prefix = "ecs-zap-runner"
                        }
                    }
                  - memory           = 3072
                  - mountPoints      = []
                  - name             = "zap2docker"
                  - portMappings     = [
                      - {
                          - containerPort = 8080
                          - hostPort      = 8080
                          - protocol      = "tcp"
                        },
                      - {
                          - containerPort = 8090
                          - hostPort      = 8090
                          - protocol      = "tcp"
                        },
                    ]
                  - volumesFrom      = []
                },
            ]
        ) -> (known after apply)
      ~ vars     = {
          ~ "awslogs-group"         = "/aws/ecs/zap_runner_ecs" -> "/aws/ecs/runners_owasp_zap_ecs"
          ~ "awslogs-stream-prefix" = "ecs-zap-runner" -> "ecs-runners-owasp-zap"
          ~ "image"                 = "507252742351.dkr.ecr.ca-central-1.amazonaws.com/scan-websites/scanners/owasp-zap:latest" -> (known after apply)
          ~ "name"                  = "zap_runner" -> "runners-owasp-zap"
            # (2 unchanged elements hidden)
        }
        # (1 unchanged attribute hidden)
    }

  # aws_cloudwatch_log_group.log must be replaced
-/+ resource "aws_cloudwatch_log_group" "log" {
      ~ arn               = "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs" -> (known after apply)
      ~ id                = "/aws/ecs/zap_runner_ecs" -> (known after apply)
      ~ name              = "/aws/ecs/zap_runner_ecs" -> "/aws/ecs/runners_owasp_zap_ecs" # forces replacement
      - tags              = {} -> null
      ~ tags_all          = {} -> (known after apply)
        # (1 unchanged attribute hidden)
    }

  # aws_ecr_repository.runners-owasp-zap will be created
  + resource "aws_ecr_repository" "runners-owasp-zap" {
      + arn                  = (known after apply)
      + id                   = (known after apply)
      + image_tag_mutability = "MUTABLE"
      + name                 = "scan-websites/runners/owasp-zap"
      + registry_id          = (known after apply)
      + repository_url       = (known after apply)
      + tags_all             = (known after apply)

      + image_scanning_configuration {
          + scan_on_push = true
        }
    }

  # aws_ecs_task_definition.runners-owasp-zap will be created
  + resource "aws_ecs_task_definition" "runners-owasp-zap" {
      + arn                      = (known after apply)
      + container_definitions    = (known after apply)
      + cpu                      = "2048"
      + execution_role_arn       = "arn:aws:iam::507252742351:role/container_execution_role"
      + family                   = "runners-owasp-zap"
      + id                       = (known after apply)
      + memory                   = "16384"
      + network_mode             = "awsvpc"
      + requires_compatibilities = [
          + "FARGATE",
        ]
      + revision                 = (known after apply)
      + tags                     = {
          + "CostCentre" = "scan-websites-production"
        }
      + tags_all                 = {
          + "CostCentre" = "scan-websites-production"
        }
      + task_role_arn            = "arn:aws:iam::507252742351:role/security_tools_execution_role"
    }

  # aws_ecs_task_definition.zap_runner will be destroyed
  - resource "aws_ecs_task_definition" "zap_runner" {
      - arn                      = "arn:aws:ecs:ca-central-1:507252742351:task-definition/zap_runner:1" -> null
      - container_definitions    = jsonencode(
            [
              - {
                  - cpu              = 0
                  - environment      = [
                      - {
                          - name  = "S3_BUCKET"
                          - value = "scan-websites-owasp-zap-report-data"
                        },
                      - {
                          - name  = "ZAP_PORT"
                          - value = "8080"
                        },
                    ]
                  - essential        = true
                  - image            = "507252742351.dkr.ecr.ca-central-1.amazonaws.com/scan-websites/scanners/owasp-zap:latest"
                  - linuxParameters  = {
                      - capabilities = {
                          - add = [
                              - "SYS_PTRACE",
                            ]
                        }
                    }
                  - logConfiguration = {
                      - logDriver = "awslogs"
                      - options   = {
                          - awslogs-group         = "/aws/ecs/zap_runner_ecs"
                          - awslogs-region        = "ca-central-1"
                          - awslogs-stream-prefix = "ecs-zap-runner"
                        }
                    }
                  - mountPoints      = []
                  - name             = "zap_runner"
                  - portMappings     = [
                      - {
                          - containerPort = 8001
                          - hostPort      = 8001
                          - protocol      = "tcp"
                        },
                    ]
                  - volumesFrom      = []
                },
              - {
                  - command          = [
                      - "zap.sh",
                      - "-daemon",
                      - "-host",
                      - "0.0.0.0",
                      - "-port",
                      - "8080",
                      - "-config",
                      - "api.disablekey=true",
                      - "-config",
                      - "api.addrs.addr.name=.*",
                      - "-config",
                      - "api.addrs.addr.regex=true",
                    ]
                  - cpu              = 1024
                  - environment      = []
                  - essential        = true
                  - image            = "owasp/zap2docker-stable"
                  - linuxParameters  = {
                      - capabilities = {
                          - drop = [
                              - "ALL",
                            ]
                        }
                    }
                  - logConfiguration = {
                      - logDriver = "awslogs"
                      - options   = {
                          - awslogs-group         = "/aws/ecs/zap_runner_ecs"
                          - awslogs-region        = "ca-central-1"
                          - awslogs-stream-prefix = "ecs-zap-runner"
                        }
                    }
                  - memory           = 3072
                  - mountPoints      = []
                  - name             = "zap2docker"
                  - portMappings     = [
                      - {
                          - containerPort = 8080
                          - hostPort      = 8080
                          - protocol      = "tcp"
                        },
                      - {
                          - containerPort = 8090
                          - hostPort      = 8090
                          - protocol      = "tcp"
                        },
                    ]
                  - volumesFrom      = []
                },
            ]
        ) -> null
      - cpu                      = "2048" -> null
      - execution_role_arn       = "arn:aws:iam::507252742351:role/container_execution_role" -> null
      - family                   = "zap_runner" -> null
      - id                       = "zap_runner" -> null
      - memory                   = "16384" -> null
      - network_mode             = "awsvpc" -> null
      - requires_compatibilities = [
          - "FARGATE",
        ] -> null
      - revision                 = 1 -> null
      - tags                     = {
          - "CostCentre" = "scan-websites-production"
        } -> null
      - tags_all                 = {
          - "CostCentre" = "scan-websites-production"
        } -> null
      - task_role_arn            = "arn:aws:iam::507252742351:role/security_tools_execution_role" -> null
    }

  # aws_iam_policy.scanners-owasp-zap will be updated in-place
  ~ resource "aws_iam_policy" "scanners-owasp-zap" {
        id        = "arn:aws:iam::507252742351:policy/scan-websites-scanners-owasp-zap"
        name      = "scan-websites-scanners-owasp-zap"
      ~ policy    = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = [
                          - "logs:PutLogEvents",
                          - "logs:CreateLogStream",
                          - "logs:CreateLogGroup",
                        ]
                      - Effect   = "Allow"
                      - Resource = [
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs:log-stream:*",
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs",
                        ]
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "ecr:GetDownloadUrlForlayer",
                          - "ecr:BatchGetImage",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:ecr:ca-central-1:507252742351:repository/scan-websites/scanners/owasp-zap"
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "ecs:RunTask",
                          - "ecs:DescribeTaskDefinition",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:ecs:ca-central-1:507252742351:task-definition/zap_runner:1"
                      - Sid      = ""
                    },
                  - {
                      - Action   = "iam:PassRole"
                      - Effect   = "Allow"
                      - Resource = [
                          - "arn:aws:iam::507252742351:role/security_tools_execution_role",
                          - "arn:aws:iam::507252742351:role/container_execution_role",
                        ]
                      - Sid      = ""
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> (known after apply)
        tags      = {}
        # (4 unchanged attributes hidden)
    }

  # aws_iam_policy.zap_runner_policies will be updated in-place
  ~ resource "aws_iam_policy" "zap_runner_policies" {
        id        = "arn:aws:iam::507252742351:policy/OWASPZapTaskExecutionPolicies"
        name      = "OWASPZapTaskExecutionPolicies"
      ~ policy    = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = [
                          - "ecr:GetDownloadUrlForLayer",
                          - "ecr:GetAuthorizationToken",
                          - "ecr:BatchGetImage",
                          - "ecr:BatchCheckLayerAvailability",
                        ]
                      - Effect   = "Allow"
                      - Resource = "*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = "lambda:InvokeFunction"
                      - Effect   = "Allow"
                      - Resource = "arn:aws:lambda:ca-central-1:507252742351:function:scan-websites*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "logs:PutLogEvents",
                          - "logs:CreateLogStream",
                          - "logs:CreateLogGroup",
                        ]
                      - Effect   = "Allow"
                      - Resource = [
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs:log-stream:*",
                          - "arn:aws:logs:ca-central-1:507252742351:log-group:/aws/ecs/zap_runner_ecs",
                        ]
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "ec2:DeleteNetworkInterface",
                          - "ec2:CreateNetworkInterface",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:ec2:ca-central-1:507252742351:network-interface/*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = "ec2:DescribeNetworkInterfaces"
                      - Effect   = "Allow"
                      - Resource = "*"
                      - Sid      = ""
                    },
                  - {
                      - Action   = [
                          - "s3:PutObjectAcl",
                          - "s3:PutObject",
                        ]
                      - Effect   = "Allow"
                      - Resource = "arn:aws:s3:::scan-websites-owasp-zap-report-data"
                      - Sid      = ""
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> (known after apply)
        tags      = {}
        # (4 unchanged attributes hidden)
    }

  # aws_lambda_function.scanners-owasp-zap will be updated in-place
  ~ resource "aws_lambda_function" "scanners-owasp-zap" {
        id                             = "scanners-owasp-zap"
        tags                           = {}
        # (17 unchanged attributes hidden)

      ~ environment {
          ~ variables = {
              - "CLUSTER"            = "arn:aws:ecs:ca-central-1:507252742351:cluster/scanning-tools"
              - "PRIVATE_SUBNETS"    = "subnet-0c3abcc29d00b8589,subnet-0d16dd34a0f0a4589,subnet-0e48adc704a15a982"
              - "REPORT_DATA_BUCKET" = "scan-websites-owasp-zap-report-data"
              - "SECURITY_GROUP"     = "sg-0711e8c6c24e225c7"
              - "TASK_DEF_ARN"       = "arn:aws:ecs:ca-central-1:507252742351:task-definition/zap_runner:1"
            } -> (known after apply)
        }


        # (2 unchanged blocks hidden)
    }

Plan: 3 to add, 3 to change, 2 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: plan.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "plan.tfplan"
Releasing state lock. This may take a few moments...

@mohdnr mohdnr merged commit c2a59b0 into main Aug 17, 2021
@mohdnr mohdnr deleted the feat/zap_ecs_container branch August 17, 2021 17:16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants