Skip to content

Commit

Permalink
added support for environment secrets to github adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
markusheiliger committed May 22, 2022
1 parent 76a9e92 commit 2b546ca
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 16 deletions.
3 changes: 2 additions & 1 deletion src/TeamCloud.Adapters.AzureDevOps/AzureDevOpsAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public sealed class AzureDevOpsAdapter : AdapterWithIdentity, IAdapterAuthorize
// however; it is used to managed singleton function execution within the functions fx !!!

public AzureDevOpsAdapter(
IAdapterProvider adapterProvider,
IAuthorizationSessionClient sessionClient,
IAuthorizationTokenClient tokenClient,
IDistributedLockManager distributedLockManager,
Expand All @@ -94,7 +95,7 @@ public AzureDevOpsAdapter(
IGraphService graphService,
IFunctionsHost functionsHost = null,
ILoggerFactory loggerFactory = null)
: base(sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
: base(adapterProvider, sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
{
this.httpClientFactory = httpClientFactory ?? new DefaultHttpClientFactory();
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace TeamCloud.Adapters.AzureResourceManager;

public sealed class AzureResourceManagerAdapter : Adapter
public sealed class AzureResourceManagerAdapter : AdapterWithIdentity
{
private readonly IAzureService azure;
private readonly IAzureResourceService azureResourceService;
Expand All @@ -40,7 +40,9 @@ public sealed class AzureResourceManagerAdapter : Adapter
// IDistributedLockManager is marked as obsolete, because it's not ready for "prime time"
// however; it is used to managed singleton function execution within the functions fx !!!

public AzureResourceManagerAdapter(IAuthorizationSessionClient sessionClient,
public AzureResourceManagerAdapter(
IAdapterProvider adapterProvider,
IAuthorizationSessionClient sessionClient,
IAuthorizationTokenClient tokenClient,
IDistributedLockManager distributedLockManager,
IAzureService azure,
Expand All @@ -52,7 +54,7 @@ public AzureResourceManagerAdapter(IAuthorizationSessionClient sessionClient,
IProjectRepository projectRepository,
IComponentRepository componentRepository,
IComponentTemplateRepository componentTemplateRepository)
: base(sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
: base(adapterProvider, sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
{
this.azure = azure ?? throw new ArgumentNullException(nameof(azure));
this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService));
Expand Down Expand Up @@ -197,6 +199,16 @@ async Task UpdateComponentRoleAssignmentsAsync()
.Add(identity.PrincipalId, Enumerable.Repeat(AzureRoleDefinition.Contributor, 1));
}

if (this is IAdapterIdentity adapterIdentity)
{
var componentDeploymentScopeServiceIdentity = await adapterIdentity
.GetServiceIdentityAsync(componentDeploymentScope)
.ConfigureAwait(false);

roleAssignmentMap
.Add(componentDeploymentScopeServiceIdentity.Id.ToString(), Enumerable.Repeat(AzureRoleDefinition.Contributor, 1));
}

if (string.IsNullOrEmpty(componentResourceId.ResourceGroup))
{
var subscription = await azureResourceService
Expand Down
113 changes: 106 additions & 7 deletions src/TeamCloud.Adapters.GitHub/GitHubAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
using Flurl;
using Flurl.Http;
using Flurl.Http.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Hosting;
using Microsoft.Graph;
using Newtonsoft.Json.Linq;
using Octokit;
using Octokit.Internal;
Expand All @@ -36,18 +39,20 @@
using TeamCloud.Serialization;
using TeamCloud.Serialization.Forms;
using TeamCloud.Templates;
using Component = TeamCloud.Model.Data.Component;
using HttpStatusCode = System.Net.HttpStatusCode;
using IHttpClientFactory = Flurl.Http.Configuration.IHttpClientFactory;
using Organization = TeamCloud.Model.Data.Organization;
using Project = TeamCloud.Model.Data.Project;
using Team = Octokit.Team;
using User = TeamCloud.Model.Data.User;

namespace TeamCloud.Adapters.GitHub;

public sealed partial class GitHubAdapter : AdapterWithIdentity, IAdapterAuthorize
{
private static readonly IJsonSerializer GitHubSerializer = new SimpleJsonSerializer();

private readonly IAdapterProvider adapterProvider;
private readonly IHttpClientFactory httpClientFactory;
private readonly IOrganizationRepository organizationRepository;
private readonly IUserRepository userRepository;
Expand All @@ -65,6 +70,7 @@ public sealed partial class GitHubAdapter : AdapterWithIdentity, IAdapterAuthori
// however; it is used to managed singleton function execution within the functions fx !!!

public GitHubAdapter(
IAdapterProvider adapterProvider,
IAuthorizationSessionClient sessionClient,
IAuthorizationTokenClient tokenClient,
IDistributedLockManager distributedLockManager,
Expand All @@ -78,8 +84,9 @@ public GitHubAdapter(
IComponentTemplateRepository componentTemplateRepository,
IGraphService graphService,
IFunctionsHost functionsHost = null)
: base(sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
: base(adapterProvider, sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
{
this.adapterProvider = adapterProvider ?? throw new ArgumentNullException(nameof(adapterProvider));
this.httpClientFactory = httpClientFactory ?? new DefaultHttpClientFactory();
this.organizationRepository = organizationRepository ?? throw new ArgumentNullException(nameof(organizationRepository));
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
Expand Down Expand Up @@ -932,7 +939,8 @@ protected override Task<Component> UpdateComponentAsync(Component component, Org
await Task.WhenAll(
SyncTeamCloudIdentityAsync(),
SynchronizeTeamCloudIdentityAsync(),
SynchronizeAzureResourceManagerEnvironmentsAsync(),
SynchronizeTeamMembersAsync(client, memberTeam, users.Where(user => user.IsMember()), component, contextUser, commandQueue),
SynchronizeTeamMembersAsync(client, adminTeam, users.Where(user => user.IsAdmin()), component, contextUser, commandQueue)
Expand All @@ -941,7 +949,7 @@ await Task.WhenAll(
return component;
async Task SyncTeamCloudIdentityAsync()
async Task SynchronizeTeamCloudIdentityAsync()
{
if (!string.IsNullOrEmpty(componentProject.ResourceId) && GitHubIdentifier.TryParse(component.ResourceId, out var componentResourceId))
{
Expand All @@ -955,8 +963,7 @@ async Task SyncTeamCloudIdentityAsync()
if (repository is not null)
{
var projectResourceId = new ResourceIdentifier(componentProject.ResourceId);
var servicePrincipalJson = GitHubExtensions.ToJson(servicePrincipal, Guid.Parse(projectResourceId.SubscriptionId));
var servicePrincipalJson = servicePrincipal.ToJson(Guid.Parse(projectResourceId.SubscriptionId));
var keyJson = await repository.Url
.AppendPathSegment("actions/secrets/public-key")
Expand All @@ -974,7 +981,7 @@ async Task SyncTeamCloudIdentityAsync()
{
owner = repository.Owner.Name,
repo = repository.Name,
secret_name = "TEAMCLOUD_CREDENTIAL",
secret_name = "TEAMCLOUD_CREDENTIALS",
encrypted_value = Convert.ToBase64String(encryptBuffer),
key_id = keyJson.SelectToken("key_id")?.ToString()
};
Expand All @@ -988,6 +995,98 @@ await repository.Url
}
}
}
async Task SynchronizeAzureResourceManagerEnvironmentsAsync()
{
if (!string.IsNullOrEmpty(componentProject.ResourceId) && GitHubIdentifier.TryParse(component.ResourceId, out var componentResourceId))
{
var repository = await client.Repository
.Get(componentResourceId.Organization, componentResourceId.Repository)
.ConfigureAwait(false);
if (repository is not null)
{
var armAdapter = adapterProvider.GetAdapter(DeploymentScopeType.AzureResourceManager);
if (armAdapter is IAdapterIdentity armAdapterIdentity)
{
var armDeploymentScopes = deploymentScopeRepository
.ListAsync(componentOrganization.Id)
.Where(ds => ds.Type == DeploymentScopeType.AzureResourceManager);
await foreach (var armDeploymentScope in armDeploymentScopes)
{
try
{
var environmentResponse = await repository.Url
.AppendPathSegment($"environments/{armDeploymentScope.Slug}")
.WithGitHubHeaders()
.WithGitHubCredentials(client.Connection.Credentials)
.AllowHttpStatus(HttpStatusCode.NotFound)
.GetAsync()
.ConfigureAwait(false);
if (environmentResponse.StatusCode == (int) HttpStatusCode.NotFound)
{
var environmentPayload = new
{
wait_timer = 0,
reviewers = default(object),
deployment_branch_policy = default(object),
};
await repository.Url
.AppendPathSegment($"environments/{armDeploymentScope.Slug}")
.WithGitHubHeaders()
.WithGitHubCredentials(client.Connection.Credentials)
.PutJsonAsync(environmentPayload)
.ConfigureAwait(false);
}
var servicePrincipal = await armAdapterIdentity
.GetServiceIdentityAsync(armDeploymentScope)
.ConfigureAwait(false);
var projectResourceId = new ResourceIdentifier(componentProject.ResourceId);
var servicePrincipalJson = servicePrincipal.ToJson(Guid.Parse(projectResourceId.SubscriptionId));
var keyJson = await repository.Url
.AppendPathSegment($"environments/{armDeploymentScope.Slug}/secrets/public-key")
.WithGitHubHeaders()
.WithGitHubCredentials(client.Connection.Credentials)
.GetJObjectAsync()
.ConfigureAwait(false);
var key = Convert.FromBase64String(keyJson.SelectToken("key")?.ToString() ?? string.Empty);
var secretBuffer = Encoding.UTF8.GetBytes(servicePrincipalJson);
var encryptBuffer = Sodium.SealedPublicKeyBox.Create(secretBuffer, key);
var azureCredentialsPayload = new
{
repository_id = repository.Id,
environment_name = armDeploymentScope.Slug,
secret_name = "AZURE_CREDENTIALS",
encrypted_value = Convert.ToBase64String(encryptBuffer),
key_id = keyJson.SelectToken("key_id")?.ToString()
};
await repository.Url
.AppendPathSegment($"environments/{armDeploymentScope.Slug}/secrets/{azureCredentialsPayload.secret_name}")
.WithGitHubHeaders()
.WithGitHubCredentials(client.Connection.Credentials)
.PutJsonAsync(azureCredentialsPayload)
.ConfigureAwait(false);
}
catch (Exception exc)
{
throw new Exception($"Failed to synchronize environment '{armDeploymentScope.DisplayName}'", exc);
}
}
}
}
}
}
});

protected override Task<Component> DeleteComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector<ICommand> commandQueue)
Expand Down
1 change: 1 addition & 0 deletions src/TeamCloud.Adapters.GitHub/GitHubAdapter_Register.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ <h1>GitHub Provider Setup</h1>
"default_permissions": {
"actions": "write",
"administration": "write",
"environments": "write",
"checks": "write",
"contents": "write",
"issues": "write",
Expand Down
5 changes: 3 additions & 2 deletions src/TeamCloud.Adapters.Kubernetes/KubernetesAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public sealed class KubernetesAdapter : Adapter
// IDistributedLockManager is marked as obsolete, because it's not ready for "prime time"
// however; it is used to managed singleton function execution within the functions fx !!!

public KubernetesAdapter(IAuthorizationSessionClient sessionClient,
public KubernetesAdapter(IAdapterProvider adapterProvider,
IAuthorizationSessionClient sessionClient,
IAuthorizationTokenClient tokenClient,
IDistributedLockManager distributedLockManager,
IAzureService azure,
Expand All @@ -48,7 +49,7 @@ public KubernetesAdapter(IAuthorizationSessionClient sessionClient,
IDeploymentScopeRepository deploymentScopeRepository,
IProjectRepository projectRepository,
IUserRepository userRepository)
: base(sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
: base(adapterProvider, sessionClient, tokenClient, distributedLockManager, azure, graphService, organizationRepository, deploymentScopeRepository, projectRepository, userRepository)
{
this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService));
}
Expand Down
5 changes: 4 additions & 1 deletion src/TeamCloud.Adapters/Adapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public abstract class Adapter : IAdapter
{
private static readonly JSchema dataSchemaEmpty = new() { Type = JSchemaType.Object };
private static readonly JObject formSchemaEmpty = new();
private readonly IAdapterProvider adapterProvider;

#pragma warning disable CS0618 // Type or member is obsolete

Expand All @@ -43,7 +44,8 @@ public abstract class Adapter : IAdapter
private readonly IProjectRepository projectRepository;
private readonly IUserRepository userRepository;

protected Adapter(IAuthorizationSessionClient sessionClient,
protected Adapter(IAdapterProvider adapterProvider,
IAuthorizationSessionClient sessionClient,
IAuthorizationTokenClient tokenClient,
IDistributedLockManager distributedLockManager,
IAzureService azure,
Expand All @@ -53,6 +55,7 @@ protected Adapter(IAuthorizationSessionClient sessionClient,
IProjectRepository projectRepository,
IUserRepository userRepository)
{
this.adapterProvider = adapterProvider ?? throw new ArgumentNullException(nameof(adapterProvider));
this.sessionClient = sessionClient ?? throw new ArgumentNullException(nameof(sessionClient));
this.tokenClient = tokenClient ?? throw new ArgumentNullException(nameof(tokenClient));
this.distributedLockManager = distributedLockManager ?? throw new ArgumentNullException(nameof(distributedLockManager));
Expand Down
2 changes: 2 additions & 0 deletions src/TeamCloud.Adapters/AdapterWithIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public abstract class AdapterWithIdentity : Adapter, IAdapterIdentity
#pragma warning disable CS0618 // Type or member is obsolete

protected AdapterWithIdentity(
IAdapterProvider adapterProvider,
IAuthorizationSessionClient sessionClient,
IAuthorizationTokenClient tokenClient,
IDistributedLockManager distributedLockManager,
Expand All @@ -35,6 +36,7 @@ protected AdapterWithIdentity(
IProjectRepository projectRepository,
IUserRepository userRepository)
: base(
adapterProvider,
sessionClient,
tokenClient,
distributedLockManager,
Expand Down
2 changes: 1 addition & 1 deletion src/TeamCloud.Git/TeamCloud.Git.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.20.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Octokit" Version="0.51.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
Expand Down
2 changes: 1 addition & 1 deletion src/TeamCloud.Orchestrator/TeamCloud.Orchestrator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="4.5.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="Microsoft.Azure.SignalR" Version="1.15.0" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.15.0" />
Expand Down

0 comments on commit 2b546ca

Please sign in to comment.