blob: c25f2559d8910159ad7d267f81306326df0795b6 [file] [log] [blame]
// 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
}