Skip to content

Commit 5e792bf

Browse files
abhi7860Abhishek ShahmsJinLei
authored
[Bastion] Add support for Bastion shareable links (#25623)
* Init * Adding help content * Update test run * Null check * Update ChangeLog.md * Update help * Update test result * Minor string changes * Update ChangeLog.md * Updating BSL label * Update help * Update Remove-AzBastionShareableLink.md * Update ChangeLog.md --------- Co-authored-by: Abhishek Shah <[email protected]> Co-authored-by: Jin Lei <[email protected]>
1 parent 4a9f511 commit 5e792bf

17 files changed

+9901
-3
lines changed

src/Network/Network.Test/ScenarioTests/BastionTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,13 @@ public void TestBastionCreateWithFeatures()
6565
{
6666
TestRunner.RunTestScript("Test-BastionCreateWithFeatures");
6767
}
68+
69+
[Fact]
70+
[Trait(Category.AcceptanceType, Category.CheckIn)]
71+
[Trait(Category.Owner, NrpTeamAlias.bastion)]
72+
public void TestBastionShareableLink()
73+
{
74+
TestRunner.RunTestScript("Test-BastionShareableLink");
75+
}
6876
}
6977
}

src/Network/Network.Test/ScenarioTests/BastionTests.ps1

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,103 @@ function Test-BastionIpObjectParam {
462462
Clean-ResourceGroup $rgname
463463
}
464464
}
465+
466+
<#
467+
Test Bastion Shareable Link
468+
#>
469+
function Test-BastionShareableLink {
470+
# Setup
471+
$rgname = Get-ResourceGroupName
472+
$resourceTypeParent = "Microsoft.Network/bastionHosts"
473+
$location = Get-ProviderLocation $resourceTypeParent
474+
$subnetName = "AzureBastionSubnet"
475+
$vnetName = "$(Get-ResourceName)-Vnet"
476+
$vnetName2 = "$(Get-ResourceName)-Vnet"
477+
$publicIpName = "$(Get-ResourceName)-Pip"
478+
$publicIpName2 = "$(Get-ResourceName)-Pip"
479+
$bastionName = "$(Get-ResourceName)-Bastion"
480+
$bastionName2 = "$(Get-ResourceName)-Bastion2"
481+
$vmUsername = "azureuser"
482+
$vmPassword = ConvertTo-SecureString "$vmUsername@123" -AsPlainText -Force
483+
$vmName = "$(Get-ResourceName)-Vm"
484+
$vmSize = "Standard_D2ds_v4"
485+
$nicName = "$(Get-ResourceName)-Nic"
486+
487+
try {
488+
# Create the resource group
489+
$resourceGroup = New-AzResourceGroup -Name $rgName -Location $location
490+
491+
# Create the Virtual Network
492+
$subnet = New-AzVirtualNetworkSubnetConfig -Name $subnetName -AddressPrefix 10.0.0.0/24
493+
$vnet = New-AzVirtualNetwork -Name $vnetName -ResourceGroupName $rgname -Location $location -AddressPrefix 10.0.0.0/16 -Subnet $subnet
494+
$vnet2 = New-AzVirtualNetwork -Name $vnetName2 -ResourceGroupName $rgname -Location $location -AddressPrefix 10.0.0.0/16 -Subnet $subnet
495+
496+
# Create Public IP
497+
$publicIp = New-AzPublicIpAddress -ResourceGroupName $rgname -name $publicIpName -location $location -AllocationMethod Static -Sku Standard
498+
$publicIp2 = New-AzPublicIpAddress -ResourceGroupName $rgname -name $publicIpName2 -location $location -AllocationMethod Static -Sku Standard
499+
500+
# Create Bastion
501+
$createBastionJob = New-AzBastion -Name $bastionName -ResourceGroupName $rgname -VirtualNetwork $vnet -PublicIpAddress $publicIp -EnableShareableLink $true -AsJob
502+
$createBastionJob2 = New-AzBastion -Name $bastionName2 -ResourceGroupName $rgname -VirtualNetwork $vnet2 -PublicIpAddress $publicIp2 -Sku Basic -AsJob
503+
504+
# Create VM
505+
$nic = New-AzNetworkInterface -Name $NICName -ResourceGroupName $RgName -Location $location -SubnetId $vnet.Subnets[0].Id
506+
$Credential = New-Object System.Management.Automation.PSCredential ($vmUsername, $vmPassword);
507+
$vm = New-AzVMConfig -VMName $vmName -VMSize $vmSize
508+
$vm = Set-AzVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $Credential -ProvisionVMAgent -EnableAutoUpdate
509+
$vm = Add-AzVMNetworkInterface -VM $vm -Id $NIC.Id
510+
$vm = Set-AzVMSourceImage -VM $vm -PublisherName "MicrosoftWindowsServer" -Offer "WindowsServer" -Skus "2022-datacenter-azure-edition-core" -Version latest
511+
$vm = Set-AzVMBootDiagnostic -VM $vm -Disable
512+
$createVmJob = New-AzVM -ResourceGroupName $RgName -Location $location -VM $vm -Verbose -AsJob
513+
514+
# Wait for create Basic Bastion completion
515+
$createBastionJob2 | Wait-Job
516+
$bastion2 = $createBastionJob2 | Receive-Job
517+
Assert-NotNull $bastion2
518+
519+
# Receive error message
520+
$randomName = Get-ResourceName
521+
# New BSL
522+
Assert-Throws { New-AzBastionShareableLink -ResourceGroupName $rgname -Name $randomName -TargetVmId $bastionName2 } "Resource '$randomName' not found"
523+
Assert-Throws { New-AzBastionShareableLink -ResourceGroupName $rgname -Name $bastionName2 -TargetVmId $bastionName2 } "Shareable link feature is not enabled"
524+
# Get BSL
525+
Assert-Throws { Get-AzBastionShareableLink -ResourceGroupName $rgname -Name $randomName -TargetVmId $bastionName2 } "Resource '$randomName' not found"
526+
Assert-Throws { Get-AzBastionShareableLink -ResourceGroupName $rgname -Name $bastionName2 -TargetVmId $bastionName2 } "Shareable link feature is not enabled"
527+
# Remove BSL
528+
Assert-Throws { Remove-AzBastionShareableLink -ResourceGroupName $rgname -Name $randomName -TargetVmId $bastionName2 -Force } "Resource '$randomName' not found"
529+
Assert-Throws { Remove-AzBastionShareableLink -ResourceGroupName $rgname -Name $bastionName2 -TargetVmId $bastionName2 -Force } "Shareable link feature is not enabled"
530+
531+
# Wait for create Bastion completion
532+
$createBastionJob | Wait-Job
533+
$bastion = $createBastionJob | Receive-Job
534+
Assert-NotNull $bastion
535+
Assert-AreEqual $true $bastion.EnableShareableLink
536+
537+
# Wait for create VM completion
538+
$createVmJob | Wait-Job
539+
$vm = Get-AzVM -ResourceGroupName $RgName -Name $vmName
540+
Assert-NotNull $vm
541+
Assert-NotNull $vm.Id
542+
543+
# Create BSL
544+
New-AzBastionShareableLink -InputObject $bastion -TargetVmId $vm.Id
545+
546+
# Get BSL
547+
$getBsl = Get-AzBastionShareableLink -InputObject $bastion
548+
Assert-NotNull $getBsl
549+
Assert-AreEqual $vm.Id $getBsl.VmId
550+
Assert-NotNull $getBsl.Bsl
551+
Assert-NotNull $getBsl.CreatedAt
552+
553+
# Delete BSL
554+
Remove-AzBastionShareableLink -InputObject $bastion -TargetVmId $vm.Id -Force
555+
556+
# Get BSL
557+
$getBsl = Get-AzBastionShareableLink -InputObject $bastion
558+
Assert-AreEqual 0 $getBsl.Count
559+
}
560+
finally {
561+
# Clean up
562+
Clean-ResourceGroup $rgname
563+
}
564+
}

src/Network/Network.Test/SessionRecords/Commands.Network.Test.ScenarioTests.BastionTests/TestBastionShareableLink.json

Lines changed: 8433 additions & 0 deletions
Large diffs are not rendered by default.

src/Network/Network/Az.Network.psd1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ CmdletsToExport = 'Add-AzApplicationGatewayAuthenticationCertificate',
164164
'Get-AzApplicationSecurityGroup',
165165
'Get-AzAutoApprovedPrivateLinkService',
166166
'Get-AzAvailablePrivateEndpointType', 'Get-AzAvailableServiceAlias',
167-
'Get-AzAvailableServiceDelegation', 'Get-AzBastion',
167+
'Get-AzAvailableServiceDelegation', 'Get-AzBastion', 'Get-AzBastionShareableLink',
168168
'Get-AzBgpServiceCommunity', 'Get-AzCustomIpPrefix',
169169
'Get-AzDdosProtectionPlan', 'Get-AzDelegation',
170170
'Get-AzEffectiveNetworkSecurityGroup', 'Get-AzEffectiveRouteTable',
@@ -333,7 +333,7 @@ CmdletsToExport = 'Add-AzApplicationGatewayAuthenticationCertificate',
333333
'New-AzApplicationGatewayTrustedRootCertificate',
334334
'New-AzApplicationGatewayUrlPathMapConfig',
335335
'New-AzApplicationGatewayWebApplicationFirewallConfiguration',
336-
'New-AzApplicationSecurityGroup', 'New-AzBastion',
336+
'New-AzApplicationSecurityGroup', 'New-AzBastion', 'New-AzBastionShareableLink',
337337
'New-AzContainerNicConfig', 'New-AzContainerNicConfigIpConfig',
338338
'New-AzCustomIpPrefix', 'New-AzDdosProtectionPlan',
339339
'New-AzDelegation', 'New-AzExpressRouteCircuit',
@@ -476,7 +476,7 @@ CmdletsToExport = 'Add-AzApplicationGatewayAuthenticationCertificate',
476476
'Remove-AzApplicationGatewayTrustedClientCertificate',
477477
'Remove-AzApplicationGatewayTrustedRootCertificate',
478478
'Remove-AzApplicationGatewayUrlPathMapConfig',
479-
'Remove-AzApplicationSecurityGroup', 'Remove-AzBastion',
479+
'Remove-AzApplicationSecurityGroup', 'Remove-AzBastion', 'Remove-AzBastionShareableLink',
480480
'Remove-AzCustomIpPrefix', 'Remove-AzDdosProtectionPlan',
481481
'Remove-AzDelegation', 'Remove-AzExpressRouteCircuit',
482482
'Remove-AzExpressRouteCircuitAuthorization',

src/Network/Network/Bastion/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@ internal class Constants
2525
// Scale Units
2626
internal const int MinimumScaleUnits = 2;
2727
internal const int MaximumScaleUnits = 50;
28+
29+
// Shareable Link
30+
internal const string ShareableLink = "ShareableLink";
2831
}
2932
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
namespace Microsoft.Azure.Commands.Network.Bastion
16+
{
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Management.Automation;
20+
21+
using Microsoft.Azure.Commands.Network.Models;
22+
using Microsoft.Azure.Commands.Network.Models.Bastion;
23+
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
24+
using Microsoft.Azure.Management.Internal.Resources.Utilities.Models;
25+
using Microsoft.Azure.Management.Network;
26+
27+
[Cmdlet(VerbsCommon.Get,
28+
ResourceManager.Common.AzureRMConstants.AzureRMPrefix + Constants.BastionResourceName + Constants.ShareableLink,
29+
DefaultParameterSetName = BastionParameterSetNames.ByResourceGroupName + BastionParameterSetNames.ByName,
30+
SupportsShouldProcess = true),
31+
OutputType(typeof(List<PSBastionShareableLink>))]
32+
public class GetAzBastionShareableLinkCommand : BastionBaseCmdlet
33+
{
34+
[Parameter(
35+
Mandatory = true,
36+
ParameterSetName = BastionParameterSetNames.ByResourceGroupName + BastionParameterSetNames.ByName,
37+
HelpMessage = "The resource group name where Bastion resource exists")]
38+
[ResourceGroupCompleter]
39+
[ValidateNotNullOrEmpty]
40+
public string ResourceGroupName { get; set; }
41+
42+
[Alias("ResourceName", "BastionName")]
43+
[Parameter(
44+
Mandatory = true,
45+
ParameterSetName = BastionParameterSetNames.ByResourceGroupName + BastionParameterSetNames.ByName,
46+
HelpMessage = "The Bastion resource name.")]
47+
[ValidateNotNullOrEmpty]
48+
[ResourceNameCompleter("Bastion", "ResourceGroupName")]
49+
public string Name { get; set; }
50+
51+
[Parameter(
52+
Mandatory = true,
53+
ParameterSetName = BastionParameterSetNames.ByResourceId,
54+
HelpMessage = "The Bastion Resource ID")]
55+
[ValidateNotNullOrEmpty]
56+
public string ResourceId { get; set; }
57+
58+
[Parameter(
59+
Mandatory = true,
60+
ParameterSetName = BastionParameterSetNames.ByBastionObject,
61+
HelpMessage = "Bastion Object")]
62+
[ValidateNotNullOrEmpty]
63+
public PSBastion InputObject { get; set; }
64+
65+
[Parameter(
66+
Mandatory = false,
67+
ValueFromPipeline = true,
68+
HelpMessage = "ID of the VMs to get Bastion shareable links")]
69+
public List<string> TargetVmId { get; set; }
70+
71+
[Parameter(
72+
Mandatory = false,
73+
ValueFromPipeline = true,
74+
HelpMessage = "Run cmdlet in the background")]
75+
public SwitchParameter AsJob { get; set; }
76+
77+
public override void Execute()
78+
{
79+
base.Execute();
80+
if (ParameterSetName.Equals(BastionParameterSetNames.ByResourceId, StringComparison.OrdinalIgnoreCase))
81+
{
82+
var parsedResourceId = new ResourceIdentifier(this.ResourceId);
83+
this.Name = parsedResourceId.ResourceName;
84+
this.ResourceGroupName = parsedResourceId.ResourceGroupName;
85+
}
86+
else if (ParameterSetName.Equals(BastionParameterSetNames.ByBastionObject, StringComparison.OrdinalIgnoreCase))
87+
{
88+
this.Name = this.InputObject.Name;
89+
this.ResourceGroupName = this.InputObject.ResourceGroupName;
90+
}
91+
92+
if (!this.TryGetBastion(this.ResourceGroupName, this.Name, out PSBastion bastion))
93+
{
94+
throw new ItemNotFoundException(string.Format(Properties.Resources.ResourceNotFound, this.Name));
95+
}
96+
97+
if (!bastion.EnableShareableLink.Value)
98+
{
99+
throw new PropertyNotFoundException(Properties.Resources.BastionShareableLinkNotEnabled);
100+
}
101+
102+
var psBslRequest = new PSBastionShareableLinkRequest(this.TargetVmId);
103+
var getBslResultIter = this.NetworkClient.NetworkManagementClient.GetBastionShareableLink(this.ResourceGroupName, this.Name, psBslRequest.ToSdkObject());
104+
105+
List<PSBastionShareableLink> psBslResult = new List<PSBastionShareableLink>();
106+
foreach (var bsl in getBslResultIter)
107+
{
108+
psBslResult.Add(new PSBastionShareableLink(bsl));
109+
}
110+
111+
WriteVerbose($"Found {psBslResult.Count} Bastion shareable links");
112+
WriteObject(psBslResult);
113+
}
114+
}
115+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
namespace Microsoft.Azure.Commands.Network.Bastion
16+
{
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Management.Automation;
20+
21+
using Microsoft.Azure.Commands.Network.Models;
22+
using Microsoft.Azure.Commands.Network.Models.Bastion;
23+
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
24+
using Microsoft.Azure.Management.Internal.Resources.Utilities.Models;
25+
using Microsoft.Azure.Management.Network;
26+
27+
[Cmdlet(VerbsCommon.New,
28+
ResourceManager.Common.AzureRMConstants.AzureRMPrefix + Constants.BastionResourceName + Constants.ShareableLink,
29+
DefaultParameterSetName = BastionParameterSetNames.ByResourceGroupName + BastionParameterSetNames.ByName,
30+
SupportsShouldProcess = true),
31+
OutputType(typeof(List<PSBastionShareableLink>))]
32+
public class NewAzBastionShareableLinkCommand : BastionBaseCmdlet
33+
{
34+
[Parameter(
35+
Mandatory = true,
36+
ParameterSetName = BastionParameterSetNames.ByResourceGroupName + BastionParameterSetNames.ByName,
37+
HelpMessage = "The resource group name where Bastion resource exists")]
38+
[ResourceGroupCompleter]
39+
[ValidateNotNullOrEmpty]
40+
public string ResourceGroupName { get; set; }
41+
42+
[Alias("ResourceName", "BastionName")]
43+
[Parameter(
44+
Mandatory = true,
45+
ParameterSetName = BastionParameterSetNames.ByResourceGroupName + BastionParameterSetNames.ByName,
46+
HelpMessage = "The Bastion resource name")]
47+
[ValidateNotNullOrEmpty]
48+
[ResourceNameCompleter("Bastion", "ResourceGroupName")]
49+
public string Name { get; set; }
50+
51+
[Parameter(
52+
Mandatory = true,
53+
ParameterSetName = BastionParameterSetNames.ByResourceId,
54+
HelpMessage = "The Bastion resource ID")]
55+
[ValidateNotNullOrEmpty]
56+
public string ResourceId { get; set; }
57+
58+
[Parameter(
59+
Mandatory = true,
60+
ParameterSetName = BastionParameterSetNames.ByBastionObject,
61+
HelpMessage = "Bastion object")]
62+
[ValidateNotNullOrEmpty]
63+
public PSBastion InputObject { get; set; }
64+
65+
[Parameter(
66+
Mandatory = true,
67+
ValueFromPipeline = true,
68+
HelpMessage = "ID of the VMs that require generation of Bastion shareable links")]
69+
[ValidateNotNullOrEmpty]
70+
public List<string> TargetVmId { get; set; }
71+
72+
[Parameter(
73+
Mandatory = false,
74+
ValueFromPipeline = true,
75+
HelpMessage = "Run cmdlet in the background")]
76+
public SwitchParameter AsJob { get; set; }
77+
78+
public override void Execute()
79+
{
80+
base.Execute();
81+
if (ParameterSetName.Equals(BastionParameterSetNames.ByResourceId, StringComparison.OrdinalIgnoreCase))
82+
{
83+
var parsedResourceId = new ResourceIdentifier(this.ResourceId);
84+
this.Name = parsedResourceId.ResourceName;
85+
this.ResourceGroupName = parsedResourceId.ResourceGroupName;
86+
}
87+
else if (ParameterSetName.Equals(BastionParameterSetNames.ByBastionObject, StringComparison.OrdinalIgnoreCase))
88+
{
89+
this.Name = this.InputObject.Name;
90+
this.ResourceGroupName = this.InputObject.ResourceGroupName;
91+
}
92+
93+
if (!this.TryGetBastion(this.ResourceGroupName, this.Name, out PSBastion bastion))
94+
{
95+
throw new ItemNotFoundException(string.Format(Properties.Resources.ResourceNotFound, this.Name));
96+
}
97+
98+
if (!bastion.EnableShareableLink.Value)
99+
{
100+
throw new PropertyNotFoundException(Properties.Resources.BastionShareableLinkNotEnabled);
101+
}
102+
103+
var psBslRequest = new PSBastionShareableLinkRequest(this.TargetVmId);
104+
this.NetworkClient.NetworkManagementClient.PutBastionShareableLink(this.ResourceGroupName, this.Name, psBslRequest.ToSdkObject());
105+
106+
var getBslResultIter = this.NetworkClient.NetworkManagementClient.GetBastionShareableLink(this.ResourceGroupName, this.Name, psBslRequest.ToSdkObject());
107+
108+
List<PSBastionShareableLink> psBslResult = new List<PSBastionShareableLink>();
109+
foreach (var bsl in getBslResultIter)
110+
{
111+
psBslResult.Add(new PSBastionShareableLink(bsl));
112+
}
113+
114+
WriteVerbose($"Generated {psBslResult.Count} Bastion shareable links");
115+
WriteObject(psBslResult);
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)