Connecting to SQL Server using Powershell with Azure AD MFA - sql-server

I am trying to connect to my Azure SQL instance using an Access Token from Azure AD. I was following this tutorial over here: https://medium.com/microsoftazure/deploying-a-dacpac-to-azure-with-azure-pipelines-and-managed-identity-89703d405e00
But something is not working right in the approach.
The first thing was to make sure my user was setup in the database via:
CREATE USER [myemail#email.com] FROM EXTERNAL PROVIDER WITH DEFAULT_SCHEMA=[dbo]
Which is the same process from this answer: https://stackoverflow.com/a/62161471/1963929
And then I tested both SQL Server Management Studio and Azure Data Studio, both worked perfectly.
But when I try exactly the same thing in Powershell it does not work, all I get is the dread Login failed for user '<token-identified principal>'.
Here’s what I tried
$conn = New-Object System.Data.SqlClient.SQLConnection
$conn.ConnectionString = "Server=tcp:azure-sql.database.windows.net,1433;Initial Catalog=default;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
$conn.AccessToken = $(az account get-access-token --resource=https://database.windows.net/ --query accessToken)
$conn.Open()
The error that I receive when connecting to my db is the following
MethodInvocationException: Exception calling "Open" with "0" argument(s): "Login failed for user ''."
Then I thought “maybe I’m using the wrong settings” so I tried the using a client id that my app is using to connect to the same db. And this time I tested multiple scopes: none, .default, and user_impersonation.
$clientid = "azure-data-studio-client-id"
$request = Invoke-RestMethod -Method GET -Uri "https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode" -Body #{client_id=$clientid; scope="https://database.windows.net/user_impersonation"} -ContentType "application/x-www-form-urlencoded"
$request.message
$tokens = Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/organizations/oauth2/v2.0/token" -Body #{client_id=$clientid; grant_type="urn:ietf:params:oauth:grant-type:device_code"; code = $request.device_code} -ContentType "application/x-www-form-urlencoded"
$accesstoken = $tokens.access_token
So I thought maybe Azure Data Studio has superpowers, and used another account in there without doing CREATE USER and I got the right error
And this error proves me that CREATE USER is necessary, but it does not explain why I can't do this via Powershell.
I also tried Node and Tedious like the following:
const dbConfig = {
authentication: {
type: "azure-active-directory-access-token",
options: {
token: token
}
},
server: getDatabasePerEnvironment(environment),
database: databaseName,
options: {
trustServerCertificate: false,
encrypt: true,
port: 1433
}
};
const connection = new tedious.Connection(dbConfig);
Same error:
"ConnectionError: Login failed for user ''.
Does anyone know what I'm doing wrong?

I found the issue, and my problem is more minuscule than I thought.
I found my answer in this answer: Azure SQL Grant Access for AD User using PowerShell and ServicePrincipal
What's happening is this line:
$conn.AccessToken = $(az account get-access-token --resource=https://database.windows.net/ --query accessToken)
returns an Access Token wrapped in Double Quotes
$conn.AccessToken = $(az account get-access-token --subscription $subscription --resource https://database.windows.net --query accessToken -o tsv)
That -o tsv at the end will trim the double quotes from the output.
On TediousJS the problem was that I was doing
const tokenPayload = JSON.parse(execSync("az account get-access-token").toString());
What I needed to be doing is:
const tokenPayload = execSync(
"az account get-access-token --subscription YOUR-SUBSCRIPTION --resource https://database.windows.net --query accessToken -o tsv"
).toString();
So the steps you need to do to use the Azure CLI token with SQL Server are the following:
Configure an Active Directory Admin on Azure SQL
Execute something like the following to add your user
CREATE USER [youremail#mail.com] FROM EXTERNAL PROVIDER WITH DEFAULT_SCHEMA=[dbo]
ALTER ROLE db_datareader ADD MEMBER [youremail#mail.com];
ALTER ROLE db_datawriter ADD MEMBER [youremail#mail.com];
ALTER ROLE db_ddladmin ADD MEMBER [youremail#mail.com];
Confirm you can connect with Azure AD with SSMS or Azure Data Studio
Try the following:
$conn = New-Object System.Data.SqlClient.SQLConnection
$conn.ConnectionString = "Server=yourserver.database.windows.net;Initial Catalog=Subledger;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
$conn.AccessToken = $(az account get-access-token --subscription YOUR-SUBSCRIPTION --resource https://database.windows.net --query accessToken -o tsv)
$conn.Open()

Related

Terraform - Azure Service Principal deployment - insufficient permissions

I am trying to create azure service principal. I am connection to azure from my laptop using service principal, I have added required permissions, service principal (which I am using to connect to azure) is a member of global administrators, sp is a member of an application developer and application administrator role in azure AD. In order to connect to Azure I am using following PowerShell commands.
$ApplicationId = "aaa"
$AppPassword = "bbb"
$TenantId = "ccc"
$SecuredPassword = ConvertTo-SecureString -String $AppPassword -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList
$ApplicationId, $SecuredPassword
Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $Credential
When I try run - terraform apply -var-file="variables.tfvars" I am getting an error:
with azuread_application.azuread_app,
│ on service-principal.tf line 3, in resource "azuread_application" "azuread_app":
│ 3: resource "azuread_application" "azuread_app" {
│
│ ApplicationsClient.BaseClient.Post(): unexpected status 403 with OData error: Authorization_RequestDenied: Insufficient privileges to complete the operation.
I was able to deploy other resources without any problems. I am using remote backend state file, located on Azure Container.
terraform code below:
data "azuread_client_config" "client_config" {}
resource "azuread_application" "azuread_app" {
display_name = "sp_name"
owners = [data.azuread_client_config.client_config.object_id]
}
resource "azuread_service_principal" "azuread_sp" {
application_id = azuread_application.azuread_app.application_id
app_role_assignment_required = false
owners = [data.azuread_client_config.client_config.object_id]
}
resource "azuread_service_principal_password" "azuread_sp_password" {
service_principal_id = azuread_service_principal.azuread_sp.object_id
}
I tested the same scenario in my environment using the below code and the Service Principal was successfully created from terraform.
terraform {
backend "azurerm" {
storage_account_name = "cloudshellansuman123" # replace with your storage account name
container_name = "test" #replace with your container name
key = "terraform.tfstate"
access_key = "ukyaH/Jxxxxxxxxxxxxxxxxx="#replace with your storage account access key
}
}
provider "azurerm" {
features{}
client_id="de398e56-xxxxxxxxxxxxxxxxxx-20d07416ecb0"#replace with your service principal client ID which you are using to connect with Azure
client_secret= "-IP7Q~uDLoxxxxxxxxxxxxRGtHMMXj7-.-lA"#replace with your service principal client Secret which you are using to connect with Azure
tenant_id = "ab07xxxxxxxxxxxxxxx--xxx-620b694ded30"#replace with your AzureAD tenant ID which the subscription is a part of
subscription_id="8xxxxxxxxxxx-xxxxxxx-xxxxxxxxx-xxae"#replace with your Subscription ID on Which the Service Principal has Owner/Contributor access
}
provider "azuread" {
client_id="de398e-xx-x-x-x-x-x-x-x416ecb0"#replace with your service principal client ID which you are using to connect with Azure AD
client_secret= "-IP7Q~uDLoxxxxxxxxxxxxxxGtHMMXj7-.-lA"#replace with your service principal client Secret which you are using to connect with Azure AD
tenant_id = "ab0xxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxx30"#replace with your AzureAD tenant ID which the subscription is a part of
}
data "azuread_client_config" "current" {}
resource "azuread_application" "terraform" {
display_name = "Ansumantest"
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_application_password" "terraform" {
application_object_id = azuread_application.terraform.object_id
}
resource "azuread_service_principal" "terraform" {
application_id = azuread_application.terraform.application_id
owners = [data.azuread_client_config.current.object_id]
}
For Testing I created a Service Principal which I am using to connect with Azure and granted all the same permissions that you have granted and also added a Owner access on the subscription like below :
Output:
Note: Using the above code , you don't need again to connect to Azure using PowerShell. It will get authenticated directly using the .tf configuration. Also make sure to use Latest AzureRM and AzureAD provider Versions in terraform i.e. 2.95.0 & 2.17.0 respectively.

Get-ShardMapManager without Username and Password

Is it possible to call Get-ShardMapManager when creating a new shard map manager without involving username and password (through hardcoding or storing the values in key vault)
Here is the code that I currently have in powershell
$ShardMapManager = Get-ShardMapManager -Username $DbUsrId -Password $DbPwd
can i use token or something like that? just whatever that is not involving username and password? thanks
No, you can't. From the source code of Get-ShardMapManager, UserName and Password are all Mandatory=$true.
In this case, if you want to avoid using them just because of the security issue, you can use azure keyvault to store them via Set-AzKeyVaultSecret as you mentioned. Retrieve them in the commands, then pass them to Get-ShardMapManager, just user/service principal that added to the access policy of the keyvault is able to get them(or has the Key Vault Administrator if you select Azure role-based access control in Access policies blade of the keyvault).
Make sure you have installed Az powershell module, then use the command below.
Connect-AzAccount
$DbUsrId = Get-AzKeyVaultSecret -VaultName "keyvaultname" -Name "username" -AsPlainText
$DbPwd = Get-AzKeyVaultSecret -VaultName "keyvaultname" -Name "password" -AsPlainText
$ShardMapManager = Get-ShardMapManager -Username $DbUsrId -Password $DbPwd

How to Connect to azuread module non-interactively when MFA is enabled?

I need to connect to AD in azure function app using powershell script. (as it is in function i need to do it without prompt) I am trying this:
Import-Module D:\home\site\wwwroot\HttpTrigger1\AzureAD\AzureAD.psd1 -UseWindowsPowershell
$creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential
In my function app I have enabled Authentication through Log in with Azure Active Directory.
Is there a way to use that authentication in powershell script to connect to azuread module. I mean the user clicks on the function-app url, logs-in with their credentials and that authentication can be used in the script for connect-azuread. The current script is not working as MFA is enabled, which cannot be removed as per our use-case.
Use-case: I have an application in the form of an ARM template that would be deployed as a managed application.
The ARM template is supposed to deploy a set of resources on the tenant of the user, whoever purchases the app. But I need "client id"
and "client secret" of the application registration on user/customer's tenant with O365 mgt api permissions, as input to
my mainTemplate.json.
This App registration is a one-time thing and is not possible through ARM template, that is why I am trying to achieve the above via
powershell. I am creating a powershell function-app, enabled Authentication through Log in with Azure Active Directory.
Idea behind this approach is that at the time of purchasing the app, while filling-in other details(like Resource group name and region) at the UI(created by createUIDefinition.json), the user clicks on the function app link,
logs-in and the script runs in the background. The script should be able to create the app registration at the user's tenant and provide
back the client id and client secret of that app reg.
Unfortunately No !
If MFA is enabled, you will not be able to login non-interactively. This is kind of intentional considering to make it more secure. You cannot as well pass along the authentication.
The workaround for this you could possibly make use of the Azure Service Principal.Get the function authenticated and make the Azure Service Principal to do the job.
What Are Azure Service Principal ?
Service principals are non-interactive Azure accounts. Like other user accounts, their permissions are managed within Azure Active Directory.
Sharing some reference articles to get a deeper insight on the Azure Service Principal:
Creating the Service Principal (The creation is one time process)
https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#app-registration-app-objects-and-service-principals
Creating the Service Principal through Powershell
https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell#create-service-principal-with-self-signed-certificate
Authenticating the Service Principal
https://learn.microsoft.com/en-us/powershell/azure/authenticate-azureps?view=azps-5.1.0
Coming back to your scenario, to execute Connect-AzureAD with out the interactive login using the service principal you could use the below snippet
# Login to Azure AD PowerShell With Admin Account
Connect-AzureAD
# Create the self signed cert
$currentDate = Get-Date
$endDate = $currentDate.AddYears(1)
$notAfter = $endDate.AddYears(1)
$pwd = ""
$thumb = (New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my -DnsName com.foo.bar -KeyExportPolicy Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -NotAfter $notAfter).Thumbprint
$pwd = ConvertTo-SecureString -String $pwd -Force -AsPlainText
Export-PfxCertificate -cert "cert:\localmachine\my\$thumb" -FilePath c:\temp\examplecert.pfx -Password $pwd
# Load the certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate("C:\temp\examplecert.pfx", $pwd)
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
# Create the Azure Active Directory Application
$application = New-AzureADApplication -DisplayName "test123" -IdentifierUris "https://test123"
New-AzureADApplicationKeyCredential -ObjectId $application.ObjectId -CustomKeyIdentifier "Test123" -StartDate $currentDate -EndDate $endDate -Type AsymmetricX509Cert -Usage Verify -Value $keyValue
# Create the Service Principal and connect it to the Application
$sp=New-AzureADServicePrincipal -AppId $application.AppId
# Give the Service Principal Reader access to the current tenant (Get-AzureADDirectoryRole)
Add-AzureADDirectoryRoleMember -ObjectId 5997d714-c3b5-4d5b-9973-ec2f38fd49d5 -RefObjectId $sp.ObjectId
# Get Tenant Detail
$tenant=Get-AzureADTenantDetail
# Now you can login to Azure PowerShell with your Service Principal and Certificate
Connect-AzureAD -TenantId $tenant.ObjectId -ApplicationId $sp.AppId -CertificateThumbprint $thumb
Short Explanation of the code :
The above script creates Service Principal, grants a Read Access at the tenant level and connects to the Azure AD at the end using the created Service Principal.

Azure VM SQL Server connection string

*** Apologies folks - I appended wrong code ---now replaced below here
I have a simple Visual Studio .NET web forms app. I run it on my Azure VM called dexram (Windows 10) and I also created a SQL Server on the Azure VM. There is a user on the VM called 5001211 that has admin authority in Windows. It can use SSMS to access the database no problems.
All my connection string attempts fail in the C# code. This is strange as the VS web app and the SQL Server are both running on the Azure VM.
Here are the strings I tried and the messages I got underneath:
string Server = "Data Source = dexram; Initial Catalog = FruitNVeg; User ID=5001211;Password=Fitsh3ly;";
This connection string throws an error:
Login failed for user '5001211'
string Server = "Data Source = tcp:dexram,1433; Database = FruitNVeg; User ID = 5001211#dexram; Password = Fitsh3ly; Trusted_Connection = False; Encrypt = True;";
The certificate chain was issued by an authority that is not trusted
string Server = "Data Source = tcp:dexram,1433; Authentication = Active Directory Integrated; Database = FruitNVeg;";
The certificate chain was issued by an authority that is not trusted
string Server = "Data Source = tcp:dexram,1433; Authentication = Active Directory Password; Database = FruitNVeg; UID=5001211#dexram;PWD=Fitsh3ly;";
The certificate chain was issued by an authority that is not trusted
Thanks Dan - no luck - I created as per your suggestion and made 5001211 sysadmin and got following results:
string Server = "Data Source = dexram; Initial Catalog = FruitNVeg; User ID=5001211;Password=Fitsh3ly;";
Gives -- > Login failed for user '5001211'
string Server = "Data Source = tcp:dexram,1433; Database = FruitNVeg; User ID = 5001211#dexram; Password = Fitsh3ly; Trusted_Connection = True; Encrypt = True;";
Gives -- > The certificate chain was issued by an authority that is not trusted
I am thinking I need to get a cert. created as I think (?) my SQL calls from my VS app are going out over the internet (even though the 2 tools (VS and SQL Svr) are on the same VM machine) ?
you must first create a user in sql server after use from string format below
Data Source=instanse name or use .;Initial Catalog=database bame;User ID=created user in sql server;Password=your password
and do setting below for user
User dexram\5001211 is a Windows account. Your app connection string specifies a SQL login named 5001211. You need to create a SQL login named 5001211 and an associated database user:
USE FruitNVeg;
CREATE LOGIN [5001211] WITH PASSWORD = 'Fitsh3ly';
CREATE USER [5001211];
The user will also need permissions on the objects the application uses in the FruitNVeg database. Although you could add the login to a privileged role like sysadmin to avoid granting these permissions, the best practice is to use a minimally privileged account for routine application database access that has only the required permissions:
USE FruitNVeg;
GRANT SELECT ON dbo.Apples TO [5001211];
As per this URL --> https://blog.greglow.com/2020/01/16/sql-sql-server-the-certificate-chain-was-issued-by-an-authority-that-is-not-trusted/
I used the sql config manager and set "Trust Server Cert" to yes and that fixed the problem it seems

How to add an AAD group with datawrite and dataread permissions to an Azure DB through PowerShell

I have an Azure SQL Server with an SQL database. I would like to add an AAD Group with datawrite and dataread permissions to this database through PowerShell.
I have no idea how to do this. Can anyone help?
Adding a user to a role is usually accomplished with SQL Statements. This is how this would be done with SQL.
CREATE USER [group name]
FROM external provider
DEFAULT_SCHEMA dbo
Once the user has been added, you can then add them to a group by issuing the following statements;
ALTER ROLE db_datareader ADD MEMBER [group_name]
ALTER ROLE db_datawriter ADD MEMBER [group_name]
Note, within SSMS, you must be in the context of the database you want to add the user to. Azure SQL does not support USE statements, so ensure you selected the correct database.
To do it through powershell, you would probably want to use the following CmdLet, Add-RoleMember, but I have not used these CmdLets with Azure SQL Server before.
This is one way to add users to a SQL database via Powershell;
$Instance = $ENV:AzureSQLServer + ".database.windows.net"
$Query = "CREATE USER [$ENV:AdUser] FROM EXTERNAL PROVIDER"
$ConnString = "Server=" + $Instance + ";Database=master;Authentication=Active Directory Password;UID=" + $Env:SqlAdminUser + ";Password=" + $Env:SqlAdminPass + ";Trusted_Connection=false;Encrypt=True;Connection Timeout=30;"
Invoke-SQlCmd -ConnectionString $ConnString -Query $Query
We use this script in a PowerShell task in Jenkins to add users to databases. The Statements could be modified to also add the users to the appropriate roles as well.
I assume that you want to set AAD Group as AAD admin for your SQL database.
With this scenario, you can use Set-AzureRmSqlServerActiveDirectoryAdministrator:
Set-AzureRmSqlServerActiveDirectoryAdministrator -ResourceGroupName "ResourceGroup01" -ServerName "Server01" -DisplayName "DBAs" -ObjectId "40b79501-b343-44ed-9ce7-da4c8cc7353b"
Result:
ResourceGroupName ServerName DisplayName ObjectId
----------------- ---------- ----------- --------
ResourceGroup01 Server01 DBAs 40b79501-b343-44ed-9ce7-da4c8cc7353b
NOTE:
You can only set the AAD Group with securtiy enabled.
Please let me know if this helps!
You first have to create an ADD group using e. g. the New-AzureADGroup cmdlet that is part of the AzureAD module.
Then you have to assign the desired role to your desired resource (e. g. the SQL DB Contributor role to your DB Server) using the New-AzureRmRoleAssignment cmdlet.
Further reading: Manage role-based access control with Azure PowerShell

Resources