3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
- using System . Linq ;
7
6
using System . Security ;
8
- using System . Security . Cryptography . X509Certificates ;
9
7
using Azure . Identity ;
10
- using Azure . ResourceManager ;
11
- using Azure . ResourceManager . Storage ;
12
8
using Azure . Security . KeyVault . Secrets ;
13
9
using Microsoft . SqlServer . ADO . Identity ;
14
10
namespace Microsoft . SqlServer . Test . Manageability . Utils . Helpers
15
11
{
16
12
/// <summary>
17
13
/// Retrieves a decrypted secret from Azure Key Vault or environment using certificate auth or client secret or managed identity
18
14
/// </summary>
19
- public class AzureKeyVaultHelper
15
+ public class AzureKeyVaultHelper : ICredential
20
16
{
21
- /// <summary>
22
- /// The set of certificate thumbprints associated with the service principal.
23
- /// If this collection is non-empty, AzureApplicationId and AzureTenantId must also be set to valid values.
24
- /// Set these properties to use certificate-based authentication without relying on environment variables to specify the certificate.
25
- /// </summary>
26
- public IEnumerable < string > CertificateThumbprints { get ; set ; }
27
17
/// <summary>
28
18
/// The Azure application id associated with the service principal
29
19
/// </summary>
@@ -39,14 +29,17 @@ public class AzureKeyVaultHelper
39
29
/// <summary>
40
30
/// The name of the Azure key vault where test secrets are stored.
41
31
/// </summary>
42
- public string KeyVaultName { get ; private set ; }
43
-
44
- private static readonly IDictionary < string , SecureString > secretCache = new Dictionary < string , SecureString > ( ) ;
32
+ public string KeyVaultName { get ; set ; }
33
+
34
+ /// <summary>
35
+ /// The AzureStorageHelper instance used to access the storage account
36
+ /// </summary>
37
+ public AzureStorageHelper StorageHelper { get ; set ; }
38
+ private static readonly IDictionary < string , SecureString > secretCache = new Dictionary < string , SecureString > ( ) ;
45
39
private static readonly object syncObj = new object ( ) ;
46
40
public static readonly string SSMS_TEST_SECRET_PREFIX = "SQLA-SSMS-Test-" ;
47
41
48
42
private SecretClient secretClient = null ;
49
- private ArmClient armClient = null ;
50
43
51
44
/// <summary>
52
45
/// Constructs a new AzureKeyVaultHelper that relies on an instance of Azure.Identity.DefaultAzureCredential to access the given vault.
@@ -56,7 +49,6 @@ public AzureKeyVaultHelper(string keyVaultName)
56
49
{
57
50
58
51
KeyVaultName = keyVaultName ;
59
- CertificateThumbprints = Enumerable . Empty < string > ( ) ;
60
52
}
61
53
62
54
/// <summary>
@@ -101,7 +93,7 @@ public string GetDecryptedSecret(string secretName)
101
93
Console . WriteLine ( @"Got Exception fetching secret. Type:{0}, Inner:{1}, Outer:{2}" , e . GetType ( ) , e . InnerException , e ) ;
102
94
throw ;
103
95
}
104
- // Note we aren't bothering to cache secrets we found from GetEnvironmentVariable since that API is already fast
96
+ // Note we aren't bothering to cache secrets we found from GetEnvironmentVariable since that API is already fast
105
97
lock ( syncObj )
106
98
{
107
99
secretCache [ secretName ] = secret . StringToSecureString ( ) ;
@@ -110,71 +102,38 @@ public string GetDecryptedSecret(string secretName)
110
102
return secret ;
111
103
}
112
104
113
- private Azure . Core . TokenCredential GetCredential ( )
105
+ /// <summary>
106
+ /// Returns a TokenCredential that implements Managed Identity, DefaultAzureCredential, and AzurePipelinesCredential in that order.
107
+ /// </summary>
108
+ /// <returns></returns>
109
+ public Azure . Core . TokenCredential GetCredential ( )
114
110
{
111
+ TraceHelper . TraceInformation ( $ "Getting credential for Azure in tenant { AzureTenantId } ") ;
115
112
// prefer managed identity then local user on dev machine over the certificate
116
113
var credentials = new List < Azure . Core . TokenCredential > ( ) { new ManagedIdentityCredential ( AzureManagedIdentityClientId ) , new DefaultAzureCredential ( new DefaultAzureCredentialOptions { ExcludeManagedIdentityCredential = true , TenantId = AzureTenantId } ) } ;
117
- foreach ( var thumbprint in CertificateThumbprints ?? Enumerable . Empty < string > ( ) )
114
+ var options = new AzureDevOpsFederatedTokenCredentialOptions ( ) { TenantId = AzureTenantId , ClientId = AzureApplicationId } ;
115
+ if ( options . ServiceConnectionId != null )
118
116
{
119
- var certificate = FindCertificate ( thumbprint ) ;
120
- if ( certificate != null )
121
- {
122
- credentials . Add ( new ClientCertificateCredential ( AzureTenantId , AzureApplicationId , certificate ) ) ;
123
- }
117
+ TraceHelper . TraceInformation ( $ "Adding AzurePipelinesCredential for tenant id { options . TenantId } using service connection { options . ServiceConnectionId } ") ;
118
+ credentials . Insert ( 0 , new AzurePipelinesCredential ( options . TenantId , options . ClientId , options . ServiceConnectionId , options . SystemAccessToken ) ) ;
124
119
}
125
- credentials . Add ( new AzureDevOpsFederatedTokenCredential ( new AzureDevOpsFederatedTokenCredentialOptions ( ) { TenantId = AzureTenantId , ClientId = AzureApplicationId } ) ) ;
126
120
return new ChainedTokenCredential ( credentials . ToArray ( ) ) ;
127
121
}
128
122
123
+ /// <summary>
124
+ /// Returns the account access key for the given storage account resource id.
125
+ /// </summary>
126
+ /// <param name="storageAccountResourceId"></param>
127
+ /// <returns></returns>
129
128
public string GetStorageAccountAccessKey ( string storageAccountResourceId )
130
129
{
131
130
TraceHelper . TraceInformation ( $ "Fetching storage access key for { storageAccountResourceId } ") ;
132
- if ( armClient == null )
133
- {
134
- armClient = new ArmClient ( GetCredential ( ) ) ;
135
- }
136
- var storageAccount = armClient . GetStorageAccountResource ( new Azure . Core . ResourceIdentifier ( storageAccountResourceId ) ) ;
137
- return storageAccount . GetKeys ( ) . First ( ) . Value ;
131
+ return new AzureStorageHelper ( storageAccountResourceId , this ) . GetStorageAccountAccessKey ( storageAccountResourceId ) ;
138
132
}
133
+ }
139
134
140
- private static X509Certificate2 FindCertificate ( string thumbprint )
141
- {
142
- X509Certificate2 certificate = null ;
143
- if ( System . Runtime . InteropServices . RuntimeInformation . IsOSPlatform ( System . Runtime . InteropServices . OSPlatform . Windows ) )
144
- {
145
- var store = new X509Store ( StoreName . My , StoreLocation . LocalMachine ) ;
146
- try
147
- {
148
- store . Open ( OpenFlags . ReadOnly ) ;
149
- X509Certificate2Collection certificateCollection = store . Certificates . Find ( X509FindType . FindByThumbprint , thumbprint , validOnly : false ) ;
150
- if ( certificateCollection . Count == 0 )
151
- {
152
- TraceHelper . TraceInformation ( "Couldn't find Smo cert {0} in local machine. Looking in current user" , thumbprint ) ;
153
- var userStore = new X509Store ( StoreName . My , StoreLocation . CurrentUser ) ;
154
- userStore . Open ( OpenFlags . ReadOnly ) ;
155
- try
156
- {
157
- certificateCollection = userStore . Certificates . Find ( X509FindType . FindByThumbprint ,
158
- thumbprint , validOnly : false ) ;
159
- }
160
- finally
161
- {
162
- userStore . Close ( ) ;
163
- }
164
- }
165
- if ( certificateCollection . Count != 0 )
166
- {
167
- TraceHelper . TraceInformation ( "Found cert {0}" , thumbprint ) ;
168
- certificate = certificateCollection [ 0 ] ;
169
- }
170
- }
171
- finally
172
- {
173
- store . Close ( ) ;
174
- }
175
- }
176
- return certificate ;
177
- }
178
-
135
+ public interface ICredential
136
+ {
137
+ Azure . Core . TokenCredential GetCredential ( ) ;
179
138
}
180
139
}
0 commit comments