github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/lxd/shared.go (about)

     1  // Copyright 2023 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Licensed under the Apache 2.0 license. See github.com/lxc/lxd COPYING file for details.
     5  
     6  package lxd
     7  
     8  import (
     9  	"crypto/ecdsa"
    10  	"crypto/elliptic"
    11  	"crypto/rand"
    12  	"crypto/x509"
    13  	"crypto/x509/pkix"
    14  	"encoding/pem"
    15  	"fmt"
    16  	"math/big"
    17  	"net"
    18  	"os"
    19  	"os/user"
    20  	"time"
    21  )
    22  
    23  // The following file exists because we can no longer import the
    24  // github.com/lxc/lxd/shared package when we build with CGO_ENABLED=1. Using
    25  // CGO will then compile in some lxc/lxd C code, which causes problems when
    26  // attempting to cross-compile.
    27  
    28  // IsUnixSocket returns true if the given path is either a Unix socket
    29  // or a symbolic link pointing at a Unix socket.
    30  func IsUnixSocket(path string) bool {
    31  	stat, err := os.Stat(path)
    32  	if err != nil {
    33  		return false
    34  	}
    35  
    36  	return (stat.Mode() & os.ModeSocket) == os.ModeSocket
    37  }
    38  
    39  // GenerateMemCert creates client or server certificate and key pair,
    40  // returning them as byte arrays in memory.
    41  func GenerateMemCert(client bool, addHosts bool) ([]byte, []byte, error) {
    42  	privk, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
    43  	if err != nil {
    44  		return nil, nil, fmt.Errorf("Failed to generate key: %w", err)
    45  	}
    46  
    47  	validFrom := time.Now()
    48  	validTo := validFrom.Add(10 * 365 * 24 * time.Hour)
    49  
    50  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    51  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    52  	if err != nil {
    53  		return nil, nil, fmt.Errorf("Failed to generate serial number: %w", err)
    54  	}
    55  
    56  	userEntry, err := user.Current()
    57  	var username string
    58  	if err == nil {
    59  		username = userEntry.Username
    60  		if username == "" {
    61  			username = "UNKNOWN"
    62  		}
    63  	} else {
    64  		username = "UNKNOWN"
    65  	}
    66  
    67  	hostname, err := os.Hostname()
    68  	if err != nil {
    69  		hostname = "UNKNOWN"
    70  	}
    71  
    72  	template := x509.Certificate{
    73  		SerialNumber: serialNumber,
    74  		Subject: pkix.Name{
    75  			Organization: []string{"linuxcontainers.org"},
    76  			CommonName:   fmt.Sprintf("%s@%s", username, hostname),
    77  		},
    78  		NotBefore: validFrom,
    79  		NotAfter:  validTo,
    80  
    81  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
    82  		BasicConstraintsValid: true,
    83  	}
    84  
    85  	if client {
    86  		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
    87  	} else {
    88  		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
    89  	}
    90  
    91  	if addHosts {
    92  		hosts, err := mynames()
    93  		if err != nil {
    94  			return nil, nil, fmt.Errorf("Failed to get my hostname: %w", err)
    95  		}
    96  
    97  		for _, h := range hosts {
    98  			ip, _, err := net.ParseCIDR(h)
    99  			if err == nil {
   100  				if !ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast() {
   101  					template.IPAddresses = append(template.IPAddresses, ip)
   102  				}
   103  			} else {
   104  				template.DNSNames = append(template.DNSNames, h)
   105  			}
   106  		}
   107  	} else if !client {
   108  		template.DNSNames = []string{"unspecified"}
   109  	}
   110  
   111  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privk.PublicKey, privk)
   112  	if err != nil {
   113  		return nil, nil, fmt.Errorf("Failed to create certificate: %w", err)
   114  	}
   115  
   116  	data, err := x509.MarshalECPrivateKey(privk)
   117  	if err != nil {
   118  		return nil, nil, err
   119  	}
   120  
   121  	cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
   122  	key := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: data})
   123  
   124  	return cert, key, nil
   125  }
   126  
   127  // mynames a list of names for which the certificate will be valid.
   128  // This will include the hostname and ip address.
   129  func mynames() ([]string, error) {
   130  	h, err := os.Hostname()
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	ret := []string{h, "127.0.0.1/8", "::1/128"}
   136  	return ret, nil
   137  }