diff --git a/cmd/dkl-dir2config/render-context.go b/cmd/dkl-dir2config/render-context.go index 8ba0333..7544f19 100644 --- a/cmd/dkl-dir2config/render-context.go +++ b/cmd/dkl-dir2config/render-context.go @@ -160,6 +160,10 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]interface{}) map[strin } return map[string]interface{}{ + "password": func(name string) (s string) { + return fmt.Sprintf("{{ password %q %q }}", cluster, name) + }, + "token": func(name string) (s string) { return fmt.Sprintf("{{ token %q %q }}", cluster, name) }, diff --git a/cmd/dkl-local-server/render-context.go b/cmd/dkl-local-server/render-context.go index 6b0ecf8..097bfea 100644 --- a/cmd/dkl-local-server/render-context.go +++ b/cmd/dkl-local-server/render-context.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "fmt" "io" "log" "net/http" @@ -135,6 +136,14 @@ func (ctx *renderContext) templateFuncs() map[string]interface{} { } return map[string]interface{}{ + "password": func(cluster, name string) (password string, err error) { + password = secretData.Password(cluster, name) + if len(password) == 0 { + err = fmt.Errorf("password %q not defined for cluster %q", name, cluster) + } + return + }, + "token": func(cluster, name string) (s string, err error) { return secretData.Token(cluster, name) }, diff --git a/cmd/dkl-local-server/secrets.go b/cmd/dkl-local-server/secrets.go index e0b2a73..167b809 100644 --- a/cmd/dkl-local-server/secrets.go +++ b/cmd/dkl-local-server/secrets.go @@ -10,6 +10,7 @@ import ( "net" "os" "path/filepath" + "sort" "sync" "github.com/cloudflare/cfssl/config" @@ -36,8 +37,9 @@ type SecretData struct { } type ClusterSecrets struct { - CAs map[string]*CA - Tokens map[string]string + CAs map[string]*CA + Tokens map[string]string + Passwords map[string]string } type CA struct { @@ -101,6 +103,14 @@ func (sd *SecretData) Save() error { return ioutil.WriteFile(secretDataPath(), ba, 0600) } +func newClusterSecrets() *ClusterSecrets { + return &ClusterSecrets{ + CAs: make(map[string]*CA), + Tokens: make(map[string]string), + Passwords: make(map[string]string), + } +} + func (sd *SecretData) cluster(name string) (cs *ClusterSecrets) { cs, ok := sd.clusters[name] if ok { @@ -112,15 +122,47 @@ func (sd *SecretData) cluster(name string) (cs *ClusterSecrets) { log.Info("secret-data: new cluster: ", name) - cs = &ClusterSecrets{ - CAs: make(map[string]*CA), - Tokens: make(map[string]string), - } + cs = newClusterSecrets() sd.clusters[name] = cs sd.changed = true return } +func (sd *SecretData) Passwords(cluster string) (passwords []string) { + cs := sd.cluster(cluster) + + passwords = make([]string, 0, len(cs.Passwords)) + for name := range cs.Passwords { + passwords = append(passwords, name) + } + + sort.Strings(passwords) + + return +} + +func (sd *SecretData) Password(cluster, name string) (password string) { + cs := sd.cluster(cluster) + + if cs.Passwords == nil { + cs.Passwords = make(map[string]string) + } + + password = cs.Passwords[name] + return +} + +func (sd *SecretData) SetPassword(cluster, name, password string) { + cs := sd.cluster(cluster) + + if cs.Passwords == nil { + cs.Passwords = make(map[string]string) + } + + cs.Passwords[name] = password + sd.changed = true +} + func (sd *SecretData) Token(cluster, name string) (token string, err error) { cs := sd.cluster(cluster) diff --git a/cmd/dkl-local-server/ws-clusters.go b/cmd/dkl-local-server/ws-clusters.go index 8f4913a..262b0cd 100644 --- a/cmd/dkl-local-server/ws-clusters.go +++ b/cmd/dkl-local-server/ws-clusters.go @@ -61,3 +61,42 @@ func wsClusterAddons(req *restful.Request, resp *restful.Response) { resp.Write([]byte(cluster.Addons)) } + +func wsClusterPasswords(req *restful.Request, resp *restful.Response) { + cluster := wsReadCluster(req, resp) + if cluster == nil { + return + } + + resp.WriteEntity(secretData.Passwords(cluster.Name)) +} +func wsClusterPassword(req *restful.Request, resp *restful.Response) { + cluster := wsReadCluster(req, resp) + if cluster == nil { + return + } + + name := req.PathParameter("password-name") + + resp.WriteEntity(secretData.Password(cluster.Name, name)) +} +func wsClusterSetPassword(req *restful.Request, resp *restful.Response) { + cluster := wsReadCluster(req, resp) + if cluster == nil { + return + } + + name := req.PathParameter("password-name") + + var password string + if err := req.ReadEntity(&password); err != nil { + wsError(resp, err) // FIXME this is a BadRequest + return + } + + secretData.SetPassword(cluster.Name, name, password) + + if err := secretData.Save(); err != nil { + wsError(resp, err) + } +} diff --git a/cmd/dkl-local-server/ws.go b/cmd/dkl-local-server/ws.go index 289ce6f..d5b3b85 100644 --- a/cmd/dkl-local-server/ws.go +++ b/cmd/dkl-local-server/ws.go @@ -31,6 +31,13 @@ func buildWS() *restful.WebService { Returns(http.StatusOK, "OK", nil). Returns(http.StatusNotFound, "The cluster does not exists or does not have addons defined", nil)) + ws.Route(ws.GET("/clusters/{cluster-name}/passwords").Filter(adminAuth).To(wsClusterPasswords). + Doc("List cluster's passwords")) + ws.Route(ws.GET("/clusters/{cluster-name}/passwords/{password-name}").Filter(adminAuth).To(wsClusterPassword). + Doc("Get cluster's password")) + ws.Route(ws.PUT("/clusters/{cluster-name}/passwords/{password-name}").Filter(adminAuth).To(wsClusterSetPassword). + Doc("Set cluster's password")) + // hosts API ws.Route(ws.GET("/hosts").Filter(hostsAuth).To(wsListHosts). Doc("List hosts"))