mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 04:27:54 +00:00
Refactor and provide JSON file loaders for endpoints and tokens
This commit is contained in:
parent
d17a236af3
commit
e3278d965a
@ -21,19 +21,23 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Make sure the CA cert for the cluster exists and is readable.
|
||||
// We are expecting a base64 encoded version of the cert PEM as this is how
|
||||
// the cert would most likely be provided via kubernetes secrets.
|
||||
if _, err := os.Stat(kd.CAPath); os.IsNotExist(err) {
|
||||
log.Fatalf("CA does not exist: %s", kd.CAPath)
|
||||
// Make sure we can load critical files, and be nice to the user by
|
||||
// printing descriptive error message when we fail.
|
||||
for desc, path := range map[string]string{
|
||||
"root CA certificate": kd.CAPath,
|
||||
"token map file": kd.TokenMapPath,
|
||||
"list of API endpoints": kd.EndpointListPath,
|
||||
} {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
log.Fatalf("%s does not exist: %s", desc, path)
|
||||
}
|
||||
// Test read permissions
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to open %s (%q [%s])", desc, path, err)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
// Test read permissions
|
||||
file, err := os.Open(kd.CAPath)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: Unable to read %s", kd.CAPath)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
router := kd.NewRouter()
|
||||
log.Printf("Listening for requests on port 9898.")
|
||||
|
@ -14,45 +14,23 @@ package kubediscovery
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/square/go-jose"
|
||||
)
|
||||
|
||||
// TODO: Just using a hardcoded token for now.
|
||||
const tempTokenId string = "TOKENID"
|
||||
const tempToken string = "EF1BA4F26DDA9FE2"
|
||||
const secretPath = "/tmp/secret"
|
||||
|
||||
// CAPath is the expected location of our cluster's CA to be distributed to
|
||||
// clients looking to connect. Because we expect to use kubernetes secrets
|
||||
// for the time being, this file is expected to be a base64 encoded version
|
||||
// of the normal cert PEM.
|
||||
const CAPath = "/tmp/secret/ca.pem"
|
||||
|
||||
// tokenLoader is an interface for abstracting how we validate
|
||||
// token IDs and lookup their corresponding token.
|
||||
type tokenLoader interface {
|
||||
// Lookup returns the token for a given token ID, or an error if the token ID
|
||||
// does not exist. Both token and it's ID are expected to be hex encoded strings.
|
||||
Lookup(tokenId string) (string, error)
|
||||
}
|
||||
|
||||
type hardcodedTokenLoader struct {
|
||||
}
|
||||
|
||||
func (tl *hardcodedTokenLoader) Lookup(tokenId string) (string, error) {
|
||||
if tokenId == tempTokenId {
|
||||
return tempToken, nil
|
||||
}
|
||||
return "", errors.New(fmt.Sprintf("invalid token: %s", tokenId))
|
||||
}
|
||||
const CAPath = secretPath + "/ca.pem"
|
||||
|
||||
// caLoader is an interface for abstracting how we load the CA certificates
|
||||
// for the cluster.
|
||||
@ -63,42 +41,94 @@ type caLoader interface {
|
||||
// fsCALoader is a caLoader for loading the PEM encoded CA from
|
||||
// /tmp/secret/ca.pem.
|
||||
type fsCALoader struct {
|
||||
certData string
|
||||
}
|
||||
|
||||
func (cl *fsCALoader) LoadPEM() (string, error) {
|
||||
file, err := os.Open(CAPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if cl.certData != "" {
|
||||
data, err := ioutil.ReadFile(CAPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cl.certData = base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cl.certData, nil
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
const TokenMapPath = secretPath + "/token-map.json"
|
||||
const EndpointListPath = secretPath + "/endpoint-list.json"
|
||||
|
||||
// tokenLoader is an interface for abstracting how we validate
|
||||
// token IDs and lookup their corresponding token.
|
||||
type tokenLoader interface {
|
||||
// Lookup returns the token for a given token ID, or an error if the token ID
|
||||
// does not exist. Both token and it's ID are expected be strings.
|
||||
LoadAndLookup(tokenID string) (string, error)
|
||||
}
|
||||
|
||||
type jsonFileTokenLoader struct {
|
||||
tokenMap map[string]string
|
||||
}
|
||||
|
||||
func (tl *jsonFileTokenLoader) LoadAndLookup(tokenID string) (string, error) {
|
||||
if len(tl.tokenMap) == 0 {
|
||||
data, err := ioutil.ReadFile(TokenMapPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := json.Unmarshal(data, &tl.tokenMap); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if val, ok := tl.tokenMap[tokenID]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return "", errors.New(fmt.Sprintf("invalid token: %s", tokenID))
|
||||
}
|
||||
|
||||
type endpointsLoader interface {
|
||||
LoadList() ([]string, error)
|
||||
}
|
||||
|
||||
type jsonFileEnpointsLoader struct {
|
||||
endpoints []string
|
||||
}
|
||||
|
||||
func (el *jsonFileEnpointsLoader) LoadList() ([]string, error) {
|
||||
if len(el.endpoints) == 0 {
|
||||
data, err := ioutil.ReadFile(EndpointListPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(data, &el.endpoints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return el.endpoints, nil
|
||||
}
|
||||
|
||||
// ClusterInfoHandler implements the http.ServeHTTP method and allows us to
|
||||
// mock out portions of the request handler in tests.
|
||||
type ClusterInfoHandler struct {
|
||||
tokenLoader tokenLoader
|
||||
caLoader caLoader
|
||||
tokenLoader tokenLoader
|
||||
caLoader caLoader
|
||||
endpointsLoader endpointsLoader
|
||||
}
|
||||
|
||||
func NewClusterInfoHandler() *ClusterInfoHandler {
|
||||
tl := hardcodedTokenLoader{}
|
||||
cl := fsCALoader{}
|
||||
return &ClusterInfoHandler{
|
||||
tokenLoader: &tl,
|
||||
caLoader: &cl,
|
||||
tokenLoader: &jsonFileTokenLoader{},
|
||||
caLoader: &fsCALoader{},
|
||||
endpointsLoader: &jsonFileEnpointsLoader{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
tokenId := req.FormValue("token-id")
|
||||
log.Printf("Got token ID: %s", tokenId)
|
||||
token, err := cih.tokenLoader.Lookup(tokenId)
|
||||
tokenID := req.FormValue("token-id")
|
||||
log.Printf("Got token ID: %s", tokenID)
|
||||
token, err := cih.tokenLoader.LoadAndLookup(tokenID)
|
||||
if err != nil {
|
||||
log.Printf("Invalid token: %s", err)
|
||||
http.Error(resp, "Forbidden", http.StatusForbidden)
|
||||
@ -106,32 +136,42 @@ func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
|
||||
}
|
||||
log.Printf("Loaded token: %s", token)
|
||||
|
||||
// TODO probably should not leak server-side errors to the client
|
||||
caPEM, err := cih.caLoader.LoadPEM()
|
||||
caB64 := base64.StdEncoding.EncodeToString([]byte(caPEM))
|
||||
|
||||
if err != nil {
|
||||
http.Error(resp, "Error encoding CA", http.StatusInternalServerError)
|
||||
err = fmt.Errorf("Error loading root CA certificate data: %s", err)
|
||||
log.Println(err)
|
||||
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
endpoints, err := cih.endpointsLoader.LoadList()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error loading list of API endpoints: %s", err)
|
||||
log.Println(err)
|
||||
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
clusterInfo := ClusterInfo{
|
||||
Type: "ClusterInfo",
|
||||
Version: "v1",
|
||||
RootCertificates: caB64,
|
||||
CertificateAuthorities: []string{caPEM},
|
||||
Endpoints: endpoints,
|
||||
}
|
||||
|
||||
// Instantiate an signer using HMAC-SHA256.
|
||||
hmacTestKey := fromHexBytes(token)
|
||||
signer, err := jose.NewSigner(jose.HS256, hmacTestKey)
|
||||
signer, err := jose.NewSigner(jose.HS256, []byte(token))
|
||||
if err != nil {
|
||||
http.Error(resp, fmt.Sprintf("Error creating JWS signer: %s", err), http.StatusInternalServerError)
|
||||
err = fmt.Errorf("Error creating JWS signer: %s", err)
|
||||
log.Println(err)
|
||||
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(clusterInfo)
|
||||
if err != nil {
|
||||
http.Error(resp, fmt.Sprintf("Error serializing clusterInfo to JSON: %s", err),
|
||||
http.StatusInternalServerError)
|
||||
err = fmt.Errorf("Error serializing clusterInfo to JSON: %s", err)
|
||||
log.Println(err)
|
||||
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -140,8 +180,9 @@ func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
|
||||
// indicate a problem in an underlying cryptographic primitive.
|
||||
jws, err := signer.Sign(payload)
|
||||
if err != nil {
|
||||
http.Error(resp, fmt.Sprintf("Error signing clusterInfo to JSON: %s", err),
|
||||
http.StatusInternalServerError)
|
||||
err = fmt.Errorf("Error signing clusterInfo with JWS: %s", err)
|
||||
log.Println(err)
|
||||
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -153,13 +194,3 @@ func (cih *ClusterInfoHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
|
||||
resp.Write([]byte(serialized))
|
||||
|
||||
}
|
||||
|
||||
// TODO: Move into test package
|
||||
// TODO: Should we use base64 instead?
|
||||
func fromHexBytes(base16 string) []byte {
|
||||
val, err := hex.DecodeString(base16)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Invalid test data: %s", err))
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
@ -12,10 +12,9 @@ limitations under the License.
|
||||
*/
|
||||
package kubediscovery
|
||||
|
||||
// TODO: Sync with kubeadm api type
|
||||
type ClusterInfo struct {
|
||||
Type string
|
||||
Version string
|
||||
RootCertificates string `json:"rootCertificates"`
|
||||
// TODO: ClusterID, Endpoints
|
||||
// TODO Kind, apiVersion
|
||||
// TODO clusterId, fetchedTime, expiredTime
|
||||
CertificateAuthorities []string `json:"certificateAuthorities,omitempty"`
|
||||
Endpoints []string `json:"endpoints,omitempty"`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user