Skip to content

Commit 1eef33c

Browse files
committed
Add azure-deploy workflow
1 parent d485be4 commit 1eef33c

File tree

3 files changed

+549
-0
lines changed

3 files changed

+549
-0
lines changed

.github/workflows/azure-deploy.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Deploy to Azure
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- 'Demo/**'
10+
- 'Src/**'
11+
- '.github/workflows/azure-deploy.yml'
12+
13+
env:
14+
# Setting these variables allows .NET CLI to use rich color codes in console output
15+
TERM: xterm
16+
DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true
17+
# Skip boilerplate output
18+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
19+
DOTNET_NOLOGO: true
20+
DOTNET_CLI_TELEMETRY_OPTOUT: true
21+
22+
jobs:
23+
build-and-deploy:
24+
runs-on: ubuntu-latest
25+
permissions:
26+
contents: read
27+
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
31+
32+
- name: Install .NET
33+
uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # v4.3.0
34+
with:
35+
dotnet-version: "8.0.x"
36+
37+
- name: Run restore
38+
run: dotnet restore Demo/Demo.csproj
39+
40+
- name: Run build
41+
run: >
42+
dotnet build Demo/Demo.csproj
43+
--no-restore
44+
--configuration Release
45+
46+
- name: Run tests
47+
run: >
48+
dotnet test Tests/Fido2.Tests/Fido2.Tests.csproj
49+
--configuration Release
50+
--logger "trx;LogFileName=test-results.trx"
51+
52+
- name: Publish application
53+
run: >
54+
dotnet publish Demo/Demo.csproj
55+
--no-restore
56+
--no-build
57+
--configuration Release
58+
--output ./publish
59+
60+
- name: Deploy to Azure Web App
61+
uses: azure/webapps-deploy@v3
62+
with:
63+
package: ./publish
64+
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}

current.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Current MDS (Metadata Service) Implementation
2+
3+
## Architecture Overview
4+
5+
The current MDS implementation follows a clean separation of concerns:
6+
7+
- **`IMetadataRepository`** - Handles all complexity (JWT validation, cert chains, HTTP calls, FIDO spec compliance)
8+
- **`IMetadataService`** - Simple caching wrapper around repositories
9+
- **Registration API** - Builder pattern for configuration
10+
11+
## Core Interfaces
12+
13+
### IMetadataService
14+
15+
```csharp
16+
public interface IMetadataService
17+
{
18+
/// <summary>
19+
/// Gets the metadata payload entry by a guid asynchronously.
20+
/// </summary>
21+
/// <param name="aaguid">The Authenticator Attestation GUID.</param>
22+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
23+
/// <returns>Returns the entry; Otherwise <c>null</c>.</returns>
24+
Task<MetadataBLOBPayloadEntry?> GetEntryAsync(Guid aaguid, CancellationToken cancellationToken = default);
25+
26+
/// <summary>
27+
/// Gets a value indicating whether the internal access token is valid.
28+
/// </summary>
29+
/// <returns>
30+
/// Returns <c>true</c> if access token is valid, or <c>false</c> if the access token is equal to an invalid token value.
31+
/// </returns>
32+
bool ConformanceTesting();
33+
}
34+
```
35+
36+
### IMetadataRepository
37+
38+
```csharp
39+
public interface IMetadataRepository
40+
{
41+
Task<MetadataBLOBPayload> GetBLOBAsync(CancellationToken cancellationToken = default);
42+
43+
Task<MetadataStatement?> GetMetadataStatementAsync(MetadataBLOBPayload blob, MetadataBLOBPayloadEntry entry, CancellationToken cancellationToken = default);
44+
}
45+
```
46+
47+
## Current Implementations
48+
49+
### Repository Implementations
50+
51+
#### 1. Fido2MetadataServiceRepository
52+
- **Purpose**: Official FIDO Alliance MDS3 service
53+
- **Features**:
54+
- JWT validation with certificate chain verification
55+
- Downloads from `https://mds3.fidoalliance.org/`
56+
- Validates against GlobalSign root certificate
57+
- Handles CRL checking for revoked certificates
58+
- **Usage**: Production metadata from FIDO Alliance
59+
60+
#### 2. ConformanceMetadataRepository
61+
- **Purpose**: FIDO conformance testing
62+
- **Features**:
63+
- Downloads from conformance test endpoints
64+
- Uses fake root certificate for testing
65+
- Supports multiple conformance endpoints
66+
- **Usage**: Conformance testing scenarios
67+
68+
#### 3. FileSystemMetadataRepository
69+
- **Purpose**: Local filesystem cache/storage
70+
- **Features**:
71+
- Loads metadata statements from local directory
72+
- Creates fake entries with `NOT_FIDO_CERTIFIED` status
73+
- No JWT validation (assumes pre-validated files)
74+
- **Usage**: Local development, caching, testing
75+
76+
### Service Implementation
77+
78+
#### DistributedCacheMetadataService
79+
- **Purpose**: Caching wrapper for repositories
80+
- **Features**:
81+
- **L1 Cache**: IMemoryCache (1 hour default)
82+
- **L2 Cache**: IDistributedCache (8 days default)
83+
- **L3 Source**: IMetadataRepository collection
84+
- Graceful error handling with logging
85+
- Respects `NextUpdate` field from BLOB payload
86+
- Automatic cache invalidation based on timestamps
87+
88+
**Cache Hierarchy:**
89+
1. Check memory cache
90+
2. Check distributed cache, refresh if expired
91+
3. Fetch from repositories
92+
4. Store in both cache layers
93+
94+
## Current Registration API
95+
96+
### Builder Interfaces
97+
98+
```csharp
99+
public interface IFido2NetLibBuilder
100+
{
101+
IServiceCollection Services { get; }
102+
}
103+
104+
public interface IFido2MetadataServiceBuilder
105+
{
106+
IServiceCollection Services { get; }
107+
}
108+
109+
public class Fido2NetLibBuilder : IFido2NetLibBuilder, IFido2MetadataServiceBuilder
110+
{
111+
public IServiceCollection Services { get; }
112+
}
113+
```
114+
115+
### Registration Methods
116+
117+
```csharp
118+
public static class Fido2NetLibBuilderExtensions
119+
{
120+
// Core FIDO2 registration
121+
public static IFido2NetLibBuilder AddFido2(this IServiceCollection services, IConfiguration configuration);
122+
public static IFido2NetLibBuilder AddFido2(this IServiceCollection services, Action<Fido2Configuration> setupAction);
123+
124+
// Metadata service registration (hardcoded to DistributedCacheMetadataService)
125+
public static void AddCachedMetadataService(this IFido2NetLibBuilder builder, Action<IFido2MetadataServiceBuilder> configAction)
126+
{
127+
builder.Services.AddScoped<IMetadataService, DistributedCacheMetadataService>();
128+
configAction(new Fido2NetLibBuilder(builder.Services));
129+
}
130+
131+
// Repository registration methods (called within configAction above)
132+
public static IFido2MetadataServiceBuilder AddFileSystemMetadataRepository(this IFido2MetadataServiceBuilder builder, string directoryPath);
133+
public static IFido2MetadataServiceBuilder AddConformanceMetadataRepository(this IFido2MetadataServiceBuilder builder, HttpClient client = null, string origin = "");
134+
public static IFido2MetadataServiceBuilder AddFidoMetadataRepository(this IFido2MetadataServiceBuilder builder, Action<IHttpClientBuilder> clientBuilder = null);
135+
}
136+
```
137+
138+
## Current Usage Patterns
139+
140+
### Default Usage (Demo Project)
141+
142+
```csharp
143+
builder.Services.AddFido2(options =>
144+
{
145+
options.ServerDomain = builder.Configuration["fido2:serverDomain"];
146+
options.ServerName = "FIDO2 Test";
147+
options.Origins = builder.Configuration.GetSection("fido2:origins").Get<HashSet<string>>();
148+
options.TimestampDriftTolerance = builder.Configuration.GetValue<int>("fido2:timestampDriftTolerance");
149+
options.MDSCacheDirPath = builder.Configuration["fido2:MDSCacheDirPath"];
150+
options.BackupEligibleCredentialPolicy = builder.Configuration.GetValue<Fido2Configuration.CredentialBackupPolicy>("fido2:backupEligibleCredentialPolicy");
151+
options.BackedUpCredentialPolicy = builder.Configuration.GetValue<Fido2Configuration.CredentialBackupPolicy>("fido2:backedUpCredentialPolicy");
152+
})
153+
.AddCachedMetadataService(config =>
154+
{
155+
config.AddFidoMetadataRepository(httpClientBuilder =>
156+
{
157+
//TODO: any specific config you want for accessing the MDS
158+
});
159+
});
160+
```
161+
162+
### Multiple Repositories
163+
164+
```csharp
165+
.AddCachedMetadataService(config =>
166+
{
167+
config.AddFidoMetadataRepository();
168+
config.AddFileSystemMetadataRepository("/local/cache/path");
169+
config.AddConformanceMetadataRepository(httpClient, "https://example.com");
170+
});
171+
```
172+
173+
### Service Dependencies
174+
175+
The `DistributedCacheMetadataService` expects these services to be registered:
176+
- `IEnumerable<IMetadataRepository>` - All registered repositories
177+
- `IDistributedCache` - For L2 caching
178+
- `IMemoryCache` - For L1 caching
179+
- `ILogger<DistributedCacheMetadataService>` - For logging
180+
- `ISystemClock` - For cache expiry calculations
181+
182+
## Current Limitations
183+
184+
### 1. Forced Service Implementation
185+
- `AddCachedMetadataService()` hardcodes `DistributedCacheMetadataService`
186+
- No way to register custom `IMetadataService` implementations
187+
- Cannot disable caching entirely
188+
189+
### 2. Repository Registration Pattern
190+
- Repositories are registered inside service configuration
191+
- Conceptually backwards - services depend on repos, not vice versa
192+
- Cannot register repositories without registering a service
193+
194+
### 3. Limited Flexibility
195+
- Cannot easily implement custom caching strategies
196+
- No way to compose different caching approaches
197+
- Testing scenarios require workarounds
198+
199+
## Default Service Registration
200+
201+
When no metadata service is configured, the system falls back to:
202+
203+
```csharp
204+
// In Fido2NetLibBuilderExtensions.AddServices()
205+
services.AddSingleton<IMetadataService, NullMetadataService>(); //Default implementation if we choose not to enable MDS
206+
```
207+
208+
The `NullMetadataService` provides no-op implementations:
209+
- `GetEntryAsync()` always returns `null`
210+
- `ConformanceTesting()` always returns `false`
211+
212+
## Configuration Options
213+
214+
### Cache Timing (DistributedCacheMetadataService)
215+
216+
```csharp
217+
protected readonly TimeSpan _defaultMemoryCacheInterval = TimeSpan.FromHours(1);
218+
protected readonly TimeSpan _nextUpdateBufferPeriod = TimeSpan.FromHours(25);
219+
protected readonly TimeSpan _defaultDistributedCacheInterval = TimeSpan.FromDays(8);
220+
```
221+
222+
### File Cache Location
223+
224+
Controlled by `Fido2Configuration.MDSCacheDirPath` configuration value.
225+
226+
### HTTP Client Configuration
227+
228+
`AddFidoMetadataRepository()` allows custom HTTP client configuration:
229+
230+
```csharp
231+
config.AddFidoMetadataRepository(httpClientBuilder =>
232+
{
233+
httpClientBuilder.ConfigureHttpClient(client =>
234+
{
235+
client.Timeout = TimeSpan.FromSeconds(30);
236+
});
237+
});
238+
```
239+
240+
## Key Strengths of Current Design
241+
242+
1. **Clean Separation**: Repositories handle complexity, services handle caching
243+
2. **Extensible Repository Pattern**: Easy to add new metadata sources
244+
3. **Layered Caching**: Memory + distributed cache with smart expiry
245+
4. **Error Resilience**: Graceful fallback between repositories
246+
5. **FIDO Compliant**: Full JWT validation and certificate verification
247+
6. **Production Ready**: Handles real-world scenarios (CRL checks, timeouts, etc.)
248+
249+
## Areas for Improvement
250+
251+
1. **Registration API**: Better expose the existing flexibility
252+
2. **Service Alternatives**: Easy way to use custom caching strategies
253+
3. **Repository Independence**: Decouple repository registration from service registration
254+
4. **Testing Support**: Easier ways to disable caching or use mock implementations

0 commit comments

Comments
 (0)