code.gitea.io/gitea@v1.22.3/services/doctor/authorizedkeys.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package doctor
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    16  	"code.gitea.io/gitea/modules/container"
    17  	"code.gitea.io/gitea/modules/log"
    18  	"code.gitea.io/gitea/modules/setting"
    19  	asymkey_service "code.gitea.io/gitea/services/asymkey"
    20  )
    21  
    22  const tplCommentPrefix = `# gitea public key`
    23  
    24  func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) error {
    25  	if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
    26  		return nil
    27  	}
    28  
    29  	fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
    30  	f, err := os.Open(fPath)
    31  	if err != nil {
    32  		if !autofix {
    33  			logger.Critical("Unable to open authorized_keys file. ERROR: %v", err)
    34  			return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err)
    35  		}
    36  		logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err)
    37  		if err = asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
    38  			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
    39  			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
    40  		}
    41  	}
    42  	defer f.Close()
    43  
    44  	linesInAuthorizedKeys := make(container.Set[string])
    45  
    46  	scanner := bufio.NewScanner(f)
    47  	for scanner.Scan() {
    48  		line := scanner.Text()
    49  		if strings.HasPrefix(line, tplCommentPrefix) {
    50  			continue
    51  		}
    52  		linesInAuthorizedKeys.Add(line)
    53  	}
    54  	if err = scanner.Err(); err != nil {
    55  		return fmt.Errorf("scan: %w", err)
    56  	}
    57  	// although there is a "defer close" above, here close explicitly before the generating, because it needs to open the file for writing again
    58  	_ = f.Close()
    59  
    60  	// now we regenerate and check if there are any lines missing
    61  	regenerated := &bytes.Buffer{}
    62  	if err := asymkey_model.RegeneratePublicKeys(ctx, regenerated); err != nil {
    63  		logger.Critical("Unable to regenerate authorized_keys file. ERROR: %v", err)
    64  		return fmt.Errorf("Unable to regenerate authorized_keys file. ERROR: %w", err)
    65  	}
    66  	scanner = bufio.NewScanner(regenerated)
    67  	for scanner.Scan() {
    68  		line := scanner.Text()
    69  		if strings.HasPrefix(line, tplCommentPrefix) {
    70  			continue
    71  		}
    72  		if linesInAuthorizedKeys.Contains(line) {
    73  			continue
    74  		}
    75  		if !autofix {
    76  			logger.Critical(
    77  				"authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"",
    78  				fPath,
    79  				"gitea admin regenerate keys",
    80  				"gitea doctor --run authorized-keys --fix")
    81  			return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`)
    82  		}
    83  		logger.Warn("authorized_keys is out of date. Attempting rewrite...")
    84  		err = asymkey_service.RewriteAllPublicKeys(ctx)
    85  		if err != nil {
    86  			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
    87  			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  func init() {
    94  	Register(&Check{
    95  		Title:     "Check if OpenSSH authorized_keys file is up-to-date",
    96  		Name:      "authorized-keys",
    97  		IsDefault: true,
    98  		Run:       checkAuthorizedKeys,
    99  		Priority:  4,
   100  	})
   101  }