Merge pull request #61803 from awly/client-auth-exec-tls

Automatic merge from submit-queue (batch tested with PRs 61803, 64305, 64170, 64361, 64339). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Add TLS support to exec authenticator plugin

**What this PR does / why we need it**:
https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md#tls-client-certificate-support

Allows exec plugin to return raw TLS key/cert data. This data populates
transport.Config.TLS field.
This requires a change to AuthProvider interface to expose TLS configs,
not only RoundTripper.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #61421

**Special notes for your reviewer**:

**Release note**:

```release-note
Exec authenticator plugin supports TLS client certificates.
```

Kubernetes-commit: f701b7529937493a64f2f2553aa9a5cd7020d9b7
This commit is contained in:
Kubernetes Publisher 2018-05-30 17:34:11 -07:00
commit 4444bdf630
14 changed files with 572 additions and 276 deletions

394
Godeps/Godeps.json generated
View File

@ -288,783 +288,787 @@
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/labels",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/selection",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/types",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/version",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/watch",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "b8cd30b2bea1fca0d13fff9082d62f9956ca68aa"
"Rev": "94ebb086c69b9fec4ddbfb6a1433d28ecca9292b"
},
{
"ImportPath": "k8s.io/client-go/discovery",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/discovery/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/admissionregistration",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/admissionregistration/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/admissionregistration/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/apps",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/apps/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/apps/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/apps/v1beta2",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/autoscaling",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/autoscaling/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/autoscaling/v2beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/batch",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/batch/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/batch/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/batch/v2alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/certificates",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/certificates/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/core",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/core/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/events",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/events/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/extensions",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/extensions/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/internalinterfaces",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/networking",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/networking/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/policy",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/policy/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/rbac",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/rbac/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/rbac/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/rbac/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/scheduling",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/scheduling/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/scheduling/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/settings",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/settings/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/storage",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/storage/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/storage/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/informers/storage/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/scheme",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1beta2",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/apps/v1beta2/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authentication/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authentication/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authentication/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authentication/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authorization/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authorization/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authorization/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/authorization/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/autoscaling/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/autoscaling/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/batch/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/batch/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/batch/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/batch/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/batch/v2alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/batch/v2alpha1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/certificates/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/core/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/core/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/events/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/events/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/extensions/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/extensions/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/networking/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/networking/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/policy/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/policy/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/rbac/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/rbac/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/rbac/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/rbac/v1alpha1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/rbac/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/rbac/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/settings/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/settings/v1alpha1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1alpha1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/kubernetes/typed/storage/v1beta1/fake",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/admissionregistration/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/admissionregistration/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/apps/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/apps/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/apps/v1beta2",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/autoscaling/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/autoscaling/v2beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/batch/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/batch/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/batch/v2alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/certificates/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/core/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/events/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/extensions/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/networking/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/policy/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/rbac/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/rbac/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/rbac/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/scheduling/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/scheduling/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/settings/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/storage/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/storage/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/listers/storage/v1beta1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/pkg/apis/clientauthentication",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/pkg/version",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/plugin/pkg/client/auth/exec",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/rest",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/rest/watch",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/testing",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/auth",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/cache",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/clientcmd",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/clientcmd/api",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/clientcmd/api/latest",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/clientcmd/api/v1",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/metrics",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/pager",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/record",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/tools/reference",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/transport",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/buffer",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/cert",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/connrotation",
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/flowcontrol",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/homedir",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/integer",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/retry",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/client-go/util/workqueue",
"Rev": "2f54fb8dfb4a6e42d372f0a593ec77042f602066"
"Rev": "ec14e9de0ef50401418999468bfb96cd235044f6"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",

View File

@ -75,6 +75,8 @@ func AddConversionFuncs(scheme *runtime.Scheme) error {
Convert_unversioned_LabelSelector_to_map,
Convert_Slice_string_To_Slice_int32,
Convert_Slice_string_To_v1_DeletionPropagation,
)
}
@ -304,3 +306,13 @@ func Convert_Slice_string_To_Slice_int32(in *[]string, out *[]int32, s conversio
}
return nil
}
// Convert_Slice_string_To_v1_DeletionPropagation allows converting a URL query parameter propagationPolicy
func Convert_Slice_string_To_v1_DeletionPropagation(input *[]string, out *DeletionPropagation, s conversion.Scope) error {
if len(*input) > 0 {
*out = DeletionPropagation((*input)[0])
} else {
*out = ""
}
return nil
}

View File

@ -57,7 +57,14 @@ type ExecCredentialStatus struct {
// +optional
ExpirationTimestamp *metav1.Time
// Token is a bearer token used by the client for request authentication.
// +optional
Token string
// PEM-encoded client TLS certificate.
// +optional
ClientCertificateData string
// PEM-encoded client TLS private key.
// +optional
ClientKeyData string
}
// Response defines metadata about a failed request, including HTTP status code and

View File

@ -52,12 +52,20 @@ type ExecCredentialSpec struct {
}
// ExecCredentialStatus holds credentials for the transport to use.
//
// Token and ClientKeyData are sensitive fields. This data should only be
// transmitted in-memory between client and exec plugin process. Exec plugin
// itself should at least be protected via file permissions.
type ExecCredentialStatus struct {
// ExpirationTimestamp indicates a time when the provided credentials expire.
// +optional
ExpirationTimestamp *metav1.Time `json:"expirationTimestamp,omitempty"`
// Token is a bearer token used by the client for request authentication.
Token string `json:"token,omitempty"`
// PEM-encoded client TLS certificates (including intermediates, if any).
ClientCertificateData string `json:"clientCertificateData,omitempty"`
// PEM-encoded private key for the above certificate.
ClientKeyData string `json:"clientKeyData,omitempty"`
}
// Response defines metadata about a failed request, including HTTP status code and

View File

@ -99,6 +99,8 @@ func Convert_clientauthentication_ExecCredentialSpec_To_v1alpha1_ExecCredentialS
func autoConvert_v1alpha1_ExecCredentialStatus_To_clientauthentication_ExecCredentialStatus(in *ExecCredentialStatus, out *clientauthentication.ExecCredentialStatus, s conversion.Scope) error {
out.ExpirationTimestamp = (*v1.Time)(unsafe.Pointer(in.ExpirationTimestamp))
out.Token = in.Token
out.ClientCertificateData = in.ClientCertificateData
out.ClientKeyData = in.ClientKeyData
return nil
}
@ -110,6 +112,8 @@ func Convert_v1alpha1_ExecCredentialStatus_To_clientauthentication_ExecCredentia
func autoConvert_clientauthentication_ExecCredentialStatus_To_v1alpha1_ExecCredentialStatus(in *clientauthentication.ExecCredentialStatus, out *ExecCredentialStatus, s conversion.Scope) error {
out.ExpirationTimestamp = (*v1.Time)(unsafe.Pointer(in.ExpirationTimestamp))
out.Token = in.Token
out.ClientCertificateData = in.ClientCertificateData
out.ClientKeyData = in.ClientKeyData
return nil
}

View File

@ -18,11 +18,15 @@ package exec
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"reflect"
"sync"
"time"
@ -35,6 +39,8 @@ import (
"k8s.io/client-go/pkg/apis/clientauthentication"
"k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/transport"
"k8s.io/client-go/util/connrotation"
)
const execInfoEnv = "KUBERNETES_EXEC_INFO"
@ -147,14 +153,55 @@ type Authenticator struct {
// The mutex also guards calling the plugin. Since the plugin could be
// interactive we want to make sure it's only called once.
mu sync.Mutex
cachedToken string
cachedCreds *credentials
exp time.Time
onRotate func()
}
// WrapTransport instruments an existing http.RoundTripper with credentials returned
// by the plugin.
func (a *Authenticator) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &roundTripper{a, rt}
type credentials struct {
token string
cert *tls.Certificate
}
// UpdateTransportConfig updates the transport.Config to use credentials
// returned by the plugin.
func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error {
wt := c.WrapTransport
c.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
if wt != nil {
rt = wt(rt)
}
return &roundTripper{a, rt}
}
getCert := c.TLS.GetCert
c.TLS.GetCert = func() (*tls.Certificate, error) {
// If previous GetCert is present and returns a valid non-nil
// certificate, use that. Otherwise use cert from exec plugin.
if getCert != nil {
cert, err := getCert()
if err != nil {
return nil, err
}
if cert != nil {
return cert, nil
}
}
return a.cert()
}
var dial func(ctx context.Context, network, addr string) (net.Conn, error)
if c.Dial != nil {
dial = c.Dial
} else {
dial = (&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext
}
d := connrotation.NewDialer(dial)
a.onRotate = d.CloseAll
c.Dial = d.DialContext
return nil
}
type roundTripper struct {
@ -169,11 +216,13 @@ func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return r.base.RoundTrip(req)
}
token, err := r.a.token()
creds, err := r.a.getCreds()
if err != nil {
return nil, fmt.Errorf("getting token: %v", err)
return nil, fmt.Errorf("getting credentials: %v", err)
}
if creds.token != "" {
req.Header.Set("Authorization", "Bearer "+creds.token)
}
req.Header.Set("Authorization", "Bearer "+token)
res, err := r.base.RoundTrip(req)
if err != nil {
@ -184,47 +233,60 @@ func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
Header: res.Header,
Code: int32(res.StatusCode),
}
if err := r.a.refresh(token, resp); err != nil {
glog.Errorf("refreshing token: %v", err)
if err := r.a.maybeRefreshCreds(creds, resp); err != nil {
glog.Errorf("refreshing credentials: %v", err)
}
}
return res, nil
}
func (a *Authenticator) tokenExpired() bool {
func (a *Authenticator) credsExpired() bool {
if a.exp.IsZero() {
return false
}
return a.now().After(a.exp)
}
func (a *Authenticator) token() (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.cachedToken != "" && !a.tokenExpired() {
return a.cachedToken, nil
func (a *Authenticator) cert() (*tls.Certificate, error) {
creds, err := a.getCreds()
if err != nil {
return nil, err
}
return a.getToken(nil)
return creds.cert, nil
}
// refresh executes the plugin to force a rotation of the token.
func (a *Authenticator) refresh(token string, r *clientauthentication.Response) error {
func (a *Authenticator) getCreds() (*credentials, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.cachedCreds != nil && !a.credsExpired() {
return a.cachedCreds, nil
}
if err := a.refreshCredsLocked(nil); err != nil {
return nil, err
}
return a.cachedCreds, nil
}
// maybeRefreshCreds executes the plugin to force a rotation of the
// credentials, unless they were rotated already.
func (a *Authenticator) maybeRefreshCreds(creds *credentials, r *clientauthentication.Response) error {
a.mu.Lock()
defer a.mu.Unlock()
if token != a.cachedToken {
// Token already rotated.
// Since we're not making a new pointer to a.cachedCreds in getCreds, no
// need to do deep comparison.
if creds != a.cachedCreds {
// Credentials already rotated.
return nil
}
_, err := a.getToken(r)
return err
return a.refreshCredsLocked(r)
}
// getToken executes the plugin and reads the credentials from stdout. It must be
// called while holding the Authenticator's mutex.
func (a *Authenticator) getToken(r *clientauthentication.Response) (string, error) {
// refreshCredsLocked executes the plugin and reads the credentials from
// stdout. It must be called while holding the Authenticator's mutex.
func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) error {
cred := &clientauthentication.ExecCredential{
Spec: clientauthentication.ExecCredentialSpec{
Response: r,
@ -234,7 +296,7 @@ func (a *Authenticator) getToken(r *clientauthentication.Response) (string, erro
data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred)
if err != nil {
return "", fmt.Errorf("encode ExecCredentials: %v", err)
return fmt.Errorf("encode ExecCredentials: %v", err)
}
env := append(a.environ(), a.env...)
@ -250,23 +312,26 @@ func (a *Authenticator) getToken(r *clientauthentication.Response) (string, erro
}
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("exec: %v", err)
return fmt.Errorf("exec: %v", err)
}
_, gvk, err := codecs.UniversalDecoder(a.group).Decode(stdout.Bytes(), nil, cred)
if err != nil {
return "", fmt.Errorf("decode stdout: %v", err)
return fmt.Errorf("decoding stdout: %v", err)
}
if gvk.Group != a.group.Group || gvk.Version != a.group.Version {
return "", fmt.Errorf("exec plugin is configured to use API version %s, plugin returned version %s",
return fmt.Errorf("exec plugin is configured to use API version %s, plugin returned version %s",
a.group, schema.GroupVersion{Group: gvk.Group, Version: gvk.Version})
}
if cred.Status == nil {
return "", fmt.Errorf("exec plugin didn't return a status field")
return fmt.Errorf("exec plugin didn't return a status field")
}
if cred.Status.Token == "" {
return "", fmt.Errorf("exec plugin didn't return a token")
if cred.Status.Token == "" && cred.Status.ClientCertificateData == "" && cred.Status.ClientKeyData == "" {
return fmt.Errorf("exec plugin didn't return a token or cert/key pair")
}
if (cred.Status.ClientCertificateData == "") != (cred.Status.ClientKeyData == "") {
return fmt.Errorf("exec plugin returned only certificate or key, not both")
}
if cred.Status.ExpirationTimestamp != nil {
@ -274,7 +339,24 @@ func (a *Authenticator) getToken(r *clientauthentication.Response) (string, erro
} else {
a.exp = time.Time{}
}
a.cachedToken = cred.Status.Token
return a.cachedToken, nil
newCreds := &credentials{
token: cred.Status.Token,
}
if cred.Status.ClientKeyData != "" && cred.Status.ClientCertificateData != "" {
cert, err := tls.X509KeyPair([]byte(cred.Status.ClientCertificateData), []byte(cred.Status.ClientKeyData))
if err != nil {
return fmt.Errorf("failed parsing client key/certificate: %v", err)
}
newCreds.cert = &cert
}
oldCreds := a.cachedCreds
a.cachedCreds = newCreds
// Only close all connections when TLS cert rotates. Token rotation doesn't
// need the extra noise.
if a.onRotate != nil && oldCreds != nil && !reflect.DeepEqual(oldCreds.cert, a.cachedCreds.cert) {
a.onRotate()
}
return nil
}

View File

@ -59,39 +59,10 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
// TransportConfig converts a client config to an appropriate transport config.
func (c *Config) TransportConfig() (*transport.Config, error) {
wt := c.WrapTransport
if c.ExecProvider != nil {
provider, err := exec.GetAuthenticator(c.ExecProvider)
if err != nil {
return nil, err
}
if wt != nil {
previousWT := wt
wt = func(rt http.RoundTripper) http.RoundTripper {
return provider.WrapTransport(previousWT(rt))
}
} else {
wt = provider.WrapTransport
}
}
if c.AuthProvider != nil {
provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister)
if err != nil {
return nil, err
}
if wt != nil {
previousWT := wt
wt = func(rt http.RoundTripper) http.RoundTripper {
return provider.WrapTransport(previousWT(rt))
}
} else {
wt = provider.WrapTransport
}
}
return &transport.Config{
conf := &transport.Config{
UserAgent: c.UserAgent,
Transport: c.Transport,
WrapTransport: wt,
WrapTransport: c.WrapTransport,
TLS: transport.TLSConfig{
Insecure: c.Insecure,
ServerName: c.ServerName,
@ -111,5 +82,29 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
Extra: c.Impersonate.Extra,
},
Dial: c.Dial,
}, nil
}
if c.ExecProvider != nil {
provider, err := exec.GetAuthenticator(c.ExecProvider)
if err != nil {
return nil, err
}
if err := provider.UpdateTransportConfig(conf); err != nil {
return nil, err
}
}
if c.AuthProvider != nil {
provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister)
if err != nil {
return nil, err
}
wt := conf.WrapTransport
if wt != nil {
conf.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return provider.WrapTransport(wt(rt))
}
} else {
conf.WrapTransport = provider.WrapTransport
}
}
return conf, nil
}

View File

@ -25,6 +25,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest"
)
@ -72,7 +74,6 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc {
return func(action Action) (bool, runtime.Object, error) {
ns := action.GetNamespace()
gvr := action.GetResource()
// Here and below we need to switch on implementation types,
// not on interfaces, as some interfaces are identical
// (e.g. UpdateAction and CreateAction), so if we use them,
@ -125,6 +126,34 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc {
}
return true, nil, nil
case PatchActionImpl:
obj, err := tracker.Get(gvr, ns, action.GetName())
if err != nil {
// object is not registered
return false, nil, err
}
old, err := json.Marshal(obj)
if err != nil {
return true, nil, err
}
// Only supports strategic merge patch
// TODO: Add support for other Patch types
mergedByte, err := strategicpatch.StrategicMergePatch(old, action.GetPatch(), obj)
if err != nil {
return true, nil, err
}
if err = json.Unmarshal(mergedByte, obj); err != nil {
return true, nil, err
}
if err = tracker.Update(gvr, obj, ns); err != nil {
return true, nil, err
}
return true, obj, nil
default:
return false, nil, fmt.Errorf("no reaction implemented for %s", action)
}

View File

@ -72,6 +72,9 @@ type EventRecorder interface {
// PastEventf is just like Eventf, but with an option to specify the event's 'timestamp' field.
PastEventf(object runtime.Object, timestamp metav1.Time, eventtype, reason, messageFmt string, args ...interface{})
// AnnotatedEventf is just like eventf, but with annotations attached
AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{})
}
// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log.
@ -250,7 +253,7 @@ type recorderImpl struct {
clock clock.Clock
}
func (recorder *recorderImpl) generateEvent(object runtime.Object, timestamp metav1.Time, eventtype, reason, message string) {
func (recorder *recorderImpl) generateEvent(object runtime.Object, annotations map[string]string, timestamp metav1.Time, eventtype, reason, message string) {
ref, err := ref.GetReference(recorder.scheme, object)
if err != nil {
glog.Errorf("Could not construct reference to: '%#v' due to: '%v'. Will not report event: '%v' '%v' '%v'", object, err, eventtype, reason, message)
@ -262,7 +265,7 @@ func (recorder *recorderImpl) generateEvent(object runtime.Object, timestamp met
return
}
event := recorder.makeEvent(ref, eventtype, reason, message)
event := recorder.makeEvent(ref, annotations, eventtype, reason, message)
event.Source = recorder.source
go func() {
@ -281,7 +284,7 @@ func validateEventType(eventtype string) bool {
}
func (recorder *recorderImpl) Event(object runtime.Object, eventtype, reason, message string) {
recorder.generateEvent(object, metav1.Now(), eventtype, reason, message)
recorder.generateEvent(object, nil, metav1.Now(), eventtype, reason, message)
}
func (recorder *recorderImpl) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
@ -289,10 +292,14 @@ func (recorder *recorderImpl) Eventf(object runtime.Object, eventtype, reason, m
}
func (recorder *recorderImpl) PastEventf(object runtime.Object, timestamp metav1.Time, eventtype, reason, messageFmt string, args ...interface{}) {
recorder.generateEvent(object, timestamp, eventtype, reason, fmt.Sprintf(messageFmt, args...))
recorder.generateEvent(object, nil, timestamp, eventtype, reason, fmt.Sprintf(messageFmt, args...))
}
func (recorder *recorderImpl) makeEvent(ref *v1.ObjectReference, eventtype, reason, message string) *v1.Event {
func (recorder *recorderImpl) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
recorder.generateEvent(object, annotations, metav1.Now(), eventtype, reason, fmt.Sprintf(messageFmt, args...))
}
func (recorder *recorderImpl) makeEvent(ref *v1.ObjectReference, annotations map[string]string, eventtype, reason, message string) *v1.Event {
t := metav1.Time{Time: recorder.clock.Now()}
namespace := ref.Namespace
if namespace == "" {
@ -300,8 +307,9 @@ func (recorder *recorderImpl) makeEvent(ref *v1.ObjectReference, eventtype, reas
}
return &v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
Namespace: namespace,
Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
Namespace: namespace,
Annotations: annotations,
},
InvolvedObject: *ref,
Reason: reason,

View File

@ -45,6 +45,10 @@ func (f *FakeRecorder) Eventf(object runtime.Object, eventtype, reason, messageF
func (f *FakeRecorder) PastEventf(object runtime.Object, timestamp metav1.Time, eventtype, reason, messageFmt string, args ...interface{}) {
}
func (f *FakeRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
f.Eventf(object, eventtype, reason, messageFmt, args)
}
// NewFakeRecorder creates new fake event recorder with event channel with
// buffer of given size.
func NewFakeRecorder(bufferSize int) *FakeRecorder {

View File

@ -43,6 +43,7 @@ type tlsCacheKey struct {
caData string
certData string
keyData string
getCert string
serverName string
dial string
}
@ -52,7 +53,7 @@ func (t tlsCacheKey) String() string {
if len(t.keyData) > 0 {
keyText = "<redacted>"
}
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, dial:%s", t.insecure, t.caData, t.certData, keyText, t.serverName, t.dial)
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, getCert: %s, serverName:%s, dial:%s", t.insecure, t.caData, t.certData, keyText, t.getCert, t.serverName, t.dial)
}
func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
@ -109,6 +110,7 @@ func tlsConfigKey(c *Config) (tlsCacheKey, error) {
caData: string(c.TLS.CAData),
certData: string(c.TLS.CertData),
keyData: string(c.TLS.KeyData),
getCert: fmt.Sprintf("%p", c.TLS.GetCert),
serverName: c.TLS.ServerName,
dial: fmt.Sprintf("%p", c.Dial),
}, nil

View File

@ -18,6 +18,7 @@ package transport
import (
"context"
"crypto/tls"
"net"
"net/http"
)
@ -84,7 +85,12 @@ func (c *Config) HasTokenAuth() bool {
// HasCertAuth returns whether the configuration has certificate authentication or not.
func (c *Config) HasCertAuth() bool {
return len(c.TLS.CertData) != 0 || len(c.TLS.CertFile) != 0
return (len(c.TLS.CertData) != 0 || len(c.TLS.CertFile) != 0) && (len(c.TLS.KeyData) != 0 || len(c.TLS.KeyFile) != 0)
}
// HasCertCallbacks returns whether the configuration has certificate callback or not.
func (c *Config) HasCertCallback() bool {
return c.TLS.GetCert != nil
}
// TLSConfig holds the information needed to set up a TLS transport.
@ -99,4 +105,6 @@ type TLSConfig struct {
CAData []byte // Bytes of the PEM-encoded server trusted root certificates. Supercedes CAFile.
CertData []byte // Bytes of the PEM-encoded client certificate. Supercedes CertFile.
KeyData []byte // Bytes of the PEM-encoded client key. Supercedes KeyFile.
GetCert func() (*tls.Certificate, error) // Callback that returns a TLS client certificate. CertData, CertFile, KeyData and KeyFile supercede this field.
}

View File

@ -28,7 +28,7 @@ import (
// or transport level security defined by the provided Config.
func New(config *Config) (http.RoundTripper, error) {
// Set transport level security
if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.TLS.Insecure) {
if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.HasCertCallback() || config.TLS.Insecure) {
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
}
@ -52,7 +52,7 @@ func New(config *Config) (http.RoundTripper, error) {
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
// by the provided Config. Will return nil if no transport level security is requested.
func TLSConfigFor(c *Config) (*tls.Config, error) {
if !(c.HasCA() || c.HasCertAuth() || c.TLS.Insecure || len(c.TLS.ServerName) > 0) {
if !(c.HasCA() || c.HasCertAuth() || c.HasCertCallback() || c.TLS.Insecure || len(c.TLS.ServerName) > 0) {
return nil, nil
}
if c.HasCA() && c.TLS.Insecure {
@ -75,12 +75,40 @@ func TLSConfigFor(c *Config) (*tls.Config, error) {
tlsConfig.RootCAs = rootCertPool(c.TLS.CAData)
}
var staticCert *tls.Certificate
if c.HasCertAuth() {
// If key/cert were provided, verify them before setting up
// tlsConfig.GetClientCertificate.
cert, err := tls.X509KeyPair(c.TLS.CertData, c.TLS.KeyData)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
staticCert = &cert
}
if c.HasCertAuth() || c.HasCertCallback() {
tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
// Note: static key/cert data always take precedence over cert
// callback.
if staticCert != nil {
return staticCert, nil
}
if c.HasCertCallback() {
cert, err := c.TLS.GetCert()
if err != nil {
return nil, err
}
// GetCert may return empty value, meaning no cert.
if cert != nil {
return cert, nil
}
}
// Both c.TLS.CertData/KeyData were unset and GetCert didn't return
// anything. Return an empty tls.Certificate, no client cert will
// be sent to the server.
return &tls.Certificate{}, nil
}
}
return tlsConfig, nil

View File

@ -0,0 +1,105 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package connrotation implements a connection dialer that tracks and can close
// all created connections.
//
// This is used for credential rotation of long-lived connections, when there's
// no way to re-authenticate on a live connection.
package connrotation
import (
"context"
"net"
"sync"
)
// DialFunc is a shorthand for signature of net.DialContext.
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
// Dialer opens connections through Dial and tracks them.
type Dialer struct {
dial DialFunc
mu sync.Mutex
conns map[*closableConn]struct{}
}
// NewDialer creates a new Dialer instance.
//
// If dial is not nil, it will be used to create new underlying connections.
// Otherwise net.DialContext is used.
func NewDialer(dial DialFunc) *Dialer {
return &Dialer{
dial: dial,
conns: make(map[*closableConn]struct{}),
}
}
// CloseAll forcibly closes all tracked connections.
//
// Note: new connections may get created before CloseAll returns.
func (d *Dialer) CloseAll() {
d.mu.Lock()
conns := d.conns
d.conns = make(map[*closableConn]struct{})
d.mu.Unlock()
for conn := range conns {
conn.Close()
}
}
// Dial creates a new tracked connection.
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
// DialContext creates a new tracked connection.
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := d.dial(ctx, network, address)
if err != nil {
return nil, err
}
closable := &closableConn{Conn: conn}
// Start tracking the connection
d.mu.Lock()
d.conns[closable] = struct{}{}
d.mu.Unlock()
// When the connection is closed, remove it from the map. This will
// be no-op if the connection isn't in the map, e.g. if CloseAll()
// is called.
closable.onClose = func() {
d.mu.Lock()
delete(d.conns, closable)
d.mu.Unlock()
}
return closable, nil
}
type closableConn struct {
onClose func()
net.Conn
}
func (c *closableConn) Close() error {
go c.onClose()
return c.Conn.Close()
}