mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-07 00:17:13 +00:00
- use self-signed certificate to build TLS webhook server;
- remove CSR related code
This commit is contained in:
parent
47525f9923
commit
52ef3f42f6
2 changed files with 178 additions and 139 deletions
|
@ -1,16 +1,15 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kyverno/kyverno/pkg/config"
|
"github.com/kyverno/kyverno/pkg/config"
|
||||||
tls "github.com/kyverno/kyverno/pkg/tls"
|
tls "github.com/kyverno/kyverno/pkg/tls"
|
||||||
certificates "k8s.io/api/certificates/v1beta1"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +25,8 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.T
|
||||||
tlsPair := c.ReadTlsPair(certProps)
|
tlsPair := c.ReadTlsPair(certProps)
|
||||||
if tls.IsTLSPairShouldBeUpdated(tlsPair) {
|
if tls.IsTLSPairShouldBeUpdated(tlsPair) {
|
||||||
logger.Info("Generating new key/certificate pair for TLS")
|
logger.Info("Generating new key/certificate pair for TLS")
|
||||||
tlsPair, err = c.generateTLSPemPair(certProps, fqdncn)
|
// tlsPair, err = c.generateTLSPemPair(certProps, fqdncn)
|
||||||
|
tlsPair, err = c.buildTLSPemPair(certProps, fqdncn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -39,107 +39,18 @@ func (c *Client) InitTLSPemPair(configuration *rest.Config, fqdncn bool) (*tls.T
|
||||||
return tlsPair, nil
|
return tlsPair, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//generateTlsPemPair Issues TLS certificate for webhook server using given PEM private key
|
//buildTLSPemPair Issues TLS certificate for webhook server using self-signed CA cert
|
||||||
// Returns signed and approved TLS certificate in PEM format
|
// Returns signed and approved TLS certificate in PEM format
|
||||||
func (c *Client) generateTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) {
|
func (c *Client) buildTLSPemPair(props tls.TlsCertificateProps, fqdncn bool) (*tls.TlsPemPair, error) {
|
||||||
privateKey, err := tls.TLSGeneratePrivateKey()
|
caCert, caPEM, err := tls.GenerateCACert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
certRequest, err := tls.CertificateGenerateRequest(privateKey, props, fqdncn)
|
if err := c.WriteCACert(caPEM, props); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to create certificate request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certRequest, err = c.submitAndApproveCertificateRequest(certRequest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to submit and approve certificate request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCert, err := c.fetchCertificateFromRequest(certRequest, 10)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to configure a certificate for the Kyverno controller. A CA certificate is required to allow the Kubernetes API Server to communicate with Kyverno. You can either provide a certificate or configure your cluster to allow certificate signing. Please refer to https://github.com/kyverno/kyverno/installation.md.: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tls.TlsPemPair{
|
|
||||||
Certificate: tlsCert,
|
|
||||||
PrivateKey: tls.TLSPrivateKeyToPem(privateKey),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submits and approves certificate request, returns request which need to be fetched
|
|
||||||
func (c *Client) submitAndApproveCertificateRequest(req *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) {
|
|
||||||
logger := c.log.WithName("submitAndApproveCertificateRequest")
|
|
||||||
certClient, err := c.GetCSRInterface()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
csrList, err := c.ListResource("", CSRs, "", nil)
|
return tls.GenerateCertPEM(caCert, props, fqdncn)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to list existing certificate requests: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, csr := range csrList.Items {
|
|
||||||
if csr.GetName() == req.ObjectMeta.Name {
|
|
||||||
err := c.DeleteResource("", CSRs, "", csr.GetName(), false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to delete existing certificate request: %v", err)
|
|
||||||
}
|
|
||||||
logger.Info("Old certificate request is deleted")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unstrRes, err := c.CreateResource("", CSRs, "", req, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logger.Info("Certificate request created", "name", unstrRes.GetName())
|
|
||||||
|
|
||||||
res, err := convertToCSR(unstrRes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res.Status.Conditions = append(res.Status.Conditions, certificates.CertificateSigningRequestCondition{
|
|
||||||
Type: certificates.CertificateApproved,
|
|
||||||
Reason: "NKP-Approve",
|
|
||||||
Message: "This CSR was approved by Nirmata kyverno controller",
|
|
||||||
})
|
|
||||||
res, err = certClient.UpdateApproval(res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to approve certificate request: %v", err)
|
|
||||||
}
|
|
||||||
logger.Info("Certificate request is approved", "name", res.ObjectMeta.Name)
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetches certificate from given request. Tries to obtain certificate for maxWaitSeconds
|
|
||||||
func (c *Client) fetchCertificateFromRequest(req *certificates.CertificateSigningRequest, maxWaitSeconds uint8) ([]byte, error) {
|
|
||||||
// TODO: react of SIGINT and SIGTERM
|
|
||||||
timeStart := time.Now()
|
|
||||||
for time.Since(timeStart) < time.Duration(maxWaitSeconds)*time.Second {
|
|
||||||
unstrR, err := c.GetResource("", CSRs, "", req.ObjectMeta.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r, err := convertToCSR(unstrR)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Status.Certificate != nil {
|
|
||||||
return r.Status.Certificate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, condition := range r.Status.Conditions {
|
|
||||||
if condition.Type == certificates.CertificateDenied {
|
|
||||||
return nil, errors.New(condition.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Cerificate fetch timeout is reached: %d seconds", maxWaitSeconds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ReadRootCASecret returns the RootCA from the pre-defined secret
|
//ReadRootCASecret returns the RootCA from the pre-defined secret
|
||||||
|
@ -213,6 +124,56 @@ func (c *Client) ReadTlsPair(props tls.TlsCertificateProps) *tls.TlsPemPair {
|
||||||
return &pemPair
|
return &pemPair
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) WriteCACert(caPEM *tls.TlsPemPair, props tls.TlsCertificateProps) error {
|
||||||
|
logger := c.log.WithName("CAcert")
|
||||||
|
name := generateRootCASecretName(props)
|
||||||
|
|
||||||
|
secretUnstr, err := c.GetResource("", Secrets, props.Namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
secret := &v1.Secret{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Secret",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: props.Namespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
selfSignedAnnotation: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
rootCAKey: caPEM.Certificate,
|
||||||
|
},
|
||||||
|
Type: v1.SecretTypeOpaque,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.CreateResource("", Secrets, props.Namespace, secret, false)
|
||||||
|
if err == nil {
|
||||||
|
logger.Info("secret created", "name", name, "namespace", props.Namespace)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// secret := v1.Secret{}
|
||||||
|
if _, ok := secretUnstr.GetAnnotations()[selfSignedAnnotation]; !ok {
|
||||||
|
secretUnstr.SetAnnotations(map[string]string{selfSignedAnnotation: "true"})
|
||||||
|
}
|
||||||
|
|
||||||
|
dataMap := map[string]interface{}{
|
||||||
|
rootCAKey: base64.StdEncoding.EncodeToString(caPEM.Certificate)}
|
||||||
|
|
||||||
|
if err := unstructured.SetNestedMap(secretUnstr.Object, dataMap, "data"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.UpdateResource("", Secrets, props.Namespace, secretUnstr, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("secret updated", "name", name, "namespace", props.Namespace)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//WriteTlsPair Writes the pair of TLS certificate and key to the specified secret.
|
//WriteTlsPair Writes the pair of TLS certificate and key to the specified secret.
|
||||||
// Updates existing secret or creates new one.
|
// Updates existing secret or creates new one.
|
||||||
func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPemPair) error {
|
func (c *Client) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPemPair) error {
|
||||||
|
|
156
pkg/tls/tls.go
156
pkg/tls/tls.go
|
@ -1,19 +1,21 @@
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
certificates "k8s.io/api/certificates/v1beta1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const certValidityDuration = 10 * 365 * 24 * time.Hour
|
||||||
|
|
||||||
//TlsCertificateProps Properties of TLS certificate which should be issued for webhook server
|
//TlsCertificateProps Properties of TLS certificate which should be issued for webhook server
|
||||||
type TlsCertificateProps struct {
|
type TlsCertificateProps struct {
|
||||||
Service string
|
Service string
|
||||||
|
@ -27,6 +29,11 @@ type TlsPemPair struct {
|
||||||
PrivateKey []byte
|
PrivateKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyPair struct {
|
||||||
|
Cert *x509.Certificate
|
||||||
|
Key *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
//TLSGeneratePrivateKey Generates RSA private key
|
//TLSGeneratePrivateKey Generates RSA private key
|
||||||
func TLSGeneratePrivateKey() (*rsa.PrivateKey, error) {
|
func TLSGeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||||
return rsa.GenerateKey(rand.Reader, 2048)
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
@ -42,29 +49,92 @@ func TLSPrivateKeyToPem(rsaKey *rsa.PrivateKey) []byte {
|
||||||
return pem.EncodeToMemory(privateKey)
|
return pem.EncodeToMemory(privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TlsCertificateRequestToPem Creates PEM block from raw certificate request
|
func pemEncode(certificateDER []byte, key *rsa.PrivateKey) ([]byte, []byte, error) {
|
||||||
func certificateRequestToPem(csrRaw []byte) []byte {
|
certBuf := &bytes.Buffer{}
|
||||||
csrBlock := &pem.Block{
|
if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}); err != nil {
|
||||||
Type: "CERTIFICATE REQUEST",
|
return nil, nil, fmt.Errorf("encoding cert: %v", err)
|
||||||
Bytes: csrRaw,
|
}
|
||||||
|
keyBuf := &bytes.Buffer{}
|
||||||
|
if err := pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("encoding key: %v", err)
|
||||||
|
}
|
||||||
|
return certBuf.Bytes(), keyBuf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return pem.EncodeToMemory(csrBlock)
|
func TLSCertificateToPem(certificateDER []byte) []byte {
|
||||||
|
certificate := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certificateDER,
|
||||||
}
|
}
|
||||||
|
|
||||||
//CertificateGenerateRequest Generates raw certificate signing request
|
return pem.EncodeToMemory(certificate)
|
||||||
func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificateProps, fqdncn bool) (*certificates.CertificateSigningRequest, error) {
|
}
|
||||||
|
|
||||||
|
// GenerateCACert creates the self-signed CA cert and private key
|
||||||
|
// it will be used to sign the webhook server certificate
|
||||||
|
func GenerateCACert() (*KeyPair, *TlsPemPair, error) {
|
||||||
|
now := time.Now()
|
||||||
|
begin := now.Add(-1 * time.Hour)
|
||||||
|
end := now.Add(certValidityDuration)
|
||||||
|
templ := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(0),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "*.kyverno.svc",
|
||||||
|
},
|
||||||
|
NotBefore: begin,
|
||||||
|
NotAfter: end,
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
}
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error generating key: %v", err)
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error creating certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pemPair := &TlsPemPair{
|
||||||
|
Certificate: TLSCertificateToPem(der),
|
||||||
|
PrivateKey: TLSPrivateKeyToPem(key),
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(der)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error parsing certificate %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
caCert := &KeyPair{
|
||||||
|
Cert: cert,
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
return caCert, pemPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCertPEM takes the results of GenerateCACert and uses it to create the
|
||||||
|
// PEM-encoded public certificate and private key, respectively
|
||||||
|
func GenerateCertPEM(caCert *KeyPair, props TlsCertificateProps, fqdncn bool) (*TlsPemPair, error) {
|
||||||
|
now := time.Now()
|
||||||
|
begin := now.Add(-1 * time.Hour)
|
||||||
|
end := now.Add(certValidityDuration)
|
||||||
|
|
||||||
dnsNames := make([]string, 3)
|
dnsNames := make([]string, 3)
|
||||||
dnsNames[0] = props.Service
|
dnsNames[0] = fmt.Sprintf("%s", props.Service)
|
||||||
dnsNames[1] = props.Service + "." + props.Namespace
|
csCommonName := dnsNames[0]
|
||||||
|
|
||||||
|
dnsNames[1] = fmt.Sprintf("%s.%s", props.Service, props.Namespace)
|
||||||
// The full service name is the CommonName for the certificate
|
// The full service name is the CommonName for the certificate
|
||||||
commonName := GenerateInClusterServiceName(props)
|
commonName := GenerateInClusterServiceName(props)
|
||||||
dnsNames[2] = commonName
|
dnsNames[2] = fmt.Sprintf("%s", commonName)
|
||||||
csCommonName := props.Service
|
|
||||||
if fqdncn {
|
if fqdncn {
|
||||||
// use FQDN as CommonName as a workaournd for https://github.com/kyverno/kyverno/issues/542
|
// use FQDN as CommonName as a workaournd for https://github.com/kyverno/kyverno/issues/542
|
||||||
csCommonName = commonName
|
csCommonName = commonName
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []net.IP
|
var ips []net.IP
|
||||||
apiServerIP := net.ParseIP(props.ApiServerHost)
|
apiServerIP := net.ParseIP(props.ApiServerHost)
|
||||||
if apiServerIP != nil {
|
if apiServerIP != nil {
|
||||||
|
@ -73,39 +143,47 @@ func CertificateGenerateRequest(privateKey *rsa.PrivateKey, props TlsCertificate
|
||||||
dnsNames = append(dnsNames, props.ApiServerHost)
|
dnsNames = append(dnsNames, props.ApiServerHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
csrTemplate := x509.CertificateRequest{
|
templ := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: csCommonName,
|
CommonName: csCommonName,
|
||||||
},
|
},
|
||||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
|
||||||
DNSNames: dnsNames,
|
DNSNames: dnsNames,
|
||||||
IPAddresses: ips,
|
// IPAddresses: ips,
|
||||||
|
NotBefore: begin,
|
||||||
|
NotAfter: end,
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privateKey)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error generating key for webhook %v", err)
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, templ, caCert.Cert, key.Public(), caCert.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating certificate for webhook %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &certificates.CertificateSigningRequest{
|
pemPair := &TlsPemPair{
|
||||||
TypeMeta: metav1.TypeMeta{
|
Certificate: TLSCertificateToPem(der),
|
||||||
APIVersion: "certificates.k8s.io/v1beta1",
|
PrivateKey: TLSPrivateKeyToPem(key),
|
||||||
Kind: "CertificateSigningRequest",
|
}
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
// certPEM := TLSCertificateToPem(der)
|
||||||
Name: props.Service + "." + props.Namespace + ".cert-request",
|
// keyPEM := TLSPrivateKeyToPem(key)
|
||||||
},
|
return pemPair, nil
|
||||||
Spec: certificates.CertificateSigningRequestSpec{
|
}
|
||||||
Request: certificateRequestToPem(csrBytes),
|
|
||||||
Groups: []string{"system:masters", "system:authenticated"},
|
//TlsCertificateRequestToPem Creates PEM block from raw certificate request
|
||||||
Usages: []certificates.KeyUsage{
|
func certificateRequestToPem(csrRaw []byte) []byte {
|
||||||
certificates.UsageDigitalSignature,
|
csrBlock := &pem.Block{
|
||||||
certificates.UsageKeyEncipherment,
|
Type: "CERTIFICATE REQUEST",
|
||||||
certificates.UsageServerAuth,
|
Bytes: csrRaw,
|
||||||
certificates.UsageClientAuth,
|
}
|
||||||
},
|
|
||||||
},
|
return pem.EncodeToMemory(csrBlock)
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//GenerateInClusterServiceName The generated service name should be the common name for TLS certificate
|
//GenerateInClusterServiceName The generated service name should be the common name for TLS certificate
|
||||||
|
|
Loading…
Add table
Reference in a new issue