public async Task WhenClosingAllTunnels_CloseEventsAreFired() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelClosedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <ITunnel>(); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromResult(true)); mockTunnel.Setup(t => t.Close()); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new InstanceLocator("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync( destination, It.IsAny <ISshRelayPolicy>())) .Returns(Task.FromResult(mockTunnel.Object)); var tunnel = await broker.ConnectAsync( destination, new AllowAllRelayPolicy(), TimeSpan.FromMinutes(1)); await broker.DisconnectAllAsync(); mockEventService.Verify(s => s.FireAsync(It.IsAny <TunnelClosedEvent>()), Times.Once); }
public async Task WhenConnectSuccessful_ThenOpenTunnelsIncludesTunnel() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <ITunnel>(); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromResult(true)); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new InstanceLocator("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync( destination, It.IsAny <ISshRelayPolicy>())) .Returns(Task.FromResult(mockTunnel.Object)); var tunnel = await broker.ConnectAsync( destination, new AllowAllRelayPolicy(), TimeSpan.FromMinutes(1)); Assert.IsNotNull(tunnel); Assert.AreEqual(1, broker.OpenTunnels.Count()); Assert.IsTrue(broker.IsConnected(destination)); Assert.AreSame(tunnel, broker.OpenTunnels.First()); }
public async Task WhenClosingAllTunnels_AllTunnelsAreClosed() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <Tunnel>(null, null, null); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromResult(true)); mockTunnel.Setup(t => t.Close()); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new VmInstanceReference("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync(destination)) .Returns(Task.FromResult(mockTunnel.Object)); var tunnel = await broker.ConnectAsync(destination, TimeSpan.FromMinutes(1)); Assert.AreEqual(1, broker.OpenTunnels.Count()); await broker.DisconnectAllAsync(); Assert.AreEqual(0, broker.OpenTunnels.Count()); }
public async Task WhenClosingTunnel_ThenTunnelIsRemovedFromOpenTunnels() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <ITunnel>(); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromResult(true)); mockTunnel.Setup(t => t.Close()); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new InstanceLocator("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync( destination, It.IsAny <ISshRelayPolicy>())) .Returns(Task.FromResult(mockTunnel.Object)); var tunnel = await broker.ConnectAsync( destination, new AllowAllRelayPolicy(), TimeSpan.FromMinutes(1)); Assert.AreEqual(1, broker.OpenTunnels.Count()); await broker.DisconnectAsync(destination); Assert.AreEqual(0, broker.OpenTunnels.Count()); }
public void WhenProbeFails_ThenOpenTunnelsDoesNotIncludeTunnel() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <ITunnel>(); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromException(new ApplicationException())); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new InstanceLocator("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync(destination)) .Returns(Task.FromResult(mockTunnel.Object)); AssertEx.ThrowsAggregateException <ApplicationException>(() => { broker.ConnectAsync(destination, TimeSpan.FromMinutes(1)).Wait(); }); Assert.AreEqual(0, broker.OpenTunnels.Count()); }
public async Task WhenConnectSuccessful_OpenEventIsFired() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <ITunnel>(); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromResult(true)); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new InstanceLocator("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync(destination)) .Returns(Task.FromResult(mockTunnel.Object)); var tunnel = await broker.ConnectAsync(destination, TimeSpan.FromMinutes(1)); mockEventService.Verify(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>()), Times.Once); }
public async Task WhenConnectingTwice_ExistingTunnelIsReturned() { var mockTunnelService = new Mock <ITunnelService>(); var mockEventService = new Mock <IEventService>(); mockEventService.Setup(s => s.FireAsync(It.IsAny <TunnelOpenedEvent>())) .Returns(Task.FromResult(true)); var mockTunnel = new Mock <ITunnel>(); mockTunnel.Setup(t => t.Probe(It.IsAny <TimeSpan>())) .Returns(Task.FromResult(true)); var broker = new TunnelBrokerService(mockTunnelService.Object, mockEventService.Object); var vmInstanceRef = new InstanceLocator("project", "zone", "instance"); var destination = new TunnelDestination(vmInstanceRef, 3389); mockTunnelService.Setup(s => s.CreateTunnelAsync(destination)) .Returns(Task.FromResult(mockTunnel.Object)); var tunnel1 = await broker.ConnectAsync(destination, TimeSpan.FromMinutes(1)); var tunnel2 = await broker.ConnectAsync(destination, TimeSpan.FromMinutes(1)); Assert.IsNotNull(tunnel1); Assert.IsNotNull(tunnel2); Assert.AreSame(tunnel1, tunnel2); Assert.AreEqual(1, broker.OpenTunnels.Count()); }
public async Task WhenInstanceAvailableButRelayPolicyFails_ThenProbeThrowsUnauthorizedException( [WindowsInstance] ResourceTask <InstanceLocator> testInstance, [Credential(Role = PredefinedRole.IapTunnelUser)] ResourceTask <ICredential> credential) { var service = new TunnelService(CreateAuthorizationAdapter( await credential, null)); var destination = new TunnelDestination( await testInstance, 3389); using (var tunnel = await service.CreateTunnelAsync( destination, new DenyAllPolicy())) { // The Probe should still succeed. await tunnel.Probe(TimeSpan.FromSeconds(20)); // Trying to send ot receive anything should cause a connection reset. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(new IPEndPoint(IPAddress.Loopback, tunnel.LocalPort)); socket.ReceiveTimeout = 100; Assert.AreEqual(0, socket.Receive(new byte[1])); } }
private async Task ConnectInstanceAsync( InstanceLocator instanceRef, VmInstanceConnectionSettings settings) { var tunnel = await this.jobService.RunInBackground( new JobDescription( $"Opening Cloud IAP tunnel to {instanceRef.Name}...", JobUserFeedbackType.BackgroundFeedback), async token => { try { var destination = new TunnelDestination( instanceRef, (ushort)settings.RdpPort.IntValue); // Give IAP the same timeout for probing as RDP itself. // Note that the timeouts are not additive. var timeout = TimeSpan.FromSeconds(settings.ConnectionTimeout.IntValue); return(await this.tunnelBrokerService.ConnectAsync( destination, new SameProcessRelayPolicy(), timeout) .ConfigureAwait(false)); } catch (NetworkStreamClosedException e) { throw new IapRdpConnectionFailedException( "Connecting to the instance failed. Make sure that you have " + "configured your firewall rules to permit Cloud IAP access " + $"to {instanceRef.Name}", HelpTopics.CreateIapFirewallRule, e); } catch (UnauthorizedException) { throw new IapRdpConnectionFailedException( "You are not authorized to connect to this VM instance.\n\n" + $"Verify that the Cloud IAP API is enabled in the project {instanceRef.ProjectId} " + "and that your user has the 'IAP-secured Tunnel User' role.", HelpTopics.IapAccess); } }).ConfigureAwait(true); this.remoteDesktopService.Connect( instanceRef, "localhost", (ushort)tunnel.LocalPort, settings); }
public async Task WhenInstanceAvailableAndUserInRole_ThenCreateTunnelAndProbeSucceeds( [WindowsInstance] ResourceTask <InstanceLocator> testInstance, [Credential(Role = PredefinedRole.IapTunnelUser)] ResourceTask <ICredential> credential) { var service = new TunnelService(CreateAuthorizationAdapter( await credential)); var destination = new TunnelDestination( await testInstance, 3389); var tunnel = await service.CreateTunnelAsync(destination); Assert.AreEqual(destination, tunnel.Destination); await tunnel.Probe(TimeSpan.FromSeconds(20)); tunnel.Close(); }
private async Task ConnectInstance(VmInstanceNode vmNode) { if (this.remoteDesktopService.TryActivate(vmNode.Reference)) { // RDP session was already open, nothing left to do. return; } var destination = new TunnelDestination(vmNode.Reference, RemoteDesktopPort); // TODO: make configurable var timeout = TimeSpan.FromSeconds(30); var tunnel = await this.jobService.RunInBackground( new JobDescription("Opening Cloud IAP tunnel..."), async token => { try { var tunnelBrokerService = this.serviceProvider.GetService <TunnelBrokerService>(); return(await tunnelBrokerService.ConnectAsync(destination, timeout)); } catch (NetworkStreamClosedException e) { throw new ApplicationException( "Connecting to the instance failed. Make sure that you have " + "configured your firewall rules to permit Cloud IAP access " + $"to {vmNode.InstanceName}", e); } catch (UnauthorizedException) { throw new ApplicationException( "You are not authorized to connect to this VM instance.\n\n" + $"Verify that the Cloud IAP API is enabled in the project {vmNode.Reference.ProjectId} " + "and that your user has the 'IAP-secured Tunnel User' role."); } }); this.remoteDesktopService.Connect( vmNode.Reference, "localhost", (ushort)tunnel.LocalPort, vmNode.EffectiveSettingsWithInheritanceApplied); }
public async Task WhenInstanceAvailableButRelayPolicyFails_ThenXxxx( [WindowsInstance] ResourceTask <InstanceLocator> testInstance, [Credential(Role = PredefinedRole.ComputeViewer)] ResourceTask <ICredential> credential) { var service = new TunnelService(CreateAuthorizationAdapter( await credential)); var destination = new TunnelDestination( await testInstance, 3389); var tunnel = await service.CreateTunnelAsync( destination, new DenyAllPolicy()); AssertEx.ThrowsAggregateException <UnauthorizedException>( () => tunnel.Probe(TimeSpan.FromSeconds(20)).Wait()); tunnel.Close(); }
public async Task WhenDeviceEnrolled_ThenAuditLogIndicatesDevice( [WindowsInstance] ResourceTask <InstanceLocator> testInstance) { var service = new TunnelService(CreateAuthorizationAdapterForSecureConnectUser()); // Probe a random port so that we have something unique to look for // in the audit log. var randomPort = (ushort)new Random().Next(10000, 50000); var destination = new TunnelDestination( await testInstance, randomPort); using (var tunnel = await service.CreateTunnelAsync( destination, new SameProcessRelayPolicy())) { Assert.AreEqual(destination, tunnel.Destination); Assert.IsTrue(tunnel.IsMutualTlsEnabled); // The probe will fail, but it will leave a record in the audit log. try { await tunnel.Probe(TimeSpan.FromSeconds(5)); } catch (UnauthorizedException) { } } var logs = await GetIapAccessLogsForPortAsync(randomPort); Assert.IsTrue(logs.Any(), "data access log emitted"); var metadata = (JToken)logs.First().ProtoPayload["metadata"]; Assert.AreNotEqual( "Unknown", metadata.Value <string>("device_state")); CollectionAssert.Contains( new[] { "Normal", "Cross Organization" }, metadata.Value <string>("device_state")); }
private async Task ConnectInstanceAsync( VmInstanceReference instanceRef, VmInstanceConnectionSettings settings) { var tunnel = await this.jobService.RunInBackground( new JobDescription("Opening Cloud IAP tunnel..."), async token => { try { var destination = new TunnelDestination(instanceRef, RemoteDesktopPort); // Give IAP the same timeout for probing as RDP itself. // Note that the timeouts are not additive. var timeout = TimeSpan.FromSeconds(settings.ConnectionTimeout); return(await this.tunnelBrokerService.ConnectAsync(destination, timeout)); } catch (NetworkStreamClosedException e) { throw new ApplicationException( "Connecting to the instance failed. Make sure that you have " + "configured your firewall rules to permit Cloud IAP access " + $"to {instanceRef.InstanceName}", e); } catch (UnauthorizedException) { throw new ApplicationException( "You are not authorized to connect to this VM instance.\n\n" + $"Verify that the Cloud IAP API is enabled in the project {instanceRef.ProjectId} " + "and that your user has the 'IAP-secured Tunnel User' role."); } }); this.remoteDesktopService.Connect( instanceRef, "localhost", (ushort)tunnel.LocalPort, settings); }
public async Task WhenInstanceNotAvailable_ThenProbeFails( [Credential(Role = PredefinedRole.ComputeViewer)] ResourceTask <ICredential> credential) { var service = new TunnelService(CreateAuthorizationAdapter( await credential)); var destination = new TunnelDestination( new InstanceLocator( TestProject.ProjectId, "us-central1-a", "nonexistinginstance"), 3389); var tunnel = await service.CreateTunnelAsync(destination); Assert.AreEqual(destination, tunnel.Destination); AssertEx.ThrowsAggregateException <UnauthorizedException>( () => tunnel.Probe(TimeSpan.FromSeconds(20)).Wait()); tunnel.Close(); }
public async Task WhenInstanceAvailableAndUserInRole_ThenCreateTunnelAndProbeSucceeds( [WindowsInstance] ResourceTask <InstanceLocator> testInstance, [Credential(Role = PredefinedRole.IapTunnelUser)] ResourceTask <ICredential> credential) { var service = new TunnelService(CreateAuthorizationAdapter( await credential, null)); var destination = new TunnelDestination( await testInstance, 3389); using (var tunnel = await service.CreateTunnelAsync( destination, new SameProcessRelayPolicy())) { Assert.AreEqual(destination, tunnel.Destination); Assert.IsFalse(tunnel.IsMutualTlsEnabled); await tunnel.Probe(TimeSpan.FromSeconds(20)); } }
public async Task WhenInstanceAvailableButUserNotInRole_ThenProbeFails( [WindowsInstance] ResourceTask <InstanceLocator> testInstance, [Credential(Role = PredefinedRole.ComputeViewer)] ResourceTask <ICredential> credential) { var service = new TunnelService(CreateAuthorizationAdapter( await credential, null)); var destination = new TunnelDestination( await testInstance, 3389); using (var tunnel = await service.CreateTunnelAsync( destination, new SameProcessRelayPolicy())) { Assert.AreEqual(destination, tunnel.Destination); AssertEx.ThrowsAggregateException <UnauthorizedException>( () => tunnel.Probe(TimeSpan.FromSeconds(20)).Wait()); } }
//--------------------------------------------------------------------- // ISshConnectionService. //--------------------------------------------------------------------- public async Task ActivateOrConnectInstanceAsync(IProjectModelInstanceNode vmNode) { Debug.Assert(vmNode.IsSshSupported()); if (this.sessionBroker.TryActivate(vmNode.Instance)) { // SSH session was active, nothing left to do. return; } // Select node so that tracking windows are updated. await this.projectModelService.SetActiveNodeAsync( vmNode, CancellationToken.None) .ConfigureAwait(true); var instance = vmNode.Instance; var settings = (InstanceConnectionSettings)this.settingsService .GetConnectionSettings(vmNode) .TypedCollection; var timeout = TimeSpan.FromSeconds(settings.SshConnectionTimeout.IntValue); // // Start job to create IAP tunnel. // var tunnelTask = this.jobService.RunInBackground( new JobDescription( $"Opening Cloud IAP tunnel to {instance.Name}...", JobUserFeedbackType.BackgroundFeedback), async token => { try { var destination = new TunnelDestination( vmNode.Instance, (ushort)settings.SshPort.IntValue); // NB. Give IAP the same timeout for probing as SSH itself. return(await this.tunnelBroker.ConnectAsync( destination, new SameProcessRelayPolicy(), timeout) .ConfigureAwait(false)); } catch (NetworkStreamClosedException e) { throw new ConnectionFailedException( "Connecting to the instance failed. Make sure that you have " + "configured your firewall rules to permit Cloud IAP access " + $"to {instance.Name}", HelpTopics.CreateIapFirewallRule, e); } catch (UnauthorizedException) { throw new ConnectionFailedException( "You are not authorized to connect to this VM instance.\n\n" + $"Verify that the Cloud IAP API is enabled in the project {instance.ProjectId} " + "and that your user has the 'IAP-secured Tunnel User' role.", HelpTopics.IapAccess); } }); // // Load persistent CNG key. This must be done on the UI thread. // var email = this.authorizationAdapter.Authorization.Email; var rsaKey = this.keyStoreAdapter.CreateRsaKey( $"IAPDESKTOP_{email}", CngKeyUsages.Signing, true, this.window); Debug.Assert(rsaKey != null); // // Start job to publish key, using whatever mechanism is appropriate // for this instance. // var sshKey = new RsaSshKey(rsaKey); try { var sshSettings = this.sshSettingsRepository.GetSettings(); var authorizedKeyTask = this.jobService.RunInBackground( new JobDescription( $"Publishing SSH key for {instance.Name}...", JobUserFeedbackType.BackgroundFeedback), async token => { // // Authorize the key. // return(await this.authorizedKeyService.AuthorizeKeyAsync( vmNode.Instance, sshKey, TimeSpan.FromSeconds(sshSettings.PublicKeyValidity.IntValue), NullIfEmpty(settings.SshUsername.StringValue), AuthorizeKeyMethods.All, token) .ConfigureAwait(true)); }); // // Wait for both jobs to continue (they are both fairly slow). // await Task.WhenAll(tunnelTask, authorizedKeyTask) .ConfigureAwait(true); var language = sshSettings.IsPropagateLocaleEnabled.BoolValue ? CultureInfo.CurrentUICulture : null; // // NB. ConnectAsync takes ownership of the key and will retain // it for the lifetime of the session. // await this.sessionBroker.ConnectAsync( instance, new IPEndPoint(IPAddress.Loopback, tunnelTask.Result.LocalPort), authorizedKeyTask.Result, language, timeout) .ConfigureAwait(true); } catch (Exception) { sshKey.Dispose(); throw; } }