Пример #1
0
        public void WhenMetadataItemIsEmpty_ThenKeySetIsEmpty()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = " "
            };

            CollectionAssert.IsEmpty(MetadataAuthorizedKeySet.FromMetadata(metadata).Keys);
        }
Пример #2
0
        public void WhenMetadataItemHasWrongKey_ThenFromMetadataThrowsArgumentException()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.LegacyMetadataKey,
                Value = " "
            };

            Assert.Throws <ArgumentException>(
                () => MetadataAuthorizedKeySet.FromMetadata(metadata));
        }
Пример #3
0
        public void WhenMetadataItemContainsJunk_ThenFromMetadataThrowsArgumentException()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = "junk junk junk "
            };

            Assert.Throws <ArgumentException>(
                () => MetadataAuthorizedKeySet.FromMetadata(metadata));
        }
Пример #4
0
        public void WhenMetadataItemContainsData_ThenKeySetIsPopulated()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = "alice:ssh-rsa key alice\n" +
                        "bob:ssh-rsa key google-ssh {\"userName\":\"[email protected]\",\"expireOn\":\"2050-01-15T15:22:35Z\"}"
            };

            var keySet = MetadataAuthorizedKeySet.FromMetadata(metadata);

            Assert.AreEqual(2, keySet.Keys.Count());
        }
        private static void MergeKeyIntoMetadata(
            Metadata metadata,
            MetadataAuthorizedKey newKey)
        {
            //
            // Merge new key into existing keyset, and take
            // the opportunity to purge expired keys.
            //
            var newKeySet = MetadataAuthorizedKeySet.FromMetadata(metadata)
                            .RemoveExpiredKeys()
                            .Add(newKey);

            metadata.Add(MetadataAuthorizedKeySet.MetadataKey, newKeySet.ToString());
        }
Пример #6
0
        public void WhenAddingNewKey_ThenAddReturnsNewSet()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = "alice:ssh-rsa key alice"
            };

            var keySet = MetadataAuthorizedKeySet.FromMetadata(metadata)
                         .Add(MetadataAuthorizedKey.Parse("bob:ssh-rsa key notalice"))
                         .Add(MetadataAuthorizedKey.Parse("bob:ssh-rsa key2 bob"));

            Assert.AreEqual(3, keySet.Keys.Count());
        }
Пример #7
0
        public void WhenAddingDuplicateKey_ThenAddReturnsThis()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = "alice:ssh-rsa key alice"
            };

            var keySet = MetadataAuthorizedKeySet.FromMetadata(metadata);

            Assert.AreSame(
                keySet,
                keySet.Add(MetadataAuthorizedKey.Parse("alice:ssh-rsa key notalice")));
        }
Пример #8
0
        public async Task WhenExistingInvalidManagedKeyFound_ThenNewKeyIsPushed()
        {
            using (var key = RsaSshKey.NewEphemeralKey())
            {
                var existingProjectKeySet = MetadataAuthorizedKeySet
                                            .FromMetadata(new Metadata())
                                            .Add(new ManagedMetadataAuthorizedKey(
                                                     "bob",
                                                     "ssh-rsa",
                                                     key.PublicKeyString,
                                                     new ManagedKeyMetadata(SampleEmailAddress, DateTime.UtcNow.AddMinutes(-5))));

                var computeEngineAdapter = CreateComputeEngineAdapterMock(
                    osLoginEnabledForProject: false,
                    osLoginEnabledForInstance: false,
                    osLogin2fa: false,
                    legacySshKeyPresent: false,
                    projectWideKeysBlockedForProject: false,
                    projectWideKeysBlockedForInstance: false,
                    existingProjectKeySet: existingProjectKeySet,
                    existingInstanceKeySet: null);
                var service = new AuthorizedKeyService(
                    CreateAuthorizationAdapterMock().Object,
                    computeEngineAdapter.Object,
                    CreateResourceManagerAdapterMock(true).Object,
                    CreateOsLoginServiceMock().Object);

                var authorizedKey = await service.AuthorizeKeyAsync(
                    SampleLocator,
                    key,
                    TimeSpan.FromMinutes(1),
                    "bob",
                    AuthorizeKeyMethods.All,
                    CancellationToken.None);

                Assert.IsNotNull(authorizedKey);
                Assert.AreEqual(AuthorizeKeyMethods.ProjectMetadata, authorizedKey.AuthorizationMethod);
                Assert.AreEqual("bob", authorizedKey.Username);

                computeEngineAdapter.Verify(a => a.UpdateMetadataAsync(
                                                It.IsAny <InstanceLocator>(),
                                                It.IsAny <Action <Metadata> >(),
                                                It.IsAny <CancellationToken>()), Times.Never);

                computeEngineAdapter.Verify(a => a.UpdateCommonInstanceMetadataAsync(
                                                It.IsAny <string>(),
                                                It.IsAny <Action <Metadata> >(),
                                                It.IsAny <CancellationToken>()), Times.Once);
            }
        }
Пример #9
0
        public void WhenMetadataContainsDifferentKeys_ThenFromMetadataThrowsArgumentException()
        {
            var metadata = new Metadata()
            {
                Items = new[]
                {
                    new Metadata.ItemsData()
                    {
                        Key = "foo"
                    }
                }
            };

            CollectionAssert.IsEmpty(MetadataAuthorizedKeySet.FromMetadata(metadata).Keys);
        }
Пример #10
0
        public void WhenKeyExpired_ThenRemoveExpiredKeysStripsKey()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = $"alice:ssh-rsa key alice\n" +
                        $"joe:ssh-rsa key2 google-ssh {{\"userName\":\"[email protected]\",\"expireOn\":\"{DateTime.UtcNow.AddMinutes(-1):O}\"}}\n" +
                        $"moe:ssh-rsa key2 google-ssh {{\"userName\":\"[email protected]\",\"expireOn\":\"{DateTime.UtcNow.AddMinutes(1):O}\"}}\n"
            };

            var keySet = MetadataAuthorizedKeySet.FromMetadata(metadata)
                         .RemoveExpiredKeys();

            Assert.AreEqual(2, keySet.Keys.Count());
            Assert.IsFalse(keySet.Keys.Any(k => k.LoginUsername == "joe"));
        }
Пример #11
0
        public void WhenMetadataItemContainsUnnecessaryWhitespace_ThenKeySetIsPopulated()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value =
                    "alice:ssh-rsa key alice\r\n" +
                    "bob:ssh-rsa key google-ssh {\"userName\":\"[email protected]\",\"expireOn\":\"2050-01-15T15:22:35Z\"}\n" +
                    "\n" +
                    " carol:ssh-rsa key carol \t\r\n" +
                    "dave:ssh-rsa key dave\r\n"
            };

            var keySet = MetadataAuthorizedKeySet.FromMetadata(metadata);

            Assert.AreEqual(4, keySet.Keys.Count());
        }
Пример #12
0
        public void WhenSetContainsEntriesWithEmptyUsername_ThenAddMaintainsEntry()
        {
            var metadata = new Metadata.ItemsData()
            {
                Key   = MetadataAuthorizedKeySet.MetadataKey,
                Value = $"alice:ssh-rsa key alice\n" +
                        $":ssh-rsa phantomkey2 phantom\n" +
                        $":ssh-rsa phantomkey3 google-ssh {{\"userName\":\"[email protected]\",\"expireOn\":\"{DateTime.UtcNow.AddMinutes(1):O}\"}}\n" +
                        $"moe:ssh-rsa key2 google-ssh {{\"userName\":\"[email protected]\",\"expireOn\":\"{DateTime.UtcNow.AddMinutes(1):O}\"}}\n"
            };

            var keySet = MetadataAuthorizedKeySet.FromMetadata(metadata)
                         .RemoveExpiredKeys()
                         .Add(MetadataAuthorizedKey.Parse("bob:ssh-rsa key2 bob"));

            Assert.AreEqual(5, keySet.Keys.Count());
            Assert.AreEqual("", keySet.Keys.First(k => k.Key == "phantomkey2").LoginUsername);
            Assert.AreEqual("", keySet.Keys.First(k => k.Key == "phantomkey3").LoginUsername);
        }
        //---------------------------------------------------------------------
        // IPublicKeyService.
        //---------------------------------------------------------------------

        public async Task <AuthorizedKey> AuthorizeKeyAsync(
            InstanceLocator instance,
            ISshKey key,
            TimeSpan validity,
            string preferredPosixUsername,
            AuthorizeKeyMethods allowedMethods,
            CancellationToken token)
        {
            Utilities.ThrowIfNull(instance, nameof(key));
            Utilities.ThrowIfNull(key, nameof(key));

            using (ApplicationTraceSources.Default.TraceMethod().WithParameters(instance))
            {
                //
                // Query metadata for instance and project in parallel.
                //
                var instanceDetailsTask = this.computeEngineAdapter.GetInstanceAsync(
                    instance,
                    token)
                                          .ConfigureAwait(false);
                var projectDetailsTask = this.computeEngineAdapter.GetProjectAsync(
                    instance.ProjectId,
                    token)
                                         .ConfigureAwait(false);

                var instanceDetails = await instanceDetailsTask;
                var projectDetails  = await projectDetailsTask;

                var osLoginEnabled = IsFlagEnabled(
                    projectDetails,
                    instanceDetails,
                    EnableOsLoginFlag);

                ApplicationTraceSources.Default.TraceVerbose(
                    "OS Login status for {0}: {1}", instance, osLoginEnabled);

                if (osLoginEnabled)
                {
                    //
                    // If OS Login is enabled, it has to be used. Any metadata keys
                    // are ignored.
                    //
                    if (!allowedMethods.HasFlag(AuthorizeKeyMethods.Oslogin))
                    {
                        throw new InvalidOperationException(
                                  $"{instance} requires OS Login to beused");
                    }

                    if (IsFlagEnabled(projectDetails, instanceDetails, EnableOsLoginMultiFactorFlag))
                    {
                        throw new NotImplementedException(
                                  "OS Login 2-factor authentication is not supported");
                    }

                    //
                    // NB. It's cheaper to unconditionally push the key than
                    // to check for previous keys first.
                    //
                    return(await this.osLoginService.AuthorizeKeyAsync(
                               instance.ProjectId,
                               OsLoginSystemType.Linux,
                               key,
                               validity,
                               token)
                           .ConfigureAwait(false));
                }
                else
                {
                    var instanceMetadata = instanceDetails.Metadata;
                    var projectMetadata  = projectDetails.CommonInstanceMetadata;

                    //
                    // Check if there is a legacy SSH key. If there is one,
                    // other keys are ignored.
                    //
                    // NB. legacy SSH keys were instance-only, so checking
                    // the instance metadata is sufficient.
                    //
                    if (IsLegacySshKeyPresent(instanceMetadata))
                    {
                        throw new UnsupportedLegacySshKeyEncounteredException(
                                  $"Connecting to the VM instance {instance.Name} is not supported " +
                                  "because the instance uses legacy SSH keys in its metadata (sshKeys)",
                                  HelpTopics.ManagingMetadataAuthorizedKeys);
                    }

                    //
                    // There is no legacy key, so we're good to push a new key.
                    //
                    // Now figure out which username to use and where to push it.
                    //
                    var blockProjectSshKeys = IsFlagEnabled(
                        projectDetails,
                        instanceDetails,
                        BlockProjectSshKeysFlag);

                    bool useInstanceKeySet;
                    if (allowedMethods.HasFlag(AuthorizeKeyMethods.ProjectMetadata) &&
                        allowedMethods.HasFlag(AuthorizeKeyMethods.InstanceMetadata))
                    {
                        //
                        // Both allowed - use project metadata unless:
                        // - project keys are blocked
                        // - we do not have the permission to update project metadata.
                        //
                        var canUpdateProjectMetadata = await this.resourceManagerAdapter
                                                       .IsGrantedPermission(
                            instance.ProjectId,
                            Permissions.ComputeProjectsSetCommonInstanceMetadata,
                            token)
                                                       .ConfigureAwait(false);

                        useInstanceKeySet = blockProjectSshKeys || !canUpdateProjectMetadata;
                    }
                    else if (allowedMethods.HasFlag(AuthorizeKeyMethods.ProjectMetadata))
                    {
                        // Only project allowed.
                        if (blockProjectSshKeys)
                        {
                            throw new InvalidOperationException(
                                      $"Project {instance.ProjectId} does not allow project-level SSH keys");
                        }
                        else
                        {
                            useInstanceKeySet = false;
                        }
                    }
                    else if (allowedMethods.HasFlag(AuthorizeKeyMethods.InstanceMetadata))
                    {
                        // Only instance allowed.
                        useInstanceKeySet = true;
                    }
                    else
                    {
                        // Neither project nor instance allowed.
                        throw new ArgumentException(nameof(allowedMethods));
                    }

                    var profile = AuthorizedKey.ForMetadata(
                        key,
                        preferredPosixUsername,
                        useInstanceKeySet,
                        this.authorizationAdapter.Authorization);
                    Debug.Assert(profile.Username != null);

                    var metadataKey = new ManagedMetadataAuthorizedKey(
                        profile.Username,
                        key.Type,
                        key.PublicKeyString,
                        new ManagedKeyMetadata(
                            this.authorizationAdapter.Authorization.Email,
                            DateTime.UtcNow.Add(validity)));

                    var existingKeySet = MetadataAuthorizedKeySet.FromMetadata(
                        useInstanceKeySet
                            ? instanceMetadata
                            : projectMetadata);

                    if (existingKeySet
                        .RemoveExpiredKeys()
                        .Contains(metadataKey))
                    {
                        //
                        // The key is there already, so we are all set.
                        //
                        ApplicationTraceSources.Default.TraceVerbose(
                            "Existing SSH key found for {0}",
                            profile.Username);
                    }
                    else
                    {
                        //
                        // Key not known yet, so we have to push it to
                        // the metadata.
                        //
                        ApplicationTraceSources.Default.TraceVerbose(
                            "Pushing new SSH key for {0}",
                            profile.Username);

                        await PushPublicKeyToMetadataAsync(
                            instance,
                            useInstanceKeySet,
                            metadataKey,
                            token)
                        .ConfigureAwait(false);
                    }

                    return(profile);
                }
            }
        }
Пример #14
0
        public void WhenMetadataIsEmpry_ThenFromMetadataThrowsArgumentException()
        {
            var metadata = new Metadata();

            CollectionAssert.IsEmpty(MetadataAuthorizedKeySet.FromMetadata(metadata).Keys);
        }
Пример #15
0
        private Mock <IComputeEngineAdapter> CreateComputeEngineAdapterMock(
            bool?osLoginEnabledForProject,
            bool?osLoginEnabledForInstance,
            bool osLogin2fa,
            bool legacySshKeyPresent,
            bool projectWideKeysBlockedForProject,
            bool projectWideKeysBlockedForInstance,
            MetadataAuthorizedKeySet existingProjectKeySet  = null,
            MetadataAuthorizedKeySet existingInstanceKeySet = null)
        {
            var projectMetadata = new Metadata();

            if (osLoginEnabledForProject.HasValue)
            {
                projectMetadata.Add("enable-oslogin",
                                    osLoginEnabledForProject.Value.ToString());
            }

            if (osLoginEnabledForProject.HasValue && osLogin2fa)
            {
                projectMetadata.Add("enable-oslogin-2fa", "true");
            }

            if (projectWideKeysBlockedForProject)
            {
                projectMetadata.Add("block-project-ssh-keys", "true");
            }

            if (existingProjectKeySet != null)
            {
                projectMetadata.Add(
                    MetadataAuthorizedKeySet.MetadataKey,
                    existingProjectKeySet.ToString());
            }

            var instanceMetadata = new Metadata();

            if (osLoginEnabledForInstance.HasValue)
            {
                instanceMetadata.Add("enable-oslogin",
                                     osLoginEnabledForInstance.Value.ToString());
            }

            if (osLoginEnabledForInstance.HasValue && osLogin2fa)
            {
                instanceMetadata.Add("enable-oslogin-2fa", "true");
            }

            if (legacySshKeyPresent)
            {
                instanceMetadata.Add("sshKeys", "somedata");
            }

            if (projectWideKeysBlockedForInstance)
            {
                instanceMetadata.Add("block-project-ssh-keys", "true");
            }

            if (existingInstanceKeySet != null)
            {
                instanceMetadata.Add(
                    MetadataAuthorizedKeySet.MetadataKey,
                    existingInstanceKeySet.ToString());
            }

            var adapter = new Mock <IComputeEngineAdapter>();

            adapter
            .Setup(a => a.GetProjectAsync(
                       It.IsAny <string>(),
                       It.IsAny <CancellationToken>()))
            .ReturnsAsync(new Project()
            {
                CommonInstanceMetadata = projectMetadata
            });
            adapter
            .Setup(a => a.GetInstanceAsync(
                       It.IsAny <InstanceLocator>(),
                       It.IsAny <CancellationToken>()))
            .ReturnsAsync(new Instance()
            {
                Metadata = instanceMetadata
            });
            return(adapter);
        }