Commit 9ed99eea authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

initial commit

parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+204 −0
Original line number Diff line number Diff line
# Created by https://www.toptal.com/developers/gitignore/api/venv,python,VisualStudioCode
# Edit at https://www.toptal.com/developers/gitignore?templates=venv,python,VisualStudioCode

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml


### venv ###
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
pip-selfcheck.json

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide

# End of https://www.toptal.com/developers/gitignore/api/venv,python,VisualStudioCode

.DS_Store

.gitlab-ci.yml

0 → 100644
+138 −0
Original line number Diff line number Diff line
include:
  - project: "to-be-continuous/docker"
    ref: "5.4"
    file: "templates/gitlab-ci-docker.yml"
  - project: 'to-be-continuous/python'
    ref: '6.3'
    file: '/templates/gitlab-ci-python.yml'

stages:
  - build
  - test
  - package-build
  - package-test
  - infra
  - deploy
  - acceptance
  - publish
  - infra-prod
  - production

variables:
  PYTHON_IMAGE: "registry.hub.docker.com/library/python:3.11"
  PYTHON_SBOM_DISABLED: "true"
  DOCKER_BUILD_ARGS: "--cache-ttl=6h"
  DOCKER_PROD_PUBLISH_STRATEGY: "auto"
  AWS_ACCOUNT: 123456789012
  AWS_REGION: us-east-1

.test-scripts: &test-scripts |
  # BEGSCRIPT
  set -e

  function log_info() {
      echo -e "[\\e[1;94mINFO\\e[0m] $*"
  }

  function log_warn() {
      echo -e "[\\e[1;93mWARN\\e[0m] $*"
  }

  function log_error() {
      echo -e "[\\e[1;91mERROR\\e[0m] $*"
  }

  function fail() {
    log_error "$*"
    exit 1
  }

  function assert_eq() {
    local expected="$1"
    local actual="$2"

    if [ "$expected" == "$actual" ]; then
      log_info "$expected == $actual"
      return 0
    else
      fail "$expected == $actual"
      return 1
    fi
  }

  # ENDSCRIPT

.test-base:
  image: "registry.hub.docker.com/badouralix/curl-jq"
  stage: acceptance
  services:
    - name: "$DOCKER_SNAPSHOT_IMAGE"
      alias: "aws-auth-provider"
  before_script:
    - *test-scripts

test-ping:
  extends: .test-base
  script:
    # test: ping responds pong
    - response_status=`curl -s -o "resp.txt" -w "%{http_code}" http://aws-auth-provider/ping`
    - assert_eq "200" $response_status
    - assert_eq "pong" $(cat resp.txt)

# test: get token with implicit env detection fails if no credentials are passed (error 401)
test-token-no-creds-fails:
  extends: .test-base
  script:
    - response_status=$(curl -s -o "resp.txt" -w "%{http_code}" "http://aws-auth-provider/ecr/token")
    - cat resp.txt
    - assert_eq "401" $response_status

# test: get token with invalid OIDC role fails with 500
test-token-invalid-oidc-fails:
  extends: .test-base
  variables:
    AWS_OIDC_ROLE_ARN: "arn:aws:iam::$AWS_ACCOUNT:role/no-such-role"
    AWS_JWT: $AWS_JWT
  id_tokens:
    # required by the AWS auth provider service (OIDC authentication method)
    AWS_JWT:
      aud: "$CI_SERVER_URL"
  script:
    - response_status=$(curl -s -o "resp.txt" -w "%{http_code}" "http://aws-auth-provider/ecr/token")
    - cat resp.txt
    - assert_eq "500" $response_status

# test: get token with valid OIDC account and provider shall succeed
# requires the following project variables are set:
# - AWS_WORKING_ACCOUNT
# - AWS_WORKING_REGION
# - AWS_WORKING_OIDC_ROLE_ARN
test-token-succeeds:
  extends: .test-base
  variables:
    AWS_JWT: $AWS_JWT
    AWS_TEST_REGION: $AWS_WORKING_REGION
    AWS_TEST_OIDC_ROLE_ARN: $AWS_WORKING_OIDC_ROLE_ARN
  id_tokens:
    # required by the AWS auth provider service (OIDC authentication method)
    AWS_JWT:
      aud: "$CI_SERVER_URL"
  script:
    - response_status=$(curl -s -o "resp.txt" -w "%{http_code}" "http://aws-auth-provider/ecr/token?env_ctx=TEST")
    - |
      if [[ "$response_status" != 200 ]]
      then
        echo "Get ECR token failed ($response_status)"
        curl -s -v "http://aws-auth-provider/ecr/token?env_ctx=TEST"
      fi
    - assert_eq "200" $response_status
    - ecr_token=$(cat resp.txt)
    # see: https://docs.docker.com/registry/spec/api/#listing-repositories
    - |
      response_status=$(curl -s -o resp.txt -w "%{http_code}" -H "Authorization: Basic $ecr_token" "https://$AWS_WORKING_ACCOUNT.dkr.ecr.$AWS_WORKING_REGION.amazonaws.com/v2/_catalog")
    - assert_eq "200" $response_status
  rules:
    - if: $CI_SERVER_HOST != "gitlab.com"
      when: never
    - if: '$AWS_WORKING_ACCOUNT && $AWS_WORKING_REGION && $AWS_WORKING_OIDC_ROLE_ARN'
 No newline at end of file

Dockerfile

0 → 100644
+29 −0
Original line number Diff line number Diff line
FROM registry.hub.docker.com/library/python:3.11 as requirements-stage
 
WORKDIR /tmp

# hadolint ignore=DL3013
RUN pip install --no-cache-dir poetry

COPY ./pyproject.toml ./poetry.lock* /tmp/

RUN poetry export -f requirements.txt --output requirements.txt --without-hashes

###########

FROM registry.hub.docker.com/library/python:3.11-slim-buster

ENV PORT=80
WORKDIR /code
 
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt

RUN apt-get -y update && apt-get -y upgrade \
    &&  rm -rf /var/lib/apt/lists/* \
    && pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

EXPOSE ${PORT}
# hadolint ignore=DL3025
CMD uvicorn app.main:app --host=0.0.0.0 --port=${PORT}

README.md

0 → 100644
+108 −0
Original line number Diff line number Diff line
# AWS Auth Provider

This project builds a Docker image with an API able to retrieve authorization tokens from AWS APIs.

It is aimed at being used in GitLab CI as a [service container](https://docs.gitlab.com/ee/ci/services/)
in order to decouple the image of your jobs and the way AWS authorization tokens are retrieved.

## API usage

### The notion of `env_ctx`

This API supports the notion of `env_ctx`. It can either be guessed contextually (read next chapter), or explicitly passed in all API endpoints.

The `env_ctx` is used when retrieving a configuration value - say `SOME_SECRET`:

* the value will first be readed from `$AWS_{env_ctx}_SOME_SECRET`,
* if not valuated, will fallback to `$AWS_SOME_SECRET`.

This is therefore a way of specializing configuration variables to a specific context.

#### How is guessed `env_ctx`?

When not explicitly set, `env_ctx` is automatically guessed based on [GitLab predefined variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html):

| `$CI_COMMIT_REF_NAME` | `env_ctx` value |
| --------------------- | --------------- |
| `master` or `main`    | **PROD** if `$CI_JOB_STAGE` is one of `publish`, `infra-prod`, `production`, `.post`<br/>**STAGING** otherwise |
| `develop`             | **INTEG** |
| _any other branch_    | **REVIEW** |

### Supported authentication methods

The API supports two authentication methods:

1. basic authentication with AWS access key ID & secret access key,
2. or [federated authentication using OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/aws/).

#### Basic authentication

If you wish to use this authentication method, you'll have pass the AWS access key ID & secret access key as environment variables.
The API is able to segregate variables per environment type.

The expected environment variables are `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (with their specialized values depending on the `env_ctx`).

#### Federated authentication using OpenID Connect

The API supports [OpenID Connect to retrieve temporary credentials](https://docs.gitlab.com/ee/ci/cloud_services/aws/).

If you wish to use this authentication mode, please apply carefully the instructions from the GitLab guide, then provide the following variables to the API:

* `AWS_JWT` for the JWT token (using GitLab [ID Tokens](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html)),
* `AWS_OIDC_ROLE_ARN`: the configured role ARN.

You may specialize those variables for the current `env_ctx`.

### API endpoint: GET ECR token

This API retrieves an [ECR authorization token](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetAuthorizationToken.html).

#### Query Parameters

| Name       | Description                                                   | Required              | 
|------------|---------------------------------------------------------------|-----------------------|
| `region`   | AWS region to use                                             | no _(can be retrieved from env)_ |
| `env_ctx`  | the [environment context to consider](#the-notion-of-env_ctx) | no _(can be guessed)_ |

## Use in Gitlab CI

Finally, the Docker image can be used in your GitLab CI files as follows:

```yaml
variables:
  # required by the AWS auth provider service
  AWS_REGION: us-east-1
  # default OIDC role ARN (OIDC authentication method)
  AWS_OIDC_ROLE_ARN: "arn:aws:iam::123456789012:role/cicd-role-default"
  # specialized AWS region for 'TEST1'
  AWS_TEST1_REGION: eu-west-1
  # specialized OIDC role ARN for 'TEST1'
  AWS_TEST1_OIDC_ROLE_ARN: "arn:aws:iam::123456789012:role/cicd-role-test1"

docker-build-step1:
  image: docker:24.0.5
  services:
    # add Docker daemon
    - name: docker:24.0.5-dind
    # add AWS Auth Provider as a service
    - name: $CI_REGISTRY/to-be-continuous/tools/aws-auth-provider:main
      alias: aws-auth-provider
  variables:
    # my variable (not required by AWS auth provider service)
    ecr_registry: 123456789012.dkr.ecr.$AWS_TEST1_REGION.amazonaws.com
  id_tokens:
    # required by the AWS auth provider service (OIDC authentication method)
    AWS_JWT:
      aud: "https://123456789012.dkr.ecr.$AWS_TEST1_REGION.amazonaws.com"
  before-script:
    # retrieve authorization token from ECR (in context 'TEST1')
    - ecr_token=$(curl -s -S -f "http://aws-auth-provider/ecr/token?env_ctx=TEST1")
    # login
    - echo "$ecr_token" | docker login --username AWS --password-stdin $ecr_registry
    - docker info
  script:
    # build and push my image
    - docker build --tag ecr_registry/my-image:latest .
    - docker push ecr_registry/my-image:latest
```
 No newline at end of file

SECURITY.md

0 → 100644
+14 −0
Original line number Diff line number Diff line
# Security Policy

## Supported Versions

Security fixes and updates are only applied to the latest released version. So always try to be up to date.

## Reporting a Vulnerability

In order to minimize risks of attack while investigating and fixing the issue, any vulnerability shall be reported by 
opening a [**confidential** issue on gitlab.com](https://gitlab.com/to-be-continuous/tools/aws-auth-provider/-/issues/new).

Follow-up and fixing will be made on a _best effort_ basis.

If you have doubts about a potential vulnerability, please reach out one of the maintainers on Discord.