Skip to content

Commit 73cef3b

Browse files
TylerLeonhardtdaxian-dbw
authored andcommitted
Remove Az modules from the worker and add MSI support (#108)
1 parent 5fd9170 commit 73cef3b

File tree

4 files changed

+76
-12
lines changed

4 files changed

+76
-12
lines changed

src/PowerShell/PowerShellManager.cs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,69 @@ internal PowerShellManager(ILogger logger)
5050

5151
internal void AuthenticateToAzure()
5252
{
53-
// Try to authenticate to Azure
54-
// TODO: The Azure Functions Host might supply these differently. This might change but works for the demo
53+
// Check if Az.Profile is available
54+
Collection<PSModuleInfo> azprofile = _pwsh.AddCommand("Get-Module")
55+
.AddParameter("ListAvailable")
56+
.AddParameter("Name", "Az.Profile")
57+
.InvokeAndClearCommands<PSModuleInfo>();
58+
59+
if (azprofile.Count == 0)
60+
{
61+
_logger.Log(LogLevel.Trace, "Required module to automatically authenticate with Azure `Az.Profile` was not found in the PSModulePath.");
62+
return;
63+
}
64+
65+
// Try to authenticate to Azure using MSI
66+
string msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET");
67+
string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
68+
string accountId = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME");
69+
70+
if (!string.IsNullOrEmpty(msiSecret) &&
71+
!string.IsNullOrEmpty(msiEndpoint) &&
72+
!string.IsNullOrEmpty(accountId))
73+
{
74+
// NOTE: There is a limitation in Azure PowerShell that prevents us from using the parameter set:
75+
// Connect-AzAccount -MSI or Connect-AzAccount -Identity
76+
// see this GitHub issue https://github.com/Azure/azure-powershell/issues/7876
77+
// As a workaround, we can all an API endpoint on the MSI_ENDPOINT to get an AccessToken and use that to authenticate
78+
Collection<PSObject> response = _pwsh.AddCommand("Microsoft.PowerShell.Utility\\Invoke-RestMethod")
79+
.AddParameter("Method", "Get")
80+
.AddParameter("Headers", new Hashtable {{ "Secret", msiSecret }})
81+
.AddParameter("Uri", $"{msiEndpoint}?resource=https://management.azure.com&api-version=2017-09-01")
82+
.InvokeAndClearCommands<PSObject>();
83+
84+
if(_pwsh.HadErrors)
85+
{
86+
_logger.Log(LogLevel.Warning, "Failed to Authenticate to Azure via MSI. Check the logs for the errors generated.");
87+
}
88+
else
89+
{
90+
// We have successfully authenticated to Azure so we can return out.
91+
using (ExecutionTimer.Start(_logger, "Authentication to Azure"))
92+
{
93+
_pwsh.AddCommand("Az.Profile\\Connect-AzAccount")
94+
.AddParameter("AccessToken", response[0].Properties["access_token"].Value)
95+
.AddParameter("AccountId", accountId)
96+
.InvokeAndClearCommands();
97+
98+
if(_pwsh.HadErrors)
99+
{
100+
_logger.Log(LogLevel.Warning, "Failed to Authenticate to Azure. Check the logs for the errors generated.");
101+
}
102+
else
103+
{
104+
// We've successfully authenticated to Azure so we can return
105+
return;
106+
}
107+
}
108+
}
109+
}
110+
else
111+
{
112+
_logger.Log(LogLevel.Trace, "Skip authentication to Azure via MSI. Environment variables for authenticating to Azure are not present.");
113+
}
114+
115+
// Try to authenticate to Azure using Service Principal
55116
string applicationId = Environment.GetEnvironmentVariable("SERVICE_PRINCIPAL_APP_ID");
56117
string applicationSecret = Environment.GetEnvironmentVariable("SERVICE_PRINCIPAL_APP_PASSWORD");
57118
string tenantId = Environment.GetEnvironmentVariable("SERVICE_PRINCIPAL_TENANT_ID");
@@ -60,7 +121,7 @@ internal void AuthenticateToAzure()
60121
string.IsNullOrEmpty(applicationSecret) ||
61122
string.IsNullOrEmpty(tenantId))
62123
{
63-
_logger.Log(LogLevel.Warning, "Required environment variables to authenticate to Azure were not present");
124+
_logger.Log(LogLevel.Trace, "Skip authentication to Azure via Service Principal. Environment variables for authenticating to Azure are not present.");
64125
return;
65126
}
66127

@@ -71,13 +132,18 @@ internal void AuthenticateToAzure()
71132
secureString.AppendChar(item);
72133
}
73134

74-
using (ExecutionTimer.Start(_logger, "Authentication to Azure completed."))
135+
using (ExecutionTimer.Start(_logger, "Authentication to Azure"))
75136
{
76137
_pwsh.AddCommand("Az.Profile\\Connect-AzAccount")
77138
.AddParameter("Credential", new PSCredential(applicationId, secureString))
78139
.AddParameter("ServicePrincipal")
79140
.AddParameter("TenantId", tenantId)
80141
.InvokeAndClearCommands();
142+
143+
if(_pwsh.HadErrors)
144+
{
145+
_logger.Log(LogLevel.Warning, "Failed to Authenticate to Azure via Service Principal. Check the logs for the errors generated.");
146+
}
81147
}
82148
}
83149

@@ -89,8 +155,6 @@ internal void InitializeRunspace()
89155

90156
// Set the PSModulePath
91157
Environment.SetEnvironmentVariable("PSModulePath", Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules"));
92-
93-
AuthenticateToAzure();
94158
}
95159

96160
/// <summary>

src/RequestProcessor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ internal StreamingMessage ProcessFunctionLoadRequest(StreamingMessage request)
111111
string functionAppModulesPath = Path.GetFullPath(
112112
Path.Combine(functionLoadRequest.Metadata.Directory, "..", "Modules"));
113113
_powerShellManager.PrependToPSModulePath(functionAppModulesPath);
114+
115+
// Since this is the first time we know where the location of the FunctionApp is,
116+
// we can attempt to authenticate to Azure at this time.
117+
_powerShellManager.AuthenticateToAzure();
118+
114119
_prependedPath = true;
115120
}
116121
}

src/requirements.psd1

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,4 @@
44
Version = '1.1.0.0'
55
Target = 'src/Modules'
66
}
7-
'Az' = @{
8-
Version = '0.2.2'
9-
Target = 'src/Modules'
10-
}
117
}

test/Unit/PowerShell/PowerShellManagerTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ public void InitializeRunspaceSuccess()
4242
var manager = new PowerShellManager(logger);
4343
manager.InitializeRunspace();
4444

45-
Assert.Single(logger.FullLog);
46-
Assert.Equal("Warning: Required environment variables to authenticate to Azure were not present", logger.FullLog[0]);
45+
Assert.Empty(logger.FullLog);
4746
}
4847

4948
[Fact]

0 commit comments

Comments
 (0)