Commit 026513d2 authored by Apoorva Srinivas Appadoo's avatar Apoorva Srinivas Appadoo
Browse files

feat: add kubeconfig management


Squashed commit of the following:

commit 9d9fa92a557769aaba65af639756f3f95da0ef0e
Author: Apoorva Srinivas Appadoo <apoorva-srinivas.appadoo@etu.univ-cotedazur.fr>
Date:   Mon Oct 6 07:59:01 2025 +0200

    fix: ruff

    Signed-off-by: default avatarApoorva Srinivas Appadoo <apoorva-srinivas.appadoo@etu.univ-cotedazur.fr>

commit 494230abb96310d5794f14e6aef4dc69b9a816a4
Author: Apoorva Srinivas Appadoo <apoorva-srinivas.appadoo@etu.univ-cotedazur.fr>
Date:   Fri Oct 3 13:52:12 2025 +0200

    chore: remove dotenv loading and logging configuration that was used for debug

    Signed-off-by: default avatarApoorva Srinivas Appadoo <apoorva-srinivas.appadoo@etu.univ-cotedazur.fr>

commit 086283eaf23321dfcbb61ac8ef5375317b85d336
Author: Apoorva Srinivas Appadoo <apoorva-srinivas.appadoo@etu.univ-cotedazur.fr>
Date:   Fri Oct 3 13:43:05 2025 +0200

    feat: add kubeconfig generation

    Signed-off-by: default avatarApoorva Srinivas Appadoo <apoorva-srinivas.appadoo@etu.univ-cotedazur.fr>
parent 022b5837
Loading
Loading
Loading
Loading
+97 −0
Original line number Diff line number Diff line
@@ -118,6 +118,77 @@ This API retrieves the [CodeArtifact repository endpoint](https://docs.aws.amazo
| `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 /kubeconfig`

This API generates a complete kubeconfig file for an AWS EKS cluster with a valid authentication token.

The generated kubeconfig includes:
- Cluster endpoint and certificate authority
- Pre-authenticated token (matches `aws eks get-token` format)
- Configurable namespace and user name

The authentication token is generated using AWS STS and is compatible with EKS authentication requirements.

#### Query Parameters

| Name           | Description                                                   | Required              | Default         |
|----------------|---------------------------------------------------------------|-----------------------|-----------------|
| `cluster_name` | Name of the EKS cluster                                       | **yes**               | -               |
| `region`       | AWS region where the cluster is located                       | no _(can be retrieved from env)_ | -               |
| `env_ctx`      | the [environment context to consider](#the-notion-of-env_ctx) | no _(can be guessed)_ | -               |
| `role_arn`     | AWS role ARN for authentication                               | no _(can be retrieved from env)_ | -               |
| `ttl_minutes`  | Time-to-live for the token in minutes (max: 15)               | no                    | `15`            |
| `namespace`    | Default namespace for kubectl context                         | no                    | `default`       |
| `user_name`    | Username in the kubeconfig                                    | no                    | `kubectl-user`  |

#### Example Usage

```bash
# Generate kubeconfig for a cluster
curl -s "http://aws-auth-provider/kubeconfig?cluster_name=my-cluster&region=us-east-1" > kubeconfig.yaml

# Use the kubeconfig
export KUBECONFIG=kubeconfig.yaml
kubectl get nodes
```

#### Example Response

```yaml
apiVersion: v1
kind: Config
clusters:
- name: my-cluster
  cluster:
    server: https://ABC123.gr7.us-east-1.eks.amazonaws.com
    certificate-authority-data: LS0tLS1CRUdJTi...
contexts:
- name: my-cluster-context
  context:
    cluster: my-cluster
    user: kubectl-user
    namespace: default
current-context: my-cluster-context
users:
- name: kubectl-user
  user:
    token: k8s-aws-v1.aHR0cHM6Ly9zdHMuYW1hem9u...
preferences: {}
metadata:
  token-ttl-minutes: 60
  token-expires-at: '2025-10-03T12:00:00Z'
  cluster-arn: arn:aws:eks:us-east-1:123456789012:cluster/my-cluster
  cluster-version: '1.33'
  cluster-status: ACTIVE
  authentication-mode: API
```

#### Notes

- The generated token TTL can be configured from 1 to 15 minutes (default: 15 minutes)
- Token TTL is capped at 15 minutes for security best practices (as recommended by AWS)
- Ensure your AWS credentials have `eks:DescribeCluster` permission for the cluster and permissions to access the cluster




@@ -164,4 +235,30 @@ docker-build-step1:
    # build and push my image
    - docker build --tag ecr_registry/my-image:latest .
    - docker push ecr_registry/my-image:latest

deploy-to-eks:
  image: bitnami/kubectl:latest
  services:
    # add AWS Auth Provider as a service
    - name: $CI_REGISTRY/to-be-continuous/tools/aws-auth-provider:latest
      alias: aws-auth-provider
  variables:
    EKS_CLUSTER_NAME: my-production-cluster
    #  secrets have to be explicitly declared in the YAML to be exported to the service
    AWS_JWT: "$AWS_JWT"
  id_tokens:
    # required by the AWS auth provider service (OIDC authentication method)
    AWS_JWT:
      aud: "https://sts.amazonaws.com"
  before_script:
    # retrieve kubeconfig from AWS Auth Provider
    - curl -s -S -f "http://aws-auth-provider/kubeconfig?cluster_name=$EKS_CLUSTER_NAME&env_ctx=PROD" > /tmp/kubeconfig.yaml
    - export KUBECONFIG=/tmp/kubeconfig.yaml
    # verify connection
    - kubectl cluster-info
    - kubectl get nodes
  script:
    # deploy your application
    - kubectl apply -f k8s/deployment.yaml
    - kubectl rollout status deployment/my-app
```
+197 −1
Original line number Diff line number Diff line
import base64
import json
import logging
import os
import re
import tempfile
from datetime import datetime, timedelta
from functools import cache
from http import HTTPStatus
from typing import Literal, Optional

import boto3
import yaml
from botocore.signers import RequestSigner
from fastapi import FastAPI, HTTPException, Query, Request, Response
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
@@ -133,7 +137,7 @@ async def request_validation_exception_handler(
) -> Response:
    return PlainTextResponse(
        status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
        content=jsonable_encoder(exc.errors()),
        content=json.dumps(jsonable_encoder(exc.errors()), indent=2),
    )


@@ -269,3 +273,195 @@ def get_codeartifact_repository_endpoint(
    )

    return response["repositoryEndpoint"]


def get_eks_token(cluster_name, ttl_minutes=15) -> str:
    """
    Generate an EKS authentication token that matches the format of 'aws eks get-token'.

    Args:
        cluster_name: Name of the EKS cluster
        ttl_minutes: Time-to-live in minutes (default: 15, max: 15 for security best practices)

    Returns:
        EKS authentication token in k8s-aws-v1 format
    """
    # Cap TTL at 15 minutes as recommended by AWS for security
    # STS presigned URLs support longer TTLs, but EKS recommends short-lived tokens
    ttl_minutes = min(ttl_minutes, 15)

    session = boto3.session.Session()
    client = session.client(
        "sts", region_name="us-east-1"
    )  # Always use us-east-1 for global endpoint
    service_id = client.meta.service_model.service_id

    signer = RequestSigner(
        service_id,
        "us-east-1",  # Global STS endpoint uses us-east-1
        "sts",
        "v4",
        session.get_credentials(),
        session.events,
    )

    params = {
        "method": "GET",
        "url": "https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15",  # Global endpoint
        "body": {},
        "headers": {
            "x-k8s-aws-id": cluster_name,
        },
        "context": {},
    }

    # Generate presigned URL with the header included in the signature
    signed_url = signer.generate_presigned_url(
        params,
        region_name="us-east-1",
        expires_in=ttl_minutes * 60,  # Convert minutes to seconds
        operation_name="",
    )

    # Kubernetes expects the token prefixed with "k8s-aws-v1."
    token = "k8s-aws-v1." + base64.urlsafe_b64encode(signed_url.encode("utf-8")).decode(
        "utf-8"
    ).rstrip("=")
    return token


@app.get("/kubeconfig", response_class=PlainTextResponse)
def generate_kubeconfig(
    cluster_name: str = Query(alias="cluster_name"),
    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"),
    ttl_minutes: int = Query(default=15, ge=1, le=15, alias="ttl_minutes"),
    namespace: str = Query(default="default", alias="namespace"),
    user_name: str = Query(default="kubectl-user", alias="user_name"),
) -> str:
    """
    Generate a kubeconfig file by retrieving cluster information from AWS EKS.
    The kubeconfig will be AWS-agnostic with a token that has the specified TTL.

    Args:
        cluster_name: Name of the EKS cluster
        env_ctx: Environment context for AWS authentication
        region: AWS region where the cluster is located
        role_arn: AWS role ARN for authentication
        ttl_minutes: Time-to-live for the token in minutes (default: 15, max: 15 for security)
        namespace: Default namespace (default: "default")
        user_name: Username for the kubeconfig (default: "kubectl-user")

    Returns:
        YAML-formatted kubeconfig as a string
    """

    configure_boto(env_ctx, region, role_arn)

    # Get EKS cluster information
    eks_client = boto3.client("eks")
    try:
        cluster_response = eks_client.describe_cluster(name=cluster_name)
        cluster = cluster_response["cluster"]
        logger.info(f"Retrieved cluster info for {cluster_name}")
        logger.debug(f"Cluster structure: {json.dumps(cluster, indent=2, default=str)}")

        # Check cluster status
        cluster_status = cluster.get("status", "UNKNOWN")
        if cluster_status != "ACTIVE":
            logger.warning(
                f"Cluster {cluster_name} is not ACTIVE (status: {cluster_status})"
            )
            if cluster_status == "CREATING":
                raise HTTPException(
                    status_code=503,
                    detail=f"EKS cluster '{cluster_name}' is still being created. Please wait for it to become ACTIVE.",
                )
            elif cluster_status in ["FAILED", "DELETING"]:
                raise HTTPException(
                    status_code=503,
                    detail=f"EKS cluster '{cluster_name}' is in {cluster_status} state and cannot be accessed.",
                )

    except HTTPException:
        raise  # Re-raise HTTP exceptions
    except Exception as e:
        logger.error(f"Failed to describe EKS cluster {cluster_name}: {str(e)}")
        raise HTTPException(
            status_code=404,
            detail=f"EKS cluster '{cluster_name}' not found or not accessible: {str(e)}",
        )

    # Extract cluster information
    cluster_endpoint = cluster.get("endpoint")
    if not cluster_endpoint:
        raise HTTPException(
            status_code=500, detail=f"EKS cluster '{cluster_name}' endpoint not found"
        )
    # Extract certificate authority data with better error handling
    cert_authority = cluster.get("certificateAuthority")
    if not cert_authority:
        raise HTTPException(
            status_code=500,
            detail=f"Certificate authority not found for cluster '{cluster_name}'",
        )

    cluster_ca_data = cert_authority.get("data")
    if not cluster_ca_data:
        raise HTTPException(
            status_code=500,
            detail=f"Certificate authority data not found for cluster '{cluster_name}'",
        )

    # Generate token with specified TTL
    token = get_eks_token(cluster_name, ttl_minutes=ttl_minutes)

    # Calculate expiration time
    expiry_time = datetime.utcnow() + timedelta(minutes=ttl_minutes)

    # Access configuration (if provided by EKS)
    access_config = cluster.get("accessConfig", {}) if isinstance(cluster, dict) else {}

    # Create kubeconfig structure
    kubeconfig = {
        "apiVersion": "v1",
        "kind": "Config",
        "clusters": [
            {
                "name": cluster_name,
                "cluster": {
                    "server": cluster_endpoint,
                    "certificate-authority-data": cluster_ca_data,
                },
            }
        ],
        "contexts": [
            {
                "name": f"{cluster_name}-context",
                "context": {
                    "cluster": cluster_name,
                    "user": user_name,
                    "namespace": namespace,
                },
            }
        ],
        "current-context": f"{cluster_name}-context",
        "users": [{"name": user_name, "user": {"token": token}}],
        "preferences": {},
        # Add metadata about token expiration for reference
        "metadata": {
            "token-ttl-minutes": ttl_minutes,
            "token-expires-at": expiry_time.isoformat() + "Z",
            "cluster-arn": cluster.get("arn", ""),
            "cluster-version": cluster.get("version", ""),
            "cluster-status": cluster.get("status", ""),
            "authentication-mode": access_config.get(
                "authenticationMode", "CONFIG_MAP"
            ),
            "note": "If authentication fails, ensure your AWS IAM user/role is mapped in the cluster's access entries (for API mode) or aws-auth ConfigMap (for CONFIG_MAP mode)",
        },
    }

    # Convert to YAML and return
    return yaml.dump(kubeconfig, default_flow_style=False, sort_keys=False)
+85 −2
Original line number Diff line number Diff line
# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.

[[package]]
name = "anyio"
@@ -358,6 +358,89 @@ files = [
[package.dependencies]
six = ">=1.5"

[[package]]
name = "pyyaml"
version = "6.0.3"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
    {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"},
    {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"},
    {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"},
    {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"},
    {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"},
    {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"},
    {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"},
    {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
    {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
    {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"},
    {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"},
    {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"},
    {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"},
    {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"},
    {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"},
    {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"},
    {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"},
    {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"},
    {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"},
    {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"},
    {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"},
    {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"},
    {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"},
    {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"},
    {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"},
    {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"},
    {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"},
    {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"},
    {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"},
    {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"},
    {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"},
    {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"},
    {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"},
    {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"},
    {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"},
    {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"},
    {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"},
    {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"},
    {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"},
    {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"},
    {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"},
    {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"},
    {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"},
    {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"},
    {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"},
    {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"},
    {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"},
    {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"},
    {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"},
    {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"},
    {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"},
    {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"},
    {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"},
    {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"},
    {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"},
    {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"},
    {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"},
    {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"},
    {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"},
    {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"},
    {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"},
    {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"},
    {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"},
    {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"},
    {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"},
    {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"},
    {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"},
    {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"},
    {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"},
    {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"},
    {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"},
    {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"},
    {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
]

[[package]]
name = "ruff"
version = "0.13.3"
@@ -500,4 +583,4 @@ standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)
[metadata]
lock-version = "2.1"
python-versions = ">=3.12"
content-hash = "f5f67c2941bd31d46be2ce5b1d01aaed68a2568b9c16d00c80036ea095e484af"
content-hash = "7489c09075bf5da6db0d6c2f9ec67d1126d0c2522db9eabb9564ea30e2ec116c"
+1 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ description = ""
authors = [ ]
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["fastapi (>=0.116.0,<1.0.0)", "starlette (>=0.48.0,<1.0.0)", "uvicorn (>=0.36.0,<1.0.0)", "boto3 (>=1.40.35,<2.0.0)", "urllib3 (>=2.5.0,<3.0.0)", "h11 (>=0.16.0,<1.0.0)", "idna (>=3.10,<4.0)", "pydantic-core (>=2.39.0,<3.0.0)"]
dependencies = ["fastapi (>=0.116.0,<1.0.0)", "starlette (>=0.48.0,<1.0.0)", "uvicorn (>=0.36.0,<1.0.0)", "boto3 (>=1.40.35,<2.0.0)", "urllib3 (>=2.5.0,<3.0.0)", "h11 (>=0.16.0,<1.0.0)", "idna (>=3.10,<4.0)", "pydantic-core (>=2.39.0,<3.0.0)", "pyyaml (>=6.0,<7.0)"]

[project.scripts]
aws-auth-provider = "aws_auth_provider:run"