1717 /// Virtual Client component that installs certificates from Azure Key Vault
1818 /// into the appropriate certificate store for the operating system.
1919 /// </summary>
20+ [ SupportedPlatforms ( "linux-arm64,linux-x64,win-arm64,win-x64" , true ) ]
2021 public class CertificateInstallation : VirtualClientComponent
2122 {
2223 private ISystemManagement systemManagement ;
@@ -84,11 +85,11 @@ public bool WithPrivateKey
8485 /// <summary>
8586 /// Gets the directory where the certificate will be exported. If not provided, the certificate will not be exported to a file.
8687 /// </summary>
87- public string CertificateDownloadDir
88+ public string CertificateInstallationDir
8889 {
8990 get
9091 {
91- return this . Parameters . GetValue < string > ( nameof ( this . CertificateDownloadDir ) , string . Empty ) ;
92+ return this . Parameters . GetValue < string > ( nameof ( this . CertificateInstallationDir ) ) ;
9293 }
9394 }
9495
@@ -125,39 +126,11 @@ protected override async Task ExecuteAsync(EventContext telemetryContext, Cancel
125126 IKeyVaultManager keyVault = this . GetKeyVaultManager ( ) ;
126127 X509Certificate2 certificate = await keyVault . GetCertificateAsync ( this . CertificateName , cancellationToken , null , this . WithPrivateKey ) ;
127128
128- if ( this . Platform == PlatformID . Win32NT )
129- {
130- await this . InstallCertificateOnWindowsAsync ( certificate , cancellationToken ) ;
131- }
132- else if ( this . Platform == PlatformID . Unix )
133- {
134- await this . InstallCertificateOnUnixAsync ( certificate , cancellationToken ) ;
135- }
136- else
137- {
138- throw new PlatformNotSupportedException ( $ "The '{ nameof ( CertificateInstallation ) } ' component is not supported on platform '{ this . Platform } '.") ;
139- }
129+ await this . InstallCertificateOnMachineAsync ( certificate , cancellationToken ) ;
140130
141- // Export the certificate if requested
142- if ( ! string . IsNullOrEmpty ( this . CertificateDownloadDir ) )
131+ if ( ! string . IsNullOrWhiteSpace ( this . CertificateInstallationDir ) )
143132 {
144- string certificateFileName = this . WithPrivateKey
145- ? $ "{ this . CertificateName } .pfx"
146- : $ "{ this . CertificateName } .cer";
147-
148- X509ContentType contentType = this . WithPrivateKey
149- ? X509ContentType . Pfx
150- : X509ContentType . Cert ;
151-
152- string certificatePath = this . Combine ( this . CertificateDownloadDir , certificateFileName ) ;
153-
154- if ( this . fileSystem . File . Exists ( certificatePath ) )
155- {
156- this . fileSystem . File . Delete ( certificatePath ) ;
157- }
158-
159- byte [ ] certBytes = certificate . Export ( contentType , string . Empty ) ;
160- await this . fileSystem . File . WriteAllBytesAsync ( certificatePath , certBytes ) ;
133+ await this . InstallCertificateLocallyAsync ( certificate , cancellationToken ) ;
161134 }
162135 }
163136 catch ( Exception exc )
@@ -169,9 +142,9 @@ protected override async Task ExecuteAsync(EventContext telemetryContext, Cancel
169142 }
170143
171144 /// <summary>
172- /// Installs the certificate in the appropriate certificate store on a Windows system .
145+ /// Installs the certificate in the appropriate certificate store.
173146 /// </summary>
174- protected virtual Task InstallCertificateOnWindowsAsync ( X509Certificate2 certificate , CancellationToken cancellationToken )
147+ protected virtual Task InstallCertificateOnMachineAsync ( X509Certificate2 certificate , CancellationToken cancellationToken )
175148 {
176149 return Task . Run ( ( ) =>
177150 {
@@ -185,72 +158,6 @@ protected virtual Task InstallCertificateOnWindowsAsync(X509Certificate2 certifi
185158 } ) ;
186159 }
187160
188- /// <summary>
189- /// Installs the certificate in the appropriate certificate store on a Unix/Linux system.
190- /// </summary>
191- protected virtual async Task InstallCertificateOnUnixAsync ( X509Certificate2 certificate , CancellationToken cancellationToken )
192- {
193- // On Unix/Linux systems, we install the certificate in the default location for the
194- // user as well as in a static location. In the future we will likely use the static location
195- // only.
196- string certificateDirectory = null ;
197-
198- try
199- {
200- // When "sudo" is used to run the installer, we need to know the logged
201- // in user account. On Linux systems, there is an environment variable 'SUDO_USER'
202- // that defines the logged in user.
203-
204- string user = this . GetEnvironmentVariable ( EnvironmentVariable . USER ) ;
205- string sudoUser = this . GetEnvironmentVariable ( EnvironmentVariable . SUDO_USER ) ;
206- certificateDirectory = $ "/home/{ user } /.dotnet/corefx/cryptography/x509stores/my";
207-
208- if ( ! string . IsNullOrWhiteSpace ( sudoUser ) )
209- {
210- // The installer is being executed with "sudo" privileges. We want to use the
211- // logged in user profile vs. "root".
212- certificateDirectory = $ "/home/{ sudoUser } /.dotnet/corefx/cryptography/x509stores/my";
213- }
214- else if ( user == "root" )
215- {
216- // The installer is being executed from the "root" account on Linux.
217- certificateDirectory = $ "/root/.dotnet/corefx/cryptography/x509stores/my";
218- }
219-
220- Console . WriteLine ( $ "Certificate Store = { certificateDirectory } ") ;
221-
222- if ( ! this . fileSystem . Directory . Exists ( certificateDirectory ) )
223- {
224- this . fileSystem . Directory . CreateDirectory ( certificateDirectory ) ;
225- }
226-
227- using ( X509Store store = new X509Store ( StoreName . My , StoreLocation . CurrentUser , OpenFlags . ReadWrite ) )
228- {
229- store . Open ( OpenFlags . ReadWrite ) ;
230- store . Add ( certificate ) ;
231- store . Close ( ) ;
232- }
233-
234- await this . fileSystem . File . WriteAllBytesAsync (
235- this . Combine ( certificateDirectory , $ "{ certificate . Thumbprint } .pfx") ,
236- certificate . Export ( X509ContentType . Pfx ) ) ;
237-
238- // Permissions 777 (-rwxrwxrwx)
239- // https://linuxhandbook.com/linux-file-permissions/
240- using ( IProcessProxy process = this . processManager . CreateProcess ( "chmod" , $ "-R 777 { certificateDirectory } ") )
241- {
242- await process . StartAndWaitAsync ( cancellationToken ) ;
243- process . ThrowIfErrored < DependencyException > ( ) ;
244- }
245- }
246- catch ( UnauthorizedAccessException )
247- {
248- throw new UnauthorizedAccessException (
249- $ "Access permissions denied for certificate directory '{ certificateDirectory } '. Execute the installer with " +
250- $ "sudo/root privileges to install SDK certificates in privileged locations.") ;
251- }
252- }
253-
254161 /// <summary>
255162 /// Gets the Key Vault manager to use to retrieve certificates from Key Vault.
256163 /// </summary>
@@ -278,5 +185,50 @@ protected IKeyVaultManager GetKeyVaultManager()
278185 $ "Either valid --KeyVault or --Token or --TokenPath must be passed in order to set up authentication with Key Vault.") ;
279186 }
280187 }
188+
189+ /// <summary>
190+ /// Installs the certificate in static location
191+ /// </summary>
192+ protected async Task InstallCertificateLocallyAsync ( X509Certificate2 certificate , CancellationToken cancellationToken )
193+ {
194+ try
195+ {
196+ string certificateFileName = this . WithPrivateKey
197+ ? $ "{ this . CertificateName } .pfx"
198+ : $ "{ this . CertificateName } .cer";
199+
200+ X509ContentType contentType = this . WithPrivateKey
201+ ? X509ContentType . Pfx
202+ : X509ContentType . Cert ;
203+
204+ byte [ ] certBytes = certificate . Export ( contentType , string . Empty ) ;
205+
206+ string certificatePath = this . Combine ( this . CertificateInstallationDir , certificateFileName ) ;
207+
208+ if ( ! this . fileSystem . Directory . Exists ( this . CertificateInstallationDir ) )
209+ {
210+ this . fileSystem . Directory . CreateDirectory ( this . CertificateInstallationDir ) ;
211+ }
212+
213+ await this . fileSystem . File . WriteAllBytesAsync ( certificatePath , certBytes ) ;
214+
215+ if ( this . Platform == PlatformID . Unix )
216+ {
217+ // Permissions 777 (-rwxrwxrwx)
218+ // https://linuxhandbook.com/linux-file-permissions/
219+ using ( IProcessProxy process = this . processManager . CreateProcess ( "chmod" , $ "-R 777 { this . CertificateInstallationDir } ") )
220+ {
221+ await process . StartAndWaitAsync ( cancellationToken ) ;
222+ process . ThrowIfErrored < DependencyException > ( ) ;
223+ }
224+ }
225+ }
226+ catch ( UnauthorizedAccessException )
227+ {
228+ throw new UnauthorizedAccessException (
229+ $ "Access permissions denied for certificate directory '{ this . CertificateInstallationDir } '. Execute the installer with " +
230+ $ "admin/sudo/root privileges to install certificates in privileged locations.") ;
231+ }
232+ }
281233 }
282234}
0 commit comments