Skip to content

Commit

Permalink
Add support for field TransparentProxy to SvcDefaults and ProxyDefaul…
Browse files Browse the repository at this point in the history
…ts (#485)

* Update ProxyDefaults and ServiceDefaults with Transparent Proxy
- Update the spec of ServiceDefaults and ProxyDefaults to support
  transparent proxy changes that are introduced as a part of Consul 1.10

* Remove `Config` suffix from v1alpha1 structs
- Remove unused contstants for MeshGateway modes.

* Error out from the webhook if TransparentProxy field is explicitly set.
  • Loading branch information
Ashwin Venkatesh authored Apr 22, 2021
1 parent 345419e commit 2e98ec9
Show file tree
Hide file tree
Showing 23 changed files with 328 additions and 162 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.9.4 # Consul's OSS version to use in tests
CONSUL_ENT_VERSION: 1.9.4+ent # Consul's enterprise version to use in tests
CONSUL_VERSION: 1.10.0-beta1 # Consul's OSS version to use in tests
CONSUL_ENT_VERSION: 1.10.0+ent-beta1 # Consul's enterprise version to use in tests

jobs:
go-fmt-and-vet:
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/ingressgateway_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type IngressGatewayWebhook struct {
//
// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation.
//
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-ingressgateway,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=ingressgateways,versions=v1alpha1,name=mutate-ingressgateway.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-ingressgateway,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=ingressgateways,versions=v1alpha1,name=mutate-ingressgateway.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1

func (v *IngressGatewayWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
var resource IngressGateway
Expand Down
22 changes: 14 additions & 8 deletions api/v1alpha1/proxydefaults_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ type ProxyDefaultsSpec struct {
// +kubebuilder:pruning:PreserveUnknownFields
Config json.RawMessage `json:"config,omitempty"`
// MeshGateway controls the default mesh gateway configuration for this service.
MeshGateway MeshGatewayConfig `json:"meshGateway,omitempty"`
MeshGateway MeshGateway `json:"meshGateway,omitempty"`
// Expose controls the default expose path configuration for Envoy.
Expose ExposeConfig `json:"expose,omitempty"`
Expose Expose `json:"expose,omitempty"`
// TransparentProxy controls configuration specific to proxies in transparent mode.
TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"`
}

func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta {
Expand Down Expand Up @@ -146,12 +148,13 @@ func (in *ProxyDefaults) SetLastSyncedTime(time *metav1.Time) {
func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry {
consulConfig := in.convertConfig()
return &capi.ProxyConfigEntry{
Kind: in.ConsulKind(),
Name: in.ConsulName(),
MeshGateway: in.Spec.MeshGateway.toConsul(),
Expose: in.Spec.Expose.toConsul(),
Config: consulConfig,
Meta: meta(datacenter),
Kind: in.ConsulKind(),
Name: in.ConsulName(),
MeshGateway: in.Spec.MeshGateway.toConsul(),
Expose: in.Spec.Expose.toConsul(),
Config: consulConfig,
TransparentProxy: in.Spec.TransparentProxy.toConsul(),
Meta: meta(datacenter),
}
}

Expand All @@ -171,6 +174,9 @@ func (in *ProxyDefaults) Validate(namespacesEnabled bool) error {
if err := in.Spec.MeshGateway.validate(path.Child("meshGateway")); err != nil {
allErrs = append(allErrs, err)
}
if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil {
allErrs = append(allErrs, err)
}
if err := in.validateConfig(path.Child("config")); err != nil {
allErrs = append(allErrs, err)
}
Expand Down
32 changes: 25 additions & 7 deletions api/v1alpha1/proxydefaults_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
Spec: ProxyDefaultsSpec{},
},
Theirs: &capi.ProxyConfigEntry{
Name: common.Global,
Kind: capi.ProxyDefaults,
Namespace: "default",
Name: common.Global,
Kind: capi.ProxyDefaults,
Namespace: "default",
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 0,
},
CreateIndex: 1,
ModifyIndex: 2,
Meta: map[string]string{
Expand All @@ -47,10 +50,10 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
},
Spec: ProxyDefaultsSpec{
Config: json.RawMessage(`{"envoy_tracing_json": "{\"http\":{\"name\":\"envoy.zipkin\",\"config\":{\"collector_cluster\":\"zipkin\",\"collector_endpoint\":\"/api/v1/spans\",\"shared_span_context\":false}}}"}`),
MeshGateway: MeshGatewayConfig{
MeshGateway: MeshGateway{
Mode: "local",
},
Expose: ExposeConfig{
Expose: Expose{
Checks: true,
Paths: []ExposePath{
{
Expand All @@ -67,6 +70,9 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
},
},
},
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
},
},
},
Theirs: &capi.ProxyConfigEntry{
Expand Down Expand Up @@ -95,6 +101,9 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) {
},
},
},
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 1000,
},
},
Matches: true,
},
Expand Down Expand Up @@ -138,6 +147,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
common.SourceKey: common.SourceValue,
common.DatacenterKey: "datacenter",
},
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 0,
},
},
},
"every field set": {
Expand All @@ -147,10 +159,10 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
},
Spec: ProxyDefaultsSpec{
Config: json.RawMessage(`{"envoy_tracing_json": "{\"http\":{\"name\":\"envoy.zipkin\",\"config\":{\"collector_cluster\":\"zipkin\",\"collector_endpoint\":\"/api/v1/spans\",\"shared_span_context\":false}}}"}`),
MeshGateway: MeshGatewayConfig{
MeshGateway: MeshGateway{
Mode: "remote",
},
Expose: ExposeConfig{
Expose: Expose{
Checks: true,
Paths: []ExposePath{
{
Expand All @@ -167,6 +179,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
},
},
},
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
},
},
},
Exp: &capi.ProxyConfigEntry{
Expand Down Expand Up @@ -196,6 +211,9 @@ func TestProxyDefaults_ToConsul(t *testing.T) {
},
},
},
TransparentProxy: &capi.TransparentProxyConfig{
OutboundListenerPort: 1000,
},
Meta: map[string]string{
common.SourceKey: common.SourceValue,
common.DatacenterKey: "datacenter",
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/proxydefaults_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type ProxyDefaultsWebhook struct {
// NOTE: The below line cannot be combined with any other comment. If it is
// it will break the code generation.
//
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-proxydefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=proxydefaults,versions=v1alpha1,name=mutate-proxydefaults.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-proxydefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=proxydefaults,versions=v1alpha1,name=mutate-proxydefaults.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1

func (v *ProxyDefaultsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
var proxyDefaults ProxyDefaults
Expand Down
17 changes: 16 additions & 1 deletion api/v1alpha1/proxydefaults_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestValidateProxyDefault(t *testing.T) {
Name: common.Global,
},
Spec: ProxyDefaultsSpec{
MeshGateway: MeshGatewayConfig{
MeshGateway: MeshGateway{
Mode: "local",
},
},
Expand All @@ -77,6 +77,21 @@ func TestValidateProxyDefault(t *testing.T) {
expAllow: false,
expErrMessage: "proxydefaults resource name must be \"global\"",
},
"transparentProxy value set": {
existingResources: []runtime.Object{},
newResource: &ProxyDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "global",
},
Spec: ProxyDefaultsSpec{
TransparentProxy: &TransparentProxy{
OutboundListenerPort: 1000,
},
},
},
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",
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
Expand Down
88 changes: 15 additions & 73 deletions api/v1alpha1/servicedefaults_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,38 +49,14 @@ type ServiceDefaultsSpec struct {
// service-splitter and service-router config entries for a service.
Protocol string `json:"protocol,omitempty"`
// MeshGateway controls the default mesh gateway configuration for this service.
MeshGateway MeshGatewayConfig `json:"meshGateway,omitempty"`
MeshGateway MeshGateway `json:"meshGateway,omitempty"`
// Expose controls the default expose path configuration for Envoy.
Expose ExposeConfig `json:"expose,omitempty"`
Expose Expose `json:"expose,omitempty"`
// ExternalSNI is an optional setting that allows for the TLS SNI value
// to be changed to a non-connect value when federating with an external system.
ExternalSNI string `json:"externalSNI,omitempty"`
}

// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
type ExposeConfig struct {
// Checks defines whether paths associated with Consul checks will be exposed.
// This flag triggers exposing all HTTP and GRPC check paths registered for the service.
Checks bool `json:"checks,omitempty"`

// Paths is the list of paths exposed through the proxy.
Paths []ExposePath `json:"paths,omitempty"`
}

type ExposePath struct {
// ListenerPort defines the port of the proxy's listener for exposed paths.
ListenerPort int `json:"listenerPort,omitempty"`

// Path is the path to expose through the proxy, ie. "/metrics".
Path string `json:"path,omitempty"`

// LocalPathPort is the port that the service is listening on for the given path.
LocalPathPort int `json:"localPathPort,omitempty"`

// Protocol describes the upstream's service protocol.
// Valid values are "http" and "http2", defaults to "http".
Protocol string `json:"protocol,omitempty"`
// TransparentProxy controls configuration specific to proxies in transparent mode.
TransparentProxy *TransparentProxy `json:"transparentProxy,omitempty"`
}

func (in *ServiceDefaults) ConsulKind() string {
Expand Down Expand Up @@ -160,13 +136,14 @@ func (in *ServiceDefaults) SyncedConditionStatus() corev1.ConditionStatus {
// ToConsul converts the entry into it's Consul equivalent struct.
func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry {
return &capi.ServiceConfigEntry{
Kind: in.ConsulKind(),
Name: in.ConsulName(),
Protocol: in.Spec.Protocol,
MeshGateway: in.Spec.MeshGateway.toConsul(),
Expose: in.Spec.Expose.toConsul(),
ExternalSNI: in.Spec.ExternalSNI,
Meta: meta(datacenter),
Kind: in.ConsulKind(),
Name: in.ConsulName(),
Protocol: in.Spec.Protocol,
MeshGateway: in.Spec.MeshGateway.toConsul(),
Expose: in.Spec.Expose.toConsul(),
ExternalSNI: in.Spec.ExternalSNI,
TransparentProxy: in.Spec.TransparentProxy.toConsul(),
Meta: meta(datacenter),
}
}

Expand All @@ -179,6 +156,9 @@ func (in *ServiceDefaults) Validate(namespacesEnabled bool) error {
if err := in.Spec.MeshGateway.validate(path.Child("meshGateway")); err != nil {
allErrs = append(allErrs, err)
}
if err := in.Spec.TransparentProxy.validate(path.Child("transparentProxy")); err != nil {
allErrs = append(allErrs, err)
}
allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...)

if len(allErrs) > 0 {
Expand Down Expand Up @@ -208,41 +188,3 @@ func (in *ServiceDefaults) MatchesConsul(candidate capi.ConfigEntry) bool {
func (in *ServiceDefaults) ConsulGlobalResource() bool {
return false
}

// toConsul returns the ExposeConfig for the entry
func (e ExposeConfig) toConsul() capi.ExposeConfig {
var paths []capi.ExposePath
for _, path := range e.Paths {
paths = append(paths, capi.ExposePath{
ListenerPort: path.ListenerPort,
Path: path.Path,
LocalPathPort: path.LocalPathPort,
Protocol: path.Protocol,
})
}
return capi.ExposeConfig{
Checks: e.Checks,
Paths: paths,
}
}

func (e ExposeConfig) validate(path *field.Path) []*field.Error {
var errs field.ErrorList
protocols := []string{"http", "http2"}
for i, pathCfg := range e.Paths {
indexPath := path.Child("paths").Index(i)
if invalidPathPrefix(pathCfg.Path) {
errs = append(errs, field.Invalid(
indexPath.Child("path"),
pathCfg.Path,
`must begin with a '/'`))
}
if pathCfg.Protocol != "" && !sliceContains(protocols, pathCfg.Protocol) {
errs = append(errs, field.Invalid(
indexPath.Child("protocol"),
pathCfg.Protocol,
notInSliceMessage(protocols)))
}
}
return errs
}
Loading

0 comments on commit 2e98ec9

Please sign in to comment.