diff --git a/pkg/server/api.go b/pkg/server/api.go index 16dc150dc6..e0a50255a7 100644 --- a/pkg/server/api.go +++ b/pkg/server/api.go @@ -7,6 +7,8 @@ import ( "path" "github.com/golang/glog" + + ignv2_2types "github.com/coreos/ignition/config/v2_2/types" ) type poolRequest struct { @@ -29,6 +31,9 @@ type APIServer struct { func NewAPIServer(a *APIHandler, p int, is bool, c, k string) *APIServer { mux := http.NewServeMux() mux.Handle("/config/", a) + mux.Handle("/pointerconfig/", &pointerHandler { + server: a.server, + }) mux.Handle("/healthz", &healthHandler{}) mux.Handle("/", &defaultHandler{}) @@ -61,6 +66,35 @@ func (a *APIServer) Serve() { } } +func completeIgnitionRequest(w http.ResponseWriter, r *http.Request, conf *ignv2_2types.Config) { + if conf == nil { + w.Header().Set("Content-Length", "0") + w.WriteHeader(http.StatusNotFound) + return + } + + data, err := json.Marshal(conf) + if err != nil { + w.Header().Set("Content-Length", "0") + w.WriteHeader(http.StatusInternalServerError) + glog.Errorf("failed to marshal config: %v", err) + return + } + + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data))) + w.Header().Set("Content-Type", "application/json") + if r.Method == http.MethodHead { + w.WriteHeader(http.StatusOK) + return + } + + _, err = w.Write(data) + if err != nil { + glog.Errorf("failed to write response: %v", err) + } +} + + // APIHandler is the HTTP Handler for the // Machine Config Server. type APIHandler struct { @@ -101,31 +135,42 @@ func (sh *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { glog.Errorf("couldn't get config for req: %v, error: %v", cr, err) return } - if conf == nil && err == nil { + completeIgnitionRequest(w, r, conf) +} + +// pointerHandler is the HTTP Handler for the +// Machine Config Server. +type pointerHandler struct { + server Server +} + +// ServeHTTP handles the requests for the machine config server +// API handler. +func (sh *pointerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet && r.Method != http.MethodHead { w.Header().Set("Content-Length", "0") - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusMethodNotAllowed) return } - data, err := json.Marshal(conf) - if err != nil { + if r.URL.Path == "" { w.Header().Set("Content-Length", "0") - w.WriteHeader(http.StatusInternalServerError) - glog.Errorf("failed to marshal %v config: %v", cr, err) + w.WriteHeader(http.StatusBadRequest) return } - w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data))) - w.Header().Set("Content-Type", "application/json") - if r.Method == http.MethodHead { - w.WriteHeader(http.StatusOK) - return + cr := poolRequest{ + machineConfigPool: path.Base(r.URL.Path), } - _, err = w.Write(data) + conf, err := sh.server.GetPointerConfig(cr) if err != nil { - glog.Errorf("failed to write %v response: %v", cr, err) + w.Header().Set("Content-Length", "0") + w.WriteHeader(http.StatusInternalServerError) + glog.Errorf("couldn't get config for req: %v, error: %v", cr, err) + return } + completeIgnitionRequest(w, r, conf) } type healthHandler struct{} diff --git a/pkg/server/api_test.go b/pkg/server/api_test.go index edb9a4be76..7871b28431 100644 --- a/pkg/server/api_test.go +++ b/pkg/server/api_test.go @@ -1,6 +1,7 @@ package server import ( + "errors" "fmt" "io/ioutil" "net/http" @@ -17,6 +18,9 @@ type mockServer struct { func (ms *mockServer) GetConfig(pr poolRequest) (*ignv2_2types.Config, error) { return ms.GetConfigFn(pr) } +func (ms *mockServer) GetPointerConfig(pr poolRequest) (*ignv2_2types.Config, error) { + return nil, errors.New("Not implemented") +} type checkResponse func(t *testing.T, response *http.Response) diff --git a/pkg/server/bootstrap_server.go b/pkg/server/bootstrap_server.go index e394ea6e32..05b529606e 100644 --- a/pkg/server/bootstrap_server.go +++ b/pkg/server/bootstrap_server.go @@ -39,6 +39,11 @@ func NewBootstrapServer(dir, kubeconfig string) (Server, error) { }, nil } +// GetPointerConfig is not implemented yet - currently the installer generates this +func (bsc *bootstrapServer) GetPointerConfig(cr poolRequest) (*ignv2_2types.Config, error) { + return nil, fmt.Errorf("Not implemented") +} + // GetConfig fetches the machine config(type - Ignition) from the bootstrap server, // based on the pool request. // It returns nil for conf, error if the config isn't found. It returns a formatted diff --git a/pkg/server/cluster_server.go b/pkg/server/cluster_server.go index 9cd0db4002..d7185d13e7 100644 --- a/pkg/server/cluster_server.go +++ b/pkg/server/cluster_server.go @@ -3,9 +3,11 @@ package server import ( "fmt" "io/ioutil" + "net/url" "path/filepath" - ignv2_2types "github.com/coreos/ignition/config/v2_2/types" + ignition "github.com/coreos/ignition/config/v2_2/types" + "github.com/vincent-petithory/dataurl" yaml "github.com/ghodss/yaml" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,6 +31,11 @@ const ( var _ = Server(&clusterServer{}) type clusterServer struct { + // apiServerHost is the hostname of the API server + apiServerHost string + // rootCA is the root CA + rootCA []byte + // machineClient is used to interact with the // machine config, pool objects. machineClient v1.MachineconfigurationV1Interface @@ -48,16 +55,55 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) { return nil, fmt.Errorf("Failed to create Kubernetes rest client: %v", err) } + rootCA, err := ioutil.ReadFile("/run/secrets/kubernetes.io/serviceaccount/ca.crt") + if err != nil { + return nil, err + } + + apiURL, err := url.Parse(apiserverURL) + if err != nil { + return nil, err + } + mc := v1.NewForConfigOrDie(restConfig) return &clusterServer{ + apiServerHost: apiURL.Hostname(), + rootCA: rootCA, machineClient: mc, kubeconfigFunc: func() ([]byte, []byte, error) { return kubeconfigFromSecret(bootstrapTokenDir, apiserverURL) }, }, nil } +// GetPointerConfig ends up as e.g. user-data in AWS. +func (cs *clusterServer) GetPointerConfig(cr poolRequest) (*ignition.Config, error) { + return &ignition.Config{ + Ignition: ignition.Ignition{ + Version: ignition.MaxVersion.String(), + Config: ignition.IgnitionConfig{ + Append: []ignition.ConfigReference{{ + Source: func() *url.URL { + return &url.URL{ + Scheme: "https", + Host: fmt.Sprintf("%s:22623", cs.apiServerHost), + Path: fmt.Sprintf("/config/%s", cr.machineConfigPool), + } + }().String(), + }}, + }, + Security: ignition.Security{ + TLS: ignition.TLS{ + CertificateAuthorities: []ignition.CaReference{{ + Source: dataurl.EncodeBytes([]byte(cs.rootCA)), + }}, + }, + }, + }, + }, nil +} + // GetConfig fetches the machine config(type - Ignition) from the cluster, // based on the pool request. -func (cs *clusterServer) GetConfig(cr poolRequest) (*ignv2_2types.Config, error) { +func (cs *clusterServer) GetConfig(cr poolRequest) (*ignition.Config, error) { mp, err := cs.machineClient.MachineConfigPools().Get(cr.machineConfigPool, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("could not fetch pool. err: %v", err) diff --git a/pkg/server/server.go b/pkg/server/server.go index 776252a437..ad4d4aef39 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -34,6 +34,7 @@ type appenderFunc func(*ignv2_2types.Config) error // Server defines the interface that is implemented by different // machine config server implementations. type Server interface { + GetPointerConfig(poolRequest) (*ignv2_2types.Config, error) GetConfig(poolRequest) (*ignv2_2types.Config, error) }