private AuthorizedKey( ISshKey key, AuthorizeKeyMethods method, string posixUsername) { Debug.Assert(IsValidUsername(posixUsername)); Debug.Assert(method.IsSingleFlag()); this.Key = key; this.AuthorizationMethod = method; this.Username = posixUsername; }
//--------------------------------------------------------------------- // 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); } } }