最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to use both Service Principal and System Assigned Identity in Django on Azure Container Apps? - Stack Overflow

programmeradmin0浏览0评论

Before using Azure Container Apps (ACA), I deployed my applications on Kubernetes (AKS). In AKS, I only had to set environment variables for authentication, and the Python SDK for Azure would connect to my subscription seamlessly.

Now, I need to migrate my app to ACA, but in a different subscription while still requiring access to resources in the original subscription.

Context: My team (Team A) has been granted permission by our security officer to continue using a Service Principal to access Subscription A. We are working in a Hub-and-Spoke configuration, where network flows are open between Subscription A and Subscription B. However, Team B does not want to grant permissions to our Service Principal to access MySQL in Subscription B. Instead, they have configured System Assigned Managed Identity (MSI) for our ACA instance. What works: Currently, my Django application successfully accesses resources in Subscription A using DefaultAzureCredential, with the following environment variables:

  • AZURE_TENANT_ID
  • AZURE_CLIENT_ID
  • AZURE_CLIENT_SECRET

This setup works fine when accessing Subscription A from Subscription B.

The issue: My MySQL database is in Subscription B, and I need to authenticate using the System Assigned Managed Identity (MSI) instead of the Service Principal.

Question: How can I use System Assigned Identity for MySQL in Django while keeping the rest of my Python code authenticated via DefaultAzureCredential (which still needs the Service Principal)?

My Django settings: I tried configuring the database connection as follows:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": env("DATABASE_NAME"),
        "HOST": env("DATABASE_HOST"),
        "PORT": 3306,
        "CONN_MAX_AGE": env.int("DATABASE_CONN_MAX_AGE", default=0),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "Authentication=ActiveDirectoryMsi;Encrypt=yes;TrustServerCertificate=no",
        },
    }
}

Error received: I get the following error:

File "/home/docker4sg/miniconda3/envs/py310/lib/python3.10/site-packages/django/db/backends/sqlite3/base.py", line 200, in get_new_connection
    conn = Database.connect(**conn_params)
TypeError: 'driver' is an invalid keyword argument for this function

I think this error is linked to a bad driver installation. And it's not linked to SP/MSI.

Debugging with az cli: When I tried debugging using Azure CLI, I ran:

az login --identity

And got this error:

Failed to connect to MSI. Please make sure MSI is configured correctly.
Get Token request returned: <Response [405]>

I found this post suggesting a workaround, so I tried:

az login --identity                       
No access was configured for the VM, hence no subscriptions were found. If this is expected, use '--allow-no-subscriptions' to have tenant level access.

Is it possible to use both a Service Principal (via DefaultAzureCredential) and System Assigned Identity (for MySQL) at the same time?

If so, how can I configure Django to use System Assigned Identity for MySQL, while keeping DefaultAzureCredential for the rest of the Python SDK calls?

EDIT:

Some more debug, using ManagedIdentityCredential:

from azure.identity import ManagedIdentityCredential
credential = ManagedIdentityCredential(
       client_id="XXXXXXX-XXXX-XXXX-XXXX-XXXXXXX",
   )

I get this error:

azure.identity._exceptions.CredentialUnavailableError: App Service managed identity configuration not found in environment. invalid_scope

Removing the client_id I don't get an error on the auth, I can get a token.

from azure.identity import ManagedIdentityCredential

credential = ManagedIdentityCredential()
token_msi = credential.get_token(
    "/.default"
).token

import pymysql

pymysql.connect(
    host="xxxxxxxxxxxxx.mysql.database.azure",
    user="xxxxxx-xxxx-xxxx-xxxx-xxxxxx",
    password=token_msi,
    db="my_db",
)

And I get:

pymysql.err.OperationalError: (1045, "Access denied for user '52181d1e-0ab2-4e0c-b19d-876223d1f44d'@'10.34.45.173' (using password: YES)")

I've checked in the IAM, and it has the correct roles. But I don't have any database created.

Before using Azure Container Apps (ACA), I deployed my applications on Kubernetes (AKS). In AKS, I only had to set environment variables for authentication, and the Python SDK for Azure would connect to my subscription seamlessly.

Now, I need to migrate my app to ACA, but in a different subscription while still requiring access to resources in the original subscription.

Context: My team (Team A) has been granted permission by our security officer to continue using a Service Principal to access Subscription A. We are working in a Hub-and-Spoke configuration, where network flows are open between Subscription A and Subscription B. However, Team B does not want to grant permissions to our Service Principal to access MySQL in Subscription B. Instead, they have configured System Assigned Managed Identity (MSI) for our ACA instance. What works: Currently, my Django application successfully accesses resources in Subscription A using DefaultAzureCredential, with the following environment variables:

  • AZURE_TENANT_ID
  • AZURE_CLIENT_ID
  • AZURE_CLIENT_SECRET

This setup works fine when accessing Subscription A from Subscription B.

The issue: My MySQL database is in Subscription B, and I need to authenticate using the System Assigned Managed Identity (MSI) instead of the Service Principal.

Question: How can I use System Assigned Identity for MySQL in Django while keeping the rest of my Python code authenticated via DefaultAzureCredential (which still needs the Service Principal)?

My Django settings: I tried configuring the database connection as follows:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": env("DATABASE_NAME"),
        "HOST": env("DATABASE_HOST"),
        "PORT": 3306,
        "CONN_MAX_AGE": env.int("DATABASE_CONN_MAX_AGE", default=0),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "Authentication=ActiveDirectoryMsi;Encrypt=yes;TrustServerCertificate=no",
        },
    }
}

Error received: I get the following error:

File "/home/docker4sg/miniconda3/envs/py310/lib/python3.10/site-packages/django/db/backends/sqlite3/base.py", line 200, in get_new_connection
    conn = Database.connect(**conn_params)
TypeError: 'driver' is an invalid keyword argument for this function

I think this error is linked to a bad driver installation. And it's not linked to SP/MSI.

Debugging with az cli: When I tried debugging using Azure CLI, I ran:

az login --identity

And got this error:

Failed to connect to MSI. Please make sure MSI is configured correctly.
Get Token request returned: <Response [405]>

I found this post suggesting a workaround, so I tried:

az login --identity                       
No access was configured for the VM, hence no subscriptions were found. If this is expected, use '--allow-no-subscriptions' to have tenant level access.

Is it possible to use both a Service Principal (via DefaultAzureCredential) and System Assigned Identity (for MySQL) at the same time?

If so, how can I configure Django to use System Assigned Identity for MySQL, while keeping DefaultAzureCredential for the rest of the Python SDK calls?

EDIT:

Some more debug, using ManagedIdentityCredential:

from azure.identity import ManagedIdentityCredential
credential = ManagedIdentityCredential(
       client_id="XXXXXXX-XXXX-XXXX-XXXX-XXXXXXX",
   )

I get this error:

azure.identity._exceptions.CredentialUnavailableError: App Service managed identity configuration not found in environment. invalid_scope

Removing the client_id I don't get an error on the auth, I can get a token.

from azure.identity import ManagedIdentityCredential

credential = ManagedIdentityCredential()
token_msi = credential.get_token(
    "https://ossrdbms-aad.database.windows/.default"
).token

import pymysql

pymysql.connect(
    host="xxxxxxxxxxxxx.mysql.database.azure",
    user="xxxxxx-xxxx-xxxx-xxxx-xxxxxx",
    password=token_msi,
    db="my_db",
)

And I get:

pymysql.err.OperationalError: (1045, "Access denied for user '52181d1e-0ab2-4e0c-b19d-876223d1f44d'@'10.34.45.173' (using password: YES)")

I've checked in the IAM, and it has the correct roles. But I don't have any database created.

Share Improve this question edited Mar 18 at 13:53 BeGreen asked Mar 18 at 9:06 BeGreenBeGreen 9531 gold badge19 silver badges53 bronze badges 3
  • 1 To use both a Service Principal (via DefaultAzureCredential) for Subscription A and a System Assigned Managed Identity (MSI) for MySQL in Subscription B, configure your Django database connection to use ManagedIdentityCredential for MySQL authentication and DefaultAzureCredential for other Azure SDK calls. Set up a custom connection using mysql-connector-python with MSI authentication for MySQL, while keeping the rest of the app authenticated via DefaultAzureCredential. Ensure your ACA instance has the required permissions on the MySQL server. – Rukmini Commented Mar 18 at 12:26
  • Trying to run ManagedIdentityCredential I get this error: azure.identity._exceptions.CredentialUnavailableError: App Service managed identity configuration not found in environment. invalid_scope But it seems to be a good strategy to use specific auth and not the default one that uses var env as priority 1. – BeGreen Commented Mar 18 at 13:48
  • It was indeed the good solution, it did not work directly because they made an alias on the client_id, meaning I had to set a predefined user for the database connection. I had to use ManagedIdentityCredential without client_id argument. – BeGreen Commented Mar 19 at 8:00
Add a comment  | 

1 Answer 1

Reset to default 0

Here is the solution to connect directly to mysql server from Azure.

from azure.identity import ManagedIdentityCredential
import pymysql

# Get Managed Identity Token
credential = ManagedIdentityCredential()
token_msi = credential.get_token(
    "https://ossrdbms-aad.database.windows/.default"
).token


# Define connection parameters
connection = pymysql.connect(
    host=f"xxxxxxxx.mysql.database.azure",
    user="xxxx", # Predefined my an alias from team B. I did not use the client_id
    password=token_msi,
    db=f"xxxx", # Same here, they made a db with the name of the project
    ssl={"fake_flag_to_enable_tls": True}
)

# Execute a query
query = "SHOW DATABASES;"

with connection.cursor() as cursor:
    cursor.execute(query)
    print(cursor.fetchall())

For Django I had to do a bit more, in settings.py

from .get_azure_token import get_db_token


DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": "xxxx",
        "USER": "xxxx",
        "PASSWORD": None,  # Token will be assigned dynamically
        "HOST": f"xxxxx.mysql.database.azure",
        "PORT": 3306,
    }
}

get_db_token()

I had to pip install mysqlclient + azure-identity. Also had to install a MySQL Client CLI on the linux for Django to work. Did not managed to make it work with pymysql which is pure python implementation of mysql client.

Made a file get_azure_token with this content:

import os
import django.conf as conf
from azure.identity import ManagedIdentityCredential

def get_db_token():
    azure_credential = ManagedIdentityCredential()
    token = azure_credential.get_token(
        "https://ossrdbms-aad.database.windows"
    ).token
    conf.settings.DATABASES["default"]["PASSWORD"] = token

You can use DefaultAzureCredential instead of ManagedIdentityCredential if you want to adapt the way you connect. I my case I needed the System Identity to connect instead of the Service Principal declare in environment variables.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论