Created
December 13, 2021 12:42
-
-
Save snobu/684b768fc2f00654b74f6d5fe525d49c to your computer and use it in GitHub Desktop.
Use Managed Identity to access Azure SQL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<packages> | |
<package id="Azure.Core" version="1.20.0" targetFramework="net472" /> | |
<package id="Azure.Identity" version="1.5.0" targetFramework="net472" /> | |
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.0.0" targetFramework="net472" /> | |
<package id="Microsoft.Identity.Client" version="4.30.1" targetFramework="net472" /> | |
<package id="Microsoft.Identity.Client.Extensions.Msal" version="2.18.4" targetFramework="net472" /> | |
<package id="System.Buffers" version="4.5.1" targetFramework="net472" /> | |
<package id="System.Diagnostics.DiagnosticSource" version="4.6.0" targetFramework="net472" /> | |
<package id="System.Memory" version="4.5.4" targetFramework="net472" /> | |
<package id="System.Memory.Data" version="1.0.2" targetFramework="net472" /> | |
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" /> | |
<package id="System.Runtime.CompilerServices.Unsafe" version="4.6.0" targetFramework="net472" /> | |
<package id="System.Security.Cryptography.ProtectedData" version="4.5.0" targetFramework="net472" /> | |
<package id="System.Text.Encodings.Web" version="4.7.2" targetFramework="net472" /> | |
<package id="System.Text.Json" version="4.6.0" targetFramework="net472" /> | |
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" /> | |
<package id="System.ValueTuple" version="4.5.0" targetFramework="net472" /> | |
</packages> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Data.SqlClient; | |
using System.Threading.Tasks; | |
using Azure.Identity; | |
using Azure.Core; | |
namespace NET47ManagedIdentitytoAzureSQL | |
{ | |
class MainClass | |
{ | |
public static async Task Main(string[] args) | |
{ | |
// You should be reading these from your app configuration. | |
// Treat both server and database name as secrets (e.g. read them from Key Vault) | |
string server = "<YOUR_AZURE_SQL_INSTANCE>.database.windows.net"; | |
string database = "<DATABASE_NAME>"; | |
string connectionString = $"Data Source={server}; Initial Catalog={database}; Encrypt=True; Trusted_Connection=False;"; | |
SqlConnection conn = new SqlConnection(connectionString); | |
// Before you can use access tokens you need to enable Managed Identity | |
// (either system or user assigned) on your Virtual Machine(s) - | |
// https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/tutorial-windows-vm-access-sql#enable | |
// | |
// Then, on the Azure SQL side create a contained user and assign roles to it - | |
// https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/tutorial-windows-vm-access-sql#create-contained-user | |
// | |
// In essence: | |
// CREATE USER [VM_NAME] FROM EXTERNAL PROVIDER | |
// ALTER ROLE db_datareader ADD MEMBER [VM_NAME] | |
// | |
// VM_NAME is the name of the *system* assigned Managed Identity for your VM. | |
// It's the same with the VM name by default. | |
// | |
// If you use *user* assigned managed identity use the string literal name | |
// of the identity as your [VM_NAME] value | |
// Let's use *user* assigned managed identity - | |
string userAssignedClientId = "b1b8ea61-32a6-466c-8c81-1102545fc1e0"; | |
string audience = "https://database.windows.net"; | |
// Open a connection to the database using an access token | |
try | |
{ | |
AccessToken accessToken = await GetManagedIdentityAccessToken(audience, userAssignedClientId); | |
conn.AccessToken = accessToken.Token; | |
conn.Open(); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine($"[EXCEPTION] {ex.Source}: {ex.Message}"); | |
Console.ReadKey(); | |
Environment.Exit(11); | |
} | |
Console.WriteLine($"Connection state is {conn.State} to database {conn.Database}\n"); | |
Console.WriteLine("List of tables in the database:"); | |
string query = @"SELECT * FROM INFORMATION_SCHEMA.TABLES"; | |
SqlCommand cmd = new SqlCommand(query, conn); | |
SqlDataReader dr = cmd.ExecuteReader(); | |
if (dr.HasRows) | |
{ | |
while (dr.Read()) | |
{ | |
// Show retrieved records | |
Console.WriteLine($"{dr.GetString(0)} " + | |
$"{dr.GetString(1)} " + | |
$"{dr.GetString(2)} " + | |
$"{dr.GetString(3)}"); | |
} | |
} | |
else | |
{ | |
Console.WriteLine("Connection to the database is successful but the query returned no data."); | |
} | |
Console.WriteLine("\nPress any key to exit."); | |
Console.ReadKey(); | |
} | |
/// <summary> | |
/// Get a Managed Identity access token with a *system* assigned identity | |
/// </summary> | |
/// <param name="audience">The resource token gets issued for</param> | |
/// <returns></returns> | |
public static async Task<AccessToken> GetManagedIdentityAccessToken(string audience) | |
{ | |
// For *system* assigned managed identity - | |
var credential = new DefaultAzureCredential(); | |
TokenRequestContext context = new TokenRequestContext( | |
// The value here is also known as "resource" or token "audience" | |
new[] { audience }); | |
AccessToken accessToken = await credential.GetTokenAsync(context); | |
return accessToken; | |
} | |
/// <summary> | |
/// Get a Managed Identity access token with a *user* assigned identity | |
/// </summary> | |
/// <param name="audience">The resource token gets issued for</param> | |
/// <param name="userAssignedClientId">Client ID (GUID) of user assigned identity</param> | |
/// <returns></returns> | |
public static async Task<AccessToken> GetManagedIdentityAccessToken(string audience, string userAssignedClientId) | |
{ | |
// For *user* assigned managed identity - | |
// Use the GUID of the user assigned managed identity when creating | |
// the DefaultAzureCredential instance. | |
var credential = new DefaultAzureCredential( | |
new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId }); | |
TokenRequestContext context = new TokenRequestContext( | |
// The value here is also known as "resource" or token "audience" | |
new[] { audience }); | |
AccessToken accessToken = await credential.GetTokenAsync(context); | |
return accessToken; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment