Skip to content

Commit

Permalink
server: Add /pointerconfig API
Browse files Browse the repository at this point in the history
This is prep for fixing
openshift#683

Today the installer generates this "pointer Ignition" which ends up
as e.g. user-data in AWS, which is just a pair of (MCS URL, root CA).
We need to do this because of size limitations on AWS user data.

It makes a lot of sense for the MCO to be in control of generating
the pointer ignition config too, as it helps centralize knowledge
of Ignition.

Ultimately, the goal here is tighter integration between machineAPI
and the the MCO; e.g. the machineAPI asks to generate the pointer
Ignition config, rather than having it stored as a static secret.
This could then unlock the ability for the MCO to inject e.g.
one time auth tokens.
  • Loading branch information
cgwalters committed May 15, 2019
1 parent d489e70 commit fbfe53f
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 15 deletions.
71 changes: 58 additions & 13 deletions pkg/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"path"

"github.com/golang/glog"

ignv2_2types "github.com/coreos/ignition/config/v2_2/types"
)

type poolRequest struct {
Expand All @@ -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{})

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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{}
Expand Down
4 changes: 4 additions & 0 deletions pkg/server/api_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"errors"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -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)

Expand Down
5 changes: 5 additions & 0 deletions pkg/server/bootstrap_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 48 additions & 2 deletions pkg/server/cluster_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down

0 comments on commit fbfe53f

Please sign in to comment.