mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-05-06 09:16:59 +00:00
173 lines
5.0 KiB
Go
173 lines
5.0 KiB
Go
package clientcert
|
|
|
|
import (
|
|
"crypto/x509/pkix"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
certificatesv1 "k8s.io/api/certificates/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
restclient "k8s.io/client-go/rest"
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
certutil "k8s.io/client-go/util/cert"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
// HasValidClientCertificate checks if there exists a valid client certificate in the given secret
|
|
// Returns true if all the conditions below are met:
|
|
// 1. KubeconfigFile exists when hasKubeconfig is true
|
|
// 2. TLSKeyFile exists
|
|
// 3. TLSCertFile exists and the certificate is not expired
|
|
// 4. If subject is specified, it matches the subject in the certificate stored in TLSCertFile
|
|
func HasValidHubKubeconfig(secret *corev1.Secret, subject *pkix.Name) bool {
|
|
if len(secret.Data) == 0 {
|
|
klog.V(4).Infof("No data found in secret %q", secret.Namespace+"/"+secret.Name)
|
|
return false
|
|
}
|
|
|
|
if _, ok := secret.Data[KubeconfigFile]; !ok {
|
|
klog.V(4).Infof("No %q found in secret %q", KubeconfigFile, secret.Namespace+"/"+secret.Name)
|
|
return false
|
|
}
|
|
|
|
if _, ok := secret.Data[TLSKeyFile]; !ok {
|
|
klog.V(4).Infof("No %q found in secret %q", TLSKeyFile, secret.Namespace+"/"+secret.Name)
|
|
return false
|
|
}
|
|
|
|
certData, ok := secret.Data[TLSCertFile]
|
|
if !ok {
|
|
klog.V(4).Infof("No %q found in secret %q", TLSCertFile, secret.Namespace+"/"+secret.Name)
|
|
return false
|
|
}
|
|
|
|
valid, err := IsCertificateValid(certData, subject)
|
|
if err != nil {
|
|
klog.V(4).Infof("Unable to validate certificate in secret %s: %v", secret.Namespace+"/"+secret.Name, err)
|
|
return false
|
|
}
|
|
|
|
return valid
|
|
}
|
|
|
|
// IsCertificateValid return true if
|
|
// 1) All certs in client certificate are not expired.
|
|
// 2) At least one cert matches the given subject if specified
|
|
func IsCertificateValid(certData []byte, subject *pkix.Name) (bool, error) {
|
|
certs, err := certutil.ParseCertsPEM(certData)
|
|
if err != nil {
|
|
return false, errors.New("unable to parse certificate")
|
|
}
|
|
|
|
if len(certs) == 0 {
|
|
return false, errors.New("No cert found in certificate")
|
|
}
|
|
|
|
now := time.Now()
|
|
// make sure no cert in the certificate chain expired
|
|
for _, cert := range certs {
|
|
if now.After(cert.NotAfter) {
|
|
klog.V(4).Infof("Part of the certificate is expired: %v", cert.NotAfter)
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
if subject == nil {
|
|
return true, nil
|
|
}
|
|
|
|
// check subject of certificates
|
|
for _, cert := range certs {
|
|
if cert.Subject.CommonName != subject.CommonName {
|
|
continue
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
klog.V(4).Infof("Certificate is not issued for subject (cn=%s)", subject.CommonName)
|
|
return false, nil
|
|
}
|
|
|
|
// getCertValidityPeriod returns the validity period of the client certificate in the secret
|
|
func getCertValidityPeriod(secret *corev1.Secret) (*time.Time, *time.Time, error) {
|
|
if secret.Data == nil {
|
|
return nil, nil, fmt.Errorf("no client certificate found in secret %q", secret.Namespace+"/"+secret.Name)
|
|
}
|
|
|
|
certData, ok := secret.Data[TLSCertFile]
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("no client certificate found in secret %q", secret.Namespace+"/"+secret.Name)
|
|
}
|
|
|
|
certs, err := certutil.ParseCertsPEM(certData)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to parse TLS certificates: %w", err)
|
|
}
|
|
|
|
if len(certs) == 0 {
|
|
return nil, nil, errors.New("No cert found in certificate")
|
|
}
|
|
|
|
// find out the validity period for all certs in the certificate chain
|
|
var notBefore, notAfter *time.Time
|
|
for index, cert := range certs {
|
|
if index == 0 {
|
|
notBefore = &cert.NotBefore
|
|
notAfter = &cert.NotAfter
|
|
continue
|
|
}
|
|
|
|
if notBefore.Before(cert.NotBefore) {
|
|
notBefore = &cert.NotBefore
|
|
}
|
|
|
|
if notAfter.After(cert.NotAfter) {
|
|
notAfter = &cert.NotAfter
|
|
}
|
|
}
|
|
|
|
return notBefore, notAfter, nil
|
|
}
|
|
|
|
// BuildKubeconfig builds a kubeconfig based on a rest config template with a cert/key pair
|
|
func BuildKubeconfig(clientConfig *restclient.Config, certPath, keyPath string) clientcmdapi.Config {
|
|
// Build kubeconfig.
|
|
kubeconfig := clientcmdapi.Config{
|
|
// Define a cluster stanza based on the bootstrap kubeconfig.
|
|
Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": {
|
|
Server: clientConfig.Host,
|
|
InsecureSkipTLSVerify: false,
|
|
CertificateAuthorityData: clientConfig.CAData,
|
|
}},
|
|
// Define auth based on the obtained client cert.
|
|
AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": {
|
|
ClientCertificate: certPath,
|
|
ClientKey: keyPath,
|
|
}},
|
|
// Define a context that connects the auth info and cluster, and set it as the default
|
|
Contexts: map[string]*clientcmdapi.Context{"default-context": {
|
|
Cluster: "default-cluster",
|
|
AuthInfo: "default-auth",
|
|
Namespace: "configuration",
|
|
}},
|
|
CurrentContext: "default-context",
|
|
}
|
|
|
|
return kubeconfig
|
|
}
|
|
|
|
// isCSRApproved returns true if the given csr has been approved
|
|
func isCSRApproved(csr *certificatesv1.CertificateSigningRequest) bool {
|
|
approved := false
|
|
for _, condition := range csr.Status.Conditions {
|
|
if condition.Type == certificatesv1.CertificateDenied {
|
|
return false
|
|
} else if condition.Type == certificatesv1.CertificateApproved {
|
|
approved = true
|
|
}
|
|
}
|
|
|
|
return approved
|
|
}
|