diff --git a/function-app-v1/ProviderRelay/__init__.py b/function-app-v1/ProviderRelay/__init__.py index 061eeb2..cd76574 100644 --- a/function-app-v1/ProviderRelay/__init__.py +++ b/function-app-v1/ProviderRelay/__init__.py @@ -1,92 +1,92 @@ -# Copyright 2024 Stacklet -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -import json -import logging -import os - -import azure.functions as func - -from azure.identity import DefaultAzureCredential - -import boto3 -import botocore - - -def get_session(client_id, audience, role_arn): - client = boto3.client("sts") - creds = DefaultAzureCredential( - managed_identity_client=client_id, exclude_environment_credential=True - ) - token = creds.get_token(audience) - try: - res = client.assume_role_with_web_identity( - WebIdentityToken=token.token, - RoleArn=role_arn, - RoleSessionName="StackletAzureRelay", - ) - except Exception as e: - logging.error(f"unable to assume role:{e}") - raise - - session = boto3.session.Session( - aws_access_key_id=res["Credentials"]["AccessKeyId"], - aws_secret_access_key=res["Credentials"]["SecretAccessKey"], - aws_session_token=res["Credentials"]["SessionToken"], - ) - logging.info("Got session") - return session - - -def main(msg: func.QueueMessage): - client_id = os.environ["AZURE_CLIENT_ID"] - audience = os.environ["AZURE_AUDIENCE"] - - target_account = os.environ["AWS_TARGET_ACCOUNT"] - region = os.environ["AWS_TARGET_REGION"] - role_name = os.environ["AWS_TARGET_ROLE_NAME"] - partition = os.environ["AWS_TARGET_PARTITION"] - role_arn = f"arn:{partition}:iam::{target_account}:role/{role_name}" - - session = get_session(client_id, audience, role_arn) - events_client = session.client("events", region_name=region) - - body_string = msg.get_body().decode("utf-8") - body = json.loads(body_string) - source = body["data"]["operationName"].split("/")[0] - - try: - logging.info('Forwarding event to Stacklet') - logging.info(body_string) - events_client.put_events( - Entries=[ - { - "Time": msg.insertion_time, - "Source": source, - "DetailType": "CloudEvent/Azure System Topic Event", - "Detail": body_string, - "EventBusName": os.environ["AWS_TARGET_EVENT_BUS"], - } - ] - ) - except botocore.exceptions.ClientError as e: - if e.response["Error"]["Code"] == "AccessDeniedException" and str(e).endswith( - "with an explicit deny in a resource-based policy" - ): - logging.warning("skipping event") - return - logging.error(f"failed to put event:{e}") - raise +# Copyright 2024 Stacklet +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import json +import logging +import os + +import azure.functions as func + +from azure.identity import DefaultAzureCredential + +import boto3 +import botocore + + +def get_session(client_id, audience, role_arn): + client = boto3.client("sts") + creds = DefaultAzureCredential( + managed_identity_client=client_id, exclude_environment_credential=True + ) + token = creds.get_token(audience) + try: + res = client.assume_role_with_web_identity( + WebIdentityToken=token.token, + RoleArn=role_arn, + RoleSessionName="StackletAzureRelay", + ) + except Exception as e: + logging.error(f"unable to assume role:{e}") + raise + + session = boto3.session.Session( + aws_access_key_id=res["Credentials"]["AccessKeyId"], + aws_secret_access_key=res["Credentials"]["SecretAccessKey"], + aws_session_token=res["Credentials"]["SessionToken"], + ) + logging.info("Got session") + return session + + +def main(msg: func.QueueMessage): + client_id = os.environ["AZURE_CLIENT_ID"] + audience = os.environ["AZURE_AUDIENCE"] + + target_account = os.environ["AWS_TARGET_ACCOUNT"] + region = os.environ["AWS_TARGET_REGION"] + role_name = os.environ["AWS_TARGET_ROLE_NAME"] + partition = os.environ["AWS_TARGET_PARTITION"] + role_arn = f"arn:{partition}:iam::{target_account}:role/{role_name}" + + session = get_session(client_id, audience, role_arn) + events_client = session.client("events", region_name=region) + + body_string = msg.get_body().decode("utf-8") + body = json.loads(body_string) + source = body["data"]["operationName"].split("/")[0] + + try: + logging.info('Forwarding event to Stacklet') + logging.info(body_string) + events_client.put_events( + Entries=[ + { + "Time": msg.insertion_time, + "Source": source, + "DetailType": "CloudEvent/Azure System Topic Event", + "Detail": body_string, + "EventBusName": os.environ["AWS_TARGET_EVENT_BUS"], + } + ] + ) + except botocore.exceptions.ClientError as e: + if e.response["Error"]["Code"] == "AccessDeniedException" and str(e).endswith( + "with an explicit deny in a resource-based policy" + ): + logging.warning("skipping event") + return + logging.error(f"failed to put event:{e}") + raise diff --git a/function.tf b/function.tf index 1fbdaa0..c40e6f5 100644 --- a/function.tf +++ b/function.tf @@ -44,15 +44,14 @@ resource "azurerm_linux_function_app" "stacklet" { application_stack { python_version = "3.10" } + application_insights_key = azurerm_application_insights.stacklet.instrumentation_key } app_settings = { SCM_DO_BUILD_DURING_DEPLOYMENT = true - APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.stacklet.instrumentation_key AZURE_CLIENT_ID = azurerm_user_assigned_identity.stacklet_identity.client_id AZURE_AUDIENCE = local.audience AZURE_STORAGE_QUEUE_NAME = azurerm_storage_queue.stacklet.name - AZURE_SUBSCRIPTION_ID = data.azurerm_subscription.current.subscription_id AWS_TARGET_ACCOUNT = var.aws_target_account AWS_TARGET_REGION = var.aws_target_region AWS_TARGET_ROLE_NAME = var.aws_target_role_name @@ -65,6 +64,12 @@ resource "azurerm_linux_function_app" "stacklet" { identity_ids = [azurerm_user_assigned_identity.stacklet_identity.id] } tags = local.tags + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-resource-id"] + ] + } } resource "local_file" "function_json" { diff --git a/locals.tf b/locals.tf index 7c5bb10..776a47b 100644 --- a/locals.tf +++ b/locals.tf @@ -15,9 +15,7 @@ # SPDX-License-Identifier: Apache-2.0 locals { - object_id = azurerm_user_assigned_identity.stacklet_identity.principal_id app_role_id = var.azuread_application == null ? random_uuid.app_role_uuid.id : data.azuread_application.stacklet_application[0].app_role_ids.AssumeRoleWithWebIdentity - resource_id = local.azuread_service_principal.object_id audience = "api://stacklet/provider/azure/${var.aws_target_prefix}" diff --git a/main.tf b/main.tf index 64c359a..fb66127 100644 --- a/main.tf +++ b/main.tf @@ -27,8 +27,12 @@ data "azurerm_role_definition" "builtin" { resource "random_uuid" "app_role_uuid" {} +locals { + resource_group_name = coalesce(var.resource_group_name, var.prefix) +} + resource "azurerm_resource_group" "stacklet_rg" { - name = var.prefix + name = local.resource_group_name location = var.resource_group_location tags = local.tags } @@ -85,15 +89,8 @@ data "azuread_service_principal" "stacklet_sp" { display_name = var.azuread_application } -resource "null_resource" "stacklet" { - depends_on = [local.azuread_application, local.azuread_service_principal] - provisioner "local-exec" { - command = <