Commit c879ca9f authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch 'master' into 'master'

feat: add codeartifact queries

See merge request to-be-continuous/tools/aws-auth-provider!53
parents 96cb83b0 6fb962b0
Loading
Loading
Loading
Loading
+97 −6
Original line number Diff line number Diff line
@@ -88,15 +88,15 @@ test-health:
    - assert_eq "ok" $(cat resp.txt)

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

# test: get token with invalid OIDC role fails with 500
test-token-invalid-oidc-fails:
.test-token-invalid-oidc-fails:
  extends: .test-base
  variables:
    AWS_OIDC_ROLE_ARN: "arn:aws:iam::$AWS_ACCOUNT:role/no-such-role"
@@ -106,16 +106,55 @@ test-token-invalid-oidc-fails:
    AWS_JWT:
      aud: "$CI_SERVER_URL"
  script:
    - response_status=$(curl -s -o "resp.txt" -w "%{http_code}" "http://aws-auth-provider/ecr/auth/token")
    - response_status=$(curl -s -o "resp.txt" -w "%{http_code}" "${TEST_ENDPOINT}")
    - cat resp.txt
    - assert_eq "500" $response_status
    
test-ecr-token-no-creds-fails:
  extends: .test-token-no-creds-fails
  variables:
    TEST_ENDPOINT: "http://aws-auth-provider/ecr/auth/token"

test-ecr-token-invalid-oidc-fails:
  extends: .test-token-invalid-oidc-fails
  variables:
    TEST_ENDPOINT: "http://aws-auth-provider/ecr/auth/token"

test-codeartifact-token-no-creds-fails:
  extends: .test-token-no-creds-fails
  variables:
    TEST_ENDPOINT: "http://aws-auth-provider/codeartifact/auth/token"

test-codeartifact-repository-no-creds-fails:
  extends: .test-token-no-creds-fails
  variables:
    TEST_ENDPOINT: "http://aws-auth-provider/codeartifact/repository/endpoint?format=pypi"
    AWS_CODEARTIFACT_DOMAIN: "my-domain"
    AWS_CODEARTIFACT_DOMAIN_OWNER: "123456789012"
    AWS_CODEARTIFACT_REPOSITORY: "my-repo"

test-codeartifact-token-invalid-oidc-fails:
  extends: .test-token-invalid-oidc-fails
  variables:
    TEST_ENDPOINT: "http://aws-auth-provider/codeartifact/auth/token"
    AWS_CODEARTIFACT_DOMAIN: "my-domain"
    AWS_CODEARTIFACT_DOMAIN_OWNER: "123456789012"

test-codeartifact-repository-invalid-oidc-fails:
  extends: .test-token-invalid-oidc-fails
  variables:
    TEST_ENDPOINT: "http://aws-auth-provider/codeartifact/repository/endpoint?format=pypi"
    AWS_CODEARTIFACT_DOMAIN: "my-domain"
    AWS_CODEARTIFACT_DOMAIN_OWNER: "123456789012"
    AWS_CODEARTIFACT_REPOSITORY: "my-repo"


# 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:
test-ecr-token-succeeds:
  extends: .test-base
  variables:
    AWS_JWT: $AWS_JWT
@@ -146,3 +185,55 @@ test-token-succeeds:
    - if: $CI_SERVER_HOST != "gitlab.com"
      when: never
    - if: '$AWS_WORKING_ACCOUNT && $AWS_WORKING_REGION && $AWS_WORKING_OIDC_ROLE_ARN'

# 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
# - AWS_WORKING_CODEARTIFACT_DOMAIN
# - AWS_WORKING_CODEARTIFACT_DOMAIN_OWNER
# - CODEARTIFACT_REPOSITORY - an existing repository in the domain
# - CODEARTIFACT_PACKAGE_NAME - an existing package name in the repository
test-codeartifact-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
    AWS_TEST_CODEARTIFACT_DOMAIN: $AWS_WORKING_CODEARTIFACT_DOMAIN
    AWS_TEST_CODEARTIFACT_DOMAIN_OWNER: $AWS_WORKING_CODEARTIFACT_DOMAIN_OWNER
    AWS_TEST_CODEARTIFACT_REPOSITORY: $CODEARTIFACT_REPOSITORY
  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/codeartifact/auth/token?env_ctx=TEST")
    - |
      if [[ "$response_status" != 200 ]]
      then
        echo "Get codeartifact token failed ($response_status)"
        curl -s -v "http://aws-auth-provider/codeartifact/auth/token?env_ctx=TEST"
      fi
    - assert_eq "200" $response_status
    - codeartifact_token=$(cat resp.txt)
    - response_status=$(curl -s -o resp.txt -w "%{http_code}" "http://aws-auth-provider/codeartifact/repository/endpoint?format=pypi?env_ctx=TEST"
    - |
      if [[ "$response_status" != 200 ]]
      then
        echo "Get codeartifact repository endpoint failed ($response_status)"
        curl -s -v "http://aws-auth-provider/codeartifact/repository/endpoint?format=pypi?env_ctx=TEST"
      fi
    - assert_eq "200" $response_status
    - codeartifact_endpoint=$(cat resp.txt)
    - |
      response_status=$(curl -s -o resp.txt -w "%{http_code}" -H "Authorization: Bearer $codeartifact_token" "${codeartifact_endpoint}simple/${CODEARTIFACT_PACKAGE_NAME}/")
    - assert_eq "200" $response_status
  rules:
    # exclude tags
    - if: $CI_COMMIT_TAG
      when: never
    - if: $CI_SERVER_HOST != "gitlab.com"
      when: never
    - if: '$AWS_WORKING_ACCOUNT && $AWS_WORKING_REGION && $AWS_WORKING_OIDC_ROLE_ARN && $AWS_WORKING_CODEARTIFACT_DOMAIN && $AWS_WORKING_CODEARTIFACT_DOMAIN_OWNER && $CODEARTIFACT_PACKAGE_NAME && $CODEARTIFACT_REPOSITORY'
 No newline at end of file
+34 −1
Original line number Diff line number Diff line
@@ -88,6 +88,39 @@ This API retrieves the [ECR authorization token](https://docs.aws.amazon.com/Ama
| `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)_ |

### `GET /codeartifact/auth/token`

This API retrieves an [CodeArtifact authorization token](https://docs.aws.amazon.com/codeartifact/latest/APIReference/API_GetAuthorizationToken.html).
(can be used with [pip](https://docs.aws.amazon.com/codeartifact/latest/ug/python-configure-pip.html))


#### Query Parameters

| Name       | Description                                                   | Required              |
|------------|---------------------------------------------------------------|-----------------------|
| `domain`   | CodeArtifact domain to use                                    | no _(can be retrieved from env)_ |
| `domain_owner` | CodeArtifact domain owner to use                           | no _(can be retrieved from env)_ |
| `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)_ |

### `GET /codeartifact/repository/endpoint`

This API retrieves the [CodeArtifact repository endpoint](https://docs.aws.amazon.com/codeartifact/latest/APIReference/API_GetRepositoryEndpoint.html).

#### Query Parameters

| Name       | Description                                                   | Required              |
|------------|---------------------------------------------------------------|-----------------------|
| `format`   | the format of the endpoint (e.g. `npm`, `pypi`, `maven`) [full list](https://docs.aws.amazon.com/codeartifact/latest/APIReference/API_GetRepositoryEndpoint.html#CodeArtifact-GetRepositoryEndpoint-request-format) | yes |     
| `domain`   | CodeArtifact domain to use                                    | no _(can be retrieved from env)_ |
| `domain_owner` | CodeArtifact domain owner to use                           | no _(can be retrieved from env)_ |
| `repository` | CodeArtifact repository to use                              | no _(can be retrieved from env)_ |
| `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:
+82 −2
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ import re
import tempfile
from functools import cache
from http import HTTPStatus
from typing import Optional
from typing import Literal, Optional

import boto3
from fastapi import FastAPI, HTTPException, Query, Request, Response
@@ -173,3 +173,83 @@ def get_ecr_auth_password(
    # token is base64("<username>:<password>")
    user_password = base64.b64decode(b64token).decode("utf-8")
    return user_password.split(":")[1]


@app.get("/codeartifact/auth/token", response_class=PlainTextResponse)
def get_codeartifact_auth_token(
    env_ctx: str = Query(default=None, alias="env_ctx"),
    region: str = Query(default=None, alias="region"),
    role_arn: str = Query(default=None, alias="role_arn"),
    domain: str = Query(default=None, alias="domain"),
    domain_owner: str = Query(default=None, alias="domain_owner"),
) -> str:
    configure_boto(env_ctx, region, role_arn)
    # see: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codeartifact/client/get_authorization_token.html
    client = boto3.client("codeartifact")
    domain = domain or getenv_cleared(f"AWS_{env_ctx}_CODEARTIFACT_DOMAIN") or getenv_cleared(
        "AWS_CODEARTIFACT_DOMAIN"
    )

    if not domain:
        logger.error("AWS CodeArtifact domain not found")
        raise HTTPException(status_code=400, detail="AWS CodeArtifact domain not found")
    
    domain_owner = domain_owner or getenv_cleared(
        f"AWS_{env_ctx}_CODEARTIFACT_DOMAIN_OWNER"
    ) or getenv_cleared("AWS_CODEARTIFACT_DOMAIN_OWNER")
    
    if not domain_owner:
        logger.error("AWS CodeArtifact domain owner not found")
        raise HTTPException(
            status_code=400, detail="AWS CodeArtifact domain owner not found")
    
    response = client.get_authorization_token(
        domain=domain, domainOwner=domain_owner
    )
    
    return response["authorizationToken"]

@app.get("/codeartifact/repository/endpoint", response_class=PlainTextResponse)
def get_codeartifact_repository_endpoint(
    env_ctx: str = Query(default=None, alias="env_ctx"),
    region: str = Query(default=None, alias="region"),
    role_arn: str = Query(default=None, alias="role_arn"),
    domain: str = Query(default=None, alias="domain"),
    domain_owner: str = Query(default=None, alias="domain_owner"),
    repository: str = Query(default=None, alias="repository"),
    format: Literal['npm','pypi','maven','nuget','generic','ruby','swift','cargo'] = Query(alias='format')
) -> str:
    configure_boto(env_ctx, region, role_arn)
    # see: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codeartifact/client/get_repository_endpoint.html
    client = boto3.client("codeartifact")
    domain = domain or getenv_cleared(f"AWS_{env_ctx}_CODEARTIFACT_DOMAIN") or getenv_cleared(
        "AWS_CODEARTIFACT_DOMAIN"
    )

    if not domain:
        logger.error("AWS CodeArtifact domain not found")
        raise HTTPException(status_code=400, detail="AWS CodeArtifact domain not found")
    
    domain_owner = domain_owner or getenv_cleared(
        f"AWS_{env_ctx}_CODEARTIFACT_DOMAIN_OWNER"
    ) or getenv_cleared("AWS_CODEARTIFACT_DOMAIN_OWNER")
    
    if not domain_owner:
        logger.error("AWS CodeArtifact domain owner not found")
        raise HTTPException(
            status_code=400, detail="AWS CodeArtifact domain owner not found")
    
    repository = repository or getenv_cleared(
        f"AWS_{env_ctx}_CODEARTIFACT_REPOSITORY"
    ) or getenv_cleared("AWS_CODEARTIFACT_REPOSITORY")
    
    if not repository:
        logger.error("AWS CodeArtifact repository not found")
        raise HTTPException(
            status_code=400, detail="AWS CodeArtifact repository not found")
    
    response = client.get_repository_endpoint(
        domain=domain, domainOwner=domain_owner, repository=repository, format=format
    )
    
    return response["repositoryEndpoint"]
 No newline at end of file