diff --git a/src/britive/federation_providers/__init__.py b/src/britive/federation_providers/__init__.py index ba95794..04129f0 100644 --- a/src/britive/federation_providers/__init__.py +++ b/src/britive/federation_providers/__init__.py @@ -1,4 +1,5 @@ from .aws import AwsFederationProvider +from .aws_sts_jwt import AwsStsJwtFederationProvider from .azure_system_assigned_managed_identity import AzureSystemAssignedManagedIdentityFederationProvider from .azure_user_assigned_managed_identity import AzureUserAssignedManagedIdentityFederationProvider from .bitbucket import BitbucketFederationProvider @@ -12,6 +13,7 @@ class FederationProviders: def __init__(self, britive) -> None: self.aws = AwsFederationProvider(britive) + self.aws_sts_jwt = AwsStsJwtFederationProvider(britive) self.azure_system_assigned_managed_identity = AzureSystemAssignedManagedIdentityFederationProvider(britive) self.azure_user_assigned_managed_identity = AzureUserAssignedManagedIdentityFederationProvider(britive) self.bitbucket = BitbucketFederationProvider(britive) diff --git a/src/britive/federation_providers/aws_sts_jwt.py b/src/britive/federation_providers/aws_sts_jwt.py new file mode 100644 index 0000000..3e0e164 --- /dev/null +++ b/src/britive/federation_providers/aws_sts_jwt.py @@ -0,0 +1,40 @@ +from .federation_provider import FederationProvider + + +class AwsStsJwtFederationProvider(FederationProvider): + def __init__( + self, + profile: str = None, + audience: str = None, + duration_seconds: int = 300, + signing_algorithm: str = 'ES384', + ) -> None: + self.profile = profile + self.audience = audience or 'britive' + self.duration_seconds = max(60, min(3600, duration_seconds)) + self.signing_algorithm = signing_algorithm + super().__init__() + + def get_token(self) -> str: + try: + import boto3 + import botocore.exceptions as botoexceptions + except ImportError as e: + raise Exception( + 'boto3 required - please install boto3 package to use the aws-sts-jwt federation provider' + ) from e + + try: + session = boto3.Session(profile_name=self.profile) + except botoexceptions.ProfileNotFound as e: + raise Exception(f'Error: {e!s}') from e + + sts_client = session.client('sts') + + response = sts_client.get_web_identity_token( + Audience=[self.audience], + DurationSeconds=self.duration_seconds, + SigningAlgorithm=self.signing_algorithm, + ) + + return f'OIDC::{response["WebIdentityToken"]}' diff --git a/src/britive/helpers/utils.py b/src/britive/helpers/utils.py index be9cbce..1346dfa 100644 --- a/src/britive/helpers/utils.py +++ b/src/britive/helpers/utils.py @@ -10,6 +10,7 @@ from britive.exceptions.unauthorized import InvalidTenantError, unauthorized_code_map from britive.federation_providers import ( AwsFederationProvider, + AwsStsJwtFederationProvider, AzureSystemAssignedManagedIdentityFederationProvider, AzureUserAssignedManagedIdentityFederationProvider, BitbucketFederationProvider, @@ -102,9 +103,10 @@ def source_federation_token(provider: str, tenant: Optional[str] = None, duratio sourced outside of this SDK and provided as input via the standard token presentation options. - Six federation providers are currently supported by this method. + The following federation providers are currently supported by this method. * AWS IAM/STS, with optional profile specified - (aws) + * AWS STS JWT via GetWebIdentityToken - (awsstsjwt) * Azure System Assigned Managed Identities (azuresmi) * Azure User Assigned Managed Identities (azureumi) * Bitbucket Pipelines (bitbucket) @@ -116,14 +118,21 @@ def source_federation_token(provider: str, tenant: Optional[str] = None, duratio Any other OIDC federation provider can be used and tokens can be provided to this class for authentication to a Britive tenant. Details of how to construct these tokens can be found at https://docs.britive.com. - :param provider: The name of the federation provider. Valid options are `aws`, `azuresmi`, `azureumi`, `bitbucket`, - `gcp`, `github`, `gitlab`, and `spacelift`. + :param provider: The name of the federation provider. Valid options are `aws`, `awsstsjwt`, `azuresmi`, `azureumi`, + `bitbucket`, `gcp`, `github`, `gitlab`, and `spacelift`. For the AWS provider it is possible to provide a profile via value `aws-profile`. If no profile is provided then the boto3 `Session.get_credentials()` method will be used to obtain AWS credentials, which follows the order provided here: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials + For the AWS STS JWT provider (awsstsjwt) it is possible to provide optional parameters via pipe-delimited + values: `awsstsjwt-|||`. All parameters are optional. + Use an empty string for profile to skip it (e.g. `awsstsjwt-|myaudience`). Defaults: audience=`britive`, + signing_algorithm=`ES384`, duration_seconds=`300`. Valid signing algorithms are `ES384` and `RS256`. + Duration must be between 60 and 3600 seconds. This provider requires the AWS account to have IAM outbound + identity federation enabled. + For Azure User Assigned Managed Identities (azureumi) a client id is required. It must be provided in the form `azureumi-`. From the Azure documentation...a user-assigned identity's client ID or, when using Pod Identity, the client ID of an Azure AD app registration. This argument @@ -168,6 +177,16 @@ def source_federation_token(provider: str, tenant: Optional[str] = None, duratio if provider_name in federation_providers: return federation_providers[provider_name]() + if provider_name == 'awsstsjwt': + parts = helper[1].split('|') if len(helper) > 1 else [] + profile = safe_list_get(parts, 0) or None + audience = safe_list_get(parts, 1) or None + signing_algorithm = safe_list_get(parts, 2) or 'ES384' + duration = int(safe_list_get(parts, 3) or 300) + return AwsStsJwtFederationProvider( + profile=profile, audience=audience, duration_seconds=duration, signing_algorithm=signing_algorithm + ).get_token() + if provider_name == 'azuresmi': return AzureSystemAssignedManagedIdentityFederationProvider(audience=safe_list_get(helper, 1)).get_token()