Merge pull request #78543 from cheftako/kas-np3
Get network-proxy working with GCE. Kubernetes-commit: 4c315aa8d977a526a1314e1cc3652edc2987bf68
This commit is contained in:
commit
f03b723bf5
|
|
@ -424,7 +424,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/component-base",
|
"ImportPath": "k8s.io/component-base",
|
||||||
"Rev": "ddf6c23a6db8"
|
"Rev": "d6d4632c35d0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/gengo",
|
"ImportPath": "k8s.io/gengo",
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -55,7 +55,7 @@ require (
|
||||||
k8s.io/api v0.0.0-20190806064354-8b51d7113622
|
k8s.io/api v0.0.0-20190806064354-8b51d7113622
|
||||||
k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f
|
k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f
|
||||||
k8s.io/client-go v0.0.0-20190807061213-4fd06e107451
|
k8s.io/client-go v0.0.0-20190807061213-4fd06e107451
|
||||||
k8s.io/component-base v0.0.0-20190807061817-ddf6c23a6db8
|
k8s.io/component-base v0.0.0-20190807101431-d6d4632c35d0
|
||||||
k8s.io/klog v0.3.1
|
k8s.io/klog v0.3.1
|
||||||
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058
|
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058
|
||||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1
|
k8s.io/utils v0.0.0-20190801114015-581e00157fb1
|
||||||
|
|
@ -73,5 +73,5 @@ replace (
|
||||||
k8s.io/api => k8s.io/api v0.0.0-20190806064354-8b51d7113622
|
k8s.io/api => k8s.io/api v0.0.0-20190806064354-8b51d7113622
|
||||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f
|
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f
|
||||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20190807061213-4fd06e107451
|
k8s.io/client-go => k8s.io/client-go v0.0.0-20190807061213-4fd06e107451
|
||||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20190807061817-ddf6c23a6db8
|
k8s.io/component-base => k8s.io/component-base v0.0.0-20190807101431-d6d4632c35d0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
3
go.sum
3
go.sum
|
|
@ -156,6 +156,7 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/soheilhy/cmux v0.1.3 h1:09wy7WZk4AqO03yH85Ex1X+Uo3vDsil3Fa9AgF8Emss=
|
github.com/soheilhy/cmux v0.1.3 h1:09wy7WZk4AqO03yH85Ex1X+Uo3vDsil3Fa9AgF8Emss=
|
||||||
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
|
|
@ -223,7 +224,7 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
|
||||||
k8s.io/api v0.0.0-20190806064354-8b51d7113622/go.mod h1:SgXHCRh94q+5GrRf9Dty2ZG8+wCVmqvQbZJXXcAswkw=
|
k8s.io/api v0.0.0-20190806064354-8b51d7113622/go.mod h1:SgXHCRh94q+5GrRf9Dty2ZG8+wCVmqvQbZJXXcAswkw=
|
||||||
k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ=
|
k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ=
|
||||||
k8s.io/client-go v0.0.0-20190807061213-4fd06e107451/go.mod h1:RW3J3c0otV+R6G3oq1FpjifMKdKu05RyENQ9/UqhBdk=
|
k8s.io/client-go v0.0.0-20190807061213-4fd06e107451/go.mod h1:RW3J3c0otV+R6G3oq1FpjifMKdKu05RyENQ9/UqhBdk=
|
||||||
k8s.io/component-base v0.0.0-20190807061817-ddf6c23a6db8/go.mod h1:SbX3ww4xiCxqQFA4pJgdbgBGm1776AVbtJDXsfvNRXA=
|
k8s.io/component-base v0.0.0-20190807101431-d6d4632c35d0/go.mod h1:SbX3ww4xiCxqQFA4pJgdbgBGm1776AVbtJDXsfvNRXA=
|
||||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ var (
|
||||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
&AdmissionConfiguration{},
|
&AdmissionConfiguration{},
|
||||||
|
&EgressSelectorConfiguration{},
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,52 @@ type AdmissionPluginConfiguration struct {
|
||||||
// +optional
|
// +optional
|
||||||
Configuration *runtime.Unknown
|
Configuration *runtime.Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// EgressSelectorConfiguration provides versioned configuration for egress selector clients.
|
||||||
|
type EgressSelectorConfiguration struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
|
||||||
|
// EgressSelections contains a list of egress selection client configurations
|
||||||
|
EgressSelections []EgressSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
// EgressSelection provides the configuration for a single egress selection client.
|
||||||
|
type EgressSelection struct {
|
||||||
|
// Name is the name of the egress selection.
|
||||||
|
// Currently supported values are "Master", "Etcd" and "Cluster"
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Connection is the exact information used to configure the egress selection
|
||||||
|
Connection Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection provides the configuration for a single egress selection client.
|
||||||
|
type Connection struct {
|
||||||
|
// Type is the type of connection used to connect from client to konnectivity server.
|
||||||
|
// Currently supported values are "http-connect" and "direct".
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// httpConnect is the config needed to use http-connect to the konnectivity server.
|
||||||
|
// +optional
|
||||||
|
HTTPConnect *HTTPConnectConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPConnectConfig struct {
|
||||||
|
// URL is the location of the konnectivity server to connect to.
|
||||||
|
// As an example it might be "https://127.0.0.1:8131"
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// CABundle is the file location of the CA to be used to determine trust with the konnectivity server.
|
||||||
|
// +optional
|
||||||
|
CABundle string
|
||||||
|
|
||||||
|
// ClientKey is the file location of the client key to be used in mtls handshakes with the konnectivity server.
|
||||||
|
// +optional
|
||||||
|
ClientKey string
|
||||||
|
|
||||||
|
// ClientCert is the file location of the client certificate to be used in mtls handshakes with the konnectivity server.
|
||||||
|
// +optional
|
||||||
|
ClientCert string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ func init() {
|
||||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
&AdmissionConfiguration{},
|
&AdmissionConfiguration{},
|
||||||
|
&EgressSelectorConfiguration{},
|
||||||
)
|
)
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,63 @@ type AdmissionPluginConfiguration struct {
|
||||||
// +optional
|
// +optional
|
||||||
Configuration *runtime.Unknown `json:"configuration"`
|
Configuration *runtime.Unknown `json:"configuration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// EgressSelectorConfiguration provides versioned configuration for egress selector clients.
|
||||||
|
type EgressSelectorConfiguration struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// connectionServices contains a list of egress selection client configurations
|
||||||
|
EgressSelections []EgressSelection `json:"egressSelections"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EgressSelection provides the configuration for a single egress selection client.
|
||||||
|
type EgressSelection struct {
|
||||||
|
// name is the name of the egress selection.
|
||||||
|
// Currently supported values are "Master", "Etcd" and "Cluster"
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// connection is the exact information used to configure the egress selection
|
||||||
|
Connection Connection `json:"connection"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection provides the configuration for a single egress selection client.
|
||||||
|
type Connection struct {
|
||||||
|
// type is the type of connection used to connect from client to network/konnectivity server.
|
||||||
|
// Currently supported values are "http-connect" and "direct".
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// httpConnect is the config needed to use http-connect to the konnectivity server.
|
||||||
|
// Absence when the type is "http-connect" will cause an error
|
||||||
|
// Presence when the type is "direct" will also cause an error
|
||||||
|
// +optional
|
||||||
|
HTTPConnect *HTTPConnectConfig `json:"httpConnect,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPConnectConfig struct {
|
||||||
|
// url is the location of the proxy server to connect to.
|
||||||
|
// As an example it might be "https://127.0.0.1:8131"
|
||||||
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
// caBundle is the file location of the CA to be used to determine trust with the konnectivity server.
|
||||||
|
// Must be absent/empty http-connect using the plain http
|
||||||
|
// Must be configured for http-connect using the https protocol
|
||||||
|
// Misconfiguration will cause an error
|
||||||
|
// +optional
|
||||||
|
CABundle string `json:"caBundle,omitempty"`
|
||||||
|
|
||||||
|
// clientKey is the file location of the client key to be used in mtls handshakes with the konnectivity server.
|
||||||
|
// Must be absent/empty http-connect using the plain http
|
||||||
|
// Must be configured for http-connect using the https protocol
|
||||||
|
// Misconfiguration will cause an error
|
||||||
|
// +optional
|
||||||
|
ClientKey string `json:"clientKey,omitempty"`
|
||||||
|
|
||||||
|
// clientCert is the file location of the client certificate to be used in mtls handshakes with the konnectivity server.
|
||||||
|
// Must be absent/empty http-connect using the plain http
|
||||||
|
// Must be configured for http-connect using the https protocol
|
||||||
|
// Misconfiguration will cause an error
|
||||||
|
// +optional
|
||||||
|
ClientCert string `json:"clientCert,omitempty"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,46 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*Connection)(nil), (*apiserver.Connection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_Connection_To_apiserver_Connection(a.(*Connection), b.(*apiserver.Connection), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*apiserver.Connection)(nil), (*Connection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_apiserver_Connection_To_v1alpha1_Connection(a.(*apiserver.Connection), b.(*Connection), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*EgressSelection)(nil), (*apiserver.EgressSelection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(a.(*EgressSelection), b.(*apiserver.EgressSelection), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*apiserver.EgressSelection)(nil), (*EgressSelection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(a.(*apiserver.EgressSelection), b.(*EgressSelection), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*EgressSelectorConfiguration)(nil), (*apiserver.EgressSelectorConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(a.(*EgressSelectorConfiguration), b.(*apiserver.EgressSelectorConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*apiserver.EgressSelectorConfiguration)(nil), (*EgressSelectorConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(a.(*apiserver.EgressSelectorConfiguration), b.(*EgressSelectorConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*HTTPConnectConfig)(nil), (*apiserver.HTTPConnectConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(a.(*HTTPConnectConfig), b.(*apiserver.HTTPConnectConfig), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*apiserver.HTTPConnectConfig)(nil), (*HTTPConnectConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(a.(*apiserver.HTTPConnectConfig), b.(*HTTPConnectConfig), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,3 +141,97 @@ func autoConvert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPlu
|
||||||
func Convert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPluginConfiguration(in *apiserver.AdmissionPluginConfiguration, out *AdmissionPluginConfiguration, s conversion.Scope) error {
|
func Convert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPluginConfiguration(in *apiserver.AdmissionPluginConfiguration, out *AdmissionPluginConfiguration, s conversion.Scope) error {
|
||||||
return autoConvert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPluginConfiguration(in, out, s)
|
return autoConvert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPluginConfiguration(in, out, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_Connection_To_apiserver_Connection(in *Connection, out *apiserver.Connection, s conversion.Scope) error {
|
||||||
|
out.Type = in.Type
|
||||||
|
out.HTTPConnect = (*apiserver.HTTPConnectConfig)(unsafe.Pointer(in.HTTPConnect))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_Connection_To_apiserver_Connection is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_Connection_To_apiserver_Connection(in *Connection, out *apiserver.Connection, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_Connection_To_apiserver_Connection(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_apiserver_Connection_To_v1alpha1_Connection(in *apiserver.Connection, out *Connection, s conversion.Scope) error {
|
||||||
|
out.Type = in.Type
|
||||||
|
out.HTTPConnect = (*HTTPConnectConfig)(unsafe.Pointer(in.HTTPConnect))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_apiserver_Connection_To_v1alpha1_Connection is an autogenerated conversion function.
|
||||||
|
func Convert_apiserver_Connection_To_v1alpha1_Connection(in *apiserver.Connection, out *Connection, s conversion.Scope) error {
|
||||||
|
return autoConvert_apiserver_Connection_To_v1alpha1_Connection(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(in *EgressSelection, out *apiserver.EgressSelection, s conversion.Scope) error {
|
||||||
|
out.Name = in.Name
|
||||||
|
if err := Convert_v1alpha1_Connection_To_apiserver_Connection(&in.Connection, &out.Connection, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(in *EgressSelection, out *apiserver.EgressSelection, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(in *apiserver.EgressSelection, out *EgressSelection, s conversion.Scope) error {
|
||||||
|
out.Name = in.Name
|
||||||
|
if err := Convert_apiserver_Connection_To_v1alpha1_Connection(&in.Connection, &out.Connection, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_apiserver_EgressSelection_To_v1alpha1_EgressSelection is an autogenerated conversion function.
|
||||||
|
func Convert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(in *apiserver.EgressSelection, out *EgressSelection, s conversion.Scope) error {
|
||||||
|
return autoConvert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(in *EgressSelectorConfiguration, out *apiserver.EgressSelectorConfiguration, s conversion.Scope) error {
|
||||||
|
out.EgressSelections = *(*[]apiserver.EgressSelection)(unsafe.Pointer(&in.EgressSelections))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(in *EgressSelectorConfiguration, out *apiserver.EgressSelectorConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in *apiserver.EgressSelectorConfiguration, out *EgressSelectorConfiguration, s conversion.Scope) error {
|
||||||
|
out.EgressSelections = *(*[]EgressSelection)(unsafe.Pointer(&in.EgressSelections))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in *apiserver.EgressSelectorConfiguration, out *EgressSelectorConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(in *HTTPConnectConfig, out *apiserver.HTTPConnectConfig, s conversion.Scope) error {
|
||||||
|
out.URL = in.URL
|
||||||
|
out.CABundle = in.CABundle
|
||||||
|
out.ClientKey = in.ClientKey
|
||||||
|
out.ClientCert = in.ClientCert
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(in *HTTPConnectConfig, out *apiserver.HTTPConnectConfig, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(in *apiserver.HTTPConnectConfig, out *HTTPConnectConfig, s conversion.Scope) error {
|
||||||
|
out.URL = in.URL
|
||||||
|
out.CABundle = in.CABundle
|
||||||
|
out.ClientKey = in.ClientKey
|
||||||
|
out.ClientCert = in.ClientCert
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig is an autogenerated conversion function.
|
||||||
|
func Convert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(in *apiserver.HTTPConnectConfig, out *HTTPConnectConfig, s conversion.Scope) error {
|
||||||
|
return autoConvert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(in, out, s)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,89 @@ func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Connection) DeepCopyInto(out *Connection) {
|
||||||
|
*out = *in
|
||||||
|
if in.HTTPConnect != nil {
|
||||||
|
in, out := &in.HTTPConnect, &out.HTTPConnect
|
||||||
|
*out = new(HTTPConnectConfig)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||||
|
func (in *Connection) DeepCopy() *Connection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Connection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EgressSelection) DeepCopyInto(out *EgressSelection) {
|
||||||
|
*out = *in
|
||||||
|
in.Connection.DeepCopyInto(&out.Connection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelection.
|
||||||
|
func (in *EgressSelection) DeepCopy() *EgressSelection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EgressSelection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopyInto(out *EgressSelectorConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.EgressSelections != nil {
|
||||||
|
in, out := &in.EgressSelections, &out.EgressSelections
|
||||||
|
*out = make([]EgressSelection, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelectorConfiguration.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopy() *EgressSelectorConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EgressSelectorConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *HTTPConnectConfig) DeepCopyInto(out *HTTPConnectConfig) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPConnectConfig.
|
||||||
|
func (in *HTTPConnectConfig) DeepCopy() *HTTPConnectConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(HTTPConnectConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,89 @@ func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Connection) DeepCopyInto(out *Connection) {
|
||||||
|
*out = *in
|
||||||
|
if in.HTTPConnect != nil {
|
||||||
|
in, out := &in.HTTPConnect, &out.HTTPConnect
|
||||||
|
*out = new(HTTPConnectConfig)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||||
|
func (in *Connection) DeepCopy() *Connection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Connection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EgressSelection) DeepCopyInto(out *EgressSelection) {
|
||||||
|
*out = *in
|
||||||
|
in.Connection.DeepCopyInto(&out.Connection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelection.
|
||||||
|
func (in *EgressSelection) DeepCopy() *EgressSelection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EgressSelection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopyInto(out *EgressSelectorConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.EgressSelections != nil {
|
||||||
|
in, out := &in.EgressSelections, &out.EgressSelections
|
||||||
|
*out = make([]EgressSelection, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelectorConfiguration.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopy() *EgressSelectorConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EgressSelectorConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *HTTPConnectConfig) DeepCopyInto(out *HTTPConnectConfig) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPConnectConfig.
|
||||||
|
func (in *HTTPConnectConfig) DeepCopy() *HTTPConnectConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(HTTPConnectConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ func (s *LocationStreamer) InputStream(ctx context.Context, apiVersion, acceptHe
|
||||||
if transport == nil {
|
if transport == nil {
|
||||||
transport = http.DefaultTransport
|
transport = http.DefaultTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
CheckRedirect: s.RedirectChecker,
|
CheckRedirect: s.RedirectChecker,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
"github.com/evanphx/json-patch"
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
|
|
||||||
|
|
@ -94,6 +94,11 @@ type Config struct {
|
||||||
// This is required for proper functioning of the PostStartHooks on a GenericAPIServer
|
// This is required for proper functioning of the PostStartHooks on a GenericAPIServer
|
||||||
// TODO: move into SecureServing(WithLoopback) as soon as insecure serving is gone
|
// TODO: move into SecureServing(WithLoopback) as soon as insecure serving is gone
|
||||||
LoopbackClientConfig *restclient.Config
|
LoopbackClientConfig *restclient.Config
|
||||||
|
|
||||||
|
// EgressSelector provides a lookup mechanism for dialing outbound connections.
|
||||||
|
// It does so based on a EgressSelectorConfiguration which was read at startup.
|
||||||
|
EgressSelector *EgressSelector
|
||||||
|
|
||||||
// RuleResolver is required to get the list of rules that apply to a given user
|
// RuleResolver is required to get the list of rules that apply to a given user
|
||||||
// in a given namespace
|
// in a given namespace
|
||||||
RuleResolver authorizer.RuleResolver
|
RuleResolver authorizer.RuleResolver
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
"k8s.io/klog"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var directDialer utilnet.DialFunc = http.DefaultTransport.(*http.Transport).DialContext
|
||||||
|
|
||||||
|
type EgressSelector struct {
|
||||||
|
egressToDialer map[EgressType]utilnet.DialFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// EgressType is an indicator of which egress selection should be used for sending traffic.
|
||||||
|
// See https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190226-network-proxy.md#network-context
|
||||||
|
type EgressType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Master is the EgressType for traffic intended to go to the control plane.
|
||||||
|
Master EgressType = iota
|
||||||
|
// Etcd is the EgressType for traffic intended to go to Kubernetes persistence store.
|
||||||
|
Etcd
|
||||||
|
// Cluster is the EgressType for traffic intended to go to the system being managed by Kubernetes.
|
||||||
|
Cluster
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetworkContext is the struct used by Kubernetes API Server to indicate where it intends traffic to be sent.
|
||||||
|
type NetworkContext struct {
|
||||||
|
// EgressSelectionName is the unique name of the
|
||||||
|
// EgressSelectorConfiguration which determines
|
||||||
|
// the network we route the traffic to.
|
||||||
|
EgressSelectionName EgressType
|
||||||
|
}
|
||||||
|
|
||||||
|
// EgressSelectorLookup is the interface to get the dialer function for the network context.
|
||||||
|
type EgressSelectorLookup func(networkContext NetworkContext) (utilnet.DialFunc, error)
|
||||||
|
|
||||||
|
func (s EgressType) String() string {
|
||||||
|
switch s {
|
||||||
|
case Master:
|
||||||
|
return "master"
|
||||||
|
case Etcd:
|
||||||
|
return "etcd"
|
||||||
|
case Cluster:
|
||||||
|
return "cluster"
|
||||||
|
default:
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupServiceName(name string) (EgressType, error) {
|
||||||
|
switch strings.ToLower(name) {
|
||||||
|
case "master":
|
||||||
|
return Master, nil
|
||||||
|
case "etcd":
|
||||||
|
return Etcd, nil
|
||||||
|
case "cluster":
|
||||||
|
return Cluster, nil
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("unrecognized service name %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createConnectDialer(connectConfig *apiserver.HTTPConnectConfig) (utilnet.DialFunc, error) {
|
||||||
|
clientCert := connectConfig.ClientCert
|
||||||
|
clientKey := connectConfig.ClientKey
|
||||||
|
caCert := connectConfig.CABundle
|
||||||
|
proxyURL, err := url.Parse(connectConfig.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid proxy server url %q: %v", connectConfig.URL, err)
|
||||||
|
}
|
||||||
|
proxyAddress := proxyURL.Host
|
||||||
|
|
||||||
|
clientCerts, err := tls.LoadX509KeyPair(clientCert, clientKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read key pair %s & %s, got %v", clientCert, clientKey, err)
|
||||||
|
}
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
certBytes, err := ioutil.ReadFile(caCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read cert file %s, got %v", caCert, err)
|
||||||
|
}
|
||||||
|
ok := certPool.AppendCertsFromPEM(certBytes)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to append CA cert to the cert pool")
|
||||||
|
}
|
||||||
|
contextDialer := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
klog.V(4).Infof("Sending request to %q.", addr)
|
||||||
|
proxyConn, err := tls.Dial("tcp", proxyAddress,
|
||||||
|
&tls.Config{
|
||||||
|
Certificates: []tls.Certificate{clientCerts},
|
||||||
|
RootCAs: certPool,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dialing proxy %q failed: %v", proxyAddress, err)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, "127.0.0.1")
|
||||||
|
br := bufio.NewReader(proxyConn)
|
||||||
|
res, err := http.ReadResponse(br, nil)
|
||||||
|
if err != nil {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v",
|
||||||
|
addr, proxyAddress, err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, fmt.Errorf("proxy error from %s while dialing %s, code %d: %v",
|
||||||
|
proxyAddress, addr, res.StatusCode, res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's safe to discard the bufio.Reader here and return the
|
||||||
|
// original TCP conn directly because we only use this for
|
||||||
|
// TLS, and in TLS the client speaks first, so we know there's
|
||||||
|
// no unbuffered data. But we can double-check.
|
||||||
|
if br.Buffered() > 0 {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q",
|
||||||
|
br.Buffered(), proxyAddress)
|
||||||
|
}
|
||||||
|
klog.V(4).Infof("About to proxy request to %s over %s.", addr, proxyAddress)
|
||||||
|
return proxyConn, nil
|
||||||
|
}
|
||||||
|
return contextDialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEgressSelector configures lookup mechanism for Lookup.
|
||||||
|
// It does so based on a EgressSelectorConfiguration which was read at startup.
|
||||||
|
func NewEgressSelector(config *apiserver.EgressSelectorConfiguration) (*EgressSelector, error) {
|
||||||
|
if config == nil || config.EgressSelections == nil {
|
||||||
|
// No Connection Services configured, leaving the serviceMap empty, will return default dialer.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
cs := &EgressSelector{
|
||||||
|
egressToDialer: make(map[EgressType]utilnet.DialFunc),
|
||||||
|
}
|
||||||
|
for _, service := range config.EgressSelections {
|
||||||
|
name, err := lookupServiceName(service.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch service.Connection.Type {
|
||||||
|
case "http-connect":
|
||||||
|
contextDialer, err := createConnectDialer(service.Connection.HTTPConnect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create http-connect dialer: %v", err)
|
||||||
|
}
|
||||||
|
cs.egressToDialer[name] = contextDialer
|
||||||
|
case "direct":
|
||||||
|
cs.egressToDialer[name] = directDialer
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unrecognized service connection type %q", service.Connection.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup gets the dialer function for the network context.
|
||||||
|
// This is configured for the Kubernetes API Server at startup.
|
||||||
|
func (cs *EgressSelector) Lookup(networkContext NetworkContext) (utilnet.DialFunc, error) {
|
||||||
|
if cs.egressToDialer == nil {
|
||||||
|
// The round trip wrapper will over-ride the dialContext method appropriately
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return cs.egressToDialer[networkContext.EgressSelectionName], nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeEgressSelection struct {
|
||||||
|
directDialerCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEgressSelector(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
input *apiserver.EgressSelectorConfiguration
|
||||||
|
services []struct {
|
||||||
|
egressType EgressType
|
||||||
|
validateDialer func(dialer utilnet.DialFunc, s *fakeEgressSelection) (bool, error)
|
||||||
|
lookupError *string
|
||||||
|
dialerError *string
|
||||||
|
}
|
||||||
|
expectedError *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "direct",
|
||||||
|
input: &apiserver.EgressSelectorConfiguration{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "",
|
||||||
|
APIVersion: "",
|
||||||
|
},
|
||||||
|
EgressSelections: []apiserver.EgressSelection{
|
||||||
|
{
|
||||||
|
Name: "cluster",
|
||||||
|
Connection: apiserver.Connection{
|
||||||
|
Type: "direct",
|
||||||
|
HTTPConnect: &apiserver.HTTPConnectConfig{
|
||||||
|
URL: "",
|
||||||
|
CABundle: "",
|
||||||
|
ClientKey: "",
|
||||||
|
ClientCert: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "master",
|
||||||
|
Connection: apiserver.Connection{
|
||||||
|
Type: "direct",
|
||||||
|
HTTPConnect: &apiserver.HTTPConnectConfig{
|
||||||
|
URL: "",
|
||||||
|
CABundle: "",
|
||||||
|
ClientKey: "",
|
||||||
|
ClientCert: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "etcd",
|
||||||
|
Connection: apiserver.Connection{
|
||||||
|
Type: "direct",
|
||||||
|
HTTPConnect: &apiserver.HTTPConnectConfig{
|
||||||
|
URL: "",
|
||||||
|
CABundle: "",
|
||||||
|
ClientKey: "",
|
||||||
|
ClientCert: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
services: []struct {
|
||||||
|
egressType EgressType
|
||||||
|
validateDialer func(dialer utilnet.DialFunc, s *fakeEgressSelection) (bool, error)
|
||||||
|
lookupError *string
|
||||||
|
dialerError *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Cluster,
|
||||||
|
validateDirectDialer,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Master,
|
||||||
|
validateDirectDialer,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Etcd,
|
||||||
|
validateDirectDialer,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Setup the various pieces such as the fake dialer prior to initializing the egress selector.
|
||||||
|
// Go doesn't allow function pointer comparison, nor does its reflect package
|
||||||
|
// So overriding the default dialer to detect if it is returned.
|
||||||
|
fake := &fakeEgressSelection{}
|
||||||
|
directDialer = fake.fakeDirectDialer
|
||||||
|
cs, err := NewEgressSelector(tc.input)
|
||||||
|
if err == nil && tc.expectedError != nil {
|
||||||
|
t.Errorf("calling NewEgressSelector expected error: %s, did not get it", *tc.expectedError)
|
||||||
|
}
|
||||||
|
if err != nil && tc.expectedError == nil {
|
||||||
|
t.Errorf("unexpected error calling NewEgressSelector got: %#v", err)
|
||||||
|
}
|
||||||
|
if err != nil && tc.expectedError != nil && err.Error() != *tc.expectedError {
|
||||||
|
t.Errorf("calling NewEgressSelector expected error: %s, got %#v", *tc.expectedError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range tc.services {
|
||||||
|
networkContext := NetworkContext{EgressSelectionName: service.egressType}
|
||||||
|
dialer, lookupErr := cs.Lookup(networkContext)
|
||||||
|
if lookupErr == nil && service.lookupError != nil {
|
||||||
|
t.Errorf("calling Lookup expected error: %s, did not get it", *service.lookupError)
|
||||||
|
}
|
||||||
|
if lookupErr != nil && service.lookupError == nil {
|
||||||
|
t.Errorf("unexpected error calling Lookup got: %#v", lookupErr)
|
||||||
|
}
|
||||||
|
if lookupErr != nil && service.lookupError != nil && lookupErr.Error() != *service.lookupError {
|
||||||
|
t.Errorf("calling Lookup expected error: %s, got %#v", *service.lookupError, lookupErr)
|
||||||
|
}
|
||||||
|
fake.directDialerCalled = false
|
||||||
|
ok, dialerErr := service.validateDialer(dialer, fake)
|
||||||
|
if dialerErr == nil && service.dialerError != nil {
|
||||||
|
t.Errorf("calling Lookup expected error: %s, did not get it", *service.dialerError)
|
||||||
|
}
|
||||||
|
if dialerErr != nil && service.dialerError == nil {
|
||||||
|
t.Errorf("unexpected error calling Lookup got: %#v", dialerErr)
|
||||||
|
}
|
||||||
|
if dialerErr != nil && service.dialerError != nil && dialerErr.Error() != *service.dialerError {
|
||||||
|
t.Errorf("calling Lookup expected error: %s, got %#v", *service.dialerError, dialerErr)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Could not validate dialer for service %q", service.egressType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeEgressSelection) fakeDirectDialer(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
s.directDialerCalled = true
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDirectDialer(dialer utilnet.DialFunc, s *fakeEgressSelection) (bool, error) {
|
||||||
|
conn, err := dialer(context.Background(), "tcp", "127.0.0.1:8080")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return s.directDialerCalled, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 egressselector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver/install"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
|
||||||
|
"k8s.io/utils/path"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cfgScheme = runtime.NewScheme()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
install.Install(cfgScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadEgressSelectorConfiguration reads the egress selector configuration at the specified path.
|
||||||
|
// It returns the loaded egress selector configuration if the input file aligns with the required syntax.
|
||||||
|
// If it does not align with the provided syntax, it returns a default configuration which should function as a no-op.
|
||||||
|
// It does this by returning a nil configuration, which preserves backward compatibility.
|
||||||
|
// This works because prior to this there was no egress selector configuration.
|
||||||
|
// It returns an error if the file did not exist.
|
||||||
|
func ReadEgressSelectorConfiguration(configFilePath string) (*apiserver.EgressSelectorConfiguration, error) {
|
||||||
|
if configFilePath == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// a file was provided, so we just read it.
|
||||||
|
data, err := ioutil.ReadFile(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read egress selector configuration from %q [%v]", configFilePath, err)
|
||||||
|
}
|
||||||
|
var decodedConfig v1alpha1.EgressSelectorConfiguration
|
||||||
|
err = yaml.Unmarshal(data, &decodedConfig)
|
||||||
|
if err != nil {
|
||||||
|
// we got an error where the decode wasn't related to a missing type
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if decodedConfig.Kind != "EgressSelectorConfiguration" {
|
||||||
|
return nil, fmt.Errorf("invalid service configuration object %q", decodedConfig.Kind)
|
||||||
|
}
|
||||||
|
config, err := cfgScheme.ConvertToVersion(&decodedConfig, apiserver.SchemeGroupVersion)
|
||||||
|
if err != nil {
|
||||||
|
// we got an error where the decode wasn't related to a missing type
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if internalConfig, ok := config.(*apiserver.EgressSelectorConfiguration); ok {
|
||||||
|
return internalConfig, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to convert %T to *apiserver.EgressSelectorConfiguration", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateEgressSelectorConfiguration checks the apiserver.EgressSelectorConfiguration for
|
||||||
|
// common configuration errors. It will return error for problems such as configuring mtls/cert
|
||||||
|
// settings for protocol which do not support security. It will also try to catch errors such as
|
||||||
|
// incorrect file paths. It will return nil if it does not find anything wrong.
|
||||||
|
func ValidateEgressSelectorConfiguration(config *apiserver.EgressSelectorConfiguration) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if config == nil {
|
||||||
|
return allErrs // Treating a nil configuration as valid
|
||||||
|
}
|
||||||
|
for _, service := range config.EgressSelections {
|
||||||
|
base := field.NewPath("service", "connection")
|
||||||
|
switch service.Connection.Type {
|
||||||
|
case "direct":
|
||||||
|
allErrs = append(allErrs, validateDirectConnection(service.Connection, base)...)
|
||||||
|
case "http-connect":
|
||||||
|
allErrs = append(allErrs, validateHTTPConnection(service.Connection, base)...)
|
||||||
|
default:
|
||||||
|
allErrs = append(allErrs, field.NotSupported(
|
||||||
|
base.Child("type"),
|
||||||
|
service.Connection.Type,
|
||||||
|
[]string{"direct", "http-connect"}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDirectConnection(connection apiserver.Connection, fldPath *field.Path) field.ErrorList {
|
||||||
|
if connection.HTTPConnect != nil {
|
||||||
|
return field.ErrorList{field.Invalid(
|
||||||
|
fldPath.Child("httpConnect"),
|
||||||
|
"direct",
|
||||||
|
"httpConnect config should be absent for direct connect"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPConnection(connection apiserver.Connection, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if connection.HTTPConnect == nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect"),
|
||||||
|
"nil",
|
||||||
|
"httpConnect config should be present for http-connect"))
|
||||||
|
} else if strings.HasPrefix(connection.HTTPConnect.URL, "https://") {
|
||||||
|
if connection.HTTPConnect.CABundle == "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "caBundle"),
|
||||||
|
"nil",
|
||||||
|
"http-connect via https requires caBundle"))
|
||||||
|
} else if exists, err := path.Exists(path.CheckFollowSymlink, connection.HTTPConnect.CABundle); exists == false || err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "caBundle"),
|
||||||
|
connection.HTTPConnect.CABundle,
|
||||||
|
"http-connect ca bundle does not exist"))
|
||||||
|
}
|
||||||
|
if connection.HTTPConnect.ClientCert == "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "clientCert"),
|
||||||
|
"nil",
|
||||||
|
"http-connect via https requires clientCert"))
|
||||||
|
} else if exists, err := path.Exists(path.CheckFollowSymlink, connection.HTTPConnect.ClientCert); exists == false || err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "clientCert"),
|
||||||
|
connection.HTTPConnect.ClientCert,
|
||||||
|
"http-connect client cert does not exist"))
|
||||||
|
}
|
||||||
|
if connection.HTTPConnect.ClientKey == "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "clientKey"),
|
||||||
|
"nil",
|
||||||
|
"http-connect via https requires clientKey"))
|
||||||
|
} else if exists, err := path.Exists(path.CheckFollowSymlink, connection.HTTPConnect.ClientKey); exists == false || err != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "clientKey"),
|
||||||
|
connection.HTTPConnect.ClientKey,
|
||||||
|
"http-connect client key does not exist"))
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(connection.HTTPConnect.URL, "http://") {
|
||||||
|
if connection.HTTPConnect.CABundle != "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "caBundle"),
|
||||||
|
connection.HTTPConnect.CABundle,
|
||||||
|
"http-connect via http does not support caBundle"))
|
||||||
|
}
|
||||||
|
if connection.HTTPConnect.ClientCert != "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "clientCert"),
|
||||||
|
connection.HTTPConnect.ClientCert,
|
||||||
|
"http-connect via http does not support clientCert"))
|
||||||
|
}
|
||||||
|
if connection.HTTPConnect.ClientKey != "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "clientKey"),
|
||||||
|
connection.HTTPConnect.ClientKey,
|
||||||
|
"http-connect via http does not support clientKey"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allErrs = append(allErrs, field.Invalid(
|
||||||
|
fldPath.Child("httpConnect", "url"),
|
||||||
|
connection.HTTPConnect.URL,
|
||||||
|
"supported connection protocols are http:// and https://"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 egressselector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func strptr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadEgressSelectorConfiguration(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
contents string
|
||||||
|
createFile bool
|
||||||
|
expectedResult *apiserver.EgressSelectorConfiguration
|
||||||
|
expectedError *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
createFile: true,
|
||||||
|
contents: ``,
|
||||||
|
expectedResult: nil,
|
||||||
|
expectedError: strptr("invalid service configuration object \"\""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "absent",
|
||||||
|
createFile: false,
|
||||||
|
contents: ``,
|
||||||
|
expectedResult: nil,
|
||||||
|
expectedError: strptr("unable to read egress selector configuration from \"test-egress-selector-config-absent\" [open test-egress-selector-config-absent: no such file or directory]"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1alpha1",
|
||||||
|
createFile: true,
|
||||||
|
contents: `
|
||||||
|
apiVersion: apiserver.k8s.io/v1alpha1
|
||||||
|
kind: EgressSelectorConfiguration
|
||||||
|
egressSelections:
|
||||||
|
- name: "cluster"
|
||||||
|
connection:
|
||||||
|
type: "http-connect"
|
||||||
|
httpConnect:
|
||||||
|
url: "https://127.0.0.1:8131"
|
||||||
|
caBundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt"
|
||||||
|
clientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key"
|
||||||
|
clientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt"
|
||||||
|
- name: "master"
|
||||||
|
connection:
|
||||||
|
type: "http-connect"
|
||||||
|
httpConnect:
|
||||||
|
url: "https://127.0.0.1:8132"
|
||||||
|
caBundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt"
|
||||||
|
clientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key"
|
||||||
|
clientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt"
|
||||||
|
- name: "etcd"
|
||||||
|
connection:
|
||||||
|
type: "direct"
|
||||||
|
`,
|
||||||
|
expectedResult: &apiserver.EgressSelectorConfiguration{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "",
|
||||||
|
APIVersion: "",
|
||||||
|
},
|
||||||
|
EgressSelections: []apiserver.EgressSelection{
|
||||||
|
{
|
||||||
|
Name: "cluster",
|
||||||
|
Connection: apiserver.Connection{
|
||||||
|
Type: "http-connect",
|
||||||
|
HTTPConnect: &apiserver.HTTPConnectConfig{
|
||||||
|
URL: "https://127.0.0.1:8131",
|
||||||
|
CABundle: "/etc/srv/kubernetes/pki/konnectivity-server/ca.crt",
|
||||||
|
ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server/client.key",
|
||||||
|
ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server/client.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "master",
|
||||||
|
Connection: apiserver.Connection{
|
||||||
|
Type: "http-connect",
|
||||||
|
HTTPConnect: &apiserver.HTTPConnectConfig{
|
||||||
|
URL: "https://127.0.0.1:8132",
|
||||||
|
CABundle: "/etc/srv/kubernetes/pki/konnectivity-server-master/ca.crt",
|
||||||
|
ClientKey: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.key",
|
||||||
|
ClientCert: "/etc/srv/kubernetes/pki/konnectivity-server-master/client.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "etcd",
|
||||||
|
Connection: apiserver.Connection{
|
||||||
|
Type: "direct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong_type",
|
||||||
|
createFile: true,
|
||||||
|
contents: `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
addonmanager.kubernetes.io/mode: Reconcile
|
||||||
|
k8s-app: konnectivity-agent
|
||||||
|
namespace: kube-system
|
||||||
|
name: proxy-agent
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: konnectivity-agent
|
||||||
|
updateStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: proxy-agent
|
||||||
|
annotations:
|
||||||
|
scheduler.alpha.kubernetes.io/critical-pod: ''
|
||||||
|
spec:
|
||||||
|
priorityClassName: system-cluster-critical
|
||||||
|
# Necessary to reboot node
|
||||||
|
hostPID: true
|
||||||
|
volumes:
|
||||||
|
- name: pki
|
||||||
|
hostPath:
|
||||||
|
path: /etc/srv/kubernetes/pki/konnectivity-agent
|
||||||
|
containers:
|
||||||
|
- image: gcr.io/google-containers/proxy-agent:v0.0.3
|
||||||
|
name: proxy-agent
|
||||||
|
command: ["/proxy-agent"]
|
||||||
|
args: ["--caCert=/etc/srv/kubernetes/pki/proxy-agent/ca.crt", "--agentCert=/etc/srv/kubernetes/pki/proxy-agent/client.crt", "--agentKey=/etc/srv/kubernetes/pki/proxy-agent/client.key", "--proxyServerHost=127.0.0.1", "--proxyServerPort=8132"]
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add: ["SYS_BOOT"]
|
||||||
|
env:
|
||||||
|
- name: wrong-type
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: kube-system
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 30Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: pki
|
||||||
|
mountPath: /etc/srv/kubernetes/pki/konnectivity-agent
|
||||||
|
`,
|
||||||
|
expectedResult: nil,
|
||||||
|
expectedError: strptr("invalid service configuration object \"DaemonSet\""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
proxyConfig := fmt.Sprintf("test-egress-selector-config-%s", tc.name)
|
||||||
|
if tc.createFile {
|
||||||
|
f, err := ioutil.TempFile("", proxyConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
if err := ioutil.WriteFile(f.Name(), []byte(tc.contents), os.FileMode(0755)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
proxyConfig = f.Name()
|
||||||
|
}
|
||||||
|
config, err := ReadEgressSelectorConfiguration(proxyConfig)
|
||||||
|
if err == nil && tc.expectedError != nil {
|
||||||
|
t.Errorf("calling ReadEgressSelectorConfiguration expected error: %s, did not get it", *tc.expectedError)
|
||||||
|
}
|
||||||
|
if err != nil && tc.expectedError == nil {
|
||||||
|
t.Errorf("unexpected error calling ReadEgressSelectorConfiguration got: %#v", err)
|
||||||
|
}
|
||||||
|
if err != nil && tc.expectedError != nil && err.Error() != *tc.expectedError {
|
||||||
|
t.Errorf("calling ReadEgressSelectorConfiguration expected error: %s, got %#v", *tc.expectedError, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(config, tc.expectedResult) {
|
||||||
|
t.Errorf("problem with configuration returned from ReadEgressSelectorConfiguration expected: %#v, got: %#v", tc.expectedResult, config)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"k8s.io/utils/path"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/server"
|
||||||
|
"k8s.io/apiserver/pkg/server/egressselector"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EgressSelectorOptions holds the api server egress selector options.
|
||||||
|
// See https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190226-network-proxy.md
|
||||||
|
type EgressSelectorOptions struct {
|
||||||
|
// ConfigFile is the file path with api-server egress selector configuration.
|
||||||
|
ConfigFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEgressSelectorOptions creates a new instance of EgressSelectorOptions
|
||||||
|
//
|
||||||
|
// The option is to point to a configuration file for egress/konnectivity.
|
||||||
|
// This determines which types of requests use egress/konnectivity and how they use it.
|
||||||
|
// If empty the API Server will attempt to connect directly using the network.
|
||||||
|
func NewEgressSelectorOptions() *EgressSelectorOptions {
|
||||||
|
return &EgressSelectorOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFlags adds flags related to admission for a specific APIServer to the specified FlagSet
|
||||||
|
func (o *EgressSelectorOptions) AddFlags(fs *pflag.FlagSet) {
|
||||||
|
if o == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.StringVar(&o.ConfigFile, "egress-selector-config-file", o.ConfigFile,
|
||||||
|
"File with apiserver egress selector configuration.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyTo adds the egress selector settings to the server configuration.
|
||||||
|
// In case egress selector settings were not provided by a cluster-admin
|
||||||
|
// they will be prepared from the recommended/default/no-op values.
|
||||||
|
func (o *EgressSelectorOptions) ApplyTo(c *server.Config) error {
|
||||||
|
if o == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
npConfig, err := egressselector.ReadEgressSelectorConfiguration(o.ConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read egress selector config: %v", err)
|
||||||
|
}
|
||||||
|
errs := egressselector.ValidateEgressSelectorConfiguration(npConfig)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("failed to validate egress selector configuration: %v", errs.ToAggregate())
|
||||||
|
}
|
||||||
|
|
||||||
|
cs, err := server.NewEgressSelector(npConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to setup egress selector with config %#v: %v", npConfig, err)
|
||||||
|
}
|
||||||
|
c.EgressSelector = cs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate verifies flags passed to EgressSelectorOptions.
|
||||||
|
func (o *EgressSelectorOptions) Validate() []error {
|
||||||
|
if o == nil || o.ConfigFile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := []error{}
|
||||||
|
|
||||||
|
if exists, err := path.Exists(path.CheckFollowSymlink, o.ConfigFile); exists == false || err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("egress-selector-config-file %s does not exist", o.ConfigFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,8 @@ type RecommendedOptions struct {
|
||||||
// ProcessInfo is used to identify events created by the server.
|
// ProcessInfo is used to identify events created by the server.
|
||||||
ProcessInfo *ProcessInfo
|
ProcessInfo *ProcessInfo
|
||||||
Webhook *WebhookOptions
|
Webhook *WebhookOptions
|
||||||
|
// API Server Egress Selector is used to control outbound traffic from the API Server
|
||||||
|
EgressSelector *EgressSelectorOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRecommendedOptions(prefix string, codec runtime.Codec, processInfo *ProcessInfo) *RecommendedOptions {
|
func NewRecommendedOptions(prefix string, codec runtime.Codec, processInfo *ProcessInfo) *RecommendedOptions {
|
||||||
|
|
@ -67,6 +69,7 @@ func NewRecommendedOptions(prefix string, codec runtime.Codec, processInfo *Proc
|
||||||
Admission: NewAdmissionOptions(),
|
Admission: NewAdmissionOptions(),
|
||||||
ProcessInfo: processInfo,
|
ProcessInfo: processInfo,
|
||||||
Webhook: NewWebhookOptions(),
|
Webhook: NewWebhookOptions(),
|
||||||
|
EgressSelector: NewEgressSelectorOptions(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +82,7 @@ func (o *RecommendedOptions) AddFlags(fs *pflag.FlagSet) {
|
||||||
o.Features.AddFlags(fs)
|
o.Features.AddFlags(fs)
|
||||||
o.CoreAPI.AddFlags(fs)
|
o.CoreAPI.AddFlags(fs)
|
||||||
o.Admission.AddFlags(fs)
|
o.Admission.AddFlags(fs)
|
||||||
|
o.EgressSelector.AddFlags(fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyTo adds RecommendedOptions to the server configuration.
|
// ApplyTo adds RecommendedOptions to the server configuration.
|
||||||
|
|
@ -110,6 +114,9 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
|
||||||
} else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, initializers...); err != nil {
|
} else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, initializers...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := o.EgressSelector.ApplyTo(&config.Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +131,7 @@ func (o *RecommendedOptions) Validate() []error {
|
||||||
errors = append(errors, o.Features.Validate()...)
|
errors = append(errors, o.Features.Validate()...)
|
||||||
errors = append(errors, o.CoreAPI.Validate()...)
|
errors = append(errors, o.CoreAPI.Validate()...)
|
||||||
errors = append(errors, o.Admission.Validate()...)
|
errors = append(errors, o.Admission.Validate()...)
|
||||||
|
errors = append(errors, o.EgressSelector.Validate()...)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue