Skip to content

Commit

Permalink
feat: add subnet filter for etcd address
Browse files Browse the repository at this point in the history
This adds the ability to specify the subnet that `etcd`'s listen address
should be in. This allows users to ensure that `etcd` is on a private
subnet.

Signed-off-by: Andrew Rynhard <andrew@rynhard.io>
  • Loading branch information
andrewrynhard committed Aug 30, 2021
1 parent 3c3c281 commit 668627d
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 8 deletions.
30 changes: 23 additions & 7 deletions internal/app/machined/pkg/system/services/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ func (e *Etcd) argsForInit(ctx context.Context, r runtime.Runtime) error {
_, upgraded = meta.LegacyADV.ReadTag(adv.Upgrade)
}

primaryAddr, listenAddress, err := primaryAndListenAddresses()
primaryAddr, listenAddress, err := primaryAndListenAddresses(r.Config().Cluster().Etcd().Subnet())
if err != nil {
return fmt.Errorf("failed to calculate etcd addresses: %w", err)
}
Expand Down Expand Up @@ -521,7 +521,7 @@ func (e *Etcd) argsForControlPlane(ctx context.Context, r runtime.Runtime) error
// extraArgs (which may contain special overrides from the user.
// This needs to be refactored to allow greater binding flexibility.
// Issue #2121.
primaryAddr, listenAddress, err := primaryAndListenAddresses()
primaryAddr, listenAddress, err := primaryAndListenAddresses(r.Config().Cluster().Etcd().Subnet())
if err != nil {
return fmt.Errorf("failed to calculate etcd addresses: %w", err)
}
Expand Down Expand Up @@ -673,7 +673,7 @@ func IsDirEmpty(name string) (bool, error) {
}

// primaryAndListenAddresses calculates the primary (advertised) and listen (bind) addresses for etcd.
func primaryAndListenAddresses() (primary, listen string, err error) {
func primaryAndListenAddresses(subnet string) (primary, listen string, err error) {
ips, err := net.IPAddrs()
if err != nil {
return "", "", fmt.Errorf("failed to discover interface IP addresses: %w", err)
Expand All @@ -683,10 +683,26 @@ func primaryAndListenAddresses() (primary, listen string, err error) {
return "", "", errors.New("no valid unicast IP addresses on any interface")
}

// NOTE: we will later likely want to expose the primary IP selection to the
// user or build it with greater flexibility. For now, this maintains
// previous behavior.
primary = ips[0].String()
if subnet == "" {
primary = ips[0].String()
} else {
network, err := net.ParseCIDR(subnet)
if err != nil {
return "", "", fmt.Errorf("failed to parse subnet: %w", err)
}

for _, ip := range ips {
if network.Contains(ip) {
primary = ip.String()

break
}
}

if primary == "" {
return "", "", errors.New("no address matched the provided subnet")
}
}

// Regardless of primary selected IP, we should be liberal with our listen
// address, for maximum compatibility. Again, this should probably be
Expand Down
1 change: 1 addition & 0 deletions pkg/machinery/config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ type Etcd interface {
Image() string
CA() *x509.PEMEncodedCertificateAndKey
ExtraArgs() map[string]string
Subnet() string
}

// Token defines the requirements for a config that pertains to Kubernetes
Expand Down
5 changes: 5 additions & 0 deletions pkg/machinery/config/types/v1alpha1/v1alpha1_etcdconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ func (e *EtcdConfig) ExtraArgs() map[string]string {

return e.EtcdExtraArgs
}

// Subnet implements the config.Etcd interface.
func (e *EtcdConfig) Subnet() string {
return e.EtcdSubnet
}
8 changes: 8 additions & 0 deletions pkg/machinery/config/types/v1alpha1/v1alpha1_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ var (

clusterEtcdImageExample = (&EtcdConfig{}).Image()

clusterEtcdSubnetExample = (&EtcdConfig{EtcdSubnet: "10.0.0.0/8"}).Subnet()

clusterCoreDNSExample = &CoreDNS{
CoreDNSImage: (&CoreDNS{}).Image(),
}
Expand Down Expand Up @@ -1278,6 +1280,12 @@ type EtcdConfig struct {
// "advertise-client-urls": "https://1.2.3.4:2379",
// }
EtcdExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
// description: |
// The subnet from which the advertise URL should be.
//
// examples:
// - value: clusterEtcdSubnetExample
EtcdSubnet string `yaml:"subnet,omitempty"`
}

// ClusterNetworkConfig represents kube networking configuration options.
Expand Down
10 changes: 9 additions & 1 deletion pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ func (c *Config) Validate(mode config.RuntimeMode, options ...config.ValidationO
}

// Validate validates the config.
//
//nolint:gocyclo
func (c *ClusterConfig) Validate() error {
var result *multierror.Error

Expand All @@ -225,6 +227,12 @@ func (c *ClusterConfig) Validate() error {
result = multierror.Append(result, ecp.Validate())
}

if c.EtcdConfig != nil && c.EtcdConfig.EtcdSubnet != "" {
if _, _, err := net.ParseCIDR(c.EtcdConfig.EtcdSubnet); err != nil {
result = multierror.Append(result, fmt.Errorf("%q is not a valid subnet", c.EtcdConfig.EtcdSubnet))
}
}

result = multierror.Append(result, c.ClusterInlineManifests.Validate(), c.ClusterDiscoveryConfig.Validate(c))

return result.ErrorOrNil()
Expand Down
40 changes: 40 additions & 0 deletions pkg/machinery/config/types/v1alpha1/v1alpha1_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,46 @@ func TestValidate(t *testing.T) {
},
expectedError: "2 errors occurred:\n\t* cluster discovery service requires .cluster.id\n\t* cluster discovery service requires .cluster.secret\n\n",
},
{
name: "GoodEtcdSubnet",
config: &v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "controlplane",
},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
endpointURL,
},
},
EtcdConfig: &v1alpha1.EtcdConfig{
EtcdSubnet: "10.0.0.0/8",
},
},
},
expectedError: "",
},
{
name: "BadEtcdSubnet",
config: &v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "controlplane",
},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
endpointURL,
},
},
EtcdConfig: &v1alpha1.EtcdConfig{
EtcdSubnet: "10.0.0.0",
},
},
},
expectedError: "1 error occurred:\n\t* \"10.0.0.0\" is not a valid subnet\n\n",
},
} {
test := test

Expand Down
28 changes: 28 additions & 0 deletions website/content/docs/v0.13/Reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,9 @@ etcd:
# Extra arguments to supply to etcd.
extraArgs:
election-timeout: "5000"

# # The subnet from which the advertise URL should be.
# subnet: 10.0.0.0/8
```


Expand Down Expand Up @@ -2714,6 +2717,9 @@ ca:
# Extra arguments to supply to etcd.
extraArgs:
election-timeout: "5000"

# # The subnet from which the advertise URL should be.
# subnet: 10.0.0.0/8
```

<hr />
Expand Down Expand Up @@ -2788,6 +2794,28 @@ Note that the following args are not allowed:
- `peer-trusted-ca-file`
- `peer-key-file`

</div>

<hr />
<div class="dd">

<code>subnet</code> <i>string</i>

</div>
<div class="dt">

The subnet from which the advertise URL should be.



Examples:


``` yaml
subnet: 10.0.0.0/8
```


</div>

<hr />
Expand Down

0 comments on commit 668627d

Please sign in to comment.