Ejemplo n.º 1
0
        //---------------------------------------------------------------------
        // IOsLoginService.
        //---------------------------------------------------------------------

        public async Task <AuthorizedKey> AuthorizeKeyAsync(
            string projectId,
            OsLoginSystemType os,
            ISshKey key,
            TimeSpan validity,
            CancellationToken token)
        {
            Utilities.ThrowIfNullOrEmpty(projectId, nameof(projectId));
            Utilities.ThrowIfNull(key, nameof(key));

            if (os != OsLoginSystemType.Linux)
            {
                throw new ArgumentException(nameof(os));
            }

            if (validity.TotalSeconds <= 0)
            {
                throw new ArgumentException(nameof(validity));
            }

            using (ApplicationTraceSources.Default.TraceMethod().WithParameters(projectId))
            {
                //
                // If OS Login is enabled for a project, we have to use
                // the Posix username from the OS Login login profile.
                //
                // Note that the Posix account managed by OS login can
                // differ based on the project that we're trying to access.
                // Therefore, make sure to specify the project when
                // importing the key.
                //
                // OS Login auto-generates a username for us. Again, this
                // username might differ based on project/organization.
                //

                //
                // Import the key for the given project.
                //

                var loginProfile = await this.adapter.ImportSshPublicKeyAsync(
                    projectId,
                    key,
                    validity,
                    token)
                                   .ConfigureAwait(false);

                //
                // Although rare, there could be multiple POSIX accounts.
                //
                var account = loginProfile.PosixAccounts
                              .EnsureNotNull()
                              .FirstOrDefault(a => a.Primary == true &&
                                              a.OperatingSystemType == "LINUX");

                if (account == null)
                {
                    //
                    // This is strange, the account should have been created.
                    //
                    throw new OsLoginSshKeyImportFailedException(
                              "Imported SSH key to OSLogin, but no POSIX account was created",
                              HelpTopics.TroubleshootingOsLogin);
                }

                return(AuthorizedKey.ForOsLoginAccount(key, account));
            }
        }
Ejemplo n.º 2
0
        //---------------------------------------------------------------------
        // 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.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);
                }
            }
        }