client/internal/bootstrap/csr.go (49 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package bootstrap
import (
"crypto/ecdsa"
"crypto/elliptic"
cryptorand "crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"os"
"strings"
)
// makeKubeletClientCSR returns a valid kubelet client CSR for the bootstrapping host,
// along with the associated (ECDSA) private key.
func makeKubeletClientCSR() (csrPEM []byte, privateKey *ecdsa.PrivateKey, err error) {
hostName, err := getHostname()
if err != nil {
return nil, nil, fmt.Errorf("resolving hostname: %w", err)
}
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate ECDSA 256 private key for kubelet client CSR: %w", err)
}
template := x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"system:nodes"},
CommonName: fmt.Sprintf("system:node:%s", hostName),
},
SignatureAlgorithm: x509.ECDSAWithSHA256,
}
csrDER, err := x509.CreateCertificateRequest(cryptorand.Reader, &template, privateKey)
if err != nil {
return nil, nil, fmt.Errorf("unable to create kubelet client certificate request from template: %w", err)
}
block := &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csrDER,
}
return pem.EncodeToMemory(block), privateKey, nil
}
// Returns the canonicalized (trimmed and lowercased) hostname of the VM.
func getHostname() (string, error) {
hostName, err := os.Hostname()
if err != nil {
return "", fmt.Errorf("couldn't determine hostname: %w", err)
}
// Trim whitespaces first to avoid getting an empty hostname
// For linux, the hostname is read from file /proc/sys/kernel/hostname directly
hostName = strings.TrimSpace(hostName)
if len(hostName) == 0 {
return "", fmt.Errorf("empty hostname is invalid")
}
return strings.ToLower(hostName), nil
}