Commit b889fb35 authored by Jacob Beacham's avatar Jacob Beacham

Better messaging when GKE certificate signing fails.

On errors, the GKE signing API can respond with a JSON body that contains an error message explaining the failure. If we're able to extract it, use that message when reporting the error instead of the generic error returned by the webhook library. Also, always add an event to the CSR object on signing errors.
parent 35753487
...@@ -31,6 +31,7 @@ go_library( ...@@ -31,6 +31,7 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apiserver/pkg/util/webhook", "//vendor:k8s.io/apiserver/pkg/util/webhook",
"//vendor:k8s.io/client-go/kubernetes/typed/core/v1", "//vendor:k8s.io/client-go/kubernetes/typed/core/v1",
"//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/plugin/pkg/client/auth", "//vendor:k8s.io/client-go/plugin/pkg/client/auth",
"//vendor:k8s.io/client-go/rest", "//vendor:k8s.io/client-go/rest",
"//vendor:k8s.io/client-go/tools/clientcmd", "//vendor:k8s.io/client-go/tools/clientcmd",
...@@ -56,5 +57,8 @@ go_test( ...@@ -56,5 +57,8 @@ go_test(
srcs = ["gke_signer_test.go"], srcs = ["gke_signer_test.go"],
library = ":go_default_library", library = ":go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
deps = ["//pkg/apis/certificates/v1beta1:go_default_library"], deps = [
"//pkg/apis/certificates/v1beta1:go_default_library",
"//vendor:k8s.io/client-go/tools/record",
],
) )
...@@ -22,9 +22,11 @@ import ( ...@@ -22,9 +22,11 @@ import (
"time" "time"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
clientv1 "k8s.io/client-go/pkg/api/v1"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
...@@ -63,13 +65,14 @@ func Run(s *GKECertificatesController) error { ...@@ -63,13 +65,14 @@ func Run(s *GKECertificatesController) error {
eventBroadcaster := record.NewBroadcaster() eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")})
recorder := eventBroadcaster.NewRecorder(api.Scheme, clientv1.EventSource{Component: "gke-certificates-controller"})
clientBuilder := controller.SimpleControllerClientBuilder{ClientConfig: kubeconfig} clientBuilder := controller.SimpleControllerClientBuilder{ClientConfig: kubeconfig}
client := clientBuilder.ClientOrDie("certificate-controller") client := clientBuilder.ClientOrDie("certificate-controller")
sharedInformers := informers.NewSharedInformerFactory(client, time.Duration(12)*time.Hour) sharedInformers := informers.NewSharedInformerFactory(client, time.Duration(12)*time.Hour)
signer, err := NewGKESigner(s.ClusterSigningGKEKubeconfig, s.ClusterSigningGKERetryBackoff.Duration) signer, err := NewGKESigner(s.ClusterSigningGKEKubeconfig, s.ClusterSigningGKERetryBackoff.Duration, recorder)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package app package app
import ( import (
"encoding/json"
"fmt" "fmt"
"time" "time"
...@@ -25,6 +26,7 @@ import ( ...@@ -25,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
_ "k8s.io/kubernetes/pkg/apis/certificates/install" _ "k8s.io/kubernetes/pkg/apis/certificates/install"
certificates "k8s.io/kubernetes/pkg/apis/certificates/v1beta1" certificates "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
...@@ -40,10 +42,11 @@ type GKESigner struct { ...@@ -40,10 +42,11 @@ type GKESigner struct {
webhook *webhook.GenericWebhook webhook *webhook.GenericWebhook
kubeConfigFile string kubeConfigFile string
retryBackoff time.Duration retryBackoff time.Duration
recorder record.EventRecorder
} }
// NewGKESigner will create a new instance of a GKESigner. // NewGKESigner will create a new instance of a GKESigner.
func NewGKESigner(kubeConfigFile string, retryBackoff time.Duration) (*GKESigner, error) { func NewGKESigner(kubeConfigFile string, retryBackoff time.Duration, recorder record.EventRecorder) (*GKESigner, error) {
webhook, err := webhook.NewGenericWebhook(api.Registry, api.Codecs, kubeConfigFile, groupVersions, retryBackoff) webhook, err := webhook.NewGenericWebhook(api.Registry, api.Codecs, kubeConfigFile, groupVersions, retryBackoff)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -53,6 +56,7 @@ func NewGKESigner(kubeConfigFile string, retryBackoff time.Duration) (*GKESigner ...@@ -53,6 +56,7 @@ func NewGKESigner(kubeConfigFile string, retryBackoff time.Duration) (*GKESigner
webhook: webhook, webhook: webhook,
kubeConfigFile: kubeConfigFile, kubeConfigFile: kubeConfigFile,
retryBackoff: retryBackoff, retryBackoff: retryBackoff,
recorder: recorder,
}, nil }, nil
} }
...@@ -65,6 +69,9 @@ func (s *GKESigner) Sign(csr *certificates.CertificateSigningRequest) (*certific ...@@ -65,6 +69,9 @@ func (s *GKESigner) Sign(csr *certificates.CertificateSigningRequest) (*certific
}) })
if err := result.Error(); err != nil { if err := result.Error(); err != nil {
if bodyErr := s.resultBodyError(result); bodyErr != nil {
return nil, s.webhookError(csr, bodyErr)
}
return nil, s.webhookError(csr, err) return nil, s.webhookError(csr, err)
} }
...@@ -86,5 +93,26 @@ func (s *GKESigner) Sign(csr *certificates.CertificateSigningRequest) (*certific ...@@ -86,5 +93,26 @@ func (s *GKESigner) Sign(csr *certificates.CertificateSigningRequest) (*certific
func (s *GKESigner) webhookError(csr *certificates.CertificateSigningRequest, err error) error { func (s *GKESigner) webhookError(csr *certificates.CertificateSigningRequest, err error) error {
glog.V(2).Infof("error contacting webhook backend: %s", err) glog.V(2).Infof("error contacting webhook backend: %s", err)
s.recorder.Eventf(csr, "Warning", "SigningError", "error while calling GKE: %v", err)
return err return err
} }
// signResultError represents the structured response body of a failed call to
// GKE's SignCertificate API.
type signResultError struct {
Error struct {
Code int
Message string
Status string
}
}
// resultBodyError attempts to extract an error out of a response body.
func (s *GKESigner) resultBodyError(result rest.Result) error {
body, _ := result.Raw()
var sre signResultError
if err := json.Unmarshal(body, &sre); err == nil {
return fmt.Errorf("server responded with error: %s", sre.Error.Message)
}
return nil
}
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"text/template" "text/template"
"time" "time"
"k8s.io/client-go/tools/record"
certificates "k8s.io/kubernetes/pkg/apis/certificates/v1beta1" certificates "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
) )
...@@ -103,7 +104,7 @@ func TestGKESigner(t *testing.T) { ...@@ -103,7 +104,7 @@ func TestGKESigner(t *testing.T) {
t.Fatalf("error closing kubeconfig template: %v", err) t.Fatalf("error closing kubeconfig template: %v", err)
} }
signer, err := NewGKESigner(kubeConfig.Name(), time.Duration(500)*time.Millisecond) signer, err := NewGKESigner(kubeConfig.Name(), time.Duration(500)*time.Millisecond, record.NewFakeRecorder(10))
if err != nil { if err != nil {
t.Fatalf("error creating GKESigner: %v", err) t.Fatalf("error creating GKESigner: %v", err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment