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 }