Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf4dfdf7

Browse files
Adds a proxy for pwabuilder CLI per new security requirements
1 parent04ae6b8 commitf4dfdf7

File tree

6 files changed

+268
-19
lines changed

6 files changed

+268
-19
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
usingMicrosoft.AspNetCore.Mvc;
2+
usingMicrosoft.Extensions.Options;
3+
usingPWABuilder.Controllers;
4+
usingPWABuilder.Models;
5+
usingPWABuilder.Services;
6+
7+
namespacePWABuilder.Controllers;
8+
9+
[ApiController]
10+
[Route("api/[controller]/[action]")]
11+
publicclassMsStorePackagesController:ControllerBase
12+
{
13+
privatereadonlyIBlobStorageServiceblobStorageService;
14+
privatereadonlystringcliKey;
15+
privatereadonlyILogger<MsStorePackagesController>logger;
16+
publicMsStorePackagesController(
17+
IBlobStorageServiceblobStorageService,
18+
IOptions<AppSettings>settings,
19+
ILogger<MsStorePackagesController>logger
20+
)
21+
{
22+
this.blobStorageService=blobStorageService;
23+
this.cliKey=settings.Value.PWABuilderCliKey;
24+
this.logger=logger;
25+
}
26+
27+
/// <summary>
28+
/// This is a proxy function that downloads the pwa_builder.exe command line utility for packaging PWAs for the Microsoft Store. This utility is not publicly available per request of its authors, the Microsoft Edge team.
29+
/// The proxy is needed because the CLI is stored on a private Azure Blob Storage container that requires authentication and private virtual network to access.
30+
///
31+
/// This endpoint is used by the GitHub Action YML file that deploys PWABuilder MSStore packaging platform: https://github.com/pwa-builder/PWABuilder/blob/main/.github/workflows/deploy-msstore-to-preview.yml
32+
/// Since the GitHub Action cannot access the private storage container directly, it calls this endpoint to download the CLI using a private key. The private key is stored in two places:
33+
/// - GitHub repo's secrets - in order for the GitHub Action to access it
34+
/// - In the Azure web app `pwabuilder` -> Environment Variables, under AppSettings__PWABuilderCliKey - in order for this code to validate the key.
35+
/// </summary>
36+
/// <param name="options"></param>
37+
/// <returns></returns>
38+
39+
[HttpGet("cli")]
40+
publicasyncTask<IActionResult>GetCli(stringkey)
41+
{
42+
// Validate the key.
43+
if(key!=cliKey)
44+
{
45+
logger.LogWarning("Unauthorized attempt to download pwabuilder.exe CLI with invalid key.");
46+
returnUnauthorized();
47+
}
48+
49+
varstream=awaitblobStorageService.DownloadBlobAsync("resources","pwabuilder.zip");
50+
Response.RegisterForDispose(stream);
51+
returnFile(stream,"application/zip","pwabuilder.zip");
52+
}
53+
54+
[HttpGet("list-files-temp")]
55+
publicasyncTask<IActionResult>ListFiles(stringkey)
56+
{
57+
// Validate the key.
58+
if(key!=cliKey)
59+
{
60+
logger.LogWarning("Unauthorized attempt to list files in resources container with invalid key.");
61+
returnUnauthorized();
62+
}
63+
64+
varblobs=awaitblobStorageService.ListBlobsAsync("resources");
65+
returnOk(blobs);
66+
}
67+
}

‎apps/pwabuilder/Models/AppSettings.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ public class AppSettings
77
publicstringImageGeneratorApiUrl{get;set;}=string.Empty;
88
publicstringAnalyticsUrl{get;set;}=string.Empty;
99
publicstringApplicationInsightsConnectionString{get;set;}=string.Empty;
10-
publicstringAzureStorageAccountName{get;set;}=string.Empty;
1110
publicstringAzureManagedIdentityApplicationId{get;set;}=string.Empty;
11+
publicstringAzureStorageAccountName{get;set;}=string.Empty;
1212
publicstringAnalysisDbRedisConnectionString{get;set;}=string.Empty;
13+
publicstringPWABuilderCliKey{get;set;}=string.Empty;
1314
}

‎apps/pwabuilder/PWABuilder.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88
<ItemGroup>
99
<PackageReferenceInclude="Azure.Identity"Version="1.15.0" />
10+
<PackageReferenceInclude="Azure.Storage.Blobs"Version="12.25.0" />
1011
<PackageReferenceInclude="Azure.Storage.Queues"Version="12.23.0" />
1112
<PackageReferenceInclude="HtmlAgilityPack"Version="1.12.2" />
1213
<PackageReferenceInclude="Microsoft.ApplicationInsights"Version="2.23.0" />

‎apps/pwabuilder/Program.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646

4747
// In development, we use an in-memory database for Analysis objects. This makes local development and testing simpler, as we don't need to connect to Redis.
4848
builder.Services.AddSingleton<IPWABuilderDatabase,InMemoryPWABuilderDatabase>();
49+
50+
// In development, we use an in-memory blob storage service.
51+
builder.Services.AddSingleton<IBlobStorageService,InMemoryBlobStorageService>();
4952
}
5053
else
5154
{
@@ -54,6 +57,9 @@
5457

5558
// In production, we use PWABuilderDatabase, which uses Redis as a backing store.
5659
builder.Services.AddSingleton<IPWABuilderDatabase,PWABuilderDatabase>();
60+
61+
// In production, we use Azure Storage for blob storage.
62+
builder.Services.AddSingleton<IBlobStorageService,AzureStorageService>();
5763
}
5864
builder.Services.AddSingleton<WebStringCache>();
5965
builder.Services.AddHostedService<AnalysisJobProcessor>();
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
usingAzure.Identity;
2+
usingAzure.Storage.Blobs;
3+
usingMicrosoft.Extensions.Options;
4+
usingPWABuilder.Models;
5+
6+
namespacePWABuilder.Services;
7+
8+
/// <summary>
9+
/// Interface for accessing Azure Storage. When in local development, this will be implemented as an in-memory store. Otherwise, it will access the Azure Storage account for PWABuilder using Managed Identity.
10+
/// </summary>
11+
publicinterfaceIBlobStorageService
12+
{
13+
/// <summary>
14+
/// Downloads a blob from Azure Storage.
15+
/// </summary>
16+
/// <param name="containerName">The container name.</param>
17+
/// <param name="blobName">The blob name.</param>
18+
/// <param name="cancellationToken"></param>
19+
/// <returns></returns>
20+
Task<Stream>DownloadBlobAsync(stringcontainerName,stringblobName,CancellationTokencancellationToken=default);
21+
22+
/// <summary>
23+
/// Downloads a blob from Azure Storage as a byte array.
24+
/// </summary>
25+
/// <param name="containerName"></param>
26+
/// <param name="blobName"></param>
27+
/// <param name="cancellationToken"></param>
28+
/// <returns></returns>
29+
Task<byte[]>DownloadBlobAsBytesAsync(stringcontainerName,stringblobName,CancellationTokencancellationToken=default);
30+
31+
/// <summary>
32+
/// Lists all blobs in a container.
33+
/// </summary>
34+
/// <param name="containerName">The name of the container to list files from</param>
35+
/// <param name="prefix">Optional prefix to filter blobs by name</param>
36+
/// <param name="cancellationToken">Cancellation token</param>
37+
/// <returns>Collection of blob names</returns>
38+
Task<IEnumerable<string>>ListBlobsAsync(stringcontainerName,string?prefix=null,CancellationTokencancellationToken=default);
39+
}
40+
41+
/// <summary>
42+
/// Accesses PWABuilder's Azure Storage account. Access is controlled via Managed Identity.
43+
/// </summary>
44+
publicclassAzureStorageService:IBlobStorageService
45+
{
46+
privatereadonlyBlobServiceClientblobServiceClient;
47+
privatereadonlyILogger<AzureStorageService>logger;
48+
49+
publicAzureStorageService(IOptions<AppSettings>settings,ILogger<AzureStorageService>logger)
50+
{
51+
this.logger=logger;
52+
53+
// Azure managed identity app ID:
54+
varmanagedIdentityAppId=settings.Value.AzureManagedIdentityApplicationId;
55+
56+
// Create managed identity credential
57+
varcredential=newManagedIdentityCredential(clientId:managedIdentityAppId);
58+
59+
// Connect to Azure Storage using managed identity
60+
varstorageUri=newUri($"https://{settings.Value.AzureStorageAccountName}.blob.core.windows.net");
61+
this.blobServiceClient=newBlobServiceClient(storageUri,credential);
62+
}
63+
64+
/// <summary>
65+
/// Downloads a blob from Azure Storage.
66+
/// </summary>
67+
/// <param name="containerName">The name of the container containing the blob</param>
68+
/// <param name="blobName">The name of the blob to download</param>
69+
/// <param name="cancellationToken">Cancellation token</param>
70+
/// <returns>Stream containing the blob data</returns>
71+
publicasyncTask<Stream>DownloadBlobAsync(stringcontainerName,stringblobName,CancellationTokencancellationToken=default)
72+
{
73+
try
74+
{
75+
varblobContainerClient=blobServiceClient.GetBlobContainerClient(containerName);
76+
varblobClient=blobContainerClient.GetBlobClient(blobName);
77+
78+
varresponse=awaitblobClient.DownloadStreamingAsync(cancellationToken:cancellationToken);
79+
returnresponse.Value.Content;
80+
}
81+
catch(Exceptionex)
82+
{
83+
logger.LogError(ex,"Error downloading blob {BlobName} from container {ContainerName}",blobName,containerName);
84+
throw;
85+
}
86+
}
87+
88+
/// <summary>
89+
/// Downloads a blob from Azure Storage as a byte array.
90+
/// </summary>
91+
/// <param name="containerName">The name of the container containing the blob</param>
92+
/// <param name="blobName">The name of the blob to download</param>
93+
/// <param name="cancellationToken">Cancellation token</param>
94+
/// <returns>Byte array containing the blob data</returns>
95+
publicasyncTask<byte[]>DownloadBlobAsBytesAsync(stringcontainerName,stringblobName,CancellationTokencancellationToken=default)
96+
{
97+
try
98+
{
99+
usingvarstream=awaitDownloadBlobAsync(containerName,blobName,cancellationToken);
100+
usingvarmemoryStream=newMemoryStream();
101+
awaitstream.CopyToAsync(memoryStream,cancellationToken);
102+
returnmemoryStream.ToArray();
103+
}
104+
catch(Exceptionex)
105+
{
106+
logger.LogError(ex,"Error downloading blob {BlobName} from container {ContainerName} as bytes",blobName,containerName);
107+
throw;
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Lists all blobs in a container.
113+
/// </summary>
114+
/// <param name="containerName">The name of the container to list files from</param>
115+
/// <param name="prefix">Optional prefix to filter blobs by name</param>
116+
/// <param name="cancellationToken">Cancellation token</param>
117+
/// <returns>Collection of blob names</returns>
118+
publicasyncTask<IEnumerable<string>>ListBlobsAsync(stringcontainerName,string?prefix=null,CancellationTokencancellationToken=default)
119+
{
120+
try
121+
{
122+
varblobContainerClient=blobServiceClient.GetBlobContainerClient(containerName);
123+
varblobNames=newList<string>();
124+
125+
awaitforeach(varblobIteminblobContainerClient.GetBlobsAsync(prefix:prefix,cancellationToken:cancellationToken))
126+
{
127+
blobNames.Add(blobItem.Name);
128+
}
129+
130+
returnblobNames;
131+
}
132+
catch(Exceptionex)
133+
{
134+
logger.LogError(ex,"Error listing blobs in container {ContainerName} with prefix {Prefix}",containerName,prefix);
135+
throw;
136+
}
137+
}
138+
}
139+
140+
/// <summary>
141+
/// In-memory implementation of IBlobStorageService for local development and testing.
142+
/// </summary>
143+
publicclassInMemoryBlobStorageService:IBlobStorageService
144+
{
145+
publicTask<byte[]>DownloadBlobAsBytesAsync(stringcontainerName,stringblobName,CancellationTokencancellationToken=default)
146+
{
147+
thrownewNotImplementedException("In-memory blob storage does not support downloading blobs since we don't yet have an Upload method.");
148+
}
149+
150+
/// <summary>
151+
/// Downloads a blob from the in-memory store.
152+
/// </summary>
153+
/// <param name="containerName"></param>
154+
/// <param name="blobName"></param>
155+
/// <param name="cancellationToken"></param>
156+
/// <returns></returns>
157+
publicTask<Stream>DownloadBlobAsync(stringcontainerName,stringblobName,CancellationTokencancellationToken=default)
158+
{
159+
thrownewNotImplementedException("In-memory blob storage does not support downloading blobs since we don't yet have an Upload method.");
160+
}
161+
162+
/// <summary>
163+
/// Lists blobs in the in-memory store.
164+
/// </summary>
165+
/// <param name="containerName">The name of the container to list files from</param>
166+
/// <param name="prefix">Optional prefix to filter blobs by name</param>
167+
/// <param name="cancellationToken">Cancellation token</param>
168+
/// <returns>Collection of blob names</returns>
169+
publicTask<IEnumerable<string>>ListBlobsAsync(stringcontainerName,string?prefix=null,CancellationTokencancellationToken=default)
170+
{
171+
thrownewNotImplementedException("In-memory blob storage does not support listing blobs since we don't yet have an Upload method.");
172+
}
173+
}

‎apps/pwabuilder/appsettings.json‎

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
{
2-
"Logging": {
3-
"LogLevel": {
4-
"Default":"Information",
5-
"Microsoft.AspNetCore":"Warning"
6-
}
7-
},
8-
"AppSettings": {
9-
"AnalyticsUrl":"",
10-
"ApplicationInsightsConnectionString":"",
11-
"IOSSourceCodePath":"",
12-
"NextStepsPath":"",
13-
"ImageGeneratorApiUrl":"",
14-
"AzureStorageAccountName":"pwabuildercommon",
15-
"AzureRedisDbConnectionString":"",
16-
"AzureManagedIdentityApplicationId":""
17-
},
18-
"AllowedHosts":"*"
19-
}
2+
"Logging": {
3+
"LogLevel": {
4+
"Default":"Information",
5+
"Microsoft.AspNetCore":"Warning"
6+
}
7+
},
8+
"AppSettings": {
9+
"AnalyticsUrl":"",
10+
"ApplicationInsightsConnectionString":"",
11+
"IOSSourceCodePath":"",
12+
"NextStepsPath":"",
13+
"ImageGeneratorApiUrl":"",
14+
"AzureRedisDbConnectionString":"",
15+
"AzureManagedIdentityApplicationId":"",
16+
"AzureStorageAccountName":"",
17+
"PWABuilderCliKey":""
18+
},
19+
"AllowedHosts":"*"
20+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp