-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Service client for Yandex Identity and Access Management (#18)
- Loading branch information
Showing
16 changed files
with
414 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using YaCloudKit.IAM; | ||
|
||
var configurationSections = new Dictionary<string, string> | ||
{ | ||
{ "YandexIam:ServiceAccountId", "your-service-account-id" }, | ||
{ "YandexIam:PublicKeyId", "your-public-key-id" } | ||
}; | ||
var configuration = new ConfigurationBuilder() | ||
.AddInMemoryCollection(configurationSections) | ||
.Build(); | ||
|
||
var services = new ServiceCollection(); | ||
services | ||
.AddYandexFilePrivateKeyProvider("your-private-key-file-path") | ||
.AddDefaultYandexIamServiceClient(configuration); | ||
|
||
var serviceProvider = services.BuildServiceProvider(); | ||
|
||
var yandexIamServiceClient = serviceProvider.GetRequiredService<IYandexIamServiceClient>(); | ||
|
||
var iamTokenResponse = await yandexIamServiceClient.GetIamForServiceAccountAsync(); | ||
|
||
Console.WriteLine("IAM token: " + iamTokenResponse.IamToken.Substring(0, 20) + "..."); | ||
Console.WriteLine("Expires at: " + iamTokenResponse.ExpiresAt); |
15 changes: 15 additions & 0 deletions
15
samples/YaCloudKit.IAM.Examples/YaCloudKit.IAM.Examples.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\IdentityAccessManagement\YaCloudKit.IAM\YaCloudKit.IAM.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
10 changes: 10 additions & 0 deletions
10
src/IdentityAccessManagement/YaCloudKit.IAM/IYandexIamServiceClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using YaCloudKit.IAM.Model; | ||
|
||
namespace YaCloudKit.IAM; | ||
|
||
public interface IYandexIamServiceClient | ||
{ | ||
Task<IamTokenResponse> GetIamForYandexAccountAsync(string yandexPassportOauthToken, CancellationToken cancellationToken = default); | ||
|
||
Task<IamTokenResponse> GetIamForServiceAccountAsync(CancellationToken cancellationToken = default); | ||
} |
36 changes: 36 additions & 0 deletions
36
src/IdentityAccessManagement/YaCloudKit.IAM/Jwt/YandexJsonWebTokenGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Security.Claims; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.IdentityModel.Tokens; | ||
using YaCloudKit.IAM.Rsa; | ||
|
||
namespace YaCloudKit.IAM.Jwt; | ||
|
||
public class YandexJsonWebTokenGenerator(IOptionsMonitor<YandexIamOptions> options, IYandexRsaFactory rsaFactory) | ||
{ | ||
public async Task<string> GenerateJwtAsync(CancellationToken cancellationToken = default) | ||
{ | ||
var optionsValue = options.CurrentValue; | ||
ArgumentNullException.ThrowIfNull(optionsValue.ServiceAccountId); | ||
ArgumentNullException.ThrowIfNull(optionsValue.PublicKeyId); | ||
|
||
using var rsa = await rsaFactory.CreateRsaAsync(cancellationToken); | ||
var header = new JwtHeader(new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSsaPssSha256)) | ||
{ | ||
{ "kid", optionsValue.PublicKeyId } | ||
}; | ||
|
||
var now = DateTimeOffset.UtcNow; | ||
var claims = new List<Claim> | ||
{ | ||
new(JwtRegisteredClaimNames.Aud, "https://iam.api.cloud.yandex.net/iam/v1/tokens"), | ||
new(JwtRegisteredClaimNames.Iss, optionsValue.ServiceAccountId), | ||
new("iat", now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer), | ||
new("exp", now.AddSeconds(3600).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer) | ||
}; | ||
var payload = new JwtPayload(claims); | ||
var jwtSecurityToken = new JwtSecurityToken(header, payload); | ||
var handler = new JwtSecurityTokenHandler(); | ||
return handler.WriteToken(jwtSecurityToken); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/IdentityAccessManagement/YaCloudKit.IAM/Model/IamTokenResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System.Text.Json.Serialization; | ||
|
||
namespace YaCloudKit.IAM.Model; | ||
|
||
public record IamTokenResponse | ||
{ | ||
[JsonPropertyName("iamToken")] | ||
public required string IamToken { get; init; } | ||
|
||
[JsonPropertyName("expiresAt")] | ||
public required DateTime ExpiresAt { get; init; } | ||
} |
6 changes: 6 additions & 0 deletions
6
src/IdentityAccessManagement/YaCloudKit.IAM/Rsa/IYandexPrivateKeyProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace YaCloudKit.IAM.Rsa; | ||
|
||
public interface IYandexPrivateKeyProvider | ||
{ | ||
Task<char[]> GetPrivateKeyAsync(CancellationToken cancellationToken = default); | ||
} |
44 changes: 44 additions & 0 deletions
44
src/IdentityAccessManagement/YaCloudKit.IAM/Rsa/YandexCachedPrivateKeyProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
namespace YaCloudKit.IAM.Rsa; | ||
|
||
public abstract class YandexCachedPrivateKeyProvider(bool cache = false) : IYandexPrivateKeyProvider, IDisposable | ||
{ | ||
private readonly SemaphoreSlim _semaphore = new(1, 1); | ||
private char[]? _privateKey; | ||
|
||
protected abstract Task<char[]> GetPrivateKeyCoreAsync(CancellationToken cancellationToken); | ||
|
||
|
||
public async Task<char[]> GetPrivateKeyAsync(CancellationToken cancellation) | ||
{ | ||
if (_privateKey is not null) | ||
{ | ||
return _privateKey; | ||
} | ||
|
||
await _semaphore.WaitAsync(cancellation); | ||
try | ||
{ | ||
if (_privateKey is not null) | ||
{ | ||
return _privateKey; | ||
} | ||
|
||
var localPrivateKey = await GetPrivateKeyCoreAsync(cancellation); | ||
if (cache) | ||
{ | ||
_privateKey = localPrivateKey; | ||
} | ||
|
||
return _privateKey ?? localPrivateKey; | ||
} | ||
finally | ||
{ | ||
_semaphore.Release(); | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_semaphore.Dispose(); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/IdentityAccessManagement/YaCloudKit.IAM/Rsa/YandexFilePrivateKeyProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace YaCloudKit.IAM.Rsa; | ||
|
||
public class YandexFilePrivateKeyProvider(string privateKeyFilePath, bool cacheResult = false) | ||
: YandexCachedPrivateKeyProvider(cacheResult) | ||
{ | ||
protected override async Task<char[]> GetPrivateKeyCoreAsync(CancellationToken cancellationToken) | ||
{ | ||
using var reader = new StreamReader(privateKeyFilePath); | ||
return (await reader.ReadToEndAsync(cancellationToken)).ToCharArray(); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/IdentityAccessManagement/YaCloudKit.IAM/Rsa/YandexFuncPrivateKeyProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace YaCloudKit.IAM.Rsa; | ||
|
||
public class YandexFuncPrivateKeyProvider( | ||
Func<CancellationToken, Task<char[]>> privateKeyFunc, | ||
bool cacheResult = false) | ||
: YandexCachedPrivateKeyProvider(cacheResult) | ||
{ | ||
protected override Task<char[]> GetPrivateKeyCoreAsync(CancellationToken cancellationToken) | ||
{ | ||
return privateKeyFunc(cancellationToken); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/IdentityAccessManagement/YaCloudKit.IAM/Rsa/YandexRsaFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using System.Security.Cryptography; | ||
|
||
namespace YaCloudKit.IAM.Rsa; | ||
|
||
public interface IYandexRsaFactory | ||
{ | ||
Task<RSA> CreateRsaAsync(CancellationToken cancellationToken = default); | ||
} | ||
|
||
public class YandexRsaFactory(IYandexPrivateKeyProvider privateKeyProvider) : IYandexRsaFactory | ||
{ | ||
public async Task<RSA> CreateRsaAsync(CancellationToken cancellationToken = default) | ||
{ | ||
var rsa = RSA.Create(); | ||
var privateKey = await privateKeyProvider.GetPrivateKeyAsync(cancellationToken); | ||
rsa.ImportFromPem(privateKey); | ||
return rsa; | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/IdentityAccessManagement/YaCloudKit.IAM/Rsa/YandexStaticPrivateKeyProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
namespace YaCloudKit.IAM.Rsa; | ||
|
||
public class YandexStaticPrivateKeyProvider : IYandexPrivateKeyProvider | ||
{ | ||
private readonly char[] _privateKey; | ||
|
||
public YandexStaticPrivateKeyProvider(string privateKey) | ||
{ | ||
ArgumentNullException.ThrowIfNull(privateKey); | ||
_privateKey = privateKey.ToCharArray(); | ||
} | ||
|
||
public Task<char[]> GetPrivateKeyAsync(CancellationToken _) => Task.FromResult(_privateKey); | ||
} |
89 changes: 89 additions & 0 deletions
89
src/IdentityAccessManagement/YaCloudKit.IAM/ServiceCollectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using YaCloudKit.IAM.Jwt; | ||
using YaCloudKit.IAM.Rsa; | ||
|
||
namespace YaCloudKit.IAM; | ||
|
||
public static class ServiceCollectionExtensions | ||
{ | ||
public static IServiceCollection AddYandexFilePrivateKeyProvider( | ||
this IServiceCollection services, | ||
string privateKeyFilePath, | ||
bool cacheResult = false) | ||
{ | ||
services.TryAddSingleton<IYandexPrivateKeyProvider>( | ||
new YandexFilePrivateKeyProvider(privateKeyFilePath, cacheResult)); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddYandexStaticPrivateKeyProvider( | ||
this IServiceCollection services, | ||
string privateKey) | ||
{ | ||
services.TryAddSingleton<IYandexPrivateKeyProvider>( | ||
new YandexStaticPrivateKeyProvider(privateKey)); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddYandexFuncPrivateKeyProvider( | ||
this IServiceCollection services, | ||
Func<IServiceProvider, CancellationToken, Task<char[]>> privateKeyFunc, | ||
bool cacheResult = false) | ||
{ | ||
Func<IServiceProvider, IYandexPrivateKeyProvider> factory = sp => | ||
{ | ||
return new YandexFuncPrivateKeyProvider( | ||
ct => privateKeyFunc(sp, ct), cacheResult); | ||
}; | ||
services.TryAddSingleton(factory); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddYandexFuncPrivateKeyProvider( | ||
this IServiceCollection services, | ||
Func<CancellationToken, Task<char[]>> privateKeyFunc, | ||
bool cacheResult = false) | ||
{ | ||
services.TryAddSingleton<IYandexPrivateKeyProvider>( | ||
new YandexFuncPrivateKeyProvider(privateKeyFunc, cacheResult)); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddYandexJwtGenerationServices( | ||
this IServiceCollection services, | ||
IConfiguration configuration, | ||
string optionsSectionName = YandexIamOptions.SectionName) | ||
{ | ||
services.AddOptions<YandexIamOptions>() | ||
.Bind(configuration.GetSection(optionsSectionName)); | ||
services.TryAddSingleton<IYandexRsaFactory, YandexRsaFactory>(); | ||
services.TryAddSingleton<YandexJsonWebTokenGenerator>(); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddYandexIamServiceClient( | ||
this IServiceCollection services, | ||
TimeSpan? requestTimeout = null) | ||
{ | ||
services | ||
.AddHttpClient<IYandexIamServiceClient, YandexIamServiceClient>( | ||
(sp, httpClient) => | ||
{ | ||
httpClient.BaseAddress = new Uri(YandexIamOptions.ApiHost); | ||
httpClient.Timeout = requestTimeout ?? TimeSpan.FromSeconds(10); | ||
}); | ||
return services; | ||
} | ||
|
||
public static IServiceCollection AddDefaultYandexIamServiceClient( | ||
this IServiceCollection services, | ||
IConfiguration configuration, | ||
TimeSpan? requestTimeout = null) | ||
{ | ||
services.AddYandexJwtGenerationServices(configuration); | ||
services.AddYandexIamServiceClient(requestTimeout); | ||
return services; | ||
} | ||
} |
Oops, something went wrong.