public void WhenMetadataItemIsEmpty_ThenKeySetIsEmpty() { var metadata = new Metadata.ItemsData() { Key = MetadataAuthorizedKeySet.MetadataKey, Value = " " }; CollectionAssert.IsEmpty(MetadataAuthorizedKeySet.FromMetadata(metadata).Keys); }
public void WhenMetadataItemHasWrongKey_ThenFromMetadataThrowsArgumentException() { var metadata = new Metadata.ItemsData() { Key = MetadataAuthorizedKeySet.LegacyMetadataKey, Value = " " }; Assert.Throws <ArgumentException>( () => MetadataAuthorizedKeySet.FromMetadata(metadata)); }
public void WhenMetadataItemContainsJunk_ThenFromMetadataThrowsArgumentException() { var metadata = new Metadata.ItemsData() { Key = MetadataAuthorizedKeySet.MetadataKey, Value = "junk junk junk " }; Assert.Throws <ArgumentException>( () => MetadataAuthorizedKeySet.FromMetadata(metadata)); }
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()); }
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()); }
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"))); }
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); } }
public void WhenMetadataContainsDifferentKeys_ThenFromMetadataThrowsArgumentException() { var metadata = new Metadata() { Items = new[] { new Metadata.ItemsData() { Key = "foo" } } }; CollectionAssert.IsEmpty(MetadataAuthorizedKeySet.FromMetadata(metadata).Keys); }
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")); }
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()); }
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); } } }
public void WhenMetadataIsEmpry_ThenFromMetadataThrowsArgumentException() { var metadata = new Metadata(); CollectionAssert.IsEmpty(MetadataAuthorizedKeySet.FromMetadata(metadata).Keys); }
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); }