Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixup automatically created feed permissions #8037

Merged
merged 6 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,11 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<NETCORE_ENGINEERING_TELEMETRY>Publish</NETCORE_ENGINEERING_TELEMETRY>

<!-- Default publishing infra target is 2. -->
<PublishingInfraVersion Condition="'$(PublishingInfraVersion)' == ''">2</PublishingInfraVersion>
<!-- Default publishing infra target is 3. -->
<PublishingInfraVersion Condition="'$(PublishingInfraVersion)' == ''">3</PublishingInfraVersion>
</PropertyGroup>

<Import Project="SetupTargetFeeds.proj" Condition="'$(PublishingInfraVersion)' == '2'" />

<Target Name="Execute">
<CallTarget Targets="SetupTargetFeeds" Condition="'$(PublishingInfraVersion)' == '2'">
<Output TaskParameter="TargetOutputs" ItemName="TargetFeedConfig"/>
</CallTarget>

<Error Condition="'$(ManifestsBasePath)' == ''" Text="ManifestsBasePath is empty. Please provide the full path to asset manifest(s) directory." />
<Error Condition="'$(BlobBasePath)' == '' OR '$(PackageBasePath)' == ''" Text="A valid full path to BlobBasePath and PackageBasePath is required." />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@
- AzdoTargetFeedPAT : Required token to publish assets to feeds
-->

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<NETCORE_ENGINEERING_TELEMETRY>Publish</NETCORE_ENGINEERING_TELEMETRY>
michellemcdaniel marked this conversation as resolved.
Show resolved Hide resolved
<AzureDevOpsOrg Condition="'$(AzureDevOpsOrg)' == ''">dnceng</AzureDevOpsOrg>
</PropertyGroup>

<Target Name="Execute">
<Error
Condition="'$(IsInternalBuild)' == ''"
Expand All @@ -34,21 +28,33 @@
Condition="'$(AzdoTargetFeedPAT)' == ''"
Text="Parameters 'AzdoTargetFeedPAT' is empty." />

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<NETCORE_ENGINEERING_TELEMETRY>Publish</NETCORE_ENGINEERING_TELEMETRY>
<AzureDevOpsOrg Condition="'$(AzureDevOpsOrg)' == ''">dnceng</AzureDevOpsOrg>
<AzureDevOpsProject Condition="'(AzureDevOpsProject)' == '' and '(AzureDevOpsOrg)' == 'dnceng' and '$(IsInternalBuild)' == 'true'">internal</AzureDevOpsProject>
mmitche marked this conversation as resolved.
Show resolved Hide resolved
<AzureDevOpsProject Condition="'(AzureDevOpsProject)' == '' and '(AzureDevOpsOrg)' == 'dnceng' and '$(IsInternalBuild)' == 'false'">public</AzureDevOpsProject>
</PropertyGroup>

<Error
Condition="'$(AzureDevOpsProject)' == ''"
Text="Parameters 'AzureDevOpsProject' is empty." />

<!--Create the shipping feed-->
<CreateAzureDevOpsFeed
IsInternal="$(IsInternalBuild)"
AzureDevOpsPersonalAccessToken="$(AzdoTargetFeedPAT)"
FeedName="$(FeedName)-shipping"
AzureDevOpsOrg="$(AzureDevOpsOrg)">
AzureDevOpsOrg="$(AzureDevOpsOrg)"
AzureDevOpsProject="$(AzureDevOpsProject)">
<Output TaskParameter="TargetFeedURL" PropertyName="ShippingAzdoPackageFeedURL"/>
</CreateAzureDevOpsFeed>

<!--Create the nonshipping feed-->
<CreateAzureDevOpsFeed
IsInternal="$(IsInternalBuild)"
AzureDevOpsPersonalAccessToken="$(AzdoTargetFeedPAT)"
FeedName="$(FeedName)-nonshipping"
AzureDevOpsOrg="$(AzureDevOpsOrg)">
AzureDevOpsOrg="$(AzureDevOpsOrg)"
AzureDevOpsProject="$(AzureDevOpsProject)">
<Output TaskParameter="TargetFeedURL" PropertyName="NonShippingAzdoPackageFeedURL"/>
</CreateAzureDevOpsFeed>

Expand Down
333 changes: 0 additions & 333 deletions src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SetupTargetFeeds.proj

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

namespace Microsoft.DotNet.Build.Tasks.Feed
{
/// <summary>
/// Represents the body of a request sent when creating a new feed.
/// </summary>
/// <remarks>>
/// When creating a new feed, we want to set up permissions based on the org and project.
/// Right now, only dnceng's public and internal projects are supported.
/// New feeds automatically get the feed administrators and project collection administrators as owners,
/// but we want to automatically add some additional permissions so that the build services can push to them,
/// and aadTenant users can read from them.
/// </remarks>
public class AzureDevOpsArtifactFeed
{
public AzureDevOpsArtifactFeed(string name, string organization, string project)
{
Name = name;
switch (organization)
{
case "dnceng":
switch (project)
{
case "public":
case "internal":
Permissions = new List<AzureDevOpsFeedPermission>
{
// Project Collection Build Service
new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", "contributor"),
// internal Build Service
new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"),
// Project administrators
new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"),
};
break;
default:
throw new NotImplementedException($"Project '{project}' within organization '{organization}' contains no feed permissions information.");
}
break;
default:
throw new NotImplementedException($"Organization '{organization}' contains no feed permissions information.");

}
}

public string Name { get; set; }

public List<AzureDevOpsFeedPermission> Permissions { get; private set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.DotNet.Build.Tasks.Feed
{
public class AzureDevOpsFeedPermission
{
public AzureDevOpsFeedPermission(string identityDescriptor, string role)
{
IdentityDescriptor = identityDescriptor;
Role = role;
}

public string IdentityDescriptor { get; set; }

public string Role { get; set; }
}
}
13 changes: 13 additions & 0 deletions src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.DotNet.Build.Tasks.Feed
{
/// <summary>
/// Represents a feed view
/// </summary>
public class AzureDevOpsFeedView
{
public AzureDevOpsFeedVisibility Visibility { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.DotNet.Build.Tasks.Feed
{
public enum AzureDevOpsFeedVisibility
{
collection,
aadTenant,
@private,
none
}
}
131 changes: 80 additions & 51 deletions src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,24 @@ public class CreateAzureDevOpsFeed : MSBuild.Task
[Output]
public string TargetFeedName { get; set; }

/// <summary>
/// Organization that the feed should be created in
/// </summary>
[Required]
public string AzureDevOpsOrg { get; set; }

/// <summary>
/// Project that that feed should be created in. The public/internal visibility
/// of this project will determine whether the feed is public.
/// </summary>
[Required]
public string AzureDevOpsProject { get; set; }

/// <summary>
/// Personal access token used to authorize to the API and create the feed
/// </summary>
[Required]
public bool IsInternal { get; set; }
public string AzureDevOpsPersonalAccessToken { get; set; }

public string RepositoryName { get; set; }

Expand All @@ -40,12 +56,9 @@ public class CreateAzureDevOpsFeed : MSBuild.Task
/// </summary>
public string ContentIdentifier { get; set; }

[Required]
public string AzureDevOpsPersonalAccessToken { get; set; }

public string AzureDevOpsFeedsApiVersion { get; set; } = "5.0-preview.1";
public string AzureDevOpsFeedsApiVersion { get; set; } = "5.1-preview.1";

public string AzureDevOpsOrg { get; set; } = "dnceng";
public string LocalViewVisibility { get; set; } = "collection";

/// <summary>
/// Number of characters from the commit SHA prefix that should be included in the feed name.
Expand Down Expand Up @@ -86,16 +99,19 @@ private async Task<bool> ExecuteAsync()
// or contain any of these: @ ~ ; { } ' + = , < > | / \ ? : & $ * " # [ ] %
string feedCompatibleRepositoryName = RepositoryName?.Replace('/', '-');

string accessType = IsInternal ? "internal" : "public";
string publicSegment = IsInternal ? string.Empty : "public/";
string accessId = IsInternal ? "int" : "pub";
// For clarity, and compatibility with existing infrastructure, we include the feed visibility tag.
// This serves two purposes:
// 1. In nuget.config files (and elsewhere), the name at a glance can identify its visibility
// 2. Existing automation has knowledge of "darc-int" and "darc-pub" for purposes of injecting authentication for internal builds
// and managing the isolated feeds within the NuGet.config files.
string accessTag = GetFeedVisibilityTag(AzureDevOpsOrg, AzureDevOpsProject);
string extraContentInfo = !string.IsNullOrEmpty(ContentIdentifier) ? $"-{ContentIdentifier}" : "";
string baseFeedName = FeedName ?? $"darc-{accessId}{extraContentInfo}-{feedCompatibleRepositoryName}-{CommitSha.Substring(0, ShaUsableLength)}";
string baseFeedName = FeedName ?? $"darc-{accessTag}{extraContentInfo}-{feedCompatibleRepositoryName}-{CommitSha.Substring(0, ShaUsableLength)}";
string versionedFeedName = baseFeedName;
bool needsUniqueName = false;
int subVersion = 0;

Log.LogMessage(MessageImportance.High, $"Creating the new {accessType} Azure DevOps artifacts feed '{baseFeedName}'...");
Log.LogMessage(MessageImportance.High, $"Creating the new Azure DevOps artifacts feed '{baseFeedName}'...");

if (baseFeedName.Length > MaxLengthForAzDoFeedNames)
{
Expand All @@ -118,20 +134,43 @@ private async Task<bool> ExecuteAsync()
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", AzureDevOpsPersonalAccessToken))));

AzureDevOpsArtifactFeed newFeed = new AzureDevOpsArtifactFeed(versionedFeedName, AzureDevOpsOrg);
AzureDevOpsArtifactFeed newFeed = new AzureDevOpsArtifactFeed(versionedFeedName, AzureDevOpsOrg, AzureDevOpsProject);

string body = JsonConvert.SerializeObject(newFeed, _serializerSettings);
string createBody = JsonConvert.SerializeObject(newFeed, _serializerSettings);

HttpRequestMessage postMessage = new HttpRequestMessage(HttpMethod.Post, $"{publicSegment}_apis/packaging/feeds");
postMessage.Content = new StringContent(body, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(postMessage);
using HttpRequestMessage createFeedMessage = new HttpRequestMessage(HttpMethod.Post, $"{AzureDevOpsProject}/_apis/packaging/feeds");
createFeedMessage.Content = new StringContent(createBody, Encoding.UTF8, "application/json");
using HttpResponseMessage createFeedResponse = await client.SendAsync(createFeedMessage);

if (response.StatusCode == HttpStatusCode.Created)
if (createFeedResponse.StatusCode == HttpStatusCode.Created)
{
needsUniqueName = false;
baseFeedName = versionedFeedName;

// Now update the 'Local' feed view with aad tenant visibility.
var feedViewVisibilityPatch = new AzureDevOpsFeedView()
{
Visibility = AzureDevOpsFeedVisibility.collection
};

string patchBody = JsonConvert.SerializeObject(feedViewVisibilityPatch, _serializerSettings);

// Note that Framework doesn't natively have Patch
#if NETFRAMEWORK
HttpMethod patchMethod = new HttpMethod("PATCH");
#else
HttpMethod patchMethod = HttpMethod.Patch;
#endif
using HttpRequestMessage patchFeedViewMessage = new HttpRequestMessage(patchMethod, $"{AzureDevOpsProject}/_apis/packaging/feeds/{baseFeedName}/views/Local");
patchFeedViewMessage.Content = new StringContent(patchBody, Encoding.UTF8, "application/json");
using HttpResponseMessage patchFeedViewResponse = await client.SendAsync(patchFeedViewMessage);

if (patchFeedViewResponse.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"Feed view 'Local' for '{baseFeedName}' could not be updated to have aadTenant visibility. Exception: {await patchFeedViewResponse.Content.ReadAsStringAsync()}");
}
}
else if (response.StatusCode == HttpStatusCode.Conflict)
else if (createFeedResponse.StatusCode == HttpStatusCode.Conflict)
{
versionedFeedName = $"{baseFeedName}-{++subVersion}";
needsUniqueName = true;
Expand All @@ -144,12 +183,12 @@ private async Task<bool> ExecuteAsync()
}
else
{
throw new Exception($"Feed '{baseFeedName}' was not created. Request failed with status code {response.StatusCode}. Exception: {await response.Content.ReadAsStringAsync()}");
throw new Exception($"Feed '{baseFeedName}' was not created. Request failed with status code {createFeedResponse.StatusCode}. Exception: {await createFeedResponse.Content.ReadAsStringAsync()}");
}
}
} while (needsUniqueName);

TargetFeedURL = $"https://pkgs.dev.azure.com/{AzureDevOpsOrg}/{publicSegment}_packaging/{baseFeedName}/nuget/v3/index.json";
TargetFeedURL = $"https://pkgs.dev.azure.com/{AzureDevOpsOrg}/{AzureDevOpsProject}/_packaging/{baseFeedName}/nuget/v3/index.json";
TargetFeedName = baseFeedName;

Log.LogMessage(MessageImportance.High, $"Feed '{TargetFeedURL}' created successfully!");
Expand All @@ -161,41 +200,31 @@ private async Task<bool> ExecuteAsync()

return !Log.HasLoggedErrors;
}
}

public class Permission
{
public Permission(string identityDescriptor, int role)
{
IdentityDescriptor = identityDescriptor;
Role = role;
}

public string IdentityDescriptor { get; set; }

public int Role { get; set; }
}

public class AzureDevOpsArtifactFeed
{
public AzureDevOpsArtifactFeed(string name, string organization)
/// <summary>
/// Returns a tag for feed visibility that will be added to the feed name
/// </summary>
/// <param name="organization">Organization containing the feed</param>
/// <param name="project">Project within <paramref name="organization"/> containing the feed</param>
/// <returns>Feed tag</returns>
/// <exception cref="NotImplementedException"></exception>
private string GetFeedVisibilityTag(string organization, string project)
{
Name = name;
if (organization == "dnceng")
switch (organization)
{
Permissions = new List<Permission>
{
// Mimic the permissions added to a feed when created in the browser
new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", 3), // Project Collection Build Service
new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", 3), // internal Build Service
new Permission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", 4), // Feed administrators
new Permission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1846651262-2896117056-2992157471-3474698899-1-2052915359-1158038602-2757432096-2854636005", 4) // Feed administrators and contributors
};
case "dnceng":
switch (project)
{
case "internal":
return "int";
case "public":
return "pub";
default:
throw new NotImplementedException($"Project '{project}' within organization '{organization}' has no visibility mapping.");
}
default:
throw new NotImplementedException($"Organization '{organization}' has no visibility mapping.");
}
}

public string Name { get; set; }

public List<Permission> Permissions { get; private set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class SetupTargetFeedConfigV3 : SetupTargetFeedConfigBase

public TaskLoggingHelper Log { get; }

public string AzureDevOpsOrg => "dnceng";

public SetupTargetFeedConfigV3(
TargetChannelConfig targetChannelConfig,
bool isInternalBuild,
Expand Down Expand Up @@ -93,7 +95,8 @@ private IEnumerable<TargetFeedConfig> Feeds()
var packagesFeedTask = new CreateAzureDevOpsFeed()
{
BuildEngine = BuildEngine,
IsInternal = IsInternalBuild,
AzureDevOpsOrg = AzureDevOpsOrg,
AzureDevOpsProject = IsInternalBuild ? "internal" : "public",
AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey,
RepositoryName = RepositoryName,
CommitSha = CommitSha
Expand All @@ -112,7 +115,8 @@ private IEnumerable<TargetFeedConfig> Feeds()
var symbolsFeedTask = new CreateAzureDevOpsFeed()
{
BuildEngine = BuildEngine,
IsInternal = IsInternalBuild,
AzureDevOpsOrg = AzureDevOpsOrg,
AzureDevOpsProject = IsInternalBuild ? "internal" : "public",
AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey,
RepositoryName = RepositoryName,
CommitSha = CommitSha,
Expand Down