Example #1
0
        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);
        }
Example #2
0
        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());
        }
Example #3
0
        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());
        }
Example #4
0
        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());
        }
Example #5
0
        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());
        }
Example #6
0
        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);
        }
Example #7
0
        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());
        }
Example #8
0
        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]));
            }
        }
Example #9
0
        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();
        }
Example #11
0
        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);
        }
Example #12
0
        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();
        }
Example #16
0
        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));
            }
        }
Example #17
0
        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;
            }
        }