Skip to content

Instantly share code, notes, and snippets.

@mfojtik
Created April 21, 2021 17:14
Show Gist options
  • Save mfojtik/375a302682679bac15fe8673f3afa56b to your computer and use it in GitHub Desktop.
Save mfojtik/375a302682679bac15fe8673f3afa56b to your computer and use it in GitHub Desktop.
package limitedclient
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"time"
certv1 "k8s.io/api/certificates/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
certv1client "k8s.io/client-go/kubernetes/typed/certificates/v1"
"k8s.io/client-go/rest"
)
type Factory struct {
lifetimeDuration time.Duration
originalRESTConfig *rest.Config
csrClient certv1client.CertificateSigningRequestInterface
}
func New(config *rest.Config) *Factory {
kubeClient := kubernetes.NewForConfigOrDie(config)
return &Factory{
lifetimeDuration: 0,
originalRESTConfig: config,
csrClient: kubeClient.CertificatesV1().CertificateSigningRequests(),
}
}
func (f *Factory) WithLifetimeDuration(t time.Duration) *Factory {
f.lifetimeDuration = t
return f
}
func (f *Factory) Kubernetes(ctx context.Context) (kubernetes.Interface, error) {
key, cert, err := getClientKeys(ctx, f.csrClient)
if err != nil {
return nil, err
}
restConfig := *f.originalRESTConfig
restConfig.TLSClientConfig = rest.TLSClientConfig{
ServerName: f.originalRESTConfig.ServerName,
CAData: f.originalRESTConfig.CAData,
CertData: cert,
KeyData: key,
}
return kubernetes.NewForConfig(&restConfig)
}
func getClientKeys(ctx context.Context, csrClient certv1client.CertificateSigningRequestInterface) ([]byte, []byte, error) {
key, err := getPrivateKey()
if err != nil {
return nil, nil, err
}
keyBytes := x509.MarshalPKCS1PrivateKey(key)
requestBytes, err := getCertificateRequestForKey(key)
if err != nil {
return nil, nil, err
}
csr, err := csrClient.Create(ctx, &certv1.CertificateSigningRequest{
Spec: certv1.CertificateSigningRequestSpec{
Request: requestBytes,
SignerName: "kubernetes.io/kube-apiserver-client",
Usages: []certv1.KeyUsage{certv1.UsageAny},
},
}, metav1.CreateOptions{})
if err != nil {
return nil, nil, err
}
approvedCsr, err := csrClient.UpdateApproval(ctx, csr.Name, csr, metav1.UpdateOptions{})
if err != nil {
return nil, nil, err
}
return keyBytes, approvedCsr.Status.Certificate, nil
}
func getCertificateRequestForKey(key *rsa.PrivateKey) ([]byte, error) {
request := x509.CertificateRequest{
SignatureAlgorithm: x509.SHA256WithRSA,
PublicKey: key,
}
csr, err := x509.CreateCertificateRequest(rand.Reader, &request, key)
if err != nil {
return nil, err
}
csrPemBlock := &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr,
}
return pem.EncodeToMemory(csrPemBlock), nil
}
func getPrivateKey() (*rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return key, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment