github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/repository/certificates.go (about)

     1  /*This file is part of kuberpult.
     2  
     3  Kuberpult is free software: you can redistribute it and/or modify
     4  it under the terms of the Expat(MIT) License as published by
     5  the Free Software Foundation.
     6  
     7  Kuberpult is distributed in the hope that it will be useful,
     8  but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  MIT License for more details.
    11  
    12  You should have received a copy of the MIT License
    13  along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
    14  
    15  Copyright 2023 freiheit.com*/
    16  
    17  package repository
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"context"
    23  	"crypto/sha256"
    24  	"fmt"
    25  	"os"
    26  
    27  	"github.com/freiheit-com/kuberpult/pkg/logger"
    28  	git "github.com/libgit2/git2go/v34"
    29  	"go.uber.org/zap"
    30  	"golang.org/x/crypto/ssh"
    31  )
    32  
    33  type Certificates struct {
    34  	KnownHostsFile string
    35  }
    36  
    37  func (c *Certificates) load() (*certificateStore, error) {
    38  	store := &certificateStore{
    39  		sha256Hashes: map[string][]byte{},
    40  	}
    41  	if c.KnownHostsFile != "" {
    42  		file, err := os.Open(c.KnownHostsFile)
    43  		if err != nil {
    44  			return nil, err
    45  		}
    46  		defer file.Close()
    47  		scanner := bufio.NewScanner(file)
    48  		for scanner.Scan() {
    49  			_, hosts, pubKey, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
    50  			if err != nil {
    51  				return nil, err
    52  			}
    53  			hasher := sha256.New()
    54  			hasher.Write(pubKey.Marshal())
    55  			sum := hasher.Sum(nil)
    56  			for _, h := range hosts {
    57  				store.sha256Hashes[h] = sum
    58  			}
    59  
    60  		}
    61  	}
    62  	return store, nil
    63  }
    64  
    65  type certificateStore struct {
    66  	sha256Hashes map[string][]byte
    67  }
    68  
    69  func (store *certificateStore) CertificateCheckCallback(ctx context.Context) func(cert *git.Certificate, valid bool, hostname string) error {
    70  	if store == nil {
    71  		return func(cert *git.Certificate, valid bool, hostname string) error {
    72  			return fmt.Errorf("certificates error") // should never be called
    73  		}
    74  	}
    75  	logger := logger.FromContext(ctx)
    76  	return func(cert *git.Certificate, valid bool, hostname string) error {
    77  		if cert.Kind == git.CertificateHostkey {
    78  			if hsh, ok := store.sha256Hashes[hostname]; ok {
    79  				if bytes.Equal(hsh, cert.Hostkey.HashSHA256[:]) {
    80  					return nil
    81  				} else {
    82  					logger.Error("git.ssh.hostkeyMismatch",
    83  						zap.String("hostname", hostname),
    84  						zap.String("hostkey.expected", fmt.Sprintf("%x", hsh)),
    85  						zap.String("hostkey.actual", fmt.Sprintf("%x", cert.Hostkey.HashSHA256)),
    86  					)
    87  				}
    88  			} else {
    89  				logger.Error("git.ssh.hostnameUnknown",
    90  					zap.String("hostname", hostname),
    91  				)
    92  			}
    93  		}
    94  		return fmt.Errorf("certificates error")
    95  	}
    96  }