Skip to content

Commit

Permalink
Send bootstrap query from nodeup to kops-controller
Browse files Browse the repository at this point in the history
  • Loading branch information
johngmyers committed Jul 30, 2020
1 parent 3f52eaa commit 73041ab
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 5 deletions.
7 changes: 6 additions & 1 deletion cmd/kops-controller/pkg/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@ go_library(
srcs = ["server.go"],
importpath = "k8s.io/kops/cmd/kops-controller/pkg/server",
visibility = ["//visibility:public"],
deps = ["//cmd/kops-controller/pkg/config:go_default_library"],
deps = [
"//cmd/kops-controller/pkg/config:go_default_library",
"//pkg/apis/nodeup:go_default_library",
"//vendor/github.com/gorilla/mux:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
62 changes: 60 additions & 2 deletions cmd/kops-controller/pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ package server

import (
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"runtime/debug"

"github.com/gorilla/mux"
"k8s.io/klog"
"k8s.io/kops/cmd/kops-controller/pkg/config"
"k8s.io/kops/pkg/apis/nodeup"
)

type Server struct {
Expand All @@ -36,12 +42,64 @@ func NewServer(opt *config.Options) (*Server, error) {
PreferServerCipherSuites: true,
},
}
return &Server{

s := &Server{
opt: opt,
server: server,
}, nil
}
r := mux.NewRouter()
r.Handle("/bootstrap", http.HandlerFunc(s.bootstrap))
server.Handler = recovery(r)

return s, nil
}

func (s *Server) Start() error {
return s.server.ListenAndServeTLS(s.opt.Server.ServerCertificatePath, s.opt.Server.ServerKeyPath)
}

func (s *Server) bootstrap(w http.ResponseWriter, r *http.Request) {
if r.Body == nil {
klog.Infof("bootstrap %s no body", r.RemoteAddr)
w.WriteHeader(http.StatusBadRequest)
return
}

// TODO: authenticate request

req := &nodeup.BootstrapRequest{}
err := json.NewDecoder(r.Body).Decode(req)
if err != nil {
klog.Infof("bootstrap %s decode err: %v", r.RemoteAddr, err)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("failed to decode: %v", err)))
return
}

if req.APIVersion != nodeup.BootstrapAPIVersion {
klog.Infof("bootstrap %s wrong APIVersion", r.RemoteAddr)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("unexpected APIVersion"))
return
}

w.Header().Set("Content-Type", "application/json")
resp := &nodeup.BootstrapResponse{}
_ = json.NewEncoder(w).Encode(resp)
klog.Infof("bootstrap %s success", r.RemoteAddr)
}

// recovery is responsible for ensuring we don't exit on a panic.
func recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
w.WriteHeader(http.StatusInternalServerError)

klog.Errorf("failed to handle request: threw exception: %v: %s", err, debug.Stack())
}
}()

next.ServeHTTP(w, req)
})
}
1 change: 1 addition & 0 deletions nodeup/pkg/model/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"architecture.go",
"bootstrap_client.go",
"cloudconfig.go",
"containerd.go",
"context.go",
Expand Down
46 changes: 46 additions & 0 deletions nodeup/pkg/model/bootstrap_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
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 model

import (
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
)

// SecretBuilder writes secrets
type BootstrapClientBuilder struct {
*NodeupModelContext
}

func (b BootstrapClientBuilder) Build(c *fi.ModelBuilderContext) error {
if b.IsMaster || !b.UseKopsControllerForNodeBootstrap() {
return nil
}

cert, err := b.GetCert(fi.CertificateIDCA)
if err != nil {
return err
}

bootstrapClient := &nodetasks.BootstrapClient{
CA: cert,
}
c.AddTask(bootstrapClient)
return nil
}

var _ fi.ModelBuilder = &BootstrapClientBuilder{}
5 changes: 4 additions & 1 deletion pkg/apis/nodeup/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = ["config.go"],
srcs = [
"bootstrap.go",
"config.go",
],
importpath = "k8s.io/kops/pkg/apis/nodeup",
visibility = ["//visibility:public"],
deps = [
Expand Down
29 changes: 29 additions & 0 deletions pkg/apis/nodeup/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
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 nodeup

const BootstrapAPIVersion = "bootstrap.kops.k8s.io/v1alpha1"

// BootstrapRequest is a request from nodeup to kops-controller for bootstrapping a node.
type BootstrapRequest struct {
// APIVersion defines the versioned schema of this representation of a request.
APIVersion string `json:"apiVersion"`
}

// BootstrapRespose is a response to a BootstrapRequest.
type BootstrapResponse struct {
}
3 changes: 2 additions & 1 deletion upup/pkg/fi/nodeup/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
}

loader := &Loader{}
loader.Builders = append(loader.Builders, &model.BootstrapClientBuilder{NodeupModelContext: modelContext})
loader.Builders = append(loader.Builders, &model.NTPBuilder{NodeupModelContext: modelContext})
loader.Builders = append(loader.Builders, &model.MiscUtilsBuilder{NodeupModelContext: modelContext})
loader.Builders = append(loader.Builders, &model.DirectoryBuilder{NodeupModelContext: modelContext})
Expand Down Expand Up @@ -305,7 +306,7 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
return fmt.Errorf("unsupported target type %q", c.Target)
}

context, err := fi.NewContext(target, nil, cloud, keyStore, secretStore, configBase, checkExisting, taskMap)
context, err := fi.NewContext(target, c.cluster, cloud, keyStore, secretStore, configBase, checkExisting, taskMap)
if err != nil {
klog.Exitf("error building context: %v", err)
}
Expand Down
3 changes: 3 additions & 0 deletions upup/pkg/fi/nodeup/nodetasks/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go_library(
srcs = [
"archive.go",
"bindmount.go",
"bootstrap_client.go",
"chattr.go",
"createsdir.go",
"file.go",
Expand All @@ -21,9 +22,11 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/kops:go_default_library",
"//pkg/apis/nodeup:go_default_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",
Expand Down
123 changes: 123 additions & 0 deletions upup/pkg/fi/nodeup/nodetasks/bootstrap_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
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 nodetasks

import (
"bufio"
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"

"k8s.io/kops/pkg/apis/nodeup"
"k8s.io/kops/pkg/wellknownports"
"k8s.io/kops/upup/pkg/fi"
)

type BootstrapClient struct {
// CA is the CA certificate for kops-controller.
CA []byte

client *http.Client
}

var _ fi.Task = &BootstrapClient{}
var _ fi.HasName = &BootstrapClient{}

func (b *BootstrapClient) GetName() *string {
name := "BootstrapClient"
return &name
}

func (b *BootstrapClient) String() string {
return "BootstrapClient"
}

func (b *BootstrapClient) Run(c *fi.Context) error {
req := nodeup.BootstrapRequest{
APIVersion: nodeup.BootstrapAPIVersion,
}

err := b.queryBootstrap(c, req)
if err != nil {
return err
}

return nil
}

func (b *BootstrapClient) queryBootstrap(c *fi.Context, req nodeup.BootstrapRequest) error {
if b.client == nil {
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(b.CA)

b.client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
MinVersion: tls.VersionTLS12,
},
},
}
}

reqBytes, err := json.Marshal(req)
if err != nil {
return err
}

bootstrapUrl := url.URL{
Scheme: "https",
Host: net.JoinHostPort(c.Cluster.Spec.MasterInternalName, strconv.Itoa(wellknownports.KopsControllerPort)),
Path: "/bootstrap",
}
resp, err := b.client.Post(bootstrapUrl.String(), "application/json", bytes.NewReader(reqBytes))
if err != nil {
return err
}

if resp.StatusCode != http.StatusOK {
detail := ""
if resp.Body != nil {
scanner := bufio.NewScanner(resp.Body)
if scanner.Scan() {
detail = scanner.Text()
}
_ = resp.Body.Close()
}
return fmt.Errorf("bootstrap returned status code %d: %s", resp.StatusCode, detail)
}

var bootstrapResp nodeup.BootstrapResponse
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}

err = json.Unmarshal(body, &bootstrapResp)
if err != nil {
return err
}

return nil
}

0 comments on commit 73041ab

Please sign in to comment.