Commit 2c54be80 authored by Quinton Hoole's avatar Quinton Hoole

Merge pull request #9302 from brendandburns/fix

Add messages to indicate that opening an external firewall may be necessary.
parents f048d0df 380790a8
# Services and Firewalls
Many cloud providers (e.g. Google Compute Engine) define firewalls that help keep prevent inadvertent
exposure to the internet. When exposing a service to the external world, you may need to open up
one or more ports in these firewalls to serve traffic. This document describes this process, as
well as any provider specific details that may be necessary.
### Google Compute Engine
Google Compute Engine firewalls are documented [elsewhere](https://cloud.google.com/compute/docs/networking#firewalls_1).
You can add a firewall with the ```gcloud``` command line tool:
```
gcloud compute firewall-rules create my-rule --allow=tcp:<port>
```
**Note**
There is one important security note when using firewalls on Google Compute Engine:
Firewalls are defined per-vm, rather than per-ip address. This means that if you open a firewall for that service's ports,
anything that serves on that port on that VM's host IP address may potentially serve traffic.
Note that this is not a problem for other Kubernetes services, as they listen on IP addresses that are different than the
host node's external IP address.
Consider:
* You create a Service with an external load balancer (IP Address 1.2.3.4) and port 80
* You open the firewall for port 80 for all nodes in your cluster, so that the external Service actually can deliver packets to your Service
* You start an nginx server, running on port 80 on the host virtual machine (IP Address 2.3.4.5). This nginx is **also** exposed to the internet on the VM's external IP address.
Consequently, please be careful when opening firewalls in Google Compute Engine or Google Container Engine. You may accidentally be exposing other services to the wilds of the internet.
### Other cloud providers
Coming soon.
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/services-firewalls.md?pixel)]()
......@@ -19,12 +19,15 @@ package cmd
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
......@@ -102,6 +105,7 @@ func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) err
}
count++
info.Refresh(obj, true)
printObjectSpecificMessage(info.Object, out)
fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name)
return nil
})
......@@ -113,3 +117,36 @@ func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) err
}
return nil
}
func printObjectSpecificMessage(obj runtime.Object, out io.Writer) {
switch obj := obj.(type) {
case *api.Service:
if obj.Spec.Type == api.ServiceTypeLoadBalancer {
msg := fmt.Sprintf(`
An external load-balanced service was created. On many platforms (e.g. Google Compute Engine),
you will also need to explicitly open a Firewall rule for the service port(s) (%s) to serve traffic.
See https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs/services-firewall.md for more details.
`, makePortsString(obj.Spec.Ports))
out.Write([]byte(msg))
}
if obj.Spec.Type == api.ServiceTypeNodePort {
msg := fmt.Sprintf(`
You have exposed your service on an external port on all nodes in your cluster.
If you want to expose this service to the external internet, you may need to set up
firewall rules for the service port(s) (%s) to serve traffic.
See https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs/services-firewall.md for more details.
`, makePortsString(obj.Spec.Ports))
out.Write([]byte(msg))
}
}
}
func makePortsString(ports []api.ServicePort) string {
pieces := make([]string, len(ports))
for ix := range ports {
pieces[ix] = fmt.Sprintf("%s:%d", strings.ToLower(string(ports[ix].Protocol)), ports[ix].Port)
}
return strings.Join(pieces, ",")
}
......@@ -21,7 +21,9 @@ import (
"net/http"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
func TestExtraArgsFail(t *testing.T) {
......@@ -129,3 +131,77 @@ func TestCreateDirectory(t *testing.T) {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestPrintObjectSpecificMessage(t *testing.T) {
tests := []struct {
obj runtime.Object
expectOutput bool
}{
{
obj: &api.Service{},
expectOutput: false,
},
{
obj: &api.Pod{},
expectOutput: false,
},
{
obj: &api.Service{Spec: api.ServiceSpec{Type: api.ServiceTypeLoadBalancer}},
expectOutput: true,
},
{
obj: &api.Service{Spec: api.ServiceSpec{Type: api.ServiceTypeNodePort}},
expectOutput: true,
},
}
for _, test := range tests {
buff := &bytes.Buffer{}
printObjectSpecificMessage(test.obj, buff)
if test.expectOutput && buff.Len() == 0 {
t.Errorf("Expected output, saw none for %v", test.obj)
}
if !test.expectOutput && buff.Len() > 0 {
t.Errorf("Expected no output, saw %s for %v", buff.String(), test.obj)
}
}
}
func TestMakePortsString(t *testing.T) {
tests := []struct {
ports []api.ServicePort
expectedOutput string
}{
{ports: nil, expectedOutput: ""},
{ports: []api.ServicePort{}, expectedOutput: ""},
{ports: []api.ServicePort{
{
Port: 80,
Protocol: "TCP",
},
},
expectedOutput: "tcp:80",
},
{ports: []api.ServicePort{
{
Port: 80,
Protocol: "TCP",
},
{
Port: 8080,
Protocol: "UDP",
},
{
Port: 9000,
Protocol: "TCP",
},
},
expectedOutput: "tcp:80,udp:8080,tcp:9000",
},
}
for _, test := range tests {
output := makePortsString(test.ports)
if output != test.expectedOutput {
t.Errorf("expected: %s, saw: %s.", test.expectedOutput, output)
}
}
}
......@@ -201,5 +201,15 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
}
}
if cmdutil.GetFlagBool(cmd, "create-external-load-balancer") {
msg := fmt.Sprintf(`
An external load-balanced service was created. On many platforms (e.g. Google Compute Engine),
you will also need to explicitly open a firewall rule for the service port (%d) to serve traffic.
See https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs/services-firewall.md for more details.
`, cmdutil.GetFlagInt(cmd, "port"))
out.Write([]byte(msg))
}
return f.PrintObject(cmd, object, out)
}
......@@ -69,8 +69,7 @@ func TestRunExposeService(t *testing.T) {
Selector: map[string]string{"func": "stream"},
},
},
expected: "services/foo",
status: 200,
status: 200,
},
{
name: "no-name-passed-from-the-cli",
......@@ -103,7 +102,40 @@ func TestRunExposeService(t *testing.T) {
Selector: map[string]string{"run": "this"},
},
},
expected: "services/mayor",
status: 200,
},
{
name: "expose-external-service",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
TypeMeta: api.TypeMeta{Kind: "Service", APIVersion: "v1"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "create-external-load-balancer": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "12", Labels: map[string]string{"svc": "test"}},
TypeMeta: api.TypeMeta{Kind: "Service", APIVersion: "v1"},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Name: "default",
Protocol: api.Protocol("UDP"),
Port: 14,
},
},
Selector: map[string]string{"func": "stream"},
Type: api.ServiceTypeLoadBalancer,
},
},
expected: "you will also need to explicitly open a firewall",
status: 200,
},
}
......@@ -135,9 +167,11 @@ func TestRunExposeService(t *testing.T) {
}
cmd.Run(cmd, test.args)
out := buf.String()
if strings.Contains(out, test.expected) {
t.Errorf("%s: unexpected output: %s", test.name, out)
if len(test.expected) > 0 {
out := buf.String()
if !strings.Contains(out, test.expected) {
t.Errorf("%s: unexpected output: %s", test.name, out)
}
}
}
}
......@@ -115,6 +115,7 @@ func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err
}
info.Refresh(obj, true)
printObjectSpecificMessage(obj, out)
fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name)
return nil
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment