talos/pkg/userdata/services.go
Seán C McCord 8884b85905 fix(trustd): allow hostnames for trustd endpoints
Fixes #666

Also adds IPv6 to tests for trustd endpoints

Signed-off-by: Seán C McCord <ulexus@gmail.com>
2019-07-24 15:28:03 -07:00

221 lines
6.3 KiB
Go

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package userdata
import (
"net"
"regexp"
"strconv"
"github.com/hashicorp/go-multierror"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/xerrors"
)
// ValidHostnamePattern is a pattern which should match valid DNS hostnames according to RFC1123
const ValidHostnamePattern = `^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`
var validHostnameRegex *regexp.Regexp
func init() {
validHostnameRegex = regexp.MustCompile(ValidHostnamePattern)
}
// Env represents a set of environment variables.
type Env = map[string]string
// Services represents the set of services available to configure.
type Services struct {
Init *Init `yaml:"init"`
Kubelet *Kubelet `yaml:"kubelet"`
Kubeadm *Kubeadm `yaml:"kubeadm"`
Trustd *Trustd `yaml:"trustd"`
Proxyd *Proxyd `yaml:"proxyd"`
OSD *OSD `yaml:"osd"`
CRT *CRT `yaml:"crt"`
NTPd *NTPd `yaml:"ntp"`
}
// Validate triggers the specified validation checks to run
func (s *Services) Validate(checks ...ServiceCheck) error {
var result *multierror.Error
for _, check := range checks {
result = multierror.Append(result, check(s))
}
return result.ErrorOrNil()
}
// ServiceCheck defines the function type for checks
type ServiceCheck func(*Services) error
// CheckServices ensures the minimum necessary services config has been provided
func CheckServices() ServiceCheck {
return func(s *Services) error {
var result *multierror.Error
if s.Kubeadm == nil {
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.kubeadm", "", ErrRequiredSection))
}
if s.Trustd == nil {
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.trustd", "", ErrRequiredSection))
}
return result.ErrorOrNil()
}
}
// OSD describes the configuration of the osd service.
type OSD struct {
CommonServiceOptions `yaml:",inline"`
}
// Proxyd describes the configuration of the proxyd service.
type Proxyd struct {
CommonServiceOptions `yaml:",inline"`
}
// CRT describes the configuration of the container runtime service.
type CRT struct {
CommonServiceOptions `yaml:",inline"`
}
// CommonServiceOptions represents the set of options common to all services.
type CommonServiceOptions struct {
Env Env `yaml:"env,omitempty"`
}
// NTPd describes the configuration of the ntp service.
type NTPd struct {
CommonServiceOptions `yaml:",inline"`
Server string `yaml:"server,omitempty"`
}
// Kubelet describes the configuration of the kubelet service.
type Kubelet struct {
CommonServiceOptions `yaml:",inline"`
ExtraMounts []specs.Mount `yaml:"extraMounts"`
}
// Trustd describes the configuration of the Root of Trust (RoT) service. The
// username and password are used by master nodes, and worker nodes. The master
// nodes use them to authenticate clients, while the workers use them to
// authenticate as a client. The endpoints should only be specified in the
// worker user data, and should include all master nodes participating as a RoT.
type Trustd struct {
CommonServiceOptions `yaml:",inline"`
Token string `yaml:"token"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Endpoints []string `yaml:"endpoints,omitempty"`
CertSANs []string `yaml:"certSANs,omitempty"`
BootstrapNode string `yaml:"bootstrapNode,omitempty"`
}
// TrustdCheck defines the function type for checks
type TrustdCheck func(*Trustd) error
// Validate triggers the specified validation checks to run
func (t *Trustd) Validate(checks ...TrustdCheck) error {
var result *multierror.Error
for _, check := range checks {
result = multierror.Append(result, check(t))
}
return result.ErrorOrNil()
}
// CheckTrustdAuth ensures that a trustd token has been specified
func CheckTrustdAuth() TrustdCheck {
return func(t *Trustd) error {
var result *multierror.Error
if t.Token == "" && (t.Username == "" || t.Password == "") {
if t.Token == "" {
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.trustd.token", t.Token, ErrRequiredSection))
} else {
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.trustd.username:password", t.Username+":"+t.Password, ErrRequiredSection))
}
}
return result.ErrorOrNil()
}
}
// CheckTrustdEndpointsAreValidIPsOrHostnames ensures that the specified trustd endpoints
/// are valid IP addresses or DNS hostnames.
func CheckTrustdEndpointsAreValidIPsOrHostnames() TrustdCheck {
return func(t *Trustd) error {
var result *multierror.Error
for idx, endpoint := range t.Endpoints {
if ip := net.ParseIP(endpoint); ip != nil {
continue
}
if validHostnameRegex.MatchString(endpoint) {
continue
}
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.trustd.endpoints["+strconv.Itoa(idx)+"]", endpoint, ErrInvalidAddress))
}
return result.ErrorOrNil()
}
}
// CheckTrustdEndpointsArePresent ensures that tustd endpoints are present.
func CheckTrustdEndpointsArePresent() TrustdCheck {
return func(t *Trustd) error {
var result *multierror.Error
if len(t.Endpoints) == 0 {
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.trustd.endpoints", t.Endpoints, ErrRequiredSection))
}
return result.ErrorOrNil()
}
}
// Init describes the configuration of the init service.
type Init struct {
CNI string `yaml:"cni,omitempty"`
}
// InitCheck defines the function type for checks
type InitCheck func(*Init) error
// Validate triggers the specified validation checks to run
func (i *Init) Validate(checks ...InitCheck) error {
var result *multierror.Error
for _, check := range checks {
result = multierror.Append(result, check(i))
}
return result.ErrorOrNil()
}
// CheckInitCNI ensures that a valid cni driver has been specified
func CheckInitCNI() InitCheck {
return func(i *Init) error {
var result *multierror.Error
switch i.CNI {
case "calico":
return result.ErrorOrNil()
case "flannel":
return result.ErrorOrNil()
default:
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "services.init.cni", i.CNI, ErrUnsupportedCNI))
}
return result.ErrorOrNil()
}
}