local-server/cmd/dkl-local-server/render-context.go

309 lines
6.2 KiB
Go
Raw Normal View History

2018-06-12 10:09:47 +00:00
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
2018-06-16 11:45:27 +00:00
"encoding/json"
2019-04-13 09:36:58 +00:00
"fmt"
2018-12-10 10:59:24 +00:00
"io"
2018-06-12 10:09:47 +00:00
"log"
2018-12-10 10:59:24 +00:00
"net/http"
2018-06-19 06:49:09 +00:00
"path"
2018-06-12 10:09:47 +00:00
"path/filepath"
2018-12-10 13:44:05 +00:00
"text/template"
2018-06-12 10:09:47 +00:00
2018-06-16 12:04:58 +00:00
cfsslconfig "github.com/cloudflare/cfssl/config"
2018-06-16 11:45:27 +00:00
"github.com/cloudflare/cfssl/csr"
2018-06-12 10:09:47 +00:00
yaml "gopkg.in/yaml.v2"
2018-06-16 12:04:58 +00:00
2018-06-12 10:09:47 +00:00
"novit.nc/direktil/pkg/config"
2018-12-10 10:59:24 +00:00
"novit.nc/direktil/pkg/localconfig"
2018-06-12 10:09:47 +00:00
)
type renderContext struct {
2018-12-10 10:59:24 +00:00
Host *localconfig.Host
SSLConfig string
2018-06-12 10:09:47 +00:00
}
2018-12-10 10:59:24 +00:00
func renderCtx(w http.ResponseWriter, r *http.Request, ctx *renderContext, what string,
create func(out io.Writer, ctx *renderContext) error) error {
log.Printf("sending %s for %q", what, ctx.Host.Name)
2018-07-03 07:35:52 +00:00
2018-12-10 10:59:24 +00:00
tag, err := ctx.Tag()
if err != nil {
return err
2018-07-03 07:35:52 +00:00
}
2018-06-12 10:09:47 +00:00
2018-12-10 10:59:24 +00:00
// get it or create it
content, meta, err := casStore.GetOrCreate(tag, what, func(out io.Writer) error {
log.Printf("building %s for %q", what, ctx.Host.Name)
return create(out, ctx)
})
2018-06-12 10:09:47 +00:00
2018-12-10 10:59:24 +00:00
if err != nil {
return err
2018-06-12 10:09:47 +00:00
}
2018-12-10 10:59:24 +00:00
// serve it
http.ServeContent(w, r, what, meta.ModTime(), content)
return nil
}
2019-01-21 22:44:11 +00:00
var prevSSLConfig = "-"
2018-12-10 10:59:24 +00:00
func newRenderContext(host *localconfig.Host, cfg *localconfig.Config) (ctx *renderContext, err error) {
2019-01-21 22:44:11 +00:00
if prevSSLConfig != cfg.SSLConfig {
var sslCfg *cfsslconfig.Config
if len(cfg.SSLConfig) == 0 {
sslCfg = &cfsslconfig.Config{}
} else {
sslCfg, err = cfsslconfig.LoadConfig([]byte(cfg.SSLConfig))
if err != nil {
return
}
}
err = loadSecretData(sslCfg)
if err != nil {
return
}
prevSSLConfig = cfg.SSLConfig
}
2018-06-12 10:09:47 +00:00
return &renderContext{
2018-12-10 10:59:24 +00:00
SSLConfig: cfg.SSLConfig,
Host: host,
2018-07-03 07:35:52 +00:00
}, nil
2018-06-12 10:09:47 +00:00
}
func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
2018-12-10 10:59:24 +00:00
tmpl, err := template.New(ctx.Host.Name + "/config").
2019-01-21 22:44:11 +00:00
Funcs(ctx.templateFuncs()).
2018-12-10 10:59:24 +00:00
Parse(ctx.Host.Config)
2018-07-07 01:22:35 +00:00
2018-12-10 10:59:24 +00:00
if err != nil {
2018-07-07 01:22:35 +00:00
return
}
2018-06-20 03:01:10 +00:00
buf := bytes.NewBuffer(make([]byte, 0, 4096))
2018-12-10 10:59:24 +00:00
if err = tmpl.Execute(buf, nil); err != nil {
2018-06-20 03:01:10 +00:00
return
}
if secretData.Changed() {
err = secretData.Save()
if err != nil {
return
}
}
ba = buf.Bytes()
cfg = &config.Config{}
if err = yaml.Unmarshal(buf.Bytes(), cfg); err != nil {
return
}
return
}
2019-01-21 22:44:11 +00:00
func (ctx *renderContext) templateFuncs() map[string]interface{} {
2018-12-10 10:59:24 +00:00
getKeyCert := func(cluster, caName, name, profile, label, reqJson string) (kc *KeyCert, err error) {
2018-06-16 11:45:27 +00:00
certReq := &csr.CertificateRequest{
KeyRequest: csr.NewBasicKeyRequest(),
}
2018-12-10 10:59:24 +00:00
err = json.Unmarshal([]byte(reqJson), certReq)
2018-06-16 11:45:27 +00:00
if err != nil {
2018-12-10 10:59:24 +00:00
log.Print("CSR unmarshal failed on: ", reqJson)
2018-06-16 11:45:27 +00:00
return
}
2018-12-10 10:59:24 +00:00
return secretData.KeyCert(cluster, caName, name, profile, label, certReq)
2018-06-16 11:45:27 +00:00
}
2018-06-19 06:49:09 +00:00
asYaml := func(v interface{}) (string, error) {
ba, err := yaml.Marshal(v)
if err != nil {
return "", err
}
return string(ba), nil
}
2018-06-20 03:01:10 +00:00
return map[string]interface{}{
2019-04-13 09:36:58 +00:00
"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
},
2018-12-10 10:59:24 +00:00
"token": func(cluster, name string) (s string, err error) {
2018-06-19 06:49:09 +00:00
return secretData.Token(cluster, name)
},
2018-12-10 10:59:24 +00:00
"ca_key": func(cluster, name string) (s string, err error) {
2018-06-16 11:45:27 +00:00
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
s = string(ca.Key)
return
},
2018-12-10 10:59:24 +00:00
"ca_crt": func(cluster, name string) (s string, err error) {
2018-06-16 11:45:27 +00:00
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
s = string(ca.Cert)
return
},
2018-12-10 10:59:24 +00:00
"ca_dir": func(cluster, name string) (s string, err error) {
2018-06-19 06:49:09 +00:00
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
2018-12-10 10:59:24 +00:00
dir := "/etc/tls-ca/" + name
2018-06-19 06:49:09 +00:00
return asYaml([]config.FileDef{
{
Path: path.Join(dir, "ca.crt"),
Mode: 0644,
Content: string(ca.Cert),
},
{
Path: path.Join(dir, "ca.key"),
Mode: 0600,
Content: string(ca.Key),
},
})
},
2018-12-10 10:59:24 +00:00
"tls_key": func(cluster, caName, name, profile, label, reqJson string) (s string, err error) {
kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson)
2018-06-16 11:45:27 +00:00
if err != nil {
return
}
s = string(kc.Key)
return
},
2018-12-10 10:59:24 +00:00
"tls_crt": func(cluster, caName, name, profile, label, reqJson string) (s string, err error) {
kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson)
2018-06-16 11:45:27 +00:00
if err != nil {
return
}
s = string(kc.Cert)
return
},
2018-06-19 06:49:09 +00:00
2019-01-22 10:07:48 +00:00
"tls_dir": func(dir, cluster, caName, name, profile, label, reqJson string) (s string, err error) {
2018-12-10 10:59:24 +00:00
ca, err := secretData.CA(cluster, caName)
2018-06-19 06:49:09 +00:00
if err != nil {
return
}
2018-12-10 10:59:24 +00:00
kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson)
2018-06-19 06:49:09 +00:00
if err != nil {
return
}
return asYaml([]config.FileDef{
{
Path: path.Join(dir, "ca.crt"),
Mode: 0644,
Content: string(ca.Cert),
},
{
Path: path.Join(dir, "tls.crt"),
Mode: 0644,
Content: string(kc.Cert),
},
{
Path: path.Join(dir, "tls.key"),
Mode: 0600,
Content: string(kc.Key),
},
})
},
2019-12-03 10:03:20 +00:00
"ssh_host_keys": func(dir, cluster, host string) (s string, err error) {
pairs, err := secretData.SSHKeyPairs(cluster, host)
if err != nil {
return
}
files := make([]config.FileDef, 0, len(pairs)*2)
for _, pair := range pairs {
basePath := path.Join(dir, "ssh_host_"+pair.Type+"_key")
files = append(files, []config.FileDef{
{
Path: basePath,
Mode: 0600,
Content: pair.Private,
},
{
Path: basePath + ".pub",
Mode: 0644,
Content: pair.Public,
},
}...)
}
return asYaml(files)
},
2018-06-12 10:09:47 +00:00
}
}
func (ctx *renderContext) distFilePath(path ...string) string {
return filepath.Join(append([]string{*dataDir, "dist"}, path...)...)
}
func (ctx *renderContext) Tag() (string, error) {
h := sha256.New()
_, cfg, err := ctx.Config()
if err != nil {
return "", err
}
enc := yaml.NewEncoder(h)
for _, o := range []interface{}{cfg, ctx} {
if err := enc.Encode(o); err != nil {
return "", err
}
}
return hex.EncodeToString(h.Sum(nil)), nil
}
2018-06-20 03:01:10 +00:00
func asMap(v interface{}) map[string]interface{} {
ba, err := yaml.Marshal(v)
if err != nil {
panic(err) // shouldn't happen
}
result := make(map[string]interface{})
if err := yaml.Unmarshal(ba, result); err != nil {
2018-06-20 03:01:10 +00:00
panic(err) // shouldn't happen
}
return result
}