package main import ( "crypto/rand" "encoding/base32" "encoding/json" "fmt" "log" "path" "strconv" "strings" cfsslconfig "github.com/cloudflare/cfssl/config" "github.com/cloudflare/cfssl/csr" yaml "gopkg.in/yaml.v2" "novit.tech/direktil/pkg/bootstrapconfig" "novit.tech/direktil/pkg/config" ) func templateFuncs(sslCfg *cfsslconfig.Config) map[string]any { getKeyCert := func(cluster, caName, name, profile, label, reqJson string) (kc KeyCert, err error) { certReq := &csr.CertificateRequest{ KeyRequest: csr.NewKeyRequest(), } err = json.Unmarshal([]byte(reqJson), certReq) if err != nil { log.Print("CSR unmarshal failed on: ", reqJson) return } return getUsableKeyCert(cluster, caName, name, profile, label, certReq, sslCfg) } hash := func(plain, seed []byte, hashAlg string) (hashed string, err error) { switch hashAlg { case "sha512crypt": return sha512crypt(plain, seed) case "bootstrap": return bootstrapconfig.JoinSeedAndHash(seed, bootstrapconfig.PasswordHashFromSeed(seed, plain)), nil default: return "", fmt.Errorf("unknown hash alg: %q", hashAlg) } } return map[string]any{ "quote": strconv.Quote, "password": func(cluster, name, hashAlg string) (password string, err error) { key := cluster + "/" + name seed, err := seeds.GetOrCreate(key, func() (seed []byte, err error) { seed = make([]byte, 16) _, err = rand.Read(seed) return }) if err != nil { return "", fmt.Errorf("failed to get seed: %w", err) } password, err = clusterPasswords.GetOrCreate(key, func() (password string, err error) { raw := make([]byte, 10) _, err = rand.Read(raw) if err != nil { return "", fmt.Errorf("failed to generate password: %w", err) } password = strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(raw)) return }) if err != nil { return } return hash([]byte(password), seed, hashAlg) }, "token": getOrCreateClusterToken, "ca_key": func(cluster, name string) (s string, err error) { ca, err := getUsableClusterCA(cluster, name) if err != nil { return } s = string(ca.Key) return }, "ca_crt": func(cluster, name string) (s string, err error) { ca, err := getUsableClusterCA(cluster, name) if err != nil { return } s = string(ca.Cert) return }, "ca_dir": func(cluster, name string) (s string, err error) { ca, err := getUsableClusterCA(cluster, name) if err != nil { return } dir := "/etc/tls-ca/" + name 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), }, }) }, "tls_key": func(cluster, caName, name, profile, label, reqJson string) (s string, err error) { kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson) if err != nil { return } s = string(kc.Key) return }, "tls_crt": func(cluster, caName, name, profile, label, reqJson string) (s string, err error) { kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson) if err != nil { return } s = string(kc.Cert) return }, "tls_dir": func(dir, cluster, caName, name, profile, label, reqJson string) (s string, err error) { ca, err := getUsableClusterCA(cluster, caName) if err != nil { return } kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson) 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), }, }) }, } } func asYaml(v interface{}) (string, error) { ba, err := yaml.Marshal(v) if err != nil { return "", err } return string(ba), nil }