mirror of https://github.com/kubernetes/kops.git
Boot nodes without state store access
kops-controller can now serve the instance group & cluster config to nodes, as part of the bootstrap process. This enables nodes to boot without access to the state store (i.e. without S3 / GCS / etc permissions) Feature-flagged behind the KopsControllerStateStore feature-flag.
This commit is contained in:
parent
62b7ae0c49
commit
4ac9d5c17b
|
@ -4,16 +4,19 @@ go_library(
|
|||
name = "go_default_library",
|
||||
srcs = [
|
||||
"keystore.go",
|
||||
"node_config.go",
|
||||
"server.go",
|
||||
],
|
||||
importpath = "k8s.io/kops/cmd/kops-controller/pkg/server",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/kops-controller/pkg/config:go_default_library",
|
||||
"//pkg/apis/kops/registry:go_default_library",
|
||||
"//pkg/apis/nodeup:go_default_library",
|
||||
"//pkg/pki:go_default_library",
|
||||
"//pkg/rbac:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//util/pkg/vfs:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/klog/v2:go_default_library",
|
||||
],
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kops/pkg/apis/kops/registry"
|
||||
"k8s.io/kops/pkg/apis/nodeup"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
func (s *Server) getNodeConfig(ctx context.Context, req *nodeup.BootstrapRequest, identity *fi.VerifyResult) (*nodeup.NodeConfig, error) {
|
||||
klog.Infof("getting node config for %+v", req)
|
||||
|
||||
instanceGroupName := identity.InstanceGroupName
|
||||
if instanceGroupName == "" {
|
||||
return nil, fmt.Errorf("did not find InstanceGroup for node %q", identity.NodeName)
|
||||
}
|
||||
|
||||
nodeConfig := &nodeup.NodeConfig{}
|
||||
|
||||
// Note: For now, we're assuming there is only a single cluster, and it is ours.
|
||||
// We therefore use the configured base path
|
||||
|
||||
// Today we load the full cluster config from the state store (e.g. S3) every time
|
||||
// TODO: we should generate it on the fly (to allow for cluster reconfiguration)
|
||||
{
|
||||
p := s.configBase.Join(registry.PathClusterCompleted)
|
||||
|
||||
b, err := p.ReadFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading cluster config %q: %w", p, err)
|
||||
}
|
||||
nodeConfig.ClusterFullConfig = string(b)
|
||||
}
|
||||
|
||||
{
|
||||
p := s.configBase.Join("instancegroup", instanceGroupName)
|
||||
|
||||
b, err := p.ReadFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading InstanceGroup %q: %v", p, err)
|
||||
}
|
||||
nodeConfig.InstanceGroupConfig = string(b)
|
||||
}
|
||||
|
||||
// We populate some certificates that we know the node will need.
|
||||
for _, name := range []string{"ca"} {
|
||||
cert, _, _, err := s.keystore.FindKeypair(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting certificate %q: %w", name, err)
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
return nil, fmt.Errorf("certificate %q not found", name)
|
||||
}
|
||||
|
||||
certData, err := cert.AsString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshalling certificate %q: %w", name, err)
|
||||
}
|
||||
|
||||
nodeConfig.Certificates = append(nodeConfig.Certificates, &nodeup.NodeConfigCertificate{
|
||||
Name: name,
|
||||
Cert: certData,
|
||||
})
|
||||
}
|
||||
|
||||
return nodeConfig, nil
|
||||
}
|
|
@ -36,6 +36,7 @@ import (
|
|||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/pkg/rbac"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/util/pkg/vfs"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -44,6 +45,9 @@ type Server struct {
|
|||
server *http.Server
|
||||
verifier fi.Verifier
|
||||
keystore pki.Keystore
|
||||
|
||||
// configBase is the base of the configuration storage.
|
||||
configBase vfs.Path
|
||||
}
|
||||
|
||||
func NewServer(opt *config.Options, verifier fi.Verifier) (*Server, error) {
|
||||
|
@ -61,6 +65,13 @@ func NewServer(opt *config.Options, verifier fi.Verifier) (*Server, error) {
|
|||
server: server,
|
||||
verifier: verifier,
|
||||
}
|
||||
|
||||
configBase, err := vfs.Context.BuildVfsPath(opt.ConfigBase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse ConfigBase %q: %v", opt.ConfigBase, err)
|
||||
}
|
||||
s.configBase = configBase
|
||||
|
||||
r := http.NewServeMux()
|
||||
r.Handle("/bootstrap", http.HandlerFunc(s.bootstrap))
|
||||
server.Handler = recovery(r)
|
||||
|
@ -121,6 +132,18 @@ func (s *Server) bootstrap(w http.ResponseWriter, r *http.Request) {
|
|||
Certs: map[string]string{},
|
||||
}
|
||||
|
||||
// Support for nodes that have no access to the state store
|
||||
if req.IncludeNodeConfig {
|
||||
nodeConfig, err := s.getNodeConfig(r.Context(), req, id)
|
||||
if err != nil {
|
||||
klog.Infof("bootstrap failed to build node config: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("failed to build node config"))
|
||||
return
|
||||
}
|
||||
resp.NodeConfig = nodeConfig
|
||||
}
|
||||
|
||||
// Skew the certificate lifetime by up to 30 days based on information about the requesting node.
|
||||
// This is so that different nodes created at the same time have the certificates they generated
|
||||
// expire at different times, but all certificates on a given node expire around the same time.
|
||||
|
|
|
@ -18,8 +18,12 @@ package model
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/wellknownports"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
||||
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
|
||||
|
@ -56,18 +60,29 @@ func (b BootstrapClientBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bootstrapClient := &nodetasks.BootstrapClient{
|
||||
baseURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort("kops-controller.internal."+b.Cluster.ObjectMeta.Name, strconv.Itoa(wellknownports.KopsControllerPort)),
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
bootstrapClient := &nodetasks.KopsBootstrapClient{
|
||||
Authenticator: authenticator,
|
||||
CA: cert,
|
||||
Certs: b.bootstrapCerts,
|
||||
BaseURL: baseURL,
|
||||
}
|
||||
|
||||
bootstrapClientTask := &nodetasks.BootstrapClientTask{
|
||||
Client: bootstrapClient,
|
||||
Certs: b.bootstrapCerts,
|
||||
}
|
||||
|
||||
for _, cert := range b.bootstrapCerts {
|
||||
cert.Cert.Task = bootstrapClient
|
||||
cert.Key.Task = bootstrapClient
|
||||
cert.Cert.Task = bootstrapClientTask
|
||||
cert.Key.Task = bootstrapClientTask
|
||||
}
|
||||
|
||||
c.AddTask(bootstrapClient)
|
||||
c.AddTask(bootstrapClientTask)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,38 @@ type BootstrapRequest struct {
|
|||
APIVersion string `json:"apiVersion"`
|
||||
// Certs are the requested certificates and their respective public keys.
|
||||
Certs map[string]string `json:"certs"`
|
||||
|
||||
// IncludeNodeConfig controls whether the cluster & instance group configuration should be returned.
|
||||
// This allows for nodes without access to the kops state store.
|
||||
IncludeNodeConfig bool `json:"includeNodeConfig"`
|
||||
}
|
||||
|
||||
// BootstrapResponse is a response to a BootstrapRequest.
|
||||
type BootstrapResponse struct {
|
||||
// Certs are the issued certificates.
|
||||
Certs map[string]string
|
||||
|
||||
// NodeConfig contains the node configuration, if IncludeNodeConfig is set.
|
||||
NodeConfig *NodeConfig `json:"nodeConfig,omitempty"`
|
||||
}
|
||||
|
||||
// NodeConfig holds configuration needed to boot a node (without the kops state store)
|
||||
type NodeConfig struct {
|
||||
// InstanceGroupConfig holds the configuration for the node's instance group
|
||||
InstanceGroupConfig string `json:"instanceGroupConfig,omitempty"`
|
||||
|
||||
// ClusterFullConfig holds the configuration for the cluster
|
||||
ClusterFullConfig string `json:"clusterFullConfig,omitempty"`
|
||||
|
||||
// Certificates holds certificates that are already issued
|
||||
Certificates []*NodeConfigCertificate `json:"certificates,omitempty"`
|
||||
}
|
||||
|
||||
// NodeConfigCertificate holds a certificate that the node needs to boot.
|
||||
type NodeConfigCertificate struct {
|
||||
// Name identifies the certificate.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Cert is the certificate data.
|
||||
Cert string `json:"cert,omitempty"`
|
||||
}
|
||||
|
|
|
@ -66,6 +66,19 @@ type Config struct {
|
|||
SysctlParameters []string `json:",omitempty"`
|
||||
// VolumeMounts are a collection of volume mounts.
|
||||
VolumeMounts []kops.VolumeMountSpec `json:",omitempty"`
|
||||
|
||||
// ConfigServer holds the configuration for the configuration server
|
||||
ConfigServer *ConfigServerOptions `json:"configServer,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigServerOptions struct {
|
||||
// Server is the address of the configuration server to use (kops-controller)
|
||||
Server string `json:"server,omitempty"`
|
||||
// CA is the ca-certificate to require for the configuration server
|
||||
CA string `json:"ca,omitempty"`
|
||||
|
||||
// CloudProvider is the cloud provider in use (needed for authentication)
|
||||
CloudProvider string `json:"cloudProvider,omitempty"`
|
||||
}
|
||||
|
||||
// Image is a docker image we should pre-load
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"keystore.go",
|
||||
"secretstore.go",
|
||||
],
|
||||
importpath = "k8s.io/kops/pkg/configserver",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/kops:go_default_library",
|
||||
"//pkg/apis/nodeup:go_default_library",
|
||||
"//pkg/pki:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//util/pkg/vfs:go_default_library",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configserver
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/apis/nodeup"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/util/pkg/vfs"
|
||||
)
|
||||
|
||||
//configserverKeyStore is a KeyStore backed by the config server.
|
||||
type configserverKeyStore struct {
|
||||
nodeConfig *nodeup.NodeConfig
|
||||
}
|
||||
|
||||
func NewKeyStore(nodeConfig *nodeup.NodeConfig) fi.CAStore {
|
||||
return &configserverKeyStore{
|
||||
nodeConfig: nodeConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// FindKeypair implements fi.Keystore
|
||||
func (s *configserverKeyStore) FindKeypair(name string) (*pki.Certificate, *pki.PrivateKey, bool, error) {
|
||||
return nil, nil, false, fmt.Errorf("FindKeypair %q not supported by configserverKeyStore", name)
|
||||
}
|
||||
|
||||
// FindKeypair implements fi.Keystore
|
||||
func (s *configserverKeyStore) CreateKeypair(signer string, name string, template *x509.Certificate, privateKey *pki.PrivateKey) (*pki.Certificate, error) {
|
||||
return nil, fmt.Errorf("CreateKeypair not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindKeypair implements fi.Keystore
|
||||
func (s *configserverKeyStore) StoreKeypair(id string, cert *pki.Certificate, privateKey *pki.PrivateKey) error {
|
||||
return fmt.Errorf("StoreKeypair not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindKeypair implements fi.Keystore
|
||||
func (s *configserverKeyStore) MirrorTo(basedir vfs.Path) error {
|
||||
return fmt.Errorf("MirrorTo not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// CertificatePool implements fi.CAStore
|
||||
func (s *configserverKeyStore) CertificatePool(name string, createIfMissing bool) (*fi.CertificatePool, error) {
|
||||
return nil, fmt.Errorf("CertificatePool not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindCertificatePool implements fi.CAStore
|
||||
func (s *configserverKeyStore) FindCertificatePool(name string) (*fi.CertificatePool, error) {
|
||||
return nil, fmt.Errorf("FindCertificatePool not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindCertificateKeyset implements fi.CAStore
|
||||
func (s *configserverKeyStore) FindCertificateKeyset(name string) (*kops.Keyset, error) {
|
||||
return nil, fmt.Errorf("FindCertificateKeyset not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindPrivateKey implements fi.CAStore
|
||||
func (s *configserverKeyStore) FindPrivateKey(name string) (*pki.PrivateKey, error) {
|
||||
return nil, fmt.Errorf("FindPrivateKey not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindPrivateKeyset implements fi.CAStore
|
||||
func (s *configserverKeyStore) FindPrivateKeyset(name string) (*kops.Keyset, error) {
|
||||
return nil, fmt.Errorf("FindPrivateKeyset not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// FindCert implements fi.CAStore
|
||||
func (s *configserverKeyStore) FindCert(name string) (*pki.Certificate, error) {
|
||||
for _, cert := range s.nodeConfig.Certificates {
|
||||
if cert.Name == name {
|
||||
// Special case for the CA certificate
|
||||
c, err := pki.ParsePEMCertificate([]byte(cert.Cert))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing certificate %q: %w", name, err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("FindCert(%q) not supported by configserverKeyStore", name)
|
||||
}
|
||||
|
||||
// ListKeysets implements fi.CAStore
|
||||
func (s *configserverKeyStore) ListKeysets() ([]*kops.Keyset, error) {
|
||||
return nil, fmt.Errorf("ListKeysets not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// AddCert implements fi.CAStore
|
||||
func (s *configserverKeyStore) AddCert(name string, cert *pki.Certificate) error {
|
||||
return fmt.Errorf("AddCert not supported by configserverKeyStore")
|
||||
}
|
||||
|
||||
// DeleteKeysetItem implements fi.CAStore
|
||||
func (s *configserverKeyStore) DeleteKeysetItem(item *kops.Keyset, id string) error {
|
||||
return fmt.Errorf("DeleteKeysetItem not supported by configserverKeyStore")
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kops/pkg/apis/nodeup"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/util/pkg/vfs"
|
||||
)
|
||||
|
||||
//configserverSecretStore is a SecretStore backed by the config server.
|
||||
type configserverSecretStore struct {
|
||||
nodeConfig *nodeup.NodeConfig
|
||||
}
|
||||
|
||||
func NewSecretStore(nodeConfig *nodeup.NodeConfig) fi.SecretStore {
|
||||
return &configserverSecretStore{
|
||||
nodeConfig: nodeConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// Secret implements fi.SecretStore
|
||||
func (s *configserverSecretStore) Secret(id string) (*fi.Secret, error) {
|
||||
return nil, fmt.Errorf("Secret not supported by configserverSecretStore")
|
||||
}
|
||||
|
||||
// DeleteSecret implements fi.SecretStore
|
||||
func (s *configserverSecretStore) DeleteSecret(id string) error {
|
||||
return fmt.Errorf("DeleteSecret not supported by configserverSecretStore")
|
||||
}
|
||||
|
||||
// FindSecret implements fi.SecretStore
|
||||
func (s *configserverSecretStore) FindSecret(id string) (*fi.Secret, error) {
|
||||
return nil, fmt.Errorf("FindSecret not supported by configserverSecretStore")
|
||||
}
|
||||
|
||||
// GetOrCreateSecret implements fi.SecretStore
|
||||
func (s *configserverSecretStore) GetOrCreateSecret(id string, secret *fi.Secret) (current *fi.Secret, created bool, err error) {
|
||||
return nil, false, fmt.Errorf("GetOrCreateSecret not supported by configserverSecretStore")
|
||||
}
|
||||
|
||||
// ReplaceSecret implements fi.SecretStore
|
||||
func (s *configserverSecretStore) ReplaceSecret(id string, secret *fi.Secret) (current *fi.Secret, err error) {
|
||||
return nil, fmt.Errorf("ReplaceSecret not supported by configserverSecretStore")
|
||||
}
|
||||
|
||||
// ListSecrets implements fi.SecretStore
|
||||
func (s *configserverSecretStore) ListSecrets() ([]string, error) {
|
||||
return nil, fmt.Errorf("ListSecrets not supported by configserverSecretStore")
|
||||
}
|
||||
|
||||
// MirrorTo implements fi.SecretStore
|
||||
func (s *configserverSecretStore) MirrorTo(basedir vfs.Path) error {
|
||||
return fmt.Errorf("MirrorTo not supported by configserverSecretStore")
|
||||
}
|
|
@ -101,6 +101,8 @@ var (
|
|||
PublicJWKS = New("PublicJWKS", Bool(false))
|
||||
// Azure toggles the Azure support.
|
||||
Azure = New("Azure", Bool(false))
|
||||
// KopsControllerStateStore enables fetching the kops state from kops-controller, instead of requiring access to S3/GCS/etc.
|
||||
KopsControllerStateStore = New("KopsControllerStateStore", Bool(false))
|
||||
)
|
||||
|
||||
// FeatureFlag defines a feature flag
|
||||
|
|
|
@ -80,6 +80,7 @@ go_test(
|
|||
"//pkg/apis/nodeup:go_default_library",
|
||||
"//pkg/testutils/golden:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//upup/pkg/fi/fitasks:go_default_library",
|
||||
"//util/pkg/architectures:go_default_library",
|
||||
"//util/pkg/hashing:go_default_library",
|
||||
"//util/pkg/mirrors:go_default_library",
|
||||
|
|
|
@ -37,12 +37,13 @@ import (
|
|||
"k8s.io/kops/pkg/model/resources"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
||||
"k8s.io/kops/upup/pkg/fi/fitasks"
|
||||
"k8s.io/kops/util/pkg/architectures"
|
||||
"k8s.io/kops/util/pkg/mirrors"
|
||||
)
|
||||
|
||||
type NodeUpConfigBuilder interface {
|
||||
BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string) (*nodeup.Config, error)
|
||||
BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string, ca fi.Resource) (*nodeup.Config, error)
|
||||
}
|
||||
|
||||
// BootstrapScriptBuilder creates the bootstrap script
|
||||
|
@ -58,6 +59,12 @@ type BootstrapScript struct {
|
|||
resource fi.TaskDependentResource
|
||||
// alternateNameTasks are tasks that contribute api-server IP addresses.
|
||||
alternateNameTasks []fi.HasAddress
|
||||
|
||||
// ca holds the CA certificate
|
||||
ca fi.Resource
|
||||
|
||||
// caTask holds the CA task, for dependency analysis.
|
||||
caTask fi.Task
|
||||
}
|
||||
|
||||
var _ fi.Task = &BootstrapScript{}
|
||||
|
@ -65,7 +72,7 @@ var _ fi.HasName = &BootstrapScript{}
|
|||
var _ fi.HasDependencies = &BootstrapScript{}
|
||||
|
||||
// kubeEnv returns the nodeup config for the instance group
|
||||
func (b *BootstrapScript) kubeEnv(ig *kops.InstanceGroup, c *fi.Context) (string, error) {
|
||||
func (b *BootstrapScript) kubeEnv(ig *kops.InstanceGroup, c *fi.Context, ca fi.Resource) (string, error) {
|
||||
var alternateNames []string
|
||||
|
||||
for _, hasAddress := range b.alternateNameTasks {
|
||||
|
@ -82,7 +89,7 @@ func (b *BootstrapScript) kubeEnv(ig *kops.InstanceGroup, c *fi.Context) (string
|
|||
}
|
||||
|
||||
sort.Strings(alternateNames)
|
||||
config, err := b.builder.NodeUpConfigBuilder.BuildConfig(ig, alternateNames)
|
||||
config, err := b.builder.NodeUpConfigBuilder.BuildConfig(ig, alternateNames, ca)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -192,6 +199,12 @@ func (b *BootstrapScript) buildEnvironmentVariables(cluster *kops.Cluster) (map[
|
|||
// ResourceNodeUp generates and returns a nodeup (bootstrap) script from a
|
||||
// template file, substituting in specific env vars & cluster spec configuration
|
||||
func (b *BootstrapScriptBuilder) ResourceNodeUp(c *fi.ModelBuilderContext, ig *kops.InstanceGroup) (fi.Resource, error) {
|
||||
caTaskObject, found := c.Tasks["Keypair/ca"]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("keypair/ca task not found")
|
||||
}
|
||||
caTask := caTaskObject.(*fitasks.Keypair)
|
||||
|
||||
// Bastions can have AdditionalUserData, but if there isn't any skip this part
|
||||
if ig.IsBastion() && len(ig.Spec.AdditionalUserData) == 0 {
|
||||
templateResource, err := NewTemplateResource("nodeup", "", nil, nil)
|
||||
|
@ -205,6 +218,8 @@ func (b *BootstrapScriptBuilder) ResourceNodeUp(c *fi.ModelBuilderContext, ig *k
|
|||
Name: ig.Name,
|
||||
ig: ig,
|
||||
builder: b,
|
||||
caTask: caTask,
|
||||
ca: caTask.Certificate(),
|
||||
}
|
||||
task.resource.Task = task
|
||||
c.AddTask(task)
|
||||
|
@ -225,6 +240,8 @@ func (b *BootstrapScript) GetDependencies(tasks map[string]fi.Task) []fi.Task {
|
|||
}
|
||||
}
|
||||
|
||||
deps = append(deps, b.caTask)
|
||||
|
||||
return deps
|
||||
}
|
||||
|
||||
|
@ -255,7 +272,7 @@ func (b *BootstrapScript) Run(c *fi.Context) error {
|
|||
return ""
|
||||
},
|
||||
"KubeEnv": func() (string, error) {
|
||||
return b.kubeEnv(b.ig, c)
|
||||
return b.kubeEnv(b.ig, c, b.ca)
|
||||
},
|
||||
|
||||
"EnvironmentVariables": func() (string, error) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"k8s.io/kops/pkg/apis/nodeup"
|
||||
"k8s.io/kops/pkg/testutils/golden"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/fitasks"
|
||||
"k8s.io/kops/util/pkg/architectures"
|
||||
"k8s.io/kops/util/pkg/hashing"
|
||||
"k8s.io/kops/util/pkg/mirrors"
|
||||
|
@ -62,7 +63,7 @@ type nodeupConfigBuilder struct {
|
|||
cluster *kops.Cluster
|
||||
}
|
||||
|
||||
func (n *nodeupConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string) (*nodeup.Config, error) {
|
||||
func (n *nodeupConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string, ca fi.Resource) (*nodeup.Config, error) {
|
||||
return nodeup.NewConfig(n.cluster, ig), nil
|
||||
}
|
||||
|
||||
|
@ -130,6 +131,13 @@ func TestBootstrapUserData(t *testing.T) {
|
|||
Tasks: make(map[string]fi.Task),
|
||||
}
|
||||
|
||||
caTask := &fitasks.Keypair{
|
||||
Name: fi.String(fi.CertificateIDCA),
|
||||
Subject: "cn=kubernetes",
|
||||
Type: "ca",
|
||||
}
|
||||
c.AddTask(caTask)
|
||||
|
||||
bs := &BootstrapScriptBuilder{
|
||||
NodeUpConfigBuilder: &nodeupConfigBuilder{cluster: cluster},
|
||||
NodeUpAssets: map[architectures.Architecture]*mirrors.MirroredAsset{
|
||||
|
|
|
@ -972,7 +972,7 @@ func createBuilderForCluster(cluster *kops.Cluster, instanceGroups []*kops.Insta
|
|||
type nodeupConfigBuilder struct {
|
||||
}
|
||||
|
||||
func (n *nodeupConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string) (*nodeup.Config, error) {
|
||||
func (n *nodeupConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string, ca fi.Resource) (*nodeup.Config, error) {
|
||||
return &nodeup.Config{}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ type Authenticator interface {
|
|||
type VerifyResult struct {
|
||||
// Nodename is the name that this node is authorized to use.
|
||||
NodeName string
|
||||
|
||||
// InstanceGroupName is the name of the kops InstanceGroup this node is a member of.
|
||||
InstanceGroupName string
|
||||
}
|
||||
|
||||
// Verifier verifies authentication credentials for requests.
|
||||
|
|
|
@ -20,9 +20,11 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
|
@ -54,6 +56,7 @@ import (
|
|||
"k8s.io/kops/pkg/model/spotinstmodel"
|
||||
"k8s.io/kops/pkg/resources/digitalocean"
|
||||
"k8s.io/kops/pkg/templates"
|
||||
"k8s.io/kops/pkg/wellknownports"
|
||||
"k8s.io/kops/upup/models"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/aliup"
|
||||
|
@ -1310,11 +1313,12 @@ func newNodeUpConfigBuilder(cluster *kops.Cluster, assetBuilder *assets.AssetBui
|
|||
images: images,
|
||||
protokubeImage: protokubeImage,
|
||||
}
|
||||
|
||||
return &configBuilder, nil
|
||||
}
|
||||
|
||||
// BuildNodeUpConfig returns the NodeUp config, in YAML format
|
||||
func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string) (*nodeup.Config, error) {
|
||||
func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAdditionalIPs []string, caResource fi.Resource) (*nodeup.Config, error) {
|
||||
cluster := n.cluster
|
||||
|
||||
if ig == nil {
|
||||
|
@ -1335,9 +1339,33 @@ func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAddit
|
|||
}
|
||||
}
|
||||
config.ClusterName = cluster.ObjectMeta.Name
|
||||
config.ConfigBase = fi.String(n.configBase.Path())
|
||||
config.InstanceGroupName = ig.ObjectMeta.Name
|
||||
|
||||
useConfigServer := featureflag.KopsControllerStateStore.Enabled() && (role != kops.InstanceGroupRoleMaster)
|
||||
if useConfigServer {
|
||||
baseURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort("kops-controller.internal."+cluster.ObjectMeta.Name, strconv.Itoa(wellknownports.KopsControllerPort)),
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
ca, err := fi.ResourceAsString(caResource)
|
||||
if err != nil {
|
||||
// CA task may not have run yet; we'll retry
|
||||
return nil, fmt.Errorf("failed to read CA certificate: %w", err)
|
||||
}
|
||||
|
||||
configServer := &nodeup.ConfigServerOptions{
|
||||
Server: baseURL.String(),
|
||||
CloudProvider: cluster.Spec.CloudProvider,
|
||||
CA: ca,
|
||||
}
|
||||
|
||||
config.ConfigServer = configServer
|
||||
} else {
|
||||
config.ConfigBase = fi.String(n.configBase.Path())
|
||||
}
|
||||
|
||||
if role == kops.InstanceGroupRoleMaster {
|
||||
config.ApiserverAdditionalIPs = apiserverAdditionalIPs
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ go_library(
|
|||
"//vendor/github.com/aws/aws-sdk-go/aws/arn:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/awserr:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/client:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/ec2metadata:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/endpoints:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/request:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library",
|
||||
|
|
|
@ -17,11 +17,14 @@ limitations under the License.
|
|||
package awsup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
|
@ -35,6 +38,24 @@ type awsAuthenticator struct {
|
|||
|
||||
var _ fi.Authenticator = &awsAuthenticator{}
|
||||
|
||||
// RegionFromMetadata returns the current region from the aws metdata
|
||||
func RegionFromMetadata(ctx context.Context) (string, error) {
|
||||
config := aws.NewConfig()
|
||||
config = config.WithCredentialsChainVerboseErrors(true)
|
||||
|
||||
s, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
metadata := ec2metadata.New(s, config)
|
||||
|
||||
region, err := metadata.RegionWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get region from ec2 metadata: %w", err)
|
||||
}
|
||||
return region, nil
|
||||
}
|
||||
|
||||
func NewAWSAuthenticator(region string) (fi.Authenticator, error) {
|
||||
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true).WithRegion(region)
|
||||
sess, err := session.NewSession(config)
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
nodeidentityaws "k8s.io/kops/pkg/nodeidentity/aws"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
|
@ -166,17 +167,17 @@ func (a awsVerifier) VerifyToken(token string, body []byte) (*fi.VerifyResult, e
|
|||
return nil, fmt.Errorf("received status code %d from STS: %s", response.StatusCode, string(responseBody))
|
||||
}
|
||||
|
||||
result := GetCallerIdentityResponse{}
|
||||
err = xml.NewDecoder(bytes.NewReader(responseBody)).Decode(&result)
|
||||
callerIdentity := GetCallerIdentityResponse{}
|
||||
err = xml.NewDecoder(bytes.NewReader(responseBody)).Decode(&callerIdentity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding STS response: %v", err)
|
||||
}
|
||||
|
||||
if result.GetCallerIdentityResult[0].Account != a.accountId {
|
||||
return nil, fmt.Errorf("incorrect account %s", result.GetCallerIdentityResult[0].Account)
|
||||
if callerIdentity.GetCallerIdentityResult[0].Account != a.accountId {
|
||||
return nil, fmt.Errorf("incorrect account %s", callerIdentity.GetCallerIdentityResult[0].Account)
|
||||
}
|
||||
|
||||
arn := result.GetCallerIdentityResult[0].Arn
|
||||
arn := callerIdentity.GetCallerIdentityResult[0].Arn
|
||||
parts := strings.Split(arn, ":")
|
||||
if len(parts) != 6 {
|
||||
return nil, fmt.Errorf("arn %q contains unexpected number of colons", arn)
|
||||
|
@ -225,7 +226,18 @@ func (a awsVerifier) VerifyToken(token string, body []byte) (*fi.VerifyResult, e
|
|||
return nil, fmt.Errorf("found multiple instances with instance id: %s", instanceID)
|
||||
}
|
||||
|
||||
return &fi.VerifyResult{
|
||||
NodeName: aws.StringValue(instances.Reservations[0].Instances[0].PrivateDnsName),
|
||||
}, nil
|
||||
instance := instances.Reservations[0].Instances[0]
|
||||
|
||||
result := &fi.VerifyResult{
|
||||
NodeName: aws.StringValue(instance.PrivateDnsName),
|
||||
}
|
||||
|
||||
for _, tag := range instance.Tags {
|
||||
tagKey := aws.StringValue(tag.Key)
|
||||
if tagKey == nodeidentityaws.CloudTagInstanceGroupName {
|
||||
result.InstanceGroupName = aws.StringValue(tag.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -310,3 +310,8 @@ func (e *Keypair) CertificateSHA1Fingerprint() fi.Resource {
|
|||
e.ensureResources()
|
||||
return e.certificateSHA1Fingerprint
|
||||
}
|
||||
|
||||
func (e *Keypair) Certificate() fi.Resource {
|
||||
e.ensureResources()
|
||||
return e.certificate
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ go_library(
|
|||
"//pkg/apis/kops/registry:go_default_library",
|
||||
"//pkg/apis/nodeup:go_default_library",
|
||||
"//pkg/assets:go_default_library",
|
||||
"//pkg/configserver:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//upup/pkg/fi/cloudup/awsup:go_default_library",
|
||||
"//upup/pkg/fi/nodeup/cloudinit:go_default_library",
|
||||
"//upup/pkg/fi/nodeup/local:go_default_library",
|
||||
"//upup/pkg/fi/nodeup/nodetasks:go_default_library",
|
||||
|
|
|
@ -17,11 +17,13 @@ limitations under the License.
|
|||
package nodeup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -33,7 +35,9 @@ import (
|
|||
"k8s.io/kops/pkg/apis/kops/registry"
|
||||
"k8s.io/kops/pkg/apis/nodeup"
|
||||
"k8s.io/kops/pkg/assets"
|
||||
"k8s.io/kops/pkg/configserver"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
||||
"k8s.io/kops/upup/pkg/fi/nodeup/cloudinit"
|
||||
"k8s.io/kops/upup/pkg/fi/nodeup/local"
|
||||
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
|
||||
|
@ -64,6 +68,8 @@ type NodeUpCommand struct {
|
|||
|
||||
// Run is responsible for perform the nodeup process
|
||||
func (c *NodeUpCommand) Run(out io.Writer) error {
|
||||
ctx := context.Background()
|
||||
|
||||
if c.ConfigLocation != "" {
|
||||
config, err := vfs.Context.ReadFile(c.ConfigLocation)
|
||||
if err != nil {
|
||||
|
@ -83,7 +89,17 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
|
|||
}
|
||||
|
||||
var configBase vfs.Path
|
||||
if fi.StringValue(c.config.ConfigBase) != "" {
|
||||
|
||||
// If we're using a config server instead of vfs, nodeConfig will hold our configuration
|
||||
var nodeConfig *nodeup.NodeConfig
|
||||
|
||||
if c.config.ConfigServer != nil {
|
||||
response, err := getNodeConfigFromServer(ctx, c.config.ConfigServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeConfig = response.NodeConfig
|
||||
} else if fi.StringValue(c.config.ConfigBase) != "" {
|
||||
var err error
|
||||
configBase, err = vfs.Context.BuildVfsPath(*c.config.ConfigBase)
|
||||
if err != nil {
|
||||
|
@ -102,11 +118,15 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
|
|||
return fmt.Errorf("cannot parse inferred ConfigBase %q: %v", basePath, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("ConfigBase is required")
|
||||
return fmt.Errorf("ConfigBase or ConfigServer is required")
|
||||
}
|
||||
|
||||
c.cluster = &api.Cluster{}
|
||||
{
|
||||
if nodeConfig != nil {
|
||||
if err := utils.YamlUnmarshal([]byte(nodeConfig.ClusterFullConfig), c.cluster); err != nil {
|
||||
return fmt.Errorf("error parsing Cluster config response: %w", err)
|
||||
}
|
||||
} else {
|
||||
clusterLocation := fi.StringValue(c.config.ClusterLocation)
|
||||
|
||||
var p vfs.Path
|
||||
|
@ -131,7 +151,12 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
if c.config.InstanceGroupName != "" {
|
||||
if nodeConfig != nil {
|
||||
c.instanceGroup = &api.InstanceGroup{}
|
||||
if err := utils.YamlUnmarshal([]byte(nodeConfig.InstanceGroupConfig), c.instanceGroup); err != nil {
|
||||
return fmt.Errorf("error parsing InstanceGroup config response: %v", err)
|
||||
}
|
||||
} else if c.config.InstanceGroupName != "" {
|
||||
instanceGroupLocation := configBase.Join("instancegroup", c.config.InstanceGroupName)
|
||||
|
||||
c.instanceGroup = &api.InstanceGroup{}
|
||||
|
@ -183,7 +208,9 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
|
|||
|
||||
var secretStore fi.SecretStore
|
||||
var keyStore fi.Keystore
|
||||
if c.cluster.Spec.SecretStore != "" {
|
||||
if nodeConfig != nil {
|
||||
modelContext.SecretStore = configserver.NewSecretStore(nodeConfig)
|
||||
} else if c.cluster.Spec.SecretStore != "" {
|
||||
klog.Infof("Building SecretStore at %q", c.cluster.Spec.SecretStore)
|
||||
p, err := vfs.Context.BuildVfsPath(c.cluster.Spec.SecretStore)
|
||||
if err != nil {
|
||||
|
@ -196,7 +223,9 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
|
|||
return fmt.Errorf("SecretStore not set")
|
||||
}
|
||||
|
||||
if c.cluster.Spec.KeyStore != "" {
|
||||
if nodeConfig != nil {
|
||||
modelContext.KeyStore = configserver.NewKeyStore(nodeConfig)
|
||||
} else if c.cluster.Spec.KeyStore != "" {
|
||||
klog.Infof("Building KeyStore at %q", c.cluster.Spec.KeyStore)
|
||||
p, err := vfs.Context.BuildVfsPath(c.cluster.Spec.KeyStore)
|
||||
if err != nil {
|
||||
|
@ -569,3 +598,43 @@ func loadKernelModules(context *model.NodeupModelContext) error {
|
|||
// TODO: Add to /etc/modules-load.d/ ?
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNodeConfigFromServer queries kops-controller for our node's configuration.
|
||||
func getNodeConfigFromServer(ctx context.Context, config *nodeup.ConfigServerOptions) (*nodeup.BootstrapResponse, error) {
|
||||
var authenticator fi.Authenticator
|
||||
|
||||
switch api.CloudProviderID(config.CloudProvider) {
|
||||
case api.CloudProviderAWS:
|
||||
region, err := awsup.RegionFromMetadata(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err := awsup.NewAWSAuthenticator(region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authenticator = a
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported cloud provider %s", config.CloudProvider)
|
||||
}
|
||||
|
||||
client := &nodetasks.KopsBootstrapClient{
|
||||
Authenticator: authenticator,
|
||||
}
|
||||
|
||||
if config.CA != "" {
|
||||
client.CA = []byte(config.CA)
|
||||
}
|
||||
|
||||
u, err := url.Parse(config.Server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse configuration server url %q: %w", config.Server, err)
|
||||
}
|
||||
client.BaseURL = *u
|
||||
|
||||
request := nodeup.BootstrapRequest{
|
||||
APIVersion: nodeup.BootstrapAPIVersion,
|
||||
IncludeNodeConfig: true,
|
||||
}
|
||||
return client.QueryBootstrap(ctx, &request)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ go_library(
|
|||
"//pkg/backoff:go_default_library",
|
||||
"//pkg/kubeconfig:go_default_library",
|
||||
"//pkg/pki:go_default_library",
|
||||
"//pkg/wellknownports:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//upup/pkg/fi/nodeup/cloudinit:go_default_library",
|
||||
"//upup/pkg/fi/nodeup/local:go_default_library",
|
||||
|
|
|
@ -19,6 +19,7 @@ package nodetasks
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
@ -26,27 +27,23 @@ import (
|
|||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"path"
|
||||
|
||||
"k8s.io/kops/pkg/apis/nodeup"
|
||||
"k8s.io/kops/pkg/pki"
|
||||
"k8s.io/kops/pkg/wellknownports"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
type BootstrapClient struct {
|
||||
// Authenticator generates authentication credentials for requests.
|
||||
Authenticator fi.Authenticator
|
||||
// CA is the CA certificate for kops-controller.
|
||||
CA []byte
|
||||
type BootstrapClientTask struct {
|
||||
// Certs are the requested certificates.
|
||||
Certs map[string]*BootstrapCert
|
||||
|
||||
client *http.Client
|
||||
keys map[string]*pki.PrivateKey
|
||||
// Client holds the client wrapper for the kops-bootstrap protocol
|
||||
Client *KopsBootstrapClient
|
||||
|
||||
keys map[string]*pki.PrivateKey
|
||||
}
|
||||
|
||||
type BootstrapCert struct {
|
||||
|
@ -54,11 +51,11 @@ type BootstrapCert struct {
|
|||
Key *fi.TaskDependentResource
|
||||
}
|
||||
|
||||
var _ fi.Task = &BootstrapClient{}
|
||||
var _ fi.HasName = &BootstrapClient{}
|
||||
var _ fi.HasDependencies = &BootstrapClient{}
|
||||
var _ fi.Task = &BootstrapClientTask{}
|
||||
var _ fi.HasName = &BootstrapClientTask{}
|
||||
var _ fi.HasDependencies = &BootstrapClientTask{}
|
||||
|
||||
func (b *BootstrapClient) GetDependencies(tasks map[string]fi.Task) []fi.Task {
|
||||
func (b *BootstrapClientTask) GetDependencies(tasks map[string]fi.Task) []fi.Task {
|
||||
// BootstrapClient depends on the protokube service to ensure gossip DNS
|
||||
var deps []fi.Task
|
||||
for _, v := range tasks {
|
||||
|
@ -69,16 +66,18 @@ func (b *BootstrapClient) GetDependencies(tasks map[string]fi.Task) []fi.Task {
|
|||
return deps
|
||||
}
|
||||
|
||||
func (b *BootstrapClient) GetName() *string {
|
||||
func (b *BootstrapClientTask) GetName() *string {
|
||||
name := "BootstrapClient"
|
||||
return &name
|
||||
}
|
||||
|
||||
func (b *BootstrapClient) String() string {
|
||||
return "BootstrapClient"
|
||||
func (b *BootstrapClientTask) String() string {
|
||||
return "BootstrapClientTask"
|
||||
}
|
||||
|
||||
func (b *BootstrapClient) Run(c *fi.Context) error {
|
||||
func (b *BootstrapClientTask) Run(c *fi.Context) error {
|
||||
ctx := context.TODO()
|
||||
|
||||
req := nodeup.BootstrapRequest{
|
||||
APIVersion: nodeup.BootstrapAPIVersion,
|
||||
Certs: map[string]string{},
|
||||
|
@ -109,7 +108,7 @@ func (b *BootstrapClient) Run(c *fi.Context) error {
|
|||
req.Certs[name] = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: pkData}))
|
||||
}
|
||||
|
||||
resp, err := b.queryBootstrap(c, &req)
|
||||
resp, err := b.Client.QueryBootstrap(ctx, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -129,12 +128,24 @@ func (b *BootstrapClient) Run(c *fi.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *BootstrapClient) queryBootstrap(c *fi.Context, req *nodeup.BootstrapRequest) (*nodeup.BootstrapResponse, error) {
|
||||
if b.client == nil {
|
||||
type KopsBootstrapClient struct {
|
||||
// Authenticator generates authentication credentials for requests.
|
||||
Authenticator fi.Authenticator
|
||||
// CA is the CA certificate for kops-controller.
|
||||
CA []byte
|
||||
|
||||
// BaseURL is the base URL for the server
|
||||
BaseURL url.URL
|
||||
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func (b *KopsBootstrapClient) QueryBootstrap(ctx context.Context, req *nodeup.BootstrapRequest) (*nodeup.BootstrapResponse, error) {
|
||||
if b.httpClient == nil {
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AppendCertsFromPEM(b.CA)
|
||||
|
||||
b.client = &http.Client{
|
||||
b.httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: certPool,
|
||||
|
@ -149,12 +160,9 @@ func (b *BootstrapClient) queryBootstrap(c *fi.Context, req *nodeup.BootstrapReq
|
|||
return nil, err
|
||||
}
|
||||
|
||||
bootstrapUrl := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort("kops-controller.internal."+c.Cluster.ObjectMeta.Name, strconv.Itoa(wellknownports.KopsControllerPort)),
|
||||
Path: "/bootstrap",
|
||||
}
|
||||
httpReq, err := http.NewRequest("POST", bootstrapUrl.String(), bytes.NewReader(reqBytes))
|
||||
bootstrapURL := b.BaseURL
|
||||
bootstrapURL.Path = path.Join(bootstrapURL.Path, "/bootstrap")
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", bootstrapURL.String(), bytes.NewReader(reqBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -166,7 +174,7 @@ func (b *BootstrapClient) queryBootstrap(c *fi.Context, req *nodeup.BootstrapReq
|
|||
}
|
||||
httpReq.Header.Set("Authorization", token)
|
||||
|
||||
resp, err := b.client.Do(httpReq)
|
||||
resp, err := b.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ func (p *Service) GetDependencies(tasks map[string]fi.Task) []fi.Task {
|
|||
switch v := v.(type) {
|
||||
case *Package, *UpdatePackages, *UserTask, *GroupTask, *Chattr, *BindMount, *Archive:
|
||||
deps = append(deps, v)
|
||||
case *Service, *LoadImageTask, *IssueCert, *BootstrapClient, *KubeConfig:
|
||||
case *Service, *LoadImageTask, *IssueCert, *BootstrapClientTask, *KubeConfig:
|
||||
// ignore
|
||||
case *File:
|
||||
if len(v.BeforeServices) > 0 {
|
||||
|
|
Loading…
Reference in New Issue