consul/api/config_entry_gateways_test.go

498 lines
13 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestAPI_ConfigEntries_IngressGateway(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
config_entries := c.ConfigEntries()
ingress1 := &IngressGatewayConfigEntry{
Kind: IngressGateway,
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
}
ingress2 := &IngressGatewayConfigEntry{
Kind: IngressGateway,
Name: "bar",
TLS: GatewayTLSConfig{
ingress: allow setting TLS min version and cipher suites in ingress gateway config entries (#11576) * xds: refactor ingress listener SDS configuration * xds: update resolveListenerSDS call args in listeners_test * ingress: add TLS min, max and cipher suites to GatewayTLSConfig * xds: implement envoyTLSVersions and envoyTLSCipherSuites * xds: merge TLS config * xds: configure TLS parameters with ingress TLS context from leaf * xds: nil check in resolveListenerTLSConfig validation * xds: nil check in makeTLSParameters* functions * changelog: add entry for TLS params on ingress config entries * xds: remove indirection for TLS params in TLSConfig structs * xds: return tlsContext, nil instead of ambiguous err Co-authored-by: Chris S. Kim <ckim@hashicorp.com> * xds: switch zero checks to types.TLSVersionUnspecified * ingress: add validation for ingress config entry TLS params * ingress: validate listener TLS config * xds: add basic ingress with TLS params tests * xds: add ingress listeners mixed TLS min version defaults precedence test * xds: add more explicit tests for ingress listeners inheriting gateway defaults * xds: add test for single TLS listener on gateway without TLS defaults * xds: regen golden files for TLSVersionInvalid zero value, add TLSVersionAuto listener test * types/tls: change TLSVersion to string * types/tls: update TLSCipherSuite to string type * types/tls: implement validation functions for TLSVersion and TLSCipherSuites, make some maps private * api: add TLS params to GatewayTLSConfig, add tests * api: add TLSMinVersion to ingress gateway config entry test JSON * xds: switch to Envoy TLS cipher suite encoding from types package * xds: fixup validation for TLSv1_3 min version with cipher suites * add some kitchen sink tests and add a missing struct tag * xds: check if mergedCfg.TLSVersion is in TLSVersionsWithConfigurableCipherSuites * xds: update connectTLSEnabled comment * xds: remove unsued resolveGatewayServiceTLSConfig function * xds: add makeCommonTLSContextFromLeafWithoutParams * types/tls: add LessThan comparator function for concrete values * types/tls: change tlsVersions validation map from string to TLSVersion keys * types/tls: remove unused envoyTLSCipherSuites * types/tls: enable chacha20 cipher suites for Consul agent * types/tls: remove insecure cipher suites from allowed config TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 are both explicitly listed as insecure and disabled in the Go source. Refs https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/crypto/tls/cipher_suites.go;l=329-330 * types/tls: add ValidateConsulAgentCipherSuites function, make direct lookup map private * types/tls: return all unmatched cipher suites in validation errors * xds: check that Envoy API value matching TLS version is found when building TlsParameters * types/tls: check that value is found in map before appending to slice in MarshalEnvoyTLSCipherSuiteStrings * types/tls: cast to string rather than fmt.Printf in TLSCihperSuite.String() * xds: add TLSVersionUnspecified to list of configurable cipher suites * structs: update note about config entry warning * xds: remove TLS min version cipher suite unconfigurable test placeholder * types/tls: update tests to remove assumption about private map values Co-authored-by: R.B. Boyer <rb@hashicorp.com>
2022-01-11 16:46:42 +00:00
Enabled: true,
TLSMinVersion: "TLSv1_2",
},
Defaults: &IngressServiceConfig{
MaxConnections: uint32Pointer(2048),
MaxPendingRequests: uint32Pointer(4096),
PassiveHealthCheck: &PassiveHealthCheck{
MaxFailures: 20,
Interval: 500000000,
},
},
}
global := &ProxyConfigEntry{
Kind: ProxyDefaults,
Name: ProxyConfigGlobal,
Config: map[string]interface{}{
"protocol": "http",
},
}
// set default protocol to http so that ingress gateways pass validation
_, wm, err := config_entries.Set(global, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// set it
_, wm, err = config_entries.Set(ingress1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// also set the second one
_, wm, err = config_entries.Set(ingress2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := config_entries.Get(IngressGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// verify it
readIngress, ok := entry.(*IngressGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, ingress1.Kind, readIngress.Kind)
require.Equal(t, ingress1.Name, readIngress.Name)
require.Equal(t, ingress1.Meta, readIngress.Meta)
require.Equal(t, ingress1.Meta, readIngress.GetMeta())
// update it
ingress1.Listeners = []IngressListener{
{
Port: 2222,
Protocol: "http",
Services: []IngressService{
{
Name: "asdf",
Hosts: []string{"test.example.com"},
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{
"x-foo": "bar",
},
},
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-foo"},
},
TLS: &GatewayServiceTLSConfig{
SDS: &GatewayTLSSDSConfig{
ClusterName: "foo",
CertResource: "bar",
},
},
MaxConnections: uint32Pointer(5120),
MaxPendingRequests: uint32Pointer(512),
MaxConcurrentRequests: uint32Pointer(2048),
PassiveHealthCheck: &PassiveHealthCheck{
MaxFailures: 10,
},
},
},
TLS: &GatewayTLSConfig{
SDS: &GatewayTLSSDSConfig{
ClusterName: "baz",
CertResource: "qux",
},
},
},
}
ingress1.TLS = GatewayTLSConfig{
SDS: &GatewayTLSSDSConfig{
ClusterName: "qux",
CertResource: "bug",
},
}
// CAS fail
written, _, err := config_entries.CAS(ingress1, 0, nil)
require.NoError(t, err)
require.False(t, written)
// CAS success
written, wm, err = config_entries.CAS(ingress1, readIngress.ModifyIndex, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
require.True(t, written)
// update no cas
ingress2.Listeners = []IngressListener{
{
Port: 3333,
Protocol: "http",
Services: []IngressService{
{
Name: "qwer",
},
},
},
}
_, wm, err = config_entries.Set(ingress2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// list them
entries, qm, err := config_entries.List(IngressGateway, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
require.Len(t, entries, 2)
for _, entry = range entries {
switch entry.GetName() {
case "foo":
// this also verifies that the update value was persisted and
// the updated values are seen
readIngress, ok = entry.(*IngressGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, ingress1.Kind, readIngress.Kind)
require.Equal(t, ingress1.Name, readIngress.Name)
require.Len(t, readIngress.Listeners, 1)
require.Len(t, readIngress.Listeners[0].Services, 1)
2021-11-08 20:22:10 +00:00
// Set namespace and partition to blank so that OSS and ent can utilize the same tests
readIngress.Listeners[0].Services[0].Namespace = ""
2021-11-08 20:22:10 +00:00
readIngress.Listeners[0].Services[0].Partition = ""
require.Equal(t, ingress1.Listeners, readIngress.Listeners)
case "bar":
readIngress, ok = entry.(*IngressGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, ingress2.Kind, readIngress.Kind)
require.Equal(t, ingress2.Name, readIngress.Name)
require.Equal(t, *ingress2.Defaults.MaxConnections, *readIngress.Defaults.MaxConnections)
require.Equal(t, uint32(4096), *readIngress.Defaults.MaxPendingRequests)
require.Equal(t, uint32(0), *readIngress.Defaults.MaxConcurrentRequests)
require.Equal(t, uint32(20), readIngress.Defaults.PassiveHealthCheck.MaxFailures)
require.Equal(t, time.Duration(500000000), readIngress.Defaults.PassiveHealthCheck.Interval)
require.Nil(t, readIngress.Defaults.PassiveHealthCheck.EnforcingConsecutive5xx)
require.Len(t, readIngress.Listeners, 1)
require.Len(t, readIngress.Listeners[0].Services, 1)
2021-11-08 20:22:10 +00:00
// Set namespace and partition to blank so that OSS and ent can utilize the same tests
readIngress.Listeners[0].Services[0].Namespace = ""
2021-11-08 20:22:10 +00:00
readIngress.Listeners[0].Services[0].Partition = ""
require.Equal(t, ingress2.Listeners, readIngress.Listeners)
}
}
// delete it
wm, err = config_entries.Delete(IngressGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// verify deletion
_, _, err = config_entries.Get(IngressGateway, "foo", nil)
require.Error(t, err)
}
func TestAPI_ConfigEntries_TerminatingGateway(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
configEntries := c.ConfigEntries()
terminating1 := &TerminatingGatewayConfigEntry{
Kind: TerminatingGateway,
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
}
terminating2 := &TerminatingGatewayConfigEntry{
Kind: TerminatingGateway,
Name: "bar",
}
// set it
_, wm, err := configEntries.Set(terminating1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// also set the second one
_, wm, err = configEntries.Set(terminating2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := configEntries.Get(TerminatingGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// verify it
readTerminating, ok := entry.(*TerminatingGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, terminating1.Kind, readTerminating.Kind)
require.Equal(t, terminating1.Name, readTerminating.Name)
require.Equal(t, terminating1.Meta, readTerminating.Meta)
require.Equal(t, terminating1.Meta, readTerminating.GetMeta())
// update it
terminating1.Services = []LinkedService{
{
Name: "web",
CAFile: "/etc/web/ca.crt",
CertFile: "/etc/web/client.crt",
KeyFile: "/etc/web/tls.key",
SNI: "mydomain",
},
}
// CAS fail
written, _, err := configEntries.CAS(terminating1, 0, nil)
require.NoError(t, err)
require.False(t, written)
// CAS success
written, wm, err = configEntries.CAS(terminating1, readTerminating.ModifyIndex, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
require.True(t, written)
// re-setting should not yield an error
_, wm, err = configEntries.Set(terminating1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
terminating2.Services = []LinkedService{
{
Name: "*",
CAFile: "/etc/certs/ca.crt",
CertFile: "/etc/certs/client.crt",
KeyFile: "/etc/certs/tls.key",
SNI: "mydomain",
},
}
_, wm, err = configEntries.Set(terminating2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// list them
entries, qm, err := configEntries.List(TerminatingGateway, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
require.Len(t, entries, 2)
for _, entry = range entries {
switch entry.GetName() {
case "foo":
// this also verifies that the update value was persisted and
// the updated values are seen
readTerminating, ok = entry.(*TerminatingGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, terminating1.Kind, readTerminating.Kind)
require.Equal(t, terminating1.Name, readTerminating.Name)
require.Len(t, readTerminating.Services, 1)
// Set namespace to blank so that OSS and ent can utilize the same tests
readTerminating.Services[0].Namespace = ""
require.Equal(t, terminating1.Services, readTerminating.Services)
case "bar":
readTerminating, ok = entry.(*TerminatingGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, terminating2.Kind, readTerminating.Kind)
require.Equal(t, terminating2.Name, readTerminating.Name)
require.Len(t, readTerminating.Services, 1)
// Set namespace to blank so that OSS and ent can utilize the same tests
readTerminating.Services[0].Namespace = ""
require.Equal(t, terminating2.Services, readTerminating.Services)
}
}
// delete it
wm, err = configEntries.Delete(TerminatingGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// verify deletion
_, _, err = configEntries.Get(TerminatingGateway, "foo", nil)
require.Error(t, err)
}
func TestAPI_ConfigEntries_APIGateway(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
configEntries := c.ConfigEntries()
listener1 := APIGatewayListener{
Name: "listener1",
Hostname: "host.com",
Port: 3360,
Protocol: "http",
}
listener2 := APIGatewayListener{
Name: "listener2",
Hostname: "host2.com",
Port: 3362,
Protocol: "http",
}
apigw1 := &APIGatewayConfigEntry{
Kind: APIGateway,
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
Listeners: []APIGatewayListener{listener1},
}
apigw2 := &APIGatewayConfigEntry{
Kind: APIGateway,
Name: "bar",
Listeners: []APIGatewayListener{listener2},
}
// set it
_, wm, err := configEntries.Set(apigw1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// also set the second one
_, wm, err = configEntries.Set(apigw2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := configEntries.Get(APIGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// verify it
readGW, ok := entry.(*APIGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, apigw1.Kind, readGW.Kind)
require.Equal(t, apigw1.Name, readGW.Name)
require.Equal(t, apigw1.Meta, readGW.Meta)
require.Equal(t, apigw1.Meta, readGW.GetMeta())
// update it
apigw1.Listeners = []APIGatewayListener{
listener1,
{
Name: "listener3",
Hostname: "host3.com",
Port: 3363,
Protocol: "http",
},
}
// CAS fail
written, _, err := configEntries.CAS(apigw1, 0, nil)
require.NoError(t, err)
require.False(t, written)
// CAS success
written, wm, err = configEntries.CAS(apigw1, readGW.ModifyIndex, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
require.True(t, written)
// re-setting should not yield an error
_, wm, err = configEntries.Set(apigw1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
apigw2.Listeners = []APIGatewayListener{
listener2,
{
Name: "listener4",
Hostname: "host4.com",
Port: 3364,
Protocol: "http",
},
}
_, wm, err = configEntries.Set(apigw2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// list them
entries, qm, err := configEntries.List(APIGateway, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
require.Len(t, entries, 2)
for _, entry = range entries {
switch entry.GetName() {
case "foo":
// this also verifies that the update value was persisted and
// the updated values are seen
readGW, ok = entry.(*APIGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, apigw1.Kind, readGW.Kind)
require.Equal(t, apigw1.Name, readGW.Name)
require.Len(t, readGW.Listeners, 2)
require.Equal(t, apigw1.Listeners, readGW.Listeners)
case "bar":
readGW, ok = entry.(*APIGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, apigw2.Kind, readGW.Kind)
require.Equal(t, apigw2.Name, readGW.Name)
require.Len(t, readGW.Listeners, 2)
require.Equal(t, apigw2.Listeners, readGW.Listeners)
}
}
// delete it
wm, err = configEntries.Delete(APIGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// verify deletion
_, _, err = configEntries.Get(APIGateway, "foo", nil)
require.Error(t, err)
}