Skip to content

Commit

Permalink
Add support for TransparentProxy.DialedDirectly
Browse files Browse the repository at this point in the history
* renamed `types.go` => `shared_type.go` to better reflect what that
file contains.
* Added `DialedDirectly` field to `TransparentProxy` struct
* Renamed `CatalogDestinationsOnly` to `MeshDestinationsOnly` to match
Consul renaming (hashicorp/consul#10397)
* Added tests to validate for ProxyDefaults since that config entry now
has more fields. The validate methods were technically tested through
ServiceDefaults tests but I added them to ProxyDefaults since they both
rely on that shared validation.
* Fixed validation for OutboundListenerPort so that it returned the port
as the error rather than the entire TransparentProxy struct.
* Updated Consul api package to latest
* Updated controller-runtime to 0.6.0 because I was getting an error on
0.5.0.
* Regenerated YAML CRDs
  • Loading branch information
lkysow committed Jun 14, 2021
1 parent 61f66df commit 8f0a394
Show file tree
Hide file tree
Showing 21 changed files with 829 additions and 266 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ executors:
- image: docker.mirror.hashicorp.services/circleci/golang:1.14
environment:
TEST_RESULTS: /tmp/test-results # path to where test results are saved
CONSUL_VERSION: 1.10.0-beta3 # Consul's OSS version to use in tests
CONSUL_ENT_VERSION: 1.10.0+ent-beta3 # Consul's enterprise version to use in tests
CONSUL_VERSION: 1.10.0-beta4 # Consul's OSS version to use in tests
CONSUL_ENT_VERSION: 1.10.0+ent-beta4 # Consul's enterprise version to use in tests

jobs:
go-fmt-and-vet:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ ifeq (, $(shell which controller-gen))
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0 ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.0 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
Expand Down
6 changes: 3 additions & 3 deletions api/v1alpha1/mesh_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ type MeshSpec struct {

// TransparentProxyMeshConfig controls configuration specific to proxies in "transparent" mode. Added in v1.10.0.
type TransparentProxyMeshConfig struct {
// CatalogDestinationsOnly determines whether sidecar proxies operating in "transparent" mode can proxy traffic
// MeshDestinationsOnly determines whether sidecar proxies operating in "transparent" mode can proxy traffic
// to IP addresses not registered in Consul's catalog. If enabled, traffic will only be proxied to upstreams
// with service registrations in the catalog.
CatalogDestinationsOnly bool `json:"catalogDestinationsOnly,omitempty"`
MeshDestinationsOnly bool `json:"meshDestinationsOnly,omitempty"`
}

func (in *TransparentProxyMeshConfig) toConsul() capi.TransparentProxyMeshConfig {
return capi.TransparentProxyMeshConfig{CatalogDestinationsOnly: in.CatalogDestinationsOnly}
return capi.TransparentProxyMeshConfig{MeshDestinationsOnly: in.MeshDestinationsOnly}
}

func (in *Mesh) GetObjectMeta() metav1.ObjectMeta {
Expand Down
8 changes: 4 additions & 4 deletions api/v1alpha1/mesh_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ func TestMesh_MatchesConsul(t *testing.T) {
},
Spec: MeshSpec{
TransparentProxy: TransparentProxyMeshConfig{
CatalogDestinationsOnly: true,
MeshDestinationsOnly: true,
},
},
},
Theirs: &capi.MeshConfigEntry{
TransparentProxy: capi.TransparentProxyMeshConfig{
CatalogDestinationsOnly: true,
MeshDestinationsOnly: true,
},
CreateIndex: 1,
ModifyIndex: 2,
Expand Down Expand Up @@ -107,13 +107,13 @@ func TestMesh_ToConsul(t *testing.T) {
},
Spec: MeshSpec{
TransparentProxy: TransparentProxyMeshConfig{
CatalogDestinationsOnly: true,
MeshDestinationsOnly: true,
},
},
},
Exp: &capi.MeshConfigEntry{
TransparentProxy: capi.TransparentProxyMeshConfig{
CatalogDestinationsOnly: true,
MeshDestinationsOnly: true,
},
Namespace: "",
Meta: map[string]string{
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/mesh_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestValidateMesh(t *testing.T) {
},
Spec: MeshSpec{
TransparentProxy: TransparentProxyMeshConfig{
CatalogDestinationsOnly: true,
MeshDestinationsOnly: true,
},
},
},
Expand Down
124 changes: 124 additions & 0 deletions api/v1alpha1/proxydefaults_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
Namespace: "default",
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 0,
DialedDirectly: false,
},
CreateIndex: 1,
ModifyIndex: 2,
Expand Down Expand Up @@ -72,6 +73,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
},
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
},
},
Expand Down Expand Up @@ -103,6 +105,7 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
},
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
},
Matches: true,
Expand Down Expand Up @@ -149,6 +152,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
},
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 0,
DialedDirectly: false,
},
},
},
Expand Down Expand Up @@ -181,6 +185,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
},
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
},
},
Expand Down Expand Up @@ -213,6 +218,7 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
},
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
Meta: map[string]string{
common.SourceKey: common.SourceValue,
Expand All @@ -231,6 +237,124 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
}
}

// Test validation for fields other than Config. Config is tested
// in separate tests below.
func TestProxyDefaults_Validate(t *testing.T) {
cases := map[string]struct {
input *ProxyDefaults
expectedErrMsg string
}{
"meshgateway.mode": {
&ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
MeshGateway: MeshGateway{
Mode: "foobar",
},
},
},
`proxydefaults.consul.hashicorp.com "global" is invalid: spec.meshGateway.mode: Invalid value: "foobar": must be one of "remote", "local", "none", ""`,
},
"expose.paths[].protocol": {
&ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
Expose: Expose{
Paths: []ExposePath{
{
Protocol: "invalid-protocol",
Path: "/valid-path",
},
},
},
},
},
`proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].protocol: Invalid value: "invalid-protocol": must be one of "http", "http2"`,
},
"expose.paths[].path": {
&ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
Expose: Expose{
Paths: []ExposePath{
{
Protocol: "http",
Path: "invalid-path",
},
},
},
},
},
`proxydefaults.consul.hashicorp.com "global" is invalid: spec.expose.paths[0].path: Invalid value: "invalid-path": must begin with a '/'`,
},
"transparentProxy.outboundListenerPort": {
&ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
},
},
},
"proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port",
},
"mode": {
&ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
Mode: proxyModeRef("transparent"),
},
},
"proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode",
},
"multi-error": {
&ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
MeshGateway: MeshGateway{
Mode: "invalid-mode",
},
Expose: Expose{
Paths: []ExposePath{
{
Protocol: "invalid-protocol",
Path: "invalid-path",
},
},
},
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
},
Mode: proxyModeRef("transparent"),
},
},
"proxydefaults.consul.hashicorp.com \"global\" is invalid: [spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]",
},
}
for name, testCase := range cases {
t.Run(name, func(t *testing.T) {
err := testCase.input.Validate(false)
if testCase.expectedErrMsg != "" {
require.EqualError(t, err, testCase.expectedErrMsg)
} else {
require.NoError(t, err)
}
})
}
}

func TestProxyDefaults_ValidateConfigValid(t *testing.T) {
cases := map[string]json.RawMessage{
"envoy_tracing_json": json.RawMessage(`{"envoy_tracing_json": "{\"http\":{\"name\":\"envoy.zipkin\",\"config\":{\"collector_cluster\":\"zipkin\",\"collector_endpoint\":\"/api/v1/spans\",\"shared_span_context\":false}}}"}`),
Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/proxydefaults_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestValidateProxyDefault(t *testing.T) {
expAllow: false,
expErrMessage: "proxydefaults resource name must be \"global\"",
},
"transparentProxy value set": {
"transparentProxy.outboundListenerPort set": {
existingResources: []runtime.Object{},
newResource: &ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -90,7 +90,7 @@ func TestValidateProxyDefault(t *testing.T) {
},
},
expAllow: false,
expErrMessage: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy: Invalid value: v1alpha1.TransparentProxy{OutboundListenerPort:1000}: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port",
expErrMessage: "proxydefaults.consul.hashicorp.com \"global\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port",
},
"mode value set": {
existingResources: []runtime.Object{},
Expand Down
12 changes: 9 additions & 3 deletions api/v1alpha1/servicedefaults_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) {
Kind: capi.ServiceDefaults,
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 0,
DialedDirectly: false,
},
Meta: map[string]string{
common.SourceKey: common.SourceValue,
Expand Down Expand Up @@ -65,6 +66,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) {
ExternalSNI: "external-sni",
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
UpstreamConfig: &Upstreams{
Defaults: &Upstream{
Expand Down Expand Up @@ -165,6 +167,7 @@ func TestServiceDefaults_ToConsul(t *testing.T) {
ExternalSNI: "external-sni",
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
UpstreamConfig: &capi.UpstreamConfiguration{
Defaults: &capi.UpstreamConfig{
Expand Down Expand Up @@ -265,6 +268,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) {
Namespace: "namespace",
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 0,
DialedDirectly: false,
},
CreateIndex: 1,
ModifyIndex: 2,
Expand Down Expand Up @@ -304,6 +308,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) {
ExternalSNI: "sni-value",
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
UpstreamConfig: &Upstreams{
Defaults: &Upstream{
Expand Down Expand Up @@ -403,6 +408,7 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) {
ExternalSNI: "sni-value",
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 1000,
DialedDirectly: true,
},
UpstreamConfig: &capi.UpstreamConfiguration{
Defaults: &capi.UpstreamConfig{
Expand Down Expand Up @@ -585,7 +591,7 @@ func TestServiceDefaults_Validate(t *testing.T) {
},
`servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.expose.paths[0].path: Invalid value: "invalid-path": must begin with a '/'`,
},
"transparentProxy": {
"transparentProxy.outboundListenerPort": {
&ServiceDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "my-service",
Expand All @@ -596,7 +602,7 @@ func TestServiceDefaults_Validate(t *testing.T) {
},
},
},
"servicedefaults.consul.hashicorp.com \"my-service\" is invalid: spec.transparentProxy: Invalid value: v1alpha1.TransparentProxy{OutboundListenerPort:1000}: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port",
"servicedefaults.consul.hashicorp.com \"my-service\" is invalid: spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port",
},
"mode": {
&ServiceDefaults{
Expand Down Expand Up @@ -702,7 +708,7 @@ func TestServiceDefaults_Validate(t *testing.T) {
Mode: proxyModeRef("transparent"),
},
},
"servicedefaults.consul.hashicorp.com \"my-service\" is invalid: [spec.protocol: Invalid value: \"invalid\": must be one of \"tcp\", \"http\", \"http2\", \"grpc\", spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy: Invalid value: v1alpha1.TransparentProxy{OutboundListenerPort:1000}: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]",
"servicedefaults.consul.hashicorp.com \"my-service\" is invalid: [spec.protocol: Invalid value: \"invalid\": must be one of \"tcp\", \"http\", \"http2\", \"grpc\", spec.meshGateway.mode: Invalid value: \"invalid-mode\": must be one of \"remote\", \"local\", \"none\", \"\", spec.transparentProxy.outboundListenerPort: Invalid value: 1000: use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port, spec.mode: Invalid value: \"transparent\": use the annotation `consul.hashicorp.com/transparent-proxy` to configure the Transparent Proxy Mode, spec.expose.paths[0].path: Invalid value: \"invalid-path\": must begin with a '/', spec.expose.paths[0].protocol: Invalid value: \"invalid-protocol\": must be one of \"http\", \"http2\"]",
},
}

Expand Down
22 changes: 18 additions & 4 deletions api/v1alpha1/types.go → api/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)

// This file contains structs that are shared between multiple config entries.

type MeshGatewayMode string

// Expose describes HTTP paths to expose through Envoy outside of Connect.
Expand Down Expand Up @@ -38,8 +40,14 @@ type ExposePath struct {
}

type TransparentProxy struct {
// The port of the listener where outbound application traffic is being redirected to.
// OutboundListenerPort is the port of the listener where outbound application
// traffic is being redirected to.
OutboundListenerPort int `json:"outboundListenerPort,omitempty"`

// DialedDirectly indicates whether transparent proxies can dial this proxy instance directly.
// The discovery chain is not considered when dialing a service instance directly.
// This setting is useful when addressing stateful services, such as a database cluster with a leader node.
DialedDirectly bool `json:"dialedDirectly,omitempty"`
}

// MeshGateway controls how Mesh Gateways are used for upstream Connect
Expand Down Expand Up @@ -115,12 +123,18 @@ func (in *TransparentProxy) toConsul() *capi.TransparentProxyConfig {
if in == nil {
return &capi.TransparentProxyConfig{OutboundListenerPort: 0}
}
return &capi.TransparentProxyConfig{OutboundListenerPort: in.OutboundListenerPort}
return &capi.TransparentProxyConfig{
OutboundListenerPort: in.OutboundListenerPort,
DialedDirectly: in.DialedDirectly,
}
}

func (in *TransparentProxy) validate(path *field.Path) *field.Error {
if in != nil {
return field.Invalid(path, in, "use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port")
if in == nil {
return nil
}
if in.OutboundListenerPort != 0 {
return field.Invalid(path.Child("outboundListenerPort"), in.OutboundListenerPort, "use the annotation `consul.hashicorp.com/transparent-proxy-outbound-listener-port` to configure the Outbound Listener Port")
}
return nil
}
Expand Down
Loading

0 comments on commit 8f0a394

Please sign in to comment.