code.gitea.io/gitea@v1.19.3/modules/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  )
    20  
    21  const tplCommentPrefix = `# gitea public key`
    22  
    23  func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) error {
    24  	if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
    25  		return nil
    26  	}
    27  
    28  	fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
    29  	f, err := os.Open(fPath)
    30  	if err != nil {
    31  		if !autofix {
    32  			logger.Critical("Unable to open authorized_keys file. ERROR: %v", err)
    33  			return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err)
    34  		}
    35  		logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err)
    36  		if err = asymkey_model.RewriteAllPublicKeys(); err != nil {
    37  			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
    38  			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
    39  		}
    40  	}
    41  	defer f.Close()
    42  
    43  	linesInAuthorizedKeys := make(container.Set[string])
    44  
    45  	scanner := bufio.NewScanner(f)
    46  	for scanner.Scan() {
    47  		line := scanner.Text()
    48  		if strings.HasPrefix(line, tplCommentPrefix) {
    49  			continue
    50  		}
    51  		linesInAuthorizedKeys.Add(line)
    52  	}
    53  	f.Close()
    54  
    55  	// now we regenerate and check if there are any lines missing
    56  	regenerated := &bytes.Buffer{}
    57  	if err := asymkey_model.RegeneratePublicKeys(ctx, regenerated); err != nil {
    58  		logger.Critical("Unable to regenerate authorized_keys file. ERROR: %v", err)
    59  		return fmt.Errorf("Unable to regenerate authorized_keys file. ERROR: %w", err)
    60  	}
    61  	scanner = bufio.NewScanner(regenerated)
    62  	for scanner.Scan() {
    63  		line := scanner.Text()
    64  		if strings.HasPrefix(line, tplCommentPrefix) {
    65  			continue
    66  		}
    67  		if linesInAuthorizedKeys.Contains(line) {
    68  			continue
    69  		}
    70  		if !autofix {
    71  			logger.Critical(
    72  				"authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"",
    73  				fPath,
    74  				"gitea admin regenerate keys",
    75  				"gitea doctor --run authorized-keys --fix")
    76  			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"`)
    77  		}
    78  		logger.Warn("authorized_keys is out of date. Attempting rewrite...")
    79  		err = asymkey_model.RewriteAllPublicKeys()
    80  		if err != nil {
    81  			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
    82  			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  func init() {
    89  	Register(&Check{
    90  		Title:     "Check if OpenSSH authorized_keys file is up-to-date",
    91  		Name:      "authorized-keys",
    92  		IsDefault: true,
    93  		Run:       checkAuthorizedKeys,
    94  		Priority:  4,
    95  	})
    96  }