Skip to content

Commit

Permalink
feat(kuma-cp) domain name support in dataplane.networking.address (#965)
Browse files Browse the repository at this point in the history
Signed-off-by: Ilya Lobkov <[email protected]>
  • Loading branch information
lobkovilya authored Aug 19, 2020
1 parent adda484 commit 19421f8
Show file tree
Hide file tree
Showing 18 changed files with 358 additions and 27 deletions.
26 changes: 23 additions & 3 deletions app/kuma-dp/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"

kuma_version "github.com/kumahq/kuma/pkg/version"
Expand Down Expand Up @@ -67,12 +68,14 @@ func newRunCmd() *cobra.Command {
return errors.Wrap(err, "could retrieve catalog")
}
if catalog.Apis.DataplaneToken.Enabled() {
if cfg.DataplaneRuntime.TokenPath == "" {
if cfg.DataplaneRuntime.TokenPath == "" && cfg.DataplaneRuntime.Token == "" {
return errors.New("Kuma CP is configured with Dataplane Token Server therefore the Dataplane Token is required. " +
"Generate token using 'kumactl generate dataplane-token > /path/file' and provide it via --dataplane-token-file=/path/file argument to Kuma DP")
}
if err := kumadp_config.ValidateTokenPath(cfg.DataplaneRuntime.TokenPath); err != nil {
return err
if cfg.DataplaneRuntime.TokenPath != "" {
if err := kumadp_config.ValidateTokenPath(cfg.DataplaneRuntime.TokenPath); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -101,6 +104,15 @@ func newRunCmd() *cobra.Command {
runLog.Info("generated Envoy configuration will be stored in a temporary directory", "dir", tmpDir)
}

if cfg.DataplaneRuntime.Token != "" {
path := filepath.Join(cfg.DataplaneRuntime.ConfigDir, cfg.Dataplane.Name)
if err := writeFile(path, []byte(cfg.DataplaneRuntime.Token), 0600); err != nil {
runLog.Error(err, "unable to create file with dataplane token")
return err
}
cfg.DataplaneRuntime.TokenPath = path
}

dataplane, err := envoy.New(envoy.Opts{
Catalog: catalog,
Config: cfg,
Expand Down Expand Up @@ -135,5 +147,13 @@ func newRunCmd() *cobra.Command {
cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.BinaryPath, "binary-path", cfg.DataplaneRuntime.BinaryPath, "Binary path of Envoy executable")
cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.ConfigDir, "config-dir", cfg.DataplaneRuntime.ConfigDir, "Directory in which Envoy config will be generated")
cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.TokenPath, "dataplane-token-file", cfg.DataplaneRuntime.TokenPath, "Path to a file with dataplane token (use 'kumactl generate dataplane-token' to get one)")
cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.Token, "dataplane-token", cfg.DataplaneRuntime.Token, "Dataplane Token")
return cmd
}

func writeFile(filename string, data []byte, perm os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(filename), perm); err != nil {
return err
}
return ioutil.WriteFile(filename, data, perm)
}
3 changes: 2 additions & 1 deletion pkg/api-server/config_ws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ var _ = Describe("Config WS", func() {
},
"environment": "universal",
"general": {
"advertisedHostname": "localhost"
"advertisedHostname": "localhost",
"dnsCacheTTL": "10s"
},
"guiServer": {
"apiServerUrl": ""
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/app/kuma-cp/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kuma_cp

import (
"time"

"github.com/pkg/errors"

"github.com/kumahq/kuma/pkg/config/multicluster"
Expand Down Expand Up @@ -269,6 +271,8 @@ type GeneralConfig struct {
// Hostname that other components should use in order to connect to the Control Plane.
// Control Plane will use this value in configuration generated for dataplanes, in responses to `kumactl`, etc.
AdvertisedHostname string `yaml:"advertisedHostname" envconfig:"kuma_general_advertised_hostname"`
// DNSCacheTTL represents duration for how long Kuma CP will cache result of resolving dataplane's domain name
DNSCacheTTL time.Duration `yaml:"dnsCacheTTL" envconfig:"kuma_general_dns_cache_ttl"`
}

var _ config.Config = &GeneralConfig{}
Expand All @@ -283,5 +287,6 @@ func (g *GeneralConfig) Validate() error {
func DefaultGeneralConfig() *GeneralConfig {
return &GeneralConfig{
AdvertisedHostname: "localhost",
DNSCacheTTL: 10 * time.Second,
}
}
2 changes: 2 additions & 0 deletions pkg/config/app/kuma-cp/kuma-cp.defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ general:
# Hostname that other components should use in order to connect to the Control Plane.
# Control Plane will use this value in configuration generated for dataplanes, in responses to `kumactl`, etc.
advertisedHostname: localhost # ENV: KUMA_GENERAL_ADVERTISED_HOSTNAME
# dnsCacheTTL represents duration for how long Kuma CP will cache result of resolving dataplane's domain name
dnsCacheTTL: 10s

# Web GUI Server configuration
guiServer:
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/app/kuma-dp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ type DataplaneRuntime struct {
ConfigDir string `yaml:"configDir,omitempty" envconfig:"kuma_dataplane_runtime_config_dir"`
// Path to a file with dataplane token (use 'kumactl generate dataplane-token' to get one)
TokenPath string `yaml:"dataplaneTokenPath,omitempty" envconfig:"kuma_dataplane_runtime_token_path"`
// Token is dataplane token's value provided directly, will be stored to a temporary file before applying
Token string `yaml:"dataplaneToken,omitempty" envconfig:"kuma_dataplane_runtime_token"`
}

var _ config.Config = &Config{}
Expand Down
4 changes: 4 additions & 0 deletions pkg/core/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package bootstrap

import (
"context"
"net"

"github.com/kumahq/kuma/pkg/core/managers/apis/zoneinsight"

"github.com/kumahq/kuma/api/mesh/v1alpha1"
"github.com/kumahq/kuma/pkg/core/dns/lookup"

config_manager "github.com/kumahq/kuma/pkg/core/config/manager"
"github.com/kumahq/kuma/pkg/core/resources/apis/system"
Expand Down Expand Up @@ -84,6 +86,8 @@ func buildRuntime(cfg kuma_cp.Config) (core_runtime.Runtime, error) {
leaderInfoComponent := &component.LeaderInfoComponent{}
builder.WithLeaderInfo(leaderInfoComponent)

builder.WithLookupIP(lookup.CachedLookupIP(net.LookupIP, cfg.General.DNSCacheTTL))

rt, err := builder.Build()
if err != nil {
return nil, err
Expand Down
39 changes: 39 additions & 0 deletions pkg/core/dns/lookup/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package lookup

import (
"net"
"sync"
"time"

"github.com/kumahq/kuma/pkg/core"
)

type cacheRecord struct {
ips []net.IP
creationTime time.Time
}

func CachedLookupIP(f LookupIPFunc, ttl time.Duration) LookupIPFunc {
cache := map[string]*cacheRecord{}
var rwmux sync.RWMutex
return func(host string) ([]net.IP, error) {
rwmux.RLock()
r, ok := cache[host]
rwmux.RUnlock()

if ok && r.creationTime.Add(ttl).After(core.Now()) {
return r.ips, nil
}

ips, err := f(host)
if err != nil {
return nil, err
}

rwmux.Lock()
cache[host] = &cacheRecord{ips: ips, creationTime: core.Now()}
rwmux.Unlock()

return ips, nil
}
}
13 changes: 13 additions & 0 deletions pkg/core/dns/lookup/cache_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lookup_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestDNSCaching(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "DNS with cache Suite")
}
52 changes: 52 additions & 0 deletions pkg/core/dns/lookup/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package lookup_test

import (
"net"
"time"

"github.com/kumahq/kuma/pkg/core"
"github.com/kumahq/kuma/pkg/core/dns/lookup"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("DNS with cache", func() {
var counter int
var table map[string][]net.IP
lookupFunc := func(host string) ([]net.IP, error) {
counter++
return table[host], nil
}
var cachingLookupFunc lookup.LookupIPFunc

BeforeEach(func() {
cachingLookupFunc = lookup.CachedLookupIP(lookupFunc, 1*time.Second)
table = map[string][]net.IP{}
counter = 0
})

It("should use cache on the second call", func() {
_, _ = cachingLookupFunc("example.com")
_, _ = cachingLookupFunc("example.com")
Expect(counter).To(Equal(1))
})

It("should avoid cache after TTL", func() {
table["example.com"] = []net.IP{net.ParseIP("192.168.0.1")}

ip, _ := cachingLookupFunc("example.com")
Expect(ip[0]).To(Equal(net.ParseIP("192.168.0.1")))

ip, _ = cachingLookupFunc("example.com")
Expect(ip[0]).To(Equal(net.ParseIP("192.168.0.1")))

table["example.com"] = []net.IP{net.ParseIP("10.20.0.1")}
core.Now = func() time.Time {
return time.Now().Add(2 * time.Second)
}
ip, _ = cachingLookupFunc("example.com")
Expect(ip[0]).To(Equal(net.ParseIP("10.20.0.1")))
Expect(counter).To(Equal(2))
})
})
7 changes: 7 additions & 0 deletions pkg/core/dns/lookup/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lookup

import (
"net"
)

type LookupIPFunc func(string) ([]net.IP, error)
19 changes: 16 additions & 3 deletions pkg/core/resources/apis/mesh/dataplane_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mesh
import (
"fmt"
"net"
"regexp"

mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
"github.com/kumahq/kuma/pkg/core/validators"
Expand Down Expand Up @@ -31,9 +32,7 @@ func validateNetworking(networking *mesh_proto.Dataplane_Networking) validators.
if len(networking.GetInbound()) > 0 && networking.Gateway != nil {
err.AddViolationAt(path, "inbound cannot be defined both with gateway")
}
if net.ParseIP(networking.Address) == nil {
err.AddViolationAt(path.Field("address"), "address has to be valid IP address")
}
err.Add(validateAddress(path, networking.Address))
if networking.Gateway != nil {
result := validateGateway(networking.Gateway)
err.AddErrorAt(path.Field("gateway"), result)
Expand All @@ -49,6 +48,20 @@ func validateNetworking(networking *mesh_proto.Dataplane_Networking) validators.
return err
}

var DNSRegex = regexp.MustCompile(`^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`)

func validateAddress(path validators.PathBuilder, address string) validators.ValidationError {
var err validators.ValidationError
if address == "" {
err.AddViolationAt(path.Field("address"), "address can't be empty")
return err
}
if !DNSRegex.MatchString(address) {
err.AddViolationAt(path.Field("address"), "address has to be valid IP address or domain name")
}
return err
}

func validateIngressNetworking(networking *mesh_proto.Dataplane_Networking) validators.ValidationError {
var err validators.ValidationError
path := validators.RootedAt("networking")
Expand Down
62 changes: 49 additions & 13 deletions pkg/core/resources/apis/mesh/dataplane_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ var _ = Describe("Dataplane", func() {
inbound:
- port: 10001`,
),
Entry("dataplane domain name in the address", `
type: Dataplane
name: dp-1
mesh: default
networking:
address: example.com
inbound:
- port: 8080
tags:
kuma.io/service: backend
version: "1"
outbound:
- port: 3333
tags:
kuma.io/service: redis`,
),
)

type testCase struct {
Expand Down Expand Up @@ -171,50 +187,70 @@ var _ = Describe("Dataplane", func() {
- field: networking
message: has to contain at least one inbound interface or gateway`,
}),
Entry("networking: both inbounds and gateway are defined", testCase{
Entry("networking.address: empty", testCase{
dataplane: `
type: Dataplane
name: dp-1
mesh: default
networking:
address: 192.168.0.1
inbound:
- port: 8080
servicePort: 7777
tags:
kuma.io/service: backend
version: "1"
gateway:
tags:
kuma.io/service: kong
outbound:
- port: 3333
service: redis`,
tags:
kuma.io/service: redis`,
expected: `
violations:
- field: networking
message: inbound cannot be defined both with gateway`,
- field: networking.address
message: address can't be empty`,
}),
Entry("networking: invalid address", testCase{
Entry("networking.address: invalid format", testCase{
dataplane: `
type: Dataplane
name: dp-1
mesh: default
networking:
address: invalid
address: ..>_<..
inbound:
- port: 8080
tags:
kuma.io/service: backend
version: "1"
outbound:
- port: 3333
tags:
kuma.io/service: redis`,
expected: `
violations:
- field: networking.address
message: address has to be valid IP address or domain name`,
}),
Entry("networking: both inbounds and gateway are defined", testCase{
dataplane: `
type: Dataplane
name: dp-1
mesh: default
networking:
address: 192.168.0.1
inbound:
- port: 8080
servicePort: 7777
tags:
kuma.io/service: backend
version: "1"
gateway:
tags:
kuma.io/service: kong
outbound:
- port: 3333
service: redis`,
expected: `
violations:
- field: networking.address
message: address has to be valid IP address`,
- field: networking
message: inbound cannot be defined both with gateway`,
}),
Entry("networking.inbound: port of the range", testCase{
dataplane: `
Expand Down
Loading

0 comments on commit 19421f8

Please sign in to comment.