feat: introduce Talos API access from Kubernetes

We add a new CRD, `serviceaccounts.talos.dev` (with `tsa` as short name), and its controller which allows users to get a `Secret` containing a short-lived Talosconfig in their namespaces with the roles they need. Additionally, we introduce the `talosctl inject serviceaccount` command to accept a YAML file with Kubernetes manifests and inject them with Talos service accounts so that they can be directly applied to Kubernetes afterwards. If Talos API access feature is enabled on Talos side, the injected workloads will be able to talk to Talos API.

Closes siderolabs/talos#4422.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
This commit is contained in:
Utku Ozdemir 2022-07-28 19:45:04 +02:00
parent d7be308921
commit 84e712a9f1
No known key found for this signature in database
GPG Key ID: 65933E76F0549B0D
30 changed files with 2872 additions and 250 deletions

View File

@ -847,12 +847,16 @@ func trimVersion(version string) string {
}
func init() {
defaultTalosConfig, err := clientconfig.GetDefaultPath()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to find default Talos config path: %s", err)
}
createCmd.Flags().StringVar(&talosconfig, "talosconfig", defaultTalosConfig, "The path to the Talos configuration file")
createCmd.Flags().StringVar(
&talosconfig,
"talosconfig",
"",
fmt.Sprintf("The path to the Talos configuration file. Defaults to '%s' env variable if set, otherwise '%s' and '%s' in order.",
constants.TalosConfigEnvVar,
filepath.Join("$HOME", constants.TalosDir, constants.TalosconfigFilename),
filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
),
)
createCmd.Flags().StringVar(&nodeImage, "image", helpers.DefaultImage(images.DefaultTalosImageRepository), "the image to use")
createCmd.Flags().StringVar(&nodeInstallImage, nodeInstallImageFlag, helpers.DefaultImage(images.DefaultInstallerImageRepository), "the installer image to use")
createCmd.Flags().StringVar(&nodeVmlinuzPath, "vmlinuz-path", helpers.ArtifactPath(constants.KernelAssetWithArch), "the compressed kernel image to use")

View File

@ -0,0 +1,14 @@
// 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 inject
import "github.com/spf13/cobra"
// Cmd represents the debug command.
var Cmd = &cobra.Command{
Use: "inject",
Short: "Inject Talos API resources into Kubernetes manifests",
Long: ``,
}

View File

@ -0,0 +1,68 @@
// 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 inject
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/talos-systems/talos/pkg/kubernetes/inject"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
var serviceAccountCmdFlags struct {
file string
roles []string
}
var serviceAccountCmd = &cobra.Command{
Use: fmt.Sprintf("%s [--roles='<ROLE_1>,<ROLE_2>'] -f <manifest.yaml>", constants.ServiceAccountResourceSingular),
Aliases: []string{constants.ServiceAccountResourceShortName},
Short: "Inject Talos API ServiceAccount into Kubernetes manifests",
Example: fmt.Sprintf(
`talosctl inject %[1]s --roles="os:admin" -f deployment.yaml > deployment-injected.yaml
Alternatively, stdin can be piped to the command:
cat deployment.yaml | talosctl inject %[1]s --roles="os:admin" -f - > deployment-injected.yaml
`,
constants.ServiceAccountResourceSingular,
),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
var err error
if serviceAccountCmdFlags.file == "" {
return cmd.Help()
}
reader := os.Stdin
if serviceAccountCmdFlags.file != "-" {
reader, err = os.Open(serviceAccountCmdFlags.file)
if err != nil {
return err
}
}
injectedYaml, err := inject.ServiceAccount(reader, serviceAccountCmdFlags.roles)
if err != nil {
return err
}
fmt.Println(string(injectedYaml))
return nil
},
}
func init() {
serviceAccountCmd.Flags().StringVarP(&serviceAccountCmdFlags.file, "file", "f", "",
fmt.Sprintf("file with Kubernetes manifests to be injected with %s", constants.ServiceAccountResourceKind))
serviceAccountCmd.Flags().StringSliceVarP(&serviceAccountCmdFlags.roles, "roles", "r", []string{"os:reader"},
fmt.Sprintf("roles to add to the generated %s manifests", constants.ServiceAccountResourceKind))
Cmd.AddCommand(serviceAccountCmd)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/cluster"
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/debug"
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/gen"
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/inject"
)
// Commands is a list of commands published by the package.
@ -28,4 +29,5 @@ func init() {
addCommand(cluster.Cmd)
addCommand(gen.Cmd)
addCommand(debug.Cmd)
addCommand(inject.Cmd)
}

View File

@ -7,6 +7,7 @@ package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
@ -14,7 +15,7 @@ import (
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt"
"github.com/talos-systems/talos/cmd/talosctl/cmd/talos"
"github.com/talos-systems/talos/pkg/cli"
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
// rootCmd represents the base command when called without any subcommands.
@ -30,12 +31,16 @@ var rootCmd = &cobra.Command{
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() error {
defaultTalosConfig, err := clientconfig.GetDefaultPath()
if err != nil {
return err
}
rootCmd.PersistentFlags().StringVar(&talos.Talosconfig, "talosconfig", defaultTalosConfig, "The path to the Talos configuration file")
rootCmd.PersistentFlags().StringVar(
&talos.Talosconfig,
"talosconfig",
"",
fmt.Sprintf("The path to the Talos configuration file. Defaults to '%s' env variable if set, otherwise '%s' and '%s' in order.",
constants.TalosConfigEnvVar,
filepath.Join("$HOME", constants.TalosDir, constants.TalosconfigFilename),
filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
),
)
rootCmd.PersistentFlags().StringVar(&talos.Cmdcontext, "context", "", "Context to be used in command")
rootCmd.PersistentFlags().StringSliceVarP(&talos.Nodes, "nodes", "n", []string{}, "target the specified nodes")
rootCmd.PersistentFlags().StringSliceVarP(&talos.Endpoints, "endpoints", "e", []string{}, "override default endpoints in Talos configuration")

View File

@ -14,6 +14,32 @@ preface = """\
"""
[notes]
[notes.api-access-from-kubernetes]
title = "Talos API access from Kubernetes"
description = """\
Talos now supports access to its API from within Kubernetes. It can be configured in the machine config as below:
```yaml
machine:
features:
kubernetesTalosAPIAccess:
enabled: true
allowedRoles:
- os:reader
allowedKubernetesNamespaces:
- kube-system
```
This feature introduces a new custom resource definition, `serviceaccounts.talos.dev`.
Creating custom resources of this type will provide credentials to access Talos API from within Kubernetes.
The new CLI subcommand `talosctl inject serviceaccount` can be used to configure Kubernetes manifests with Talos service accounts as below:
```
talosctl inject serviceaccount -f manifests.yaml > manifests-injected.yaml
kubectl apply -f manifests-injected.yaml
```
See [documentation](https://www.talos.dev/v1.2/advanced/configuration/talos-api-access-from-k8s/) for more details.
"""
[notes.seccomp]
title = "Seccomp Profiles"

View File

@ -165,6 +165,8 @@ func (ctrl *ManifestController) render(cfg k8s.BootstrapManifestsConfigSpec, scr
KubernetesTalosAPIServiceNamespace string
ApidPort int
TalosServiceAccount TalosServiceAccount
}{
BootstrapManifestsConfigSpec: cfg,
Secrets: scrt,
@ -173,6 +175,15 @@ func (ctrl *ManifestController) render(cfg k8s.BootstrapManifestsConfigSpec, scr
KubernetesTalosAPIServiceNamespace: constants.KubernetesTalosAPIServiceNamespace,
ApidPort: constants.ApidPort,
TalosServiceAccount: TalosServiceAccount{
Group: constants.ServiceAccountResourceGroup,
Version: constants.ServiceAccountResourceVersion,
Kind: constants.ServiceAccountResourceKind,
ResourceSingular: constants.ServiceAccountResourceSingular,
ResourcePlural: constants.ServiceAccountResourcePlural,
ShortName: constants.ServiceAccountResourceShortName,
},
}
type manifestDesc struct {
@ -226,6 +237,7 @@ func (ctrl *ManifestController) render(cfg k8s.BootstrapManifestsConfigSpec, scr
defaultManifests = append(defaultManifests,
[]manifestDesc{
{"12-talos-api-service", talosAPIService},
{"13-talos-service-account-crd", talosServiceAccountCRDTemplate},
}...,
)
}
@ -276,3 +288,14 @@ func (ctrl *ManifestController) teardownAll(ctx context.Context, r controller.Ru
return nil
}
// TalosServiceAccount is a struct used by the template engine which contains the needed variables to
// be able to construct the Talos Service Account CRD.
type TalosServiceAccount struct {
Group string
Version string
Kind string
ResourceSingular string
ResourcePlural string
ShortName string
}

View File

@ -13,7 +13,6 @@ import (
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/go-pointer"
"go.etcd.io/etcd/client/v3/concurrency"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -155,7 +154,7 @@ func (ctrl *ManifestApplyController) Run(ctx context.Context, r controller.Runti
return fmt.Errorf("error building dynamic client: %w", err)
}
if err = ctrl.etcdLock(ctx, logger, func() error {
if err = etcd.WithLock(ctx, constants.EtcdTalosManifestApplyMutex, logger, func() error {
return ctrl.apply(ctx, logger, mapper, dyn, manifests)
}); err != nil {
return err
@ -176,36 +175,6 @@ func (ctrl *ManifestApplyController) Run(ctx context.Context, r controller.Runti
}
}
func (ctrl *ManifestApplyController) etcdLock(ctx context.Context, logger *zap.Logger, f func() error) error {
etcdClient, err := etcd.NewLocalClient()
if err != nil {
return fmt.Errorf("error creating etcd client: %w", err)
}
defer etcdClient.Close() //nolint:errcheck
session, err := concurrency.NewSession(etcdClient.Client)
if err != nil {
return fmt.Errorf("error creating etcd session: %w", err)
}
defer session.Close() //nolint:errcheck
mutex := concurrency.NewMutex(session, constants.EtcdTalosManifestApplyMutex)
logger.Debug("waiting for mutex")
if err := mutex.Lock(ctx); err != nil {
return fmt.Errorf("error acquiring mutex: %w", err)
}
logger.Debug("mutex acquired")
defer mutex.Unlock(ctx) //nolint:errcheck
return f()
}
//nolint:gocyclo
func (ctrl *ManifestApplyController) apply(ctx context.Context, logger *zap.Logger, mapper *restmapper.DeferredDiscoveryRESTMapper, dyn dynamic.Interface, manifests resource.List) error {
// flatten list of objects to be applied

View File

@ -718,3 +718,43 @@ spec:
protocol: TCP
targetPort: {{ .ApidPort }}
`)
// talosServiceAccountCRDTemplate is the template of the CRD which
// allows injecting Talos with credentials into the Kubernetes cluster.
var talosServiceAccountCRDTemplate = []byte(`apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: {{ .TalosServiceAccount.ResourcePlural }}.{{ .TalosServiceAccount.Group }}
spec:
conversion:
strategy: None
group: {{ .TalosServiceAccount.Group }}
names:
kind: {{ .TalosServiceAccount.Kind }}
listKind: {{ .TalosServiceAccount.Kind }}List
plural: {{ .TalosServiceAccount.ResourcePlural }}
singular: {{ .TalosServiceAccount.ResourceSingular }}
shortNames:
- {{ .TalosServiceAccount.ShortName }}
scope: Namespaced
versions:
- name: {{ .TalosServiceAccount.Version }}
schema:
openAPIV3Schema:
properties:
spec:
type: object
properties:
roles:
type: array
items:
type: string
status:
type: object
properties:
failureReason:
type: string
type: object
served: true
storage: true
`)

View File

@ -39,57 +39,7 @@ func (ctrl *EndpointController) Name() string {
// Inputs implements controller.Controller interface.
func (ctrl *EndpointController) Inputs() []controller.Input {
return nil
}
// Outputs implements controller.Controller interface.
func (ctrl *EndpointController) Outputs() []controller.Output {
return nil
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *EndpointController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
for {
if err := r.UpdateInputs([]controller.Input{
{
Namespace: config.NamespaceName,
Type: kubeaccess.ConfigType,
ID: pointer.To(kubeaccess.ConfigID),
Kind: controller.InputWeak,
},
}); err != nil {
return err
}
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}
kubeaccessConfig, err := r.Get(ctx, kubeaccess.NewConfig(config.NamespaceName, kubeaccess.ConfigID).Metadata())
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error fetching kubeaccess config: %w", err)
}
}
if kubeaccessConfig == nil || !kubeaccessConfig.(*kubeaccess.Config).TypedSpec().Enabled {
// disabled, nothing to do
continue
}
if err = ctrl.reconcile(ctx, r, logger); err != nil {
return err
}
}
}
//nolint:gocyclo
func (ctrl *EndpointController) reconcile(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
if err := r.UpdateInputs([]controller.Input{
return []controller.Input{
{
Namespace: config.NamespaceName,
Type: kubeaccess.ConfigType,
@ -107,12 +57,18 @@ func (ctrl *EndpointController) reconcile(ctx context.Context, r controller.Runt
Type: k8s.EndpointType,
Kind: controller.InputWeak,
},
}); err != nil {
return err
}
}
r.QueueReconcile()
// Outputs implements controller.Controller interface.
func (ctrl *EndpointController) Outputs() []controller.Output {
return nil
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *EndpointController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
for {
select {
case <-r.EventCh():
@ -128,8 +84,8 @@ func (ctrl *EndpointController) reconcile(ctx context.Context, r controller.Runt
}
if kubeaccessConfig == nil || !kubeaccessConfig.(*kubeaccess.Config).TypedSpec().Enabled {
// disabled, bail out
return nil
// disabled, do not do anything
continue
}
endpointResources, err := r.List(ctx, resource.NewMetadata(k8s.ControlPlaneNamespaceName, k8s.EndpointType, "", resource.VersionUndefined))

View File

@ -0,0 +1,204 @@
// 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 kubeaccess
import (
"context"
"errors"
"fmt"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/go-pointer"
"github.com/talos-systems/crypto/x509"
"go.uber.org/zap"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"github.com/talos-systems/talos/internal/app/machined/pkg/controllers/kubeaccess/serviceaccount"
"github.com/talos-systems/talos/internal/pkg/etcd"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/resources/config"
"github.com/talos-systems/talos/pkg/machinery/resources/kubeaccess"
"github.com/talos-systems/talos/pkg/machinery/resources/secrets"
)
// CRDController manages Kubernetes endpoints resource for Talos API endpoints.
type CRDController struct{}
// Name implements controller.Controller interface.
func (ctrl *CRDController) Name() string {
return "kubeaccess.CRDController"
}
// Inputs implements controller.Controller interface.
func (ctrl *CRDController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: config.NamespaceName,
Type: kubeaccess.ConfigType,
ID: pointer.To(kubeaccess.ConfigID),
Kind: controller.InputWeak,
},
{
Namespace: secrets.NamespaceName,
Type: secrets.KubernetesType,
ID: pointer.To(secrets.KubernetesID),
Kind: controller.InputWeak,
},
{
Namespace: secrets.NamespaceName,
Type: secrets.OSRootType,
ID: pointer.To(secrets.OSRootID),
Kind: controller.InputWeak,
},
}
}
// Outputs implements controller.Controller interface.
func (ctrl *CRDController) Outputs() []controller.Output {
return nil
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo,cyclop
func (ctrl *CRDController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
var crdControllerCtxCancel context.CancelFunc
crdControllerErrCh := make(chan error, 1)
stopCRDController := func() {
if crdControllerCtxCancel != nil {
crdControllerCtxCancel()
<-crdControllerErrCh
crdControllerCtxCancel = nil
}
}
defer stopCRDController()
for {
select {
case <-ctx.Done():
return nil //nolint:govet
case <-r.EventCh():
case err := <-crdControllerErrCh:
if crdControllerCtxCancel != nil {
crdControllerCtxCancel()
}
crdControllerCtxCancel = nil
if err != nil && !errors.Is(err, context.Canceled) {
return fmt.Errorf("error from crd controller: %w", err)
}
}
kubeaccessConfig, err := safe.ReaderGet[*kubeaccess.Config](ctx, r, kubeaccess.NewConfig(config.NamespaceName, kubeaccess.ConfigID).Metadata())
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error fetching kubeaccess config: %w", err)
}
continue
}
var kubeaccessConfigSpec *kubeaccess.ConfigSpec
if kubeaccessConfig != nil {
kubeaccessConfigSpec = kubeaccessConfig.TypedSpec()
}
if kubeaccessConfig == nil || kubeaccessConfigSpec == nil || !kubeaccessConfigSpec.Enabled {
stopCRDController()
continue
}
kubeSecretsResources, err := safe.ReaderGet[*secrets.Kubernetes](ctx, r, resource.NewMetadata(
secrets.NamespaceName,
secrets.KubernetesType,
secrets.KubernetesID,
resource.VersionUndefined,
))
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error fetching kubernetes secrets: %w", err)
}
continue
}
kubeSecretsSpec := kubeSecretsResources.TypedSpec()
osSecretsResource, err := safe.ReaderGet[*secrets.OSRoot](ctx, r, resource.NewMetadata(
secrets.NamespaceName,
secrets.OSRootType,
secrets.OSRootID,
resource.VersionUndefined,
))
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error fetching os secrets: %w", err)
}
continue
}
osSecretsSpec := osSecretsResource.TypedSpec()
kubeconfig, err := clientcmd.BuildConfigFromKubeconfigGetter("", func() (*clientcmdapi.Config, error) {
return clientcmd.Load([]byte(kubeSecretsSpec.LocalhostAdminKubeconfig))
})
if err != nil {
return fmt.Errorf("error loading kubeconfig: %w", err)
}
stopCRDController()
var crdControllerCtx context.Context
crdControllerCtx, crdControllerCtxCancel = context.WithCancel(ctx) //nolint:govet
go func() {
crdControllerErrCh <- ctrl.runCRDController(
crdControllerCtx,
osSecretsSpec.CA,
kubeconfig,
kubeaccessConfigSpec,
logger,
)
}()
}
}
func (ctrl *CRDController) runCRDController(
ctx context.Context,
talosCA *x509.PEMEncodedCertificateAndKey,
kubeconfig *rest.Config,
kubeaccessCfgSpec *kubeaccess.ConfigSpec,
logger *zap.Logger,
) error {
return etcd.WithLock(ctx, constants.EtcdTalosServiceAccountCRDControllerMutex, logger, func() error {
crdCtrl, err := serviceaccount.NewCRDController(
talosCA,
kubeconfig,
kubeaccessCfgSpec.AllowedKubernetesNamespaces,
kubeaccessCfgSpec.AllowedAPIRoles,
logger,
)
if err != nil {
return err
}
return crdCtrl.Run(ctx, 1)
})
}

View File

@ -0,0 +1,644 @@
// 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 serviceaccount
import (
"bytes"
"context"
stdlibx509 "crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"reflect"
"sort"
"sync"
"time"
"github.com/talos-systems/crypto/x509"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/dynamic/dynamiclister"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/connrotation"
"k8s.io/client-go/util/workqueue"
taloskubernetes "github.com/talos-systems/talos/pkg/kubernetes"
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/generic/slices"
"github.com/talos-systems/talos/pkg/machinery/role"
)
const (
certTTL = time.Hour * 6
certRenewThreshold = time.Hour * 1
successResourceSynced = "Synced"
messageResourceSynced = "Synced successfully"
errResourceExists = "ErrResourceExists"
messageResourceExists = "%s already exists and is not managed by controller: %s"
errRolesNotFound = "ErrRolesNotFound"
messageRolesNotFound = "Roles not found"
errNamespaceNotAllowed = "ErrNamespaceNotAllowed"
messageNamespaceNotAllowed = "Namespace is not allowed: %s"
errRolesNotAllowed = "ErrRolesNotAllowed"
messageRolesNotAllowed = "Roles not allowed: %v"
controllerAgentName = "talos-sa-controller"
informerResyncPeriod = time.Minute * 1
talosconfigContextName = "default"
endpoint = constants.KubernetesTalosAPIServiceName + "." + constants.KubernetesTalosAPIServiceNamespace
kindSecret = "Secret"
)
var (
talosSAGV = schema.GroupVersion{
Group: constants.ServiceAccountResourceGroup,
Version: constants.ServiceAccountResourceVersion,
}
talosSAGVR = talosSAGV.WithResource(constants.ServiceAccountResourcePlural)
talosSAGVK = talosSAGV.WithKind(constants.ServiceAccountResourceKind)
)
// CRDController is the controller implementation for TalosServiceAccount resources.
type CRDController struct {
talosCA *x509.PEMEncodedCertificateAndKey
allowedNamespaces []string
allowedRoles map[string]struct{}
queue workqueue.RateLimitingInterface
kubeInformerFactory kubeinformers.SharedInformerFactory
dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory
kubeClient kubernetes.Interface
dynamicClient dynamic.Interface
dialer *connrotation.Dialer
secretsSynced cache.InformerSynced
talosSAsSynced cache.InformerSynced
secretsLister corelisters.SecretLister
dynamicLister dynamiclister.Lister
eventRecorder record.EventRecorder
logger *zap.Logger
}
// NewCRDController creates a new CRD controller.
func NewCRDController(
talosCA *x509.PEMEncodedCertificateAndKey,
kubeconfig *rest.Config,
allowedNamespaces []string,
allowedRoles []string,
logger *zap.Logger,
) (*CRDController, error) {
dialer := taloskubernetes.NewDialer()
kubeconfig.Dial = dialer.DialContext
kubeCli, err := kubernetes.NewForConfig(kubeconfig)
if err != nil {
return nil, err
}
dynCli, err := dynamic.NewForConfig(kubeconfig)
if err != nil {
return nil, err
}
dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(dynCli, informerResyncPeriod)
resourceInformer := dynamicInformerFactory.ForResource(talosSAGVR)
informer := resourceInformer.Informer()
indexer := informer.GetIndexer()
lister := dynamiclister.New(indexer, talosSAGVR)
kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeCli, informerResyncPeriod)
secrets := kubeInformerFactory.Core().V1().Secrets()
logger.Debug("creating event broadcaster")
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartStructuredLogging(0)
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeCli.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
controller := CRDController{
talosCA: talosCA,
allowedNamespaces: allowedNamespaces,
allowedRoles: slices.ToSet(allowedRoles),
dynamicInformerFactory: dynamicInformerFactory,
kubeInformerFactory: kubeInformerFactory,
kubeClient: kubeCli,
dynamicClient: dynCli,
dialer: dialer,
dynamicLister: lister,
queue: workqueue.NewNamedRateLimitingQueue(
workqueue.DefaultControllerRateLimiter(),
constants.ServiceAccountResourceKind,
),
logger: logger,
secretsSynced: secrets.Informer().HasSynced,
talosSAsSynced: informer.HasSynced,
eventRecorder: recorder,
secretsLister: secrets.Lister(),
}
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.enqueueTalosSA,
UpdateFunc: func(oldTalosSA, newTalosSA interface{}) {
controller.enqueueTalosSA(newTalosSA)
},
})
secrets.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.handleSecret,
UpdateFunc: func(oldSec, newSec interface{}) {
newSecret := newSec.(*corev1.Secret) //nolint:errcheck
oldSecret := oldSec.(*corev1.Secret) //nolint:errcheck
if newSecret.ResourceVersion == oldSecret.ResourceVersion {
return
}
controller.handleSecret(newSec)
},
DeleteFunc: controller.handleSecret,
})
return &controller, nil
}
// Run starts the CRD controller.
func (t *CRDController) Run(ctx context.Context, workers int) error {
var wg sync.WaitGroup
defer func() {
t.queue.ShutDown()
t.dialer.CloseAll()
wg.Wait()
t.logger.Debug("all workers have shut down")
}()
t.kubeInformerFactory.Start(ctx.Done())
t.dynamicInformerFactory.Start(ctx.Done())
t.logger.Sugar().Debugf("starting %s controller", constants.ServiceAccountResourceKind)
t.logger.Debug("waiting for informer caches to sync")
if ok := cache.WaitForCacheSync(ctx.Done(), t.secretsSynced, t.talosSAsSynced); !ok {
return fmt.Errorf("failed to wait for caches to sync")
}
t.logger.Debug("starting workers")
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
wait.Until(func() { t.runWorker(ctx) }, time.Second, ctx.Done())
wg.Done()
}()
}
t.logger.Debug("started workers")
<-ctx.Done()
t.logger.Debug("shutting down workers")
return nil
}
func (t *CRDController) runWorker(ctx context.Context) {
for t.processNextWorkItem(ctx) {
}
}
func (t *CRDController) processNextWorkItem(ctx context.Context) bool {
obj, shutdown := t.queue.Get()
if shutdown {
return false
}
err := func(obj interface{}) error {
defer t.queue.Done(obj)
var key string
var ok bool
if key, ok = obj.(string); !ok {
t.queue.Forget(obj)
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
return nil
}
if err := t.syncHandler(ctx, key); err != nil {
t.queue.AddRateLimited(key)
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
}
t.queue.Forget(obj)
t.logger.Sugar().Debugf("successfully synced '%s'", key)
return nil
}(obj)
if err != nil {
utilruntime.HandleError(err)
return true
}
return true
}
//nolint:gocyclo,cyclop,dupl
func (t *CRDController) syncHandler(ctx context.Context, key string) error {
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
return nil
}
talosSA, err := t.dynamicLister.Namespace(namespace).Get(name)
if err != nil {
if kubeerrors.IsNotFound(err) {
utilruntime.HandleError(fmt.Errorf("talosSA '%s' in work queue no longer exists", key))
return nil
}
return err
}
secret, err := t.secretsLister.Secrets(namespace).Get(name)
secretNotFound := kubeerrors.IsNotFound(err)
if err != nil && !secretNotFound {
return err
}
if !secretNotFound && !metav1.IsControlledBy(secret, talosSA) {
msg := fmt.Sprintf(messageResourceExists, kindSecret, key)
err = t.updateTalosSAStatus(ctx, talosSA, msg)
if err != nil {
return err
}
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errResourceExists, msg)
return errors.New(msg)
}
desiredRoles, found, err := unstructured.NestedStringSlice(talosSA.UnstructuredContent(), "spec", "roles")
if err != nil || !found {
msg := fmt.Sprint(messageRolesNotFound)
updateErr := t.updateTalosSAStatus(ctx, talosSA, msg)
if updateErr != nil {
return updateErr
}
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errRolesNotFound, messageRolesNotFound)
if err != nil {
return fmt.Errorf("%s: %w", msg, err)
}
return errors.New(msg)
}
desiredRoleSet, _ := role.Parse(desiredRoles)
if !slices.Contains(t.allowedNamespaces, func(allowedNS string) bool {
return allowedNS == namespace
}) {
msg := fmt.Sprintf(messageNamespaceNotAllowed, namespace)
err = t.updateTalosSAStatus(ctx, talosSA, msg)
if err != nil {
return err
}
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errNamespaceNotAllowed, msg)
return nil
}
var unallowedRoles []string
for _, desiredRole := range desiredRoles {
_, allowed := t.allowedRoles[desiredRole]
if !allowed {
unallowedRoles = append(unallowedRoles, desiredRole)
}
}
if len(unallowedRoles) > 0 {
msg := fmt.Sprintf(messageRolesNotAllowed, unallowedRoles)
err = t.updateTalosSAStatus(ctx, talosSA, msg)
if err != nil {
return err
}
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errRolesNotAllowed, msg)
return nil
}
if secretNotFound {
var newSecret *corev1.Secret
newSecret, err = t.newSecret(talosSA, desiredRoleSet)
if err != nil {
return err
}
_, err = t.kubeClient.CoreV1().Secrets(namespace).Create(ctx, newSecret, metav1.CreateOptions{})
if err != nil {
return err
}
} else if t.needsUpdate(secret, desiredRoleSet.Strings()) {
var newTalosconfigBytes []byte
newTalosconfigBytes, err = t.generateTalosconfig(desiredRoleSet)
if err != nil {
return err
}
secret.Data[constants.TalosconfigFilename] = newTalosconfigBytes
_, err = t.kubeClient.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{})
if err != nil {
return err
}
}
err = t.updateTalosSAStatus(ctx, talosSA, "")
if err != nil {
return err
}
t.eventRecorder.Event(talosSA, corev1.EventTypeNormal, successResourceSynced, messageResourceSynced)
return nil
}
func (t *CRDController) enqueueTalosSA(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
utilruntime.HandleError(err)
return
}
t.queue.Add(key)
}
func (t *CRDController) handleSecret(obj interface{}) {
var object metav1.Object
var ok bool
if object, ok = obj.(metav1.Object); !ok {
tombstone, tombstoneOK := obj.(cache.DeletedFinalStateUnknown)
if !tombstoneOK {
utilruntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
return
}
object, tombstoneOK = tombstone.Obj.(metav1.Object)
if !tombstoneOK {
utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type"))
return
}
t.logger.Sugar().Debugf("recovered deleted object '%s' from tombstone", object.GetName())
}
t.logger.Sugar().Debugf("processing object: %s", object.GetName())
if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
if ownerRef.Kind != constants.ServiceAccountResourceKind {
return
}
talosSA, err := t.dynamicLister.Namespace(object.GetNamespace()).Get(ownerRef.Name)
if err != nil {
t.logger.Sugar().Debugf("ignoring orphaned object '%s/%s' of %s '%s'",
object.GetNamespace(), object.GetName(), constants.ServiceAccountResourceKind, ownerRef.Name)
return
}
t.enqueueTalosSA(talosSA)
return
}
}
func (t *CRDController) updateTalosSAStatus(
ctx context.Context,
talosSA *unstructured.Unstructured,
failureReason string,
) error {
var err error
talosSACopy := talosSA.DeepCopy()
if err != nil {
return err
}
if failureReason == "" {
unstructured.RemoveNestedField(talosSACopy.UnstructuredContent(), "status", "failureReason")
if err != nil {
return err
}
} else {
err = unstructured.SetNestedField(talosSACopy.UnstructuredContent(), failureReason, "status", "failureReason")
if err != nil {
return err
}
}
_, err = t.dynamicClient.Resource(talosSAGVR).
Namespace(talosSACopy.GetNamespace()).
Update(ctx, talosSACopy, metav1.UpdateOptions{})
return err
}
//nolint:gocyclo
func (t *CRDController) needsUpdate(secret *corev1.Secret, desiredRoles []string) bool {
talosconfigInSecret, ok := secret.Data[constants.TalosconfigFilename]
if !ok {
t.logger.Debug("talosconfig not found in secret", zap.String("key", constants.TalosconfigFilename))
return true
}
parsedTalosconfigInSecret, err := clientconfig.ReadFrom(bytes.NewReader(talosconfigInSecret))
if err != nil {
t.logger.Debug("error parsing talosconfig in secret", zap.Error(err))
return true
}
talosconfigCtx := parsedTalosconfigInSecret.Contexts[parsedTalosconfigInSecret.Context]
talosconfigCA, err := base64.StdEncoding.DecodeString(talosconfigCtx.CA)
if err != nil {
t.logger.Debug("error decoding talosconfig CA", zap.Error(err))
return true
}
if !reflect.DeepEqual(t.talosCA.Crt, talosconfigCA) {
t.logger.Debug("ca mismatch detected")
return true
}
if len(talosconfigCtx.Endpoints) != 1 || talosconfigCtx.Endpoints[0] != endpoint {
t.logger.Debug(
"endpoint mismatch detected",
zap.Strings("actual", talosconfigCtx.Endpoints),
zap.Strings("expected", []string{endpoint}),
)
return true
}
talosconfigCRT, err := base64.StdEncoding.DecodeString(talosconfigCtx.Crt)
if err != nil {
t.logger.Debug("error decoding talosconfig CRT", zap.Error(err))
return true
}
block, _ := pem.Decode(talosconfigCRT)
if block == nil {
t.logger.Debug("could not decode talosconfig CRT")
return true
}
certificate, err := stdlibx509.ParseCertificate(block.Bytes)
if err != nil {
t.logger.Debug("error parsing certificate in talosconfig of secret", zap.Error(err))
return true
}
if certificate.NotAfter.IsZero() {
t.logger.Debug("certificate in talosconfig of secret has no expiration date", zap.Error(err))
return true
}
if time.Now().Add(certTTL).Before(certificate.NotAfter) {
t.logger.Debug(
"certificate in talosconfig has expiration date too far in the future",
zap.Time("expiration", certificate.NotAfter),
)
return true
}
if time.Now().Add(certRenewThreshold).After(certificate.NotAfter) {
t.logger.Debug(
"certificate in talosconfig needs renewal",
zap.Time("expiration", certificate.NotAfter),
)
return true
}
actualRoles := certificate.Subject.Organization
sort.Strings(actualRoles)
sort.Strings(desiredRoles)
if !reflect.DeepEqual(actualRoles, desiredRoles) {
t.logger.Debug("roles in certificate do not match desired roles",
zap.Strings("actual", actualRoles), zap.Strings("desired", desiredRoles))
return true
}
return false
}
func (t *CRDController) newSecret(talosSA *unstructured.Unstructured, roles role.Set) (*corev1.Secret, error) {
config, err := t.generateTalosconfig(roles)
if err != nil {
return nil, err
}
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: talosSA.GetName(),
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(talosSA, talosSAGVK),
},
},
Data: map[string][]byte{
constants.TalosconfigFilename: config,
},
}, nil
}
func (t *CRDController) generateTalosconfig(roles role.Set) ([]byte, error) {
var newCert *x509.PEMEncodedCertificateAndKey
newCert, err := generate.NewAdminCertificateAndKey(time.Now(), t.talosCA, roles, certTTL)
if err != nil {
return nil, err
}
newTalosconfig := clientconfig.NewConfig(talosconfigContextName, []string{endpoint}, t.talosCA.Crt, newCert)
return newTalosconfig.Bytes()
}

View File

@ -145,6 +145,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
&k8s.StaticPodConfigController{},
&kubeaccess.ConfigController{},
&kubeaccess.EndpointController{},
&kubeaccess.CRDController{},
&kubespan.ConfigController{},
&kubespan.EndpointController{},
&kubespan.IdentityController{},

View File

@ -0,0 +1,303 @@
// 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/.
//go:build integration_api
// +build integration_api
package api
import (
"context"
"fmt"
"time"
"github.com/siderolabs/go-pointer"
"github.com/talos-systems/go-retry/retry"
corev1 "k8s.io/api/core/v1"
eventsv1 "k8s.io/api/events/v1"
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/talos-systems/talos/internal/integration/base"
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
"github.com/talos-systems/talos/pkg/machinery/client"
"github.com/talos-systems/talos/pkg/machinery/client/config"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
var (
serviceAccountGVR = schema.GroupVersionResource{
Group: constants.ServiceAccountResourceGroup,
Version: constants.ServiceAccountResourceVersion,
Resource: constants.ServiceAccountResourcePlural,
}
secretGVR = schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "secrets",
}
)
// ServiceAccountSuite verifies Talos ServiceAccount.
type ServiceAccountSuite struct {
base.K8sSuite
ctx context.Context //nolint:containedctx
ctxCancel context.CancelFunc
}
// SuiteName ...
func (suite *ServiceAccountSuite) SuiteName() string {
return "api.ServiceAccountSuite"
}
// SetupTest ...
func (suite *ServiceAccountSuite) SetupTest() {
// make sure API calls have timeout
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 5*time.Minute)
}
// TearDownTest ...
func (suite *ServiceAccountSuite) TearDownTest() {
if suite.ctxCancel != nil {
suite.ctxCancel()
}
}
// TestValid tests Kubernetes service accounts.
//
//nolint:dupl
func (suite *ServiceAccountSuite) TestValid() {
name := "test-valid"
err := suite.configureAPIAccess(true, []string{"os:reader"}, []string{"kube-system"})
suite.Assert().NoError(err)
_, err = suite.getCRD()
suite.Assert().NoError(err)
sa, err := suite.createServiceAccount("kube-system", name, []string{"os:reader"})
suite.Assert().NoError(err)
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "default", name) // nolint:errcheck
err = suite.WaitForEventExists(suite.ctx, "kube-system", func(event eventsv1.Event) bool {
return event.Regarding.UID == sa.GetUID() &&
event.Type == corev1.EventTypeNormal &&
event.Reason == "Synced"
})
suite.Assert().NoError(err)
secret, err := suite.waitForSecret("kube-system", name)
suite.Assert().NoError(err)
talosConfig := secret.Data["config"]
conf, err := config.FromBytes(talosConfig)
suite.Assert().NoError(err)
expectedServiceName := fmt.Sprintf(
"%s.%s",
constants.KubernetesTalosAPIServiceName,
constants.KubernetesTalosAPIServiceNamespace,
)
suite.Assert().Equal([]string{expectedServiceName}, conf.Contexts[conf.Context].Endpoints)
err = suite.DeleteResource(suite.ctx, serviceAccountGVR, "kube-system", name)
suite.Require().NoError(err)
err = suite.EnsureResourceIsDeleted(suite.ctx, 30*time.Second, secretGVR, "kube-system", name)
suite.Assert().NoError(err)
}
// TestNotAllowedNamespace tests Kubernetes service accounts in not allowed namespaces.
//
//nolint:dupl
func (suite *ServiceAccountSuite) TestNotAllowedNamespace() {
name := "test-allowed-ns"
err := suite.configureAPIAccess(true, []string{"os:reader"}, []string{"kube-system"})
suite.Assert().NoError(err)
sa, err := suite.createServiceAccount("default", name, []string{"os:reader"})
suite.Assert().NoError(err)
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "default", name) // nolint:errcheck
err = suite.WaitForEventExists(suite.ctx, "default", func(event eventsv1.Event) bool {
return event.Regarding.UID == sa.GetUID() &&
event.Type == corev1.EventTypeWarning &&
event.Reason == "ErrNamespaceNotAllowed"
})
suite.Assert().NoError(err)
}
// TestNotAllowedRoles tests Kubernetes service accounts with not allowed roles.
//
//nolint:dupl
func (suite *ServiceAccountSuite) TestNotAllowedRoles() {
name := "test-not-allowed-roles"
err := suite.configureAPIAccess(true, []string{"os:reader"}, []string{"kube-system"})
suite.Assert().NoError(err)
sa, err := suite.createServiceAccount("kube-system", name, []string{"os:admin"})
suite.Assert().NoError(err)
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "kube-system", name) // nolint:errcheck
err = suite.WaitForEventExists(suite.ctx, "kube-system", func(event eventsv1.Event) bool {
return event.Regarding.UID == sa.GetUID() &&
event.Type == corev1.EventTypeWarning &&
event.Reason == "ErrRolesNotAllowed"
})
suite.Assert().NoError(err)
}
// TestFeatureNotEnabled tests Kubernetes service accounts when API access feature is not enabled.
//
//nolint:dupl
func (suite *ServiceAccountSuite) TestFeatureNotEnabled() {
name := "test-feature-not-enabled"
err := suite.configureAPIAccess(false, []string{"os:reader"}, []string{"kube-system"})
suite.Assert().NoError(err)
sa, err := suite.createServiceAccount("kube-system", name, []string{"os:reader"})
if kubeerrors.IsNotFound(err) {
// CRD is not created because the feature was never enabled, all good
return
}
suite.Assert().NoError(err)
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "kube-system", name) // nolint:errcheck
err = suite.WaitForEventExists(suite.ctx, "kube-system", func(event eventsv1.Event) bool {
return event.Regarding.UID == sa.GetUID() &&
event.Type == corev1.EventTypeWarning &&
event.Reason == "ErrAccessNotEnabled"
})
suite.Assert().NoError(err)
}
func (suite *ServiceAccountSuite) waitForSecret(ns, name string) (*corev1.Secret, error) {
var (
secret *corev1.Secret
err error
)
err = retry.Constant(1*time.Minute).RetryWithContext(suite.ctx, func(ctx context.Context) error {
secret, err = suite.Clientset.CoreV1().Secrets(ns).Get(suite.ctx, name, metav1.GetOptions{})
if kubeerrors.IsNotFound(err) {
return retry.ExpectedError(err)
}
return err
})
if err != nil {
return nil, err
}
return secret, nil
}
func (suite *ServiceAccountSuite) getCRD() (*unstructured.Unstructured, error) {
crdName := fmt.Sprintf("%s.%s", constants.ServiceAccountResourcePlural, constants.ServiceAccountResourceGroup)
return suite.DynamicClient.Resource(schema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Version: "v1",
Resource: "customresourcedefinitions",
}).Get(suite.ctx, crdName, metav1.GetOptions{})
}
func (suite *ServiceAccountSuite) createServiceAccount(ns string, name string, roles []string) (*unstructured.Unstructured, error) {
return suite.DynamicClient.Resource(serviceAccountGVR).Namespace(ns).Create(suite.ctx, &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": fmt.Sprintf("%s/%s", constants.ServiceAccountResourceGroup, constants.ServiceAccountResourceVersion),
"kind": constants.ServiceAccountResourceKind,
"metadata": map[string]interface{}{
"name": name,
},
"spec": map[string]interface{}{
"roles": roles,
},
},
}, metav1.CreateOptions{})
}
// configureAPIAccess configures the API access feature on all control plane nodes.
func (suite *ServiceAccountSuite) configureAPIAccess(
enabled bool,
allowedRoles []string,
allowedNamespaces []string,
) error {
controlPlaneIPs := suite.DiscoverNodeInternalIPsByType(suite.ctx, machine.TypeControlPlane)
for _, ip := range controlPlaneIPs {
nodeCtx := client.WithNodes(suite.ctx, ip)
nodeConfig, err := suite.ReadConfigFromNode(nodeCtx)
if err != nil {
return err
}
nodeConfigRaw, ok := nodeConfig.Raw().(*v1alpha1.Config)
if !ok {
return fmt.Errorf("unexpected node config type %T", nodeConfig.Raw())
}
accessConfig := v1alpha1.KubernetesTalosAPIAccessConfig{
AccessEnabled: pointer.To(enabled),
AccessAllowedRoles: allowedRoles,
AccessAllowedKubernetesNamespaces: allowedNamespaces,
}
nodeConfigRaw.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig = &accessConfig
bytes, err := nodeConfigRaw.Bytes()
if err != nil {
return err
}
_, err = suite.Client.ApplyConfiguration(nodeCtx, &machineapi.ApplyConfigurationRequest{
Data: bytes,
Mode: machineapi.ApplyConfigurationRequest_NO_REBOOT,
})
if err != nil {
return err
}
}
if enabled { // wait for CRD and the Talos endpoint to be created
return retry.Constant(30*time.Second).RetryWithContext(suite.ctx, func(ctx context.Context) error {
_, err := suite.getCRD()
if err != nil {
return retry.ExpectedError(err)
}
_, err = suite.Clientset.CoreV1().
Services(constants.KubernetesTalosAPIServiceNamespace).
Get(suite.ctx, constants.KubernetesTalosAPIServiceName, metav1.GetOptions{})
if err != nil {
return retry.ExpectedError(err)
}
return nil
})
}
return nil
}
func init() {
allSuites = append(allSuites, new(ServiceAccountSuite))
}

View File

@ -14,12 +14,17 @@ import (
"github.com/talos-systems/go-retry/retry"
corev1 "k8s.io/api/core/v1"
eventsv1 "k8s.io/api/events/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"github.com/talos-systems/talos/pkg/machinery/generic/slices"
)
// K8sSuite is a base suite for K8s tests.
@ -27,6 +32,7 @@ type K8sSuite struct {
APISuite
Clientset *kubernetes.Clientset
DynamicClient dynamic.Interface
DiscoveryClient *discovery.DiscoveryClient
}
@ -51,6 +57,9 @@ func (k8sSuite *K8sSuite) SetupSuite() {
k8sSuite.Clientset, err = kubernetes.NewForConfig(config)
k8sSuite.Require().NoError(err)
k8sSuite.DynamicClient, err = dynamic.NewForConfig(config)
k8sSuite.Require().NoError(err)
k8sSuite.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(config)
k8sSuite.Require().NoError(err)
}
@ -111,3 +120,49 @@ func (k8sSuite *K8sSuite) GetK8sNodeReadinessStatus(ctx context.Context, nodeNam
return "", fmt.Errorf("node %s has no readiness condition", nodeName)
}
// DeleteResource deletes the resource with the given GroupVersionResource, namespace and name.
// Does not return an error if the resource is not found.
func (k8sSuite *K8sSuite) DeleteResource(ctx context.Context, gvr schema.GroupVersionResource, ns, name string) error {
err := k8sSuite.DynamicClient.Resource(gvr).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{})
if errors.IsNotFound(err) {
return nil
}
return err
}
// EnsureResourceIsDeleted ensures that the resource with the given GroupVersionResource, namespace and name does not exist on Kubernetes.
// It repeatedly checks the resource for the given duration.
func (k8sSuite *K8sSuite) EnsureResourceIsDeleted(
ctx context.Context,
duration time.Duration,
gvr schema.GroupVersionResource,
ns, name string,
) error {
return retry.Constant(duration).RetryWithContext(ctx, func(ctx context.Context) error {
_, err := k8sSuite.DynamicClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
if errors.IsNotFound(err) {
return nil
}
return err
})
}
// WaitForEventExists waits for the event with the given namespace and check condition to exist on Kubernetes.
func (k8sSuite *K8sSuite) WaitForEventExists(ctx context.Context, ns string, checkFn func(event eventsv1.Event) bool) error {
return retry.Constant(15*time.Second).RetryWithContext(ctx, func(ctx context.Context) error {
events, err := k8sSuite.Clientset.EventsV1().Events(ns).List(ctx, metav1.ListOptions{})
filteredEvents := slices.Filter(events.Items, func(item eventsv1.Event) bool {
return checkFn(item)
})
if len(filteredEvents) == 0 {
return retry.ExpectedError(err)
}
return nil
})
}

View File

@ -0,0 +1,95 @@
// 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/.
//go:build integration_cli
// +build integration_cli
package cli
import (
"bytes"
_ "embed"
"fmt"
"io"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
"github.com/talos-systems/talos/internal/integration/base"
)
var (
//go:embed testdata/inject/talosconfig-input.yaml
inputManifests []byte
//go:embed testdata/inject/talosconfig-expected.yaml
expectedManifests []byte
)
// InjectSuite verifies inject command.
type InjectSuite struct {
base.CLISuite
}
// SuiteName ...
func (suite *InjectSuite) SuiteName() string {
return "cli.InjectSuite"
}
// TestServiceAccount tests inject serviceaccount command.
func (suite *InjectSuite) TestServiceAccount() {
suite.testServiceAccount(inputManifests)
}
// TestServiceAccountAlreadyInjectedNoChange tests inject serviceaccount command when the input manifest is already injected,
// makes sure that it stays the same.
func (suite *InjectSuite) TestServiceAccountAlreadyInjectedNoChange() {
suite.testServiceAccount(expectedManifests)
}
func (suite *InjectSuite) testServiceAccount(input []byte) {
expectedDocs, err := yamlDocs(expectedManifests)
suite.Require().NoError(err)
tempDir := suite.T().TempDir()
inputPath := filepath.Join(tempDir, "input.yaml")
err = os.WriteFile(inputPath, input, 0o644)
suite.Require().NoError(err)
stdout, _ := suite.RunCLI([]string{"inject", "serviceaccount", "-f", inputPath, "--roles", "os:reader,os:admin"})
stdoutDocs, err := yamlDocs([]byte(stdout))
suite.Require().NoError(err)
suite.Assert().Equal(expectedDocs, stdoutDocs, "inject serviceaccount output did not match expected output")
}
func yamlDocs(input []byte) ([]map[string]any, error) {
decoder := yaml.NewDecoder(bytes.NewReader(input))
var docs []map[string]any
for {
var doc map[string]any
if err := decoder.Decode(&doc); err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("document decode failed: %w", err)
}
docs = append(docs, doc)
}
return docs, nil
}
func init() {
allSuites = append(allSuites, new(InjectSuite))
}

View File

@ -0,0 +1,302 @@
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: donottouch
spec:
roles:
- os:reader
- os:admin
---
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
name: test1
spec:
containers:
- env:
- name: TEST
value: test
image: alpine:3
name: container1
resources: {}
volumeMounts:
- mountPath: /mnt/vol1
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: alpine:3
name: container2
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
initContainers:
- image: busybox
name: init1
resources: {}
volumeMounts:
- mountPath: /tmp/hello
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: busybox
name: init2
resources: {}
volumeMounts:
- mountPath: /tmp/hello
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
volumes:
- emptyDir: {}
name: vol1
- name: talos-secrets
secret:
secretName: test1-talos-secrets
status: {}
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: test1-talos-secrets
spec:
roles:
- os:reader
- os:admin
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
name: test1
spec:
selector:
matchLabels:
app: test
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: alpine:3
name: container1
resources: {}
volumeMounts:
- mountPath: /mnt/vol1
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: alpine:3
name: container2
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
volumes:
- emptyDir: {}
name: vol1
- name: talos-secrets
secret:
secretName: test1-talos-secrets
status: {}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
creationTimestamp: null
name: test2
spec:
selector:
matchLabels:
app: test
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: alpine:3
name: container1
resources: {}
volumeMounts:
- mountPath: /mnt/vol1
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: alpine:3
name: container2
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
volumes:
- emptyDir: {}
name: vol1
- name: talos-secrets
secret:
secretName: test2-talos-secrets
updateStrategy: {}
status:
currentNumberScheduled: 0
desiredNumberScheduled: 0
numberMisscheduled: 0
numberReady: 0
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: test2-talos-secrets
spec:
roles:
- os:reader
- os:admin
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
creationTimestamp: null
name: test3
spec:
selector:
matchLabels:
app: test
serviceName: test
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: alpine:3
name: container1
resources: {}
volumeMounts:
- mountPath: /mnt/vol1
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: alpine:3
name: container2
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
volumes:
- emptyDir: {}
name: vol1
- name: talos-secrets
secret:
secretName: test3-talos-secrets
updateStrategy: {}
status:
availableReplicas: 0
replicas: 0
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: test3-talos-secrets
spec:
roles:
- os:reader
- os:admin
---
apiVersion: batch/v1
kind: CronJob
metadata:
creationTimestamp: null
name: test4
namespace: testns
spec:
jobTemplate:
metadata:
creationTimestamp: null
labels:
app: test
spec:
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: alpine:3
name: container1
resources: {}
volumeMounts:
- mountPath: /mnt/vol1
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: alpine:3
name: container2
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
volumes:
- name: talos-secrets
secret:
secretName: test4-talos-secrets
schedule: '*/1 * * * *'
status: {}
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: test4-talos-secrets
namespace: testns
spec:
roles:
- os:reader
- os:admin
---
apiVersion: batch/v1
kind: Job
metadata:
creationTimestamp: null
name: test5
namespace: testns2
spec:
template:
metadata:
creationTimestamp: null
spec:
containers:
- image: alpine:3
name: container1
resources: {}
volumeMounts:
- mountPath: /mnt/vol1
name: vol1
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
- image: alpine:3
name: container2
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
volumes:
- name: talos-secrets
secret:
secretName: test5-talos-secrets
status: {}
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: test5-talos-secrets
namespace: testns2
spec:
roles:
- os:reader
- os:admin
---

View File

@ -0,0 +1,159 @@
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: donottouch
spec:
roles:
- os:reader
- os:admin
---
apiVersion: v1
kind: Pod
metadata:
name: test1
spec:
volumes:
- name: vol1
emptyDir: {}
initContainers:
- name: init1
image: busybox
volumeMounts:
- name: vol1
mountPath: /tmp/hello
- name: init2
image: busybox
volumeMounts:
- name: vol1
mountPath: /tmp/hello
containers:
- name: container1
image: alpine:3
env:
- name: TEST
value: test
volumeMounts:
- name: vol1
mountPath: /mnt/vol1
- name: container2
image: alpine:3
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test1
spec:
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
volumes:
- name: vol1
emptyDir: {}
containers:
- name: container1
image: alpine:3
volumeMounts:
- name: vol1
mountPath: /mnt/vol1
- name: container2
image: alpine:3
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test2
spec:
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
volumes:
- name: vol1
emptyDir: {}
containers:
- name: container1
image: alpine:3
volumeMounts:
- name: vol1
mountPath: /mnt/vol1
- name: container2
image: alpine:3
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test3
spec:
serviceName: test
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
volumes:
- name: vol1
emptyDir: {}
containers:
- name: container1
image: alpine:3
volumeMounts:
- name: vol1
mountPath: /mnt/vol1
- name: container2
image: alpine:3
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: test4
namespace: testns
spec:
schedule: "*/1 * * * *"
jobTemplate:
metadata:
labels:
app: test
spec:
template:
metadata:
labels:
app: test
spec:
containers:
- name: container1
image: alpine:3
volumeMounts:
- name: vol1
mountPath: /mnt/vol1
- name: container2
image: alpine:3
---
apiVersion: batch/v1
kind: Job
metadata:
name: test5
namespace: testns2
spec:
template:
spec:
containers:
- name: container1
image: alpine:3
volumeMounts:
- name: vol1
mountPath: /mnt/vol1
- name: container2
image: alpine:3

View File

@ -11,6 +11,7 @@ package integration_test
import (
"context"
"flag"
"fmt"
"os"
"path/filepath"
"testing"
@ -120,7 +121,7 @@ func TestIntegration(t *testing.T) {
}
func init() {
defaultTalosConfig, _ := clientconfig.GetDefaultPath() //nolint:errcheck
defaultTalosConfigs, _ := clientconfig.GetDefaultPaths() //nolint:errcheck
defaultStateDir, err := clientconfig.GetTalosDirectory()
if err == nil {
@ -130,7 +131,16 @@ func init() {
flag.BoolVar(&failFast, "talos.failfast", false, "fail the test run on the first failed test")
flag.BoolVar(&crashdumpEnabled, "talos.crashdump", true, "print crashdump on test failure (only if provisioner is enabled)")
flag.StringVar(&talosConfig, "talos.config", defaultTalosConfig, "The path to the Talos configuration file")
flag.StringVar(
&talosConfig,
"talos.config",
defaultTalosConfigs[0].Path,
fmt.Sprintf("The path to the Talos configuration file. Defaults to '%s' env variable if set, otherwise '%s' and '%s' in order.",
constants.TalosConfigEnvVar,
filepath.Join("$HOME", constants.TalosDir, constants.TalosconfigFilename),
filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
),
)
flag.StringVar(&endpoint, "talos.endpoint", "", "endpoint to use (overrides config)")
flag.StringVar(&k8sEndpoint, "talos.k8sendpoint", "", "Kubernetes endpoint to use (overrides kubeconfig)")
flag.StringVar(&provisionerName, "talos.provisioner", "", "Talos cluster provisioner to use, if not set cluster state is disabled")

View File

@ -462,15 +462,12 @@ func (suite *UpgradeSuite) setupCluster() {
)
suite.Require().NoError(err)
defaultTalosConfig, err := clientconfig.GetDefaultPath()
suite.Require().NoError(err)
c, err := clientconfig.Open(defaultTalosConfig)
c, err := clientconfig.Open("")
suite.Require().NoError(err)
c.Merge(suite.configBundle.TalosConfig())
suite.Require().NoError(c.Save(defaultTalosConfig))
suite.Require().NoError(c.Save(""))
suite.clusterAccess = access.NewAdapter(suite.Cluster, provision.WithTalosConfig(suite.configBundle.TalosConfig()))

44
internal/pkg/etcd/lock.go Normal file
View File

@ -0,0 +1,44 @@
// 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 etcd
import (
"context"
"fmt"
"go.etcd.io/etcd/client/v3/concurrency"
"go.uber.org/zap"
)
// WithLock executes the given function exclusively by acquiring an Etcd lock with the given key.
func WithLock(ctx context.Context, key string, logger *zap.Logger, f func() error) error {
etcdClient, err := NewLocalClient()
if err != nil {
return fmt.Errorf("error creating etcd client: %w", err)
}
defer etcdClient.Close() //nolint:errcheck
session, err := concurrency.NewSession(etcdClient.Client)
if err != nil {
return fmt.Errorf("error creating etcd session: %w", err)
}
defer session.Close() //nolint:errcheck
mutex := concurrency.NewMutex(session, key)
logger.Debug("waiting for mutex", zap.String("key", key))
if err = mutex.Lock(ctx); err != nil {
return fmt.Errorf("error acquiring mutex for key %s: %w", key, err)
}
logger.Debug("mutex acquired", zap.String("key", key))
defer mutex.Unlock(ctx) //nolint:errcheck
return f()
}

View File

@ -8,7 +8,6 @@ package installer
import (
"context"
"fmt"
"os"
"strings"
"sync"
@ -427,26 +426,11 @@ func (installer *Installer) apply(conn *Connection) error {
}
func (installer *Installer) writeTalosconfig(list *tview.Flex, talosconfig *clientconfig.Config) error {
path, err := clientconfig.GetDefaultPath()
config, err := clientconfig.Open("")
if err != nil {
return err
}
f, err := os.Open(path)
var config *clientconfig.Config
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
config, err = clientconfig.ReadFrom(f)
if err != nil {
return err
}
}
text := tview.NewTextView()
addLines := func(lines ...string) {
t := text.GetText(false)
@ -457,17 +441,12 @@ func (installer *Installer) writeTalosconfig(list *tview.Flex, talosconfig *clie
addLines(
"",
fmt.Sprintf("Merging talosconfig into %s...", path),
fmt.Sprintf("Merging talosconfig into %s...", config.Path().Path),
)
text.SetBackgroundColor(color)
list.AddItem(text, 0, 1, false)
renames := []clientconfig.Rename{}
if config != nil {
renames = config.Merge(talosconfig)
} else {
config = talosconfig
}
renames := config.Merge(talosconfig)
for _, rename := range renames {
addLines(fmt.Sprintf("Renamed %s.", rename.String()))
@ -481,7 +460,7 @@ func (installer *Installer) writeTalosconfig(list *tview.Flex, talosconfig *clie
config.Context = context
addLines(fmt.Sprintf("Set current context to %q.", context))
err = config.Save(path)
err = config.Save("")
if err != nil {
return err
}

View File

@ -0,0 +1,378 @@
// 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 inject
import (
"bytes"
"errors"
"fmt"
"io"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
const (
injectToEnv = false
volumeName = "talos-secrets"
nameSuffix = "-talos-secrets"
apiVersionField = "apiVersion"
kindField = "kind"
metadataField = "metadata"
namespaceField = "namespace"
nameField = "name"
yamlSeparator = "---\n"
)
// ServiceAccount takes a YAML with Kubernetes manifests and requested Talos roles as input
// and injects Talos service accounts into them.
//
//nolint:gocyclo
func ServiceAccount(reader io.Reader, roles []string) ([]byte, error) {
var err error
objectSerializer := json.NewSerializerWithOptions(
json.DefaultMetaFactory,
nil,
nil,
json.SerializerOptions{
Yaml: true,
Pretty: true,
Strict: true,
},
)
seenResourceIDs := make(map[string]struct{})
var buf bytes.Buffer
decoder := yaml.NewDecoder(reader)
// loop over all documents in a possibly YAML with multiple documents separated by ---
for {
var raw map[string]any
err = decoder.Decode(&raw)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, err
}
if raw == nil {
continue
}
var injected metav1.Object
injected, err = injectToObject(raw)
if err != nil { // not a known resource with a PodSpec
// if this is already a Talos ServiceAccount resource we have seen,
// we keep it only if we have not seen it yet (means it belongs to the user, not injected by us)
id := readResourceIDFromServiceAccount(raw)
if id != "" {
if _, ok := seenResourceIDs[id]; ok {
continue
}
seenResourceIDs[id] = struct{}{}
}
err = yaml.NewEncoder(&buf).Encode(raw)
if err != nil {
return nil, err
}
buf.WriteString(yamlSeparator)
continue
}
// injectable resource type which contains a PodSpec
runtimeObject, ok := injected.(runtime.Object)
if !ok {
return nil, errors.New("injected object is not a runtime.Object")
}
err = objectSerializer.Encode(runtimeObject, &buf)
if err != nil {
return nil, err
}
buf.WriteString(yamlSeparator)
id := readResourceIDFromObject(injected)
// inject service account for the resource
if _, ok = seenResourceIDs[id]; !ok {
sa := buildServiceAccount(injected.GetNamespace(), fmt.Sprintf("%s%s", injected.GetName(), nameSuffix), roles)
err = yaml.NewEncoder(&buf).Encode(sa)
if err != nil {
return nil, err
}
buf.WriteString(yamlSeparator)
// mark resource as seen
seenResourceIDs[id] = struct{}{}
}
}
return buf.Bytes(), nil
}
func buildServiceAccount(namespace string, name string, roles []string) map[string]any {
metadata := map[string]any{
nameField: name,
}
if namespace != "" {
metadata[namespaceField] = namespace
}
return map[string]any{
apiVersionField: fmt.Sprintf(
"%s/%s",
constants.ServiceAccountResourceGroup,
constants.ServiceAccountResourceVersion,
),
kindField: constants.ServiceAccountResourceKind,
metadataField: metadata,
"spec": map[string]any{
"roles": roles,
},
}
}
func isServiceAccount(raw map[string]any) bool {
apiVersionKind, err := readResourceAPIVersionKind(raw)
if err != nil {
return false
}
return apiVersionKind == fmt.Sprintf(
"%s/%s/%s",
constants.ServiceAccountResourceGroup,
constants.ServiceAccountResourceVersion,
constants.ServiceAccountResourceKind,
)
}
// injectToDocument takes a single YAML document and attempts to inject a ServiceAccount
// into it if it is a known Kubernetes resource type which contains a corev1.PodSpec.
//
//nolint:gocyclo
func injectToObject(raw map[string]any) (metav1.Object, error) {
var err error
apiVersionKind, err := readResourceAPIVersionKind(raw)
if err != nil {
return nil, err
}
switch apiVersionKind {
case "v1/Pod":
return injectToPodSpecObject[corev1.Pod](raw, func(obj *corev1.Pod) *corev1.PodSpec {
return &obj.Spec
})
case "apps/v1/Deployment":
return injectToPodSpecObject[appsv1.Deployment](raw, func(obj *appsv1.Deployment) *corev1.PodSpec {
return &obj.Spec.Template.Spec
})
case "apps/v1/StatefulSet":
return injectToPodSpecObject[appsv1.StatefulSet](raw, func(obj *appsv1.StatefulSet) *corev1.PodSpec {
return &obj.Spec.Template.Spec
})
case "apps/v1/DaemonSet":
return injectToPodSpecObject[appsv1.DaemonSet](raw, func(obj *appsv1.DaemonSet) *corev1.PodSpec {
return &obj.Spec.Template.Spec
})
case "batch/v1/Job":
return injectToPodSpecObject[batchv1.Job](raw, func(obj *batchv1.Job) *corev1.PodSpec {
return &obj.Spec.Template.Spec
})
case "batch/v1/CronJob":
return injectToPodSpecObject[batchv1.CronJob](raw, func(obj *batchv1.CronJob) *corev1.PodSpec {
return &obj.Spec.JobTemplate.Spec.Template.Spec
})
}
return nil, fmt.Errorf("unsupported object type: %s", apiVersionKind)
}
func injectToPodSpecObject[T any](raw map[string]any, podSpecFunc func(*T) *corev1.PodSpec) (*T, error) {
objectName, nameFound, err := unstructured.NestedString(raw, metadataField, nameField)
if err != nil {
return nil, err
}
if !nameFound {
return nil, errors.New("object has no name")
}
var obj T
err = runtime.DefaultUnstructuredConverter.FromUnstructuredWithValidation(raw, &obj, false)
if err != nil {
return nil, err
}
injectToPodSpec(fmt.Sprintf("%s%s", objectName, nameSuffix), podSpecFunc(&obj))
return &obj, nil
}
func readResourceAPIVersionKind(raw map[string]any) (string, error) {
apiVersion, found, err := unstructured.NestedString(raw, apiVersionField)
if err != nil {
return "", err
}
if !found {
return "", fmt.Errorf("%s not found", apiVersionField)
}
kind, found, err := unstructured.NestedString(raw, kindField)
if err != nil {
return "", err
}
if !found {
return "", fmt.Errorf("%s not found", kindField)
}
return fmt.Sprintf("%s/%s", apiVersion, kind), nil
}
func readResourceIDFromObject(obj metav1.Object) string {
if obj.GetNamespace() == "" {
return obj.GetName()
}
return fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())
}
func readResourceIDFromServiceAccount(raw map[string]any) string {
if !isServiceAccount(raw) {
return ""
}
name, nameFound, err := unstructured.NestedString(raw, metadataField, nameField)
if err != nil || !nameFound {
return ""
}
nameTrimmed := strings.TrimSuffix(name, nameSuffix)
ns, nsFound, err := unstructured.NestedString(raw, metadataField, namespaceField)
if err != nil {
return ""
}
if nsFound {
return fmt.Sprintf("%s/%s", ns, nameTrimmed)
}
return nameTrimmed
}
func injectToPodSpec(secretName string, podSpec *corev1.PodSpec) {
podSpec.Volumes = injectToVolumes(secretName, podSpec.Volumes)
podSpec.InitContainers = injectToContainers(podSpec.InitContainers)
podSpec.Containers = injectToContainers(podSpec.Containers)
}
func injectToVolumes(name string, volumes []corev1.Volume) []corev1.Volume {
result := make([]corev1.Volume, 0, len(volumes))
for _, volume := range volumes {
if volume.Name != volumeName {
result = append(result, volume)
}
}
result = append(result, corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: name,
},
},
})
return result
}
func injectToContainers(containers []corev1.Container) []corev1.Container {
result := make([]corev1.Container, 0, len(containers))
for _, container := range containers {
injectToContainer(&container)
result = append(result, container)
}
return result
}
func injectToContainer(container *corev1.Container) {
volumeMounts := make([]corev1.VolumeMount, 0, len(container.VolumeMounts))
for _, mount := range container.VolumeMounts {
if mount.Name != volumeName {
volumeMounts = append(volumeMounts, mount)
}
}
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: volumeName,
MountPath: constants.ServiceAccountMountPath,
})
container.VolumeMounts = volumeMounts
if injectToEnv {
container.Env = injectToContainerEnv(container.Env)
}
}
func injectToContainerEnv(env []corev1.EnvVar) []corev1.EnvVar {
result := make([]corev1.EnvVar, 0, len(env))
for _, envVar := range env {
if envVar.Name != constants.TalosConfigEnvVar {
result = append(result, envVar)
}
}
result = append(result, corev1.EnvVar{
Name: constants.TalosConfigEnvVar,
Value: filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
})
return result
}

View File

@ -13,13 +13,16 @@ import (
"path/filepath"
"github.com/talos-systems/crypto/x509"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
// Config represents the client configuration file (talosconfig).
type Config struct {
Context string `yaml:"context"`
Contexts map[string]*Context `yaml:"contexts"`
// path is the config Path config is read from.
path Path
}
// NewConfig returns the client configuration file with a single context.
@ -61,21 +64,50 @@ func (c *Context) upgrade() {
}
// Open reads the config and initializes a Config struct.
func Open(p string) (c *Config, err error) {
if err = ensure(p); err != nil {
// If path is explicitly set, it will be used.
// If not, the default path rules will be used.
func Open(path string) (*Config, error) {
var (
confPath Path
err error
)
if path != "" { // path is explicitly specified, ensure that is created and use it
confPath = Path{
Path: path,
WriteAllowed: true,
}
err = ensure(confPath.Path)
if err != nil {
return nil, err
}
} else { // path is implicit, get the first already existing & readable path or ensure that it is created
confPath, err = firstValidPath()
if err != nil {
return nil, err
}
}
config, err := fromFile(confPath.Path)
if err != nil {
return nil, err
}
var f *os.File
config.path = confPath
f, err = os.Open(p)
return config, nil
}
func fromFile(path string) (*Config, error) {
file, err := os.Open(path)
if err != nil {
return
return nil, err
}
defer f.Close() //nolint:errcheck
defer file.Close() //nolint:errcheck
return ReadFrom(f)
return ReadFrom(file)
}
// FromString returns a config from a string.
@ -102,18 +134,37 @@ func ReadFrom(r io.Reader) (c *Config, err error) {
}
// Save writes the config to disk.
func (c *Config) Save(p string) (err error) {
configBytes, err := c.Bytes()
if err != nil {
return
// If the path is not explicitly set, the default path rules will be used.
func (c *Config) Save(path string) error {
var err error
if path != "" { // path is explicitly specified, use it
c.path = Path{
Path: path,
WriteAllowed: true,
}
} else if c.path.Path == "" { // path is implicit and is not set on config, get the first already existing & writable path or create it
c.path, err = firstValidPath()
if err != nil {
return err
}
}
if err = os.MkdirAll(filepath.Dir(p), 0o700); err != nil {
if !c.path.WriteAllowed {
return fmt.Errorf("not allowed to write to config: %s", c.path.Path)
}
configBytes, err := c.Bytes()
if err != nil {
return err
}
if err = os.WriteFile(p, configBytes, 0o600); err != nil {
return
if err = os.MkdirAll(filepath.Dir(c.path.Path), 0o700); err != nil {
return err
}
if err = os.WriteFile(c.path.Path, configBytes, 0o600); err != nil {
return err
}
return nil
@ -124,6 +175,11 @@ func (c *Config) Bytes() ([]byte, error) {
return yaml.Marshal(c)
}
// Path returns the filesystem path config was read from.
func (c *Config) Path() Path {
return c.path
}
// Rename describes context rename during merge.
type Rename struct {
From string
@ -175,14 +231,14 @@ func (c *Config) Merge(cfg *Config) []Rename {
return renames
}
func ensure(filename string) (err error) {
if _, err := os.Stat(filename); os.IsNotExist(err) {
func ensure(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
config := &Config{
Context: "",
Contexts: map[string]*Context{},
}
return config.Save(filename)
return config.Save(path)
}
return nil

View File

@ -5,12 +5,21 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
// Path represents a path to a configuration file.
type Path struct {
// Path is the filesystem path of the config.
Path string
// WriteAllowed is true if the path is allowed to be written.
WriteAllowed bool
}
// GetTalosDirectory returns path to Talos directory (~/.talos).
func GetTalosDirectory() (string, error) {
home, err := os.UserHomeDir()
@ -18,30 +27,70 @@ func GetTalosDirectory() (string, error) {
return "", err
}
return filepath.Join(home, ".talos"), nil
return filepath.Join(home, constants.TalosDir), nil
}
// GetDefaultPath returns default path to Talos config.
func GetDefaultPath() (string, error) {
if path, ok := os.LookupEnv(constants.TalosConfigEnvVar); ok {
return path, nil
}
talosSAPath := filepath.Join(constants.ServiceAccountMountPath, constants.ServiceAccountTalosconfigFilename)
_, err := os.Stat(talosSAPath)
if err != nil && !os.IsNotExist(err) && !os.IsPermission(err) {
return "", err
}
if err == nil {
return talosSAPath, nil
}
// GetDefaultPaths returns the list of config file paths in order of priority.
func GetDefaultPaths() ([]Path, error) {
talosDir, err := GetTalosDirectory()
if err != nil {
return "", err
return nil, err
}
return filepath.Join(talosDir, "config"), nil
result := make([]Path, 0, 3)
if path, ok := os.LookupEnv(constants.TalosConfigEnvVar); ok {
result = append(result, Path{
Path: path,
WriteAllowed: true,
})
}
result = append(
result,
Path{
Path: filepath.Join(talosDir, constants.TalosconfigFilename),
WriteAllowed: true,
},
Path{
Path: filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
WriteAllowed: false,
},
)
return result, nil
}
// firstValidPath iterates over the default paths and returns the first one that exists and readable.
// If no path is found, it will ensure that the first path that allows writes is created and returned.
// If no path is found that is writable, an error is returned.
func firstValidPath() (Path, error) {
paths, err := GetDefaultPaths()
if err != nil {
return Path{}, err
}
var firstWriteAllowed Path
for _, path := range paths {
_, err = os.Stat(path.Path)
if err == nil {
return path, nil
}
if firstWriteAllowed.Path == "" && path.WriteAllowed {
firstWriteAllowed = path
}
}
if firstWriteAllowed.Path == "" {
return Path{}, fmt.Errorf("no valid config paths found")
}
err = ensure(firstWriteAllowed.Path)
if err != nil {
return Path{}, err
}
return firstWriteAllowed, nil
}

View File

@ -92,12 +92,7 @@ func WithEndpoints(endpoints ...string) OptionFunc {
// Additionally use WithContextName to select a context other than the default.
func WithDefaultConfig() OptionFunc {
return func(o *Options) (err error) {
defaultConfigPath, err := clientconfig.GetDefaultPath()
if err != nil {
return fmt.Errorf("no client configuration provided and no default path found: %w", err)
}
return WithConfigFromFile(defaultConfigPath)(o)
return WithConfigFromFile("")(o)
}
}

View File

@ -438,7 +438,6 @@ func (c *ClusterDiscoveryConfig) Validate(clusterCfg *ClusterConfig) error {
// ValidateNetworkDevices runs the specified validation checks specific to the
// network devices.
func ValidateNetworkDevices(d *Device, pairedInterfaces map[string]string, checks ...NetworkDeviceCheck) ([]string, error) {
// todo utku
var result *multierror.Error
if d == nil {

View File

@ -349,9 +349,12 @@ const (
// EtcdTalosEtcdUpgradeMutex is the etcd mutex prefix to be used to set an etcd upgrade lock.
EtcdTalosEtcdUpgradeMutex = EtcdRootTalosKey + ":etcdUpgradeMutex"
// EtcdTalosManifestApplyMutex is the etcd election .
// EtcdTalosManifestApplyMutex is the etcd mutex prefix used by manifest apply controller.
EtcdTalosManifestApplyMutex = EtcdRootTalosKey + ":manifestApplyMutex"
// EtcdTalosServiceAccountCRDControllerMutex is the etcd mutex prefix used by Talos ServiceAccount crd controller.
EtcdTalosServiceAccountCRDControllerMutex = EtcdRootTalosKey + ":serviceAccountCRDController"
// EtcdImage is the reposistory for the etcd image.
EtcdImage = "gcr.io/etcd-development/etcd"
@ -708,14 +711,35 @@ const (
// KubernetesTalosAPIServiceNamespace is the namespace of the Kubernetes service to access Talos API.
KubernetesTalosAPIServiceNamespace = "default"
// TalosDir is the default name of the Talos directory under user home.
TalosDir = ".talos"
// TalosconfigFilename is the file name of Talosconfig under TalosDir or under ServiceAccountMountPath inside a pod.
TalosconfigFilename = "config"
// KubernetesTalosProvider is the name of the Talos provider as a Kubernetes label.
KubernetesTalosProvider = "talos.dev"
// ServiceAccountTalosconfigFilename is the file name of Talosconfig when it is injected into a pod.
ServiceAccountTalosconfigFilename = "config"
// ServiceAccountResourceGroup is the group name of the Talos service account CRD.
ServiceAccountResourceGroup = "talos.dev"
// ServiceAccountResourceVersion is the version of the Talos service account CRD.
ServiceAccountResourceVersion = "v1alpha1"
// ServiceAccountResourceKind is the kind name of the Talos service account CRD.
ServiceAccountResourceKind = "ServiceAccount"
// ServiceAccountResourceSingular is the singular name of the Talos service account CRD.
ServiceAccountResourceSingular = "serviceaccount"
// ServiceAccountResourceShortName is the short name of the service account CRD.
ServiceAccountResourceShortName = "tsa"
// ServiceAccountResourcePlural is the plural name of the service account CRD.
ServiceAccountResourcePlural = ServiceAccountResourceSingular + "s"
// ServiceAccountMountPath is the path of the directory in which the Talos service account secrets are mounted.
ServiceAccountMountPath = "/var/run/secrets/talos.dev/"
ServiceAccountMountPath = "/var/run/secrets/talos.dev"
// DefaultTrustedCAFile is the default path to the trusted CA file.
DefaultTrustedCAFile = "/etc/ssl/certs/ca-certificates"

View File

@ -0,0 +1,157 @@
---
title: "Talos API access from Kubernetes"
description: "How to access Talos API from within Kubernetes."
aliases:
- ../guides/talos-api-access-from-k8s
---
In this guide, we will enable the Talos feature to access the Talos API from within Kubernetes.
## Enabling the Feature
Edit the machine configuration to enable the feature, specifying the Kubernetes namespaces from which Talos API
can be accessed and the allowed Talos API roles.
```bash
talosctl -n 172.20.0.2 edit machineconfig
```
Configure the `kubernetesTalosAPIAccess` like the following:
```yaml
spec:
machine:
features:
kubernetesTalosAPIAccess:
enabled: true
allowedRoles:
- os:reader
allowedKubernetesNamespaces:
- default
```
## Injecting Talos ServiceAccount into manifests
Create the following manifest file `deployment.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: talos-api-access
spec:
selector:
matchLabels:
app: talos-api-access
template:
metadata:
labels:
app: talos-api-access
spec:
containers:
- name: talos-api-access
image: alpine:3
command:
- sh
- -c
- |
wget -O /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/download/<talos version>/talosctl-linux-amd64
chmod +x /usr/local/bin/talosctl
while true; talosctl -n 172.20.0.2 version; do sleep 1; done
```
**Note:** make sure that you replace the IP `172.20.0.2` with a valid Talos node IP.
Use `talosctl inject serviceaccount` command to inject the Talos ServiceAccount into the manifest.
```bash
talosctl inject serviceaccount -f deployment.yaml > deployment-injected.yaml
```
Inspect the generated manifest:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
name: talos-api-access
spec:
selector:
matchLabels:
app: talos-api-access
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: talos-api-access
spec:
containers:
- command:
- sh
- -c
- |
wget -O /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/download/<talos version>/talosctl-linux-amd64
chmod +x /usr/local/bin/talosctl
while true; talosctl -n 172.20.0.2 version; do sleep 1; done
image: alpine:3
name: talos-api-access
resources: {}
volumeMounts:
- mountPath: /var/run/secrets/talos.dev
name: talos-secrets
tolerations:
- operator: Exists
volumes:
- name: talos-secrets
secret:
secretName: talos-api-access-talos-secrets
status: {}
---
apiVersion: talos.dev/v1alpha1
kind: ServiceAccount
metadata:
name: talos-api-access-talos-secrets
spec:
roles:
- os:reader
---
```
As you can notice, your deployment manifest is now injected with the Talos ServiceAccount.
## Testing API Access
Apply the new manifest into `default` namespace:
```bash
kubectl apply -n default -f deployment-injected.yaml
```
Follow the logs of the pods belong to the deployment:
```bash
kubectl logs -n default -f -l app=talos-api-access
```
You'll see a repeating output similar to the following:
```text
Client:
Tag: <talos version>
SHA: ....
Built:
Go version: go1.18.4
OS/Arch: linux/amd64
Server:
NODE: 172.20.0.2
Tag: <talos version>
SHA: ...
Built:
Go version: go1.18.4
OS/Arch: linux/amd64
Enabled: RBAC
```
This means that the pod can talk to Talos API of node 172.20.0.2 successfully.

View File

@ -31,7 +31,7 @@ talosctl apply-config [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -71,7 +71,7 @@ talosctl bootstrap [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -162,7 +162,7 @@ talosctl cluster create [flags]
-n, --nodes strings target the specified nodes
--provisioner string Talos cluster provisioner to use (default "docker")
--state string directory path to store cluster state (default "/home/user/.talos/clusters")
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -192,7 +192,7 @@ talosctl cluster destroy [flags]
-n, --nodes strings target the specified nodes
--provisioner string Talos cluster provisioner to use (default "docker")
--state string directory path to store cluster state (default "/home/user/.talos/clusters")
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -222,7 +222,7 @@ talosctl cluster show [flags]
-n, --nodes strings target the specified nodes
--provisioner string Talos cluster provisioner to use (default "docker")
--state string directory path to store cluster state (default "/home/user/.talos/clusters")
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -248,7 +248,7 @@ A collection of commands for managing local docker-based or QEMU-based clusters
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -321,7 +321,7 @@ talosctl completion SHELL [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -351,7 +351,7 @@ talosctl config add <context> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -378,7 +378,7 @@ talosctl config context <context> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -405,7 +405,7 @@ talosctl config contexts [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -432,7 +432,7 @@ talosctl config endpoint <endpoint>... [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -459,7 +459,7 @@ talosctl config info [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -490,7 +490,7 @@ talosctl config merge <from> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -519,7 +519,7 @@ talosctl config new [<path>] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -546,7 +546,7 @@ talosctl config node <endpoint>... [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -569,7 +569,7 @@ Manage the client configuration file (talosconfig)
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -605,7 +605,7 @@ talosctl conformance kubernetes [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -628,7 +628,7 @@ Run conformance tests
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -657,7 +657,7 @@ talosctl containers [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -695,7 +695,7 @@ talosctl copy <src-path> -|<local-path> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -739,7 +739,7 @@ talosctl dashboard [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -767,7 +767,7 @@ talosctl disks [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -796,7 +796,7 @@ talosctl dmesg [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -836,7 +836,7 @@ talosctl edit <type> [<id>] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -863,7 +863,7 @@ talosctl etcd forfeit-leadership [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -890,7 +890,7 @@ talosctl etcd leave [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -917,7 +917,7 @@ talosctl etcd members [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -950,7 +950,7 @@ talosctl etcd remove-member <hostname> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -977,7 +977,7 @@ talosctl etcd snapshot <path> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1000,7 +1000,7 @@ Manage etcd
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1035,7 +1035,7 @@ talosctl events [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1065,7 +1065,7 @@ talosctl gen ca [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1117,7 +1117,7 @@ talosctl gen config <cluster name> <cluster endpoint> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1148,7 +1148,7 @@ talosctl gen crt [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1178,7 +1178,7 @@ talosctl gen csr [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1206,7 +1206,7 @@ talosctl gen key [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1235,7 +1235,7 @@ talosctl gen keypair [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1266,7 +1266,7 @@ talosctl gen secrets [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1289,7 +1289,7 @@ Generate CAs, certificates, and private keys
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1332,7 +1332,7 @@ talosctl get <type> [<id>] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1366,7 +1366,7 @@ talosctl health [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1393,13 +1393,76 @@ talosctl images [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos
## talosctl inject serviceaccount
Inject Talos API ServiceAccount into Kubernetes manifests
```
talosctl inject serviceaccount [--roles='<ROLE_1>,<ROLE_2>'] -f <manifest.yaml> [flags]
```
### Examples
```
talosctl inject serviceaccount --roles="os:admin" -f deployment.yaml > deployment-injected.yaml
Alternatively, stdin can be piped to the command:
cat deployment.yaml | talosctl inject serviceaccount --roles="os:admin" -f - > deployment-injected.yaml
```
### Options
```
-f, --file string file with Kubernetes manifests to be injected with ServiceAccount
-h, --help help for serviceaccount
-r, --roles strings roles to add to the generated ServiceAccount manifests (default [os:reader])
```
### Options inherited from parent commands
```
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
* [talosctl inject](#talosctl-inject) - Inject Talos API resources into Kubernetes manifests
## talosctl inject
Inject Talos API resources into Kubernetes manifests
### Options
```
-h, --help help for inject
```
### Options inherited from parent commands
```
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos
* [talosctl inject serviceaccount](#talosctl-inject-serviceaccount) - Inject Talos API ServiceAccount into Kubernetes manifests
## talosctl inspect dependencies
Inspect controller-resource dependencies as graphviz graph.
@ -1431,7 +1494,7 @@ talosctl inspect dependencies [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1454,7 +1517,7 @@ Inspect internals of Talos
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1491,7 +1554,7 @@ talosctl kubeconfig [local-path] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1526,7 +1589,7 @@ talosctl list [path] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1556,7 +1619,7 @@ talosctl logs <service name> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1584,7 +1647,7 @@ talosctl memory [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1611,7 +1674,7 @@ talosctl mounts [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1644,7 +1707,7 @@ talosctl patch <type> [<id>] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1705,7 +1768,7 @@ talosctl pcap [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1734,7 +1797,7 @@ talosctl processes [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1761,7 +1824,7 @@ talosctl read <path> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1789,7 +1852,7 @@ talosctl reboot [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1819,7 +1882,7 @@ talosctl reset [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1847,7 +1910,7 @@ talosctl restart <id> [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1874,7 +1937,7 @@ talosctl rollback [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1907,7 +1970,7 @@ talosctl service [<id> [start|stop|restart|status]] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1935,7 +1998,7 @@ talosctl shutdown [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -1963,7 +2026,7 @@ talosctl stats [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2015,7 +2078,7 @@ talosctl support [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2043,7 +2106,7 @@ talosctl time [--check server] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2074,7 +2137,7 @@ talosctl upgrade [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2110,7 +2173,7 @@ talosctl upgrade-k8s [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2141,7 +2204,7 @@ talosctl usage [path1] [path2] ... [pathN] [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2171,7 +2234,7 @@ talosctl validate [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2201,7 +2264,7 @@ talosctl version [flags]
--context string Context to be used in command
-e, --endpoints strings override default endpoints in Talos configuration
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2219,7 +2282,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos
-e, --endpoints strings override default endpoints in Talos configuration
-h, --help help for talosctl
-n, --nodes strings target the specified nodes
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
```
### SEE ALSO
@ -2242,6 +2305,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos
* [talosctl get](#talosctl-get) - Get a specific resource or list of resources.
* [talosctl health](#talosctl-health) - Check cluster health
* [talosctl images](#talosctl-images) - List the default images used by Talos
* [talosctl inject](#talosctl-inject) - Inject Talos API resources into Kubernetes manifests
* [talosctl inspect](#talosctl-inspect) - Inspect internals of Talos
* [talosctl kubeconfig](#talosctl-kubeconfig) - Download the admin kubeconfig from the node
* [talosctl list](#talosctl-list) - Retrieve a directory listing