Skip to content

Commit

Permalink
Add command to patch a webhook or api service with ca bundle (#10)
Browse files Browse the repository at this point in the history
* Don't generate ca-public cert by default
  • Loading branch information
slshen authored Oct 5, 2020
1 parent c6ea207 commit 3e19712
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 4 deletions.
95 changes: 95 additions & 0 deletions cmd/ktls/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package main

import (
"context"
"encoding/base64"
"fmt"
"log"
"os"
Expand All @@ -28,6 +29,10 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

Expand Down Expand Up @@ -127,6 +132,95 @@ func createCommand() *cobra.Command {
return c
}

func patchCABundleCommand() *cobra.Command {
var (
resource string
resourceName string
)
c := &cobra.Command{
Use: "patch-ca-bundle",
Short: "Update the caBundle property on a webhook or api service",
RunE: func(cmd *cobra.Command, args []string) error {
if err := complete(); err != nil {
return err
}
restConfig, err := ktls.GetDefaultRESTConfig()
if err != nil {
return err
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
return err
}
gvr, err := getPatchGroupVersionResource(discoveryClient, resource)
if err != nil {
return err
}
client, err := dynamic.NewForConfig(restConfig)
if err != nil {
return err
}
var path string
switch resource {
case "mutatingwebhookconfigurations":
fallthrough
case "validatingwebhookconfigurations":
path = "/webhooks/0/clientConfig/caBundle"
case "apiservices":
path = "/spec/caBundle"
default:
return fmt.Errorf("unknown resource %s", resource)
}
ckp, err := secret.GetCertificateKeyPair()
if err != nil {
return err
}
caBundle := base64.StdEncoding.EncodeToString(ckp.GetCACertPem())
patch := fmt.Sprintf(`[{"op":"add","path":"%s","value":"%s"}]`, path, caBundle)
_, err = client.Resource(gvr).Patch(context.TODO(), resourceName, types.JSONPatchType, []byte(patch),
metav1.PatchOptions{
FieldManager: "ktls",
})
if err == nil {
log.Printf("Patched CA bundle into %s %s", gvr, resourceName)
}
return err
},
}
flags := c.Flags()
flags.StringVar(&resource, "resource", "", "The resource to patch")
flags.StringVar(&resourceName, "resource-name", "", "The name of the resource to patch")
addFlags(flags)
return c
}

func getPatchGroupVersionResource(discoveryClient discovery.DiscoveryInterface, resource string) (gvr schema.GroupVersionResource, err error) {
apiResourceLists, err := discoveryClient.ServerPreferredResources()
if err != nil {
return
}
apiResourceLists = discovery.FilteredBy(discovery.ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {
if r.Name != resource {
return false
}
for _, v := range r.Verbs {
if v == "patch" {
return true
}
}
return false
}), apiResourceLists)
if len(apiResourceLists) == 0 || len(apiResourceLists[0].APIResources) == 0 {
err = fmt.Errorf("cannot find patchable resource %s", resource)
return
}
apiResources := apiResourceLists[0]
apiResource := apiResources.APIResources[0]
gv, _ := schema.ParseGroupVersion(apiResources.GroupVersion)
gvr = gv.WithResource(apiResource.Name)
return
}

func main() {
root := cobra.Command{
Use: "ktls",
Expand All @@ -141,6 +235,7 @@ func main() {
flags.BoolVar(&quiet, "q", false, "Don't print anything")
root.AddCommand(deleteCommand())
root.AddCommand(createCommand())
root.AddCommand(patchCABundleCommand())
err := root.Execute()
if err != nil {
log.Fatal(err)
Expand Down
15 changes: 11 additions & 4 deletions ktls.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/retry"
)
Expand All @@ -43,7 +44,7 @@ type TLSSecret struct {
Name string
// The name of the CA secret, defaults to Name-ca
CAName string
// The name of the secret that will hold the public
// If non-empty, persist an opaque secret with the public
// CA certificate. This duplicates the CAName secret but
// is missing the "tls.key" entry.
CAPublicName string
Expand Down Expand Up @@ -143,9 +144,13 @@ func (t *TLSSecret) getKubeClient() (kubernetes.Interface, error) {
return t.kubeClient, nil
}

func GetDefaultKubeClient() (kubernetes.Interface, error) {
func GetDefaultRESTConfig() (*rest.Config, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}).ClientConfig()
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}).ClientConfig()
}

func GetDefaultKubeClient() (kubernetes.Interface, error) {
config, err := GetDefaultRESTConfig()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -259,7 +264,9 @@ func (t *TLSSecret) generateCert() (*CertificateKeyPair, error) {
if err == nil {
err = t.persistCert(caCert, caName, true)
if err == nil {
err = t.persistCert(caCert, defaultString(t.CAPublicName, caName+"-public"), false)
if t.CAPublicName != "" {
err = t.persistCert(caCert, t.CAPublicName, false)
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions ktls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func TestCreateDelete(t *testing.T) {
ExplicitKubeClient: k,
Name: "tls",
Namespace: "default",
CAPublicName: "tls-ca-public",
}
if err := kt.Create(); err != nil {
t.Fatal(err)
Expand Down

0 comments on commit 3e19712

Please sign in to comment.