Skip to content

Commit

Permalink
Service Intentions L4 support (hashicorp#634)
Browse files Browse the repository at this point in the history
* Add support for L4 intentions
  • Loading branch information
thisisnotashwin authored Oct 7, 2020
1 parent c3ab672 commit d47ce5e
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ jobs:
-kubeconfig="$primary_kubeconfig" \
-secondary-kubeconfig="$secondary_kubeconfig" \
-debug-directory="$TEST_RESULTS/debug" \
-consul-k8s-image=hashicorpdev/consul-k8s:crd-controller-base-latest \
-consul-k8s-image=hashicorpdev/consul-k8s:crd-controller-base-latest
- store_test_results:
path: /tmp/test-results
Expand Down
2 changes: 2 additions & 0 deletions templates/controller-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rules:
- proxydefaults
- servicerouters
- servicesplitters
- serviceintentions
verbs:
- create
- delete
Expand All @@ -34,6 +35,7 @@ rules:
- proxydefaults/status
- servicerouters/status
- servicesplitters/status
- serviceintentions/status
verbs:
- get
- patch
Expand Down
19 changes: 19 additions & 0 deletions templates/controller-mutatingwebhookconfiguration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,23 @@ webhooks:
resources:
- servicesplitters
sideEffects: None
- clientConfig:
caBundle: Cg==
service:
name: {{ template "consul.fullname" . }}-controller-webhook
namespace: {{ .Release.Namespace }}
path: /mutate-v1alpha1-serviceintentions
failurePolicy: Fail
name: mutate-serviceintentions.consul.hashicorp.com
rules:
- apiGroups:
- consul.hashicorp.com
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- serviceintentions
sideEffects: None
{{- end }}
98 changes: 98 additions & 0 deletions templates/crd-serviceintentions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{{- if .Values.controller.enabled }}
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: serviceintentions.consul.hashicorp.com
spec:
group: consul.hashicorp.com
names:
kind: ServiceIntentions
listKind: ServiceIntentionsList
plural: serviceintentions
singular: serviceintentions
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: ServiceIntentions is the Schema for the serviceintentions API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ServiceIntentionsSpec defines the desired state of ServiceIntentions
properties:
destination:
properties:
name:
type: string
namespace:
type: string
type: object
sources:
items:
properties:
action:
description: IntentionAction is the action that the intention represents. This can be "allow" or "deny" to allowlist or denylist intentions.
type: string
description:
type: string
name:
type: string
namespace:
type: string
type: object
type: array
type: object
status:
properties:
conditions:
description: Conditions indicate the latest available observations of a resource's current state.
items:
description: 'Conditions define a readiness condition for a Consul resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties'
properties:
lastTransitionTime:
description: LastTransitionTime is the last time the condition transitioned from one status to another.
format: date-time
type: string
message:
description: A human readable message indicating details about the transition.
type: string
reason:
description: The reason for the condition's last transition.
type: string
status:
description: Status of the condition, one of True, False, Unknown.
type: string
type:
description: Type of condition.
type: string
required:
- status
- type
type: object
type: array
type: object
type: object
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
{{- end }}
2 changes: 1 addition & 1 deletion test/acceptance/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.14

require (
github.com/gruntwork-io/terratest v0.29.0
github.com/hashicorp/consul/api v1.6.0
github.com/hashicorp/consul/api v1.4.1-0.20201006211533-eabba09b6633
github.com/hashicorp/consul/sdk v0.6.0
github.com/stretchr/testify v1.5.1
gopkg.in/yaml.v2 v2.2.8
Expand Down
4 changes: 2 additions & 2 deletions test/acceptance/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ github.com/gruntwork-io/gruntwork-cli v0.5.1 h1:mVmVsFubUSLSCO8bGigI63HXzvzkC0uW
github.com/gruntwork-io/gruntwork-cli v0.5.1/go.mod h1:IBX21bESC1/LGoV7jhXKUnTQTZgQ6dYRsoj/VqxUSZQ=
github.com/gruntwork-io/terratest v0.29.0 h1:EyPLLxglZIHJ0jU1cbx2NJT7A3MVEmPle8ENWDDwVAA=
github.com/gruntwork-io/terratest v0.29.0/go.mod h1:aVz7181EP4okz7LMx6BLpiF7bL8wkq+h57V6uicvoc0=
github.com/hashicorp/consul/api v1.6.0 h1:SZB2hQW8AcTOpfDmiVblQbijxzsRuiyy0JpHfabvHio=
github.com/hashicorp/consul/api v1.6.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg=
github.com/hashicorp/consul/api v1.4.1-0.20201006211533-eabba09b6633 h1:7Isk6LXOQLgzLc+77snDnR9jddDkTxbm4cAW1eCfc3c=
github.com/hashicorp/consul/api v1.4.1-0.20201006211533-eabba09b6633/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg=
github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw=
github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
Expand Down
31 changes: 31 additions & 0 deletions test/acceptance/tests/controller/controller_namespaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ const (
KubeNS = "ns1"
ConsulDestNS = "from-k8s"
DefaultConsulNamespace = "default"

// The name of a service intention in consul is
// the name of the destination service and is not
// the same as the kube name of the resource.
IntentionName = "svc1"
)

// Test that the controller works with Consul Enterprise namespaces.
// These tests currently only test non-secure and secure without auto-encrypt installations
// because in the case of namespaces there isn't a significant distinction in code between auto-encrypt
// and non-auto-encrypt secure installations, so testing just one is enough.
func TestControllerNamespaces(t *testing.T) {
t.Skip()
cfg := suite.Config()
if !cfg.EnableEnterprise {
t.Skipf("skipping this test because -enable-enterprise is not set")
Expand Down Expand Up @@ -162,6 +168,13 @@ func TestControllerNamespaces(t *testing.T) {
svcSplitterEntry, ok := entry.(*api.ServiceSplitterConfigEntry)
require.True(r, ok, "could not cast to ServiceSplitterConfigEntry")
require.Equal(r, float32(100), svcSplitterEntry.Splits[0].Weight)

// service-intentions
entry, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, queryOpts)
require.NoError(r, err)
svcIntentions, ok := entry.(*api.ServiceIntentionsConfigEntry)
require.True(r, ok, "could not cast to ServiceSplitterConfigEntry")
require.Equal(r, api.IntentionActionAllow, svcIntentions.Sources[0].Action)
})
}

Expand All @@ -186,6 +199,9 @@ func TestControllerNamespaces(t *testing.T) {
t.Log("patching service-splitter custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "servicesplitter", "splitter", "-p", `{"spec": {"splits": [{"weight": 50}, {"weight": 50, "service": "other-splitter"}]}}`, "--type=merge")

t.Log("patching service-intentions custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "serviceintentions", "intentions", "-p", `{"spec": {"sources": [{"name": "svc2", "action": "deny"}]}}`, "--type=merge")

counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond}
retry.RunWith(counter, t, func(r *retry.R) {
// service-defaults
Expand Down Expand Up @@ -224,6 +240,13 @@ func TestControllerNamespaces(t *testing.T) {
require.Equal(r, float32(50), svcSplitter.Splits[0].Weight)
require.Equal(r, float32(50), svcSplitter.Splits[1].Weight)
require.Equal(r, "other-splitter", svcSplitter.Splits[1].Service)

// service-intentions
entry, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, queryOpts)
require.NoError(r, err)
svcIntentions, ok := entry.(*api.ServiceIntentionsConfigEntry)
require.True(r, ok, "could not cast to ServiceIntentionsConfigEntry")
require.Equal(r, api.IntentionActionDeny, svcIntentions.Sources[0].Action)
})
}

Expand All @@ -244,6 +267,9 @@ func TestControllerNamespaces(t *testing.T) {
t.Log("deleting service-splitter custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "servicesplitter", "splitter")

t.Log("deleting service-intentions custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "serviceintentions", "intentions")

counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond}
retry.RunWith(counter, t, func(r *retry.R) {
// service-defaults
Expand All @@ -270,6 +296,11 @@ func TestControllerNamespaces(t *testing.T) {
_, _, err = consulClient.ConfigEntries().Get(api.ServiceSplitter, "splitter", queryOpts)
require.Error(r, err)
require.Contains(r, err.Error(), "404 (Config entry not found")

// service-intentions
_, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, queryOpts)
require.Error(r, err)
require.Contains(r, err.Error(), "404 (Config entry not found")
})
}
})
Expand Down
30 changes: 30 additions & 0 deletions test/acceptance/tests/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ func TestController(t *testing.T) {
{true, true},
}

// The name of a service intention in consul is
// the name of the destination service and is not
// the same as the kube name of the resource.
const IntentionName = "svc1"

for _, c := range cases {
name := fmt.Sprintf("secure: %t; auto-encrypt: %t", c.secure, c.autoEncrypt)
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -102,6 +107,13 @@ func TestController(t *testing.T) {
svcSplitterEntry, ok := entry.(*api.ServiceSplitterConfigEntry)
require.True(r, ok, "could not cast to ServiceSplitterConfigEntry")
require.Equal(r, float32(100), svcSplitterEntry.Splits[0].Weight)

// service-intentions
entry, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, nil)
require.NoError(r, err)
svcIntentionsEntry, ok := entry.(*api.ServiceIntentionsConfigEntry)
require.True(r, ok, "could not cast to ServiceIntentionsConfigEntry")
require.Equal(r, api.IntentionActionAllow, svcIntentionsEntry.Sources[0].Action)
})
}

Expand All @@ -126,6 +138,9 @@ func TestController(t *testing.T) {
t.Log("patching service-splitter custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "patch", "servicesplitter", "splitter", "-p", `{"spec": {"splits": [{"weight": 50}, {"weight": 50, "service": "other-splitter"}]}}`, "--type=merge")

t.Log("patching service-intentions custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "patch", "serviceintentions", "intentions", "-p", `{"spec": {"sources": [{"name": "svc2", "action": "deny"}]}}`, "--type=merge")

counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond}
retry.RunWith(counter, t, func(r *retry.R) {
// service-defaults
Expand Down Expand Up @@ -164,6 +179,13 @@ func TestController(t *testing.T) {
require.Equal(r, float32(50), svcSplitter.Splits[0].Weight)
require.Equal(r, float32(50), svcSplitter.Splits[1].Weight)
require.Equal(r, "other-splitter", svcSplitter.Splits[1].Service)

// service-intentions
entry, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, nil)
require.NoError(r, err)
svcIntentions, ok := entry.(*api.ServiceIntentionsConfigEntry)
require.True(r, ok, "could not cast to ServiceIntentionsConfigEntry")
require.Equal(r, api.IntentionActionDeny, svcIntentions.Sources[0].Action)
})
}

Expand All @@ -184,6 +206,9 @@ func TestController(t *testing.T) {
t.Log("deleting service-splitter custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "delete", "servicesplitter", "splitter")

t.Log("deleting service-intentions custom resource")
helpers.RunKubectl(t, ctx.KubectlOptions(t), "delete", "serviceintentions", "intentions")

counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond}
retry.RunWith(counter, t, func(r *retry.R) {
// service-defaults
Expand All @@ -210,6 +235,11 @@ func TestController(t *testing.T) {
_, _, err = consulClient.ConfigEntries().Get(api.ServiceSplitter, "splitter", nil)
require.Error(r, err)
require.Contains(r, err.Error(), "404 (Config entry not found")

// service-intentions
_, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, nil)
require.Error(r, err)
require.Contains(r, err.Error(), "404 (Config entry not found")
})
}
})
Expand Down
20 changes: 20 additions & 0 deletions test/acceptance/tests/fixtures/crds/serviceintentions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceDefaults
metadata:
name: svc1
---
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceDefaults
metadata:
name: svc2
---
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceIntentions
metadata:
name: intentions
spec:
destination:
name: svc1
sources:
- name: svc2
action: allow
24 changes: 24 additions & 0 deletions test/unit/crd-serviceintentions.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bats

load _helpers

@test "serviceintentions/CustomResourceDefinitions: disabled by default" {
cd `chart_dir`
assert_empty helm template \
-s templates/crd-serviceintentions.yaml \
.
}

@test "serviceintentions/CustomResourceDefinitions: enabled with controller.enabled=true" {
cd `chart_dir`
local actual=$(helm template \
-s templates/crd-serviceintentions.yaml \
--set 'controller.enabled=true' \
. | tee /dev/stderr |
# The generated CRDs have "---" at the top which results in two objects
# being detected by yq, the first of which is null. We must therefore use
# yq -s so that length operates on both objects at once rather than
# individually, which would output false\ntrue and fail the test.
yq -s 'length > 0' | tee /dev/stderr)
[ "${actual}" = "true" ]
}

0 comments on commit d47ce5e

Please sign in to comment.