Building Flask web application in Github Actions

In my previous post, I described a process of using an inline services block to run an external Postgresql database to run unit tests as part of the CI pipeline for a Flask web application in Github Action.

However, running unit tests is just a single aspect of a CI/CD pipeline. As documented by the AWS Application Deployment Pipeline Reference, we also need to include additional stages such as code linting and formatting; secrets detection; and static security testing (SAST). This post will detail how I incorporated those additional stages into the Github pipeline before building and pushing the final docker image into ECR.

The diagram in AWS Application Deployment Pipeline Reference shows an additional Local Development stage which incorporates the same stages as in the build stage. These could be incorporated into the development workflow using precommit hooks, which we will explore in a future post.

The code sample below shows an example Github Actions workflow for a Flask application:

name: Pull Request
on:
  pull_request:
    types: [opened, reopened, synchronize]

jobs:
    code-quality:
        runs-on: ubuntu-latest
        needs: [unit-tests]
        steps:
        - name: Checkout
            uses: actions/checkout@v4

        - name: Install Ruff
            uses: astral-sh/ruff-action@v3
        
        - name: Lint code
            run: ruff check --output-format=github --target-version=py312
        
        - name: Code formatting
            id: format
            run: |
            ruff format --check --diff --target-version=py312 > res.md || {
                echo "## Formatting Issues" >> $GITHUB_STEP_SUMMARY
                echo '```diff' >> $GITHUB_STEP_SUMMARY
                cat res.md >> $GITHUB_STEP_SUMMARY
                echo '```' >> $GITHUB_STEP_SUMMARY
            }
            continue-on-error: true

    secrets-detection:
        runs-on: ubuntu-latest
        needs: [unit-tests]
        permissions:
        pull-requests: write
        contents: read
        steps:
        - name: Checkout
            uses: actions/checkout@v4
            with:
            fetch-depth: 0

        - name: Secrets Scanning
            uses: gitleaks/gitleaks-action@v2
            env:
            GITHUB_TOKEN: $
            GITLEAKS_ENABLE_COMMENTS: false

    security-testing:
        needs: [unit-tests]
        permissions:
        actions: read
        contents: read
        uses: ./.github/workflows/security_scan.yml

The jobs above correspond to the follow stages in the Build stage:

  • Code Quality
  • Secrets Detection
  • Static Application Security Testing ( SAST )
  • Package Artifacts

For Code Quality we are using ruff which is a Python linter and code formatter. The recommended approach is to also use it in pre-commit hooks for local development. In the job, we are installing the astral-sh/ruff-action action and then performing a linting and formatting check. For the formatting check, we output any errors found to the workflow summary.

For secrets detection, we are using the gitleaks/gitleaks-action action which scans the repository for any secrets commited such as API keys.

For SAST, we created another workflow which uses bandit. bandit is a tool which scans a python file, builds an abstract syntax tree ( AST ) from it and applies the appropriate plugins during a scan to discover any security issues.

The external workflow is created as a Reusable Github workflow. The workflow is registered as a workflow_call type which allows it to be imported and referenced in the calling workflow. The initial idea here is to create a reusable workflow which can be used later in other pipelines.

The steps for the security scan worflow is as follows:

name: Bandit security tests

on:
  workflow_call:

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      
      - name: Setup python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12.11"
      
      - name: Install Bandit
        shell: bash
        run: pip install bandit[sarif,toml]

      - name: Perform Bandit Analysis
        shell: bash
        run: |
          bandit -c bandit.yml -r . -f sarif -o results.sarif || true
      
      - name: Upload SARIF file
        uses: actions/upload-artifact@v4
        with:
          name: bandit-results.sarif
          path: |
            results.sarif

The bandit program uses a configuration file in the project repository which specifies which directories to ignore as well as some checks to skip. The scan results are output into a results.sarif file which is attached as an artifact in the next step.

Under Packaged Artifacts we use docker to build the application image and deploy it to AWS ECR service. The image will be tagged with the SHA of the build. The build step is described below:

  build:
    runs-on: ubuntu-latest
    outputs:
      image_uri: $
    permissions:
      id-token: write
      contents: read
    needs: [code-quality, secrets-detection, security-testing]
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@main
        with:
          role-to-assume: arn:aws:iam::$:role/$
          aws-region: $
      
      - name: Test AWS credentials
        run: |
          aws sts get-caller-identity
      
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2
      
      - name: Build and push image
        id: ecr_build
        env:
          REGISTRY: $
          REPOSITORY: flaskapp
        run: |
          SHORT_SHA=$(echo $GITHUB_SHA | cut -c1-7)
          docker build -t $REGISTRY/$REPOSITORY:$SHORT_SHA .
          docker push $REGISTRY/$REPOSITORY:$SHORT_SHA

          echo "image_uri=${REPOSITORY}:${SHORT_SHA}" >> "$GITHUB_OUTPUT"

The build stage relies on the previous three stages described above to complete successfully before it can proceed. We use the official aws-actions/configure-aws-credentials action to create short-lived dynamic credentials. The action requires a pre-configured IAM OIDC role which has the right permission policy to access the current github repository. We call aws sts get-caller-identity to check that the credentials were issued successfully. Next we use the official aws-actions/amazon-ecr-login to login to ECR. Finally, we call docker to create a build based on the current commit SHA and pushing it into the ECR repository. We also output the final ECR image URI as an output of the current stage so it can be reused in another part of the pipeline.

The screenshot below shows the pipeline running successfully on a push to a pull request branch: Github workflow run build stage

The screenshot below shows a successful ECR image build and push: ECR repository

The build pipeline shown here is still a works in progress. We still have not implemented the following steps:

  • software component analysis scans using Dependabot or Renovate to check for vulnerabilities in dependencies

  • software bill of materials ( SBOM ) which details all the dependencies used.

Future posts will explore these areas as the pipeline is improved upon.