mirror of
https://github.com/kubernetes/sample-controller.git
synced 2026-05-19 00:00:14 +08:00
Merge remote-tracking branch 'origin/master' into release-1.11
Kubernetes-commit: 4e3b2843df571c3b80c834d7c23bc6da1a22aab8
This commit is contained in:
Generated
+229
-225
File diff suppressed because it is too large
Load Diff
+1127
-883
File diff suppressed because it is too large
Load Diff
+33
@@ -3986,6 +3986,32 @@ message ServiceAccountList {
|
||||
repeated ServiceAccount items = 2;
|
||||
}
|
||||
|
||||
// ServiceAccountTokenProjection represents a projected service account token
|
||||
// volume. This projection can be used to insert a service account token into
|
||||
// the pods runtime filesystem for use against APIs (Kubernetes API Server or
|
||||
// otherwise).
|
||||
message ServiceAccountTokenProjection {
|
||||
// Audience is the intended audience of the token. A recipient of a token
|
||||
// must identify itself with an identifier specified in the audience of the
|
||||
// token, and otherwise should reject the token. The audience defaults to the
|
||||
// identifier of the apiserver.
|
||||
// +optional
|
||||
optional string audience = 1;
|
||||
|
||||
// ExpirationSeconds is the requested duration of validity of the service
|
||||
// account token. As the token approaches expiration, the kubelet volume
|
||||
// plugin will proactively rotate the service account token. The kubelet will
|
||||
// start trying to rotate the token if the token is older than 80 percent of
|
||||
// its time to live or if the token is older than 24 hours.Defaults to 1 hour
|
||||
// and must be at least 10 minutes.
|
||||
// +optional
|
||||
optional int64 expirationSeconds = 2;
|
||||
|
||||
// Path is the path relative to the mount point of the file to project the
|
||||
// token into.
|
||||
optional string path = 3;
|
||||
}
|
||||
|
||||
// ServiceList holds a list of services.
|
||||
message ServiceList {
|
||||
// Standard list metadata.
|
||||
@@ -4372,13 +4398,20 @@ message VolumeNodeAffinity {
|
||||
// Projection that may be projected along with other supported volume types
|
||||
message VolumeProjection {
|
||||
// information about the secret data to project
|
||||
// +optional
|
||||
optional SecretProjection secret = 1;
|
||||
|
||||
// information about the downwardAPI data to project
|
||||
// +optional
|
||||
optional DownwardAPIProjection downwardAPI = 2;
|
||||
|
||||
// information about the configMap data to project
|
||||
// +optional
|
||||
optional ConfigMapProjection configMap = 3;
|
||||
|
||||
// information about the serviceAccountToken data to project
|
||||
// +optional
|
||||
optional ServiceAccountTokenProjection serviceAccountToken = 4;
|
||||
}
|
||||
|
||||
// Represents the source of a volume to mount.
|
||||
|
||||
+30
@@ -1466,6 +1466,30 @@ type ConfigMapProjection struct {
|
||||
Optional *bool `json:"optional,omitempty" protobuf:"varint,4,opt,name=optional"`
|
||||
}
|
||||
|
||||
// ServiceAccountTokenProjection represents a projected service account token
|
||||
// volume. This projection can be used to insert a service account token into
|
||||
// the pods runtime filesystem for use against APIs (Kubernetes API Server or
|
||||
// otherwise).
|
||||
type ServiceAccountTokenProjection struct {
|
||||
// Audience is the intended audience of the token. A recipient of a token
|
||||
// must identify itself with an identifier specified in the audience of the
|
||||
// token, and otherwise should reject the token. The audience defaults to the
|
||||
// identifier of the apiserver.
|
||||
//+optional
|
||||
Audience string `json:"audience,omitempty" protobuf:"bytes,1,rep,name=audience"`
|
||||
// ExpirationSeconds is the requested duration of validity of the service
|
||||
// account token. As the token approaches expiration, the kubelet volume
|
||||
// plugin will proactively rotate the service account token. The kubelet will
|
||||
// start trying to rotate the token if the token is older than 80 percent of
|
||||
// its time to live or if the token is older than 24 hours.Defaults to 1 hour
|
||||
// and must be at least 10 minutes.
|
||||
//+optional
|
||||
ExpirationSeconds *int64 `json:"expirationSeconds,omitempty" protobuf:"varint,2,opt,name=expirationSeconds"`
|
||||
// Path is the path relative to the mount point of the file to project the
|
||||
// token into.
|
||||
Path string `json:"path" protobuf:"bytes,3,opt,name=path"`
|
||||
}
|
||||
|
||||
// Represents a projected volume source
|
||||
type ProjectedVolumeSource struct {
|
||||
// list of volume projections
|
||||
@@ -1484,11 +1508,17 @@ type VolumeProjection struct {
|
||||
// all types below are the supported types for projection into the same volume
|
||||
|
||||
// information about the secret data to project
|
||||
// +optional
|
||||
Secret *SecretProjection `json:"secret,omitempty" protobuf:"bytes,1,opt,name=secret"`
|
||||
// information about the downwardAPI data to project
|
||||
// +optional
|
||||
DownwardAPI *DownwardAPIProjection `json:"downwardAPI,omitempty" protobuf:"bytes,2,opt,name=downwardAPI"`
|
||||
// information about the configMap data to project
|
||||
// +optional
|
||||
ConfigMap *ConfigMapProjection `json:"configMap,omitempty" protobuf:"bytes,3,opt,name=configMap"`
|
||||
// information about the serviceAccountToken data to project
|
||||
// +optional
|
||||
ServiceAccountToken *ServiceAccountTokenProjection `json:"serviceAccountToken,omitempty" protobuf:"bytes,4,opt,name=serviceAccountToken"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
+16
-4
@@ -1988,6 +1988,17 @@ func (ServiceAccountList) SwaggerDoc() map[string]string {
|
||||
return map_ServiceAccountList
|
||||
}
|
||||
|
||||
var map_ServiceAccountTokenProjection = map[string]string{
|
||||
"": "ServiceAccountTokenProjection represents a projected service account token volume. This projection can be used to insert a service account token into the pods runtime filesystem for use against APIs (Kubernetes API Server or otherwise).",
|
||||
"audience": "Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver.",
|
||||
"expirationSeconds": "ExpirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes.",
|
||||
"path": "Path is the path relative to the mount point of the file to project the token into.",
|
||||
}
|
||||
|
||||
func (ServiceAccountTokenProjection) SwaggerDoc() map[string]string {
|
||||
return map_ServiceAccountTokenProjection
|
||||
}
|
||||
|
||||
var map_ServiceList = map[string]string{
|
||||
"": "ServiceList holds a list of services.",
|
||||
"metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
|
||||
@@ -2172,10 +2183,11 @@ func (VolumeNodeAffinity) SwaggerDoc() map[string]string {
|
||||
}
|
||||
|
||||
var map_VolumeProjection = map[string]string{
|
||||
"": "Projection that may be projected along with other supported volume types",
|
||||
"secret": "information about the secret data to project",
|
||||
"downwardAPI": "information about the downwardAPI data to project",
|
||||
"configMap": "information about the configMap data to project",
|
||||
"": "Projection that may be projected along with other supported volume types",
|
||||
"secret": "information about the secret data to project",
|
||||
"downwardAPI": "information about the downwardAPI data to project",
|
||||
"configMap": "information about the configMap data to project",
|
||||
"serviceAccountToken": "information about the serviceAccountToken data to project",
|
||||
}
|
||||
|
||||
func (VolumeProjection) SwaggerDoc() map[string]string {
|
||||
|
||||
+34
@@ -5159,6 +5159,31 @@ func (in *ServiceAccountList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceAccountTokenProjection) DeepCopyInto(out *ServiceAccountTokenProjection) {
|
||||
*out = *in
|
||||
if in.ExpirationSeconds != nil {
|
||||
in, out := &in.ExpirationSeconds, &out.ExpirationSeconds
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountTokenProjection.
|
||||
func (in *ServiceAccountTokenProjection) DeepCopy() *ServiceAccountTokenProjection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceAccountTokenProjection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceList) DeepCopyInto(out *ServiceList) {
|
||||
*out = *in
|
||||
@@ -5568,6 +5593,15 @@ func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) {
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
if in.ServiceAccountToken != nil {
|
||||
in, out := &in.ServiceAccountToken, &out.ServiceAccountToken
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(ServiceAccountTokenProjection)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -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
|
||||
}
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ message APIGroup {
|
||||
// The server returns only those CIDRs that it thinks that the client can match.
|
||||
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
|
||||
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
|
||||
// +optional
|
||||
repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 4;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -799,7 +799,8 @@ type APIGroup struct {
|
||||
// The server returns only those CIDRs that it thinks that the client can match.
|
||||
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
|
||||
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
|
||||
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
|
||||
// +optional
|
||||
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
|
||||
}
|
||||
|
||||
// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.
|
||||
|
||||
-42
@@ -18,7 +18,6 @@ package unstructured
|
||||
|
||||
import (
|
||||
gojson "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -450,44 +449,3 @@ func (c JSONFallbackEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UnstructuredObjectConverter is an ObjectConverter for use with
|
||||
// Unstructured objects. Since it has no schema or type information,
|
||||
// it will only succeed for no-op conversions. This is provided as a
|
||||
// sane implementation for APIs that require an object converter.
|
||||
type UnstructuredObjectConverter struct{}
|
||||
|
||||
func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error {
|
||||
unstructIn, ok := in.(*Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("input type %T in not valid for unstructured conversion", in)
|
||||
}
|
||||
|
||||
unstructOut, ok := out.(*Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("output type %T in not valid for unstructured conversion", out)
|
||||
}
|
||||
|
||||
// maybe deep copy the map? It is documented in the
|
||||
// ObjectConverter interface that this function is not
|
||||
// guaranteed to not mutate the input. Or maybe set the input
|
||||
// object to nil.
|
||||
unstructOut.Object = unstructIn.Object
|
||||
return nil
|
||||
}
|
||||
|
||||
func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
|
||||
if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() {
|
||||
gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind})
|
||||
if !ok {
|
||||
// TODO: should this be a typed error?
|
||||
return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target)
|
||||
}
|
||||
in.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (UnstructuredObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
|
||||
return "", "", errors.New("unstructured cannot convert field labels")
|
||||
}
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
|
||||
|
||||
for _, gvk := range gvks {
|
||||
internalGV := gvk.GroupVersion()
|
||||
internalGV.Version = "__internal" // this is hacky and maybe should be passed in
|
||||
internalGV.Version = APIVersionInternal // this is hacky and maybe should be passed in
|
||||
internalGVK := internalGV.WithKind(gvk.Kind)
|
||||
|
||||
if internalType, exists := s.gvkToType[internalGVK]; exists {
|
||||
|
||||
+17
-11
@@ -19,6 +19,7 @@ package versioning
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
@@ -170,17 +171,22 @@ func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
|
||||
case *runtime.Unknown:
|
||||
return c.encoder.Encode(obj, w)
|
||||
case runtime.Unstructured:
|
||||
// avoid conversion roundtrip if GVK is the right one already or is empty (yes, this is a hack, but the old behaviour we rely on in kubectl)
|
||||
objGVK := obj.GetObjectKind().GroupVersionKind()
|
||||
if len(objGVK.Version) == 0 {
|
||||
return c.encoder.Encode(obj, w)
|
||||
}
|
||||
targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK})
|
||||
if !ok {
|
||||
return runtime.NewNotRegisteredGVKErrForTarget(objGVK, c.encodeVersion)
|
||||
}
|
||||
if targetGVK == objGVK {
|
||||
return c.encoder.Encode(obj, w)
|
||||
// An unstructured list can contain objects of multiple group version kinds. don't short-circuit just
|
||||
// because the top-level type matches our desired destination type. actually send the object to the converter
|
||||
// to give it a chance to convert the list items if needed.
|
||||
if _, ok := obj.(*unstructured.UnstructuredList); !ok {
|
||||
// avoid conversion roundtrip if GVK is the right one already or is empty (yes, this is a hack, but the old behaviour we rely on in kubectl)
|
||||
objGVK := obj.GetObjectKind().GroupVersionKind()
|
||||
if len(objGVK.Version) == 0 {
|
||||
return c.encoder.Encode(obj, w)
|
||||
}
|
||||
targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK})
|
||||
if !ok {
|
||||
return runtime.NewNotRegisteredGVKErrForTarget(objGVK, c.encodeVersion)
|
||||
}
|
||||
if targetGVK == objGVK {
|
||||
return c.encoder.Encode(obj, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -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
|
||||
|
||||
+8
@@ -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
|
||||
|
||||
Generated
Vendored
+4
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+118
-36
@@ -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
|
||||
}
|
||||
|
||||
+27
-32
@@ -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
|
||||
}
|
||||
|
||||
+30
-1
@@ -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)
|
||||
}
|
||||
|
||||
+15
-7
@@ -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,
|
||||
|
||||
+4
@@ -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 {
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+9
-1
@@ -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.
|
||||
}
|
||||
|
||||
+31
-3
@@ -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
|
||||
|
||||
+105
@@ -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()
|
||||
}
|
||||
+2
-2
@@ -260,11 +260,11 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||
"Rev": "61db125d227fc9d4e373819a059516f32f7f23c7"
|
||||
"Rev": "86e28c192d2743f0232b9bc5f0a531568ef9f2a5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/kube-openapi/pkg/generators",
|
||||
"Rev": "61db125d227fc9d4e373819a059516f32f7f23c7"
|
||||
"Rev": "86e28c192d2743f0232b9bc5f0a531568ef9f2a5"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
Vendored
+75
-21
@@ -27,18 +27,33 @@ import (
|
||||
|
||||
const extensionPrefix = "x-kubernetes-"
|
||||
|
||||
// Extension tag to openapi extension
|
||||
var tagToExtension = map[string]string{
|
||||
"patchMergeKey": "x-kubernetes-patch-merge-key",
|
||||
"patchStrategy": "x-kubernetes-patch-strategy",
|
||||
"listType": "x-kubernetes-list-type",
|
||||
"listMapKey": "x-kubernetes-list-map-keys",
|
||||
// extensionAttributes encapsulates common traits for particular extensions.
|
||||
type extensionAttributes struct {
|
||||
xName string
|
||||
kind types.Kind
|
||||
allowedValues sets.String
|
||||
}
|
||||
|
||||
// Enum values per extension
|
||||
var allowedExtensionValues = map[string]sets.String{
|
||||
"x-kubernetes-patch-strategy": sets.NewString("merge", "retainKeys"),
|
||||
"x-kubernetes-list-type": sets.NewString("atomic", "set", "map"),
|
||||
// Extension tag to openapi extension attributes
|
||||
var tagToExtension = map[string]extensionAttributes{
|
||||
"patchMergeKey": extensionAttributes{
|
||||
xName: "x-kubernetes-patch-merge-key",
|
||||
kind: types.Slice,
|
||||
},
|
||||
"patchStrategy": extensionAttributes{
|
||||
xName: "x-kubernetes-patch-strategy",
|
||||
kind: types.Slice,
|
||||
allowedValues: sets.NewString("merge", "retainKeys"),
|
||||
},
|
||||
"listMapKey": extensionAttributes{
|
||||
xName: "x-kubernetes-list-map-keys",
|
||||
kind: types.Slice,
|
||||
},
|
||||
"listType": extensionAttributes{
|
||||
xName: "x-kubernetes-list-type",
|
||||
kind: types.Slice,
|
||||
allowedValues: sets.NewString("atomic", "set", "map"),
|
||||
},
|
||||
}
|
||||
|
||||
// Extension encapsulates information necessary to generate an OpenAPI extension.
|
||||
@@ -48,10 +63,25 @@ type extension struct {
|
||||
values []string // Example: [atomic]
|
||||
}
|
||||
|
||||
func (e extension) hasAllowedValues() bool {
|
||||
return tagToExtension[e.idlTag].allowedValues.Len() > 0
|
||||
}
|
||||
|
||||
func (e extension) allowedValues() sets.String {
|
||||
return tagToExtension[e.idlTag].allowedValues
|
||||
}
|
||||
|
||||
func (e extension) hasKind() bool {
|
||||
return len(tagToExtension[e.idlTag].kind) > 0
|
||||
}
|
||||
|
||||
func (e extension) kind() types.Kind {
|
||||
return tagToExtension[e.idlTag].kind
|
||||
}
|
||||
|
||||
func (e extension) validateAllowedValues() error {
|
||||
// allowedValues not set means no restrictions on values.
|
||||
allowedValues, exists := allowedExtensionValues[e.xName]
|
||||
if !exists {
|
||||
if !e.hasAllowedValues() {
|
||||
return nil
|
||||
}
|
||||
// Check for missing value.
|
||||
@@ -59,6 +89,7 @@ func (e extension) validateAllowedValues() error {
|
||||
return fmt.Errorf("%s needs a value, none given.", e.idlTag)
|
||||
}
|
||||
// For each extension value, validate that it is allowed.
|
||||
allowedValues := e.allowedValues()
|
||||
if !allowedValues.HasAll(e.values...) {
|
||||
return fmt.Errorf("%v not allowed for %s. Allowed values: %v",
|
||||
e.values, e.idlTag, allowedValues.List())
|
||||
@@ -66,6 +97,18 @@ func (e extension) validateAllowedValues() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e extension) validateType(kind types.Kind) error {
|
||||
// If this extension class has no kind, then don't validate the type.
|
||||
if !e.hasKind() {
|
||||
return nil
|
||||
}
|
||||
if kind != e.kind() {
|
||||
return fmt.Errorf("tag %s on type %v; only allowed on type %v",
|
||||
e.idlTag, kind, e.kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e extension) hasMultipleValues() bool {
|
||||
return len(e.values) > 1
|
||||
}
|
||||
@@ -82,7 +125,9 @@ func sortedMapKeys(m map[string][]string) []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
// Parses comments to return openapi extensions.
|
||||
// Parses comments to return openapi extensions. Returns a list of
|
||||
// extensions which parsed correctly, as well as a list of the
|
||||
// parse errors. Validating extensions is performed separately.
|
||||
// NOTE: Non-empty errors does not mean extensions is empty.
|
||||
func parseExtensions(comments []string) ([]extension, []error) {
|
||||
extensions := []extension{}
|
||||
@@ -108,21 +153,30 @@ func parseExtensions(comments []string) ([]extension, []error) {
|
||||
// Next, generate extensions from "idlTags" (e.g. +listType)
|
||||
tagValues := types.ExtractCommentTags("+", comments)
|
||||
for _, idlTag := range sortedMapKeys(tagValues) {
|
||||
xName, exists := tagToExtension[idlTag]
|
||||
xAttrs, exists := tagToExtension[idlTag]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
values := tagValues[idlTag]
|
||||
e := extension{
|
||||
idlTag: idlTag, // listType
|
||||
xName: xName, // x-kubernetes-list-type
|
||||
values: values, // [atomic]
|
||||
}
|
||||
if err := e.validateAllowedValues(); err != nil {
|
||||
// For now, only log the extension validation errors.
|
||||
errors = append(errors, err)
|
||||
idlTag: idlTag, // listType
|
||||
xName: xAttrs.xName, // x-kubernetes-list-type
|
||||
values: values, // [atomic]
|
||||
}
|
||||
extensions = append(extensions, e)
|
||||
}
|
||||
return extensions, errors
|
||||
}
|
||||
|
||||
func validateMemberExtensions(extensions []extension, m *types.Member) []error {
|
||||
errors := []error{}
|
||||
for _, e := range extensions {
|
||||
if err := e.validateAllowedValues(); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if err := e.validateType(m.Type.Kind); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
Generated
Vendored
+5
-6
@@ -432,7 +432,7 @@ func (g openAPITypeWriter) generateStructExtensions(t *types.Type) error {
|
||||
extensions, errors := parseExtensions(t.CommentLines)
|
||||
// Initially, we will only log struct extension errors.
|
||||
if len(errors) > 0 {
|
||||
for e := range errors {
|
||||
for _, e := range errors {
|
||||
glog.V(2).Infof("[%s]: %s\n", t.String(), e)
|
||||
}
|
||||
}
|
||||
@@ -442,17 +442,16 @@ func (g openAPITypeWriter) generateStructExtensions(t *types.Type) error {
|
||||
}
|
||||
|
||||
func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type) error {
|
||||
extensions, errors := parseExtensions(m.CommentLines)
|
||||
extensions, parseErrors := parseExtensions(m.CommentLines)
|
||||
validationErrors := validateMemberExtensions(extensions, m)
|
||||
errors := append(parseErrors, validationErrors...)
|
||||
// Initially, we will only log member extension errors.
|
||||
if len(errors) > 0 {
|
||||
errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
|
||||
for e := range errors {
|
||||
for _, e := range errors {
|
||||
glog.V(2).Infof("%s %s\n", errorPrefix, e)
|
||||
}
|
||||
}
|
||||
// TODO(seans3): Validate member extensions here.
|
||||
// Example: listType extension is only on a Slice.
|
||||
// Example: cross-extension validation - listMapKey only makes sense with listType=map
|
||||
g.emitExtensions(extensions)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user