| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Provide a tool to generate X.509 certificate with different options. |
| |
| package main |
| |
| import ( |
| "crypto" |
| "crypto/x509" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "log" |
| "os" |
| "os/exec" |
| "time" |
| ) |
| |
| import ( |
| k8s "k8s.io/api/core/v1" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/security/pkg/pki/ca" |
| "github.com/apache/dubbo-go-pixiu/security/pkg/pki/util" |
| ) |
| |
| const ( |
| // Layout for parsing time |
| timeLayout = "Jan 2 15:04:05 2006" |
| |
| // modes supported by this tool. |
| selfSignedMode = "self-signed" |
| signerMode = "signer" |
| citadelMode = "citadel" |
| ) |
| |
| var ( |
| host = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for.") |
| validFrom = flag.String("start-date", "", "Creation date in format of "+timeLayout) |
| validFor = flag.Duration("duration", 10*365*24*time.Hour, "Duration that certificate is valid for.") |
| isCA = flag.Bool("ca", false, "Whether this cert should be a Certificate Authority.") |
| signerCertFile = flag.String("signer-cert", "", "Signer certificate file (PEM encoded).") |
| signerPrivFile = flag.String("signer-priv", "", "Signer private key file (PEM encoded).") |
| isClient = flag.Bool("client", false, "Whether this certificate is for a client.") |
| org = flag.String("organization", "Juju org", "Organization for the cert.") |
| outCert = flag.String("out-cert", "cert.pem", "Output certificate file.") |
| outPriv = flag.String("out-priv", "priv.pem", "Output private key file.") |
| keySize = flag.Int("key-size", 2048, "Size of the generated private key") |
| mode = flag.String("mode", selfSignedMode, "Supported mode: self-signed, signer, citadel") |
| // Enable this flag if istio mTLS is enabled and the service is running as server side |
| isServer = flag.Bool("server", false, "Whether this certificate is for a server.") |
| ec = flag.String("ec-sig-alg", "", "Generate an elliptical curve private key with the specified algorithm") |
| sanFields = flag.String("san", "", "Subject Alternative Names") |
| ) |
| |
| func checkCmdLine() { |
| flag.Parse() |
| |
| hasCert, hasPriv := len(*signerCertFile) != 0, len(*signerPrivFile) != 0 |
| switch *mode { |
| case selfSignedMode: |
| if hasCert || hasPriv { |
| log.Fatalf("--mode=%v is incompatible with --signer-cert or --signer-priv.", selfSignedMode) |
| } |
| case signerMode: |
| if !hasCert || !hasPriv { |
| log.Fatalf("Need --signer-cert and --signer-priv for --mode=%v.", signerMode) |
| } |
| case citadelMode: |
| if hasCert || hasPriv { |
| log.Fatalf("--mode=%v is incompatible with --signer-cert or --signer-priv.", citadelMode) |
| } |
| default: |
| log.Fatalf("Unsupported mode %v", *mode) |
| } |
| } |
| |
| func saveCreds(certPem []byte, privPem []byte) { |
| err := os.WriteFile(*outCert, certPem, 0o644) |
| if err != nil { |
| log.Fatalf("Could not write output certificate: %s.", err) |
| } |
| |
| err = os.WriteFile(*outPriv, privPem, 0o600) |
| if err != nil { |
| log.Fatalf("Could not write output private key: %s.", err) |
| } |
| } |
| |
| func signCertFromCitadel() (*x509.Certificate, crypto.PrivateKey) { |
| args := []string{"get", "secret", "-n", "dubbo-system", "istio-ca-secret", "-o", "json"} |
| cmd := exec.Command("kubectl", args...) |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| log.Fatalf("Command failed error: %v\n, output\n%v\n", err, string(out)) |
| } |
| |
| var secret k8s.Secret |
| err = json.Unmarshal(out, &secret) |
| if err != nil { |
| log.Fatalf("Unmarshal secret error: %v", err) |
| } |
| key, err := util.ParsePemEncodedKey(secret.Data[ca.CAPrivateKeyFile]) |
| if err != nil { |
| log.Fatalf("Unrecognized key format from citadel %v", err) |
| } |
| cert, err := util.ParsePemEncodedCertificate(secret.Data[ca.CACertFile]) |
| if err != nil { |
| log.Fatalf("Unrecognized cert format from citadel %v", err) |
| } |
| return cert, key |
| } |
| |
| func main() { |
| checkCmdLine() |
| |
| var signerCert *x509.Certificate |
| var signerPriv crypto.PrivateKey |
| var err error |
| switch *mode { |
| case selfSignedMode: |
| case signerMode: |
| signerCert, signerPriv, err = util.LoadSignerCredsFromFiles(*signerCertFile, *signerPrivFile) |
| if err != nil { |
| log.Fatalf("Failed to load signer key cert from file: %v\n", err) |
| } |
| case citadelMode: |
| signerCert, signerPriv = signCertFromCitadel() |
| default: |
| log.Fatalf("Unsupported mode %v", *mode) |
| } |
| |
| opts := util.CertOptions{ |
| Host: *host, |
| NotBefore: getNotBefore(), |
| TTL: *validFor, |
| SignerCert: signerCert, |
| SignerPriv: signerPriv, |
| Org: *org, |
| IsCA: *isCA, |
| IsSelfSigned: *mode == selfSignedMode, |
| IsClient: *isClient, |
| RSAKeySize: *keySize, |
| IsServer: *isServer, |
| ECSigAlg: util.SupportedECSignatureAlgorithms(*ec), |
| DNSNames: *sanFields, |
| } |
| certPem, privPem, err := util.GenCertKeyFromOptions(opts) |
| if err != nil { |
| log.Fatalf("Failed to generate certificate: %v\n", err) |
| } |
| |
| saveCreds(certPem, privPem) |
| fmt.Printf("Certificate and private files successfully saved in %s and %s\n", *outCert, *outPriv) |
| } |
| |
| func getNotBefore() time.Time { |
| if *validFrom == "" { |
| return time.Now() |
| } |
| |
| t, err := time.Parse(timeLayout, *validFrom) |
| if err != nil { |
| log.Fatalf("Failed to parse the '-start-from' option as a time (error: %s)\n", err) |
| } |
| |
| return t |
| } |