From b4f3bb0082fd33651f9f89335ed7b03e7226eea7 Mon Sep 17 00:00:00 2001 From: shaj13 Date: Fri, 14 Aug 2020 12:35:41 -0700 Subject: [PATCH] feat: support kubernetes token review --- auth/strategies/kubernetes/example_test.go | 41 ++++++ auth/strategies/kubernetes/kubernetes.go | 134 ++++++++++++++++++ auth/strategies/kubernetes/kubernetes_test.go | 101 +++++++++++++ auth/strategies/kubernetes/options.go | 75 ++++++++++ auth/strategies/kubernetes/options_test.go | 69 +++++++++ .../kubernetes/testdata/error_meta_status | 1 + .../kubernetes/testdata/error_token_review | 1 + .../kubernetes/testdata/invalid_token_review | 1 + .../testdata/unauthorized_token_review | 1 + .../kubernetes/testdata/user_token_review | 1 + go.mod | 2 + go.sum | 104 ++++++++++++++ 12 files changed, 531 insertions(+) create mode 100644 auth/strategies/kubernetes/example_test.go create mode 100644 auth/strategies/kubernetes/kubernetes.go create mode 100644 auth/strategies/kubernetes/kubernetes_test.go create mode 100644 auth/strategies/kubernetes/options.go create mode 100644 auth/strategies/kubernetes/options_test.go create mode 100644 auth/strategies/kubernetes/testdata/error_meta_status create mode 100644 auth/strategies/kubernetes/testdata/error_token_review create mode 100644 auth/strategies/kubernetes/testdata/invalid_token_review create mode 100644 auth/strategies/kubernetes/testdata/unauthorized_token_review create mode 100644 auth/strategies/kubernetes/testdata/user_token_review diff --git a/auth/strategies/kubernetes/example_test.go b/auth/strategies/kubernetes/example_test.go new file mode 100644 index 0000000..ee2c031 --- /dev/null +++ b/auth/strategies/kubernetes/example_test.go @@ -0,0 +1,41 @@ +package kubernetes + +import ( + "fmt" + "net/http" + + "github.com/shaj13/go-guardian/auth/strategies/token" + "github.com/shaj13/go-guardian/store" +) + +func ExampleNew() { + cache := store.New(2) + kube := New(cache) + r, _ := http.NewRequest("", "/", nil) + _, err := kube.Authenticate(r.Context(), r) + fmt.Println(err != nil) + // Output: + // true +} + +func ExampleGetAuthenticateFunc() { + cache := store.New(2) + fn := GetAuthenticateFunc() + kube := token.New(fn, cache) + r, _ := http.NewRequest("", "/", nil) + _, err := kube.Authenticate(r.Context(), r) + fmt.Println(err != nil) + // Output: + // true +} + +func Example() { + st := SetServiceAccountToken("Service Account Token") + cache := store.New(2) + kube := New(cache, st) + r, _ := http.NewRequest("", "/", nil) + _, err := kube.Authenticate(r.Context(), r) + fmt.Println(err != nil) + // Output: + // true +} diff --git a/auth/strategies/kubernetes/kubernetes.go b/auth/strategies/kubernetes/kubernetes.go new file mode 100644 index 0000000..f7468c1 --- /dev/null +++ b/auth/strategies/kubernetes/kubernetes.go @@ -0,0 +1,134 @@ +// Package kubernetes provide auth strategy to authenticate, +// incoming HTTP requests using a Kubernetes Service Account Token. +// This authentication strategy makes it easy to introduce apps, +// into a Kubernetes Pod and make Pod authenticate Pod. +package kubernetes + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + kubeauth "k8s.io/api/authentication/v1" + kubemeta "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/shaj13/go-guardian/auth" + "github.com/shaj13/go-guardian/auth/strategies/token" + "github.com/shaj13/go-guardian/store" +) + +type kubeReview struct { + addr string + // service account token + token string + apiVersion string + audiences []string + client *http.Client +} + +func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token string) (auth.Info, error) { + tr := &kubeauth.TokenReview{ + Spec: kubeauth.TokenReviewSpec{ + Token: token, + Audiences: k.audiences, + }, + } + + body, err := json.Marshal(tr) + if err != nil { + return nil, fmt.Errorf( + "strategies/kubernetes: Failed to Marshal TokenReview Err: %s", + err, + ) + } + + url := k.addr + "/apis/" + k.apiVersion + "/tokenreviews" + + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+k.token) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + resp, err := k.client.Do(req) + if err != nil { + return nil, err + } + + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + // verify the response is not an kubernetes status error. + status := &kubemeta.Status{} + err = json.Unmarshal(body, status) + if err == nil && status.Status != kubemeta.StatusSuccess { + return nil, fmt.Errorf("strategies/kubernetes: %s", status.Message) + } + + tr = &kubeauth.TokenReview{} + err = json.Unmarshal(body, tr) + if err != nil { + return nil, fmt.Errorf( + "strategies/kubernetes: Failed to Unmarshal Response body to TokenReview Err: %s", + err, + ) + } + + if len(tr.Status.Error) > 0 { + return nil, fmt.Errorf("strategies/kubernetes: %s", tr.Status.Error) + } + + if !tr.Status.Authenticated { + return nil, fmt.Errorf("strategies/kubernetes: Token Unauthorized") + } + + user := tr.Status.User + extensions := make(map[string][]string) + for k, v := range user.Extra { + extensions[k] = v + } + + return auth.NewUserInfo(user.Username, user.UID, user.Groups, extensions), nil +} + +// GetAuthenticateFunc return function to authenticate request using kubernetes token review. +// The returned function typically used with the token strategy. +func GetAuthenticateFunc(opts ...auth.Option) token.AuthenticateFunc { + return newKubeReview(opts...).authenticate +} + +// New return strategy authenticate request using kubernetes token review. +// New is similar to token.New(). +func New(c store.Cache, opts ...auth.Option) auth.Strategy { + fn := GetAuthenticateFunc(opts...) + return token.New(fn, c, opts...) +} + +func newKubeReview(opts ...auth.Option) *kubeReview { + kr := &kubeReview{ + addr: "http://127.0.0.1:6443", + apiVersion: "authentication.k8s.io/v1", + client: &http.Client{ + Transport: &http.Transport{}, + }, + } + + for _, opt := range opts { + opt.Apply(kr) + } + + kr.addr = strings.TrimSuffix(kr.addr, "/") + kr.apiVersion = strings.TrimPrefix(strings.TrimSuffix(kr.apiVersion, "/"), "/") + return kr +} diff --git a/auth/strategies/kubernetes/kubernetes_test.go b/auth/strategies/kubernetes/kubernetes_test.go new file mode 100644 index 0000000..45b41fc --- /dev/null +++ b/auth/strategies/kubernetes/kubernetes_test.go @@ -0,0 +1,101 @@ +//nolint: lll +package kubernetes + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/shaj13/go-guardian/auth" +) + +func TestNewKubeReview(t *testing.T) { + // Round #1 -- check default + kr := newKubeReview() + assert.NotNil(t, kr.client) + assert.NotNil(t, kr.client.Transport) + assert.Equal(t, kr.apiVersion, "authentication.k8s.io/v1") + assert.Equal(t, kr.addr, "http://127.0.0.1:6443") + + // Round #2 -- apply opt and trim "/" + ver := SetAPIVersion("/test/v1/") + addr := SetAddress("http://127.0.0.1:8080/") + kr = newKubeReview(ver, addr) + assert.Equal(t, kr.apiVersion, "test/v1") + assert.Equal(t, kr.addr, "http://127.0.0.1:8080") +} + +func TestKubeReview(t *testing.T) { + table := []struct { + name string + code int + file string + err error + info auth.Info + }{ + { + name: "it return error when server return error status", + code: 200, + file: "error_meta_status", + err: fmt.Errorf("strategies/kubernetes: Kube API Error"), + }, + { + name: "it return error when server return invalid token review", + code: 200, + file: "invalid_token_review", + err: fmt.Errorf(`strategies/kubernetes: Failed to Unmarshal Response body to TokenReview Err: invalid character 'i' looking for beginning of value`), + }, + { + name: "it return error when server return Status.Error", + code: 200, + file: "error_token_review", + err: fmt.Errorf("strategies/kubernetes: Failed to authenticate token"), + }, + { + name: "it return error when server return Status.Authenticated false", + code: 200, + file: "unauthorized_token_review", + err: fmt.Errorf("strategies/kubernetes: Token Unauthorized"), + }, + { + name: "it return user info", + code: 200, + file: "user_token_review", + info: auth.NewUserInfo("test", "1", nil, map[string][]string{"ext": {"1"}}), + }, + } + + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + srv := mockKubeAPIServer(t, tt.file, tt.code) + kr := &kubeReview{ + addr: srv.URL, + client: srv.Client(), + } + r, _ := http.NewRequest("", "", nil) + info, err := kr.authenticate(r.Context(), r, "") + + assert.Equal(t, tt.err, err) + assert.Equal(t, tt.info, info) + }) + } +} + +func mockKubeAPIServer(tb testing.TB, file string, code int) *httptest.Server { + body, err := ioutil.ReadFile("./testdata/" + file) + + if err != nil { + tb.Fatalf("Failed to read testdata file Err: %s", err) + } + + h := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(code) + w.Write(body) + } + + return httptest.NewServer(http.HandlerFunc(h)) +} diff --git a/auth/strategies/kubernetes/options.go b/auth/strategies/kubernetes/options.go new file mode 100644 index 0000000..e17ec01 --- /dev/null +++ b/auth/strategies/kubernetes/options.go @@ -0,0 +1,75 @@ +package kubernetes + +import ( + "crypto/tls" + "net/http" + + "github.com/shaj13/go-guardian/auth" +) + +// SetServiceAccountToken sets kubernetes service account token +// for token review API. +func SetServiceAccountToken(token string) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.token = token + } + }) +} + +// SetHTTPClient sets underlying http client. +func SetHTTPClient(c *http.Client) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.client = c + } + }) +} + +// SetTLSConfig sets tls config for kubernetes api. +func SetTLSConfig(tls *tls.Config) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.client.Transport.(*http.Transport).TLSClientConfig = tls + } + }) +} + +// SetClientTransport sets underlying http client transport. +func SetClientTransport(rt http.RoundTripper) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.client.Transport = rt + } + }) +} + +// SetAddress sets kuberntess api server address +// e.g http://host:port or https://host:port. +func SetAddress(addr string) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.addr = addr + } + }) +} + +// SetAPIVersion sets kuberntess api version. +// e.g authentication.k8s.io/v1 +func SetAPIVersion(version string) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.apiVersion = version + } + }) +} + +// SetAudiences sets the list of the identifiers that the resource server presented +// with the token identifies as. +func SetAudiences(auds []string) auth.Option { + return auth.OptionFunc(func(v interface{}) { + if k, ok := v.(*kubeReview); ok { + k.audiences = auds + } + }) +} diff --git a/auth/strategies/kubernetes/options_test.go b/auth/strategies/kubernetes/options_test.go new file mode 100644 index 0000000..7c025fe --- /dev/null +++ b/auth/strategies/kubernetes/options_test.go @@ -0,0 +1,69 @@ +package kubernetes + +import ( + "crypto/tls" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetServiceAccountToken(t *testing.T) { + token := "test-token" + kr := new(kubeReview) + opt := SetServiceAccountToken(token) + opt.Apply(kr) + assert.Equal(t, token, kr.token) +} + +func TestSetHTTPClient(t *testing.T) { + client := new(http.Client) + kr := new(kubeReview) + opt := SetHTTPClient(client) + opt.Apply(kr) + assert.Equal(t, client, kr.client) +} + +func TestSetTLSConfig(t *testing.T) { + kr := new(kubeReview) + tls := new(tls.Config) + kr.client = &http.Client{ + Transport: &http.Transport{}, + } + opt := SetTLSConfig(tls) + opt.Apply(kr) + assert.Equal(t, tls, kr.client.Transport.(*http.Transport).TLSClientConfig) +} + +func TestSetClientTransport(t *testing.T) { + kr := new(kubeReview) + trp := new(http.Transport) + kr.client = new(http.Client) + opt := SetClientTransport(trp) + opt.Apply(kr) + assert.Equal(t, trp, kr.client.Transport) +} + +func TestSetAddress(t *testing.T) { + addr := "http://127.0.0.1:8080" + kr := new(kubeReview) + opt := SetAddress(addr) + opt.Apply(kr) + assert.Equal(t, addr, kr.addr) +} + +func TestSetAPIVersion(t *testing.T) { + ver := "authentication.k8s.io/v1" + kr := new(kubeReview) + opt := SetAPIVersion(ver) + opt.Apply(kr) + assert.Equal(t, ver, kr.apiVersion) +} + +func TestSetAudiences(t *testing.T) { + aud := []string{"admin", "guest"} + kr := new(kubeReview) + opt := SetAudiences(aud) + opt.Apply(kr) + assert.Equal(t, aud, kr.audiences) +} diff --git a/auth/strategies/kubernetes/testdata/error_meta_status b/auth/strategies/kubernetes/testdata/error_meta_status new file mode 100644 index 0000000..411925a --- /dev/null +++ b/auth/strategies/kubernetes/testdata/error_meta_status @@ -0,0 +1 @@ +{"metadata":{},"status":"Failure","message":"Kube API Error"} \ No newline at end of file diff --git a/auth/strategies/kubernetes/testdata/error_token_review b/auth/strategies/kubernetes/testdata/error_token_review new file mode 100644 index 0000000..82925fa --- /dev/null +++ b/auth/strategies/kubernetes/testdata/error_token_review @@ -0,0 +1 @@ +{"metadata":{"creationTimestamp":null},"spec":{},"status":{"user":{},"error":"Failed to authenticate token"}} \ No newline at end of file diff --git a/auth/strategies/kubernetes/testdata/invalid_token_review b/auth/strategies/kubernetes/testdata/invalid_token_review new file mode 100644 index 0000000..9852e7d --- /dev/null +++ b/auth/strategies/kubernetes/testdata/invalid_token_review @@ -0,0 +1 @@ +invalid token review josn \ No newline at end of file diff --git a/auth/strategies/kubernetes/testdata/unauthorized_token_review b/auth/strategies/kubernetes/testdata/unauthorized_token_review new file mode 100644 index 0000000..c978474 --- /dev/null +++ b/auth/strategies/kubernetes/testdata/unauthorized_token_review @@ -0,0 +1 @@ +{"metadata":{"creationTimestamp":null},"spec":{},"status":{"authenticated":false,"user":{}}} \ No newline at end of file diff --git a/auth/strategies/kubernetes/testdata/user_token_review b/auth/strategies/kubernetes/testdata/user_token_review new file mode 100644 index 0000000..ebc47ff --- /dev/null +++ b/auth/strategies/kubernetes/testdata/user_token_review @@ -0,0 +1 @@ +{"metadata":{"creationTimestamp":null},"spec":{},"status":{"authenticated":true,"user":{"username":"test","uid":"1","extra":{"ext":["1"]}}}} \ No newline at end of file diff --git a/go.mod b/go.mod index d73c12d..01b17d9 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,6 @@ go 1.13 require ( github.com/stretchr/testify v1.5.1 gopkg.in/ldap.v3 v3.1.0 + k8s.io/api v0.18.8 + k8s.io/apimachinery v0.18.8 ) diff --git a/go.sum b/go.sum index 3035525..c7c50df 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,120 @@ +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE= gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4= +k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= +k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=