istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/tools/generate_cert/main.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Provide a tool to generate X.509 certificate with different options.
    16  
    17  package main
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/x509"
    22  	"encoding/json"
    23  	"flag"
    24  	"fmt"
    25  	"log"
    26  	"os"
    27  	"os/exec"
    28  	"time"
    29  
    30  	k8s "k8s.io/api/core/v1"
    31  
    32  	"istio.io/istio/security/pkg/pki/ca"
    33  	"istio.io/istio/security/pkg/pki/util"
    34  )
    35  
    36  const (
    37  	// Layout for parsing time
    38  	timeLayout = "Jan 2 15:04:05 2006"
    39  
    40  	// modes supported by this tool.
    41  	selfSignedMode = "self-signed"
    42  	signerMode     = "signer"
    43  	citadelMode    = "citadel"
    44  )
    45  
    46  var (
    47  	host           = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for.")
    48  	validFrom      = flag.String("start-date", "", "Creation date in format of "+timeLayout)
    49  	validFor       = flag.Duration("duration", 10*365*24*time.Hour, "Duration that certificate is valid for.")
    50  	isCA           = flag.Bool("ca", false, "Whether this cert should be a Certificate Authority.")
    51  	signerCertFile = flag.String("signer-cert", "", "Signer certificate file (PEM encoded).")
    52  	signerPrivFile = flag.String("signer-priv", "", "Signer private key file (PEM encoded).")
    53  	isClient       = flag.Bool("client", false, "Whether this certificate is for a client.")
    54  	org            = flag.String("organization", "Juju org", "Organization for the cert.")
    55  	outCert        = flag.String("out-cert", "cert.pem", "Output certificate file.")
    56  	outPriv        = flag.String("out-priv", "priv.pem", "Output private key file.")
    57  	keySize        = flag.Int("key-size", 2048, "Size of the generated private key")
    58  	mode           = flag.String("mode", selfSignedMode, "Supported mode: self-signed, signer, citadel")
    59  	// Enable this flag if istio mTLS is enabled and the service is running as server side
    60  	isServer  = flag.Bool("server", false, "Whether this certificate is for a server.")
    61  	ec        = flag.String("ec-sig-alg", "", "Generate an elliptical curve private key with the specified algorithm")
    62  	curve     = flag.String("curve", "P256", "Specify the elliptic curve to use to generate an elliptical curve private key")
    63  	sanFields = flag.String("san", "", "Subject Alternative Names")
    64  )
    65  
    66  func checkCmdLine() {
    67  	flag.Parse()
    68  
    69  	hasCert, hasPriv := len(*signerCertFile) != 0, len(*signerPrivFile) != 0
    70  	switch *mode {
    71  	case selfSignedMode:
    72  		if hasCert || hasPriv {
    73  			log.Fatalf("--mode=%v is incompatible with --signer-cert or --signer-priv.", selfSignedMode)
    74  		}
    75  	case signerMode:
    76  		if !hasCert || !hasPriv {
    77  			log.Fatalf("Need --signer-cert and --signer-priv for --mode=%v.", signerMode)
    78  		}
    79  	case citadelMode:
    80  		if hasCert || hasPriv {
    81  			log.Fatalf("--mode=%v is incompatible with --signer-cert or --signer-priv.", citadelMode)
    82  		}
    83  	default:
    84  		log.Fatalf("Unsupported mode %v", *mode)
    85  	}
    86  }
    87  
    88  func saveCreds(certPem []byte, privPem []byte) {
    89  	err := os.WriteFile(*outCert, certPem, 0o644)
    90  	if err != nil {
    91  		log.Fatalf("Could not write output certificate: %s.", err)
    92  	}
    93  
    94  	err = os.WriteFile(*outPriv, privPem, 0o600)
    95  	if err != nil {
    96  		log.Fatalf("Could not write output private key: %s.", err)
    97  	}
    98  }
    99  
   100  func signCertFromCitadel() (*x509.Certificate, crypto.PrivateKey) {
   101  	args := []string{"get", "secret", "-n", "istio-system", "istio-ca-secret", "-o", "json"}
   102  	cmd := exec.Command("kubectl", args...)
   103  	out, err := cmd.CombinedOutput()
   104  	if err != nil {
   105  		log.Fatalf("Command failed error: %v\n, output\n%v\n", err, string(out))
   106  	}
   107  
   108  	var secret k8s.Secret
   109  	err = json.Unmarshal(out, &secret)
   110  	if err != nil {
   111  		log.Fatalf("Unmarshal secret error: %v", err)
   112  	}
   113  	key, err := util.ParsePemEncodedKey(secret.Data[ca.CAPrivateKeyFile])
   114  	if err != nil {
   115  		log.Fatalf("Unrecognized key format from citadel %v", err)
   116  	}
   117  	cert, err := util.ParsePemEncodedCertificate(secret.Data[ca.CACertFile])
   118  	if err != nil {
   119  		log.Fatalf("Unrecognized cert format from citadel %v", err)
   120  	}
   121  	return cert, key
   122  }
   123  
   124  func main() {
   125  	checkCmdLine()
   126  
   127  	var signerCert *x509.Certificate
   128  	var signerPriv crypto.PrivateKey
   129  	var err error
   130  	switch *mode {
   131  	case selfSignedMode:
   132  	case signerMode:
   133  		signerCert, signerPriv, err = util.LoadSignerCredsFromFiles(*signerCertFile, *signerPrivFile)
   134  		if err != nil {
   135  			log.Fatalf("Failed to load signer key cert from file: %v\n", err)
   136  		}
   137  	case citadelMode:
   138  		signerCert, signerPriv = signCertFromCitadel()
   139  	default:
   140  		log.Fatalf("Unsupported mode %v", *mode)
   141  	}
   142  
   143  	opts := util.CertOptions{
   144  		Host:         *host,
   145  		NotBefore:    getNotBefore(),
   146  		TTL:          *validFor,
   147  		SignerCert:   signerCert,
   148  		SignerPriv:   signerPriv,
   149  		Org:          *org,
   150  		IsCA:         *isCA,
   151  		IsSelfSigned: *mode == selfSignedMode,
   152  		IsClient:     *isClient,
   153  		RSAKeySize:   *keySize,
   154  		IsServer:     *isServer,
   155  		ECSigAlg:     util.SupportedECSignatureAlgorithms(*ec),
   156  		ECCCurve:     util.SupportedEllipticCurves(*curve),
   157  		DNSNames:     *sanFields,
   158  	}
   159  	certPem, privPem, err := util.GenCertKeyFromOptions(opts)
   160  	if err != nil {
   161  		log.Fatalf("Failed to generate certificate: %v\n", err)
   162  	}
   163  
   164  	saveCreds(certPem, privPem)
   165  	fmt.Printf("Certificate and private files successfully saved in %s and %s\n", *outCert, *outPriv)
   166  }
   167  
   168  func getNotBefore() time.Time {
   169  	if *validFrom == "" {
   170  		return time.Now()
   171  	}
   172  
   173  	t, err := time.Parse(timeLayout, *validFrom)
   174  	if err != nil {
   175  		log.Fatalf("Failed to parse the '-start-from' option as a time (error: %s)\n", err)
   176  	}
   177  
   178  	return t
   179  }