diff --git a/cmd/kops-controller/main.go b/cmd/kops-controller/main.go index d1ba60b9e8c38..846a8d8d3a408 100644 --- a/cmd/kops-controller/main.go +++ b/cmd/kops-controller/main.go @@ -32,7 +32,9 @@ import ( "k8s.io/kops/cmd/kops-controller/controllers" "k8s.io/kops/cmd/kops-controller/pkg/config" "k8s.io/kops/cmd/kops-controller/pkg/server" + "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/pkg/bootstrap" + "k8s.io/kops/pkg/bootstrap/pkibootstrap" "k8s.io/kops/pkg/nodeidentity" nodeidentityaws "k8s.io/kops/pkg/nodeidentity/aws" nodeidentityazure "k8s.io/kops/pkg/nodeidentity/azure" @@ -58,7 +60,6 @@ import ( ) var ( - scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") ) @@ -100,7 +101,8 @@ func main() { ctrl.SetLogger(klogr.New()) - if err := buildScheme(); err != nil { + scheme, err := buildScheme() + if err != nil { setupLog.Error(err, "error building scheme") os.Exit(1) } @@ -108,6 +110,7 @@ func main() { kubeConfig := ctrl.GetConfigOrDie() kubeConfig.Burst = 200 kubeConfig.QPS = 100 + mgr, err := ctrl.NewManager(kubeConfig, ctrl.Options{ Scheme: scheme, Metrics: metricsserver.Options{ @@ -183,6 +186,15 @@ func main() { verifiers = append(verifiers, verifier) } + if opt.Server.PKI != nil { + verifier, err := pkibootstrap.NewVerifier(opt.Server.PKI, mgr.GetClient()) + if err != nil { + setupLog.Error(err, "unable to create verifier") + os.Exit(1) + } + verifiers = append(verifiers, verifier) + } + if len(verifiers) == 0 { klog.Fatalf("server verifiers not provided") } @@ -233,15 +245,19 @@ func main() { } } -func buildScheme() error { +func buildScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() if err := corev1.AddToScheme(scheme); err != nil { - return fmt.Errorf("error registering corev1: %v", err) + return nil, fmt.Errorf("error registering corev1: %v", err) + } + if err := v1alpha2.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("error registering kops/v1alpha2 API: %v", err) } // Needed so that the leader-election system can post events if err := coordinationv1.AddToScheme(scheme); err != nil { - return fmt.Errorf("error registering coordinationv1: %v", err) + return nil, fmt.Errorf("error registering coordinationv1: %v", err) } - return nil + return scheme, nil } func addNodeController(mgr manager.Manager, vfsContext *vfs.VFSContext, opt *config.Options) error { diff --git a/cmd/kops-controller/pkg/config/options.go b/cmd/kops-controller/pkg/config/options.go index 39770c12315e2..9c6fb65d20269 100644 --- a/cmd/kops-controller/pkg/config/options.go +++ b/cmd/kops-controller/pkg/config/options.go @@ -17,6 +17,7 @@ limitations under the License. package config import ( + "k8s.io/kops/pkg/bootstrap/pkibootstrap" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/azure" "k8s.io/kops/upup/pkg/fi/cloudup/do" @@ -51,6 +52,9 @@ type ServerOptions struct { // Provider is the cloud provider. Provider ServerProviderOptions `json:"provider"` + // PKI configures private/public key node authentication. + PKI *pkibootstrap.Options `json:"pki,omitempty"` + // ServerKeyPath is the path to our TLS serving private key. ServerKeyPath string `json:"serverKeyPath,omitempty"` // ServerCertificatePath is the path to our TLS serving certificate. diff --git a/k8s/crds/kops.k8s.io_hosts.yaml b/k8s/crds/kops.k8s.io_hosts.yaml new file mode 100644 index 0000000000000..5598b08360a44 --- /dev/null +++ b/k8s/crds/kops.k8s.io_hosts.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: hosts.kops.k8s.io +spec: + group: kops.k8s.io + names: + kind: Host + listKind: HostList + plural: hosts + singular: host + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: Host represents a bare-metal machine that could be registered + as a Node. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + instanceGroup: + type: string + publicKey: + type: string + type: object + type: object + served: true + storage: true diff --git a/nodeup/pkg/model/bootstrap_client.go b/nodeup/pkg/model/bootstrap_client.go index 68ff62448c4cb..031e55a9664c6 100644 --- a/nodeup/pkg/model/bootstrap_client.go +++ b/nodeup/pkg/model/bootstrap_client.go @@ -24,6 +24,7 @@ import ( "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/bootstrap" + "k8s.io/kops/pkg/bootstrap/pkibootstrap" "k8s.io/kops/pkg/kopscontrollerclient" "k8s.io/kops/pkg/resolver" "k8s.io/kops/pkg/wellknownports" @@ -101,6 +102,13 @@ func (b BootstrapClientBuilder) Build(c *fi.NodeupModelBuilderContext) error { } authenticator = a + case "metal": + a, err := pkibootstrap.NewAuthenticatorFromFile("/etc/kubernetes/kops/pki/machine/private.pem") + if err != nil { + return err + } + authenticator = a + default: return fmt.Errorf("unsupported cloud provider for authenticator %q", b.CloudProvider()) } diff --git a/pkg/apis/kops/v1alpha2/host.go b/pkg/apis/kops/v1alpha2/host.go new file mode 100644 index 0000000000000..8db66bd4c801e --- /dev/null +++ b/pkg/apis/kops/v1alpha2/host.go @@ -0,0 +1,46 @@ +/* +Copyright 2023 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 v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Host represents a bare-metal machine that could be registered as a Node. +type Host struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec HostSpec `json:"spec,omitempty"` +} + +type HostSpec struct { + PublicKey string `json:"publicKey,omitempty"` + InstanceGroup string `json:"instanceGroup,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type HostList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Host `json:"items"` +} diff --git a/pkg/apis/kops/v1alpha2/register.go b/pkg/apis/kops/v1alpha2/register.go index 94425abfb997b..3eac7695ccb5d 100644 --- a/pkg/apis/kops/v1alpha2/register.go +++ b/pkg/apis/kops/v1alpha2/register.go @@ -61,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &KeysetList{}, &SSHCredential{}, &SSHCredentialList{}, + &Host{}, + &HostList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go index 267fa6ef4b9f3..8839468d0a176 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go @@ -2292,6 +2292,82 @@ func (in *HookSpec) DeepCopy() *HookSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Host) DeepCopyInto(out *Host) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Host. +func (in *Host) DeepCopy() *Host { + if in == nil { + return nil + } + out := new(Host) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Host) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostList) DeepCopyInto(out *HostList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Host, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostList. +func (in *HostList) DeepCopy() *HostList { + if in == nil { + return nil + } + out := new(HostList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HostList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostSpec) DeepCopyInto(out *HostSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostSpec. +func (in *HostSpec) DeepCopy() *HostSpec { + if in == nil { + return nil + } + out := new(HostSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HubbleSpec) DeepCopyInto(out *HubbleSpec) { *out = *in diff --git a/pkg/apis/kops/v1alpha3/host.go b/pkg/apis/kops/v1alpha3/host.go new file mode 100644 index 0000000000000..b6e2410adcf39 --- /dev/null +++ b/pkg/apis/kops/v1alpha3/host.go @@ -0,0 +1,46 @@ +/* +Copyright 2023 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 v1alpha3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Host represents a bare-metal machine that could be registered as a Node. +type Host struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec HostSpec `json:"spec,omitempty"` +} + +type HostSpec struct { + PublicKey string `json:"publicKey,omitempty"` + InstanceGroup string `json:"instanceGroup,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type HostList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Host `json:"items"` +} diff --git a/pkg/apis/kops/v1alpha3/register.go b/pkg/apis/kops/v1alpha3/register.go index 3dfee01e23acb..9a6009f425547 100644 --- a/pkg/apis/kops/v1alpha3/register.go +++ b/pkg/apis/kops/v1alpha3/register.go @@ -61,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &KeysetList{}, &SSHCredential{}, &SSHCredentialList{}, + &Host{}, + &HostList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go index 89a2f9df5bb9f..b83ac5625f140 100644 --- a/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go +++ b/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go @@ -2255,6 +2255,82 @@ func (in *HookSpec) DeepCopy() *HookSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Host) DeepCopyInto(out *Host) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Host. +func (in *Host) DeepCopy() *Host { + if in == nil { + return nil + } + out := new(Host) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Host) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostList) DeepCopyInto(out *HostList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Host, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostList. +func (in *HostList) DeepCopy() *HostList { + if in == nil { + return nil + } + out := new(HostList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HostList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostSpec) DeepCopyInto(out *HostSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostSpec. +func (in *HostSpec) DeepCopy() *HostSpec { + if in == nil { + return nil + } + out := new(HostSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HubbleSpec) DeepCopyInto(out *HubbleSpec) { *out = *in diff --git a/pkg/bootstrap/pkibootstrap/options.go b/pkg/bootstrap/pkibootstrap/options.go new file mode 100644 index 0000000000000..718c8a949356d --- /dev/null +++ b/pkg/bootstrap/pkibootstrap/options.go @@ -0,0 +1,26 @@ +/* +Copyright 2023 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 pkibootstrap + +// Options describes how we authenticate instances with known-public-key authentication. +type Options struct { + // MaxTimeSkew is the maximum time skew to allow (in seconds) + MaxTimeSkew int64 `json:"MaxTimeSkew,omitempty"` +} + +// AuthenticationTokenPrefix is the prefix used for authentication using PKI +const AuthenticationTokenPrefix = "x-pki-tpm " diff --git a/pkg/bootstrap/pkibootstrap/pkisigner.go b/pkg/bootstrap/pkibootstrap/pkisigner.go new file mode 100644 index 0000000000000..25d59d8191a34 --- /dev/null +++ b/pkg/bootstrap/pkibootstrap/pkisigner.go @@ -0,0 +1,151 @@ +/* +Copyright 2023 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 pkibootstrap + +import ( + "bytes" + "crypto" + cryptorand "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "os" + "time" + + "k8s.io/klog/v2" + "k8s.io/kops/pkg/bootstrap" + "k8s.io/kops/pkg/pki" +) + +type pkiAuthenticator struct { + signer crypto.Signer + keyID string + hostname string +} + +// AuthTokenData is the code data that is signed as part of the header. +type AuthTokenData struct { + // Instance is the name/id of the instance we are claiming + Instance string `json:"instance,omitempty"` + + // KeyID is the identifier of the public key we are signing with, if we're using a fixed key. + KeyID string `json:"keyID,omitempty"` + + // RequestHash is the hash of the request + RequestHash []byte `json:"requestHash,omitempty"` + + // Timestamp is the time of this request (to help prevent replay attacks) + Timestamp int64 `json:"timestamp,omitempty"` + + // Audience is the audience for this request (to help prevent replay attacks) + Audience string `json:"audience,omitempty"` +} + +var _ bootstrap.Authenticator = &pkiAuthenticator{} + +func NewAuthenticator(hostname string, signer crypto.Signer) (bootstrap.Authenticator, error) { + keyID, err := computeKeyID(signer) + if err != nil { + return nil, err + } + + return &pkiAuthenticator{hostname: hostname, signer: signer, keyID: keyID}, nil +} + +func computeKeyID(signer crypto.Signer) (string, error) { + publicKey := signer.Public() + pkData, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", fmt.Errorf("error converting public key to x509: %w", err) + } + + var b bytes.Buffer + if err := pem.Encode(&b, &pem.Block{Type: "PUBLIC KEY", Bytes: pkData}); err != nil { + return "", fmt.Errorf("error encoding public key: %w", err) + } + return b.String(), nil +} + +func NewAuthenticatorFromFile(p string) (bootstrap.Authenticator, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("couldn't determine hostname: %w", err) + } + + keyBytes, err := os.ReadFile(p) + if err != nil { + return nil, fmt.Errorf("error reading %q: %w", p, err) + } + key, err := pki.ParsePEMPrivateKey(keyBytes) + if err != nil { + return nil, fmt.Errorf("error parsing key from %q: %w", p, err) + } + + return NewAuthenticator(hostname, key.Key) +} + +func (a *pkiAuthenticator) CreateToken(body []byte) (string, error) { + requestHash := sha256.Sum256(body) + + data := AuthTokenData{ + Timestamp: time.Now().Unix(), + Audience: AudienceNodeAuthentication, + RequestHash: requestHash[:], + + KeyID: a.keyID, + Instance: a.hostname, + } + + payload, err := json.Marshal(&data) + if err != nil { + return "", fmt.Errorf("failed to marshal token data: %w", err) + } + + signature, err := a.sign(payload) + if err != nil { + return "", fmt.Errorf("failed to sign token data: %w", err) + } + token := &AuthToken{ + Data: payload, + Signature: signature, + } + + b, err := json.Marshal(token) + if err != nil { + return "", fmt.Errorf("failed to marshal token: %w", err) + } + return AuthenticationTokenPrefix + base64.StdEncoding.EncodeToString(b), nil +} + +// sign performs a TPM signature with the tpmKey, and sanity checks the result. +func (a *pkiAuthenticator) sign(payload []byte) ([]byte, error) { + beforeSign := time.Now() + + digest := sha256.Sum256(payload) + + signature, err := a.signer.Sign(cryptorand.Reader, digest[:], crypto.SHA256) + if err != nil { + return nil, fmt.Errorf("failed to sign data: %w", err) + } + + klog.Infof("signing took %v", time.Since(beforeSign)) + + return signature, nil +} diff --git a/pkg/bootstrap/pkibootstrap/pkiverifier.go b/pkg/bootstrap/pkibootstrap/pkiverifier.go new file mode 100644 index 0000000000000..c12068e4b719e --- /dev/null +++ b/pkg/bootstrap/pkibootstrap/pkiverifier.go @@ -0,0 +1,179 @@ +/* +Copyright 2023 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 pkibootstrap + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "math" + "net/http" + "strings" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" + kops "k8s.io/kops/pkg/apis/kops/v1alpha2" + "k8s.io/kops/pkg/bootstrap" + "k8s.io/kops/pkg/pki" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type verifier struct { + opt Options + client client.Client +} + +// NewVerifier constructs a new verifier. +func NewVerifier(options *Options, client client.Client) (bootstrap.Verifier, error) { + opt := *options + if opt.MaxTimeSkew == 0 { + opt.MaxTimeSkew = 300 + } + return &verifier{ + opt: opt, + client: client, + }, nil +} + +var _ bootstrap.Verifier = &verifier{} + +// TODO: Dedup with gce +func (v *verifier) parseTokenData(tokenPrefix string, authToken string, body []byte) (*AuthToken, *AuthTokenData, error) { + if !strings.HasPrefix(authToken, tokenPrefix) { + return nil, nil, fmt.Errorf("incorrect authorization type") + } + authToken = strings.TrimPrefix(authToken, tokenPrefix) + + tokenBytes, err := base64.StdEncoding.DecodeString(authToken) + if err != nil { + return nil, nil, fmt.Errorf("decoding authorization token: %w", err) + } + + token := &AuthToken{} + if err = json.Unmarshal(tokenBytes, token); err != nil { + return nil, nil, fmt.Errorf("unmarshalling authorization token: %w", err) + } + + tokenData := &AuthTokenData{} + if err := json.Unmarshal(token.Data, tokenData); err != nil { + return nil, nil, fmt.Errorf("unmarshalling authorization token data: %w", err) + } + + // Guard against replay attacks + if tokenData.Audience != AudienceNodeAuthentication { + return nil, nil, fmt.Errorf("incorrect Audience") + } + timeSkew := math.Abs(time.Since(time.Unix(tokenData.Timestamp, 0)).Seconds()) + if timeSkew > float64(v.opt.MaxTimeSkew) { + return nil, nil, fmt.Errorf("incorrect Timestamp %v", tokenData.Timestamp) + } + + // Verify the token has signed the body content. + requestHash := sha256.Sum256(body) + if !bytes.Equal(requestHash[:], tokenData.RequestHash) { + return nil, nil, fmt.Errorf("incorrect RequestHash") + } + + return token, tokenData, nil +} + +// Can generate keys with +// openssl ecparam -name prime256v1 -genkey -noout -out ec-priv-key.pem +// openssl ec -in ec-priv-key.pem -pubout > ec-pub-key.pem +// Note that golang doesn't support secp256k1: https://groups.google.com/g/golang-nuts/c/Mbkug5t3ZYA + +func (v *verifier) VerifyToken(ctx context.Context, rawRequest *http.Request, authToken string, body []byte) (*bootstrap.VerifyResult, error) { + // Reminder: we shouldn't trust any data we get from the client until we've checked the signature (and even then...) + // Thankfully the GCE SDK does seem to escape the parameters correctly, for example. + + token, tokenData, err := v.parseTokenData(AuthenticationTokenPrefix, authToken, body) + if err != nil { + return nil, err + } + + // Verify the token has a valid signature. + result, signingKey, err := v.getSigningKey(ctx, tokenData) + if err != nil { + return nil, err + } + + if !verifySignature(signingKey, token.Data, token.Signature) { + return nil, fmt.Errorf("failed to verify claim signature for node") + } + + return result, nil +} + +func (v *verifier) getSigningKey(ctx context.Context, tokenData *AuthTokenData) (*bootstrap.VerifyResult, crypto.PublicKey, error) { + nodeName := tokenData.Instance + id := types.NamespacedName{ + Namespace: "kops-system", + Name: nodeName, + } + var host kops.Host + if err := v.client.Get(ctx, id, &host); err != nil { + if apierrors.IsNotFound(err) { + return nil, nil, fmt.Errorf("host not found for %v", id) + } + return nil, nil, fmt.Errorf("error getting host %v: %w", id, err) + } + + // TODO: Check instance-group matches request (does it matter?) + + if host.Spec.PublicKey == "" { + return nil, nil, fmt.Errorf("host %v did not have public-key", id) + } + instanceGroup := host.Spec.InstanceGroup + if instanceGroup == "" { + return nil, nil, fmt.Errorf("host %v did not have spec.instanceGroup", id) + } + pubKey, err := pki.ParsePEMPublicKey([]byte(host.Spec.PublicKey)) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse public key: %w", err) + } + + var sans []string + + result := &bootstrap.VerifyResult{ + NodeName: nodeName, + InstanceGroupName: instanceGroup, + CertificateNames: sans, + } + + return result, pubKey.Key, nil +} + +func verifySignature(signingKey crypto.PublicKey, payload []byte, signature []byte) bool { + attestHash := sha256.Sum256(payload) + switch signingKey := signingKey.(type) { + case *ecdsa.PublicKey: + klog.Infof("attestHash %x", attestHash) + klog.Infof("sig %x", signature) + return ecdsa.VerifyASN1(signingKey, attestHash[:], signature) + + default: + klog.Warningf("key type %T not supported", signingKey) + return false + } +} diff --git a/pkg/bootstrap/pkibootstrap/token.go b/pkg/bootstrap/pkibootstrap/token.go new file mode 100644 index 0000000000000..277644b10b970 --- /dev/null +++ b/pkg/bootstrap/pkibootstrap/token.go @@ -0,0 +1,30 @@ +/* +Copyright 2023 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 pkibootstrap + +// AuthToken describes the authentication header data when using GCE TPM authentication. +type AuthToken struct { + // Signature is the TPM or PKI signature for data + Signature []byte `json:"signature,omitempty"` + + // Data is the data we are signing. + // It is a JSON encoded form of AuthTokenData. + Data []byte `json:"data,omitempty"` +} + +// AudienceNodeAuthentication is used in case we have multiple audiences using the TPM in future +const AudienceNodeAuthentication = "kops.k8s.io/node-bootstrap" diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_host.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_host.go new file mode 100644 index 0000000000000..ac1590c2d5722 --- /dev/null +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_host.go @@ -0,0 +1,129 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha2 "k8s.io/kops/pkg/apis/kops/v1alpha2" +) + +// FakeHosts implements HostInterface +type FakeHosts struct { + Fake *FakeKopsV1alpha2 + ns string +} + +var hostsResource = v1alpha2.SchemeGroupVersion.WithResource("hosts") + +var hostsKind = v1alpha2.SchemeGroupVersion.WithKind("Host") + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *FakeHosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(hostsResource, c.ns, name), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *FakeHosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.HostList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(hostsResource, hostsKind, c.ns, opts), &v1alpha2.HostList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.HostList{ListMeta: obj.(*v1alpha2.HostList).ListMeta} + for _, item := range obj.(*v1alpha2.HostList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *FakeHosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(hostsResource, c.ns, opts)) + +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Create(ctx context.Context, host *v1alpha2.Host, opts v1.CreateOptions) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(hostsResource, c.ns, host), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Update(ctx context.Context, host *v1alpha2.Host, opts v1.UpdateOptions) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(hostsResource, c.ns, host), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *FakeHosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(hostsResource, c.ns, name, opts), &v1alpha2.Host{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeHosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(hostsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.HostList{}) + return err +} + +// Patch applies the patch and returns the patched host. +func (c *FakeHosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(hostsResource, c.ns, name, pt, data, subresources...), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_kops_client.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_kops_client.go index 2f9d66b910009..312cfded898f1 100644 --- a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_kops_client.go +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/fake/fake_kops_client.go @@ -32,6 +32,10 @@ func (c *FakeKopsV1alpha2) Clusters(namespace string) v1alpha2.ClusterInterface return &FakeClusters{c, namespace} } +func (c *FakeKopsV1alpha2) Hosts(namespace string) v1alpha2.HostInterface { + return &FakeHosts{c, namespace} +} + func (c *FakeKopsV1alpha2) InstanceGroups(namespace string) v1alpha2.InstanceGroupInterface { return &FakeInstanceGroups{c, namespace} } diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/generated_expansion.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/generated_expansion.go index 751d6113e1832..b506a7e10be9b 100644 --- a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/generated_expansion.go +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/generated_expansion.go @@ -20,6 +20,8 @@ package v1alpha2 type ClusterExpansion interface{} +type HostExpansion interface{} + type InstanceGroupExpansion interface{} type KeysetExpansion interface{} diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/host.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/host.go new file mode 100644 index 0000000000000..1a6fe94d6974e --- /dev/null +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/host.go @@ -0,0 +1,178 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1alpha2 "k8s.io/kops/pkg/apis/kops/v1alpha2" + scheme "k8s.io/kops/pkg/client/clientset_generated/clientset/scheme" +) + +// HostsGetter has a method to return a HostInterface. +// A group's client should implement this interface. +type HostsGetter interface { + Hosts(namespace string) HostInterface +} + +// HostInterface has methods to work with Host resources. +type HostInterface interface { + Create(ctx context.Context, host *v1alpha2.Host, opts v1.CreateOptions) (*v1alpha2.Host, error) + Update(ctx context.Context, host *v1alpha2.Host, opts v1.UpdateOptions) (*v1alpha2.Host, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.Host, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.HostList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Host, err error) + HostExpansion +} + +// hosts implements HostInterface +type hosts struct { + client rest.Interface + ns string +} + +// newHosts returns a Hosts +func newHosts(c *KopsV1alpha2Client, namespace string) *hosts { + return &hosts{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *hosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *hosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.HostList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.HostList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *hosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Create(ctx context.Context, host *v1alpha2.Host, opts v1.CreateOptions) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Post(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Update(ctx context.Context, host *v1alpha2.Host, opts v1.UpdateOptions) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Put(). + Namespace(c.ns). + Resource("hosts"). + Name(host.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *hosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *hosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched host. +func (c *hosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("hosts"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/kops_client.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/kops_client.go index a5cb65af0fe18..27c99c1f55e50 100644 --- a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/kops_client.go +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha2/kops_client.go @@ -29,6 +29,7 @@ import ( type KopsV1alpha2Interface interface { RESTClient() rest.Interface ClustersGetter + HostsGetter InstanceGroupsGetter KeysetsGetter SSHCredentialsGetter @@ -43,6 +44,10 @@ func (c *KopsV1alpha2Client) Clusters(namespace string) ClusterInterface { return newClusters(c, namespace) } +func (c *KopsV1alpha2Client) Hosts(namespace string) HostInterface { + return newHosts(c, namespace) +} + func (c *KopsV1alpha2Client) InstanceGroups(namespace string) InstanceGroupInterface { return newInstanceGroups(c, namespace) } diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_host.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_host.go new file mode 100644 index 0000000000000..ba3b8278c951a --- /dev/null +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_host.go @@ -0,0 +1,129 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha3 "k8s.io/kops/pkg/apis/kops/v1alpha3" +) + +// FakeHosts implements HostInterface +type FakeHosts struct { + Fake *FakeKopsV1alpha3 + ns string +} + +var hostsResource = v1alpha3.SchemeGroupVersion.WithResource("hosts") + +var hostsKind = v1alpha3.SchemeGroupVersion.WithKind("Host") + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *FakeHosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(hostsResource, c.ns, name), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *FakeHosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.HostList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(hostsResource, hostsKind, c.ns, opts), &v1alpha3.HostList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha3.HostList{ListMeta: obj.(*v1alpha3.HostList).ListMeta} + for _, item := range obj.(*v1alpha3.HostList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *FakeHosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(hostsResource, c.ns, opts)) + +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Create(ctx context.Context, host *v1alpha3.Host, opts v1.CreateOptions) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(hostsResource, c.ns, host), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Update(ctx context.Context, host *v1alpha3.Host, opts v1.UpdateOptions) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(hostsResource, c.ns, host), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *FakeHosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(hostsResource, c.ns, name, opts), &v1alpha3.Host{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeHosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(hostsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha3.HostList{}) + return err +} + +// Patch applies the patch and returns the patched host. +func (c *FakeHosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(hostsResource, c.ns, name, pt, data, subresources...), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_kops_client.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_kops_client.go index 56d436ae17c23..16a0d3a34281f 100644 --- a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_kops_client.go +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/fake/fake_kops_client.go @@ -32,6 +32,10 @@ func (c *FakeKopsV1alpha3) Clusters(namespace string) v1alpha3.ClusterInterface return &FakeClusters{c, namespace} } +func (c *FakeKopsV1alpha3) Hosts(namespace string) v1alpha3.HostInterface { + return &FakeHosts{c, namespace} +} + func (c *FakeKopsV1alpha3) InstanceGroups(namespace string) v1alpha3.InstanceGroupInterface { return &FakeInstanceGroups{c, namespace} } diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/generated_expansion.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/generated_expansion.go index 7e145c7f39f84..c7f7b8b62484f 100644 --- a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/generated_expansion.go +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/generated_expansion.go @@ -20,6 +20,8 @@ package v1alpha3 type ClusterExpansion interface{} +type HostExpansion interface{} + type InstanceGroupExpansion interface{} type KeysetExpansion interface{} diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/host.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/host.go new file mode 100644 index 0000000000000..6a1f636476748 --- /dev/null +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/host.go @@ -0,0 +1,178 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1alpha3 "k8s.io/kops/pkg/apis/kops/v1alpha3" + scheme "k8s.io/kops/pkg/client/clientset_generated/clientset/scheme" +) + +// HostsGetter has a method to return a HostInterface. +// A group's client should implement this interface. +type HostsGetter interface { + Hosts(namespace string) HostInterface +} + +// HostInterface has methods to work with Host resources. +type HostInterface interface { + Create(ctx context.Context, host *v1alpha3.Host, opts v1.CreateOptions) (*v1alpha3.Host, error) + Update(ctx context.Context, host *v1alpha3.Host, opts v1.UpdateOptions) (*v1alpha3.Host, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.Host, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.HostList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Host, err error) + HostExpansion +} + +// hosts implements HostInterface +type hosts struct { + client rest.Interface + ns string +} + +// newHosts returns a Hosts +func newHosts(c *KopsV1alpha3Client, namespace string) *hosts { + return &hosts{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *hosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *hosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.HostList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha3.HostList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *hosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Create(ctx context.Context, host *v1alpha3.Host, opts v1.CreateOptions) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Post(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Update(ctx context.Context, host *v1alpha3.Host, opts v1.UpdateOptions) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Put(). + Namespace(c.ns). + Resource("hosts"). + Name(host.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *hosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *hosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched host. +func (c *hosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("hosts"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/kops_client.go b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/kops_client.go index 91f15f39b1617..0f9de13d45385 100644 --- a/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/kops_client.go +++ b/pkg/client/clientset_generated/clientset/typed/kops/v1alpha3/kops_client.go @@ -29,6 +29,7 @@ import ( type KopsV1alpha3Interface interface { RESTClient() rest.Interface ClustersGetter + HostsGetter InstanceGroupsGetter KeysetsGetter SSHCredentialsGetter @@ -43,6 +44,10 @@ func (c *KopsV1alpha3Client) Clusters(namespace string) ClusterInterface { return newClusters(c, namespace) } +func (c *KopsV1alpha3Client) Hosts(namespace string) HostInterface { + return newHosts(c, namespace) +} + func (c *KopsV1alpha3Client) InstanceGroups(namespace string) InstanceGroupInterface { return newInstanceGroups(c, namespace) } diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_host.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_host.go new file mode 100644 index 0000000000000..ac1590c2d5722 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_host.go @@ -0,0 +1,129 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha2 "k8s.io/kops/pkg/apis/kops/v1alpha2" +) + +// FakeHosts implements HostInterface +type FakeHosts struct { + Fake *FakeKopsV1alpha2 + ns string +} + +var hostsResource = v1alpha2.SchemeGroupVersion.WithResource("hosts") + +var hostsKind = v1alpha2.SchemeGroupVersion.WithKind("Host") + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *FakeHosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(hostsResource, c.ns, name), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *FakeHosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.HostList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(hostsResource, hostsKind, c.ns, opts), &v1alpha2.HostList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.HostList{ListMeta: obj.(*v1alpha2.HostList).ListMeta} + for _, item := range obj.(*v1alpha2.HostList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *FakeHosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(hostsResource, c.ns, opts)) + +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Create(ctx context.Context, host *v1alpha2.Host, opts v1.CreateOptions) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(hostsResource, c.ns, host), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Update(ctx context.Context, host *v1alpha2.Host, opts v1.UpdateOptions) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(hostsResource, c.ns, host), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *FakeHosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(hostsResource, c.ns, name, opts), &v1alpha2.Host{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeHosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(hostsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.HostList{}) + return err +} + +// Patch applies the patch and returns the patched host. +func (c *FakeHosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(hostsResource, c.ns, name, pt, data, subresources...), &v1alpha2.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Host), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_kops_client.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_kops_client.go index 537e8e23170a4..d84839332f5e2 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_kops_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/fake/fake_kops_client.go @@ -32,6 +32,10 @@ func (c *FakeKopsV1alpha2) Clusters(namespace string) v1alpha2.ClusterInterface return &FakeClusters{c, namespace} } +func (c *FakeKopsV1alpha2) Hosts(namespace string) v1alpha2.HostInterface { + return &FakeHosts{c, namespace} +} + func (c *FakeKopsV1alpha2) InstanceGroups(namespace string) v1alpha2.InstanceGroupInterface { return &FakeInstanceGroups{c, namespace} } diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/generated_expansion.go index 751d6113e1832..b506a7e10be9b 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/generated_expansion.go @@ -20,6 +20,8 @@ package v1alpha2 type ClusterExpansion interface{} +type HostExpansion interface{} + type InstanceGroupExpansion interface{} type KeysetExpansion interface{} diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/host.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/host.go new file mode 100644 index 0000000000000..8ea16258fb584 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/host.go @@ -0,0 +1,178 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1alpha2 "k8s.io/kops/pkg/apis/kops/v1alpha2" + scheme "k8s.io/kops/pkg/client/clientset_generated/internalclientset/scheme" +) + +// HostsGetter has a method to return a HostInterface. +// A group's client should implement this interface. +type HostsGetter interface { + Hosts(namespace string) HostInterface +} + +// HostInterface has methods to work with Host resources. +type HostInterface interface { + Create(ctx context.Context, host *v1alpha2.Host, opts v1.CreateOptions) (*v1alpha2.Host, error) + Update(ctx context.Context, host *v1alpha2.Host, opts v1.UpdateOptions) (*v1alpha2.Host, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.Host, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.HostList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Host, err error) + HostExpansion +} + +// hosts implements HostInterface +type hosts struct { + client rest.Interface + ns string +} + +// newHosts returns a Hosts +func newHosts(c *KopsV1alpha2Client, namespace string) *hosts { + return &hosts{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *hosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *hosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.HostList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.HostList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *hosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Create(ctx context.Context, host *v1alpha2.Host, opts v1.CreateOptions) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Post(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Update(ctx context.Context, host *v1alpha2.Host, opts v1.UpdateOptions) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Put(). + Namespace(c.ns). + Resource("hosts"). + Name(host.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *hosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *hosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched host. +func (c *hosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Host, err error) { + result = &v1alpha2.Host{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("hosts"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/kops_client.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/kops_client.go index 6c33a82b8637d..deb7df52a247e 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/kops_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha2/kops_client.go @@ -29,6 +29,7 @@ import ( type KopsV1alpha2Interface interface { RESTClient() rest.Interface ClustersGetter + HostsGetter InstanceGroupsGetter KeysetsGetter SSHCredentialsGetter @@ -43,6 +44,10 @@ func (c *KopsV1alpha2Client) Clusters(namespace string) ClusterInterface { return newClusters(c, namespace) } +func (c *KopsV1alpha2Client) Hosts(namespace string) HostInterface { + return newHosts(c, namespace) +} + func (c *KopsV1alpha2Client) InstanceGroups(namespace string) InstanceGroupInterface { return newInstanceGroups(c, namespace) } diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_host.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_host.go new file mode 100644 index 0000000000000..ba3b8278c951a --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_host.go @@ -0,0 +1,129 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1alpha3 "k8s.io/kops/pkg/apis/kops/v1alpha3" +) + +// FakeHosts implements HostInterface +type FakeHosts struct { + Fake *FakeKopsV1alpha3 + ns string +} + +var hostsResource = v1alpha3.SchemeGroupVersion.WithResource("hosts") + +var hostsKind = v1alpha3.SchemeGroupVersion.WithKind("Host") + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *FakeHosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(hostsResource, c.ns, name), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *FakeHosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.HostList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(hostsResource, hostsKind, c.ns, opts), &v1alpha3.HostList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha3.HostList{ListMeta: obj.(*v1alpha3.HostList).ListMeta} + for _, item := range obj.(*v1alpha3.HostList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *FakeHosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(hostsResource, c.ns, opts)) + +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Create(ctx context.Context, host *v1alpha3.Host, opts v1.CreateOptions) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(hostsResource, c.ns, host), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *FakeHosts) Update(ctx context.Context, host *v1alpha3.Host, opts v1.UpdateOptions) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(hostsResource, c.ns, host), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *FakeHosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(hostsResource, c.ns, name, opts), &v1alpha3.Host{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeHosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(hostsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha3.HostList{}) + return err +} + +// Patch applies the patch and returns the patched host. +func (c *FakeHosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Host, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(hostsResource, c.ns, name, pt, data, subresources...), &v1alpha3.Host{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Host), err +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_kops_client.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_kops_client.go index 86d5daee22587..a9d34d323e3da 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_kops_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/fake/fake_kops_client.go @@ -32,6 +32,10 @@ func (c *FakeKopsV1alpha3) Clusters(namespace string) v1alpha3.ClusterInterface return &FakeClusters{c, namespace} } +func (c *FakeKopsV1alpha3) Hosts(namespace string) v1alpha3.HostInterface { + return &FakeHosts{c, namespace} +} + func (c *FakeKopsV1alpha3) InstanceGroups(namespace string) v1alpha3.InstanceGroupInterface { return &FakeInstanceGroups{c, namespace} } diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/generated_expansion.go index 7e145c7f39f84..c7f7b8b62484f 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/generated_expansion.go @@ -20,6 +20,8 @@ package v1alpha3 type ClusterExpansion interface{} +type HostExpansion interface{} + type InstanceGroupExpansion interface{} type KeysetExpansion interface{} diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/host.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/host.go new file mode 100644 index 0000000000000..154d41302afe0 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/host.go @@ -0,0 +1,178 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1alpha3 "k8s.io/kops/pkg/apis/kops/v1alpha3" + scheme "k8s.io/kops/pkg/client/clientset_generated/internalclientset/scheme" +) + +// HostsGetter has a method to return a HostInterface. +// A group's client should implement this interface. +type HostsGetter interface { + Hosts(namespace string) HostInterface +} + +// HostInterface has methods to work with Host resources. +type HostInterface interface { + Create(ctx context.Context, host *v1alpha3.Host, opts v1.CreateOptions) (*v1alpha3.Host, error) + Update(ctx context.Context, host *v1alpha3.Host, opts v1.UpdateOptions) (*v1alpha3.Host, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.Host, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.HostList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Host, err error) + HostExpansion +} + +// hosts implements HostInterface +type hosts struct { + client rest.Interface + ns string +} + +// newHosts returns a Hosts +func newHosts(c *KopsV1alpha3Client, namespace string) *hosts { + return &hosts{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the host, and returns the corresponding host object, and an error if there is any. +func (c *hosts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Hosts that match those selectors. +func (c *hosts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.HostList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha3.HostList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested hosts. +func (c *hosts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a host and creates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Create(ctx context.Context, host *v1alpha3.Host, opts v1.CreateOptions) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Post(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a host and updates it. Returns the server's representation of the host, and an error, if there is any. +func (c *hosts) Update(ctx context.Context, host *v1alpha3.Host, opts v1.UpdateOptions) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Put(). + Namespace(c.ns). + Resource("hosts"). + Name(host.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(host). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the host and deletes it. Returns an error if one occurs. +func (c *hosts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *hosts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("hosts"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched host. +func (c *hosts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Host, err error) { + result = &v1alpha3.Host{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("hosts"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/kops_client.go b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/kops_client.go index 1994f480e23c0..9708c3c5c99df 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/kops_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/kops/v1alpha3/kops_client.go @@ -29,6 +29,7 @@ import ( type KopsV1alpha3Interface interface { RESTClient() rest.Interface ClustersGetter + HostsGetter InstanceGroupsGetter KeysetsGetter SSHCredentialsGetter @@ -43,6 +44,10 @@ func (c *KopsV1alpha3Client) Clusters(namespace string) ClusterInterface { return newClusters(c, namespace) } +func (c *KopsV1alpha3Client) Hosts(namespace string) HostInterface { + return newHosts(c, namespace) +} + func (c *KopsV1alpha3Client) InstanceGroups(namespace string) InstanceGroupInterface { return newInstanceGroups(c, namespace) } diff --git a/upup/pkg/fi/nodeup/command.go b/upup/pkg/fi/nodeup/command.go index 1388cc33a7330..c511f643c90fa 100644 --- a/upup/pkg/fi/nodeup/command.go +++ b/upup/pkg/fi/nodeup/command.go @@ -45,6 +45,7 @@ import ( "k8s.io/kops/pkg/apis/nodeup" "k8s.io/kops/pkg/assets" "k8s.io/kops/pkg/bootstrap" + "k8s.io/kops/pkg/bootstrap/pkibootstrap" "k8s.io/kops/pkg/configserver" "k8s.io/kops/pkg/kopscontrollerclient" "k8s.io/kops/pkg/resolver" @@ -654,6 +655,14 @@ func getNodeConfigFromServers(ctx context.Context, bootConfig *nodeup.BootConfig return nil, err } authenticator = a + + case "metal": + a, err := pkibootstrap.NewAuthenticatorFromFile("/etc/kubernetes/kops/pki/machine/private.pem") + if err != nil { + return nil, err + } + authenticator = a + default: return nil, fmt.Errorf("unsupported cloud provider for node configuration %s", bootConfig.CloudProvider) }