Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for cluster using http forward proxy #2481 #2777

Merged
merged 1 commit into from
Aug 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/http_proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

HTTP Forward Proxy Support
==========================

It is possible to launch a kubernetes cluster from behind an http forward proxy ("corporate proxy"). To do so, you will need to configure the `egressProxy` for the cluster.

It is assumed the proxy is already existing. If you want a private topology on AWS, for example, with an proxy instead of a NAT instance, you'll need to create the proxy yourself. See [Running in a shared VPC](run_in_existing_vpc.md).

This configuration only manages proxy configurations for Kops and the Kubernetes cluster. We can not handle proxy configuration for application containers and pods.

## Configuration

Add `spec.egressProxy` port and url as follows

``` yaml
spec:
egressProxy:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have egress which is where we configure a natGateway. Currently that is defined on the subnet level, because of the limitations of NAT subnets. We should consider whether this is the same thing, and whether we want to expand egress to cover this. My knee-jerk is yes...

(In practice, we only have to preserve serialized API compatibility, so this means renaming the field. We can harmonize the two fields later.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the flip-side, I guess the argument is that we could have both egress and egressProxy. i.e. we want to use an HTTP proxy, but we also want to tunnel through a VPN / gateway.

I'm not the expert here - if http proxy & gateway are both possible, then this makes sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think conceivably the http proxy would be in addition to one or more of these other options.

httpProxy:
host: proxy.corp.local
port: 3128
```

Currently we assume the same configuration for http and https traffic.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 easy to change later (add an httpsProxy field), great to call out the limitation explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! That was my intent. I opened an issue to track it (#3069)


## Proxy Excludes

Most clients will blindly try to use the proxy to make all calls, even to localhost and the local subnet, unless configured otherwise. Some basic exclusions necessary for successful launch and operation are added for you at initial cluster creation. If you wish to add additional exclusions, add or edit `egressProxy.excludes` with a comma separated list of hostnames. Matching is based on suffix, ie, `corp.local` will match `images.corp.local`, and `.corp.local` will match `corp.local` and `images.corp.local`, following typical `no_proxy` environment variable conventions.

``` yaml
spec:
egressProxy:
httpProxy:
host: proxy.corp.local
port: 3128
excludes: corp.local,internal.corp.com
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is only going to work for layer 7 proxies, and if we - for example - but a NAT gateway in here it would not work as expected. That said I think this is fine, it can be a validation error. YAGNI :-)

```

## AWS VPC Endpoints and S3 access

If you are hosting on AWS have configured VPC "Endpoints" for S3 or other services, you may want to add these to the `spec.egressProxy.excludes`. Keep in mind that the S3 bucket must be in the same region as the VPC for it to be accessible via the endpoint.
4 changes: 4 additions & 0 deletions docs/run_in_existing_vpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,7 @@ Please note:
in their route table. Private subnets should not have public IPs, and will typically have a NAT gateway
configured as their default route.
* kops won't create a route-table at all if we're not creating subnets.

### Proxy VPC Egress

See [HTTP Forward Proxy Support](http_proxy.md)
36 changes: 35 additions & 1 deletion nodeup/pkg/model/convenience.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ limitations under the License.

package model

import "k8s.io/kops/upup/pkg/fi"
import (
"strconv"

"github.com/golang/glog"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi"
)

// s is a helper that builds a *string from a string value
func s(v string) *string {
Expand All @@ -28,6 +35,33 @@ func i64(v int64) *int64 {
return fi.Int64(v)
}

func getProxyEnvVars(proxies *kops.EgressProxySpec) []v1.EnvVar {
if proxies == nil {
glog.V(8).Info("proxies is == nil, returning empty list")
return []v1.EnvVar{}
}

if proxies.HTTPProxy.Host == "" {
glog.Warning("EgressProxy set but no proxy host provided")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also check this in validation, but no harm in calling it out here.

If we know this is going to go wrong, should we skip setting the env vars?

We could also return an error in this case...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returning an error just forces the caller to deal with the situation right? I'm not 100% sure either way. Things are probably going to fail to come up in this situation, we defiantly want to catch this in validation in any case, but as to whether we should return an error here, since I'm not sure, I'm just going to lean towards YAGNI and leave it as is.

}

var httpProxyURL string
if proxies.HTTPProxy.Port == 0 {
httpProxyURL = "http://" + proxies.HTTPProxy.Host
} else {
httpProxyURL = "http://" + proxies.HTTPProxy.Host + ":" + strconv.Itoa(proxies.HTTPProxy.Port)
}

noProxy := proxies.ProxyExcludes

return []v1.EnvVar{
{Name: "http_proxy", Value: httpProxyURL},
{Name: "https_proxy", Value: httpProxyURL},
{Name: "NO_PROXY", Value: noProxy},
{Name: "no_proxy", Value: noProxy},
}
}

// b returns a pointer to a boolean
func b(v bool) *bool {
return fi.Bool(v)
Expand Down
1 change: 1 addition & 0 deletions nodeup/pkg/model/kubeapiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ func (b *KubeAPIServerBuilder) buildPod() (*v1.Pod, error) {
HostPort: 8080,
},
},
Env: getProxyEnvVars(b.Cluster.Spec.EgressProxy),
}

for _, path := range b.SSLHostPaths() {
Expand Down
1 change: 1 addition & 0 deletions nodeup/pkg/model/kubecontrollermanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func (b *KubeControllerManagerBuilder) buildPod() (*v1.Pod, error) {
InitialDelaySeconds: 15,
TimeoutSeconds: 15,
},
Env: getProxyEnvVars(b.Cluster.Spec.EgressProxy),
}

for _, path := range b.SSLHostPaths() {
Expand Down
1 change: 1 addition & 0 deletions nodeup/pkg/model/kubescheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func (b *KubeSchedulerBuilder) buildPod() (*v1.Pod, error) {
InitialDelaySeconds: 15,
TimeoutSeconds: 15,
},
Env: getProxyEnvVars(b.Cluster.Spec.EgressProxy),
}

addHostPathMapping(pod, container, "varlibkubescheduler", "/var/lib/kube-scheduler")
Expand Down
16 changes: 15 additions & 1 deletion nodeup/pkg/model/protokube.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ func (t *ProtokubeBuilder) ProtokubeFlags(k8sVersion semver.Version) *ProtokubeF
func (t *ProtokubeBuilder) ProtokubeEnvironmentVariables() string {
var buffer bytes.Buffer

// TODO write out an environments file for this. This is getting a tad long.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 but no need to do as part of this PR. We also are being a bit cavalier with escaping, so building up a map[string]string of env and then carefully writing it would help there also!

I think here we still need to pass -e flags though, as we're calling in to docker


// Pass in required credentials when using user-defined s3 endpoint
if os.Getenv("AWS_REGION") != "" {
buffer.WriteString(" ")
buffer.WriteString("-e 'AWS_REGION=")
Expand All @@ -284,7 +287,6 @@ func (t *ProtokubeBuilder) ProtokubeEnvironmentVariables() string {
buffer.WriteString(" ")
}

// Pass in required credentials when using user-defined s3 endpoint
if os.Getenv("S3_ENDPOINT") != "" {
buffer.WriteString(" ")
buffer.WriteString("-e S3_ENDPOINT=")
Expand All @@ -306,9 +308,21 @@ func (t *ProtokubeBuilder) ProtokubeEnvironmentVariables() string {
buffer.WriteString(" ")
}

t.writeProxyEnvVars(&buffer)

return buffer.String()
}

func (t *ProtokubeBuilder) writeProxyEnvVars(buffer *bytes.Buffer) {
for _, envVar := range getProxyEnvVars(t.Cluster.Spec.EgressProxy) {
buffer.WriteString(" -e ")
buffer.WriteString(envVar.Name)
buffer.WriteString("=")
buffer.WriteString(envVar.Value)
buffer.WriteString(" ")
}
}

// buildCertificateTask is responsible for build a certificate request task
func (t *ProtokubeBuilder) buildCeritificateTask(c *fi.ModelBuilderContext, name, filename string) error {
cert, err := t.KeyStore.Cert(name)
Expand Down
16 changes: 16 additions & 0 deletions pkg/apis/kops/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ type ClusterSpec struct {
NonMasqueradeCIDR string `json:"nonMasqueradeCIDR,omitempty"`
// SSHAccess is a list of the CIDRs that can access SSH.
SSHAccess []string `json:"sshAccess,omitempty"`
// HTTPProxy defines connection information to support use of a private cluster behind an forward HTTP Proxy
EgressProxy *EgressProxySpec `json:"egressProxy,omitempty"`

// KubernetesAPIAccess is a list of the CIDRs that can access the Kubernetes API endpoint (master HTTPS)
KubernetesAPIAccess []string `json:"kubernetesApiAccess,omitempty"`
// IsolatesMasters determines whether we should lock down masters so that they are not on the pod network.
Expand Down Expand Up @@ -285,6 +288,19 @@ type ClusterSubnetSpec struct {
Type SubnetType `json:"type,omitempty"`
}

type EgressProxySpec struct {
HTTPProxy HTTPProxy `json:"httpProxy,omitempty"`
ProxyExcludes string `json:"excludes,omitempty"`
}

type HTTPProxy struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
// TODO #3070
// User string `json:"user,omitempty"`
// Password string `json:"password,omitempty"`
}

// FillDefaults populates default values.
// This is different from PerformAssignments, because these values are changeable, and thus we don't need to
// store them (i.e. we don't need to 'lock them')
Expand Down
16 changes: 16 additions & 0 deletions pkg/apis/kops/v1alpha1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ type ClusterSpec struct {
//KubeProxyTestArgs string `json:",omitempty"`
//KubeProxyTestLogLevel string `json:",omitempty"`

// HTTPProxy defines connection information to support use of a private cluster behind an forward HTTP Proxy
EgressProxy *EgressProxySpec `json:"egressProxy,omitempty"`

// EtcdClusters stores the configuration for each cluster
EtcdClusters []*EtcdClusterSpec `json:"etcdClusters,omitempty"`

Expand Down Expand Up @@ -368,3 +371,16 @@ type ClusterZoneSpec struct {

Egress string `json:"egress,omitempty"`
}

type EgressProxySpec struct {
HTTPProxy HTTPProxy `json:"httpProxy,omitempty"`
ProxyExcludes string `json:"excludes,omitempty"`
}

type HTTPProxy struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
// TODO #3070
// User string `json:"user,omitempty"`
// Password string `json:"password,omitempty"`
}
70 changes: 70 additions & 0 deletions pkg/apis/kops/v1alpha1/zz_generated.conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_kops_DNSSpec_To_v1alpha1_DNSSpec,
Convert_v1alpha1_DockerConfig_To_kops_DockerConfig,
Convert_kops_DockerConfig_To_v1alpha1_DockerConfig,
Convert_v1alpha1_EgressProxySpec_To_kops_EgressProxySpec,
Convert_kops_EgressProxySpec_To_v1alpha1_EgressProxySpec,
Convert_v1alpha1_EtcdClusterSpec_To_kops_EtcdClusterSpec,
Convert_kops_EtcdClusterSpec_To_v1alpha1_EtcdClusterSpec,
Convert_v1alpha1_EtcdMemberSpec_To_kops_EtcdMemberSpec,
Expand All @@ -83,6 +85,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_kops_FederationSpec_To_v1alpha1_FederationSpec,
Convert_v1alpha1_FlannelNetworkingSpec_To_kops_FlannelNetworkingSpec,
Convert_kops_FlannelNetworkingSpec_To_v1alpha1_FlannelNetworkingSpec,
Convert_v1alpha1_HTTPProxy_To_kops_HTTPProxy,
Convert_kops_HTTPProxy_To_v1alpha1_HTTPProxy,
Convert_v1alpha1_HookSpec_To_kops_HookSpec,
Convert_kops_HookSpec_To_v1alpha1_HookSpec,
Convert_v1alpha1_InstanceGroup_To_kops_InstanceGroup,
Expand Down Expand Up @@ -524,6 +528,15 @@ func autoConvert_v1alpha1_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.AdditionalPolicies = in.AdditionalPolicies
if in.EgressProxy != nil {
in, out := &in.EgressProxy, &out.EgressProxy
*out = new(kops.EgressProxySpec)
if err := Convert_v1alpha1_EgressProxySpec_To_kops_EgressProxySpec(*in, *out, s); err != nil {
return err
}
} else {
out.EgressProxy = nil
}
if in.EtcdClusters != nil {
in, out := &in.EtcdClusters, &out.EtcdClusters
*out = make([]*kops.EtcdClusterSpec, len(*in))
Expand Down Expand Up @@ -705,6 +718,15 @@ func autoConvert_kops_ClusterSpec_To_v1alpha1_ClusterSpec(in *kops.ClusterSpec,
out.ServiceClusterIPRange = in.ServiceClusterIPRange
out.NonMasqueradeCIDR = in.NonMasqueradeCIDR
// WARNING: in.SSHAccess requires manual conversion: does not exist in peer-type
if in.EgressProxy != nil {
in, out := &in.EgressProxy, &out.EgressProxy
*out = new(EgressProxySpec)
if err := Convert_kops_EgressProxySpec_To_v1alpha1_EgressProxySpec(*in, *out, s); err != nil {
return err
}
} else {
out.EgressProxy = nil
}
// WARNING: in.KubernetesAPIAccess requires manual conversion: does not exist in peer-type
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
Expand Down Expand Up @@ -946,6 +968,32 @@ func Convert_kops_DockerConfig_To_v1alpha1_DockerConfig(in *kops.DockerConfig, o
return autoConvert_kops_DockerConfig_To_v1alpha1_DockerConfig(in, out, s)
}

func autoConvert_v1alpha1_EgressProxySpec_To_kops_EgressProxySpec(in *EgressProxySpec, out *kops.EgressProxySpec, s conversion.Scope) error {
if err := Convert_v1alpha1_HTTPProxy_To_kops_HTTPProxy(&in.HTTPProxy, &out.HTTPProxy, s); err != nil {
return err
}
out.ProxyExcludes = in.ProxyExcludes
return nil
}

// Convert_v1alpha1_EgressProxySpec_To_kops_EgressProxySpec is an autogenerated conversion function.
func Convert_v1alpha1_EgressProxySpec_To_kops_EgressProxySpec(in *EgressProxySpec, out *kops.EgressProxySpec, s conversion.Scope) error {
return autoConvert_v1alpha1_EgressProxySpec_To_kops_EgressProxySpec(in, out, s)
}

func autoConvert_kops_EgressProxySpec_To_v1alpha1_EgressProxySpec(in *kops.EgressProxySpec, out *EgressProxySpec, s conversion.Scope) error {
if err := Convert_kops_HTTPProxy_To_v1alpha1_HTTPProxy(&in.HTTPProxy, &out.HTTPProxy, s); err != nil {
return err
}
out.ProxyExcludes = in.ProxyExcludes
return nil
}

// Convert_kops_EgressProxySpec_To_v1alpha1_EgressProxySpec is an autogenerated conversion function.
func Convert_kops_EgressProxySpec_To_v1alpha1_EgressProxySpec(in *kops.EgressProxySpec, out *EgressProxySpec, s conversion.Scope) error {
return autoConvert_kops_EgressProxySpec_To_v1alpha1_EgressProxySpec(in, out, s)
}

func autoConvert_v1alpha1_EtcdClusterSpec_To_kops_EtcdClusterSpec(in *EtcdClusterSpec, out *kops.EtcdClusterSpec, s conversion.Scope) error {
out.Name = in.Name
out.EnableEtcdTLS = in.EnableEtcdTLS
Expand Down Expand Up @@ -1162,6 +1210,28 @@ func Convert_kops_FlannelNetworkingSpec_To_v1alpha1_FlannelNetworkingSpec(in *ko
return autoConvert_kops_FlannelNetworkingSpec_To_v1alpha1_FlannelNetworkingSpec(in, out, s)
}

func autoConvert_v1alpha1_HTTPProxy_To_kops_HTTPProxy(in *HTTPProxy, out *kops.HTTPProxy, s conversion.Scope) error {
out.Host = in.Host
out.Port = in.Port
return nil
}

// Convert_v1alpha1_HTTPProxy_To_kops_HTTPProxy is an autogenerated conversion function.
func Convert_v1alpha1_HTTPProxy_To_kops_HTTPProxy(in *HTTPProxy, out *kops.HTTPProxy, s conversion.Scope) error {
return autoConvert_v1alpha1_HTTPProxy_To_kops_HTTPProxy(in, out, s)
}

func autoConvert_kops_HTTPProxy_To_v1alpha1_HTTPProxy(in *kops.HTTPProxy, out *HTTPProxy, s conversion.Scope) error {
out.Host = in.Host
out.Port = in.Port
return nil
}

// Convert_kops_HTTPProxy_To_v1alpha1_HTTPProxy is an autogenerated conversion function.
func Convert_kops_HTTPProxy_To_v1alpha1_HTTPProxy(in *kops.HTTPProxy, out *HTTPProxy, s conversion.Scope) error {
return autoConvert_kops_HTTPProxy_To_v1alpha1_HTTPProxy(in, out, s)
}

func autoConvert_v1alpha1_HookSpec_To_kops_HookSpec(in *HookSpec, out *kops.HookSpec, s conversion.Scope) error {
if in.ExecContainer != nil {
in, out := &in.ExecContainer, &out.ExecContainer
Expand Down
17 changes: 17 additions & 0 deletions pkg/apis/kops/v1alpha2/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ type ClusterSpec struct {
// Currently only a single CIDR is supported (though a richer grammar could be added in future)
SSHAccess []string `json:"sshAccess,omitempty"`

// HTTPProxy defines connection information to support use of a private cluster behind an forward HTTP Proxy
EgressProxy *EgressProxySpec `json:"egressProxy,omitempty"`

// KubernetesAPIAccess determines the permitted access to the API endpoints (master HTTPS)
// Currently only a single CIDR is supported (though a richer grammar could be added in future)
KubernetesAPIAccess []string `json:"kubernetesApiAccess,omitempty"`
Expand Down Expand Up @@ -293,3 +296,17 @@ type ClusterSubnetSpec struct {

Type SubnetType `json:"type,omitempty"`
}

type EgressProxySpec struct {
HTTPProxy HTTPProxy `json:"httpProxy,omitempty"`
ProxyExcludes string `json:"excludes,omitempty"`
}

type HTTPProxy struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`

// TODO #3070
// User string `json:"user,omitempty"`
// Password string `json:"password,omitempty"`
}
Loading