mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-22 23:21:11 +02:00
Provides capability to add extra headers in cases where files can only be fetched with token based authenction. Signed-off-by: Niklas Wik <niklas.wik@nokia.com> feat: extra manifest headers for fetching manifests - Changed config to map of key value pairs. Signed-off-by: Niklas Wik <niklas.wik@nokia.com> fix: added docs for new extra headers fetch Signed-off-by: Niklas Wik <niklas.wik@nokia.com> fix: fix linter issue Signed-off-by: Niklas Wik <niklas.wik@nokia.com>
318 lines
8.1 KiB
Go
318 lines
8.1 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/go-getter"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/kubernetes-sigs/bootkube/pkg/asset"
|
|
"github.com/kubernetes-sigs/bootkube/pkg/tlsutil"
|
|
|
|
"github.com/talos-systems/talos/internal/pkg/runtime"
|
|
"github.com/talos-systems/talos/pkg/constants"
|
|
tnet "github.com/talos-systems/talos/pkg/net"
|
|
)
|
|
|
|
// DefaultPodSecurityPolicy is the default PSP.
|
|
var DefaultPodSecurityPolicy = []byte(`---
|
|
kind: ClusterRole
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
metadata:
|
|
name: psp:privileged
|
|
rules:
|
|
- apiGroups: ['policy']
|
|
resources: ['podsecuritypolicies']
|
|
verbs: ['use']
|
|
resourceNames:
|
|
- privileged
|
|
---
|
|
kind: ClusterRoleBinding
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
metadata:
|
|
name: psp:privileged
|
|
roleRef:
|
|
kind: ClusterRole
|
|
name: psp:privileged
|
|
apiGroup: rbac.authorization.k8s.io
|
|
subjects:
|
|
# Authorize all service accounts in a namespace:
|
|
- kind: Group
|
|
apiGroup: rbac.authorization.k8s.io
|
|
name: system:serviceaccounts
|
|
# Authorize all authenticated users in a namespace:
|
|
- kind: Group
|
|
apiGroup: rbac.authorization.k8s.io
|
|
name: system:authenticated
|
|
---
|
|
apiVersion: policy/v1beta1
|
|
kind: PodSecurityPolicy
|
|
metadata:
|
|
name: privileged
|
|
spec:
|
|
fsGroup:
|
|
rule: RunAsAny
|
|
privileged: true
|
|
runAsUser:
|
|
rule: RunAsAny
|
|
seLinux:
|
|
rule: RunAsAny
|
|
supplementalGroups:
|
|
rule: RunAsAny
|
|
volumes:
|
|
- '*'
|
|
allowedCapabilities:
|
|
- '*'
|
|
hostPID: true
|
|
hostIPC: true
|
|
hostNetwork: true
|
|
hostPorts:
|
|
- min: 1
|
|
max: 65536
|
|
`)
|
|
|
|
// nolint: gocyclo
|
|
func generateAssets(config runtime.Configurator) (err error) {
|
|
if err = os.MkdirAll(constants.ManifestsDirectory, 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure assets directory does not exist / is left over from a failed install
|
|
if err = os.RemoveAll(constants.AssetsDirectory); err != nil {
|
|
// Ignore if the directory does not exist
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
peerCrt, err := ioutil.ReadFile(constants.KubernetesEtcdPeerCert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
block, _ := pem.Decode(peerCrt)
|
|
if block == nil {
|
|
return errors.New("failed to decode peer certificate")
|
|
}
|
|
|
|
peer, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse client certificate: %w", err)
|
|
}
|
|
|
|
caCrt, err := ioutil.ReadFile(constants.KubernetesEtcdCACert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
block, _ = pem.Decode(caCrt)
|
|
if block == nil {
|
|
return errors.New("failed to decode CA certificate")
|
|
}
|
|
|
|
ca, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse etcd CA certificate: %w", err)
|
|
}
|
|
|
|
peerKey, err := ioutil.ReadFile(constants.KubernetesEtcdPeerKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
block, _ = pem.Decode(peerKey)
|
|
if block == nil {
|
|
return errors.New("failed to peer key")
|
|
}
|
|
|
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse client key: %w", err)
|
|
}
|
|
|
|
etcdServer, err := url.Parse("https://127.0.0.1:2379")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, podCIDR, err := net.ParseCIDR(config.Cluster().Network().PodCIDR())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, serviceCIDR, err := net.ParseCIDR(config.Cluster().Network().ServiceCIDR())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
urls := []string{config.Cluster().Endpoint().Hostname()}
|
|
urls = append(urls, config.Cluster().CertSANs()...)
|
|
altNames := altNamesFromURLs(urls)
|
|
|
|
k8sCA, err := config.Cluster().CA().GetCert()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get Kubernetes CA certificate: %w", err)
|
|
}
|
|
|
|
k8sKey, err := config.Cluster().CA().GetRSAKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse Kubernetes key: %w", err)
|
|
}
|
|
|
|
apiServiceIP, err := tnet.NthIPInNetwork(serviceCIDR, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dnsServiceIP, err := tnet.NthIPInNetwork(serviceCIDR, 10)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
images := asset.DefaultImages
|
|
|
|
images.Hyperkube = config.Machine().Kubelet().Image()
|
|
|
|
// Allow for overriding by users via config data
|
|
images.CoreDNS = config.Cluster().CoreDNS().Image()
|
|
images.PodCheckpointer = config.Cluster().PodCheckpointer().Image()
|
|
|
|
conf := asset.Config{
|
|
ClusterName: config.Cluster().Name(),
|
|
APIServerExtraArgs: config.Cluster().APIServer().ExtraArgs(),
|
|
ControllerManagerExtraArgs: config.Cluster().ControllerManager().ExtraArgs(),
|
|
SchedulerExtraArgs: config.Cluster().Scheduler().ExtraArgs(),
|
|
CACert: k8sCA,
|
|
CAPrivKey: k8sKey,
|
|
EtcdCACert: ca,
|
|
EtcdClientCert: peer,
|
|
EtcdClientKey: key,
|
|
EtcdServers: []*url.URL{etcdServer},
|
|
EtcdUseTLS: true,
|
|
ControlPlaneEndpoint: config.Cluster().Endpoint(),
|
|
LocalAPIServerPort: config.Cluster().LocalAPIServerPort(),
|
|
APIServiceIP: apiServiceIP,
|
|
DNSServiceIP: dnsServiceIP,
|
|
PodCIDR: podCIDR,
|
|
ServiceCIDR: serviceCIDR,
|
|
NetworkProvider: config.Cluster().Network().CNI().Name(),
|
|
AltNames: altNames,
|
|
Images: images,
|
|
BootstrapSecretsSubdir: "/assets/tls",
|
|
BootstrapTokenID: config.Cluster().Token().ID(),
|
|
BootstrapTokenSecret: config.Cluster().Token().Secret(),
|
|
AESCBCEncryptionSecret: config.Cluster().AESCBCEncryptionSecret(),
|
|
ClusterDomain: config.Cluster().Network().DNSDomain(),
|
|
}
|
|
|
|
as, err := asset.NewDefaultAssets(conf)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create list of assets: %w", err)
|
|
}
|
|
|
|
if err = as.WriteFiles(constants.AssetsDirectory); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = ioutil.WriteFile(filepath.Join(constants.AssetsDirectory, "manifests", "psp.yaml"), DefaultPodSecurityPolicy, 0600); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If "custom" is the CNI, we expect the user to supply one or more urls that point to CNI yamls
|
|
if config.Cluster().Network().CNI().Name() == constants.CustomCNI {
|
|
if err = fetchManifests(config.Cluster().Network().CNI().URLs(), map[string]string{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(config.Cluster().ExtraManifestURLs()) > 0 {
|
|
if err = fetchManifests(config.Cluster().ExtraManifestURLs(), config.Cluster().ExtraManifestHeaderMap()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func altNamesFromURLs(urls []string) *tlsutil.AltNames {
|
|
var an tlsutil.AltNames
|
|
|
|
for _, u := range urls {
|
|
ip := net.ParseIP(u)
|
|
if ip != nil {
|
|
an.IPs = append(an.IPs, ip)
|
|
continue
|
|
}
|
|
|
|
an.DNSNames = append(an.DNSNames, u)
|
|
}
|
|
|
|
return &an
|
|
}
|
|
|
|
// fetchManifests will lay down manifests in the provided urls to the bootkube assets directory
|
|
func fetchManifests(urls []string, headers map[string]string) error {
|
|
ctx := context.Background()
|
|
|
|
var result *multierror.Error
|
|
|
|
for _, url := range urls {
|
|
fileName := path.Base(url)
|
|
|
|
pwd, err := os.Getwd()
|
|
if err != nil {
|
|
result = multierror.Append(result, err)
|
|
continue
|
|
}
|
|
|
|
// Disable netrc since we don't have getent installed, and most likely
|
|
// never will.
|
|
httpGetter := &getter.HttpGetter{
|
|
Netrc: false,
|
|
Client: http.DefaultClient,
|
|
}
|
|
|
|
httpGetter.Header = make(http.Header)
|
|
|
|
for k, v := range headers {
|
|
httpGetter.Header.Add(k, v)
|
|
}
|
|
|
|
getter.Getters["http"] = httpGetter
|
|
getter.Getters["https"] = httpGetter
|
|
|
|
// We will squirrel all user-supplied manifests into a `zzz-talos` directory.
|
|
// Bootkube applies manifests alphabetically, so pushing these into a subdir with this name
|
|
// allows us to ensure they're the last things that get applied and things like PSPs and whatnot are present
|
|
client := &getter.Client{
|
|
Ctx: ctx,
|
|
Src: url,
|
|
Dst: filepath.Join(constants.AssetsDirectory, "manifests", "zzz-talos", fileName),
|
|
Pwd: pwd,
|
|
Mode: getter.ClientModeFile,
|
|
Options: []getter.ClientOption{},
|
|
}
|
|
|
|
if err = client.Get(); err != nil {
|
|
result = multierror.Append(result, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
return result.ErrorOrNil()
|
|
}
|