1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
+ // Licensed under the MIT License. See License.txt in the project root for license information.
3
+
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using System . Globalization ;
7
+ using System . IO ;
8
+ using System . Linq ;
9
+ using System . Threading . Tasks ;
10
+ using Microsoft . Azure . WebJobs . Host ;
11
+ //using Microsoft.Azure.WebJobs.Script.Config;
12
+ using Microsoft . Azure . WebJobs . Script . IO ;
13
+ using Microsoft . WindowsAzure . Storage ;
14
+ using Microsoft . WindowsAzure . Storage . Blob ;
15
+
16
+ namespace Microsoft . Azure . WebJobs . Script . WebHost
17
+ {
18
+ /// <summary>
19
+ /// An <see cref="ISecretsRepository"/> implementation that uses Azure blob storage as the backing store.
20
+ /// </summary>
21
+ public sealed class BlobStorageSecretsRepository : ISecretsRepository , IDisposable
22
+ {
23
+ private readonly string _secretsSentinelFilePath ;
24
+ private readonly string _secretsBlobPath ;
25
+ private readonly string _hostSecretsSentinelFilePath ;
26
+ private readonly string _hostSecretsBlobPath ;
27
+ private readonly AutoRecoveringFileSystemWatcher _sentinelFileWatcher ;
28
+ private readonly CloudBlobContainer _blobContainer ;
29
+ private readonly string _secretsContainerName = "azure-webjobs-secrets" ;
30
+ private readonly string _accountConnectionString ;
31
+ private bool _disposed = false ;
32
+
33
+ public BlobStorageSecretsRepository ( string secretSentinelDirectoryPath , string accountConnectionString , string siteHostName )
34
+ {
35
+ if ( secretSentinelDirectoryPath == null )
36
+ {
37
+ throw new ArgumentNullException ( nameof ( secretSentinelDirectoryPath ) ) ;
38
+ }
39
+ if ( accountConnectionString == null )
40
+ {
41
+ throw new ArgumentNullException ( nameof ( accountConnectionString ) ) ;
42
+ }
43
+
44
+ _secretsSentinelFilePath = secretSentinelDirectoryPath ;
45
+ _hostSecretsSentinelFilePath = Path . Combine ( _secretsSentinelFilePath , ScriptConstants . HostMetadataFileName ) ;
46
+
47
+ Directory . CreateDirectory ( _secretsSentinelFilePath ) ;
48
+
49
+ _sentinelFileWatcher = new AutoRecoveringFileSystemWatcher ( _secretsSentinelFilePath , "*.json" ) ;
50
+ _sentinelFileWatcher . Changed += OnChanged ;
51
+
52
+ _secretsBlobPath = siteHostName . ToLowerInvariant ( ) ;
53
+ _hostSecretsBlobPath = string . Format ( "{0}/{1}" , _secretsBlobPath , ScriptConstants . HostMetadataFileName ) ;
54
+
55
+ _accountConnectionString = accountConnectionString ;
56
+ CloudStorageAccount account = CloudStorageAccount . Parse ( _accountConnectionString ) ;
57
+ CloudBlobClient client = account . CreateCloudBlobClient ( ) ;
58
+
59
+ _blobContainer = client . GetContainerReference ( _secretsContainerName ) ;
60
+ _blobContainer . CreateIfNotExists ( ) ;
61
+ }
62
+
63
+ public event EventHandler < SecretsChangedEventArgs > SecretsChanged ;
64
+
65
+ private string GetSecretsBlobPath ( ScriptSecretsType secretsType , string functionName = null )
66
+ {
67
+ return secretsType == ScriptSecretsType . Host
68
+ ? _hostSecretsBlobPath
69
+ : string . Format ( "{0}/{1}" , _secretsBlobPath , GetSecretFileName ( functionName ) ) ;
70
+ }
71
+
72
+ private string GetSecretsSentinelFilePath ( ScriptSecretsType secretsType , string functionName = null )
73
+ {
74
+ return secretsType == ScriptSecretsType . Host
75
+ ? _hostSecretsSentinelFilePath
76
+ : Path . Combine ( _secretsSentinelFilePath , GetSecretFileName ( functionName ) ) ;
77
+ }
78
+
79
+ private static string GetSecretFileName ( string functionName )
80
+ {
81
+ return string . Format ( CultureInfo . InvariantCulture , "{0}.json" , functionName . ToLowerInvariant ( ) ) ;
82
+ }
83
+
84
+ private void OnChanged ( object sender , FileSystemEventArgs e )
85
+ {
86
+ var changeHandler = SecretsChanged ;
87
+ if ( changeHandler != null )
88
+ {
89
+ var args = new SecretsChangedEventArgs { SecretsType = ScriptSecretsType . Host } ;
90
+
91
+ if ( string . Compare ( Path . GetFileName ( e . FullPath ) , ScriptConstants . HostMetadataFileName , StringComparison . OrdinalIgnoreCase ) != 0 )
92
+ {
93
+ args . SecretsType = ScriptSecretsType . Function ;
94
+ args . Name = Path . GetFileNameWithoutExtension ( e . FullPath ) . ToLowerInvariant ( ) ;
95
+ }
96
+
97
+ changeHandler ( this , args ) ;
98
+ }
99
+ }
100
+
101
+ public async Task < string > ReadAsync ( ScriptSecretsType type , string functionName )
102
+ {
103
+ string secretsContent = null ;
104
+ string blobPath = GetSecretsBlobPath ( type , functionName ) ;
105
+ CloudBlockBlob secretBlob = _blobContainer . GetBlockBlobReference ( blobPath ) ;
106
+
107
+ if ( await secretBlob . ExistsAsync ( ) )
108
+ {
109
+ secretsContent = await secretBlob . DownloadTextAsync ( ) ;
110
+ }
111
+
112
+ return secretsContent ;
113
+ }
114
+
115
+ public async Task WriteAsync ( ScriptSecretsType type , string functionName , string secretsContent )
116
+ {
117
+ if ( secretsContent == null )
118
+ {
119
+ throw new ArgumentNullException ( nameof ( secretsContent ) ) ;
120
+ }
121
+
122
+ string blobPath = GetSecretsBlobPath ( type , functionName ) ;
123
+ CloudBlockBlob secretBlob = _blobContainer . GetBlockBlobReference ( blobPath ) ;
124
+ using ( StreamWriter writer = new StreamWriter ( await secretBlob . OpenWriteAsync ( ) ) )
125
+ {
126
+ await writer . WriteAsync ( secretsContent ) ;
127
+ }
128
+
129
+ string filePath = GetSecretsSentinelFilePath ( type , functionName ) ;
130
+ await FileUtility . WriteAsync ( filePath , DateTime . UtcNow . ToString ( ) ) ;
131
+ }
132
+
133
+ public async Task PurgeOldSecretsAsync ( IList < string > currentFunctions , TraceWriter traceWriter )
134
+ {
135
+ // no-op - allow stale secrets to remain
136
+ await Task . Yield ( ) ;
137
+ }
138
+
139
+ private void Dispose ( bool disposing )
140
+ {
141
+ if ( ! _disposed )
142
+ {
143
+ if ( disposing )
144
+ {
145
+ _sentinelFileWatcher . Dispose ( ) ;
146
+ }
147
+
148
+ _disposed = true ;
149
+ }
150
+ }
151
+
152
+ public void Dispose ( )
153
+ {
154
+ Dispose ( true ) ;
155
+ }
156
+ }
157
+ }
0 commit comments