mirror of https://github.com/linkerd/linkerd2.git
691 lines
26 KiB
Go
691 lines
26 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/linkerd/linkerd2/cli/flag"
|
|
l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
|
|
"github.com/linkerd/linkerd2/pkg/cmd"
|
|
flagspkg "github.com/linkerd/linkerd2/pkg/flags"
|
|
"github.com/linkerd/linkerd2/pkg/inject"
|
|
"github.com/linkerd/linkerd2/pkg/issuercerts"
|
|
"github.com/linkerd/linkerd2/pkg/k8s"
|
|
"github.com/linkerd/linkerd2/pkg/tls"
|
|
"github.com/linkerd/linkerd2/pkg/version"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/pflag"
|
|
corev1 "k8s.io/api/core/v1"
|
|
k8sResource "k8s.io/apimachinery/pkg/api/resource"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
)
|
|
|
|
// makeInstallUpgradeFlags builds the set of flags which are used by install and
|
|
// upgrade. These flags control the majority of how the control plane is
|
|
// configured.
|
|
func makeInstallUpgradeFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet, error) {
|
|
installUpgradeFlags := pflag.NewFlagSet("install", pflag.ExitOnError)
|
|
|
|
issuanceLifetime, err := time.ParseDuration(defaults.Identity.Issuer.IssuanceLifetime)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
clockSkewAllowance, err := time.ParseDuration(defaults.Identity.Issuer.ClockSkewAllowance)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
flags := []flag.Flag{
|
|
flag.NewBoolFlag(installUpgradeFlags, "linkerd-cni-enabled", defaults.CNIEnabled,
|
|
"Omit the NET_ADMIN capability in the PSP and the proxy-init container when injecting the proxy; requires the linkerd-cni plugin to already be installed",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.CNIEnabled = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(installUpgradeFlags, "controller-log-level", defaults.ControllerLogLevel,
|
|
"Log level for the controller and web components", func(values *l5dcharts.Values, value string) error {
|
|
values.ControllerLogLevel = value
|
|
return nil
|
|
}),
|
|
|
|
// The HA flag must be processed before flags that set these values individually so that the
|
|
// individual flags can override the HA defaults.
|
|
flag.NewBoolFlag(installUpgradeFlags, "ha", false, "Enable HA deployment config for the control plane (default false)",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.HighAvailability = value
|
|
if value {
|
|
if err := l5dcharts.MergeHAValues(values); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}),
|
|
|
|
flag.NewUintFlag(installUpgradeFlags, "controller-replicas", defaults.ControllerReplicas,
|
|
"Replicas of the controller to deploy", func(values *l5dcharts.Values, value uint) error {
|
|
values.ControllerReplicas = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewInt64Flag(installUpgradeFlags, "controller-uid", defaults.ControllerUID,
|
|
"Run the control plane components under this user ID", func(values *l5dcharts.Values, value int64) error {
|
|
values.ControllerUID = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(installUpgradeFlags, "disable-h2-upgrade", !defaults.EnableH2Upgrade,
|
|
"Prevents the controller from instructing proxies to perform transparent HTTP/2 upgrading (default false)",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.EnableH2Upgrade = !value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(installUpgradeFlags, "disable-heartbeat", defaults.DisableHeartBeat,
|
|
"Disables the heartbeat cronjob (default false)", func(values *l5dcharts.Values, value bool) error {
|
|
values.DisableHeartBeat = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewDurationFlag(installUpgradeFlags, "identity-issuance-lifetime", issuanceLifetime,
|
|
"The amount of time for which the Identity issuer should certify identity",
|
|
func(values *l5dcharts.Values, value time.Duration) error {
|
|
values.Identity.Issuer.IssuanceLifetime = value.String()
|
|
return nil
|
|
}),
|
|
|
|
flag.NewDurationFlag(installUpgradeFlags, "identity-clock-skew-allowance", clockSkewAllowance,
|
|
"The amount of time to allow for clock skew within a Linkerd cluster",
|
|
func(values *l5dcharts.Values, value time.Duration) error {
|
|
values.Identity.Issuer.ClockSkewAllowance = value.String()
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(installUpgradeFlags, "control-plane-tracing", defaults.ControlPlaneTracing,
|
|
"Enables Control Plane Tracing with the defaults", func(values *l5dcharts.Values, value bool) error {
|
|
values.ControlPlaneTracing = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(installUpgradeFlags, "control-plane-tracing-namespace", defaults.ControlPlaneTracingNamespace,
|
|
"Send control plane traces to Linkerd-Jaeger extension in this namespace", func(values *l5dcharts.Values, value string) error {
|
|
values.ControlPlaneTracingNamespace = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(installUpgradeFlags, "identity-issuer-certificate-file", "",
|
|
"A path to a PEM-encoded file containing the Linkerd Identity issuer certificate (generated by default)",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
if value != "" {
|
|
crt, err := loadCrtPEM(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values.Identity.Issuer.TLS.CrtPEM = crt
|
|
}
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(installUpgradeFlags, "identity-issuer-key-file", "",
|
|
"A path to a PEM-encoded file containing the Linkerd Identity issuer private key (generated by default)",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
if value != "" {
|
|
key, err := loadKeyPEM(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values.Identity.Issuer.TLS.KeyPEM = key
|
|
}
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(installUpgradeFlags, "identity-trust-anchors-file", "",
|
|
"A path to a PEM-encoded file containing Linkerd Identity trust anchors (generated by default)",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
if value != "" {
|
|
data, err := os.ReadFile(filepath.Clean(value))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values.IdentityTrustAnchorsPEM = string(data)
|
|
}
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(installUpgradeFlags, "enable-endpoint-slices", defaults.EnableEndpointSlices,
|
|
"Enables the usage of EndpointSlice informers and resources for destination service",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.EnableEndpointSlices = value
|
|
return nil
|
|
}),
|
|
}
|
|
|
|
// Hide developer focused flags in release builds.
|
|
release, err := version.IsReleaseChannel(version.Version)
|
|
if err != nil {
|
|
log.Errorf("Unable to parse version: %s", version.Version)
|
|
}
|
|
if release {
|
|
installUpgradeFlags.MarkHidden("control-plane-version")
|
|
}
|
|
installUpgradeFlags.MarkHidden("control-plane-tracing")
|
|
installUpgradeFlags.MarkHidden("control-plane-tracing-namespace")
|
|
|
|
return flags, installUpgradeFlags, nil
|
|
}
|
|
|
|
func loadCrtPEM(path string) (string, error) {
|
|
data, err := os.ReadFile(filepath.Clean(path))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
crt, err := tls.DecodePEMCrt(string(data))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return crt.EncodeCertificatePEM(), nil
|
|
}
|
|
|
|
func loadKeyPEM(path string) (string, error) {
|
|
data, err := os.ReadFile(filepath.Clean(path))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
key, err := tls.DecodePEMKey(string(data))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
cred := tls.Cred{PrivateKey: key}
|
|
return cred.EncodePrivateKeyPEM(), nil
|
|
}
|
|
|
|
// makeInstallFlags builds the set of flags which are used by install. These
|
|
// flags should not be changed during an upgrade and are not available to the
|
|
// upgrade command.
|
|
func makeInstallFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {
|
|
|
|
installOnlyFlags := pflag.NewFlagSet("install-only", pflag.ExitOnError)
|
|
|
|
flags := []flag.Flag{
|
|
flag.NewStringFlag(installOnlyFlags, "cluster-domain", defaults.ClusterDomain,
|
|
"Set custom cluster domain", func(values *l5dcharts.Values, value string) error {
|
|
values.ClusterDomain = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(installOnlyFlags, "identity-trust-domain", defaults.IdentityTrustDomain,
|
|
"Configures the name suffix used for identities.", func(values *l5dcharts.Values, value string) error {
|
|
values.IdentityTrustDomain = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(installOnlyFlags, "identity-external-issuer", false,
|
|
"Whether to use an external identity issuer (default false)", func(values *l5dcharts.Values, value bool) error {
|
|
if value {
|
|
values.Identity.Issuer.Scheme = string(corev1.SecretTypeTLS)
|
|
} else {
|
|
values.Identity.Issuer.Scheme = k8s.IdentityIssuerSchemeLinkerd
|
|
}
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(installOnlyFlags, "identity-external-ca", false,
|
|
"Whether to use an external CA provider (default false)", func(values *l5dcharts.Values, value bool) error {
|
|
values.Identity.ExternalCA = value
|
|
return nil
|
|
}),
|
|
}
|
|
|
|
return flags, installOnlyFlags
|
|
}
|
|
|
|
// makeProxyFlags builds the set of flags which affect how the proxy is
|
|
// configured. These flags are available to the inject command and to the
|
|
// install and upgrade commands.
|
|
func makeProxyFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {
|
|
|
|
proxyFlags := pflag.NewFlagSet("proxy", pflag.ExitOnError)
|
|
|
|
flags := []flag.Flag{
|
|
flag.NewStringFlag(proxyFlags, "proxy-image", defaults.Proxy.Image.Name, "Linkerd proxy container image name",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Image.Name = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "init-image", defaults.ProxyInit.Image.Name, "Linkerd init container image name",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.ProxyInit.Image.Name = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "init-image-version", defaults.ProxyInit.Image.Version,
|
|
"Linkerd init container image version", func(values *l5dcharts.Values, value string) error {
|
|
values.ProxyInit.Image.Version = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "image-pull-policy", defaults.ImagePullPolicy,
|
|
"Docker image pull policy", func(values *l5dcharts.Values, value string) error {
|
|
values.ImagePullPolicy = value
|
|
values.Proxy.Image.PullPolicy = value
|
|
values.ProxyInit.Image.PullPolicy = value
|
|
values.DebugContainer.Image.PullPolicy = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewUintFlag(proxyFlags, "inbound-port", uint(defaults.Proxy.Ports.Inbound),
|
|
"Proxy port to use for inbound traffic", func(values *l5dcharts.Values, value uint) error {
|
|
values.Proxy.Ports.Inbound = int32(value)
|
|
return nil
|
|
}),
|
|
|
|
flag.NewUintFlag(proxyFlags, "outbound-port", uint(defaults.Proxy.Ports.Outbound),
|
|
"Proxy port to use for outbound traffic", func(values *l5dcharts.Values, value uint) error {
|
|
values.Proxy.Ports.Outbound = int32(value)
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringSliceFlag(proxyFlags, "skip-inbound-ports", nil, "Ports and/or port ranges (inclusive) that should skip the proxy and send directly to the application",
|
|
func(values *l5dcharts.Values, value []string) error {
|
|
values.ProxyInit.IgnoreInboundPorts = strings.Join(value, ",")
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringSliceFlag(proxyFlags, "skip-outbound-ports", nil, "Outbound ports and/or port ranges (inclusive) that should skip the proxy",
|
|
func(values *l5dcharts.Values, value []string) error {
|
|
values.ProxyInit.IgnoreOutboundPorts = strings.Join(value, ",")
|
|
return nil
|
|
}),
|
|
|
|
flag.NewInt64Flag(proxyFlags, "proxy-uid", defaults.Proxy.UID, "Run the proxy under this user ID",
|
|
func(values *l5dcharts.Values, value int64) error {
|
|
values.Proxy.UID = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-log-level", defaults.Proxy.LogLevel, "Log level for the proxy",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.LogLevel = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewUintFlag(proxyFlags, "control-port", uint(defaults.Proxy.Ports.Control), "Proxy port to use for control",
|
|
func(values *l5dcharts.Values, value uint) error {
|
|
values.Proxy.Ports.Control = int32(value)
|
|
return nil
|
|
}),
|
|
|
|
flag.NewUintFlag(proxyFlags, "admin-port", uint(defaults.Proxy.Ports.Admin), "Proxy port to serve metrics on",
|
|
func(values *l5dcharts.Values, value uint) error {
|
|
values.Proxy.Ports.Admin = int32(value)
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-cpu-request", defaults.Proxy.Resources.CPU.Request, "Amount of CPU units that the proxy sidecar requests",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Resources.CPU.Request = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-memory-request", defaults.Proxy.Resources.Memory.Request, "Amount of Memory that the proxy sidecar requests",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Resources.Memory.Request = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-cpu-limit", defaults.Proxy.Resources.CPU.Limit, "Maximum amount of CPU units that the proxy sidecar can use",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
q, err := k8sResource.ParseQuantity(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c, err := inject.ToWholeCPUCores(q)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values.Proxy.Cores = c
|
|
values.Proxy.Resources.CPU.Limit = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-memory-limit", defaults.Proxy.Resources.Memory.Limit, "Maximum amount of Memory that the proxy sidecar can use",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Resources.Memory.Limit = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(proxyFlags, "enable-external-profiles", defaults.Proxy.EnableExternalProfiles, "Enable service profiles for non-Kubernetes services",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.Proxy.EnableExternalProfiles = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "default-inbound-policy", defaults.Proxy.DefaultInboundPolicy, "Inbound policy to use to control inbound access to the proxy",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.DefaultInboundPolicy = value
|
|
return nil
|
|
}),
|
|
|
|
// Deprecated flags
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-memory", defaults.Proxy.Resources.Memory.Request, "Amount of Memory that the proxy sidecar requests",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Resources.Memory.Request = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlag(proxyFlags, "proxy-cpu", defaults.Proxy.Resources.CPU.Request, "Amount of CPU units that the proxy sidecar requests",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Resources.CPU.Request = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringFlagP(proxyFlags, "proxy-version", "v", defaults.Proxy.Image.Version, "Tag to be used for the Linkerd proxy images",
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.Proxy.Image.Version = value
|
|
return nil
|
|
}),
|
|
}
|
|
|
|
registryFlag := flag.NewStringFlag(proxyFlags, "registry", cmd.DefaultDockerRegistry,
|
|
fmt.Sprintf("Docker registry to pull images from ($%s)", flagspkg.EnvOverrideDockerRegistry),
|
|
func(values *l5dcharts.Values, value string) error {
|
|
values.ControllerImage = cmd.RegistryOverride(values.ControllerImage, value)
|
|
values.PolicyController.Image.Name = cmd.RegistryOverride(values.PolicyController.Image.Name, value)
|
|
values.DebugContainer.Image.Name = cmd.RegistryOverride(values.DebugContainer.Image.Name, value)
|
|
values.Proxy.Image.Name = cmd.RegistryOverride(values.Proxy.Image.Name, value)
|
|
values.ProxyInit.Image.Name = cmd.RegistryOverride(values.ProxyInit.Image.Name, value)
|
|
return nil
|
|
})
|
|
if reg := os.Getenv(flagspkg.EnvOverrideDockerRegistry); reg != "" {
|
|
registryFlag.Set(reg)
|
|
}
|
|
flags = append(flags, registryFlag)
|
|
|
|
proxyFlags.MarkDeprecated("proxy-memory", "use --proxy-memory-request instead")
|
|
proxyFlags.MarkDeprecated("proxy-cpu", "use --proxy-cpu-request instead")
|
|
proxyFlags.MarkDeprecated("proxy-version", "use --set proxy.image.version=<version>")
|
|
|
|
// Hide developer focused flags in release builds.
|
|
release, err := version.IsReleaseChannel(version.Version)
|
|
if err != nil {
|
|
log.Errorf("Unable to parse version: %s", version.Version)
|
|
}
|
|
if release {
|
|
proxyFlags.MarkHidden("proxy-image")
|
|
proxyFlags.MarkHidden("proxy-version")
|
|
proxyFlags.MarkHidden("image-pull-policy")
|
|
proxyFlags.MarkHidden("init-image")
|
|
proxyFlags.MarkHidden("init-image-version")
|
|
}
|
|
|
|
return flags, proxyFlags
|
|
}
|
|
|
|
// makeInjectFlags builds the set of flags which are exclusive to the inject
|
|
// command. These flags configure the proxy but are not available to the
|
|
// install and upgrade commands. This is generally for proxy configuration
|
|
// which is intended to be set on individual workloads rather than being
|
|
// cluster wide.
|
|
func makeInjectFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {
|
|
injectFlags := pflag.NewFlagSet("inject", pflag.ExitOnError)
|
|
|
|
flags := []flag.Flag{
|
|
flag.NewBoolFlag(injectFlags, "native-sidecar", false, "Enable native sidecar",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.Proxy.NativeSidecar = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewInt64Flag(injectFlags, "wait-before-exit-seconds", int64(defaults.Proxy.WaitBeforeExitSeconds),
|
|
"The period during which the proxy sidecar must stay alive while its pod is terminating. "+
|
|
"Must be smaller than terminationGracePeriodSeconds for the pod (default 0)",
|
|
func(values *l5dcharts.Values, value int64) error {
|
|
values.Proxy.WaitBeforeExitSeconds = uint64(value)
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(injectFlags, "disable-identity", false,
|
|
"Disables resources from participating in TLS identity", func(values *l5dcharts.Values, value bool) error {
|
|
return errors.New("--disable-identity is no longer supported; identity is always required")
|
|
}),
|
|
|
|
flag.NewStringSliceFlag(injectFlags, "require-identity-on-inbound-ports", strings.Split(defaults.Proxy.RequireIdentityOnInboundPorts, ","),
|
|
"Inbound ports on which the proxy should require identity", func(values *l5dcharts.Values, value []string) error {
|
|
values.Proxy.RequireIdentityOnInboundPorts = strings.Join(value, ",")
|
|
return nil
|
|
}),
|
|
|
|
flag.NewBoolFlag(injectFlags, "ingress", defaults.Proxy.IsIngress, "Enable ingress mode in the linkerd proxy",
|
|
func(values *l5dcharts.Values, value bool) error {
|
|
values.Proxy.IsIngress = value
|
|
return nil
|
|
}),
|
|
|
|
flag.NewStringSliceFlag(injectFlags, "opaque-ports", strings.Split(defaults.Proxy.OpaquePorts, ","),
|
|
"Set opaque ports on the proxy", func(values *l5dcharts.Values, value []string) error {
|
|
values.Proxy.OpaquePorts = strings.Join(value, ",")
|
|
return nil
|
|
}),
|
|
}
|
|
injectFlags.MarkHidden("disable-identity")
|
|
|
|
return flags, injectFlags
|
|
}
|
|
|
|
/* Validation */
|
|
|
|
func validateValues(ctx context.Context, k *k8s.KubernetesAPI, values *l5dcharts.Values) error {
|
|
if !alphaNumDashDot.MatchString(values.LinkerdVersion) {
|
|
return fmt.Errorf("%s is not a valid version", values.LinkerdVersion)
|
|
}
|
|
|
|
if _, err := log.ParseLevel(values.ControllerLogLevel); err != nil {
|
|
return fmt.Errorf("--controller-log-level must be one of: panic, fatal, error, warn, info, debug, trace")
|
|
}
|
|
|
|
if values.Proxy.LogLevel == "" {
|
|
return errors.New("--proxy-log-level must not be empty")
|
|
}
|
|
|
|
if values.EnableEndpointSlices && k != nil {
|
|
err := k8s.EndpointSliceAccess(ctx, k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Validate only if its not empty
|
|
if values.IdentityTrustDomain != "" {
|
|
if errs := validation.IsDNS1123Subdomain(values.IdentityTrustDomain); len(errs) > 0 {
|
|
return fmt.Errorf("invalid trust domain '%s': %s", values.IdentityTrustDomain, errs[0])
|
|
}
|
|
}
|
|
|
|
err := validateProxyValues(values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {
|
|
if values.Identity.Issuer.TLS.CrtPEM != "" {
|
|
return errors.New("--identity-issuer-certificate-file must not be specified if --identity-external-issuer=true")
|
|
}
|
|
if values.Identity.Issuer.TLS.KeyPEM != "" {
|
|
return errors.New("--identity-issuer-key-file must not be specified if --identity-external-issuer=true")
|
|
}
|
|
}
|
|
|
|
if values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) && k != nil {
|
|
externalIssuerData, err := issuercerts.FetchExternalIssuerData(ctx, k, controlPlaneNamespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = externalIssuerData.VerifyAndBuildCreds()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to validate issuer credentials: %w", err)
|
|
}
|
|
}
|
|
|
|
if values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {
|
|
issuerData := issuercerts.IssuerCertData{
|
|
IssuerCrt: values.Identity.Issuer.TLS.CrtPEM,
|
|
IssuerKey: values.Identity.Issuer.TLS.KeyPEM,
|
|
TrustAnchors: values.IdentityTrustAnchorsPEM,
|
|
}
|
|
_, err := issuerData.VerifyAndBuildCreds()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to validate issuer credentials: %w", err)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateProxyValues(values *l5dcharts.Values) error {
|
|
networks := strings.Split(values.ClusterNetworks, ",")
|
|
for _, network := range networks {
|
|
if _, _, err := net.ParseCIDR(network); err != nil {
|
|
return fmt.Errorf("cannot parse destination get networks: %w", err)
|
|
}
|
|
}
|
|
|
|
if values.Proxy.Image.Version != "" && !alphaNumDashDot.MatchString(values.Proxy.Image.Version) {
|
|
return fmt.Errorf("%s is not a valid version", values.Proxy.Image.Version)
|
|
}
|
|
|
|
if !alphaNumDashDot.MatchString(values.ProxyInit.Image.Version) {
|
|
return fmt.Errorf("%s is not a valid version", values.ProxyInit.Image.Version)
|
|
}
|
|
|
|
if values.ImagePullPolicy != "Always" && values.ImagePullPolicy != "IfNotPresent" && values.ImagePullPolicy != "Never" {
|
|
return fmt.Errorf("--image-pull-policy must be one of: Always, IfNotPresent, Never")
|
|
}
|
|
|
|
if values.Proxy.Resources.CPU.Request != "" {
|
|
if _, err := k8sResource.ParseQuantity(values.Proxy.Resources.CPU.Request); err != nil {
|
|
return fmt.Errorf("Invalid cpu request '%s' for --proxy-cpu-request flag", values.Proxy.Resources.CPU.Request)
|
|
}
|
|
}
|
|
|
|
if values.Proxy.Resources.Memory.Request != "" {
|
|
if _, err := k8sResource.ParseQuantity(values.Proxy.Resources.Memory.Request); err != nil {
|
|
return fmt.Errorf("Invalid memory request '%s' for --proxy-memory-request flag", values.Proxy.Resources.Memory.Request)
|
|
}
|
|
}
|
|
|
|
if values.Proxy.Resources.CPU.Limit != "" {
|
|
cpuLimit, err := k8sResource.ParseQuantity(values.Proxy.Resources.CPU.Limit)
|
|
if err != nil {
|
|
return fmt.Errorf("Invalid cpu limit '%s' for --proxy-cpu-limit flag", values.Proxy.Resources.CPU.Limit)
|
|
}
|
|
// Not checking for error because option proxyCPURequest was already validated
|
|
if cpuRequest, _ := k8sResource.ParseQuantity(values.Proxy.Resources.CPU.Request); cpuRequest.MilliValue() > cpuLimit.MilliValue() {
|
|
return fmt.Errorf("The cpu limit '%s' cannot be lower than the cpu request '%s'", values.Proxy.Resources.CPU.Limit, values.Proxy.Resources.CPU.Request)
|
|
}
|
|
}
|
|
|
|
if values.Proxy.Resources.Memory.Limit != "" {
|
|
memoryLimit, err := k8sResource.ParseQuantity(values.Proxy.Resources.Memory.Limit)
|
|
if err != nil {
|
|
return fmt.Errorf("Invalid memory limit '%s' for --proxy-memory-limit flag", values.Proxy.Resources.Memory.Limit)
|
|
}
|
|
// Not checking for error because option proxyMemoryRequest was already validated
|
|
if memoryRequest, _ := k8sResource.ParseQuantity(values.Proxy.Resources.Memory.Request); memoryRequest.Value() > memoryLimit.Value() {
|
|
return fmt.Errorf("The memory limit '%s' cannot be lower than the memory request '%s'", values.Proxy.Resources.Memory.Limit, values.Proxy.Resources.Memory.Request)
|
|
}
|
|
}
|
|
|
|
if !validProxyLogLevel.MatchString(values.Proxy.LogLevel) {
|
|
return fmt.Errorf("\"%s\" is not a valid proxy log level - for allowed syntax check https://docs.rs/env_logger/0.6.0/env_logger/#enabling-logging",
|
|
values.Proxy.LogLevel)
|
|
}
|
|
|
|
if values.ProxyInit.IgnoreInboundPorts != "" {
|
|
if err := validateRangeSlice(strings.Split(values.ProxyInit.IgnoreInboundPorts, ",")); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if values.ProxyInit.IgnoreOutboundPorts != "" {
|
|
if err := validateRangeSlice(strings.Split(values.ProxyInit.IgnoreOutboundPorts, ",")); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := validatePolicy(values.Proxy.DefaultInboundPolicy); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validatePolicy(policy string) error {
|
|
validPolicies := []string{"all-authenticated", "all-unauthenticated", "cluster-authenticated", "cluster-unauthenticated", "deny"}
|
|
for _, p := range validPolicies {
|
|
if p == policy {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("--default-inbound-policy must be one of: %s (got %s)", strings.Join(validPolicies, ", "), policy)
|
|
}
|
|
|
|
// initializeIssuerCredentials populates the identity issuer TLS credentials.
|
|
// If we are using an externally managed issuer secret, all we need to do here
|
|
// is copy the trust root from the issuer secret. Otherwise, if no credentials
|
|
// have already been supplied, we generate them.
|
|
func initializeIssuerCredentials(ctx context.Context, k *k8s.KubernetesAPI, values *l5dcharts.Values) error {
|
|
if values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {
|
|
// Using externally managed issuer credentials. We need to copy the
|
|
// trust root.
|
|
if k == nil {
|
|
return errors.New("--ignore-cluster is not supported when --identity-external-issuer=true")
|
|
}
|
|
externalIssuerData, err := issuercerts.FetchExternalIssuerData(ctx, k, controlPlaneNamespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values.IdentityTrustAnchorsPEM = externalIssuerData.TrustAnchors
|
|
} else if values.Identity.Issuer.TLS.CrtPEM != "" || values.Identity.Issuer.TLS.KeyPEM != "" || values.IdentityTrustAnchorsPEM != "" {
|
|
// If any credentials have already been supplied, check that they are
|
|
// all present.
|
|
if values.IdentityTrustAnchorsPEM == "" {
|
|
return errors.New("a trust anchors file must be specified if other credentials are provided")
|
|
}
|
|
if values.Identity.Issuer.TLS.CrtPEM == "" {
|
|
return errors.New("a certificate file must be specified if other credentials are provided")
|
|
}
|
|
if values.Identity.Issuer.TLS.KeyPEM == "" {
|
|
return errors.New("a private key file must be specified if other credentials are provided")
|
|
}
|
|
} else {
|
|
// No credentials have been supplied so we will generate them.
|
|
root, err := tls.GenerateRootCAWithDefaults(issuerName(values.IdentityTrustDomain))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate root certificate for identity: %w", err)
|
|
}
|
|
values.Identity.Issuer.TLS.KeyPEM = root.Cred.EncodePrivateKeyPEM()
|
|
values.Identity.Issuer.TLS.CrtPEM = root.Cred.Crt.EncodeCertificatePEM()
|
|
values.IdentityTrustAnchorsPEM = root.Cred.Crt.EncodeCertificatePEM()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func issuerName(trustDomain string) string {
|
|
return fmt.Sprintf("identity.%s.%s", controlPlaneNamespace, trustDomain)
|
|
}
|